Files
ss-tools/specs/033-gradio-agent-chat/contracts/ux/agent-chat-ux.md
2026-06-09 09:43:34 +03:00

89 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#region AgentChat.Ux [C:3] [TYPE ADR] [SEMANTICS ux,agent-chat,panel,native-stack]
@defgroup Ux UX contract — Gradio submit() + LangChain v1 astream_events(). Structured JSON metadata (ChatMessage.metadata). No REST, no WebSocket, no follower.
## FSM
```
idle ──[submit("/chat")]──→ streaming ──[metadata.type: stream_token]──→ (tokens appear)
streaming ──[metadata.type: tool_start]──→ (tool card + spinner)
streaming ──[metadata.type: tool_end]──→ (tool done, checkmark)
streaming ──[metadata.type: tool_error]──→ (tool error, cross)
streaming ──[metadata.type: confirm_required]──→ awaiting_confirmation
awaiting_confirmation ──[2nd submit(__resume__,"confirm")]──→ streaming (confirm_resolved)
awaiting_confirmation ──[2nd submit(__resume__,"deny")]──→ idle (confirm_resolved: denied)
awaiting_confirmation ──[timeout 5min]──→ idle (expired)
streaming ──[final chunk]──→ idle
streaming ──[submission.cancel()]──→ idle (partial)
streaming ──[metadata.type: error]──→ error
idle ──[load history]──→ loading_history ──[success]──→ idle
loading_history ──[failure]──→ error
idle ──[connect fail]──→ disconnected ──[retry 5×5s]──→ idle
disconnected ──[retry exhausted]──→ disconnected_permanent
```
## State Mappings
| @UX_STATE | Visual | ARIA | User Can |
|-----------|--------|------|----------|
| idle (with msgs) | Input enabled, Send active, Stop hidden. Connection dot green. | — | Send, switch/delete convos, expand to /agent |
| idle (new convo) | Welcome card + examples chips (Gradio `examples`). | `aria-label="Новый диалог"` | Click chip, type |
| streaming | Tokens appear in real time. Tool cards inline. Send→Stop (destructive). Input locked. | `aria-live="polite" aria-busy="true"` | Stop, expand tool cards |
| awaiting_confirmation | Streaming pauses. Warning card: `bg-warning-light border-warning-DEFAULT`. Prompt text + Confirm/Deny buttons. Timer 5:00→0:00. | `role="alertdialog"` | Confirm, Deny |
| awaiting_confirmation (expired) | Card stays. Buttons disabled. Timer=0. "Повторить запрос?" | — | Retry |
| error | Card: `bg-destructive-light border-destructive-ring`. Error + Retry. Partial text preserved. | `role="alert"` | Retry, new message |
| disconnected | Dot red. Banner: "Агент недоступен. Переподключение через Xс..." | `role="alert"` | Read history, manual reconnect |
| disconnected_permanent | Dot red. Manual reconnect button only. | `role="alert"` | Manual reconnect |
| loading_history | Skeleton cards (3-5, `animate-pulse bg-surface-muted`). Input locked. | `aria-busy="true"` | Wait |
## Feedback
| Trigger | Feedback |
|---------|----------|
| `metadata.type: stream_token` | Append token to assistant message |
| `metadata.type: tool_start` | Tool card: name (mono) + spinner |
| `metadata.type: tool_end` | Spinner→checkmark, result in tooltip |
| `metadata.type: tool_error` | Spinner→cross, error in tooltip |
| `metadata.type: confirm_required` | Confirmation card + timer, Confirm/Deny |
| `metadata.type: confirm_resolved(result=confirmed)` | Card collapses, streaming resumes |
| `metadata.type: confirm_resolved(result=denied)` | Card collapses, agent responds |
| Stream complete | Stop→Send, input enabled |
| Connection lost | Dot red. Banner. Retry countdown |
| Connection restored | Dot green. Banner dismissed |
## Recovery
| From | Action | To |
|------|--------|-----|
| error | Retry (re-sends last msg) | streaming |
| disconnected | Auto-retry 5×5s or manual | idle |
| awaiting_confirmation (expired) | "Повторить запрос" | streaming |
| streaming (cancelled) | Type new msg | idle→streaming |
| empty (no convos) | Click chip or type | streaming |
## Reactivity
- `AgentChat.Model` atoms → component props → DOM
- `@gradio/client` `submit()``for await (event)``model._onStreamData()``$state` update
- No `$effect` for data loading — all via stream events
- `assistantChatStore.conversationId` → model reads (read-only)
## UX Tests
| @UX_TEST | Given | When | Then |
|----------|-------|------|------|
| Happy path | idle (new) | Type "привет", Send | streaming, tokens appear, idle |
| Tool call | streaming | Agent calls tool | Card with spinner→checkmark |
| Dangerous confirm | streaming | Agent wants deploy | "⏸️" card, confirm_id, timer |
| Confirm accepted | awaiting_confirmation | 2nd submit(__resume__) | "▶️", streaming resumes |
| Confirm denied | awaiting_confirmation | 2nd submit(deny) | "⏹️", idle |
| Stream cancel | streaming | submission.cancel() | idle, partial text |
| LLM error | streaming | LLM fails | "Ошибка: ..." + retry |
| Gradio disconnect | idle | Gradio dies | Dot red, 5×5s retry |
| Multi-tab gate | idle | Tab 2 opens same convo | Tab 2: send rejected |
| Empty convo | idle (new) | Load | Examples chips visible |
| History load error | idle | API fails | Error + retry |
#endregion AgentChat.Ux