feat: add extraction schema, sidebar nav, few-shot prompting, and prompt settings
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>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-04-06
|
||||
@@ -0,0 +1,63 @@
|
||||
## Context
|
||||
|
||||
The extraction endpoint (`POST /api/chat/extract`) and few-shot prompting infrastructure exist on the backend. The client needs a way to trigger extraction by uploading email files and then handle the multi-turn extraction conversation (disambiguation, result presentation).
|
||||
|
||||
The current Chat.razor has a text input with send button and Enter key handling. The extraction flow adds a new input modality (file drop) and conversation mode tracking.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Enable email file upload via drag-and-drop and file picker
|
||||
- Route uploaded emails to the extraction endpoint
|
||||
- Support disambiguation follow-up within the same conversation
|
||||
- Present extraction results clearly in the chat stream
|
||||
|
||||
**Non-Goals:**
|
||||
- .msg file parsing (MVP accepts .html only — users save emails as HTML first)
|
||||
- Structured result UI (tables, editable fields) — the agent streams formatted text/markdown
|
||||
- Offline/batch extraction of multiple emails
|
||||
- Email preview or parsing on the client before sending to the API
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Drag-and-drop on the message area, not a separate upload zone
|
||||
|
||||
**Decision:** The entire chat message area (`.message-list`) acts as the drop target, with visual feedback when a file is dragged over.
|
||||
|
||||
**Why:** Drag-and-drop onto the conversation area is the natural gesture — it mirrors how you'd "give" a document to someone in a chat. A separate upload widget adds visual clutter.
|
||||
|
||||
**Alternative considered:** Dedicated upload zone above the input area. Rejected — takes permanent screen space for an occasional action.
|
||||
|
||||
### 2. HTML files only for MVP
|
||||
|
||||
**Decision:** Accept `.html` files only. Do not support `.msg` parsing in this change.
|
||||
|
||||
**Why:** .msg is a proprietary Microsoft format requiring either a server-side library (like MsgReader) or a JS parser. HTML is what Outlook "Save As" produces and is trivially read via the File API. Supporting .msg can be a follow-up change.
|
||||
|
||||
### 3. Conversation mode tracking with `_isExtractionMode`
|
||||
|
||||
**Decision:** Add a boolean `_isExtractionMode` flag to Chat.razor. When an email is uploaded, set it to `true`. All subsequent `SendMessage()` calls route to the extraction endpoint (passing the original email HTML + growing message list). "New Chat" resets to `false`.
|
||||
|
||||
**Why:** After initial extraction, the user needs to reply to disambiguation questions. Those replies must go to the extraction endpoint with full context, not the general chat endpoint. The mode flag is the simplest routing mechanism.
|
||||
|
||||
**Alternative considered:** Separate extraction page/component. Rejected — breaks the conversational flow and duplicates the chat UI.
|
||||
|
||||
### 4. File reading via Blazor JS interop
|
||||
|
||||
**Decision:** Use `InputFile` component or JavaScript interop with the File API to read the dropped file's text content. Send the HTML string to the extraction endpoint.
|
||||
|
||||
**Why:** Blazor WASM has `InputFile` for file picker but drag-and-drop requires JS interop for the `drop` event. We need both: `InputFile` for the button, JS interop for drag-and-drop.
|
||||
|
||||
### 5. Agent streams results as markdown, no special result UI
|
||||
|
||||
**Decision:** The extraction agent's response (including the final JSON table) streams as markdown rendered by the existing rich text display. No special result component.
|
||||
|
||||
**Why:** The rich text rendering already handles tables, code blocks, and formatted output. The agent naturally presents results as a markdown table. A structured result component adds complexity without clear UX benefit at this stage.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Large email files]** → HTML emails with embedded images can be large. Mitigation: the API receives the HTML string only — embedded images are base64 in the HTML and the LLM will ignore them. If size becomes an issue, strip image tags client-side before sending.
|
||||
|
||||
**[Mode confusion]** → User may not realize they're in extraction mode. Mitigation: show a visual indicator (e.g., chip or banner) when `_isExtractionMode` is true, and include "New Chat" to reset.
|
||||
|
||||
**[Drop event handling in Blazor]** → Blazor's built-in event handling for drag-and-drop is limited. Mitigation: use a small JS interop function for the drop handler that reads the file and calls back into .NET.
|
||||
@@ -0,0 +1,29 @@
|
||||
## Why
|
||||
|
||||
The extraction agent and few-shot prompting infrastructure exist on the backend, but the chat UI has no way to send emails to the extraction endpoint. Users need to drag-and-drop or upload email files (.html) to trigger extraction. The client must route email uploads to `POST /api/chat/extract` and handle the conversational extraction flow, including disambiguation questions from the agent and result presentation.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Add drag-and-drop zone** to `Chat.razor` that accepts email files (.html)
|
||||
- **Add file picker button** as an alternative upload method
|
||||
- **Route uploaded emails** to the extraction endpoint via `ChatApiClient.SendExtractionStreamingAsync()`
|
||||
- **Handle extraction conversation flow** — initial extraction streams in, user can reply to disambiguation questions, follow-ups continue via the extraction endpoint
|
||||
- **Present extraction results** — the agent's streamed response includes formatted output; optionally add a "Copy JSON" action
|
||||
- **Track conversation mode** — after an email upload, subsequent messages route to the extraction endpoint until "New Chat" resets to general mode
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `email-upload`: Defines the drag-and-drop upload zone, file handling, visual feedback, and supported formats
|
||||
- `extraction-conversation-flow`: Defines the client-side conversation mode tracking, routing between general chat and extraction, and result presentation
|
||||
|
||||
### Modified Capabilities
|
||||
- `chat-ui`: Add the upload zone to the chat input area and track conversation mode (general vs extraction)
|
||||
|
||||
## Impact
|
||||
|
||||
- **UI changes**: New drop zone and upload button in Chat.razor, visual feedback during drag-over
|
||||
- **Chat.razor.css**: Styling for drop zone states (idle, drag-over, uploading)
|
||||
- **ChatApiClient**: Already has `SendExtractionStreamingAsync` from the previous change — this change wires it to the UI
|
||||
- **Conversation state**: New `_isExtractionMode` flag in Chat.razor to route messages correctly
|
||||
- **Depends on**: `update-extraction-schema` and `few-shot-prompt-infrastructure` (extraction endpoint must exist)
|
||||
@@ -0,0 +1,30 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Message input
|
||||
|
||||
The chat page SHALL provide a text input area at the bottom of the page where the user can type and submit messages. The input area SHALL also include a file upload button for triggering email extraction.
|
||||
|
||||
#### Scenario: Submit via button
|
||||
|
||||
- **WHEN** the user types text and clicks the send button
|
||||
- **THEN** the message is added to the conversation and the input is cleared
|
||||
|
||||
#### Scenario: Submit via Enter key
|
||||
|
||||
- **WHEN** the user types text and presses Enter
|
||||
- **THEN** the message is submitted (same as clicking send)
|
||||
|
||||
#### Scenario: Empty input blocked
|
||||
|
||||
- **WHEN** the user attempts to send an empty or whitespace-only message
|
||||
- **THEN** nothing is sent and no message is added
|
||||
|
||||
#### Scenario: Input disabled during streaming
|
||||
|
||||
- **WHEN** the assistant is currently streaming a response
|
||||
- **THEN** the input field, send button, and upload button are disabled until streaming completes
|
||||
|
||||
#### Scenario: Upload button opens file picker
|
||||
|
||||
- **WHEN** the user clicks the upload button in the input area
|
||||
- **THEN** a file picker dialog opens filtered to .html files
|
||||
@@ -0,0 +1,38 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Drag-and-drop email upload
|
||||
|
||||
The chat message area SHALL accept files dragged from the desktop or file explorer. When a supported file is dropped, the client SHALL read the file content and send it to the extraction endpoint.
|
||||
|
||||
#### Scenario: Drag HTML file onto chat
|
||||
|
||||
- **WHEN** the user drags an .html file over the message area
|
||||
- **THEN** a visual drop indicator appears (e.g., highlighted border, overlay text "Drop email here")
|
||||
|
||||
#### Scenario: Drop HTML file triggers extraction
|
||||
|
||||
- **WHEN** the user drops an .html file onto the message area
|
||||
- **THEN** the client reads the HTML content, sends it to `POST /api/chat/extract`, and streams the extraction response in the chat
|
||||
|
||||
#### Scenario: Unsupported file type rejected
|
||||
|
||||
- **WHEN** the user drops a non-.html file (e.g., .pdf, .docx)
|
||||
- **THEN** the client shows a brief error message indicating only .html files are supported
|
||||
|
||||
### Requirement: File picker upload button
|
||||
|
||||
The chat input area SHALL include an upload button (e.g., attachment icon) that opens a file picker dialog for selecting .html email files.
|
||||
|
||||
#### Scenario: Upload via file picker
|
||||
|
||||
- **WHEN** the user clicks the upload button and selects an .html file
|
||||
- **THEN** the client reads the HTML content and sends it to the extraction endpoint, same as drag-and-drop
|
||||
|
||||
### Requirement: Upload disabled during streaming
|
||||
|
||||
The upload zone and file picker SHALL be disabled while a response is streaming.
|
||||
|
||||
#### Scenario: Drop during streaming
|
||||
|
||||
- **WHEN** the user attempts to drop a file while the assistant is streaming
|
||||
- **THEN** the drop is ignored and no extraction request is sent
|
||||
@@ -0,0 +1,47 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Extraction mode tracking
|
||||
|
||||
The chat page SHALL track whether the current conversation is in extraction mode. Extraction mode is entered when an email is uploaded and exited when the user starts a new chat.
|
||||
|
||||
#### Scenario: Enter extraction mode on upload
|
||||
|
||||
- **WHEN** the user uploads an email file
|
||||
- **THEN** the conversation enters extraction mode and subsequent messages are routed to the extraction endpoint
|
||||
|
||||
#### Scenario: Exit extraction mode on New Chat
|
||||
|
||||
- **WHEN** the user clicks "New Chat" while in extraction mode
|
||||
- **THEN** the conversation exits extraction mode and returns to general chat routing
|
||||
|
||||
### Requirement: Extraction mode visual indicator
|
||||
|
||||
The chat page SHALL display a visual indicator when in extraction mode so the user knows their messages are part of an extraction conversation.
|
||||
|
||||
#### Scenario: Indicator shown in extraction mode
|
||||
|
||||
- **WHEN** the conversation is in extraction mode
|
||||
- **THEN** a visual indicator (e.g., chip, banner, or subtitle) is visible showing the extraction context
|
||||
|
||||
#### Scenario: Indicator hidden in general mode
|
||||
|
||||
- **WHEN** the conversation is in general chat mode
|
||||
- **THEN** no extraction indicator is shown
|
||||
|
||||
### Requirement: Follow-up messages route to extraction endpoint
|
||||
|
||||
In extraction mode, text messages typed by the user SHALL be sent to the extraction endpoint with the original email HTML and full conversation history, not to the general chat endpoint.
|
||||
|
||||
#### Scenario: User replies to disambiguation question
|
||||
|
||||
- **WHEN** the agent asks "Which legal entity?" and the user types "1"
|
||||
- **THEN** the client sends an ExtractionRequest with the original email HTML plus all messages (assistant question + user reply) to `POST /api/chat/extract`
|
||||
|
||||
### Requirement: Email upload message in chat
|
||||
|
||||
When an email is uploaded, the chat SHALL display a user message indicating the upload (e.g., showing the filename) before the extraction response streams in.
|
||||
|
||||
#### Scenario: Upload message displayed
|
||||
|
||||
- **WHEN** the user drops "trade_request.html"
|
||||
- **THEN** a user message appears in the chat like "[Uploaded: trade_request.html]" followed by the streaming extraction response
|
||||
41
openspec/changes/archive/2026-04-06-email-upload-ux/tasks.md
Normal file
41
openspec/changes/archive/2026-04-06-email-upload-ux/tasks.md
Normal file
@@ -0,0 +1,41 @@
|
||||
## 1. Drag-and-Drop Infrastructure
|
||||
|
||||
- [x] 1.1 Add JS interop function for drag-and-drop file reading — a small JS function that listens for `dragover`/`drop` events on a given element, reads the dropped file as text, and invokes a .NET callback with the filename and content
|
||||
- [x] 1.2 Add the JS interop script to `wwwroot/index.html` or a separate `.js` file referenced there
|
||||
- [x] 1.3 Wire the drag-and-drop JS interop to the `.message-list` element in Chat.razor — register on `OnAfterRenderAsync`, dispose on component disposal
|
||||
|
||||
## 2. File Upload Button
|
||||
|
||||
- [x] 2.1 Add `MudIconButton` with attachment icon next to the send button in the input area
|
||||
- [x] 2.2 Add hidden `InputFile` component accepting `.html` files, triggered by the icon button click
|
||||
- [x] 2.3 Handle `InputFile.OnChange` — read the selected file content as string, trigger extraction
|
||||
|
||||
## 3. Drop Zone Visual Feedback
|
||||
|
||||
- [x] 3.1 Add `_isDragOver` boolean state to Chat.razor, toggled by dragenter/dragleave events from JS interop
|
||||
- [x] 3.2 Add CSS class `.drag-over` to `.message-list` when `_isDragOver` is true — highlighted border, subtle overlay with "Drop email here" text
|
||||
- [x] 3.3 Add `.drag-over` styles to Chat.razor.css
|
||||
|
||||
## 4. Extraction Mode and Routing
|
||||
|
||||
- [x] 4.1 Add `_isExtractionMode` boolean and `_emailHtml` string fields to Chat.razor
|
||||
- [x] 4.2 When an email file is read (via drop or file picker): set `_isExtractionMode = true`, store email HTML in `_emailHtml`, add a user message showing "[Uploaded: filename.html]"
|
||||
- [x] 4.3 Create `SendExtractionMessage()` method — builds `ExtractionRequest` with `_emailHtml` and conversation messages, calls `ChatApiClient.SendExtractionStreamingAsync()`, streams response into assistant message (same pattern as `SendMessage()`)
|
||||
- [x] 4.4 Modify `SendMessage()` — if `_isExtractionMode`, build `ExtractionRequest` with `_emailHtml` + all messages and call the extraction endpoint instead of the chat endpoint
|
||||
- [x] 4.5 On file drop/upload: call `SendExtractionMessage()` for the initial extraction
|
||||
- [x] 4.6 Modify `NewChat()` to reset `_isExtractionMode = false` and `_emailHtml = ""`
|
||||
|
||||
## 5. Extraction Mode Indicator
|
||||
|
||||
- [x] 5.1 Add a `MudChip` or small banner below the tab header showing "Extraction Mode" when `_isExtractionMode` is true
|
||||
- [x] 5.2 Style the indicator in Chat.razor.css
|
||||
|
||||
## 6. Guard Rails
|
||||
|
||||
- [x] 6.1 Reject non-.html files in both drop handler and InputFile handler — show a snackbar or inline message
|
||||
- [x] 6.2 Disable drop zone and file picker during streaming (`_isStreaming` flag)
|
||||
|
||||
## 7. Build and Verify
|
||||
|
||||
- [x] 7.1 Build the solution (`dotnet build`) and confirm no compilation errors
|
||||
- [x] 7.2 Run all tests (`dotnet test`) and confirm they pass
|
||||
Reference in New Issue
Block a user