From 6075d588e3ab3bbcea31374bd1ad7ca553d64fc5 Mon Sep 17 00:00:00 2001 From: viettranx Date: Mon, 16 Mar 2026 22:37:33 +0700 Subject: [PATCH] feat(ui): improve kanban UX, fix dialog scroll, remove delegation page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Kanban: reorder columns (blocked after pending), show blocked-by info on cards, clickable blocker links in task detail, framer-motion card animation between columns - Dialogs: standardize scroll pattern across all modals — header fixed, scrollbar flush with outer edge via negative margin trick - Remove delegation page, types, events, i18n, routes, and all references - Fix activity_logs NULL jsonb scan error (COALESCE) - Board header: show text labels on action buttons (desktop) --- internal/store/pg/activity_store.go | 2 +- ui/web/src/api/protocol.ts | 19 -- ui/web/src/components/layout/sidebar.tsx | 2 - ui/web/src/i18n/index.ts | 11 +- ui/web/src/i18n/locales/en/delegations.json | 41 ---- ui/web/src/i18n/locales/en/events.json | 1 - ui/web/src/i18n/locales/en/sidebar.json | 1 - ui/web/src/i18n/locales/vi/delegations.json | 41 ---- ui/web/src/i18n/locales/vi/events.json | 1 - ui/web/src/i18n/locales/vi/sidebar.json | 1 - ui/web/src/i18n/locales/zh/delegations.json | 41 ---- ui/web/src/i18n/locales/zh/events.json | 1 - ui/web/src/i18n/locales/zh/sidebar.json | 1 - ui/web/src/lib/constants.ts | 1 - ui/web/src/lib/query-keys.ts | 4 - .../src/pages/agents/agent-create-dialog.tsx | 2 +- .../channels/channel-instance-form-dialog.tsx | 4 +- ui/web/src/pages/cron/cron-form-dialog.tsx | 2 +- ui/web/src/pages/cron/cron-run-log-dialog.tsx | 66 ++--- .../custom-tools/custom-tool-form-dialog.tsx | 2 +- .../delegations/delegation-detail-dialog.tsx | 145 ----------- .../pages/delegations/delegations-page.tsx | 229 ------------------ .../delegations/hooks/use-delegations.ts | 48 ---- .../event-sections/delegation-event-cards.tsx | 154 ------------ .../events/event-sections/event-card.tsx | 5 +- .../events/event-sections/event-categories.ts | 9 - .../event-sections/event-detail-dialog.tsx | 6 +- ui/web/src/pages/events/events-page.tsx | 3 +- ui/web/src/pages/mcp/mcp-form-dialog.tsx | 2 +- ui/web/src/pages/mcp/mcp-grants-dialog.tsx | 2 +- ui/web/src/pages/mcp/mcp-tools-dialog.tsx | 2 +- .../pages/memory/kg-entity-detail-dialog.tsx | 2 +- ui/web/src/pages/memory/kg-extract-dialog.tsx | 2 +- .../src/pages/memory/memory-create-dialog.tsx | 2 +- .../pages/memory/memory-document-dialog.tsx | 2 +- .../src/pages/memory/memory-search-dialog.tsx | 2 +- .../pending-messages/message-list-dialog.tsx | 64 ++--- .../pages/providers/provider-form-dialog.tsx | 2 +- .../src/pages/skills/skill-detail-dialog.tsx | 2 +- ui/web/src/pages/teams/board/board-header.tsx | 11 +- .../pages/teams/board/team-info-dialog.tsx | 4 +- .../pages/teams/hooks/use-team-delegations.ts | 30 --- .../task-sections/task-detail-dialog.tsx | 4 +- ui/web/src/pages/teams/team-create-dialog.tsx | 2 +- .../src/pages/teams/team-delegations-tab.tsx | 110 --------- ui/web/src/pages/teams/team-version-modal.tsx | 5 +- ui/web/src/pages/teams/team-workspace-tab.tsx | 4 +- .../src/pages/traces/trace-detail-dialog.tsx | 4 +- ui/web/src/routes.tsx | 4 - ui/web/src/types/delegation.ts | 33 --- ui/web/src/types/team-events.ts | 93 ------- 51 files changed, 114 insertions(+), 1117 deletions(-) delete mode 100644 ui/web/src/i18n/locales/en/delegations.json delete mode 100644 ui/web/src/i18n/locales/vi/delegations.json delete mode 100644 ui/web/src/i18n/locales/zh/delegations.json delete mode 100644 ui/web/src/pages/delegations/delegation-detail-dialog.tsx delete mode 100644 ui/web/src/pages/delegations/delegations-page.tsx delete mode 100644 ui/web/src/pages/delegations/hooks/use-delegations.ts delete mode 100644 ui/web/src/pages/events/event-sections/delegation-event-cards.tsx delete mode 100644 ui/web/src/pages/teams/hooks/use-team-delegations.ts delete mode 100644 ui/web/src/pages/teams/team-delegations-tab.tsx delete mode 100644 ui/web/src/types/delegation.ts diff --git a/internal/store/pg/activity_store.go b/internal/store/pg/activity_store.go index ee400920..bbb3cc0d 100644 --- a/internal/store/pg/activity_store.go +++ b/internal/store/pg/activity_store.go @@ -38,7 +38,7 @@ func (s *PGActivityStore) List(ctx context.Context, opts store.ActivityListOpts) args = append(args, limit, opts.Offset) query := fmt.Sprintf( - `SELECT id, actor_type, actor_id, action, COALESCE(entity_type,''), COALESCE(entity_id,''), details, COALESCE(ip_address,''), created_at + `SELECT id, actor_type, actor_id, action, COALESCE(entity_type,''), COALESCE(entity_id,''), COALESCE(details, 'null'::jsonb), COALESCE(ip_address,''), created_at FROM activity_logs %s ORDER BY created_at DESC LIMIT $%d OFFSET $%d`, where, len(args)-1, len(args), ) diff --git a/ui/web/src/api/protocol.ts b/ui/web/src/api/protocol.ts index dd5f8a1a..a5629ee2 100644 --- a/ui/web/src/api/protocol.ts +++ b/ui/web/src/api/protocol.ts @@ -150,10 +150,6 @@ export const Methods = { TEAMS_WORKSPACE_READ: "teams.workspace.read", TEAMS_WORKSPACE_DELETE: "teams.workspace.delete", - // Delegation history - DELEGATIONS_LIST: "delegations.list", - DELEGATIONS_GET: "delegations.get", - // Phase 3+ - NICE TO HAVE LOGS_TAIL: "logs.tail", } as const; @@ -177,17 +173,6 @@ export const Events = { VOICEWAKE_CHANGED: "voicewake.changed", CONNECT_CHALLENGE: "connect.challenge", TALK_MODE: "talk.mode", - HANDOFF: "handoff", - - // Delegation lifecycle - DELEGATION_STARTED: "delegation.started", - DELEGATION_COMPLETED: "delegation.completed", - DELEGATION_FAILED: "delegation.failed", - DELEGATION_CANCELLED: "delegation.cancelled", - DELEGATION_PROGRESS: "delegation.progress", - DELEGATION_ACCUMULATED: "delegation.accumulated", - DELEGATION_ANNOUNCE: "delegation.announce", - DELEGATION_QUALITY_GATE_RETRY: "delegation.quality_gate.retry", // Team tasks TEAM_TASK_CREATED: "team.task.created", @@ -234,10 +219,6 @@ export const Events = { /** All event names relevant to team debug view */ export const TEAM_RELATED_EVENTS: Set = new Set([ - Events.DELEGATION_STARTED, Events.DELEGATION_COMPLETED, - Events.DELEGATION_FAILED, Events.DELEGATION_CANCELLED, - Events.DELEGATION_PROGRESS, Events.DELEGATION_ACCUMULATED, - Events.DELEGATION_ANNOUNCE, Events.DELEGATION_QUALITY_GATE_RETRY, Events.TEAM_TASK_CREATED, Events.TEAM_TASK_CLAIMED, Events.TEAM_TASK_COMPLETED, Events.TEAM_TASK_CANCELLED, Events.TEAM_TASK_REVIEWED, Events.TEAM_TASK_APPROVED, diff --git a/ui/web/src/components/layout/sidebar.tsx b/ui/web/src/components/layout/sidebar.tsx index e7c0ec0d..eaf95c32 100644 --- a/ui/web/src/components/layout/sidebar.tsx +++ b/ui/web/src/components/layout/sidebar.tsx @@ -17,7 +17,6 @@ import { Plug, Volume2, Cpu, - ArrowRightLeft, ClipboardList, HardDrive, Inbox, @@ -106,7 +105,6 @@ export function Sidebar({ collapsed, onNavItemClick }: SidebarProps) { - diff --git a/ui/web/src/i18n/index.ts b/ui/web/src/i18n/index.ts index 52407d8b..ff2ce1ea 100644 --- a/ui/web/src/i18n/index.ts +++ b/ui/web/src/i18n/index.ts @@ -18,7 +18,6 @@ import enChannels from "./locales/en/channels.json"; import enProviders from "./locales/en/providers.json"; import enTraces from "./locales/en/traces.json"; import enEvents from "./locales/en/events.json"; -import enDelegations from "./locales/en/delegations.json"; import enUsage from "./locales/en/usage.json"; import enApprovals from "./locales/en/approvals.json"; import enNodes from "./locales/en/nodes.json"; @@ -52,7 +51,6 @@ import viChannels from "./locales/vi/channels.json"; import viProviders from "./locales/vi/providers.json"; import viTraces from "./locales/vi/traces.json"; import viEvents from "./locales/vi/events.json"; -import viDelegations from "./locales/vi/delegations.json"; import viUsage from "./locales/vi/usage.json"; import viApprovals from "./locales/vi/approvals.json"; import viNodes from "./locales/vi/nodes.json"; @@ -86,7 +84,6 @@ import zhChannels from "./locales/zh/channels.json"; import zhProviders from "./locales/zh/providers.json"; import zhTraces from "./locales/zh/traces.json"; import zhEvents from "./locales/zh/events.json"; -import zhDelegations from "./locales/zh/delegations.json"; import zhUsage from "./locales/zh/usage.json"; import zhApprovals from "./locales/zh/approvals.json"; import zhNodes from "./locales/zh/nodes.json"; @@ -117,7 +114,7 @@ function getInitialLanguage(): string { const ns = [ "common", "sidebar", "topbar", "login", "overview", "chat", "agents", "teams", "sessions", "skills", "cron", "config", - "channels", "providers", "traces", "events", "delegations", + "channels", "providers", "traces", "events", "usage", "approvals", "nodes", "logs", "tools", "mcp", "tts", "setup", "memory", "storage", "pending-messages", "contacts", "activity", "api-keys", "cli-credentials", @@ -130,7 +127,7 @@ i18n.use(initReactI18next).init({ overview: enOverview, chat: enChat, agents: enAgents, teams: enTeams, sessions: enSessions, skills: enSkills, cron: enCron, config: enConfig, channels: enChannels, providers: enProviders, traces: enTraces, - events: enEvents, delegations: enDelegations, usage: enUsage, + events: enEvents, usage: enUsage, approvals: enApprovals, nodes: enNodes, logs: enLogs, tools: enTools, mcp: enMcp, tts: enTts, setup: enSetup, memory: enMemory, storage: enStorage, "pending-messages": enPendingMessages, @@ -142,7 +139,7 @@ i18n.use(initReactI18next).init({ overview: viOverview, chat: viChat, agents: viAgents, teams: viTeams, sessions: viSessions, skills: viSkills, cron: viCron, config: viConfig, channels: viChannels, providers: viProviders, traces: viTraces, - events: viEvents, delegations: viDelegations, usage: viUsage, + events: viEvents, usage: viUsage, approvals: viApprovals, nodes: viNodes, logs: viLogs, tools: viTools, mcp: viMcp, tts: viTts, setup: viSetup, memory: viMemory, storage: viStorage, "pending-messages": viPendingMessages, @@ -154,7 +151,7 @@ i18n.use(initReactI18next).init({ overview: zhOverview, chat: zhChat, agents: zhAgents, teams: zhTeams, sessions: zhSessions, skills: zhSkills, cron: zhCron, config: zhConfig, channels: zhChannels, providers: zhProviders, traces: zhTraces, - events: zhEvents, delegations: zhDelegations, usage: zhUsage, + events: zhEvents, usage: zhUsage, approvals: zhApprovals, nodes: zhNodes, logs: zhLogs, tools: zhTools, mcp: zhMcp, tts: zhTts, setup: zhSetup, memory: zhMemory, storage: zhStorage, "pending-messages": zhPendingMessages, diff --git a/ui/web/src/i18n/locales/en/delegations.json b/ui/web/src/i18n/locales/en/delegations.json deleted file mode 100644 index d3d6151d..00000000 --- a/ui/web/src/i18n/locales/en/delegations.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "title": "Delegations", - "description": "Agent delegation history and results", - "sourceFilterPlaceholder": "Source agent...", - "targetFilterPlaceholder": "Target agent...", - "filter": "Filter", - "emptyTitle": "No delegations", - "emptyDescription": "No delegation records found. Delegations are recorded when agents delegate tasks to other agents.", - "columns": { - "sourceTarget": "Source / Target", - "task": "Task", - "status": "Status", - "mode": "Mode", - "duration": "Duration", - "time": "Time" - }, - "statusFilter": { - "all": "All", - "pending": "Pending", - "running": "Running", - "completed": "Completed", - "failed": "Failed", - "cancelled": "Cancelled" - }, - "detail": { - "title": "Delegation Detail", - "notFound": "Delegation not found.", - "source": "Source:", - "target": "Target:", - "status": "Status:", - "mode": "Mode:", - "duration": "Duration:", - "iterations": "Iterations:", - "created": "Created:", - "completed": "Completed:", - "trace": "Trace:", - "task": "Task", - "result": "Result", - "error": "Error" - } -} diff --git a/ui/web/src/i18n/locales/en/events.json b/ui/web/src/i18n/locales/en/events.json index eac2eca6..cfcf25a5 100644 --- a/ui/web/src/i18n/locales/en/events.json +++ b/ui/web/src/i18n/locales/en/events.json @@ -11,7 +11,6 @@ "eventCountPlural": "{{count}} events", "categories": { "all": "All", - "delegation": "Delegation", "task": "Task", "message": "Message", "agent": "Agent", diff --git a/ui/web/src/i18n/locales/en/sidebar.json b/ui/web/src/i18n/locales/en/sidebar.json index 1fc8ad5e..b92a1904 100644 --- a/ui/web/src/i18n/locales/en/sidebar.json +++ b/ui/web/src/i18n/locales/en/sidebar.json @@ -26,7 +26,6 @@ "knowledgeGraph": "Knowledge Graph", "traces": "Traces", "realtimeEvents": "Realtime Events", - "delegations": "Delegations", "usage": "Usage", "logs": "Logs", "storage": "Storage", diff --git a/ui/web/src/i18n/locales/vi/delegations.json b/ui/web/src/i18n/locales/vi/delegations.json deleted file mode 100644 index 79fc54fd..00000000 --- a/ui/web/src/i18n/locales/vi/delegations.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "title": "Ủy quyền", - "description": "Lịch sử và kết quả ủy quyền agent", - "sourceFilterPlaceholder": "Agent nguồn...", - "targetFilterPlaceholder": "Agent đích...", - "filter": "Lọc", - "emptyTitle": "Không có ủy quyền", - "emptyDescription": "Không tìm thấy bản ghi ủy quyền. Ủy quyền được ghi lại khi agent ủy thác nhiệm vụ cho agent khác.", - "columns": { - "sourceTarget": "Nguồn / Đích", - "task": "Nhiệm vụ", - "status": "Trạng thái", - "mode": "Chế độ", - "duration": "Thời lượng", - "time": "Thời gian" - }, - "statusFilter": { - "all": "Tất cả", - "pending": "Đang chờ", - "running": "Đang chạy", - "completed": "Hoàn thành", - "failed": "Thất bại", - "cancelled": "Đã hủy" - }, - "detail": { - "title": "Chi tiết ủy quyền", - "notFound": "Không tìm thấy ủy quyền.", - "source": "Nguồn:", - "target": "Đích:", - "status": "Trạng thái:", - "mode": "Chế độ:", - "duration": "Thời lượng:", - "iterations": "Số lần lặp:", - "created": "Đã tạo:", - "completed": "Hoàn thành:", - "trace": "Theo dõi:", - "task": "Nhiệm vụ", - "result": "Kết quả", - "error": "Lỗi" - } -} diff --git a/ui/web/src/i18n/locales/vi/events.json b/ui/web/src/i18n/locales/vi/events.json index 897e3c5c..86c1fe72 100644 --- a/ui/web/src/i18n/locales/vi/events.json +++ b/ui/web/src/i18n/locales/vi/events.json @@ -11,7 +11,6 @@ "eventCountPlural": "{{count}} sự kiện", "categories": { "all": "Tất cả", - "delegation": "Ủy quyền", "task": "Nhiệm vụ", "message": "Tin nhắn", "agent": "Agent", diff --git a/ui/web/src/i18n/locales/vi/sidebar.json b/ui/web/src/i18n/locales/vi/sidebar.json index d3425fa1..0e42735f 100644 --- a/ui/web/src/i18n/locales/vi/sidebar.json +++ b/ui/web/src/i18n/locales/vi/sidebar.json @@ -26,7 +26,6 @@ "knowledgeGraph": "Knowledge Graph", "traces": "Theo dõi", "realtimeEvents": "Sự kiện thời gian thực", - "delegations": "Ủy quyền", "usage": "Sử dụng", "logs": "Nhật ký", "storage": "Lưu trữ", diff --git a/ui/web/src/i18n/locales/zh/delegations.json b/ui/web/src/i18n/locales/zh/delegations.json deleted file mode 100644 index 05644761..00000000 --- a/ui/web/src/i18n/locales/zh/delegations.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "title": "委托", - "description": "Agent委托历史和结果", - "sourceFilterPlaceholder": "源Agent...", - "targetFilterPlaceholder": "目标Agent...", - "filter": "筛选", - "emptyTitle": "暂无委托", - "emptyDescription": "暂无委托记录。当Agent将任务委托给其他Agent时将自动记录。", - "columns": { - "sourceTarget": "源 / 目标", - "task": "任务", - "status": "状态", - "mode": "模式", - "duration": "耗时", - "time": "时间" - }, - "statusFilter": { - "all": "全部", - "pending": "等待中", - "running": "运行中", - "completed": "已完成", - "failed": "已失败", - "cancelled": "已取消" - }, - "detail": { - "title": "委托详情", - "notFound": "未找到委托。", - "source": "源:", - "target": "目标:", - "status": "状态:", - "mode": "模式:", - "duration": "耗时:", - "iterations": "迭代次数:", - "created": "创建时间:", - "completed": "完成时间:", - "trace": "追踪:", - "task": "任务", - "result": "结果", - "error": "错误" - } -} diff --git a/ui/web/src/i18n/locales/zh/events.json b/ui/web/src/i18n/locales/zh/events.json index 580976eb..6ec85b16 100644 --- a/ui/web/src/i18n/locales/zh/events.json +++ b/ui/web/src/i18n/locales/zh/events.json @@ -11,7 +11,6 @@ "eventCountPlural": "{{count}} 个事件", "categories": { "all": "全部", - "delegation": "委托", "task": "任务", "message": "消息", "agent": "Agent", diff --git a/ui/web/src/i18n/locales/zh/sidebar.json b/ui/web/src/i18n/locales/zh/sidebar.json index f10a72d1..885b7c7a 100644 --- a/ui/web/src/i18n/locales/zh/sidebar.json +++ b/ui/web/src/i18n/locales/zh/sidebar.json @@ -19,7 +19,6 @@ "config": "配置", "cron": "定时任务", "customTools": "自定义工具", - "delegations": "委托", "logs": "日志", "mcpServers": "MCP 服务器", "memory": "记忆", diff --git a/ui/web/src/lib/constants.ts b/ui/web/src/lib/constants.ts index 936bd156..f8b10e4f 100644 --- a/ui/web/src/lib/constants.ts +++ b/ui/web/src/lib/constants.ts @@ -15,7 +15,6 @@ export const ROUTES = { TRACES: "/traces", TRACE_DETAIL: "/traces/:id", EVENTS: "/events", - DELEGATIONS: "/delegations", USAGE: "/usage", CHANNELS: "/channels", CHANNEL_DETAIL: "/channels/:id", diff --git a/ui/web/src/lib/query-keys.ts b/ui/web/src/lib/query-keys.ts index 4fe5500c..5d597fcd 100644 --- a/ui/web/src/lib/query-keys.ts +++ b/ui/web/src/lib/query-keys.ts @@ -62,10 +62,6 @@ export const queryKeys = { all: ["usage"] as const, records: (params: Record) => ["usage", "records", params] as const, }, - delegations: { - all: ["delegations"] as const, - list: (params: Record) => ["delegations", params] as const, - }, teams: { all: ["teams"] as const, detail: (id: string) => ["teams", id] as const, diff --git a/ui/web/src/pages/agents/agent-create-dialog.tsx b/ui/web/src/pages/agents/agent-create-dialog.tsx index f4729620..253c1f30 100644 --- a/ui/web/src/pages/agents/agent-create-dialog.tsx +++ b/ui/web/src/pages/agents/agent-create-dialog.tsx @@ -122,7 +122,7 @@ export function AgentCreateDialog({ open, onOpenChange, onCreate }: AgentCreateD {t("create.title")} -
+
diff --git a/ui/web/src/pages/channels/channel-instance-form-dialog.tsx b/ui/web/src/pages/channels/channel-instance-form-dialog.tsx index 655ad09b..95ff237f 100644 --- a/ui/web/src/pages/channels/channel-instance-form-dialog.tsx +++ b/ui/web/src/pages/channels/channel-instance-form-dialog.tsx @@ -260,7 +260,7 @@ export function ChannelInstanceFormDialog({ {/* === FORM STEP === */} {step === "form" && ( <> -
+
setName(slugify(e.target.value))} placeholder={t("form.keyPlaceholder")} disabled={!!instance} /> @@ -380,7 +380,7 @@ export function ChannelInstanceFormDialog({ {/* === CONFIG STEP (rendered by registered component) === */} {step === "config" && createdInstanceId && ConfigStep && ( <> -
+
{t("create.title")} -
+
setName(slugify(e.target.value))} placeholder={t("create.namePlaceholder")} /> diff --git a/ui/web/src/pages/cron/cron-run-log-dialog.tsx b/ui/web/src/pages/cron/cron-run-log-dialog.tsx index 1a54d21e..2181e934 100644 --- a/ui/web/src/pages/cron/cron-run-log-dialog.tsx +++ b/ui/web/src/pages/cron/cron-run-log-dialog.tsx @@ -28,43 +28,45 @@ export function CronRunLogDialog({ return ( - + {t("runLog.title", { name: jobName })} - {loading && entries.length === 0 ? ( -
-
-
- ) : entries.length === 0 ? ( -

- {t("runLog.noHistory")} -

- ) : ( -
- {entries.map((entry: CronRunLogEntry, i: number) => ( -
-
- - {formatDate(new Date(entry.ts))} - - - {entry.status || "unknown"} - +
+ {loading && entries.length === 0 ? ( +
+
+
+ ) : entries.length === 0 ? ( +

+ {t("runLog.noHistory")} +

+ ) : ( +
+ {entries.map((entry: CronRunLogEntry, i: number) => ( +
+
+ + {formatDate(new Date(entry.ts))} + + + {entry.status || "unknown"} + +
+ {entry.summary && ( +

{entry.summary}

+ )} + {entry.error && ( +

{entry.error}

+ )}
- {entry.summary && ( -

{entry.summary}

- )} - {entry.error && ( -

{entry.error}

- )} -
- ))} -
- )} + ))} +
+ )} +
); diff --git a/ui/web/src/pages/custom-tools/custom-tool-form-dialog.tsx b/ui/web/src/pages/custom-tools/custom-tool-form-dialog.tsx index de5cd8f5..64ac92c1 100644 --- a/ui/web/src/pages/custom-tools/custom-tool-form-dialog.tsx +++ b/ui/web/src/pages/custom-tools/custom-tool-form-dialog.tsx @@ -97,7 +97,7 @@ export function CustomToolFormDialog({ open, onOpenChange, tool, onSubmit }: Cus {tool ? t("custom.form.editTitle") : t("custom.form.createTitle")} -
+
setName(slugify(e.target.value))} placeholder={t("custom.form.namePlaceholder")} /> diff --git a/ui/web/src/pages/delegations/delegation-detail-dialog.tsx b/ui/web/src/pages/delegations/delegation-detail-dialog.tsx deleted file mode 100644 index d688ab42..00000000 --- a/ui/web/src/pages/delegations/delegation-detail-dialog.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { useState, useEffect } from "react"; -import { useTranslation } from "react-i18next"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Badge } from "@/components/ui/badge"; -import { formatDate, formatDuration } from "@/lib/format"; -import { TraceDetailDialog } from "@/pages/traces/trace-detail-dialog"; -import type { DelegationHistoryRecord } from "@/types/delegation"; -import type { TraceData, SpanData } from "@/types/trace"; - -interface DelegationDetailDialogProps { - delegationId: string; - onClose: () => void; - getDelegation: (id: string) => Promise; - getTrace: (traceId: string) => Promise<{ trace: TraceData; spans: SpanData[] } | null>; -} - -export function DelegationDetailDialog({ delegationId, onClose, getDelegation, getTrace }: DelegationDetailDialogProps) { - const { t } = useTranslation("delegations"); - const [record, setRecord] = useState(null); - const [loading, setLoading] = useState(true); - const [viewingTraceId, setViewingTraceId] = useState(null); - - useEffect(() => { - setLoading(true); - getDelegation(delegationId) - .then((r) => setRecord(r)) - .finally(() => setLoading(false)); - }, [delegationId, getDelegation]); - - const statusVariant = - record?.status === "completed" - ? "success" - : record?.status === "failed" - ? "destructive" - : record?.status === "running" || record?.status === "pending" - ? "info" - : "secondary"; - - return ( - onClose()}> - - - {t("detail.title")} - - - {loading && !record ? ( -
-
-
- ) : !record ? ( -

{t("detail.notFound")}

- ) : ( -
- {/* Summary grid */} -
-
- {t("detail.source")}{" "} - {record.source_agent_key || record.source_agent_id.slice(0, 8)} -
-
- {t("detail.target")}{" "} - {record.target_agent_key || record.target_agent_id.slice(0, 8)} -
-
- {t("detail.status")}{" "} - {record.status} -
-
- {t("detail.mode")}{" "} - {record.mode} -
-
- {t("detail.duration")}{" "} - {formatDuration(record.duration_ms)} -
-
- {t("detail.iterations")}{" "} - {record.iterations} -
-
- {t("detail.created")}{" "} - {formatDate(record.created_at)} -
-
- {t("detail.completed")}{" "} - {record.completed_at ? formatDate(record.completed_at) : "—"} -
-
- - {record.trace_id && ( -
- {t("detail.trace")}{" "} - -
- )} - - {/* Task */} -
-

{t("detail.task")}

-
{record.task}
-
- - {/* Result */} - {record.result && ( -
-

{t("detail.result")}

-
-                  {record.result}
-                
-
- )} - - {/* Error */} - {record.error && ( -
-

{t("detail.error")}

-

{record.error}

-
- )} -
- )} - - - {viewingTraceId && ( - setViewingTraceId(null)} - getTrace={getTrace} - onNavigateTrace={setViewingTraceId} - /> - )} -
- ); -} diff --git a/ui/web/src/pages/delegations/delegations-page.tsx b/ui/web/src/pages/delegations/delegations-page.tsx deleted file mode 100644 index 6adf320b..00000000 --- a/ui/web/src/pages/delegations/delegations-page.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import { useState, useEffect, useRef, useCallback } from "react"; -import { useTranslation } from "react-i18next"; -import { ArrowRightLeft, RefreshCw, Search } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; -import { Input } from "@/components/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { PageHeader } from "@/components/shared/page-header"; -import { EmptyState } from "@/components/shared/empty-state"; -import { Pagination } from "@/components/shared/pagination"; -import { TableSkeleton } from "@/components/shared/loading-skeleton"; -import { useWsEvent } from "@/hooks/use-ws-event"; -import { useDebouncedCallback } from "@/hooks/use-debounced-callback"; -import { Events } from "@/api/protocol"; -import { formatDate, formatDuration } from "@/lib/format"; -import { useDelegations } from "./hooks/use-delegations"; -import { useTraces } from "@/pages/traces/hooks/use-traces"; -import { DelegationDetailDialog } from "./delegation-detail-dialog"; -import { useMinLoading } from "@/hooks/use-min-loading"; -import { useDeferredLoading } from "@/hooks/use-deferred-loading"; -import type { AgentEventPayload } from "@/types/chat"; -import type { DelegationHistoryRecord } from "@/types/delegation"; - -export function DelegationsPage() { - const { t } = useTranslation("delegations"); - const { t: tc } = useTranslation("common"); - const { delegations, total, loading, load, getDelegation } = useDelegations(); - const { getTrace } = useTraces(); - const spinning = useMinLoading(loading); - const showSkeleton = useDeferredLoading(loading && delegations.length === 0); - const [sourceFilter, setSourceFilter] = useState(""); - const [targetFilter, setTargetFilter] = useState(""); - const [statusFilter, setStatusFilter] = useState("all"); - const [selectedId, setSelectedId] = useState(null); - const [page, setPage] = useState(1); - const [pageSize, setPageSize] = useState(20); - - const totalPages = Math.max(1, Math.ceil(total / pageSize)); - - const sourceRef = useRef(sourceFilter); - sourceRef.current = sourceFilter; - const targetRef = useRef(targetFilter); - targetRef.current = targetFilter; - const statusRef = useRef(statusFilter); - statusRef.current = statusFilter; - const pageRef = useRef(page); - pageRef.current = page; - const pageSizeRef = useRef(pageSize); - pageSizeRef.current = pageSize; - - const buildFilters = useCallback(() => ({ - source_agent_id: sourceRef.current || undefined, - target_agent_id: targetRef.current || undefined, - status: statusRef.current !== "all" ? statusRef.current : undefined, - limit: pageSizeRef.current, - offset: (pageRef.current - 1) * pageSizeRef.current, - }), []); - - useEffect(() => { - load({ limit: pageSize, offset: (page - 1) * pageSize }); - }, [load, page, pageSize]); - - const handleRefresh = () => load(buildFilters()); - - const debouncedRefresh = useDebouncedCallback(() => load(buildFilters()), 3000); - - const handleAgentEvent = useCallback( - (payload: unknown) => { - const event = payload as AgentEventPayload; - if (!event) return; - if (event.type === "run.completed" || event.type === "run.failed") { - debouncedRefresh(); - } - }, - [debouncedRefresh], - ); - - useWsEvent(Events.AGENT, handleAgentEvent); - - const handleFilterSubmit = (e: React.FormEvent) => { - e.preventDefault(); - setPage(1); - handleRefresh(); - }; - - return ( -
- - {tc("refresh")} - - } - /> - -
-
- - setSourceFilter(e.target.value)} - placeholder={t("sourceFilterPlaceholder")} - className="pl-9" - /> -
-
- - setTargetFilter(e.target.value)} - placeholder={t("targetFilterPlaceholder")} - className="pl-9" - /> -
- - -
- -
- {showSkeleton ? ( - - ) : delegations.length === 0 ? ( - - ) : ( -
- - - - - - - - - - - - - {delegations.map((d: DelegationHistoryRecord) => ( - setSelectedId(d.id)} - > - - - - - - - - ))} - -
{t("columns.sourceTarget")}{t("columns.task")}{t("columns.status")}{t("columns.mode")}{t("columns.duration")}{t("columns.time")}
- {d.source_agent_key || d.source_agent_id.slice(0, 8)} - → - {d.target_agent_key || d.target_agent_id.slice(0, 8)} - - {d.task} - - - - {d.mode} - - {formatDuration(d.duration_ms)} - - {formatDate(d.created_at)} -
- { setPageSize(size); setPage(1); }} - /> -
- )} -
- - {selectedId && ( - setSelectedId(null)} - getDelegation={getDelegation} - getTrace={getTrace} - /> - )} -
- ); -} - -function StatusBadge({ status }: { status: string }) { - const variant = - status === "completed" - ? "success" - : status === "failed" - ? "destructive" - : status === "running" || status === "pending" - ? "info" - : "secondary"; - - return {status || "unknown"}; -} diff --git a/ui/web/src/pages/delegations/hooks/use-delegations.ts b/ui/web/src/pages/delegations/hooks/use-delegations.ts deleted file mode 100644 index b8823b1e..00000000 --- a/ui/web/src/pages/delegations/hooks/use-delegations.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useState, useCallback } from "react"; -import { useHttp } from "@/hooks/use-ws"; -import type { DelegationHistoryRecord, DelegationListFilters } from "@/types/delegation"; - -export function useDelegations() { - const http = useHttp(); - const [delegations, setDelegations] = useState([]); - const [total, setTotal] = useState(0); - const [loading, setLoading] = useState(false); - - const load = useCallback( - async (filters: DelegationListFilters = {}) => { - setLoading(true); - try { - const params: Record = {}; - if (filters.source_agent_id) params.source_agent_id = filters.source_agent_id; - if (filters.target_agent_id) params.target_agent_id = filters.target_agent_id; - if (filters.team_id) params.team_id = filters.team_id; - if (filters.user_id) params.user_id = filters.user_id; - if (filters.status) params.status = filters.status; - if (filters.limit) params.limit = String(filters.limit); - if (filters.offset !== undefined) params.offset = String(filters.offset); - - const res = await http.get<{ records: DelegationHistoryRecord[]; total?: number }>("/v1/delegations", params); - setDelegations(res.records ?? []); - setTotal(res.total ?? 0); - } catch { - // ignore - } finally { - setLoading(false); - } - }, - [http], - ); - - const getDelegation = useCallback( - async (id: string): Promise => { - try { - return await http.get(`/v1/delegations/${id}`); - } catch { - return null; - } - }, - [http], - ); - - return { delegations, total, loading, load, getDelegation }; -} diff --git a/ui/web/src/pages/events/event-sections/delegation-event-cards.tsx b/ui/web/src/pages/events/event-sections/delegation-event-cards.tsx deleted file mode 100644 index 90ba7afe..00000000 --- a/ui/web/src/pages/events/event-sections/delegation-event-cards.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { Badge } from "@/components/ui/badge"; -import { formatDuration } from "@/lib/format"; -import type { TeamEventEntry } from "@/stores/use-team-event-store"; -import type { - DelegationEventPayload, - DelegationProgressPayload, - DelegationAccumulatedPayload, - DelegationAnnouncePayload, - QualityGateRetryPayload, -} from "@/types/team-events"; - -interface Props { - entry: TeamEventEntry; - resolveAgent: (keyOrId: string | undefined) => string; -} - -export function DelegationEventCard({ entry, resolveAgent }: Props) { - switch (entry.event) { - case "delegation.started": - case "delegation.completed": - case "delegation.failed": - case "delegation.cancelled": - return ; - case "delegation.progress": - return ; - case "delegation.accumulated": - return ; - case "delegation.announce": - return ; - case "delegation.quality_gate.retry": - return ; - default: - return
{JSON.stringify(entry.payload, null, 2)}
; - } -} - -function DelegationLifecycleCard({ entry, resolveAgent }: Props) { - const p = entry.payload as DelegationEventPayload; - const source = p.source_display_name || resolveAgent(p.source_agent_key); - const target = p.target_display_name || resolveAgent(p.target_agent_key); - return ( -
-
- {source} - → - {target} - {p.mode && ( - - {p.mode} - - )} -
- {p.task &&

{p.task}

} -
- {p.delegation_id && ( - - deleg: {p.delegation_id.slice(0, 8)} - - )} - {p.elapsed_ms != null && p.elapsed_ms > 0 && ( - {formatDuration(p.elapsed_ms)} - )} - {p.error && {p.error}} -
-
- ); -} - -type ResolverProp = { resolveAgent: (keyOrId: string | undefined) => string }; - -function DelegationProgressCard({ payload: p, resolveAgent }: { payload: DelegationProgressPayload } & ResolverProp) { - return ( -
-

- {resolveAgent(p.source_agent_key)} has {p.active_delegations.length} active delegation(s) -

-
- {p.active_delegations.map((d) => ( -
- {d.target_display_name || resolveAgent(d.target_agent_key)} - {formatDuration(d.elapsed_ms)} -
- ))} -
-
- ); -} - -function DelegationAccumulatedCard({ payload: p, resolveAgent }: { payload: DelegationAccumulatedPayload } & ResolverProp) { - return ( -
- {p.target_display_name || resolveAgent(p.target_agent_key)} - result accumulated, - {p.siblings_remaining} sibling(s) remaining - {p.elapsed_ms != null && p.elapsed_ms > 0 && ( - - ({formatDuration(p.elapsed_ms)}) - - )} -
- ); -} - -function DelegationAnnounceCard({ payload: p, resolveAgent }: { payload: DelegationAnnouncePayload } & ResolverProp) { - return ( -
-
- {p.source_display_name || resolveAgent(p.source_agent_key)} - announcing {p.results.length} result(s) - ({formatDuration(p.total_elapsed_ms)}) - {p.has_media && ( - media - )} - {p.completed_task_ids && p.completed_task_ids.length > 0 && ( - - {p.completed_task_ids.length} task(s) auto-completed - - )} -
-
- {p.results.map((r) => ( -
-
- {r.display_name || resolveAgent(r.agent_key)} - {r.has_media && ( - media - )} -
- {r.content_preview && ( -

{r.content_preview}

- )} -
- ))} -
-
- ); -} - -function QualityGateRetryCard({ payload: p, resolveAgent }: { payload: QualityGateRetryPayload } & ResolverProp) { - return ( -
-
- {resolveAgent(p.target_agent_key)} - - retry {p.attempt}/{p.max_retries} - - {p.gate_type} -
- {p.feedback && ( -

{p.feedback}

- )} -
- ); -} diff --git a/ui/web/src/pages/events/event-sections/event-card.tsx b/ui/web/src/pages/events/event-sections/event-card.tsx index 3d9edd45..2c8c9d8c 100644 --- a/ui/web/src/pages/events/event-sections/event-card.tsx +++ b/ui/web/src/pages/events/event-sections/event-card.tsx @@ -6,7 +6,6 @@ import { formatRelativeTime } from "@/lib/format"; import type { TeamEventEntry } from "@/stores/use-team-event-store"; import { useAgentResolver } from "./use-agent-resolver"; import { getCategoryConfig } from "./event-categories"; -import { DelegationEventCard } from "./delegation-event-cards"; import { TaskEventCard } from "./task-event-cards"; import { MessageEventCard } from "./message-event-card"; import { AgentEventCard } from "./agent-event-cards"; @@ -26,9 +25,7 @@ export function EventCard({ entry, resolveTeam }: EventCardProps) { const CategoryIcon = config.icon; let content: React.ReactNode; - if (event.startsWith("delegation.")) { - content = ; - } else if (event.startsWith("team.task.")) { + if (event.startsWith("team.task.")) { content = ; } else if (event === "team.message.sent") { content = ; diff --git a/ui/web/src/pages/events/event-sections/event-categories.ts b/ui/web/src/pages/events/event-sections/event-categories.ts index dcaa59af..2fbd9630 100644 --- a/ui/web/src/pages/events/event-sections/event-categories.ts +++ b/ui/web/src/pages/events/event-sections/event-categories.ts @@ -1,5 +1,4 @@ import { - ArrowRightLeft, ListTodo, MessageCircle, Bot, @@ -15,13 +14,6 @@ export interface EventCategoryConfig { iconColor: string; } -const delegation: EventCategoryConfig = { - label: "Delegation", - icon: ArrowRightLeft, - borderColor: "border-l-blue-500", - iconColor: "text-blue-500", -}; - const teamTask: EventCategoryConfig = { label: "Task", icon: ListTodo, @@ -58,7 +50,6 @@ const agentLink: EventCategoryConfig = { }; export function getCategoryConfig(event: string): EventCategoryConfig { - if (event.startsWith("delegation.")) return delegation; if (event.startsWith("team.task.")) return teamTask; if (event === "team.message.sent") return teamMessage; if (event === "agent") return agent; diff --git a/ui/web/src/pages/events/event-sections/event-detail-dialog.tsx b/ui/web/src/pages/events/event-sections/event-detail-dialog.tsx index 09972be8..8399bfe0 100644 --- a/ui/web/src/pages/events/event-sections/event-detail-dialog.tsx +++ b/ui/web/src/pages/events/event-sections/event-detail-dialog.tsx @@ -24,7 +24,7 @@ export function EventDetailDialog({ entry, onClose }: EventDetailDialogProps) { return ( !open && onClose()}> - + @@ -36,7 +36,7 @@ export function EventDetailDialog({ entry, onClose }: EventDetailDialogProps) { -
+
-
+
{tab === "content" && (
{detail && ( diff --git a/ui/web/src/pages/memory/memory-search-dialog.tsx b/ui/web/src/pages/memory/memory-search-dialog.tsx index fd97d4c9..c5bf27c6 100644 --- a/ui/web/src/pages/memory/memory-search-dialog.tsx +++ b/ui/web/src/pages/memory/memory-search-dialog.tsx @@ -65,7 +65,7 @@ export function MemorySearchDialog({ open, onOpenChange, agentId }: MemorySearch
-
+
{results.length === 0 ? (
{searching ? t("searchDialog.searching") : t("searchDialog.noResults")} diff --git a/ui/web/src/pages/pending-messages/message-list-dialog.tsx b/ui/web/src/pages/pending-messages/message-list-dialog.tsx index 597147b1..4cc343d2 100644 --- a/ui/web/src/pages/pending-messages/message-list-dialog.tsx +++ b/ui/web/src/pages/pending-messages/message-list-dialog.tsx @@ -32,7 +32,7 @@ export function MessageListDialog({ return ( onClose()}> - + {t("dialog.title", { name: group.group_title || group.history_key })} @@ -40,37 +40,39 @@ export function MessageListDialog({ - {loading ? ( -
-
-
- ) : messages.length === 0 ? ( -

{t("dialog.noMessages")}

- ) : ( -
- {messages.map((msg) => ( -
-
- {msg.sender} - ({msg.sender_id}) - {msg.is_summary && ( - {t("dialog.summary")} - )} - - {formatDate(msg.created_at)} - +
+ {loading ? ( +
+
+
+ ) : messages.length === 0 ? ( +

{t("dialog.noMessages")}

+ ) : ( +
+ {messages.map((msg) => ( +
+
+ {msg.sender} + ({msg.sender_id}) + {msg.is_summary && ( + {t("dialog.summary")} + )} + + {formatDate(msg.created_at)} + +
+

{msg.body}

-

{msg.body}

-
- ))} -
- )} + ))} +
+ )} +
); diff --git a/ui/web/src/pages/providers/provider-form-dialog.tsx b/ui/web/src/pages/providers/provider-form-dialog.tsx index 79f7a7a3..68cdd64e 100644 --- a/ui/web/src/pages/providers/provider-form-dialog.tsx +++ b/ui/web/src/pages/providers/provider-form-dialog.tsx @@ -145,7 +145,7 @@ export function ProviderFormDialog({ open, onOpenChange, provider, onSubmit, exi {isEdit ? t("form.editTitle") : t("form.createTitle")} {t("form.configure")} -
+
{/* Provider type selector — always shown in create mode */} {!isEdit && ( {t("detail.files")}} - + {skill.content ? (
diff --git a/ui/web/src/pages/teams/board/board-header.tsx b/ui/web/src/pages/teams/board/board-header.tsx index e0dfde2c..922bc2b4 100644 --- a/ui/web/src/pages/teams/board/board-header.tsx +++ b/ui/web/src/pages/teams/board/board-header.tsx @@ -58,19 +58,22 @@ export function BoardHeader({ team, members, onBack, onDelete, onSettings, onMem
{/* Actions */} - -
); diff --git a/ui/web/src/pages/teams/board/team-info-dialog.tsx b/ui/web/src/pages/teams/board/team-info-dialog.tsx index dcd44698..88a6859b 100644 --- a/ui/web/src/pages/teams/board/team-info-dialog.tsx +++ b/ui/web/src/pages/teams/board/team-info-dialog.tsx @@ -34,7 +34,7 @@ export function TeamInfoDialog({ return ( <> - + {team.name} @@ -52,6 +52,7 @@ export function TeamInfoDialog({ +
{/* Team overview */}
{team.description && ( @@ -72,6 +73,7 @@ export function TeamInfoDialog({ {/* Settings form */} { onSaved(); onOpenChange(false); }} /> +
diff --git a/ui/web/src/pages/teams/hooks/use-team-delegations.ts b/ui/web/src/pages/teams/hooks/use-team-delegations.ts deleted file mode 100644 index cf89101d..00000000 --- a/ui/web/src/pages/teams/hooks/use-team-delegations.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useState, useEffect, useCallback } from "react"; -import { useHttp } from "@/hooks/use-ws"; -import type { DelegationHistoryRecord } from "@/types/delegation"; - -export function useTeamDelegations(teamId: string) { - const http = useHttp(); - const [records, setRecords] = useState([]); - const [loading, setLoading] = useState(true); - - const load = useCallback(async () => { - setLoading(true); - try { - const res = await http.get<{ records: DelegationHistoryRecord[] }>("/v1/delegations", { - team_id: teamId, - limit: "50", - }); - setRecords(res.records ?? []); - } catch { - // ignore - } finally { - setLoading(false); - } - }, [http, teamId]); - - useEffect(() => { - load(); - }, [load]); - - return { records, loading, refresh: load }; -} diff --git a/ui/web/src/pages/teams/task-sections/task-detail-dialog.tsx b/ui/web/src/pages/teams/task-sections/task-detail-dialog.tsx index a4289a27..7616dcdc 100644 --- a/ui/web/src/pages/teams/task-sections/task-detail-dialog.tsx +++ b/ui/web/src/pages/teams/task-sections/task-detail-dialog.tsx @@ -68,7 +68,7 @@ export function TaskDetailDialog({ return ( onClose()}> - +
@@ -85,7 +85,7 @@ export function TaskDetailDialog({
-
+
{/* Subject */}

{t("tasks.detail.subject")}

diff --git a/ui/web/src/pages/teams/team-create-dialog.tsx b/ui/web/src/pages/teams/team-create-dialog.tsx index 9614bf30..333c0483 100644 --- a/ui/web/src/pages/teams/team-create-dialog.tsx +++ b/ui/web/src/pages/teams/team-create-dialog.tsx @@ -116,7 +116,7 @@ export function TeamCreateDialog({ open, onOpenChange, onCreate }: TeamCreateDia {t("create.title")} -
+
(null); - const spinning = useMinLoading(loading); - - if (loading && records.length === 0) return ; - - if (records.length === 0) { - return ( - - ); - } - - return ( - <> -
- -
-
- - - - - - - - - - - - {records.map((d) => ( - setSelectedId(d.id)} - > - - - - - - - ))} - -
{t("delegations.columns.sourceTarget")}{t("delegations.columns.task")}{t("delegations.columns.status")}{t("delegations.columns.duration")}{t("delegations.columns.time")}
- {d.source_agent_key || d.source_agent_id.slice(0, 8)} - → - {d.target_agent_key || d.target_agent_id.slice(0, 8)} - - {d.task} - - - - {formatDuration(d.duration_ms)} - - {formatDate(d.created_at)} -
-
- - {selectedId && ( - setSelectedId(null)} - getDelegation={getDelegation} - getTrace={getTrace} - /> - )} - - ); -} - -function StatusBadge({ status }: { status: string }) { - const variant = - status === "completed" - ? "success" - : status === "failed" - ? "destructive" - : status === "running" || status === "pending" - ? "info" - : "secondary"; - - return {status || "unknown"}; -} diff --git a/ui/web/src/pages/teams/team-version-modal.tsx b/ui/web/src/pages/teams/team-version-modal.tsx index 08562b94..817606f6 100644 --- a/ui/web/src/pages/teams/team-version-modal.tsx +++ b/ui/web/src/pages/teams/team-version-modal.tsx @@ -36,7 +36,7 @@ export function TeamVersionModal({ open, onOpenChange }: TeamVersionModalProps) return ( - + {t("settings.versionModal.title")} @@ -47,6 +47,7 @@ export function TeamVersionModal({ open, onOpenChange }: TeamVersionModalProps) {t("settings.versionModal.title")} +
{/* Version columns header */}
@@ -117,6 +118,8 @@ export function TeamVersionModal({ open, onOpenChange }: TeamVersionModalProps)

{t("settings.versionModal.betaNote")}

+
+