feat: wire chat UI to Responses API with streaming

Add ChatController that proxies POST /api/chat to the local Responses API
(localhost:8317/v1/responses) with SSE streaming. Client reads tokens via
SetBrowserResponseStreamingEnabled and renders them incrementally. Includes
thinking indicator, input disabled during streaming, and error handling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
local
2026-04-04 01:54:28 +01:00
parent 1614a61617
commit 00e7df2802
15 changed files with 500 additions and 69 deletions

View File

@@ -0,0 +1,29 @@
## 1. Shared Models
- [x] 1.1 Create ChatRequest.cs in ChatAgent.Shared/Models with a Messages list property
## 2. API Backend
- [x] 2.1 Add appsettings.json to ChatAgent.Api with ResponsesApi:BaseUrl and ResponsesApi:Model
- [x] 2.2 Register an HttpClient for the Responses API proxy in Api Program.cs
- [x] 2.3 Create ChatController with POST /api/chat that proxies to the Responses API with streaming
- [x] 2.4 Parse Responses API SSE stream, extract response.output_text.delta events, re-emit as simplified SSE to client
## 3. Client Streaming
- [x] 3.1 Add a streaming SendChatAsync method to ChatApiClient that uses SetBrowserResponseStreamingEnabled and HttpCompletionOption.ResponseHeadersRead
- [x] 3.2 Parse the simplified SSE stream line-by-line, yielding text deltas
## 4. Chat Page Updates
- [x] 4.1 Replace hardcoded response in Chat.razor with a call to ChatApiClient.SendChatAsync
- [x] 4.2 Append tokens to the assistant message incrementally with StateHasChanged after each delta
- [x] 4.3 Add a thinking indicator shown until the first token arrives
- [x] 4.4 Disable input field and send button while streaming is in progress
- [x] 4.5 Handle errors — display error message if API call fails
- [x] 4.6 Auto-scroll during streaming (not just at the end)
## 5. Verify
- [x] 5.1 Run dotnet build to confirm no errors
- [ ] 5.2 Manually verify: send a message, see streaming response from Claude

View File

@@ -1,29 +0,0 @@
## 1. Shared Models
- [ ] 1.1 Create ChatRequest.cs in ChatAgent.Shared/Models with a Messages list property
## 2. API Backend
- [ ] 2.1 Add appsettings.json to ChatAgent.Api with ResponsesApi:BaseUrl and ResponsesApi:Model
- [ ] 2.2 Register an HttpClient for the Responses API proxy in Api Program.cs
- [ ] 2.3 Create ChatController with POST /api/chat that proxies to the Responses API with streaming
- [ ] 2.4 Parse Responses API SSE stream, extract response.output_text.delta events, re-emit as simplified SSE to client
## 3. Client Streaming
- [ ] 3.1 Add a streaming SendChatAsync method to ChatApiClient that uses SetBrowserResponseStreamingEnabled and HttpCompletionOption.ResponseHeadersRead
- [ ] 3.2 Parse the simplified SSE stream line-by-line, yielding text deltas
## 4. Chat Page Updates
- [ ] 4.1 Replace hardcoded response in Chat.razor with a call to ChatApiClient.SendChatAsync
- [ ] 4.2 Append tokens to the assistant message incrementally with StateHasChanged after each delta
- [ ] 4.3 Add a thinking indicator shown until the first token arrives
- [ ] 4.4 Disable input field and send button while streaming is in progress
- [ ] 4.5 Handle errors — display error message if API call fails
- [ ] 4.6 Auto-scroll during streaming (not just at the end)
## 5. Verify
- [ ] 5.1 Run dotnet build to confirm no errors
- [ ] 5.2 Manually verify: send a message, see streaming response from Claude

View File

@@ -0,0 +1,55 @@
## Purpose
Define the streaming AI response pipeline — backend proxy to the Responses API, SSE delivery to the WASM client, configuration, and error handling.
## Requirements
### Requirement: Chat endpoint proxies to Responses API
The API backend SHALL expose `POST /api/chat` that accepts a list of messages and proxies the request to the local Responses API at a configurable base URL using the `POST /v1/responses` endpoint.
#### Scenario: Successful proxy request
- **WHEN** the client sends a POST to `/api/chat` with a message list
- **THEN** the API forwards the messages to the Responses API with the configured model and returns the response
### Requirement: Streaming response delivery
The API backend SHALL stream the Responses API's SSE events back to the WASM client as `text/event-stream`, forwarding `response.output_text.delta` events so the client can render tokens incrementally.
#### Scenario: Tokens stream to client
- **WHEN** the Responses API emits `response.output_text.delta` events
- **THEN** the backend forwards each delta as an SSE event to the client containing the text fragment
#### Scenario: Stream completes
- **WHEN** the Responses API emits `response.completed`
- **THEN** the backend signals stream completion to the client
### Requirement: Configurable proxy target
The Responses API base URL and model name SHALL be configurable via `appsettings.json` in the API project, not hardcoded.
#### Scenario: Configuration read at startup
- **WHEN** the API starts
- **THEN** it reads `ResponsesApi:BaseUrl` and `ResponsesApi:Model` from configuration
### Requirement: Client streams from backend
The WASM client SHALL call `POST /api/chat` with `SetBrowserResponseStreamingEnabled(true)` and `HttpCompletionOption.ResponseHeadersRead`, then iterate the SSE stream to update the UI token by token.
#### Scenario: Client reads streaming response
- **WHEN** the client sends a chat request
- **THEN** it reads the response stream incrementally and appends each text delta to the assistant message in real time
### Requirement: Error propagation
If the Responses API returns an error or is unreachable, the API backend SHALL return an appropriate HTTP error status and the client SHALL display the error to the user.
#### Scenario: Proxy unreachable
- **WHEN** the Responses API is not running
- **THEN** the client displays an error message instead of an assistant response

View File

@@ -42,14 +42,33 @@ The chat page SHALL provide a text input area at the bottom of the page where th
- **WHEN** the user attempts to send an empty or whitespace-only message
- **THEN** nothing is sent and no message is added
### Requirement: Hardcoded response
#### Scenario: Input disabled during streaming
In this phase, the assistant SHALL reply with a hardcoded message to every user input. This stubs the AI integration point for future phases.
- **WHEN** the assistant is currently streaming a response
- **THEN** the input field and send button are disabled until streaming completes
#### Scenario: Bot replies to any input
### Requirement: Thinking indicator
The chat page SHALL show a visual indicator while waiting for the first token from the assistant.
#### Scenario: Indicator shown during wait
- **WHEN** the user sends a message and the assistant has not yet started streaming
- **THEN** a thinking indicator (e.g., animated dots) is shown in the assistant message area
#### Scenario: Indicator replaced by content
- **WHEN** the first token arrives from the stream
- **THEN** the thinking indicator is replaced by the streamed text
### Requirement: Streaming AI response
The assistant SHALL reply with a real AI response streamed from the backend API. Tokens appear incrementally as they arrive.
#### Scenario: Bot replies with streamed AI response
- **WHEN** the user sends any message
- **THEN** the assistant replies with a hardcoded response (e.g., "This is a placeholder response. AI integration coming soon!")
- **THEN** the assistant message appears and grows token by token as the stream delivers text
### Requirement: Auto-scroll