Skip to content

Conversation

@xinquiry
Copy link
Collaborator

@xinquiry xinquiry commented Jan 28, 2026

变更内容

  • 新功能
  • 修复 Bug
  • 增强重构
  • 其他(请描述)

简要描述本次 PR 的主要变更内容。

相关 Issue

请关联相关 Issue(如有):#编号

检查清单

默认已勾选,如不满足,请检查。

  • 已在本地测试通过
  • 已补充/更新相关文档
  • 已添加测试用例
  • 代码风格已经过 pre-commit 钩子检查

其他说明

如有特殊说明或注意事项,请补充。

Summary by Sourcery

为代理添加支持后端持久化的拖放排序功能,并在前后端版本不匹配时,引入自动的前端更新处理机制。

New Features:

  • 在详细视图和紧凑视图中启用可排序的代理列表,支持拖放操作和悬浮预览。
  • 通过新的 sort_order 字段、重新排序的 API 端点以及客户端存储逻辑,为每个用户持久化自定义代理排序。
  • 自动检测前端/后端版本不匹配,并触发清理缓存的更新流程,显示全屏更新中遮罩层。
  • 使用结构化的 i18n 键对不同语言本地化套餐速度、推理标签和功能描述。

Enhancements:

  • 根据 sort_order 值对获取到的代理进行排序,并在创建新代理时为其分配连续的 sort_order
  • 优化等级信息弹窗(tier info modal),使其使用基于键的标签和功能,以便更好地管理翻译。
  • 将代理重新排序功能接入空间布局和主代理布局,使 UI 能反映并控制已持久化的排序。
  • 扩展后端版本查询钩子,以支持在自动更新流程中按条件启用。

Build:

  • 添加 @dnd-kit/sortable 依赖,用于支持列表的拖放排序。
Original summary in English

Summary by Sourcery

Add drag-and-drop reordering for agents with backend persistence and introduce automatic frontend update handling based on backend version mismatches.

New Features:

  • Enable sortable agent lists in both detailed and compact views with drag-and-drop support and overlay previews.
  • Persist custom agent ordering per user via new sort_order field, reorder API endpoint, and client-side store logic.
  • Auto-detect frontend/backend version mismatches and trigger a cache-clearing update flow with a full-screen updating overlay.
  • Localize tier speed, reasoning labels, and feature descriptions using structured i18n keys across supported languages.

Enhancements:

  • Order fetched agents by their sort_order value and assign sequential sort_order when creating new agents.
  • Refine tier info modal to consume key-based labels and features for better translation management.
  • Wire agent reordering into spatial and main agent layouts so UI reflects and controls persisted ordering.
  • Extend backend version query hook to support conditional enabling for use in the auto-update flow.

Build:

  • Add @dnd-kit/sortable dependency for drag-and-drop list sorting support.

xinquiry and others added 4 commits January 26, 2026 15:40
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 <[email protected]>
   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
  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
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 28, 2026

Reviewer's Guide

在前端 UI 中为 Agent 实现拖拽排序,并在后端通过持久化的 sort_order 进行保存,同时引入一个基于后端版本的自动前端更新机制,并改进套餐信息(tier info)的 i18n 结构。

Agent 拖拽排序并在后端持久化的时序图

sequenceDiagram
  actor User
  participant AgentList
  participant FocusedView
  participant XyzenAgent
  participant useXyzen
  participant AgentSlice
  participant BackendAPI_reorder
  participant AgentRepository
  participant Database

  User->>AgentList: drag agent
  AgentList->>AgentList: update local items order
  AgentList-->>FocusedView: onReorder(agentIds)
  FocusedView->>useXyzen: reorderAgents(agentIds)
  useXyzen->>AgentSlice: reorderAgents(agentIds)
  AgentSlice->>AgentSlice: optimistic reorder in state
  AgentSlice->>BackendAPI_reorder: PUT /xyzen/api/v1/agents/reorder
  BackendAPI_reorder->>AgentRepository: update_agents_sort_order(user_id, agent_ids)
  AgentRepository->>Database: update Agent.sort_order
  Database-->>AgentRepository: ok
  AgentRepository-->>BackendAPI_reorder: flush
  BackendAPI_reorder-->>AgentSlice: 204 No Content
  AgentSlice-->>useXyzen: resolve
  useXyzen-->>FocusedView: resolve
  FocusedView-->>AgentList: no-op (UI already updated)
Loading

基于后端版本的前端自动更新时序图

sequenceDiagram
  participant Xyzen
  participant AutoUpdateWrapper
  participant useAutoUpdate
  participant useBackendVersion
  participant systemService
  participant BackendAPI_version
  participant clearCachesAndServiceWorkers
  participant Window

  Xyzen->>AutoUpdateWrapper: render with mainLayout
  AutoUpdateWrapper->>useAutoUpdate: enabled=true
  useAutoUpdate->>useBackendVersion: query backend version
  useBackendVersion->>systemService: getVersion()
  systemService->>BackendAPI_version: GET /system/version
  BackendAPI_version-->>systemService: {version}
  systemService-->>useBackendVersion: backendData
  useBackendVersion-->>useAutoUpdate: data, isLoading=false
  useAutoUpdate->>useAutoUpdate: compare getFrontendVersion() vs backendData.version
  alt versions_match
    useAutoUpdate->>useAutoUpdate: clearUpdateState()
    useAutoUpdate-->>AutoUpdateWrapper: {isUpdating=false,targetVersion=null}
    AutoUpdateWrapper-->>Xyzen: render children
  else versions_mismatch
    useAutoUpdate->>useAutoUpdate: getUpdateState() and increment retryCount
    useAutoUpdate->>useAutoUpdate: saveUpdateState({targetVersion,retryCount})
    useAutoUpdate->>clearCachesAndServiceWorkers: performUpdate(version)
    clearCachesAndServiceWorkers->>clearCachesAndServiceWorkers: unregister service workers
    clearCachesAndServiceWorkers->>clearCachesAndServiceWorkers: clear caches
    clearCachesAndServiceWorkers-->>useAutoUpdate: done
    useAutoUpdate->>Window: reload()
  end
Loading

Agent sort_order 与用户关系的 ER 图

erDiagram
  USER ||--o{ AGENT : owns

  USER {
    string id PK
  }

  AGENT {
    uuid id PK
    string user_id FK
    string name
    int sort_order
    datetime created_at
    datetime updated_at
  }
Loading

更新后的 Agent 排序与自动更新逻辑类图

classDiagram
  class Agent {
    +UUID id
    +string name
    +string user_id
    +string created_at
    +string updated_at
    +int sort_order
    +string prompt
  }

  class AgentRepository {
    +get_agents_by_user(user_id: string) Sequence~Agent~
    +create_agent(agent_data: AgentCreate, user_id: string) Agent
    +update_agents_sort_order(user_id: string, agent_ids: list~UUID~) None
  }

  class AgentSlice {
    +Agent[] agents
    +reorderAgents(agentIds: string[]) Promise~void~
    +fetchAgents() Promise~void~
  }

  class AgentList {
    +AgentListProps props
    +Agent[] items
    +string activeId
    +sortable: boolean
    +onReorder(agentIds: string[]) void
    +handleDragStart(event: DragStartEvent) void
    +handleDragEnd(event: DragEndEvent) void
    +handleDragCancel() void
    +renderOverlayItem() ReactNode
  }

  class SortableItem {
    +agent: Agent
    +variant: string
    +isMarketplacePublished: boolean
    +lastConversationTime: string
    +isSelected: boolean
    +status: string
    +role: string
  }

  class DragHandleProps {
    +attributes: React.HTMLAttributes~HTMLElement~
    +listeners: React.DOMAttributes~HTMLElement~
  }

  class AgentListItem {
    +agent: Agent
    +variant: string
    +isDragging: boolean
    +dragHandleProps: DragHandleProps
    +style: React.CSSProperties
    +setNodeRef(node: HTMLElement) void
  }

  class XyzenAgent {
    +agents: Agent[]
    +reorderAgents(agentIds: string[]) Promise~void~
    +handleReorder(agentIds: string[]) Promise~void~
  }

  class FocusedView {
    +agentsForList: Agent[]
    +reorderAgents(nodeIds: string[]) Promise~void~
    +handleReorder(nodeIds: string[]) Promise~void~
  }

  class AutoUpdateWrapper {
    +children: ReactNode
  }

  class useAutoUpdate {
    +isUpdating: boolean
    +targetVersion: string
    +performUpdate(version: string) Promise~void~
  }

  class UpdateOverlay {
    +targetVersion: string
  }

  AgentSlice --> Agent : manages
  AgentRepository --> Agent : persists
  AgentList --> Agent : displays
  AgentList --> SortableItem : wraps_items
  SortableItem --> AgentListItem : renders
  AgentListItem --> DragHandleProps : uses
  XyzenAgent --> AgentList : renders_detailed
  FocusedView --> AgentList : renders_compact
  XyzenAgent --> AgentSlice : calls_reorderAgents
  FocusedView --> AgentSlice : calls_reorderAgents
  AutoUpdateWrapper --> useAutoUpdate : uses
  AutoUpdateWrapper --> UpdateOverlay : renders_when_updating
Loading

文件级改动

Change Details Files
在详细视图和紧凑视图中新增可排序的拖拽 Agent 列表,并接入新的 reorder 回调。
  • 当启用 sortable 时,在 AgentList 外包裹 @dnd-kit 的 DndContext/SortableContext,配置鼠标/触摸传感器以及拖拽开始/结束的处理函数
  • 新增 SortableItem 包装组件,通过 dragHandlePropsstyleisDraggingsetNodeRef 将 useSortable 集成到 AgentListItem 中
  • 扩展 AgentList 的 props,增加 sortableonReorder,在本地维护条目顺序状态,并通过 createPortal 渲染 DragOverlay 以获得流畅的拖拽体验
  • 更新 AgentListItem 的详细和紧凑变体以支持拖拽视觉效果,在拖拽时禁用动画,并在拖拽过程中安全处理点击事件
web/src/components/agents/AgentList.tsx
web/src/components/agents/AgentListItem.tsx
web/src/components/agents/index.ts
通过后端 sort_order 持久化 Agent 排序,包括 API、持久化逻辑以及 UI 集成点。
  • 为 Agent 模型添加 sort_order 列和索引,并新增 Alembic migration,为现有 Agent 按用户补齐连续排序
  • 确保 get_agents_by_usersort_order 返回 Agent,并在创建 Agent 时分配下一个 sort_order
  • 添加 AgentRepository.update_agents_sort_order,以及新的 PUT /agents/reorder 端点,用于更新当前用户多个 Agent 的 sort_order
  • 在 Zustand 的 Agent slice 中暴露 reorderAgents,实现乐观本地重排、失败回滚,并将其接入 XyzenAgent 和 FocusedView 的 handleReorder 回调
  • 扩展 Agent 类型,增加可选的 sort_order 字段
service/app/models/agent.py
service/migrations/versions/5c6c342a4420_add_sort_order_to_agent.py
service/app/repos/agent.py
service/app/api/v1/agents.py
web/src/store/slices/agentSlice.ts
web/src/components/layouts/XyzenAgent.tsx
web/src/app/chat/spatial/FocusedView.tsx
web/src/types/agents.ts
优化套餐信息弹窗的 i18n,使速度/推理标签和功能文本在各语言中使用稳定的 key。
  • 将 TierInfo 修改为使用 speedLabelKeyreasoningLabelKeyfeatureKeys,而不是直接使用字面量字符串
  • 更新 TierInfoModal 渲染逻辑,通过命名空间 i18n key(app.tierSelector.speedLabelsreasoningLabelsfeatures)解析标签文本
  • 在 en/ja/zh 的 app.json 中添加对应的速度标签、推理标签和功能的翻译 key,并新增 “multiplier” 标签
web/src/components/layouts/components/TierInfoModal.tsx
web/src/i18n/locales/en/app.json
web/src/i18n/locales/ja/app.json
web/src/i18n/locales/zh/app.json
引入与后端版本绑定的前端客户端自动更新流程,并添加全屏更新覆盖层。
  • 为 useBackendVersion 添加可禁用查询的选项,并在新的 useAutoUpdate hook 中复用
  • 实现 useAutoUpdate hook,对比后端与前端版本,清理缓存和 service worker,在 localStorage 中跟踪重试次数,并重新加载应用
  • 创建全屏的 UpdateOverlay 组件,使用 i18n 字符串展示带版本信息的更新中文案和加载动画
  • 在主应用布局外部包裹 AutoUpdateWrapper,在更新进行中显示 UpdateOverlay,并从 features index 导出 UpdateOverlay
web/src/hooks/queries/useSystemQuery.ts
web/src/hooks/useAutoUpdate.ts
web/src/components/features/UpdateOverlay.tsx
web/src/components/features/index.ts
web/src/app/App.tsx
web/src/i18n/locales/en/app.json
web/src/i18n/locales/ja/app.json
web/src/i18n/locales/zh/app.json
为支持新行为进行一些 UI 和依赖的微调。
  • 在 web/package.json 中添加 @dnd-kit/sortable 依赖并更新 yarn.lock
  • 对 ToolSelector 和紧凑 Agent 列表项进行小的格式与 className 调整,并从 agents index 中导出 DragHandleProps
web/package.json
web/yarn.lock
web/src/components/agents/AgentListItem.tsx
web/src/components/agents/index.ts
web/src/components/layouts/components/ChatToolbar/ToolSelector.tsx

Tips and commands

与 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,可随时(重新)生成 Reviewer's Guide。
  • 批量解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,将标记所有 Sourcery 评论为已解决。当你已经处理完所有评论且不希望再看到它们时,这会很有用。
  • 批量忽略所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss,以忽略所有现有 Sourcery 评审。若你希望从一次全新的评审开始,尤其有用——别忘了随后评论 @sourcery-ai review 触发新的评审!

自定义你的体验

访问你的 dashboard 以:

  • 启用或禁用诸如 Sourcery 自动生成的 PR 摘要、Reviewer's Guide 等评审功能。
  • 修改评审语言。
  • 添加、移除或编辑自定义评审指令。
  • 调整其他评审设置。

获取帮助

Original review guide in English

Reviewer's Guide

Implements drag-and-drop reordering for agents in the UI backed by persistent sort_order in the backend, plus introduces an automatic frontend update mechanism based on backend version and improves tier info i18n structure.

Sequence diagram for agent drag-and-drop reordering with backend persistence

sequenceDiagram
  actor User
  participant AgentList
  participant FocusedView
  participant XyzenAgent
  participant useXyzen
  participant AgentSlice
  participant BackendAPI_reorder
  participant AgentRepository
  participant Database

  User->>AgentList: drag agent
  AgentList->>AgentList: update local items order
  AgentList-->>FocusedView: onReorder(agentIds)
  FocusedView->>useXyzen: reorderAgents(agentIds)
  useXyzen->>AgentSlice: reorderAgents(agentIds)
  AgentSlice->>AgentSlice: optimistic reorder in state
  AgentSlice->>BackendAPI_reorder: PUT /xyzen/api/v1/agents/reorder
  BackendAPI_reorder->>AgentRepository: update_agents_sort_order(user_id, agent_ids)
  AgentRepository->>Database: update Agent.sort_order
  Database-->>AgentRepository: ok
  AgentRepository-->>BackendAPI_reorder: flush
  BackendAPI_reorder-->>AgentSlice: 204 No Content
  AgentSlice-->>useXyzen: resolve
  useXyzen-->>FocusedView: resolve
  FocusedView-->>AgentList: no-op (UI already updated)
Loading

Sequence diagram for automatic frontend update based on backend version

sequenceDiagram
  participant Xyzen
  participant AutoUpdateWrapper
  participant useAutoUpdate
  participant useBackendVersion
  participant systemService
  participant BackendAPI_version
  participant clearCachesAndServiceWorkers
  participant Window

  Xyzen->>AutoUpdateWrapper: render with mainLayout
  AutoUpdateWrapper->>useAutoUpdate: enabled=true
  useAutoUpdate->>useBackendVersion: query backend version
  useBackendVersion->>systemService: getVersion()
  systemService->>BackendAPI_version: GET /system/version
  BackendAPI_version-->>systemService: {version}
  systemService-->>useBackendVersion: backendData
  useBackendVersion-->>useAutoUpdate: data, isLoading=false
  useAutoUpdate->>useAutoUpdate: compare getFrontendVersion() vs backendData.version
  alt versions_match
    useAutoUpdate->>useAutoUpdate: clearUpdateState()
    useAutoUpdate-->>AutoUpdateWrapper: {isUpdating=false,targetVersion=null}
    AutoUpdateWrapper-->>Xyzen: render children
  else versions_mismatch
    useAutoUpdate->>useAutoUpdate: getUpdateState() and increment retryCount
    useAutoUpdate->>useAutoUpdate: saveUpdateState({targetVersion,retryCount})
    useAutoUpdate->>clearCachesAndServiceWorkers: performUpdate(version)
    clearCachesAndServiceWorkers->>clearCachesAndServiceWorkers: unregister service workers
    clearCachesAndServiceWorkers->>clearCachesAndServiceWorkers: clear caches
    clearCachesAndServiceWorkers-->>useAutoUpdate: done
    useAutoUpdate->>Window: reload()
  end
Loading

ER diagram for Agent sort_order and user relationship

erDiagram
  USER ||--o{ AGENT : owns

  USER {
    string id PK
  }

  AGENT {
    uuid id PK
    string user_id FK
    string name
    int sort_order
    datetime created_at
    datetime updated_at
  }
Loading

Class diagram for updated agent ordering and auto-update logic

classDiagram
  class Agent {
    +UUID id
    +string name
    +string user_id
    +string created_at
    +string updated_at
    +int sort_order
    +string prompt
  }

  class AgentRepository {
    +get_agents_by_user(user_id: string) Sequence~Agent~
    +create_agent(agent_data: AgentCreate, user_id: string) Agent
    +update_agents_sort_order(user_id: string, agent_ids: list~UUID~) None
  }

  class AgentSlice {
    +Agent[] agents
    +reorderAgents(agentIds: string[]) Promise~void~
    +fetchAgents() Promise~void~
  }

  class AgentList {
    +AgentListProps props
    +Agent[] items
    +string activeId
    +sortable: boolean
    +onReorder(agentIds: string[]) void
    +handleDragStart(event: DragStartEvent) void
    +handleDragEnd(event: DragEndEvent) void
    +handleDragCancel() void
    +renderOverlayItem() ReactNode
  }

  class SortableItem {
    +agent: Agent
    +variant: string
    +isMarketplacePublished: boolean
    +lastConversationTime: string
    +isSelected: boolean
    +status: string
    +role: string
  }

  class DragHandleProps {
    +attributes: React.HTMLAttributes~HTMLElement~
    +listeners: React.DOMAttributes~HTMLElement~
  }

  class AgentListItem {
    +agent: Agent
    +variant: string
    +isDragging: boolean
    +dragHandleProps: DragHandleProps
    +style: React.CSSProperties
    +setNodeRef(node: HTMLElement) void
  }

  class XyzenAgent {
    +agents: Agent[]
    +reorderAgents(agentIds: string[]) Promise~void~
    +handleReorder(agentIds: string[]) Promise~void~
  }

  class FocusedView {
    +agentsForList: Agent[]
    +reorderAgents(nodeIds: string[]) Promise~void~
    +handleReorder(nodeIds: string[]) Promise~void~
  }

  class AutoUpdateWrapper {
    +children: ReactNode
  }

  class useAutoUpdate {
    +isUpdating: boolean
    +targetVersion: string
    +performUpdate(version: string) Promise~void~
  }

  class UpdateOverlay {
    +targetVersion: string
  }

  AgentSlice --> Agent : manages
  AgentRepository --> Agent : persists
  AgentList --> Agent : displays
  AgentList --> SortableItem : wraps_items
  SortableItem --> AgentListItem : renders
  AgentListItem --> DragHandleProps : uses
  XyzenAgent --> AgentList : renders_detailed
  FocusedView --> AgentList : renders_compact
  XyzenAgent --> AgentSlice : calls_reorderAgents
  FocusedView --> AgentSlice : calls_reorderAgents
  AutoUpdateWrapper --> useAutoUpdate : uses
  AutoUpdateWrapper --> UpdateOverlay : renders_when_updating
Loading

File-Level Changes

Change Details Files
Add sortable drag-and-drop agent lists in both detailed and compact views, wired to a new reorder callback.
  • Wrap AgentList in @dnd-kit DndContext/SortableContext when sortable is enabled, with sensors for mouse/touch and drag start/end handlers
  • Introduce SortableItem wrapper that integrates useSortable with AgentListItem via dragHandleProps, style, isDragging, and setNodeRef
  • Extend AgentList props with sortable and onReorder, maintain local item ordering state, and render a DragOverlay via createPortal for smooth dragging
  • Update AgentListItem detailed and compact variants to support drag visuals, disable motion animations while dragging, and handle clicks safely during drag
web/src/components/agents/AgentList.tsx
web/src/components/agents/AgentListItem.tsx
web/src/components/agents/index.ts
Persist agent ordering via backend sort_order, including API, persistence logic, and UI integration points.
  • Add sort_order column and index to Agent model plus Alembic migration to backfill existing agents with sequential per-user ordering
  • Ensure get_agents_by_user returns agents ordered by sort_order and assign next sort_order on agent creation
  • Add AgentRepository.update_agents_sort_order and a new PUT /agents/reorder endpoint to update multiple agents’ sort_order for the current user
  • Expose reorderAgents in the Zustand agent slice with optimistic local reordering, rollback on failure, and wire it into XyzenAgent and FocusedView handleReorder callbacks
  • Extend Agent type to include optional sort_order
service/app/models/agent.py
service/migrations/versions/5c6c342a4420_add_sort_order_to_agent.py
service/app/repos/agent.py
service/app/api/v1/agents.py
web/src/store/slices/agentSlice.ts
web/src/components/layouts/XyzenAgent.tsx
web/src/app/chat/spatial/FocusedView.tsx
web/src/types/agents.ts
Refine tier info modal i18n to use stable keys for speed/reasoning labels and feature texts across locales.
  • Change TierInfo to use speedLabelKey, reasoningLabelKey, and featureKeys instead of literal strings
  • Update TierInfoModal rendering to resolve labels via namespaced i18n keys under app.tierSelector.speedLabels, reasoningLabels, and features
  • Add corresponding translation keys for speed labels, reasoning labels, and features in en/ja/zh app.json and add a "multiplier" label
web/src/components/layouts/components/TierInfoModal.tsx
web/src/i18n/locales/en/app.json
web/src/i18n/locales/ja/app.json
web/src/i18n/locales/zh/app.json
Introduce client-side auto-update flow tied to backend version with a full-screen update overlay.
  • Add useBackendVersion options to allow disabling the query and reuse it from a new useAutoUpdate hook
  • Implement useAutoUpdate hook that compares backend and frontend versions, clears caches and service workers, tracks retries in localStorage, and reloads the app
  • Create UpdateOverlay fullscreen component that shows a spinner and versioned updating message using i18n strings
  • Wrap the main app layout in AutoUpdateWrapper to show UpdateOverlay while an update is in progress and export UpdateOverlay from the features index
web/src/hooks/queries/useSystemQuery.ts
web/src/hooks/useAutoUpdate.ts
web/src/components/features/UpdateOverlay.tsx
web/src/components/features/index.ts
web/src/app/App.tsx
web/src/i18n/locales/en/app.json
web/src/i18n/locales/ja/app.json
web/src/i18n/locales/zh/app.json
Minor UI and dependency adjustments to support new behavior.
  • Add @dnd-kit/sortable dependency to web/package.json and update yarn.lock
  • Small formatting and className tweaks for ToolSelector and compact agent list items, and export DragHandleProps from agents index
web/package.json
web/yarn.lock
web/src/components/agents/AgentListItem.tsx
web/src/components/agents/index.ts
web/src/components/layouts/components/ChatToolbar/ToolSelector.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

@xinquiry xinquiry changed the title Feature/better graph agent feat: add drag-and-drop agent reordering and auto-update on version mismatch Jan 28, 2026
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 - 我发现了 1 个问题,并给出了一些整体性的反馈:

  • 在 AgentList 中,当 ID 集合不同时,setItems(agents) 会在渲染过程中被直接调用,这是一个反模式,可能导致渲染循环;请将这段同步逻辑移到一个监听 agentsuseEffect 中。
  • AgentList 的本地排序状态只会在 agent ID 集合发生变化时同步,因此当后端驱动的重排(ID 相同但顺序不同)发生时,前端永远不会反映出来;建议在传入的 agents 顺序发生变化时也进行同步,而不仅仅是在成员变化时。
  • CompactAgentListItem 从 <button> 改成了 <div>,但没有添加键盘/aria 处理,这破坏了按钮语义和可访问性;请要么继续使用 button,要么添加合适的 role="button"tabIndex 和键盘事件处理。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In AgentList, `setItems(agents)` is called directly during render when the ID sets differ, which is an anti-pattern and can cause render loops; move this synchronization logic into a `useEffect` that watches `agents` instead.
- The AgentList local ordering state only syncs when the set of agent IDs changes, so backend-driven reorders (same IDs, different order) will never be reflected; consider also syncing when the incoming `agents` order changes, not just their membership.
- CompactAgentListItem was changed from a `<button>` to a `<div>` without adding keyboard/aria handling, which breaks button semantics and accessibility; either keep a button or add appropriate `role="button"`, `tabIndex`, and keyboard event handling.

## Individual Comments

### Comment 1
<location> `web/src/hooks/useAutoUpdate.ts:53-54` </location>
<code_context>
+/**
+ * Saves update state to localStorage
+ */
+function saveUpdateState(state: UpdateState): void {
+  localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
+}
+
</code_context>

<issue_to_address>
**suggestion:** Guard `localStorage.setItem` to avoid runtime errors in restricted environments.

`getUpdateState` already guards `localStorage` access with try/catch, but `saveUpdateState` (and `clearUpdateState`) do not. In environments with disabled storage or quota exceeded, `setItem`/`removeItem` can throw and break the auto-update flow.

Please wrap these calls in try/catch (mirroring `getUpdateState`) and treat persistence as best-effort so storage errors don’t crash auto-update.
</issue_to_address>

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

Hey - I've found 1 issue, and left some high level feedback:

  • In AgentList, setItems(agents) is called directly during render when the ID sets differ, which is an anti-pattern and can cause render loops; move this synchronization logic into a useEffect that watches agents instead.
  • The AgentList local ordering state only syncs when the set of agent IDs changes, so backend-driven reorders (same IDs, different order) will never be reflected; consider also syncing when the incoming agents order changes, not just their membership.
  • CompactAgentListItem was changed from a <button> to a <div> without adding keyboard/aria handling, which breaks button semantics and accessibility; either keep a button or add appropriate role="button", tabIndex, and keyboard event handling.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In AgentList, `setItems(agents)` is called directly during render when the ID sets differ, which is an anti-pattern and can cause render loops; move this synchronization logic into a `useEffect` that watches `agents` instead.
- The AgentList local ordering state only syncs when the set of agent IDs changes, so backend-driven reorders (same IDs, different order) will never be reflected; consider also syncing when the incoming `agents` order changes, not just their membership.
- CompactAgentListItem was changed from a `<button>` to a `<div>` without adding keyboard/aria handling, which breaks button semantics and accessibility; either keep a button or add appropriate `role="button"`, `tabIndex`, and keyboard event handling.

## Individual Comments

### Comment 1
<location> `web/src/hooks/useAutoUpdate.ts:53-54` </location>
<code_context>
+/**
+ * Saves update state to localStorage
+ */
+function saveUpdateState(state: UpdateState): void {
+  localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
+}
+
</code_context>

<issue_to_address>
**suggestion:** Guard `localStorage.setItem` to avoid runtime errors in restricted environments.

`getUpdateState` already guards `localStorage` access with try/catch, but `saveUpdateState` (and `clearUpdateState`) do not. In environments with disabled storage or quota exceeded, `setItem`/`removeItem` can throw and break the auto-update flow.

Please wrap these calls in try/catch (mirroring `getUpdateState`) and treat persistence as best-effort so storage errors don’t crash auto-update.
</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 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
@xinquiry xinquiry merged commit ec91951 into test Jan 28, 2026
1 check passed
@xinquiry xinquiry deleted the feature/better-graph-agent branch January 28, 2026 14:47
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