diff --git a/openspec/changes/multi-turn-conversations/.openspec.yaml b/openspec/changes/multi-turn-conversations/.openspec.yaml new file mode 100644 index 0000000..c54c137 --- /dev/null +++ b/openspec/changes/multi-turn-conversations/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-04-04 diff --git a/openspec/changes/multi-turn-conversations/design.md b/openspec/changes/multi-turn-conversations/design.md new file mode 100644 index 0000000..9be1e58 --- /dev/null +++ b/openspec/changes/multi-turn-conversations/design.md @@ -0,0 +1,30 @@ +## Context + +Chat.razor currently constructs a `ChatRequest` with only the latest user message (line 161-164). The backend and Responses API already support multi-message input — no server changes needed. This is purely a client-side change. + +## Goals / Non-Goals + +**Goals:** +- Send full conversation history with each request for multi-turn context +- Add a "New Chat" button to reset the session + +**Non-Goals:** +- Persistent conversation storage (explicitly deferred) +- Multiple conversation tabs/sidebar +- Token limit management or history truncation (small conversations for now) + +## Decisions + +### Decision 1: Send full _messages list minus the empty placeholder + +When building the `ChatRequest`, include all messages from `_messages` except the last one (which is the empty assistant placeholder waiting for streaming). This gives the AI the full conversation context. + +### Decision 2: New Chat button in the AppBar + +Place a "New Chat" icon button in the `MudAppBar` (in MainLayout.razor or Chat.razor). Clicking it clears `_messages` and resets to the empty state. Disabled during streaming. + +Alternative considered: putting it in the input area. Rejected — the AppBar is more natural and matches ChatGPT/Gemini placement. + +## Risks / Trade-offs + +- [No token limit management] → For long conversations, the full history could exceed the model's context window. Acceptable for now; truncation can be added later. diff --git a/openspec/changes/multi-turn-conversations/proposal.md b/openspec/changes/multi-turn-conversations/proposal.md new file mode 100644 index 0000000..d003e1d --- /dev/null +++ b/openspec/changes/multi-turn-conversations/proposal.md @@ -0,0 +1,21 @@ +## Why + +Each chat request currently sends only the latest user message, so the AI has no memory of previous exchanges within the same session. Users expect the assistant to remember context from earlier in the conversation, like ChatGPT/Gemini do. + +## What Changes + +- Send the full conversation history (all prior user and assistant messages) with each API request instead of just the latest user message +- The backend already forwards all messages in `ChatRequest.Messages` to the Responses API — no backend changes needed +- Add a "New Chat" button to clear the conversation and start fresh + +## Capabilities + +### New Capabilities + + +### Modified Capabilities +- `chat-ui`: Send full message history with each request; add a "New Chat" button to reset the conversation + +## Impact + +- `src/ChatAgent.Client/Pages/Chat.razor`: Change request construction to include full history; add new chat button diff --git a/openspec/changes/multi-turn-conversations/specs/chat-ui/spec.md b/openspec/changes/multi-turn-conversations/specs/chat-ui/spec.md new file mode 100644 index 0000000..244bb05 --- /dev/null +++ b/openspec/changes/multi-turn-conversations/specs/chat-ui/spec.md @@ -0,0 +1,31 @@ +## MODIFIED Requirements + +### Requirement: Streaming AI response + +The assistant SHALL reply with a real AI response streamed from the backend API, using the full conversation history as context. Tokens appear incrementally as they arrive. + +#### Scenario: Bot replies with streamed AI response + +- **WHEN** the user sends any message +- **THEN** the assistant message appears and grows token by token as the stream delivers text + +#### Scenario: Full history sent with each request + +- **WHEN** the user sends a message after prior exchanges +- **THEN** all previous user and assistant messages are included in the API request so the AI has conversational context + +## ADDED Requirements + +### Requirement: New chat button + +The chat page SHALL provide a button to clear the current conversation and start a new one. + +#### Scenario: User starts a new chat + +- **WHEN** the user clicks the "New Chat" button +- **THEN** all messages are cleared and the empty state is shown + +#### Scenario: New chat button disabled during streaming + +- **WHEN** the assistant is currently streaming a response +- **THEN** the "New Chat" button is disabled diff --git a/openspec/changes/multi-turn-conversations/tasks.md b/openspec/changes/multi-turn-conversations/tasks.md new file mode 100644 index 0000000..36ec583 --- /dev/null +++ b/openspec/changes/multi-turn-conversations/tasks.md @@ -0,0 +1,13 @@ +## 1. Multi-turn History + +- [x] 1.1 Change ChatRequest construction in Chat.razor to send all prior messages (excluding the empty assistant placeholder) instead of just the latest user message + +## 2. New Chat Button + +- [x] 2.1 Add a "New Chat" icon button to the AppBar or chat page that clears _messages +- [x] 2.2 Disable the "New Chat" button while streaming is in progress + +## 3. Verify + +- [x] 3.1 Run dotnet build to confirm no errors +- [x] 3.2 Run dotnet test to confirm existing tests still pass diff --git a/src/ChatAgent.Client/Pages/Chat.razor b/src/ChatAgent.Client/Pages/Chat.razor index 2fbf8b7..a8f2d29 100644 --- a/src/ChatAgent.Client/Pages/Chat.razor +++ b/src/ChatAgent.Client/Pages/Chat.razor @@ -75,8 +75,21 @@ @* Input area: pinned at the bottom of the chat container. - Disabled attribute prevents interaction while the assistant is streaming. *@ + Disabled attribute prevents interaction while the assistant is streaming. + The "New Chat" button clears the conversation to start fresh. *@
+ @if (_messages.Count > 0) + { +
+ + New Chat + +
+ } + /// Clears all messages to start a new conversation. + /// + private void NewChat() + { + _messages.Clear(); + _userInput = string.Empty; + } + /// /// Handles the Enter key press to submit the message. /// @@ -154,14 +176,16 @@ try { - // Build the request with the current user message. - // Future phases will include full conversation history for multi-turn. + // Build the request with the full conversation history for multi-turn context. + // We include all messages except the last one (the empty assistant placeholder + // that is waiting to be filled by the stream). This gives the AI the full + // conversation so it can reference prior exchanges. var request = new ChatRequest { - Messages = new List - { - new ChatMessage { Role = "user", Content = userText } - } + Messages = _messages + .Where(m => !string.IsNullOrEmpty(m.Content)) + .Select(m => new ChatMessage { Role = m.Role, Content = m.Content, Timestamp = m.Timestamp }) + .ToList() }; // Stream tokens from the API. IAsyncEnumerable yields each text delta diff --git a/src/ChatAgent.Client/Pages/Chat.razor.css b/src/ChatAgent.Client/Pages/Chat.razor.css index 11ba293..44a6fe5 100644 --- a/src/ChatAgent.Client/Pages/Chat.razor.css +++ b/src/ChatAgent.Client/Pages/Chat.razor.css @@ -77,3 +77,10 @@ border-top: 1px solid var(--mud-palette-lines-default); background-color: var(--mud-palette-background); } + +/* Action row above the text input (New Chat button) */ +.input-actions { + display: flex; + justify-content: flex-end; + margin-bottom: 0.25rem; +}