Commit Graph
67 Commits
Author SHA1 Message Date
gongzhiyong f8a73e461c fix(ci): point app_location directly to langgraph/dist for SWA upload 2026-04-10 04:44:57 +08:00
gongzhiyong 14a7e79792 fix(ci): use pnpm vite build then SWA upload from repo root with langgraph/dist 2026-04-10 04:43:02 +08:00
gongzhiyong 3edeafa062 fix(ci): let SWA action handle Vite build instead of manual pnpm build 2026-04-10 04:40:14 +08:00
gongzhiyong f43d239e6c fix(main.tsx): cast message prop to any in LoadExternalComponent 2026-04-10 04:37:21 +08:00
gongzhiyong 8d30e0cca0 fix(main.tsx): rename UIMessage→UIMsgLocal to avoid SDK type conflict 2026-04-10 04:35:15 +08:00
gongzhiyong 4f75b65d52 fix(main.tsx): add type field to local UIMessage type 2026-04-10 04:32:06 +08:00
gongzhiyong 844aaa8dcc fix(main.tsx): resolve TypeScript errors — UIMessage type, role→type, ComponentMap cast 2026-04-10 04:28:28 +08:00
gongzhiyongandClaude Sonnet 4.6 7a3cc140b0 refactor: replace SOC system with LangGraph.js gen-ui — full cleanup
## Removed (old SOC system)
- backend/ — Python FastAPI + LangGraph Python ReAct agent
- frontend/ — Next.js Gemini-style UI
- config/, doc/ — old documentation
- .github/workflows/deploy-backend.yml
- .github/workflows/deploy-frontend.yml

## Added (new LangGraph.js system)
- langgraph/src/agent/enterprise/ — enterprise agent with 6 tools
  - kb_search → KnowledgeResultCard
  - ticket_list/detail → TicketSummaryCard / TicketDetailCard
  - web_search (Jina Search+Reader) → SearchResultCard
  - sandbox_run (Daytona REST) → SandboxResultCard
- langgraph/src/agent-uis/enterprise/ — UI card components
- .github/workflows/deploy-langgraph-ui.yml — Vite SPA → Azure Static Web App

## Azure Resources
- soc-backend webapp: DELETED
- soc-frontend Static Web App (eastasia): DELETED
- soc-langgraph-ui Static Web App (eastasia): CREATED
  URL: salmon-mushroom-0d8872e00.7.azurestaticapps.net

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 04:23:51 +08:00
gongzhiyongandClaude Sonnet 4.6 645f1ecaae feat(langgraph): add enterprise UI card components and implement main.tsx Chat entry
- Create 5 enterprise UI card components under src/agent-uis/enterprise/:
  knowledge-result, ticket-summary, ticket-detail, search-result, sandbox-result
- Update ComponentMap in src/agent-uis/index.tsx to replace () => null placeholders
  with real component imports
- Implement src/main.tsx with useStream + LoadExternalComponent for full Chat UI,
  connecting to LangGraph agent with VITE_LANGGRAPH_URL env var

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 04:23:32 +08:00
gongzhiyong b621fbae92 fix(langgraph): add missing pnpm-lock.yaml for Docker build 2026-04-10 04:04:27 +08:00
gongzhiyongandClaude Sonnet 4.6 ac388b3c07 feat: add langgraph/ — LangGraph.js gen-ui server with CI/CD to Azure Container App
## Azure Resources Created
- ACR: socsocacr.azurecr.io (Operation, southeastasia)
- Container App Environment: soc-cae (Operation, southeastasia)
- Container App: soc-langgraph (Operation, southeastasia, port 2024)
  - Scale: 0-2 replicas, 0.5 vCPU / 1GB
  - Registry: socsocacr.azurecr.io (system-assigned managed identity)
- OIDC: oidc-msi-8ac6 granted Contributor on Operation resource group

## What's included
- Full langgraphjs-gen-ui-examples codebase under langgraph/
- Dockerfile: node:20-slim + pnpm, exposes port 2024
- .dockerignore, .gitignore
- GitHub Actions: .github/workflows/deploy-langgraph.yml
  - Trigger: push to main, paths langgraph/**
  - Uses az acr build (cloud build, no local Docker needed)
  - Deploys to soc-langgraph Container App

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 04:01:06 +08:00
gongzhiyongandClaude Sonnet 4.6 c6ca9dc126 fix: restore workspace components accidentally dropped from git index
workspace/ files existed on disk but were not included in previous
incremental commit, causing git to record them as deleted. Re-adding
all workspace card components, AgentWorkspace, ActivityTimeline, and
WorkspaceCardRenderer to properly track them.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:09:41 +08:00
gongzhiyongandClaude Sonnet 4.6 47921d1a45 fix: resolve 6 gen-ui issues — timeline completeness, regen sync, localStorage persistence
## Fixes
- [#3] Status events now add/update a status ActivityNode in timeline (upsertWsStatusNode)
- [#4] Done callback adds done node; error callback adds error node + sets session.status="error"
- [#5] handleRegenerate fully synced with workspace: status/tool/card/done/error all handled
- [#1][#2] localStorage persistence: completed/error sessions auto-saved, lazy-loaded on demand
- [#1] handleSelectConversation preloads workspace sessions from localStorage for history messages
- Refactored completeWsSession to include done ActivityNode in timeline
- Added errorWsSession, upsertWsStatusNode, saveWsToStorage, loadWsFromStorage helpers

## Known limitation
- [#7] workspace_card merge:true not yet used by backend (all cards are append-only for now)
- History workspace recovery depends on localStorage (browser-local, not cross-device)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:08:23 +08:00
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
gongzhiyongandClaude Sonnet 4.6 d86d9b8b32 fix(backend): handle MCP tool output format and read_url list input
- Add _extract_output_str() to unwrap MCP ToolMessage content objects
  instead of calling str() on the raw object
- Handle read_url url param when LLM passes a list instead of a string

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 22:46:58 +08:00
gongzhiyongandClaude Sonnet 4.6 ac843da725 feat: add Google Serper search tool alongside Jina MCP
- Add langchain-community dependency for GoogleSerperAPIWrapper
- Add serper_api_key to config with default key
- Create app/tools/serper.py with async serper_search tool
- Register "serper" key in tools/__init__.py (independent from "search"/Jina MCP)
- Add tool title and input/output summaries in chat.py
- Set SERPER_API_KEY on Azure App Service (Operation resource group)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 21:11:22 +08:00
gongzhiyongandClaude Sonnet 4.6 707f6bce4b feat: replace Jina REST with MCP tools for web search
- Add langchain-mcp-adapters dependency
- New app/tools/mcp_jina.py: Jina MCP client with filtered tool loading
  (search_web, read_url, sort_by_relevance)
- Update resolve_tools to return (tools, needs_jina_mcp) tuple
- Update builder.py: skip graph cache for MCP-bound tools, add search
  tool guidance to system prompt
- Refactor chat.py: extract _run_graph_stream, add MCP/non-MCP paths,
  update tool titles and summaries for MCP tool names
- Tested locally: both MCP search path and non-MCP knowledge path work

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 21:01:21 +08:00
gongzhiyongandClaude Sonnet 4.6 a11cb56962 feat: emit real status events from on_chain_start (thinking/generating)
- Add has_tool_activity flag to distinguish first agent pass (thinking)
  from post-tool agent pass (generating)
- Replace on_chain_start debug logging with status SSE emission
- Filter to only graph-level agent nodes via graph:step: tag prefix

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:52:55 +08:00
gongzhiyong 0cd19b527b feat: handle status events to create assistant placeholder early 2026-04-09 17:51:52 +08:00
gongzhiyongandClaude Sonnet 4.6 bb7a4f6629 chore: temporarily use logger.warning for on_chain_start observation
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:17:15 +08:00
gongzhiyongandClaude Sonnet 4.6 f03efbfe0c feat: add call_id (run_id) to tool events, add on_chain_start debug logging
- tool_start/tool_end/tool_error SSE payloads now include call_id field
  derived from LangGraph run_id for reliable tool event correlation
- tool_start_ts dict keyed by call_id instead of tool_name to handle
  concurrent calls to the same tool
- Added on_chain_start debug logging to observe chain names and metadata
  (no SSE emission yet, observation only)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:01:27 +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 722ec3e81d feat: add structured execution trace SSE events (status/tool_start/tool_end/tool_error)
Add CoT execution trace to the SSE stream: status events for stage
transitions, enriched tool_start with input_summary, tool_end with
output_summary and duration_ms, and tool_error for failed tool calls.
Helper functions _sse, _summarize_input, _summarize_output provide
human-readable summaries for each tool type.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:00:49 +08:00
gongzhiyongandClaude Sonnet 4.6 fe8c361127 feat(input): replace model selector buttons with Select dropdown
Replace the three pill buttons (Auto/Flash/Pro) in GeminiInput with a
single shadcn/ui Select component. Selection logic and onSelectedModelChange
callback are unchanged; only the UI widget is swapped.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:47:35 +08:00
gongzhiyongandClaude Sonnet 4.6 c54e2be5c4 fix(backend): validate conversation_id FK before inserting attachment
When the frontend passes a conversation_id that does not exist in the
conversations table (e.g. new conversation before first message, or
invalid ID), the INSERT into attachments failed with
ForeignKeyViolationError.

Now upload_attachment checks session.get(Conversation, conversation_id)
before inserting. If the conversation does not exist, conversation_id is
set to None and the attachment is stored as unlinked (blob path uses
_unlinked/ prefix). This avoids the FK constraint error while keeping
the file safely uploaded.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 21:02:03 +08:00
gongzhiyongandClaude Sonnet 4.6 5d0127bfb4 fix(backend): auto-purge polluted checkpoint when tool_calls lack ToolMessage
When a tool call crashes (e.g. httpx timeout in kb_search), the LangGraph
checkpoint retains an AIMessage with tool_calls but no corresponding
ToolMessage. Subsequent requests to the same conversation_id fail with:
  ValueError: Found AIMessages with tool_calls that do not have a
  corresponding ToolMessage

Now the except block in _stream_response detects this specific ValueError
by checking for "tool_calls" and "ToolMessage" in the error string, then
calls checkpointer.adelete_thread() to purge the corrupted thread state.
The frontend receives {"type":"error","content":"对话状态异常,已自动重置..."}
followed by {"type":"done"}, so the user can simply resend their message.

API confirmed: AsyncPostgresSaver.adelete_thread(thread_id) deletes from
checkpoints, checkpoint_blobs, and checkpoint_writes tables for the thread.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 20:53:54 +08:00
gongzhiyongandClaude Sonnet 4.6 0f2db39655 fix(backend): handle KB Agent cold-start timeout with retry and graceful errors
KB Agent runs on Azure App Service which cold-starts after idle periods,
taking 20-30s to respond. The previous 15s timeout caused httpx.ReadTimeout
that crashed the ReAct agent tool node and killed the SSE stream.

Changes:
- Increase kb_agent_search_timeout_sec default from 15 to 30 in config.py
- Add single retry with 2s backoff on ReadTimeout in kb_search tool
- Catch all exceptions and return friendly Chinese error messages instead
  of propagating exceptions to the agent loop

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 20:51:18 +08:00
gongzhiyongandClaude Opus 4.6 1e735202fc fix(backend): wrap SSE astream_events in try/except/finally
Tool call failures (httpx network errors) propagated as unhandled exceptions
through LangGraph TaskGroup, killing SSE stream mid-flight. Frontend received
neither tokens nor done event, leaving it stuck.

- except: sends {"type":"error"} SSE event so frontend shows error message
- finally: persists partial AI content and always sends {"type":"done"}

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 20:36:39 +08:00
gongzhiyongandClaude Sonnet 4.6 e766564f34 fix: sidebar menu closes on mouseLeave before user can click option
- Remove menu close from onMouseLeave (was closing before user reaches dropdown)
- Add global document click listener to close menu when clicking outside

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 20:31:59 +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 e9a11a87e9 fix: set debug=False for production
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:15:19 +08:00
gongzhiyongandClaude Sonnet 4.6 48d7dd7993 fix: resolve attachment upload 500 errors (delete_attachment startup crash + create_tables ExceptionGroup)
- Add return_dto=None to delete_attachment decorator to fix Litestar
  ImproperlyConfiguredException on 204 status code with None return type
- Catch BaseException instead of Exception in create_tables() to handle
  anyio ExceptionGroup from concurrent gunicorn workers
- Enable debug=True temporarily to capture detailed error traces

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:04:50 +08:00
gongzhiyongandClaude Opus 4.6 063d4bf2a0 fix(backend): handle create_tables race condition between gunicorn workers
When 2 gunicorn workers start simultaneously, both call create_all()
which races on CREATE TABLE attachments. The second worker hits a
PostgreSQL UniqueViolation on pg_type_typname_nsp_index because the
type already exists. Wrap create_tables() in try/except to handle
this gracefully.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:48:11 +08:00
gongzhiyongandClaude Opus 4.6 1ff0b9d4ff fix(backend): delete_attachment 204 response must not declare a body
Litestar validates that handlers with 204 status codes cannot return
a response body. The delete_attachment handler was declared as
-> Response and returned Response(content=None, status_code=204),
which caused ImproperlyConfiguredException at startup and crashed
all workers. Change return type to None and move status_code to the
@delete decorator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:39:16 +08:00
gongzhiyongandClaude Sonnet 4.6 5fddf4ff2b fix: align uploadAttachment with backend API contract
- Change FormData field name from "file" to "data" (backend expects "data")
- Pass conversation_id as query parameter instead of form field
- Add content_type and created_at to AttachmentData interface to match
  backend response shape

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:32:13 +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 712b96e26e feat(backend): implement file upload with Blob Storage, DB model, and Service Bus dispatch
- Add Attachment ORM model (id, conversation_id, message_id, filename, content_type, blob_url, size_bytes, created_at) with FK to conversations
- Add POST /api/attachments/upload (multipart, 50MB limit), GET /api/attachments/{id}, GET /api/attachments/{id}/download (302 to SAS URL), DELETE /api/attachments/{id}
- Add delete_blob() and generate_sas_url() to storage/blob.py for download redirect and cleanup
- Dispatch parse_attachment task to Service Bus for parseable types (PDF, images, CSV, DOCX, XLSX)
- Pre-create blob container on startup (best-effort)
- Add AttachmentOut schema and full API docs in doc/api.md

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:30:29 +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 433d2361a8 fix(backend): use UUID type for ticket_id parameter in get_ticket handler
Litestar's {ticket_id:uuid} path parameter requires the handler
parameter to be typed as UUID, not str. This caused a 400 validation
error on GET /api/tickets/{uuid}.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:21:55 +08:00
gongzhiyongandClaude Sonnet 4.6 346fa02e44 fix(backend): add azure-storage-blob, azure-servicebus, daytona to requirements
These packages were listed locally but never committed, causing
ModuleNotFoundError: No module named 'azure' on Azure deployment.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:12:02 +08:00
gongzhiyongandClaude Sonnet 4.6 be60d9742e fix(backend): add missing storage, tasks, document, sandbox modules
The app.storage and app.tasks packages were never committed to git,
causing ModuleNotFoundError on Azure deployment. Also adds the
document and sandbox tool modules with their config fields.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:03:30 +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