- 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>
- 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>
- 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>
- 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>
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>
- 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>
- 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>
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>
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>
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>
- 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>
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>
{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>
- 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>
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>
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>
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>
- 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>
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>
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>
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>
- 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>
- 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>
- 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>