When ensureThread() creates a new thread and calls setCurrentThreadId,
the useEffect fires thread.switchThread() again after doSubmit already
called it — wiping thread.messages mid-stream and showing the welcome
screen instead of AI replies.
Fix: add skipNextSwitchRef. When doSubmit creates a new thread and
manually calls thread.switchThread(), it sets the flag so the
subsequent useEffect is a no-op. Only needed for new threads (when
currentThreadId was null); existing-thread submits don't change
currentThreadId so the useEffect never fires.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously handleNewThread called client.threads.create() immediately,
leaving empty "新对话" threads in the sidebar when users clicked new
thread but never sent a message (ChatGPT-style behavior).
Now handleNewThread just resets UI state (setCurrentThreadId(null)).
A new ensureThread() helper in useConversation lazily creates the
backend thread only when the user actually submits a message.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire threadLoadFailed from useConversation through ChatMessages props.
When a selected thread has no loadable history (expired checkpoint or
cross-deployment), show a contextual unavailable state instead of the
welcome screen so users understand the conversation exists but history
is inaccessible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P1: Remove pre-tool content bubble above ToolCallStatus — COT thinking
text was rendering as a standalone white bubble duplicating the
collapsible thinking block inside ToolCallStatus (commit b27424e
regression).
P2-2: Auto-scroll now triggers on both thread.messages and thread.values
so streaming tokens keep the viewport following output.
P2-1: Show "回复已停止" italic hint when last AI message has no content
and is not loading — gives clear feedback after user stops generation.
P2-3: Strip bare source words (工单/知识库/网络/推断) at sentence end
in renderWithSourceBadges to remove trailing unbracketed source labels.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously: setHistoricalMessages([]) + setCurrentThreadId() fired together,
causing a render where both thread.messages (cleared by switchThread) and
historicalMessages were empty → welcome screen shown.
Fix: await loadThreadState() first, then set historicalMessages/ui and
currentThreadId atomically. historicalMessages is populated before the
first render after the switch, so the welcome screen never flashes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The threadJustSwitched suppression was too aggressive — it cleared
lastMessagesRef on every thread switch, then relied on historicalMessages
which loads async and may still be empty. Historical thread messages
would never appear.
Simplified fix: thread.switchThread() already clears thread.messages
when switching, so thread.messages is always safe to trust directly.
Use thread.messages when non-empty, else fall back to historicalMessages.
Removes threadJustSwitched / lastMessagesRef / lastThreadIdRef entirely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Show pre-tool plainTextContent above ToolCallStatus (was silently
dropped when hasThinking=false but plainText exists with tool_calls)
- Pre-tool thinking bubble gets proper bg-muted bubble styling and
isStreaming=false (thinking is complete once tool calls start)
- handleRegenerate now sets concurrentStatus="generating" for
consistent status bar feedback on regenerate
- Remove dead setConcurrentStatus prop from ChatInput interface/usage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ConfigPanel (model mode + tool group chips) is no longer rendered in
the chat layout. Tool selection was surfaced below the input after the
previous refactor but users found it visually noisy. activeTools still
drives tool filtering on submit; UI selection can be re-exposed later
via a dedicated settings entry point.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the "Auto · 云费用 1" summary toggle button that occupied a
dedicated line below the textarea. Model mode and tool group chips are
now displayed inline and always visible, eliminating the redundant
status row while keeping all selection controls accessible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The "可分析工单、查知识库" / "已启用 X 个工具" hint paragraph
below the textarea was redundant — ConfigPanel already shows the
active tool count in its summary button. Removed the <p> block and
the now-unused activeTools destructuring in ChatInput.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add cloudcost_dashboard, cloudcost_metering, cloudcost_detail, cloudcost_alerts,
and cloudcost_accounts tools to the enterprise agent with full Gen-UI card support.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change summaryExpanded default from false to true
- Long replies (>800 chars) now display full text immediately
- Users can still collapse with "收起" button if needed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove opacity-0 group-hover from ThumbsUp/ThumbsDown buttons — always visible
- Fix thread title: update title whenever currentTitle is empty, not just first message
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add h-32 spacer before bottomRef to prevent last card from being hidden behind input bar
- Fix thread title not updating: check existing title instead of only first message
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add ThumbsUp/ThumbsDown feedback buttons on AI messages (hover reveal)
- Add ExportButton to export conversation as Markdown file
- Add global keyboard shortcuts: Ctrl+Shift+O (new thread), Ctrl+Shift+S (sidebar), / (focus input), Esc (stop)
- Unlock textarea during AI generation (remove disabled={thread.isLoading})
- Smart title truncation at word/punctuation boundary (30 chars)
- Show tool inputSummary in ToolCallStatus execution log
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- POST /api/client-log endpoint in http.ts (Hono, no auth, 204 response)
prints batched log entries to stdout so `docker compose logs` shows them
- New src/utils/remote-log.ts: remoteLog/remoteWarn batch to backend every 500ms
- Replace all bracket-tagged diagnostic console.log/warn calls in main.tsx and
ToolCallStatus.tsx with remoteLog/remoteWarn (tags: thread-ui, ui-debug,
render-msg, orphan-match, orphan-name-match, orphan-result, streaming-orphan,
tool-ui-match)
- Add /api/client-log proxy entry to vite.config.ts so dev server forwards
the request to the LangGraph backend (port 2024)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When modelMode is "pro", searcher/enterprise/coder agents now call the
Responses API directly via fetch to obtain reasoning summary blocks.
The reasoning text is injected as a { type: "thinking" } content block
in the AIMessage, while flash/auto modes remain unchanged using the
existing Chat Completions path through AzureChatOpenAI.
Key changes:
- create-llm.ts: add invokeWithReasoning() with inline zod-to-JSON-Schema
converter and Responses API output parser (reasoning/message/function_call)
- All three agent nodes: Pro branch converts LangChain messages to
Responses API format (developer role, function_call_output for tool
results, function_call for AI tool invocations)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Streaming orphan UI cards section was rendering all cards with message_id=NONE
as orphans, even when they were already claimed by the message-level orphan
matching logic via card_id. Added card_id+tool_call_id check to prevent
double rendering.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
With streamSubgraphs:true, subgraph values events push messages that
the parent Supervisor later merges, causing the same message.id to
appear multiple times in thread.messages. Similarly, the same card_id
can appear in both subgraph and parent values snapshots.
- Add deduplicateMessages() at module level: keeps the last (most
up-to-date) copy per message.id, eliminating duplicate AI messages
caused by subgraph-then-parent merge events.
- Apply deduplicateUiItems() to the full allUi array before per-message
card matching, so loading→done card pairs pushed by subgraphs and
parent are collapsed globally before orphan attribution runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both thread.submit() calls now use streamMode: ["values", "messages"] so the
frontend receives real-time LLM token stream from subgraphs (searcher/coder/writer)
during the tool execution phase, eliminating the skeleton-only blank period.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>