Skip to content

Conversation

@xinquiry
Copy link
Collaborator

@xinquiry xinquiry commented Jan 30, 2026

Problem

Tool calls were rendered inconsistently:

  • During streaming, tool calls appeared inside the Agent execution timeline as pills (within the phase accordion).
  • After refresh/history load, tool calls were reconstructed as standalone assistant “tool messages”, so the UI became:
    Agent component -> message -> tool calling
    This mismatch confused users and made the tool-call UX feel different after a reload.

Solution

  1. Standardize tool-call UI to pills + details modal
  • Tool calls render as ToolCallPill everywhere.
  • Clicking a pill opens a modal showing full tool details (arguments/result/error + image preview/lightbox when present).
  • Confirm/Cancel for waiting_confirmation tool calls is supported in the modal.
  1. Route historical tool-call messages into the Agent timeline
  • Updated history processing so backend role:"tool" messages are attached to the most recent reconstructed agentExecution.phases[].toolCalls instead of becoming separate messages.
  • Tool call responses update the same phase-attached tool call when present.

Key changes

  • Added ToolCallDetails + ToolCallDetailsModal to share rich tool-call rendering.
  • Made ToolCallPill clickable.
  • Timeline (AgentStepAccordion) and history tool messages (ChatBubble) use pills + modal.
  • Updated groupToolMessagesWithAssistant() to attach historical tool calls into agentExecution phases when available.
  • Removed legacy ToolCallCard.

Files

  • Added: web/src/components/layouts/components/ToolCallDetails.tsx
  • Added: web/src/components/layouts/components/ToolCallDetailsModal.tsx
  • Updated: web/src/components/layouts/components/ToolCallPill.tsx
  • Updated: web/src/components/layouts/components/AgentStepAccordion.tsx
  • Updated: web/src/components/layouts/components/ChatBubble.tsx
  • Updated: web/src/core/chat/messageProcessor.ts
  • Removed: web/src/components/layouts/components/ToolCallCard.tsx

Verification

  • just lint-web
  • just type-web
  • just test-web "messageProcessor"

Summary by Sourcery

在统一聊天时间线与历史记录中的工具调用渲染方式的同时,引入全新的未认证登录前落地页体验,并改进认证流程的用户体验和国际化(i18n)。

New Features:

  • 新增 ToolCallDetailsToolCallDetailsModal 组件,用于展示丰富的工具调用信息,包括参数、结果、错误,以及可选的图片预览/灯箱。
  • 引入打字机效果文本组件和在认证前显示的动画落地页,包含用于启动应用或访问 GitHub 仓库的 CTA 按钮。

Bug Fixes:

  • 将历史工具调用消息路由到最近一次智能体执行阶段,使刷新后的历史记录将工具调用内联显示在智能体时间线中,而不是作为单独的助手消息显示。

Enhancements:

  • 使 ToolCallPill 可点击,并在智能体步骤手风琴视图和聊天气泡中一致使用,点击后打开共享的工具调用详情弹窗,以便检查和确认流程。
  • messageProcessor 中改进工具调用与工具响应的分组方式,通过将它们附加到重建的执行时间线中,并在收到响应时更新已有的工具调用。
  • 简化 XyzenAgent,移除 systemAgentType 过滤逻辑,并始终将完整的智能体列表传递给 AgentList
  • 调整登出行为,将认证状态标记为失败,以驱动新的落地页/认证流程。
  • 新增本地化的认证状态文案(包括新的登出条目),覆盖英文、日文和中文,并将 AuthStatus UI 接入 i18n。

Chores:

  • 抽取 OAuth 授权 URL 构造和登录发起逻辑到可复用的 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:

  • Add ToolCallDetails and ToolCallDetailsModal components to show rich tool call information, including arguments, results, errors, and optional image previews/lightbox.
  • Introduce a typewriter text component and animated landing page shown before authentication, with CTAs for starting the app or visiting the GitHub repo.

Bug Fixes:

  • Route historical tool call messages into the most recent agent execution phase so that refreshed histories display tool calls inline with the agent timeline instead of as separate assistant messages.

Enhancements:

  • Make ToolCallPill clickable and use it consistently in both the agent step accordion and chat bubbles, opening the shared tool call details modal for inspection and confirmation flows.
  • Improve grouping of tool calls and tool responses in messageProcessor by attaching them to reconstructed execution timelines and updating existing tool calls when responses arrive.
  • Simplify XyzenAgent by removing systemAgentType filtering and always passing the full agent list to AgentList.
  • Refine logout behavior to mark auth status as failed to drive the new landing/auth flow.
  • Add localized auth status strings (including new logout entry) in English, Japanese, and Chinese and wire AuthStatus UI to i18n.

Chores:

  • Extract OAuth authorize URL construction and login initiation logic into a reusable authFlow utility and remove the duplicate implementation from AuthErrorScreen.

…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
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 30, 2026

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)
Loading

统一工具调用 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)
Loading

File-Level Changes

Change Details Files
将工具调用 UI 统一为可点击的 pill,并在实时时间线和历史消息中共用同一个详情模态框。
  • 将 ToolCallPill 改为可点击的 motion 按钮,可选地打开详情,并通过是否存在 onClick 来控制 hover/光标行为。
  • 引入 ToolCallDetails,用于通过 JsonDisplay 渲染工具调用的参数、解析后的结果(包括对图片 URL / data URL 的特殊处理和灯箱)、错误信息以及时间戳。
  • 新增 ToolCallDetailsModal,在 HeadlessUI Dialog 中承载 ToolCallDetails,并在 status 为 waiting_confirmation 时提供确认/取消操作。
  • 更新 AgentStepAccordion,使其维护选中工具调用状态,在 pill 点击时打开 ToolCallDetailsModal,并在阶段头部内容中将工具调用渲染为 pills。
  • 更新 ChatBubble,将工具消息从使用 ToolCallCard 改为使用 ToolCallPill chips 来渲染,管理选中的工具调用和详情模态框,并通过模态框将确认/取消回调串联起来。
web/src/components/layouts/components/ToolCallPill.tsx
web/src/components/layouts/components/ToolCallDetails.tsx
web/src/components/layouts/components/ToolCallDetailsModal.tsx
web/src/components/layouts/components/AgentStepAccordion.tsx
web/src/components/layouts/components/ChatBubble.tsx
web/src/components/layouts/components/ToolCallCard.tsx
将历史工具调用消息路由到智能体执行时间线中,以匹配流式行为。
  • 扩展 groupToolMessagesWithAssistant,用于追踪最后一个带有 agentExecution 时间线的 assistant 消息。
  • 在遇到工具调用请求时,优先将其附加到当前或最后一个被追踪的 assistant 执行的当前/最后阶段,而不是创建独立的 assistant 工具消息。
  • 在处理工具响应时,尝试通过 lookup 或扫描最后一个 assistant 执行中的 phases 来解析工具调用,确保阶段上的工具调用在原位更新。
  • 保留之前的回退行为:当收到没有已知请求的响应时,创建占位工具调用。
web/src/core/chat/messageProcessor.ts
改进认证状态 UI,增加 i18n 支持,并添加显式的登出控制。
  • 将 AuthStatus 接入 react-i18next,用新建的 app.authStatus 命名空间中的 key 替换硬编码的中文字符串。
  • 新增 ArrowRightOnRectangleIcon 和 Logout 下拉项,点击后调用 logout 并关闭菜单。
  • 更新 tooltip、label 以及 token 相关标题以使用翻译后的字符串,并针对缺失 token、认证错误与未授权用户提供不同的提示文案。
web/src/components/features/AuthStatus.tsx
web/src/core/auth.ts
web/src/i18n/locales/en/app.json
web/src/i18n/locales/ja/app.json
web/src/i18n/locales/zh/app.json
引入新的动画登录落地页:在认证失败时展示,并仅在用户显式请求时显示认证错误界面。
  • 将认证失败时直接渲染 AuthErrorScreen 的逻辑改为条件渲染:先显示 LandingPage,只有在用户选择开始使用后才切换到 AuthErrorScreen。
  • 为 Xyzen 增加 showLandingPage prop(默认 true),并使用本地 state 跟踪何时应显示认证错误界面而不是落地页。
  • 在认证成功时重置内部的 showAuthScreen 标记,避免在后续认证状态切换中保留陈旧状态。
  • 修改 logout,将认证状态设为 failed,从而在登出后显示落地页,而不是无限加载界面。
web/src/app/App.tsx
web/src/core/auth.ts
web/src/app/landing/LandingPage.tsx
web/src/i18n/i18n.ts
web/src/i18n/locales/en/landing.json
web/src/i18n/locales/ja/landing.json
web/src/i18n/locales/zh/landing.json
将 OAuth URL 构建和登录发起流程重构为可复用的认证流程工具函数。
  • 把与 provider 相关的 authorize URL 构建逻辑迁移到新的 authFlow 工具中的 buildAuthorizeUrl 辅助函数。
  • 新增 initiateOAuthLogin,它会从 authService 查询认证状态/配置,构建 provider URL(包括 casdoor 的 CSRF state),持久化 state,并将浏览器重定向到 provider。
  • 确保不支持的 provider 和配置错误会通过抛出错误的方式暴露出来,同时记录到控制台。
web/src/utils/authFlow.ts
web/src/app/auth/AuthErrorScreen.tsx
简化智能体列表布局,并新增可复用的动画打字机文本组件以提升 UI 细节。
  • 从 XyzenAgent 中移除 systemAgentType prop 及其内部过滤逻辑,改为将原始 agents 数组直接传给 AgentList。
  • 更新 AppSide,使用简化后的 XyzenAgent,不再传递 systemAgentType prop。
  • 新增通用 Typewriter 组件,基于 motion 实现,支持配置文本、延迟、速度、光标行为以及完成回调,用于落地页主标题的打字机效果。
web/src/components/layouts/XyzenAgent.tsx
web/src/app/AppSide.tsx
web/src/components/animate-ui/components/texts/typewriter.tsx

Tips and commands

Interacting with Sourcery

  • 触发新的一轮评审: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub issue: 在回复某条评审评论时让 Sourcery 根据该评论创建 issue。你也可以在评论中回复 @sourcery-ai issue 来从该评审评论创建一个 issue。
  • 生成 pull request 标题: 在 pull request 标题中任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文任意位置写上 @sourcery-ai summary,即可在对应位置生成 PR 摘要。你也可以在 pull request 中评论 @sourcery-ai summary 来在任意时间(重新)生成摘要。
  • 生成 Reviewer's Guide: 在 pull request 中评论 @sourcery-ai guide,即可在任意时间(重新)生成评审指南。
  • 一次性解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,即可将所有 Sourcery 评论标记为已解决。如果你已经处理完所有评论且不希望再看到它们,这会很有用。
  • 一次性取消所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss,即可取消所有现有的 Sourcery 评审。尤其适用于你希望从新的评审开始的情况——别忘了再评论 @sourcery-ai review 来触发新的评审!

Customizing Your Experience

前往你的 dashboard 以:

  • 启用或禁用评审特性,例如 Sourcery 自动生成的 pull request 摘要、Reviewer's Guide 等。
  • 更改评审语言。
  • 添加、移除或编辑自定义评审指令。
  • 调整其他评审设置。

Getting Help

Original review guide in English

Reviewer's Guide

Standardizes 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 modal

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)
Loading

Updated class diagram for unified tool call UI components

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)
Loading

File-Level Changes

Change Details Files
Standardize tool-call UI as clickable pills with a shared details modal for both live timeline and historical messages.
  • Made ToolCallPill a clickable motion button that optionally opens details, with hover/cursor behavior gated on onClick.
  • Introduced ToolCallDetails to render tool-call arguments, parsed result (including special handling for image URLs/data URLs with lightbox), errors, and timestamp via JsonDisplay.
  • Added ToolCallDetailsModal to host ToolCallDetails in a HeadlessUI Dialog, including confirmation/cancellation actions when status is waiting_confirmation.
  • Updated AgentStepAccordion to maintain selected tool call state, open ToolCallDetailsModal on pill click, and render tool calls as pills inside the phase header content.
  • Updated ChatBubble to render tool messages as ToolCallPill chips instead of ToolCallCard, managing a selected tool call and details modal, and wiring confirm/cancel callbacks through the modal.
web/src/components/layouts/components/ToolCallPill.tsx
web/src/components/layouts/components/ToolCallDetails.tsx
web/src/components/layouts/components/ToolCallDetailsModal.tsx
web/src/components/layouts/components/AgentStepAccordion.tsx
web/src/components/layouts/components/ChatBubble.tsx
web/src/components/layouts/components/ToolCallCard.tsx
Route historical tool-call messages into the agent execution timeline to match streaming behavior.
  • Extended groupToolMessagesWithAssistant to track the last assistant message that has an agentExecution timeline.
  • When encountering tool-call requests, preferentially attach them to the current or last phase of the tracked assistant execution instead of creating standalone assistant tool messages.
  • On tool responses, attempt to resolve the tool call via the lookup or by scanning phases in the last assistant execution, ensuring phase-attached tool calls are updated in place.
  • Kept the previous fallback behavior of creating placeholder tool calls if a response arrives without a known request.
web/src/core/chat/messageProcessor.ts
Improve authentication status UI with i18n support and add explicit logout control.
  • Hooked AuthStatus into react-i18next and replaced hard-coded Chinese strings with keys from the new app.authStatus namespace.
  • Added ArrowRightOnRectangleIcon and a Logout dropdown item that calls logout and closes the menu.
  • Updated tooltips, labels, and token-related titles to use translated strings, including different messages for missing tokens and auth errors vs unauthorized users.
web/src/components/features/AuthStatus.tsx
web/src/core/auth.ts
web/src/i18n/locales/en/app.json
web/src/i18n/locales/ja/app.json
web/src/i18n/locales/zh/app.json
Introduce a new animated landing page shown on auth failure and only show the auth error screen on explicit request.
  • Replaced direct rendering of AuthErrorScreen on auth failure with conditional logic that shows LandingPage first, switching to AuthErrorScreen only after the user opts to get started.
  • Added a showLandingPage prop to Xyzen (default true) and local state to track when the auth screen should be shown instead of the landing page.
  • Reset the internal showAuthScreen flag when auth succeeds to avoid stale state on subsequent auth transitions.
  • Changed logout to set auth status to failed so the landing page is shown after logout instead of an indefinite loading screen.
web/src/app/App.tsx
web/src/core/auth.ts
web/src/app/landing/LandingPage.tsx
web/src/i18n/i18n.ts
web/src/i18n/locales/en/landing.json
web/src/i18n/locales/ja/landing.json
web/src/i18n/locales/zh/landing.json
Refactor OAuth URL building and login initiation into a reusable auth flow utility.
  • Moved the provider-specific authorize URL construction logic into a new buildAuthorizeUrl helper in a dedicated authFlow utility.
  • Added initiateOAuthLogin, which queries auth status/config from authService, builds the provider URL (including CSRF state for casdoor), persists state, and redirects the browser to the provider.
  • Ensured unsupported providers and configuration errors are surfaced via thrown errors and logged to console.
web/src/utils/authFlow.ts
web/src/app/auth/AuthErrorScreen.tsx
Simplify the agent list layout and add a reusable animated typewriter text component for UI polish.
  • Removed the systemAgentType prop and internal filtering logic from XyzenAgent, passing the raw agents array through to AgentList.
  • Updated AppSide to use the simplified XyzenAgent without the systemAgentType prop.
  • Added a generic Typewriter component based on motion, with configurable text, delay, speed, cursor behavior, and completion callback, used in the landing page hero title.
web/src/components/layouts/XyzenAgent.tsx
web/src/app/AppSide.tsx
web/src/components/animate-ui/components/texts/typewriter.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 2 个问题,并给出了一些整体反馈:

  • ToolCallDetailsToolCallDetailsModal 中新增的用户可见文案(例如「调用参数」「执行结果」「错误信息」「即将执行的参数」「取消」「确认执行」等标签)目前是直接硬编码为中文;建议像 authStatus 一样,将这些文案挪到 i18n 资源文件中,以便在不同语言下保持一致的本地化行为。
  • ToolCallDetails 中,parseToolResultgetImageFromResult 在渲染路径中被直接调用;如果这些组件会被频繁使用或处理较大的数据载荷,建议使用 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>

Sourcery 对开源项目永久免费——如果你觉得我们的代码审查有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进之后的审查建议。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
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
@xinquiry xinquiry merged commit 7db95f6 into test Jan 30, 2026
1 check passed
@xinquiry xinquiry deleted the feature/landing-page branch January 30, 2026 05:57
Mile-Away added a commit that referenced this pull request Feb 1, 2026
* 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>
xinquiry added a commit that referenced this pull request Feb 1, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants