Commit Graph
71 Commits
Author SHA1 Message Date
gongzhiyongandClaude Opus 4.6 32f83d714e refactor: migrate LangGraph server from Container App to Azure Web App
## Changes
- Delete Dockerfile and .dockerignore (no longer needed)
- Rewrite deploy-langgraph.yml: zip deploy to Web App (not ACR build)
- Update deploy-langgraph-ui.yml: VITE_LANGGRAPH_URL → soc-langgraph.azurewebsites.net

## Azure Resources
- CREATED: soc-langgraph Web App (Node.js 20, B1 Linux, southeastasia)
- CREATED: soc-langgraph-plan (App Service Plan, B1, southeastasia)
- DELETED: soc-langgraph Container App
- DELETED: soc-cae Container App Environment
- DELETED: socsocacr ACR
- All env vars migrated to Web App
- Startup: npx langgraphjs dev --host 0.0.0.0 --port 2024 --no-browser
- WEBSITES_PORT=2024

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:47:32 +08:00
gongzhiyong 4a485a9c5b fix(dockerfile): pre-create .langgraph_api to prevent tsx watch restart loop 2026-04-10 05:23:07 +08:00
gongzhiyong 3d35ca43be fix(dockerfile): create empty .env file — langgraphjs dev requires it to exist 2026-04-10 05:05:17 +08:00
gongzhiyong df2b72c53c fix(dockerfile): explicit port binding for Container App health probe 2026-04-10 04:54:47 +08:00
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