-
Notifications
You must be signed in to change notification settings - Fork 5
Fix: Unify tool call UI (pills in agent timeline) for streaming + refresh #208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…add landing page - Render tool calls as pills with a shared details modal (args/results/errors) - Attach historical tool_call tool messages into agentExecution phases instead of standalone messages - Remove legacy ToolCallCard-based rendering path
Reviewer's Guide将工具调用的渲染统一标准化为可点击的 pill,并在智能体时间线与历史记录中共享同一个详情模态框;重构消息分组逻辑,将历史工具调用附着到智能体执行阶段;同时引入认证/登录落地页 UX、i18n 与认证流程工具函数,并对部分组件进行小幅清理。 从历史记录到 UI pill 和模态框的统一工具调用渲染时序图sequenceDiagram
participant Backend
participant MessageProcessor as messageProcessor
participant ChatView as ChatBubble
participant AgentTimeline as AgentStepAccordion
participant User
Backend->>MessageProcessor: messages[assistant, tool, ...]
activate MessageProcessor
MessageProcessor->>MessageProcessor: groupToolMessagesWithAssistant(messages)
Note over MessageProcessor: Track lastAssistantWithExecution
MessageProcessor->>MessageProcessor: For each assistant with agentExecution
MessageProcessor->>MessageProcessor: lastAssistantWithExecution = assistant
MessageProcessor->>MessageProcessor: For each tool message
alt tool has tool_call_id and lastAssistantWithExecution
MessageProcessor->>MessageProcessor: Attach ToolCall to target phase.toolCalls
MessageProcessor->>MessageProcessor: toolCallLookup[tool_call_id] = {toolCall, lastAssistantWithExecution}
else tool response without request yet
MessageProcessor->>MessageProcessor: Search phases.toolCalls for matching id
alt found
MessageProcessor->>MessageProcessor: toolCallLookup[tool_call_id] = {existingToolCall, lastAssistantWithExecution}
else not found
MessageProcessor->>MessageProcessor: Create placeholder ToolCall and assistant tool message
MessageProcessor->>MessageProcessor: toolCallLookup[tool_call_id] = {placeholder, toolMessage}
end
end
MessageProcessor-->>Backend: groupedMessages
deactivate MessageProcessor
Backend-->>ChatView: groupedMessages (includes assistant with agentExecution)
ChatView->>AgentTimeline: Render phases with phase.toolCalls
AgentTimeline->>AgentTimeline: Map phase.toolCalls to ToolCallPill
User->>AgentTimeline: Click ToolCallPill
AgentTimeline->>AgentTimeline: setSelectedToolCallId(id)
AgentTimeline->>AgentTimeline: selectedToolCall = phase.toolCalls.find(id)
AgentTimeline->>AgentTimeline: Render ToolCallDetailsModal(toolCall)
User->>AgentTimeline: Confirm or Cancel (optional)
alt Confirm
AgentTimeline->>ChatView: confirmToolCall(channelId, toolCallId)
else Cancel
AgentTimeline->>ChatView: cancelToolCall(channelId, toolCallId)
end
Note over ChatView: Tool call messages from history also render ToolCallPill
User->>ChatView: Click ToolCallPill
ChatView->>ChatView: setSelectedToolCallId(id)
ChatView->>ChatView: Render ToolCallDetailsModal(toolCall)
统一工具调用 UI 组件的更新后类图classDiagram
class ToolCall {
+string id
+string name
+string status
+Record arguments
+unknown result
+string error
+string timestamp
}
class ToolCallPill {
+ToolCall toolCall
+string className
+function onClick()
+function getPillStyle()
}
class ToolCallDetails {
+ToolCall toolCall
+boolean isImageLightboxOpen
+function getImageFromResult(result)
+function parseToolResult(result)
}
class ToolCallDetailsModal {
+ToolCall toolCall
+boolean open
+function onClose()
+function onConfirm(toolCallId)
+function onCancel(toolCallId)
+boolean isWaitingConfirmation
}
class AgentStepAccordion {
+PhaseExecution phase
+boolean isActive
+boolean isExpanded
+string selectedToolCallId
+function setSelectedToolCallId(id)
}
class ChatBubble {
+Message message
+boolean isCopied
+string selectedToolCallId
+function setSelectedToolCallId(id)
+function confirmToolCall(channelId, toolCallId)
+function cancelToolCall(channelId, toolCallId)
}
class JsonDisplay {
+unknown data
+boolean compact
+string variant
+boolean hideHeader
+boolean enableCharts
}
class ToolCallCard {
<<removed>>
}
ToolCallPill --> ToolCall : uses
ToolCallDetails --> ToolCall : renders
ToolCallDetailsModal --> ToolCallDetails : composes
ToolCallDetailsModal --> ToolCall : shows status
AgentStepAccordion --> ToolCallPill : renders pills
AgentStepAccordion --> ToolCallDetailsModal : opens modal
ChatBubble --> ToolCallPill : renders pills for tool messages
ChatBubble --> ToolCallDetailsModal : opens modal
ToolCallDetails --> JsonDisplay : uses for arguments,result
ToolCallCard .. ToolCall : legacy standalone card (removed)
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your Experience前往你的 dashboard 以:
Getting HelpOriginal review guide in EnglishReviewer's GuideStandardizes tool call rendering as clickable pills with a shared details modal across agent timeline and history, refactors message grouping to attach historical tool calls to agent execution phases, and introduces auth/landing UX, i18n, and auth flow utilities along with minor component cleanups. Sequence diagram for unified tool call rendering from history to UI pill and modalsequenceDiagram
participant Backend
participant MessageProcessor as messageProcessor
participant ChatView as ChatBubble
participant AgentTimeline as AgentStepAccordion
participant User
Backend->>MessageProcessor: messages[assistant, tool, ...]
activate MessageProcessor
MessageProcessor->>MessageProcessor: groupToolMessagesWithAssistant(messages)
Note over MessageProcessor: Track lastAssistantWithExecution
MessageProcessor->>MessageProcessor: For each assistant with agentExecution
MessageProcessor->>MessageProcessor: lastAssistantWithExecution = assistant
MessageProcessor->>MessageProcessor: For each tool message
alt tool has tool_call_id and lastAssistantWithExecution
MessageProcessor->>MessageProcessor: Attach ToolCall to target phase.toolCalls
MessageProcessor->>MessageProcessor: toolCallLookup[tool_call_id] = {toolCall, lastAssistantWithExecution}
else tool response without request yet
MessageProcessor->>MessageProcessor: Search phases.toolCalls for matching id
alt found
MessageProcessor->>MessageProcessor: toolCallLookup[tool_call_id] = {existingToolCall, lastAssistantWithExecution}
else not found
MessageProcessor->>MessageProcessor: Create placeholder ToolCall and assistant tool message
MessageProcessor->>MessageProcessor: toolCallLookup[tool_call_id] = {placeholder, toolMessage}
end
end
MessageProcessor-->>Backend: groupedMessages
deactivate MessageProcessor
Backend-->>ChatView: groupedMessages (includes assistant with agentExecution)
ChatView->>AgentTimeline: Render phases with phase.toolCalls
AgentTimeline->>AgentTimeline: Map phase.toolCalls to ToolCallPill
User->>AgentTimeline: Click ToolCallPill
AgentTimeline->>AgentTimeline: setSelectedToolCallId(id)
AgentTimeline->>AgentTimeline: selectedToolCall = phase.toolCalls.find(id)
AgentTimeline->>AgentTimeline: Render ToolCallDetailsModal(toolCall)
User->>AgentTimeline: Confirm or Cancel (optional)
alt Confirm
AgentTimeline->>ChatView: confirmToolCall(channelId, toolCallId)
else Cancel
AgentTimeline->>ChatView: cancelToolCall(channelId, toolCallId)
end
Note over ChatView: Tool call messages from history also render ToolCallPill
User->>ChatView: Click ToolCallPill
ChatView->>ChatView: setSelectedToolCallId(id)
ChatView->>ChatView: Render ToolCallDetailsModal(toolCall)
Updated class diagram for unified tool call UI componentsclassDiagram
class ToolCall {
+string id
+string name
+string status
+Record arguments
+unknown result
+string error
+string timestamp
}
class ToolCallPill {
+ToolCall toolCall
+string className
+function onClick()
+function getPillStyle()
}
class ToolCallDetails {
+ToolCall toolCall
+boolean isImageLightboxOpen
+function getImageFromResult(result)
+function parseToolResult(result)
}
class ToolCallDetailsModal {
+ToolCall toolCall
+boolean open
+function onClose()
+function onConfirm(toolCallId)
+function onCancel(toolCallId)
+boolean isWaitingConfirmation
}
class AgentStepAccordion {
+PhaseExecution phase
+boolean isActive
+boolean isExpanded
+string selectedToolCallId
+function setSelectedToolCallId(id)
}
class ChatBubble {
+Message message
+boolean isCopied
+string selectedToolCallId
+function setSelectedToolCallId(id)
+function confirmToolCall(channelId, toolCallId)
+function cancelToolCall(channelId, toolCallId)
}
class JsonDisplay {
+unknown data
+boolean compact
+string variant
+boolean hideHeader
+boolean enableCharts
}
class ToolCallCard {
<<removed>>
}
ToolCallPill --> ToolCall : uses
ToolCallDetails --> ToolCall : renders
ToolCallDetailsModal --> ToolCallDetails : composes
ToolCallDetailsModal --> ToolCall : shows status
AgentStepAccordion --> ToolCallPill : renders pills
AgentStepAccordion --> ToolCallDetailsModal : opens modal
ChatBubble --> ToolCallPill : renders pills for tool messages
ChatBubble --> ToolCallDetailsModal : opens modal
ToolCallDetails --> JsonDisplay : uses for arguments,result
ToolCallCard .. ToolCall : legacy standalone card (removed)
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey - 我发现了 2 个问题,并给出了一些整体反馈:
ToolCallDetails和ToolCallDetailsModal中新增的用户可见文案(例如「调用参数」「执行结果」「错误信息」「即将执行的参数」「取消」「确认执行」等标签)目前是直接硬编码为中文;建议像authStatus一样,将这些文案挪到 i18n 资源文件中,以便在不同语言下保持一致的本地化行为。- 在
ToolCallDetails中,parseToolResult和getImageFromResult在渲染路径中被直接调用;如果这些组件会被频繁使用或处理较大的数据载荷,建议使用useMemo对解析结果和派生的图片 URL 进行缓存,避免在每次重新渲染时重复进行 JSON 解析和对象检查。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- New user-facing strings in `ToolCallDetails` and `ToolCallDetailsModal` (e.g. labels like 调用参数, 执行结果, 错误信息, 即将执行的参数, 取消, 确认执行) are hard-coded in Chinese; consider moving these into the i18n resources similar to `authStatus` so they localize consistently.
- In `ToolCallDetails`, `parseToolResult` and `getImageFromResult` are executed inline in the render path; if these components are used frequently or with large payloads, consider memoizing the parsed result and derived image URL with `useMemo` to avoid repeated JSON parsing and object inspection on re-renders.
## Individual Comments
### Comment 1
<location> `web/src/components/layouts/components/ToolCallDetailsModal.tsx:73-79` </location>
<code_context>
+ </div>
+
+ <div className="px-4 py-3">
+ {isWaitingConfirmation &&
+ Object.keys(toolCall.arguments || {}).length > 0 && (
+ <div className="mb-3 rounded-md border border-neutral-200 bg-neutral-50 p-3 dark:border-neutral-800 dark:bg-neutral-900/30">
+ <div className="mb-2 text-xs font-medium text-neutral-700 dark:text-neutral-300">
+ 即将执行的参数:
+ </div>
+ <ToolCallDetails toolCall={toolCall} />
+ </div>
+ )}
</code_context>
<issue_to_address>
**issue:** ToolCallDetails is nested in a "参数" wrapper and will repeat argument details.
With `status === "waiting_confirmation"`, the modal adds a wrapper labeled `即将执行的参数:` around `<ToolCallDetails>`, which already includes its own "调用参数" section. This will show the arguments twice for pre‑execution calls. To avoid duplication, either pass a prop so `ToolCallDetails` renders only the raw arguments (no extra headings) in this modal, or move the arguments-only markup into the modal and reserve `ToolCallDetails` for post‑execution display.
</issue_to_address>
### Comment 2
<location> `web/src/components/animate-ui/components/texts/typewriter.tsx:93-100` </location>
<code_context>
+ {...props}
+ >
+ <span>{displayedText}</span>
+ {cursor && (
+ <motion.span
+ data-slot="typewriter-cursor"
+ variants={cursorVariants}
+ animate="blinking"
+ className={cn(
+ "ml-1 inline-block font-light",
+ isComplete ? "opacity-100" : "opacity-100",
+ )}
+ aria-hidden="true"
</code_context>
<issue_to_address>
**nitpick:** Cursor visibility logic is redundant and doesn’t distinguish between typing and completed states.
`className` currently uses `isComplete ? "opacity-100" : "opacity-100"`, so the conditional has no effect and just adds noise to the state logic. Either change the cursor behavior when `isComplete` is true (e.g., different variant/animation or opacity) or remove the conditional altogether.
</issue_to_address>帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进之后的审查建议。
Original comment in English
Hey - I've found 2 issues, and left some high level feedback:
- New user-facing strings in
ToolCallDetailsandToolCallDetailsModal(e.g. labels like 调用参数, 执行结果, 错误信息, 即将执行的参数, 取消, 确认执行) are hard-coded in Chinese; consider moving these into the i18n resources similar toauthStatusso they localize consistently. - In
ToolCallDetails,parseToolResultandgetImageFromResultare executed inline in the render path; if these components are used frequently or with large payloads, consider memoizing the parsed result and derived image URL withuseMemoto avoid repeated JSON parsing and object inspection on re-renders.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- New user-facing strings in `ToolCallDetails` and `ToolCallDetailsModal` (e.g. labels like 调用参数, 执行结果, 错误信息, 即将执行的参数, 取消, 确认执行) are hard-coded in Chinese; consider moving these into the i18n resources similar to `authStatus` so they localize consistently.
- In `ToolCallDetails`, `parseToolResult` and `getImageFromResult` are executed inline in the render path; if these components are used frequently or with large payloads, consider memoizing the parsed result and derived image URL with `useMemo` to avoid repeated JSON parsing and object inspection on re-renders.
## Individual Comments
### Comment 1
<location> `web/src/components/layouts/components/ToolCallDetailsModal.tsx:73-79` </location>
<code_context>
+ </div>
+
+ <div className="px-4 py-3">
+ {isWaitingConfirmation &&
+ Object.keys(toolCall.arguments || {}).length > 0 && (
+ <div className="mb-3 rounded-md border border-neutral-200 bg-neutral-50 p-3 dark:border-neutral-800 dark:bg-neutral-900/30">
+ <div className="mb-2 text-xs font-medium text-neutral-700 dark:text-neutral-300">
+ 即将执行的参数:
+ </div>
+ <ToolCallDetails toolCall={toolCall} />
+ </div>
+ )}
</code_context>
<issue_to_address>
**issue:** ToolCallDetails is nested in a "参数" wrapper and will repeat argument details.
With `status === "waiting_confirmation"`, the modal adds a wrapper labeled `即将执行的参数:` around `<ToolCallDetails>`, which already includes its own "调用参数" section. This will show the arguments twice for pre‑execution calls. To avoid duplication, either pass a prop so `ToolCallDetails` renders only the raw arguments (no extra headings) in this modal, or move the arguments-only markup into the modal and reserve `ToolCallDetails` for post‑execution display.
</issue_to_address>
### Comment 2
<location> `web/src/components/animate-ui/components/texts/typewriter.tsx:93-100` </location>
<code_context>
+ {...props}
+ >
+ <span>{displayedText}</span>
+ {cursor && (
+ <motion.span
+ data-slot="typewriter-cursor"
+ variants={cursorVariants}
+ animate="blinking"
+ className={cn(
+ "ml-1 inline-block font-light",
+ isComplete ? "opacity-100" : "opacity-100",
+ )}
+ aria-hidden="true"
</code_context>
<issue_to_address>
**nitpick:** Cursor visibility logic is redundant and doesn’t distinguish between typing and completed states.
`className` currently uses `isComplete ? "opacity-100" : "opacity-100"`, so the conditional has no effect and just adds noise to the state logic. Either change the cursor behavior when `isComplete` is true (e.g., different variant/animation or opacity) or remove the conditional altogether.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- Move tool-call UI strings into i18n (app.chat.toolCall.*) for en/zh/ja - Memoize tool result parsing and image URL derivation in ToolCallDetails - Avoid duplicate argument headings in ToolCallDetailsModal for waiting_confirmation - Remove redundant typewriter cursor className conditional and fix unused state var
* feat: add drag-and-drop agent reordering and auto-update on version mismatch (#206) * fix: resolve i18next missing key warnings in TierInfoModal Replace hardcoded Chinese strings with proper i18n translation keys in the tier selector component. This fixes console warnings about missing translation keys when using the zh locale. - Add speedLabels, reasoningLabels, and features keys to app.json - Add multiplier key to tierSelector in all locales - Add recommended key to common.json in all locales - Refactor TierInfoModal.tsx to use translation key references Co-Authored-By: Claude <noreply@anthropic.com> * feat: add drag-and-drop agent reordering Integrate dnd-kit into existing AgentList and AgentListItem components to support drag-and-drop reordering in both the sidebar and spatial focused view. Backend: - Add sort_order field to Agent model - Add PATCH endpoint for bulk reordering agents - Add migration for sort_order column Frontend: - Add sortable prop to AgentList with DndContext/SortableContext - Add dragHandleProps to AgentListItem for drag behavior - Use plain div (not motion.div) when sortable to avoid animation conflicts - Use set-based comparison for state sync (only reset on add/remove) - Add reorderAgents action to agentSlice * feat: auto-refresh frontend on version mismatch with backend Add automatic update mechanism that detects when the frontend version doesn't match the backend version and refreshes the page to fetch the latest assets. This ensures users with cached frontends always get updated code without manually clearing their browser cache. - Add useAutoUpdate hook with retry logic (max 3 attempts) - Add UpdateOverlay component for update feedback - Clear service workers and caches before reload - Store retry state in localStorage to prevent infinite loops * fix: address PR review feedback for drag-and-drop agent reordering - Move AgentList state sync from render to useEffect to prevent render loops - Add isDraggingRef to prevent backend sync during active drag operations - Restore keyboard accessibility to CompactAgentListItem (role, tabIndex, aria-pressed, onKeyDown handler) - Guard localStorage writes with try/catch in useAutoUpdate to handle restricted environments --------- Co-authored-by: Claude <noreply@anthropic.com> * fix: resolve version mismatch causing refresh loops in test environment (#207) * fix: move the comment to correct position * fix: fix the test environment version * fix: remove auto-update feature to prevent refresh loops The auto-update mechanism causes infinite refresh loops when frontend and backend versions mismatch in test environment. Remove the feature entirely until a more robust solution is implemented. - Delete useAutoUpdate hook and UpdateOverlay component - Remove AutoUpdateWrapper from App.tsx * Fix: Unify tool call UI (pills in agent timeline) for streaming + refresh (#208) * feat: add simple landing page and logout button * fix(web): unify tool call rendering in agent timeline after refresh; add landing page - Render tool calls as pills with a shared details modal (args/results/errors) - Attach historical tool_call tool messages into agentExecution phases instead of standalone messages - Remove legacy ToolCallCard-based rendering path * fix(web): address review feedback for tool call modal + typewriter - Move tool-call UI strings into i18n (app.chat.toolCall.*) for en/zh/ja - Memoize tool result parsing and image URL derivation in ToolCallDetails - Avoid duplicate argument headings in ToolCallDetailsModal for waiting_confirmation - Remove redundant typewriter cursor className conditional and fix unused state var * feat: message editing and deletion (#209) * feat: add message editing and deletion with truncate-regenerate flow - Add PATCH /messages/{id} endpoint for editing user messages - Add DELETE /messages/{id} endpoint for deleting any message - Add regenerate WebSocket handler for re-running agent after edit - Add edit/delete UI to ChatBubble with hover actions - Add i18n translations for en/zh/ja Includes fixes from code review: - Fix pre-deduction error handling to skip dispatch on any failure - Reset responding state before regeneration to prevent stuck UI - Add message ownership verification before edit/delete operations * fix: improve the code according to sourcery review * fix: use version+SHA for beta image tags to ensure unique deployments (#211) The previous approach used only the pyproject.toml version (e.g., 1.0.16) which caused Kubernetes to not pull new images when multiple commits used the same version tag. Now uses format: {version}-{short-sha} (e.g., 1.0.16-f13e3c0) Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com> * feat: conversation interrupt/abort functionality (#212) * feat: add conversation interrupt/abort functionality - Add stop button and Escape key shortcut to abort streaming generation - Implement Redis-based signaling between API server and Celery worker - Worker checks abort signal every 0.5s and gracefully stops streaming - Save partial content and perform partial billing on abort - Add visual indicator for cancelled/aborted messages - Add timeout fallback (10s) to reset UI if backend doesn't respond - Add i18n strings for stop/stopping/escToStop in en/zh/ja * fix: address abort feature edge cases from code review - Clear stale abort signals at task start to prevent race condition when user reconnects quickly after disconnect - Finalize AgentRun with 'failed' status on unhandled exceptions to ensure consistent DB state across all exit paths - Move time import to module level (was inline import) * fix: address Sourcery review feedback for abort feature - Reuse existing Redis client for abort checks instead of creating new connections on each tick (performance improvement) - Fix potential Redis connection leaks in ABORT and disconnect handlers by using try/finally pattern - Track and cleanup abort timeout in frontend to prevent stale timers from racing with subsequent abort requests * Preserve phase text when copying streamed output (#210) * Preserve phase text when copying streamed output * Extract agent phase content helper * fix: use existing PhaseExecution type and correct useMemo dependencies - Replace inline PhaseWithStreamedContent type with Pick<PhaseExecution, 'streamedContent'> for type consistency across the codebase - Fix useMemo dependency array to use agentExecution?.phases instead of agentExecution to ensure proper recalculation when phases array changes * Fix streaming agent messages and prevent deleting non-persisted messages (#213) * Fix message deletion for agent executions * Address Sourcery review: stricter UUID validation and safer id assignment - Add isValidUuid utility with canonical UUID pattern (8-4-4-4-12 format) to replace loose regex that accepted invalid strings like all-hyphens - Fix streaming_start handler to only set id when eventData.id is truthy, preventing accidental overwrites with undefined/null - Improve delete guard with contextual messages ("still streaming" vs "not saved yet") and change notification type to warning - Add comprehensive tests for isValidUuid covering valid UUIDs, client IDs, invalid formats, and edge cases Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com> --------- Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com> * fix: emit message_saved event after stream abort (#215) * fix: emit message_saved event after stream abort When a user interrupts a streaming response, the message is saved to the database but the frontend never receives the message_saved event. This leaves the message with a temporary stream_ prefix ID, preventing deletion until page refresh. Now the MESSAGE_SAVED event is emitted after db.commit() in the abort handler, before STREAM_ABORTED, so the frontend updates the message ID to the real UUID and deletion works immediately. Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com> * fix: always show latest topic when clicking an agent Unify sidebar and spatial workspace to use the same logic for selecting topics. Both now fetch from backend and always show the most recently updated topic (by updated_at) instead of remembering previously active topics. Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com> * feat: improve message editing UX with edit-only option and assistant editing - Add "Edit" and "Edit & Regenerate" dropdown options for user messages - Allow editing assistant messages (content-only, no regeneration) - Add copy button to user messages - Move assistant message actions to top-right for better UX - Add auto-resizing textarea for editing long messages - Update backend to support truncate_and_regenerate flag Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: extract message content resolution into dedicated module Extract scattered content resolution logic into core/chat/messageContent.ts with two main utilities: - resolveMessageContent(): Single source of truth for content priority - getMessageDisplayMode(): Explicit rendering mode determination This refactoring: - Reduces ChatBubble.tsx complexity (60+ line IIFE → 30 line switch) - Fixes inconsistency between copy/edit and display logic - Makes content source priority explicit and documented - Adds guard for empty content to avoid rendering empty divs - Improves maintainability with testable pure functions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com> * fix: deep research structured output and recursion limit (#216) - Use function_calling method for structured output in clarify node. The default json_mode doesn't work with Claude models via GPUGEEK provider. Claude supports tool/function calling natively but not OpenAI's response_format JSON mode. - Increase recursion_limit from 25 to 50 in agent.astream() to handle complex research tasks with more iterations. Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: xinquiry(SII) <100398322+xinquiry@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
* feat: add drag-and-drop agent reordering and auto-update on version mismatch (#206) * fix: resolve i18next missing key warnings in TierInfoModal Replace hardcoded Chinese strings with proper i18n translation keys in the tier selector component. This fixes console warnings about missing translation keys when using the zh locale. - Add speedLabels, reasoningLabels, and features keys to app.json - Add multiplier key to tierSelector in all locales - Add recommended key to common.json in all locales - Refactor TierInfoModal.tsx to use translation key references Co-Authored-By: Claude <noreply@anthropic.com> * feat: add drag-and-drop agent reordering Integrate dnd-kit into existing AgentList and AgentListItem components to support drag-and-drop reordering in both the sidebar and spatial focused view. Backend: - Add sort_order field to Agent model - Add PATCH endpoint for bulk reordering agents - Add migration for sort_order column Frontend: - Add sortable prop to AgentList with DndContext/SortableContext - Add dragHandleProps to AgentListItem for drag behavior - Use plain div (not motion.div) when sortable to avoid animation conflicts - Use set-based comparison for state sync (only reset on add/remove) - Add reorderAgents action to agentSlice * feat: auto-refresh frontend on version mismatch with backend Add automatic update mechanism that detects when the frontend version doesn't match the backend version and refreshes the page to fetch the latest assets. This ensures users with cached frontends always get updated code without manually clearing their browser cache. - Add useAutoUpdate hook with retry logic (max 3 attempts) - Add UpdateOverlay component for update feedback - Clear service workers and caches before reload - Store retry state in localStorage to prevent infinite loops * fix: address PR review feedback for drag-and-drop agent reordering - Move AgentList state sync from render to useEffect to prevent render loops - Add isDraggingRef to prevent backend sync during active drag operations - Restore keyboard accessibility to CompactAgentListItem (role, tabIndex, aria-pressed, onKeyDown handler) - Guard localStorage writes with try/catch in useAutoUpdate to handle restricted environments --------- Co-authored-by: Claude <noreply@anthropic.com> * fix: resolve version mismatch causing refresh loops in test environment (#207) * fix: move the comment to correct position * fix: fix the test environment version * fix: remove auto-update feature to prevent refresh loops The auto-update mechanism causes infinite refresh loops when frontend and backend versions mismatch in test environment. Remove the feature entirely until a more robust solution is implemented. - Delete useAutoUpdate hook and UpdateOverlay component - Remove AutoUpdateWrapper from App.tsx * Fix: Unify tool call UI (pills in agent timeline) for streaming + refresh (#208) * feat: add simple landing page and logout button * fix(web): unify tool call rendering in agent timeline after refresh; add landing page - Render tool calls as pills with a shared details modal (args/results/errors) - Attach historical tool_call tool messages into agentExecution phases instead of standalone messages - Remove legacy ToolCallCard-based rendering path * fix(web): address review feedback for tool call modal + typewriter - Move tool-call UI strings into i18n (app.chat.toolCall.*) for en/zh/ja - Memoize tool result parsing and image URL derivation in ToolCallDetails - Avoid duplicate argument headings in ToolCallDetailsModal for waiting_confirmation - Remove redundant typewriter cursor className conditional and fix unused state var * feat: message editing and deletion (#209) * feat: add message editing and deletion with truncate-regenerate flow - Add PATCH /messages/{id} endpoint for editing user messages - Add DELETE /messages/{id} endpoint for deleting any message - Add regenerate WebSocket handler for re-running agent after edit - Add edit/delete UI to ChatBubble with hover actions - Add i18n translations for en/zh/ja Includes fixes from code review: - Fix pre-deduction error handling to skip dispatch on any failure - Reset responding state before regeneration to prevent stuck UI - Add message ownership verification before edit/delete operations * fix: improve the code according to sourcery review * fix: use version+SHA for beta image tags to ensure unique deployments (#211) The previous approach used only the pyproject.toml version (e.g., 1.0.16) which caused Kubernetes to not pull new images when multiple commits used the same version tag. Now uses format: {version}-{short-sha} (e.g., 1.0.16-f13e3c0) Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com> * feat: conversation interrupt/abort functionality (#212) * feat: add conversation interrupt/abort functionality - Add stop button and Escape key shortcut to abort streaming generation - Implement Redis-based signaling between API server and Celery worker - Worker checks abort signal every 0.5s and gracefully stops streaming - Save partial content and perform partial billing on abort - Add visual indicator for cancelled/aborted messages - Add timeout fallback (10s) to reset UI if backend doesn't respond - Add i18n strings for stop/stopping/escToStop in en/zh/ja * fix: address abort feature edge cases from code review - Clear stale abort signals at task start to prevent race condition when user reconnects quickly after disconnect - Finalize AgentRun with 'failed' status on unhandled exceptions to ensure consistent DB state across all exit paths - Move time import to module level (was inline import) * fix: address Sourcery review feedback for abort feature - Reuse existing Redis client for abort checks instead of creating new connections on each tick (performance improvement) - Fix potential Redis connection leaks in ABORT and disconnect handlers by using try/finally pattern - Track and cleanup abort timeout in frontend to prevent stale timers from racing with subsequent abort requests * Preserve phase text when copying streamed output (#210) * Preserve phase text when copying streamed output * Extract agent phase content helper * fix: use existing PhaseExecution type and correct useMemo dependencies - Replace inline PhaseWithStreamedContent type with Pick<PhaseExecution, 'streamedContent'> for type consistency across the codebase - Fix useMemo dependency array to use agentExecution?.phases instead of agentExecution to ensure proper recalculation when phases array changes * Fix streaming agent messages and prevent deleting non-persisted messages (#213) * Fix message deletion for agent executions * Address Sourcery review: stricter UUID validation and safer id assignment - Add isValidUuid utility with canonical UUID pattern (8-4-4-4-12 format) to replace loose regex that accepted invalid strings like all-hyphens - Fix streaming_start handler to only set id when eventData.id is truthy, preventing accidental overwrites with undefined/null - Improve delete guard with contextual messages ("still streaming" vs "not saved yet") and change notification type to warning - Add comprehensive tests for isValidUuid covering valid UUIDs, client IDs, invalid formats, and edge cases Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com> --------- Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com> * fix: emit message_saved event after stream abort (#215) * fix: emit message_saved event after stream abort When a user interrupts a streaming response, the message is saved to the database but the frontend never receives the message_saved event. This leaves the message with a temporary stream_ prefix ID, preventing deletion until page refresh. Now the MESSAGE_SAVED event is emitted after db.commit() in the abort handler, before STREAM_ABORTED, so the frontend updates the message ID to the real UUID and deletion works immediately. Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com> * fix: always show latest topic when clicking an agent Unify sidebar and spatial workspace to use the same logic for selecting topics. Both now fetch from backend and always show the most recently updated topic (by updated_at) instead of remembering previously active topics. Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com> * feat: improve message editing UX with edit-only option and assistant editing - Add "Edit" and "Edit & Regenerate" dropdown options for user messages - Allow editing assistant messages (content-only, no regeneration) - Add copy button to user messages - Move assistant message actions to top-right for better UX - Add auto-resizing textarea for editing long messages - Update backend to support truncate_and_regenerate flag Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: extract message content resolution into dedicated module Extract scattered content resolution logic into core/chat/messageContent.ts with two main utilities: - resolveMessageContent(): Single source of truth for content priority - getMessageDisplayMode(): Explicit rendering mode determination This refactoring: - Reduces ChatBubble.tsx complexity (60+ line IIFE → 30 line switch) - Fixes inconsistency between copy/edit and display logic - Makes content source priority explicit and documented - Adds guard for empty content to avoid rendering empty divs - Improves maintainability with testable pure functions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <noreply@anthropic.com> * fix: deep research structured output and recursion limit (#216) - Use function_calling method for structured output in clarify node. The default json_mode doesn't work with Claude models via GPUGEEK provider. Claude supports tool/function calling natively but not OpenAI's response_format JSON mode. - Increase recursion_limit from 25 to 50 in agent.astream() to handle complex research tasks with more iterations. Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> * fix: attach thinking content to agent execution message When agent_start event arrives before thinking_start, it consumes the loading message. The thinking_start handler then couldn't find a loading message and created a separate thinking message, causing the thinking content to appear as a separate bubble below the agent response. Fix the thinking event handlers to also check for running agent execution messages and attach thinking content to them instead of creating separate messages. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: align thinking_end condition with thinking_chunk Use consistent condition `m.agentExecution?.status === "running"` in both thinking_chunk and thinking_end handlers for finding agent messages. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Problem
Tool calls were rendered inconsistently:
Agent component -> message -> tool calling
This mismatch confused users and made the tool-call UX feel different after a reload.
Solution
ToolCallPilleverywhere.waiting_confirmationtool calls is supported in the modal.role:"tool"messages are attached to the most recent reconstructedagentExecution.phases[].toolCallsinstead of becoming separate messages.Key changes
ToolCallDetails+ToolCallDetailsModalto share rich tool-call rendering.ToolCallPillclickable.AgentStepAccordion) and history tool messages (ChatBubble) use pills + modal.groupToolMessagesWithAssistant()to attach historical tool calls intoagentExecutionphases when available.ToolCallCard.Files
web/src/components/layouts/components/ToolCallDetails.tsxweb/src/components/layouts/components/ToolCallDetailsModal.tsxweb/src/components/layouts/components/ToolCallPill.tsxweb/src/components/layouts/components/AgentStepAccordion.tsxweb/src/components/layouts/components/ChatBubble.tsxweb/src/core/chat/messageProcessor.tsweb/src/components/layouts/components/ToolCallCard.tsxVerification
just lint-webjust type-webjust test-web "messageProcessor"Summary by Sourcery
在统一聊天时间线与历史记录中的工具调用渲染方式的同时,引入全新的未认证登录前落地页体验,并改进认证流程的用户体验和国际化(i18n)。
New Features:
ToolCallDetails和ToolCallDetailsModal组件,用于展示丰富的工具调用信息,包括参数、结果、错误,以及可选的图片预览/灯箱。Bug Fixes:
Enhancements:
ToolCallPill可点击,并在智能体步骤手风琴视图和聊天气泡中一致使用,点击后打开共享的工具调用详情弹窗,以便检查和确认流程。messageProcessor中改进工具调用与工具响应的分组方式,通过将它们附加到重建的执行时间线中,并在收到响应时更新已有的工具调用。XyzenAgent,移除systemAgentType过滤逻辑,并始终将完整的智能体列表传递给AgentList。AuthStatusUI 接入 i18n。Chores:
authFlow工具中,并从AuthErrorScreen中移除重复实现。Original summary in English
Summary by Sourcery
Unify tool call rendering across the chat timeline and history while introducing a new unauthenticated landing experience and improving auth UX and i18n.
New Features:
Bug Fixes:
Enhancements:
Chores: