Overhaul extraction pipeline with new TradeItem model, conversation flow, and dedicated extraction endpoint. Add sidebar navigation with NavMenu component and landing page. Introduce few-shot prompting service and tests. Add prompt settings and email upload specs. Update OpenSpec tooling with improved export-spec and extract-feature commands. Archive completed changes and export full specs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.2 KiB
8.2 KiB
OpenSpec Artifacts for: AI Chat Agent with Streaming & Rich Text
On the target machine with OpenSpec + Claude Code:
- Create
openspec/config.yaml(see below) - Save
proposal.mdtoopenspec/changes/chat-agent-full/proposal.md - Save
design.mdtoopenspec/changes/chat-agent-full/design.md - Save
tasks.mdtoopenspec/changes/chat-agent-full/tasks.md - Run
/opsx:apply chat-agent-full - Use the portable spec (
chat-agent-full-spec.md) as reference if the AI needs detail
config.yaml
Adapt to your project name and constraints, save as openspec/config.yaml:
schema: spec-driven
context: |
<Your Project Name> — <one-line description>.
Tech stack: .NET 9, C# 13, Blazor WASM, ASP.NET Core Web API.
Key libraries: MudBlazor 9.2.0, Semantic Kernel 1.74.0, Markdig 1.1.1.
<Any target-specific constraints>.
proposal.md
Integration Rule: This feature is additive only. DO NOT modify existing files, components, services, or patterns. If the target already has an equivalent service (HTTP wrapper, markdown renderer, etc.), use the existing one. If a task conflicts with existing code, stop and notify the user before proceeding. Existing applicationX code takes precedence in all cases.
Build a complete AI chat interface on an existing MudBlazor app:
- Chat page with streaming AI responses via SSE
- Backend using Semantic Kernel with tool calling (extraction plugin)
- Multi-turn conversation support
- Rich text rendering (Markdig + HTML sanitization)
- Full xUnit test coverage
Assumes MudBlazor is already installed and configured.
design.md
Integration: additive only
- This feature is a GUEST in the target application — add new files, never modify existing ones
- If the target already has a service that overlaps (e.g., its own HttpClient wrapper, markdown renderer, error handling), use theirs — do not create a duplicate
- Conform to the target's code style, naming conventions, and DI patterns
- If a task would require modifying existing code, stop and notify the user — the user decides whether to skip, adapt, or redesign
- Only touch existing files to add a nav link or a DI registration line — never restructure
Streaming: SSE over SignalR
- SSE is simpler — one-directional text stream over HTTP, no WebSocket negotiation
- Blazor WASM supports streaming via
SetBrowserResponseStreamingEnabled(true)+ResponseHeadersRead - Client parses line-by-line:
data: {"text":"..."}\n\nfor deltas,data: [DONE]\n\nfor end,data: {"error":"..."}for errors - CRITICAL SSE loop pattern — use
while ((line = await reader.ReadLineAsync()) != null). Do NOT usereader.EndOfStream— it does a synchronous peek that Blazor WASM's fetch-backed stream rejects at runtime
AI orchestration: Semantic Kernel
- SK provides chat completion connectors, plugin system, and auto function calling
- OpenAI connector works with any OpenAI-compatible endpoint (e.g. local proxy) — base URL must include
/v1 - ExtractionPlugin imported per-request via
ImportPluginFromObject(not at Kernel build, avoids plugin state leaking across requests) FunctionChoiceBehavior.Auto()lets the LLM autonomously call tools and retry
Client architecture
- Typed HttpClient (
ChatApiClient) — centralizes API paths, easy to mock for tests, IHttpClientFactory manages handler lifetime - MarkdownService (singleton) — Markdig pipeline is immutable/thread-safe; two-pass sanitization (regex strip script/style, then tag allowlist) rather than a sanitization library to keep dependencies minimal
- Rendered HTML cache —
Dictionary<ChatMessage, string>prevents re-running Markdig on all completed messages during everyStateHasChangedwhile streaming
Chat UI layout — "Sales Assistant" page
- Page renders inside MudMainContent — do NOT add a separate AppBar or layout
- Route:
/sales-assistant, add MudNavLink in existing sidebar NavMenu - Flexbox column: message-list (flex 1, scrollable) + input-area (pinned bottom)
- MudPaper bubbles: user right-aligned (primary bg), assistant left-aligned (surface bg)
::deepCSS selectors needed for MudBlazor child components and MarkupString-injected HTML- Auto-scroll via JS interop (
scrollTop = scrollHeight) — no built-in Blazor scroll API - Container height:
calc(100vh - 64px)— CRC app uses regular MudAppBar (~64px), not Dense. MudDrawer width is handled by MudLayout automatically — no horizontal calc needed
Multi-turn
- Full conversation history sent with each request (all messages with non-empty content)
- Empty assistant placeholder excluded to avoid confusing the LLM
Test strategy
- API:
WebApplicationFactory<Program>with mockedIChatCompletionService— tests SSE contract without hitting real LLM - Client: mock
HttpMessageHandlerwith canned SSE response streams - Requires
public partial class Program { }in API for test factory access
tasks.md
Phase 1: Shared Models
- Create
ChatMessage.csin Shared/Models — Role (string), Content (string), Timestamp (DateTime) - Create
ChatRequest.cs— List Messages - Create
HealthResponse.cs— Status (string), Timestamp (DateTime) - Create
ExtractedFields.cs— Client?, Project?, Hours?, Rate?, Currency?, Date? (required); Description?, PoNumber? (optional) - Create
ValidationResult.cs— IsValid (bool), Errors (List)
Phase 2: API Backend
- Add NuGet: Microsoft.SemanticKernel 1.74.0, Connectors.OpenAI 1.74.0
- Create
ExtractionPlugin.csin Plugins/ — [KernelFunction] validates extracted fields JSON - Configure Program.cs: AddControllers, AddOpenAIChatCompletion (base URL must include /v1), AddKernel, ExtractionPlugin singleton, CORS for client origin
- Create
HealthController.cs— GET /api/health returns HealthResponse - Create
ChatController.cs— POST /api/chat, streams via SK GetStreamingChatMessageContentsAsync, SSE format: data: {"text":"..."}\n\n and data: [DONE]\n\n - Add
public partial class Program { }for test accessibility - Add appsettings.json: ResponsesApi:BaseUrl, ResponsesApi:Model
Phase 3: Client Services
- Add NuGet: Microsoft.Extensions.Http 9.0.4, Markdig 1.1.1
- Create
ChatApiClient.cs— typed HttpClient with GetHealthAsync and SendChatStreamingAsync (IAsyncEnumerable). Use SetBrowserResponseStreamingEnabled(true) + ResponseHeadersRead. Parse SSE with ReadLineAsync null check (not EndOfStream). - Create
MarkdownService.cs— Markdig with UseAdvancedExtensions, two-pass HTML sanitization (script/style strip, tag allowlist) - Register in Program.cs: AddMudServices, AddSingleton, AddHttpClient
- Add wwwroot/appsettings.json with ApiBaseUrl_Http and ApiBaseUrl_Https
Phase 4: Chat UI
- Create
SalesAssistant.razorat route "/sales-assistant" — message list with MudPaper bubbles, MudTextField with send icon, Enter key handler - Add MudNavLink to existing sidebar NavMenu: "Sales Assistant", icon SmartToy, href /sales-assistant
- Streaming: append tokens to assistant message, StateHasChanged per token, auto-scroll via JS interop
- Assistant messages: render as (MarkupString) from MarkdownService inside .markdown-body div, with rendered HTML cache
- User messages: plain MudText
- Thinking indicator: MudProgressCircular when assistant content empty during streaming
- New Chat button: clears messages, visible when conversation exists, disabled during streaming
- Multi-turn: send all non-empty messages with each request
- Create
SalesAssistant.razor.css— flex layout withcalc(100vh - 64px)height (64px = regular AppBar), message bubbles (user right/primary, assistant left/surface), markdown styles (code blocks, tables, blockquotes, headings, links)
Phase 5: Tests
- Create xUnit test project for API with WebApplicationFactory, mock IChatCompletionService
- Test HealthController (200 + valid response), ChatController (SSE streaming, error handling), ExtractionPlugin (validation logic)
- Create xUnit test project for Client with mock HttpMessageHandler
- Test ChatApiClient (delta parsing, error events), MarkdownService (rendering + sanitization)
- Verify:
dotnet test