Commit Graph
38 Commits
Author SHA1 Message Date
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
gongzhiyongandClaude Opus 4.6 743aadb771 fix: use uuid type for ticket_id to prevent /summary route conflict
{ticket_id:str} was matching /api/tickets/summary before the literal route.
Changing to {ticket_id:uuid} restricts matching to UUID format only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:43:15 +08:00
gongzhiyongandClaude Opus 4.6 9f2a144f65 fix(backend): resolve search concurrency race and add tickets summary endpoint
- Replace module-level _current_model global with contextvars.ContextVar
  to prevent concurrent requests from overwriting each other's search
  strategy (flash vs pro)
- Add GET /api/tickets/summary returning {total, by_status, by_priority}
  aggregated from Gongdan API, registered before parameterized ticket routes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:37:42 +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 Opus 4.6 3869be3ccc feat: add Azure Static Web App deployment for frontend
Create deploy-frontend.yml workflow using Azure/static-web-apps-deploy@v1.
Add output: 'export' to next.config.mjs for static site generation.
Static Web App created in Operation resource group (soc-frontend, Free tier).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:53:54 +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 Opus 4.6 72f581355c fix: use UUID as ticket id instead of ticketNumber
ticketNumber (e.g. TK-2026-296691) was being used as id, causing
GET /api/tickets/{id} to 404 on upstream Gongdan API which expects UUID.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:33:28 +08:00
gongzhiyongandClaude Opus 4.6 6423750f08 fix: add startup.sh with explicit PYTHONPATH for bundled packages
Azure Oryx could not find packages installed to .python_packages/.
Add a startup.sh script that sets PYTHONPATH to include the bundled
package directory before launching gunicorn. Update workflow to set
the Web App startup command to use startup.sh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:58:07 +08:00
gongzhiyongandClaude Opus 4.6 f2f6efd0ff fix: use Oryx server-side build instead of pre-bundled packages
The previous workflow installed dependencies with pip --target and zipped
them into the deployment package. Azure Oryx runtime could not find
the packages because it looks for antenv/ or __oryx_packages__/, not
.python_packages/. Switch to deploying source-only zip with az webapp
deploy, letting SCM_DO_BUILD_DURING_DEPLOYMENT=true trigger Oryx to
run pip install on the server and create the antenv virtualenv.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:51:25 +08:00
gongzhiyongandClaude Opus 4.6 f4d3e1655a fix: add id-token permission and fix zip package root path
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:42:50 +08:00
gongzhiyongandClaude Opus 4.6 41727c52f4 fix: switch to OIDC auth for Azure deploy, remove duplicate workflow
- Use azure/login@v2 with OIDC (Federated Identity) instead of publish-profile
- Remove auto-generated main_soc-backend.yml (wrong paths)
- Fix deployment package to include backend/ directory properly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:41:34 +08:00
gongzhiyongandClaude Opus 4.6 3759ecd1a6 feat(backend): Phase 3 - Jina search + Redis cache
- Add web_search tool with Jina Search/Reader/Rerank
- Flash mode: top 3, 8s timeout, no Reader/Rerank
- Pro mode: top 10, 20s timeout, concurrent Reader + Rerank top 5
- Add Redis async cache (TTL=300s) for search results
- Register "search" in ALL_TOOLS mapping

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:39:43 +08:00
gongzhiyongandClaude Opus 4.6 cc571afc88 fix: downgrade webapps-deploy to v2 for publish-profile support
v3 was failing with "No credentials found" despite publish-profile being set.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:39:28 +08:00
Fasthei 8c92b576be Add or update the Azure App Service build and deployment workflow config 2026-04-08 13:37:13 +08:00
gongzhiyongandClaude Opus 4.6 efb3c53623 feat: add backend service and Azure deployment workflow
- Add complete Python backend (Litestar + LangGraph) with chat, conversations, tickets APIs
- Add GitHub Actions workflow for auto-deploying backend to Azure Web App (soc-backend)
- Add gunicorn to requirements.txt for production serving
- Update CLAUDE.md and EXTERNAL_SERVICES.md with latest config
- Remove obsolete claudehd.md (merged into gpthd.md)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:31:00 +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
v0andnull 93017f291a Initialized repository for chat Gemini chat clone
Co-authored-by: null <167957975+Fasthei@users.noreply.github.com>
2026-04-07 09:23:49 +00:00