Commit Graph
19 Commits
Author SHA1 Message Date
gongzhiyongandClaude Sonnet 4.6 0a2b339550 feat: implement Generative UI — Agent Workspace + workspace_card SSE protocol
## Backend
- Add workspace_card SSE event protocol: {id, name, props, merge}
- Add _extract_llm_text / _maybe_emit_workspace_card helpers in chat.py
- Refactor all tools to dual-output format: {llm_text, ui: {name, props}}
  - kb_search → KnowledgeResultCard
  - ticket_list/detail → TicketSummaryCard / TicketDetailCard
  - web_search → SearchResultCard
  - generate_document → DocumentResultCard
  - sandbox_run → SandboxResultCard
- Update SYSTEM_PROMPT: instruct LLM not to repeat tool data (UI shows it)

## Frontend
- Three-column layout: sidebar + chat + Agent Workspace (360px right panel)
- WorkspaceSession state model with ActivityNode + WorkspaceCard
- New components/workspace/: AgentWorkspace, ActivityTimeline, WorkspaceCardRenderer
- 6 card components: Knowledge/Ticket/Search/Document/Sandbox/ErrorCard
- GeminiChat: workspace state management, SSE routing for workspace_card events
- GeminiMessage: replace TracePanel with lightweight activity summary line
- lib/api.ts: add WorkspaceSession/ActivityNode/WorkspaceCard types

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 02:57:09 +08:00
gongzhiyongandClaude Sonnet 4.6 1ad75a3602 fix: prevent duplicate AI message when switching conversations during stream
Two bugs caused the same AI response to appear twice:

1. handleSelectConversation would fetch conversation history even while
   a stream was in progress (isLoading). If the fetch resolved mid-stream,
   fetchConversation overwrote the conversation messages unconditionally,
   writing the server-persisted AI message (with a real UUID) alongside the
   still-streaming client-side message (with a temp ID), producing a duplicate.
   Fix: guard the fetch with !isLoading, and add a second check inside the
   .then callback so setConversations only writes history if the messages
   array is still empty (protects against slow-resolving network requests).

2. onDone in streamChat could theoretically be called a second time if the
   ReadableStream completed without a "done" SSE event. Fix: call
   reader.cancel() immediately after onDone so the while loop exits cleanly.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 22:58:47 +08:00
gongzhiyong 0cd19b527b feat: handle status events to create assistant placeholder early 2026-04-09 17:51:52 +08:00
gongzhiyong 056cf6be9d feat: stable trace matching via call_id 2026-04-09 16:59:17 +08:00
gongzhiyongandClaude Sonnet 4.6 5b5a55933c refactor: redesign CoT trace panel — clean summary line, real tool trace only, remove fabricated status steps
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:49:22 +08:00
gongzhiyongandClaude Sonnet 4.6 0207f34902 feat(frontend): CoT execution trace UI — TracePanel + full SSE event handling
Add TracePanel component for collapsible execution trace display,
extend Message interface with traceItems, and implement complete
SSE event processing (trace/status/content/error/done) in GeminiChat.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:00:50 +08:00
gongzhiyongandClaude Sonnet 4.6 7ec2127b85 fix: sidebar conversation menu + file chip X button interaction
Bug 1 - Sidebar MoreHorizontal now opens a dropdown menu:
- Click three-dot icon to toggle rename/delete dropdown
- Rename: inline text input with Enter to confirm, Escape to cancel
- Delete: removes from list, calls DELETE /api/conversations/{id}
- Rename calls PATCH /api/conversations/{id} with new title
- Both use optimistic local state updates

Bug 2 - File upload chip buttons now respond to clicks:
- Added e.stopPropagation() to X (remove) and retry buttons
- Prevents click from being swallowed by parent elements

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 20:16:08 +08:00
gongzhiyongandClaude Sonnet 4.6 915aaef30b feat: add file upload UI and attachment display in messages
- lib/api.ts: add AttachmentData interface, uploadAttachment() function,
  and getAttachmentDownloadUrl() helper for upcoming /api/attachments endpoint
- GeminiInput.tsx: wire + button to trigger file selection with upload
  state management (uploading/done/error), file preview chips above
  textarea with retry and remove controls, pass AttachmentData to parent
  on submit
- GeminiMessage.tsx: add attachments field to Message interface, render
  attachment list (filename + size + download link) below both user and
  assistant message content
- GeminiChat.tsx: accept AttachmentData[] in handleSend, store attachments
  on user messages for display

Backend /api/attachments endpoints not yet live — frontend is ready for
integration once backend agent completes the upload/download API.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:31:12 +08:00
gongzhiyongandClaude Sonnet 4.6 a1ae32713d feat: remove tool chips above input, add Auto model option
- Remove selected-tool chip/tag area above the input textarea; tool
  selection state is now shown only via button highlight in the tools panel
- Add "Auto" as a third model option between Flash and Pro (default)
- Auto maps to "flash" when sent to the backend (which only accepts
  flash|pro)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:13:03 +08:00
gongzhiyongandClaude Sonnet 4.6 8c7fe84a3d fix: restore Flash/Pro buttons, add GPT-5.4 label in topbar
- Restore Flash/Pro dual model buttons in GeminiInput (they control
  speed/quality mode sent to backend, not model name)
- Restore selectedModel/onSelectedModelChange props in GeminiInput
- Reset default model back to "pro" in GeminiChat
- Replace Gemini model dropdown in GeminiTopbar with static "GPT-5.4"
  label to show current model name

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:08:10 +08:00
gongzhiyongandClaude Sonnet 4.6 b9465f4c5a fix: hide typing indicator once assistant message appears during streaming
The "two diamond" bug was caused by GeminiTypingIndicator (which has its
own gem icon) rendering simultaneously with GeminiMessage (also has a gem
icon) once the first token arrived. Now the typing indicator only shows
while isLoading is true AND no assistant message exists yet (last message
is not role "assistant").

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:43:58 +08:00
gongzhiyongandClaude Sonnet 4.6 c2e14c48a0 fix: single gpt-5.4 model selector + SSE buffer edge case fix
- Replace Flash/Pro dual model buttons with single "gpt-5.4" label
- Backend still receives "flash" as model param (matches schema validation)
- Remove unused selectedModel/onSelectedModelChange props from GeminiInput
- Fix SSE stream parsing: process remaining buffer data when stream ends
  without trailing newline (potential cause of "no return" issue)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:42:50 +08:00
gongzhiyongandClaude Sonnet 4.6 71efdff800 Switch TicketSummary to use /api/tickets/summary endpoint
- Add fetchTicketSummary() and TicketSummaryData type to lib/api.ts
- Change TicketSummary props from tickets[] array to summary object
  with total, by_status, and by_priority fields
- Update GeminiChat to fetch summary data instead of ticket list
- TicketSummary now renders status and priority breakdowns from
  the summary endpoint instead of computing from raw ticket data

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:34:23 +08:00
gongzhiyongandClaude Sonnet 4.6 39aad16373 Add light/dark theme toggle with CSS custom properties
- Wire next-themes ThemeProvider in layout with class strategy, defaultTheme=dark
- Define all Gemini colors as CSS custom properties (--gem-*) in globals.css
  with light (:root) and dark (.dark) variants
- Replace all hardcoded hex colors across 7 Gemini components with var() refs
- Add Sun/Moon toggle button in GeminiTopbar (right side, before Settings)
- Theme persists to localStorage via next-themes (key: gem-theme)
- Dark theme renders identically to before (same hex values)
- Light theme: white bg, light gray surfaces, same blue-purple accent gradient

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:52:34 +08:00
gongzhiyongandClaude Sonnet 4.6 3232739a32 Fix duplicate gem icon: defer assistant message creation until first token
Instead of pre-creating an empty placeholder assistant message when
streaming starts (which races with GeminiTypingIndicator rendering),
wait until the first SSE token arrives to create the message. This
guarantees only one row is ever visible: either the typing indicator
(before tokens) or the GeminiMessage (after first token).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:45:46 +08:00
gongzhiyongandClaude Sonnet 4.6 117a8ddb58 Fix duplicate gem icon during AI streaming response
Skip rendering the empty placeholder assistant message while isLoading
is true — the GeminiTypingIndicator already shows the gem icon with
bouncing dots. Once tokens arrive and content is non-empty, the message
renders normally via GeminiMessage.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:35:57 +08:00
gongzhiyongandClaude Sonnet 4.6 60acbac464 Fix 3 P0 issues from code review
FCR-1: Set ignoreBuildErrors to false in next.config.mjs
FCR-2: Default API_URL to production backend so static builds work correctly;
       .env.local overrides to localhost for local dev
FCR-3: Remove createConversation call in handleSend; generate UUID client-side
       and let backend streamChat auto-create the conversation to avoid duplicate writes

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:21:33 +08:00
gongzhiyongandClaude Sonnet 4.6 a36f039ff9 Replace mock data with real backend API calls
- Add frontend/lib/api.ts with typed API client for conversations, tickets, and SSE chat streaming
- Replace simulateAIResponse() with real SSE streaming via POST /api/chat/stream
- Replace MOCK_CONVERSATIONS with GET /api/conversations
- Replace MOCK_TICKETS with GET /api/tickets
- Lift activeTools and selectedModel state from GeminiInput to GeminiChat for API integration
- Load conversation messages on demand via GET /api/conversations/{id}
- Backend URL configured via NEXT_PUBLIC_API_URL in .env.local

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:50:05 +08:00
gongzhiyongandClaude Sonnet 4.6 04d8fbb740 refactor: 重组项目结构,前端收拢至 frontend/,新增 backend/ 目录
- 前端文件移入 frontend/ 子目录
- 新建 backend/ 目录(待开发)
- 新增 CLAUDE.md、claudehd.md、EXTERNAL_SERVICES.md

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 18:01:04 +08:00