-
Notifications
You must be signed in to change notification settings - Fork 5
feat: add drag-and-drop agent reordering and auto-update on version mismatch #206
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
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
Reviewer's Guide在前端 UI 中为 Agent 实现拖拽排序,并在后端通过持久化的 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)
基于后端版本的前端自动更新时序图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
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
}
更新后的 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
文件级改动
Tips and commands与 Sourcery 交互
自定义你的体验访问你的 dashboard 以:
获取帮助Original review guide in EnglishReviewer's GuideImplements 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 persistencesequenceDiagram
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)
Sequence diagram for automatic frontend update based on backend versionsequenceDiagram
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
ER diagram for Agent sort_order and user relationshiperDiagram
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
}
Class diagram for updated agent ordering and auto-update logicclassDiagram
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
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 - 我发现了 1 个问题,并给出了一些整体性的反馈:
- 在 AgentList 中,当 ID 集合不同时,
setItems(agents)会在渲染过程中被直接调用,这是一个反模式,可能导致渲染循环;请将这段同步逻辑移到一个监听agents的useEffect中。 - 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>帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续的评审。
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 auseEffectthat watchesagentsinstead. - 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
agentsorder 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 appropriaterole="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>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
变更内容
简要描述本次 PR 的主要变更内容。
相关 Issue
请关联相关 Issue(如有):#编号
检查清单
默认已勾选,如不满足,请检查。
其他说明
如有特殊说明或注意事项,请补充。
Summary by Sourcery
为代理添加支持后端持久化的拖放排序功能,并在前后端版本不匹配时,引入自动的前端更新处理机制。
New Features:
sort_order字段、重新排序的 API 端点以及客户端存储逻辑,为每个用户持久化自定义代理排序。Enhancements:
sort_order值对获取到的代理进行排序,并在创建新代理时为其分配连续的sort_order。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:
Enhancements:
Build: