@arbidocs/blocks 0.5.38 → 0.5.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +10 -0
  2. package/dist/{ConnectedCallDock-V7DPSE6X.cjs → ConnectedCallDock-NGHSDLJS.cjs} +4 -4
  3. package/dist/{ConnectedCallDock-V7DPSE6X.cjs.map → ConnectedCallDock-NGHSDLJS.cjs.map} +1 -1
  4. package/dist/{ConnectedCallDock-CT3D6LLI.js → ConnectedCallDock-YJSO52N4.js} +4 -4
  5. package/dist/{ConnectedCallDock-CT3D6LLI.js.map → ConnectedCallDock-YJSO52N4.js.map} +1 -1
  6. package/dist/{ConnectedCallRoom-TNOCSTX2.cjs → ConnectedCallRoom-4XGFFYHE.cjs} +4 -4
  7. package/dist/{ConnectedCallRoom-TNOCSTX2.cjs.map → ConnectedCallRoom-4XGFFYHE.cjs.map} +1 -1
  8. package/dist/{ConnectedCallRoom-A27AM7IQ.js → ConnectedCallRoom-T6NS2MY4.js} +4 -4
  9. package/dist/{ConnectedCallRoom-A27AM7IQ.js.map → ConnectedCallRoom-T6NS2MY4.js.map} +1 -1
  10. package/dist/{ConnectedNativeDocxComparison-RWKFPJPJ.js → ConnectedNativeDocxComparison-52KQKJGY.js} +7 -7
  11. package/dist/{ConnectedNativeDocxComparison-RWKFPJPJ.js.map → ConnectedNativeDocxComparison-52KQKJGY.js.map} +1 -1
  12. package/dist/{ConnectedNativeDocxComparison-H2OZMWHT.cjs → ConnectedNativeDocxComparison-UVLZGS2V.cjs} +8 -8
  13. package/dist/{ConnectedNativeDocxComparison-H2OZMWHT.cjs.map → ConnectedNativeDocxComparison-UVLZGS2V.cjs.map} +1 -1
  14. package/dist/{ConnectedNativeDocxEditor-43BI5LN6.js → ConnectedNativeDocxEditor-BRBL3MOV.js} +6 -6
  15. package/dist/{ConnectedNativeDocxEditor-43BI5LN6.js.map → ConnectedNativeDocxEditor-BRBL3MOV.js.map} +1 -1
  16. package/dist/{ConnectedNativeDocxEditor-6DQEM6SD.cjs → ConnectedNativeDocxEditor-KHR5I7W6.cjs} +8 -8
  17. package/dist/{ConnectedNativeDocxEditor-6DQEM6SD.cjs.map → ConnectedNativeDocxEditor-KHR5I7W6.cjs.map} +1 -1
  18. package/dist/{ConnectedStreamedWordArtifact-3VKYIG6C.cjs → ConnectedStreamedWordArtifact-DSVFI7BX.cjs} +8 -8
  19. package/dist/ConnectedStreamedWordArtifact-DSVFI7BX.cjs.map +1 -0
  20. package/dist/{ConnectedStreamedWordArtifact-TEB332E3.js → ConnectedStreamedWordArtifact-EQUE6WT5.js} +7 -7
  21. package/dist/ConnectedStreamedWordArtifact-EQUE6WT5.js.map +1 -0
  22. package/dist/{WorkspaceTableView-KG546U7M.cjs → WorkspaceTableView-2CGEO5KK.cjs} +6 -6
  23. package/dist/{WorkspaceTableView-KG546U7M.cjs.map → WorkspaceTableView-2CGEO5KK.cjs.map} +1 -1
  24. package/dist/{WorkspaceTableView-OSFIWJ5C.js → WorkspaceTableView-3ARFQYNO.js} +4 -4
  25. package/dist/{WorkspaceTableView-OSFIWJ5C.js.map → WorkspaceTableView-3ARFQYNO.js.map} +1 -1
  26. package/dist/agents/index.cjs +376 -376
  27. package/dist/agents/index.js +2 -2
  28. package/dist/calls/index.cjs +35 -35
  29. package/dist/calls/index.js +7 -7
  30. package/dist/chat/index.cjs +89 -89
  31. package/dist/chat/index.js +2 -2
  32. package/dist/{chunk-BRYRZOS3.cjs → chunk-3YZ3RUSB.cjs} +4 -4
  33. package/dist/{chunk-BRYRZOS3.cjs.map → chunk-3YZ3RUSB.cjs.map} +1 -1
  34. package/dist/{chunk-VPWCARVO.js → chunk-4DDPMLGU.js} +4 -4
  35. package/dist/{chunk-VPWCARVO.js.map → chunk-4DDPMLGU.js.map} +1 -1
  36. package/dist/{chunk-24H5WTGM.js → chunk-4U4NXFV5.js} +4 -4
  37. package/dist/{chunk-24H5WTGM.js.map → chunk-4U4NXFV5.js.map} +1 -1
  38. package/dist/{chunk-6EQSP77K.cjs → chunk-5ZHAHJ4H.cjs} +5 -5
  39. package/dist/{chunk-6EQSP77K.cjs.map → chunk-5ZHAHJ4H.cjs.map} +1 -1
  40. package/dist/{chunk-BIWPXSGS.cjs → chunk-6AJJJ5HR.cjs} +7 -7
  41. package/dist/{chunk-BIWPXSGS.cjs.map → chunk-6AJJJ5HR.cjs.map} +1 -1
  42. package/dist/{chunk-2YCCCXYV.js → chunk-7LN3ISPG.js} +3 -3
  43. package/dist/{chunk-2YCCCXYV.js.map → chunk-7LN3ISPG.js.map} +1 -1
  44. package/dist/{chunk-5NTSJV5H.cjs → chunk-BY2EW2QU.cjs} +16 -91
  45. package/dist/chunk-BY2EW2QU.cjs.map +1 -0
  46. package/dist/{chunk-FHB4G77H.js → chunk-CEO7YI7I.js} +3 -3
  47. package/dist/{chunk-FHB4G77H.js.map → chunk-CEO7YI7I.js.map} +1 -1
  48. package/dist/{chunk-LTO4JYU3.cjs → chunk-DWWRLKEZ.cjs} +188 -2
  49. package/dist/chunk-DWWRLKEZ.cjs.map +1 -0
  50. package/dist/chunk-E3IEZMRK.cjs +507 -0
  51. package/dist/chunk-E3IEZMRK.cjs.map +1 -0
  52. package/dist/{chunk-MLHOG7YH.js → chunk-EFDZRAW6.js} +3 -3
  53. package/dist/{chunk-MLHOG7YH.js.map → chunk-EFDZRAW6.js.map} +1 -1
  54. package/dist/chunk-FOIDBAZB.js +497 -0
  55. package/dist/chunk-FOIDBAZB.js.map +1 -0
  56. package/dist/{chunk-MB3S2ETI.cjs → chunk-G2BKHCI2.cjs} +2 -2
  57. package/dist/chunk-G2BKHCI2.cjs.map +1 -0
  58. package/dist/{chunk-EFSL4O3B.cjs → chunk-GT5FCYV5.cjs} +7 -7
  59. package/dist/{chunk-EFSL4O3B.cjs.map → chunk-GT5FCYV5.cjs.map} +1 -1
  60. package/dist/{chunk-QWKT4DO6.cjs → chunk-GX5MKJ3F.cjs} +433 -2
  61. package/dist/chunk-GX5MKJ3F.cjs.map +1 -0
  62. package/dist/{chunk-C3YIVBTU.cjs → chunk-HDEOORSV.cjs} +9 -9
  63. package/dist/{chunk-C3YIVBTU.cjs.map → chunk-HDEOORSV.cjs.map} +1 -1
  64. package/dist/{chunk-T4P237N6.js → chunk-ILBBV2ZY.js} +2 -2
  65. package/dist/chunk-ILBBV2ZY.js.map +1 -0
  66. package/dist/{chunk-XFSEPGF7.cjs → chunk-K53QRFPJ.cjs} +4 -4
  67. package/dist/{chunk-XFSEPGF7.cjs.map → chunk-K53QRFPJ.cjs.map} +1 -1
  68. package/dist/{chunk-NOKTUWTN.cjs → chunk-K7S5BMAB.cjs} +7 -7
  69. package/dist/{chunk-NOKTUWTN.cjs.map → chunk-K7S5BMAB.cjs.map} +1 -1
  70. package/dist/{chunk-FY4C7HU5.cjs → chunk-O3MRE2HL.cjs} +2233 -8
  71. package/dist/chunk-O3MRE2HL.cjs.map +1 -0
  72. package/dist/{chunk-N3HVTMQK.js → chunk-OPZBIGW4.js} +3 -3
  73. package/dist/{chunk-N3HVTMQK.js.map → chunk-OPZBIGW4.js.map} +1 -1
  74. package/dist/{chunk-JVXHVFUA.cjs → chunk-P6HVJP6Z.cjs} +4 -4
  75. package/dist/{chunk-JVXHVFUA.cjs.map → chunk-P6HVJP6Z.cjs.map} +1 -1
  76. package/dist/{chunk-PXYP3DM5.js → chunk-PD4AKBYZ.js} +432 -6
  77. package/dist/chunk-PD4AKBYZ.js.map +1 -0
  78. package/dist/{chunk-VKY4SQMH.js → chunk-REHG7VRQ.js} +5 -5
  79. package/dist/{chunk-VKY4SQMH.js.map → chunk-REHG7VRQ.js.map} +1 -1
  80. package/dist/{chunk-M4MZPH2S.js → chunk-SEZEEHLU.js} +190 -6
  81. package/dist/chunk-SEZEEHLU.js.map +1 -0
  82. package/dist/{chunk-5JEM3BWT.js → chunk-SYP2A4GQ.js} +4 -4
  83. package/dist/{chunk-5JEM3BWT.js.map → chunk-SYP2A4GQ.js.map} +1 -1
  84. package/dist/{chunk-QFYRXQTD.js → chunk-TTPXMCJL.js} +6 -80
  85. package/dist/chunk-TTPXMCJL.js.map +1 -0
  86. package/dist/{chunk-VMLEISRZ.cjs → chunk-UZVXVTTB.cjs} +17 -17
  87. package/dist/{chunk-VMLEISRZ.cjs.map → chunk-UZVXVTTB.cjs.map} +1 -1
  88. package/dist/{chunk-YDY4GIC2.cjs → chunk-V7JDMKNQ.cjs} +4 -4
  89. package/dist/{chunk-YDY4GIC2.cjs.map → chunk-V7JDMKNQ.cjs.map} +1 -1
  90. package/dist/{chunk-YPR4FSDJ.js → chunk-VA3YYR7R.js} +8 -192
  91. package/dist/chunk-VA3YYR7R.js.map +1 -0
  92. package/dist/{chunk-NUV5F6MM.js → chunk-WAIHNFUH.js} +3 -3
  93. package/dist/{chunk-NUV5F6MM.js.map → chunk-WAIHNFUH.js.map} +1 -1
  94. package/dist/{chunk-H5BFOVVL.cjs → chunk-WRERJFEV.cjs} +2 -188
  95. package/dist/chunk-WRERJFEV.cjs.map +1 -0
  96. package/dist/{chunk-4DKI37QQ.js → chunk-YAGSGBVM.js} +2213 -13
  97. package/dist/chunk-YAGSGBVM.js.map +1 -0
  98. package/dist/{chunk-K6W2GWCN.js → chunk-ZAYUJFPI.js} +3 -3
  99. package/dist/{chunk-K6W2GWCN.js.map → chunk-ZAYUJFPI.js.map} +1 -1
  100. package/dist/connect/docx.cjs +10 -10
  101. package/dist/connect/docx.js +6 -6
  102. package/dist/connect/index.cjs +1237 -591
  103. package/dist/connect/index.cjs.map +1 -1
  104. package/dist/connect/index.d.ts +3 -0
  105. package/dist/connect/index.js +665 -23
  106. package/dist/connect/index.js.map +1 -1
  107. package/dist/connect/viewer.cjs +27 -27
  108. package/dist/connect/viewer.js +10 -10
  109. package/dist/connection/panels/ConnectedPanelHost.d.ts +48 -0
  110. package/dist/connection/panels/useSessionPanelMode.d.ts +18 -0
  111. package/dist/connection/threads-page/ConnectedThreadsPage.d.ts +14 -0
  112. package/dist/connection/threads-page/ThreadSearch.d.ts +24 -0
  113. package/dist/connection/workflows/index.d.ts +1 -0
  114. package/dist/connection/workflows/useWorkflowsPageState.d.ts +134 -0
  115. package/dist/index.cjs +601 -585
  116. package/dist/index.js +8 -8
  117. package/dist/provider/host.d.ts +3 -0
  118. package/dist/shell/PanelPortal.d.ts +30 -0
  119. package/dist/shell/index.cjs +78 -61
  120. package/dist/shell/index.d.ts +3 -0
  121. package/dist/shell/index.js +3 -2
  122. package/dist/shell/panel-host.d.ts +8 -0
  123. package/dist/shell/panel-windows.d.ts +33 -0
  124. package/dist/studio/index.cjs +5 -5
  125. package/dist/studio/index.js +3 -3
  126. package/dist/workflow-canvas/index.cjs +21 -21
  127. package/dist/workflow-canvas/index.js +5 -5
  128. package/node_modules/@arbidocs/coedit/package.json +1 -1
  129. package/package.json +5 -5
  130. package/dist/ConnectedStreamedWordArtifact-3VKYIG6C.cjs.map +0 -1
  131. package/dist/ConnectedStreamedWordArtifact-TEB332E3.js.map +0 -1
  132. package/dist/chunk-4DKI37QQ.js.map +0 -1
  133. package/dist/chunk-5NTSJV5H.cjs.map +0 -1
  134. package/dist/chunk-EZSYYENN.js +0 -2700
  135. package/dist/chunk-EZSYYENN.js.map +0 -1
  136. package/dist/chunk-FY4C7HU5.cjs.map +0 -1
  137. package/dist/chunk-H5BFOVVL.cjs.map +0 -1
  138. package/dist/chunk-LTO4JYU3.cjs.map +0 -1
  139. package/dist/chunk-M4MZPH2S.js.map +0 -1
  140. package/dist/chunk-MB3S2ETI.cjs.map +0 -1
  141. package/dist/chunk-PUX4M22Q.cjs +0 -2735
  142. package/dist/chunk-PUX4M22Q.cjs.map +0 -1
  143. package/dist/chunk-PXYP3DM5.js.map +0 -1
  144. package/dist/chunk-QFYRXQTD.js.map +0 -1
  145. package/dist/chunk-QWKT4DO6.cjs.map +0 -1
  146. package/dist/chunk-T4P237N6.js.map +0 -1
  147. package/dist/chunk-YPR4FSDJ.js.map +0 -1
@@ -1,2700 +0,0 @@
1
- "use client";
2
- import { useWorkflowView, SkillTypeBadge, SkillGlobalBadge, SkillActionButton, activateOnKey, asTaskStatus, STATUS_LABELS, STATUS_TONE, indexTasks, STATUS_ORDER, buildTaskRows, isPlainTodo, goalProgress, moveTargets, EntityChip, ThreadStatusLine, ownerExtId, descendantIds } from './chunk-4DKI37QQ.js';
3
- import { PersonPill } from './chunk-7CHI36EP.js';
4
- import { TriStateFilterMenu } from './chunk-G4GLDQLY.js';
5
- import { CellHoverIcon } from './chunk-M4S75AEJ.js';
6
- import { ToneBadge } from './chunk-5LXQPP2K.js';
7
- import { SelectionBar, SelectionBarAction } from './chunk-NUNKSZKU.js';
8
- import { FilterInput } from './chunk-YU2KUMGE.js';
9
- import { ActionLink } from './chunk-73DANGPH.js';
10
- import { GridView } from './chunk-7F5Z4K5W.js';
11
- import { TabHeader } from './chunk-ABEKUV6L.js';
12
- import { EmptyState } from './chunk-P6GHJSLR.js';
13
- import { workflowSteps, initialRunInputs, stepPhase, composeStepPrompt } from './chunk-THNTNAYI.js';
14
- import { formatCount } from './chunk-5VYJP6XD.js';
15
- import { useIsPhone } from './chunk-SLSBJRWW.js';
16
- import { testIdOr } from './chunk-7F6CBP3Z.js';
17
- import { useState, useRef, useEffect, useMemo } from 'react';
18
- import { MessageSquare, Eye, ClipboardList, Pencil, Copy, Trash2, Bot, Sparkles, Plus, Download, Lock, Globe, AlertTriangle, FileX, SkipForward, Loader2, X, Check, FileText, CornerDownRight, AlertCircle, KeyRound, ChevronDown, ChevronRight, Workflow, ChevronsUpDown, ChevronsDownUp, ListTodo, CheckCheck, MoreHorizontal, CircleDashed, RotateCcw, History, ListPlus } from 'lucide-react';
19
- import { Button, cn, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Input, VisibilityToggle, ThreadVisibilityIcon, Checkbox, UserEntity, ARBI_MARK_SRC, DropdownMenuSeparator } from '@arbidocs/react/ui';
20
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
21
-
22
- function useWorkflowPreview(workflow, { answer = "{prompt}", delayMs = 400, resolveWorkflow, compose } = {}) {
23
- const steps = workflowSteps(workflow);
24
- const [inputs, setInputs] = useState(() => initialRunInputs(workflow));
25
- const [states, setStates] = useState({});
26
- const set = (id, state) => setStates((s) => ({ ...s, [id]: state }));
27
- const timers = useRef(/* @__PURE__ */ new Map());
28
- useEffect(() => {
29
- const pending = timers.current;
30
- return () => {
31
- for (const t of pending.values()) clearTimeout(t);
32
- pending.clear();
33
- setStates(
34
- (s) => Object.fromEntries(
35
- Object.entries(s).map(([id, v]) => [
36
- id,
37
- v.status === "running" ? { status: "pending" } : v
38
- ])
39
- )
40
- );
41
- };
42
- }, []);
43
- useEffect(() => {
44
- if (Object.values(states).some((s) => s.status === "running")) return;
45
- const next = steps.find(
46
- (s) => (s.kind === "agent" || s.kind === "action") && stepPhase(steps, states, s.id) === "ready"
47
- );
48
- if (!next || next.kind !== "agent" && next.kind !== "action") return;
49
- const prompt = next.kind === "agent" ? composeStepPrompt(workflow, next, inputs, { resolveWorkflow, compose }) : "";
50
- set(next.id, { status: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString() });
51
- timers.current.set(
52
- next.id,
53
- setTimeout(() => {
54
- timers.current.delete(next.id);
55
- set(
56
- next.id,
57
- next.kind === "agent" ? { status: "done", text: answer.replace("{prompt}", prompt) } : { status: "done" }
58
- );
59
- }, delayMs)
60
- );
61
- }, [states, steps, workflow]);
62
- return useWorkflowView({
63
- steps,
64
- states,
65
- inputs,
66
- setInput: (name, value) => setInputs((i) => ({ ...i, [name]: value })),
67
- complete: async (step) => set(step.id, { status: "done" }),
68
- skip: async (id) => set(id, { status: "skipped" }),
69
- retry: async (id) => set(id, { status: "pending" }),
70
- isLocked: (id) => states[id]?.status === "done"
71
- });
72
- }
73
- var DEFAULT_AGENT_CARD_LABELS = {
74
- running: "Running",
75
- runningIndicator: "Running",
76
- runningCount: (count) => `${count} running`,
77
- taskCount: (count) => count > 0 ? formatCount(count, { one: "task", other: "tasks" }) : "no tasks yet",
78
- goalMet: (met, total) => `\u2713 ${met}/${total} met`,
79
- watch: "Watch",
80
- message: "Message",
81
- assignTask: "Assign task",
82
- edit: "Edit",
83
- copyAddress: "Copy address",
84
- deleteAgent: (name) => `Delete agent ${name}`
85
- };
86
- var ACTION = "h-9 gap-1.5 px-2 text-sm pointer-coarse:h-11";
87
- function AgentCard({
88
- name,
89
- avatar,
90
- runningCount,
91
- taskCount,
92
- goalProgress: goalProgress2,
93
- details,
94
- onWatch,
95
- onMessage,
96
- onAssignTask,
97
- onEdit,
98
- onCopyAddress,
99
- onDelete,
100
- href,
101
- onSelect,
102
- labels,
103
- testIds,
104
- testId,
105
- className,
106
- ...rest
107
- }) {
108
- const l = { ...DEFAULT_AGENT_CARD_LABELS, ...labels };
109
- return /* @__PURE__ */ jsxs(
110
- "div",
111
- {
112
- ...rest,
113
- className: cn("rounded-lg bg-muted p-4", className),
114
- "data-testid": testIdOr(testId ?? testIds?.root, { testId: `agent-card-${name}` }),
115
- "data-slot": "agent-card",
116
- children: [
117
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
118
- /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", children: [
119
- avatar,
120
- runningCount > 0 && /* @__PURE__ */ jsx(
121
- "span",
122
- {
123
- className: "absolute -right-0.5 -top-0.5 size-2.5 rounded-full bg-ai-ink",
124
- role: "img",
125
- "aria-label": l.runningIndicator,
126
- children: /* @__PURE__ */ jsx("span", { className: "absolute inset-0 animate-ping rounded-full bg-ai-ink opacity-60" })
127
- }
128
- )
129
- ] }),
130
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
131
- href ? /* @__PURE__ */ jsx(
132
- ActionLink,
133
- {
134
- href,
135
- onSelect,
136
- className: "block max-w-full truncate text-left font-medium hover:underline",
137
- children: name
138
- }
139
- ) : /* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: name }),
140
- /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex flex-wrap gap-1 text-xs text-muted-foreground", children: [
141
- runningCount > 0 ? /* @__PURE__ */ jsx("span", { className: "rounded-full bg-ai-soft px-2 py-px text-ai-ink", children: l.runningCount(runningCount) }) : /* @__PURE__ */ jsx("span", { className: "rounded-full bg-background px-2 py-px", children: l.taskCount(taskCount) }),
142
- goalProgress2 && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-background px-2 py-px", children: l.goalMet(goalProgress2.met, goalProgress2.total) })
143
- ] }),
144
- details
145
- ] })
146
- ] }),
147
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-1 pt-1", children: [
148
- onMessage && /* @__PURE__ */ jsxs(
149
- Button,
150
- {
151
- variant: "ghost",
152
- size: "sm",
153
- className: ACTION,
154
- onClick: onMessage,
155
- "data-testid": testIdOr(testIds?.message, { testId: `agent-message-${name}` }),
156
- children: [
157
- /* @__PURE__ */ jsx(MessageSquare, { size: 14 }),
158
- " ",
159
- l.message
160
- ]
161
- }
162
- ),
163
- onWatch && /* @__PURE__ */ jsxs(
164
- Button,
165
- {
166
- variant: "ghost",
167
- size: "sm",
168
- className: cn(ACTION, "text-ai-ink hover:text-ai-ink"),
169
- onClick: onWatch,
170
- "data-testid": testIdOr(testIds?.watch, { testId: `agent-watch-run-${name}` }),
171
- children: [
172
- /* @__PURE__ */ jsx(Eye, { size: 14 }),
173
- " ",
174
- l.watch
175
- ]
176
- }
177
- ),
178
- onAssignTask && /* @__PURE__ */ jsxs(
179
- Button,
180
- {
181
- variant: "ghost",
182
- size: "sm",
183
- className: ACTION,
184
- onClick: onAssignTask,
185
- "data-testid": testIdOr(testIds?.assignTask, { testId: `agent-assign-task-${name}` }),
186
- children: [
187
- /* @__PURE__ */ jsx(ClipboardList, { size: 14 }),
188
- " ",
189
- l.assignTask
190
- ]
191
- }
192
- ),
193
- onEdit && /* @__PURE__ */ jsxs(
194
- Button,
195
- {
196
- variant: "ghost",
197
- size: "sm",
198
- className: ACTION,
199
- onClick: onEdit,
200
- "data-testid": testIdOr(testIds?.edit, { testId: `agent-edit-${name}` }),
201
- children: [
202
- /* @__PURE__ */ jsx(Pencil, { size: 14 }),
203
- " ",
204
- l.edit
205
- ]
206
- }
207
- ),
208
- onCopyAddress && /* @__PURE__ */ jsxs(
209
- Button,
210
- {
211
- variant: "ghost",
212
- size: "sm",
213
- className: cn(ACTION, "text-muted-foreground"),
214
- onClick: onCopyAddress,
215
- "data-testid": testIdOr(testIds?.copyAddress, { testId: `agent-copy-address-${name}` }),
216
- children: [
217
- /* @__PURE__ */ jsx(Copy, { size: 14 }),
218
- " ",
219
- l.copyAddress
220
- ]
221
- }
222
- ),
223
- onDelete && /* @__PURE__ */ jsx(
224
- Button,
225
- {
226
- variant: "ghost",
227
- size: "sm",
228
- className: cn(ACTION, "ml-auto text-muted-foreground hover:text-red-text"),
229
- onClick: onDelete,
230
- "aria-label": l.deleteAgent(name),
231
- "data-testid": testIdOr(testIds?.delete, { testId: `agent-delete-${name}` }),
232
- children: /* @__PURE__ */ jsx(Trash2, { size: 14 })
233
- }
234
- )
235
- ] })
236
- ]
237
- }
238
- );
239
- }
240
- var DEFAULT_AGENT_GALLERY_LABELS = {
241
- description: "Personas that work for you, on demand.",
242
- emptyTitle: "Create your first agent",
243
- emptyDescription: "Describe a persona, pick its tools, generate an avatar, then hand it work from an agent card and watch it converge.",
244
- emptyCta: "Create an agent",
245
- newAgent: "New agent"
246
- };
247
- function AgentGallery({
248
- count,
249
- onCreate,
250
- children,
251
- description,
252
- labels,
253
- testIds,
254
- className
255
- }) {
256
- const l = { ...DEFAULT_AGENT_GALLERY_LABELS, ...labels };
257
- if (count === 0) {
258
- return /* @__PURE__ */ jsx("div", { className: cn("mt-8", className), children: /* @__PURE__ */ jsx(
259
- EmptyState,
260
- {
261
- className: "rounded-lg border-0 bg-muted",
262
- icon: Bot,
263
- title: l.emptyTitle,
264
- description: l.emptyDescription,
265
- action: /* @__PURE__ */ jsxs(
266
- Button,
267
- {
268
- onClick: onCreate,
269
- className: "gap-1.5",
270
- "data-testid": testIdOr(testIds?.emptyCta, { testId: "agent-empty-cta" }),
271
- children: [
272
- /* @__PURE__ */ jsx(Sparkles, { size: 16 }),
273
- " ",
274
- l.emptyCta
275
- ]
276
- }
277
- )
278
- }
279
- ) });
280
- }
281
- return /* @__PURE__ */ jsxs("div", { className, children: [
282
- /* @__PURE__ */ jsxs("div", { className: "mb-4 flex flex-wrap items-center justify-between gap-x-3 gap-y-2", children: [
283
- /* @__PURE__ */ jsx("p", { className: "min-w-0 text-sm text-muted-foreground", children: description ?? l.description }),
284
- /* @__PURE__ */ jsxs(
285
- Button,
286
- {
287
- size: "sm",
288
- onClick: onCreate,
289
- className: "h-8 gap-1.5 pointer-coarse:h-11",
290
- "data-testid": testIdOr(testIds?.createButton, { testId: "agent-create-button" }),
291
- children: [
292
- /* @__PURE__ */ jsx(Plus, { size: 16 }),
293
- " ",
294
- l.newAgent
295
- ]
296
- }
297
- )
298
- ] }),
299
- /* @__PURE__ */ jsx(
300
- "div",
301
- {
302
- className: "grid grid-cols-[repeat(auto-fill,minmax(16rem,1fr))] gap-4",
303
- "data-testid": testIdOr(testIds?.gallery, { testId: "agent-gallery" }),
304
- "data-slot": "agent-gallery",
305
- children
306
- }
307
- )
308
- ] });
309
- }
310
- var DEFAULT_SKILL_CARD_LABELS = {
311
- global: "Global",
312
- globalTitle: "Shared with everyone in the marketplace",
313
- preview: "Preview skill",
314
- edit: "Edit skill",
315
- export: "Export skill",
316
- makePrivate: "Make private",
317
- makePrivateTooltip: "Make private (unpublish)",
318
- publish: "Publish to marketplace"
319
- };
320
- function SkillCard({
321
- name,
322
- description,
323
- type,
324
- visibility = "mine",
325
- isOwner = true,
326
- icon,
327
- onClick,
328
- onPreview,
329
- onEdit,
330
- onExport,
331
- onToggleVisibility,
332
- href,
333
- labels,
334
- testId,
335
- testIds,
336
- className,
337
- ...rest
338
- }) {
339
- const l = { ...DEFAULT_SKILL_CARD_LABELS, ...labels };
340
- const isGlobal = visibility === "global";
341
- return /* @__PURE__ */ jsxs(
342
- "div",
343
- {
344
- ...rest,
345
- role: "button",
346
- tabIndex: 0,
347
- className: cn(
348
- "flex w-full cursor-pointer flex-wrap items-center gap-x-3 gap-y-1 rounded-lg bg-muted px-4 py-3 transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
349
- className
350
- ),
351
- onClick,
352
- onKeyDown: activateOnKey(onClick),
353
- "data-skill-name": name,
354
- "data-testid": testIdOr(testId ?? testIds?.root, { testId: "skill-card" }),
355
- "data-slot": "skill-card",
356
- children: [
357
- icon && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-primary [&>svg]:size-5", children: icon }),
358
- href ? (
359
- // The link owns plain + modified clicks; stop the row handler from double-firing.
360
- /* @__PURE__ */ jsx("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs(
361
- ActionLink,
362
- {
363
- href,
364
- onSelect: onClick,
365
- className: "whitespace-nowrap font-mono text-sm font-medium",
366
- children: [
367
- "/",
368
- name
369
- ]
370
- }
371
- ) })
372
- ) : /* @__PURE__ */ jsxs("span", { className: "whitespace-nowrap font-mono text-sm font-medium", children: [
373
- "/",
374
- name
375
- ] }),
376
- type && type !== "markdown" && /* @__PURE__ */ jsx(SkillTypeBadge, { type }),
377
- isGlobal && /* @__PURE__ */ jsx(SkillGlobalBadge, { labels: l }),
378
- /* @__PURE__ */ jsx("span", { className: "min-w-0 text-sm text-muted-foreground max-sm:order-last max-sm:w-full max-sm:line-clamp-2 sm:flex-1 sm:truncate", children: description }),
379
- /* @__PURE__ */ jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-1", "data-slot": "skill-card-actions", children: [
380
- /* @__PURE__ */ jsx(
381
- SkillActionButton,
382
- {
383
- label: l.preview,
384
- onClick: onPreview,
385
- testId: testIdOr(testIds?.preview, { testId: "preview-skill-button" }),
386
- children: /* @__PURE__ */ jsx(Eye, { className: "size-4 text-muted-foreground" })
387
- }
388
- ),
389
- /* @__PURE__ */ jsx(
390
- SkillActionButton,
391
- {
392
- label: l.edit,
393
- onClick: onEdit,
394
- testId: testIdOr(testIds?.edit, { testId: "edit-skill-button" }),
395
- children: /* @__PURE__ */ jsx(Pencil, { className: "size-4 text-muted-foreground" })
396
- }
397
- ),
398
- /* @__PURE__ */ jsx(
399
- SkillActionButton,
400
- {
401
- label: l.export,
402
- onClick: onExport,
403
- testId: testIdOr(testIds?.export, { testId: "export-skill-button" }),
404
- children: /* @__PURE__ */ jsx(Download, { className: "size-4 text-muted-foreground" })
405
- }
406
- ),
407
- isOwner && onToggleVisibility && /* @__PURE__ */ jsx(
408
- SkillActionButton,
409
- {
410
- label: isGlobal ? l.makePrivate : l.publish,
411
- tooltip: isGlobal ? l.makePrivateTooltip : l.publish,
412
- onClick: onToggleVisibility,
413
- testId: testIdOr(testIds?.toggleVisibility, {
414
- testId: "toggle-skill-visibility-button"
415
- }),
416
- children: isGlobal ? /* @__PURE__ */ jsx(Lock, { className: "size-4 text-muted-foreground" }) : /* @__PURE__ */ jsx(Globe, { className: "size-4 text-primary" })
417
- }
418
- )
419
- ] })
420
- ]
421
- }
422
- );
423
- }
424
- var DEFAULT_SKILL_ROW_LABELS = {
425
- global: "Global",
426
- globalTitle: "Shared with everyone in the marketplace",
427
- preview: "Preview",
428
- edit: "Edit",
429
- makePrivate: "Make private",
430
- publish: "Publish to marketplace",
431
- delete: "Delete",
432
- insertTitle: (name) => `Insert /${name} into the chat`
433
- };
434
- function SkillRow({
435
- name,
436
- description,
437
- type,
438
- isGlobal = false,
439
- icon,
440
- onInvoke,
441
- onPreview,
442
- onEdit,
443
- onTogglePublish,
444
- onDelete,
445
- href,
446
- labels,
447
- testId,
448
- className,
449
- ...rest
450
- }) {
451
- const l = { ...DEFAULT_SKILL_ROW_LABELS, ...labels };
452
- return /* @__PURE__ */ jsxs(
453
- "div",
454
- {
455
- ...rest,
456
- role: "button",
457
- tabIndex: 0,
458
- className: cn(
459
- "flex cursor-pointer items-center gap-2 px-4 py-2 hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
460
- className
461
- ),
462
- onClick: onInvoke,
463
- onKeyDown: activateOnKey(onInvoke),
464
- "data-testid": testIdOr(testId, { testId: `skills-panel-row-${name}` }),
465
- "data-slot": "skill-row",
466
- title: l.insertTitle(name),
467
- children: [
468
- icon && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-primary [&>svg]:size-4", children: icon }),
469
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
470
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
471
- href ? (
472
- // The link owns plain + modified clicks; stop the row handler from double-firing.
473
- /* @__PURE__ */ jsx("span", { className: "min-w-0", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs(ActionLink, { href, onSelect: onInvoke, className: "truncate font-mono text-sm", children: [
474
- "/",
475
- name
476
- ] }) })
477
- ) : /* @__PURE__ */ jsxs("span", { className: "truncate font-mono text-sm", children: [
478
- "/",
479
- name
480
- ] }),
481
- type && type !== "markdown" && /* @__PURE__ */ jsx(SkillTypeBadge, { type }),
482
- isGlobal && /* @__PURE__ */ jsx(SkillGlobalBadge, { labels: l })
483
- ] }),
484
- description && /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-muted-foreground", children: description })
485
- ] }),
486
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
487
- /* @__PURE__ */ jsx(SkillActionButton, { label: l.preview, onClick: () => onPreview(), children: /* @__PURE__ */ jsx(Eye, { className: "size-4" }) }),
488
- onEdit && /* @__PURE__ */ jsx(SkillActionButton, { label: l.edit, onClick: () => onEdit(), children: /* @__PURE__ */ jsx(Pencil, { className: "size-4" }) }),
489
- onTogglePublish && /* @__PURE__ */ jsx(
490
- SkillActionButton,
491
- {
492
- label: isGlobal ? l.makePrivate : l.publish,
493
- onClick: () => onTogglePublish(),
494
- children: isGlobal ? /* @__PURE__ */ jsx(Lock, { className: "size-4" }) : /* @__PURE__ */ jsx(Globe, { className: "size-4 text-primary" })
495
- }
496
- ),
497
- onDelete && /* @__PURE__ */ jsx(SkillActionButton, { label: l.delete, onClick: () => onDelete(), children: /* @__PURE__ */ jsx(Trash2, { className: "size-4 text-destructive" }) })
498
- ] })
499
- ]
500
- }
501
- );
502
- }
503
-
504
- // src/agents/tasks/elapsed.ts
505
- function formatElapsed(startTime, endTime) {
506
- const elapsedMs = (endTime || Date.now()) - startTime;
507
- if (elapsedMs < 1e3) return `${Math.round(elapsedMs)}ms`;
508
- if (elapsedMs < 6e4) return `${Math.round(elapsedMs / 1e3)}s`;
509
- if (elapsedMs < 36e5) {
510
- const minutes2 = Math.floor(elapsedMs / 6e4);
511
- const seconds = Math.round(elapsedMs % 6e4 / 1e3);
512
- return seconds > 0 ? `${minutes2}m ${seconds}s` : `${minutes2}m`;
513
- }
514
- const hours = Math.floor(elapsedMs / 36e5);
515
- const minutes = Math.round(elapsedMs % 36e5 / 6e4);
516
- return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
517
- }
518
- var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["parsing", "encrypting", "indexing", "analysing"]);
519
- var RUNNING_STATUSES = /* @__PURE__ */ new Set(["queued", ...ACTIVE_STATUSES]);
520
- var TONES = {
521
- completed: { text: "text-green-text", bar: "bg-green-text/90", Icon: Check },
522
- failed: { text: "text-red-text", bar: "bg-red-text/90", Icon: X },
523
- parsing: { text: "text-blue-text", bar: "bg-blue-text/90", Icon: Loader2 },
524
- encrypting: { text: "text-blue-text", bar: "bg-blue-text/90", Icon: Loader2 },
525
- indexing: { text: "text-blue-text", bar: "bg-blue-text/90", Icon: Loader2 },
526
- analysing: { text: "text-blue-text", bar: "bg-blue-text/90", Icon: Loader2 },
527
- queued: { text: "text-yellow-text", bar: "bg-yellow-text/90", Icon: Loader2 },
528
- skipped: { text: "text-gray-text", bar: "bg-gray-text/90", Icon: SkipForward },
529
- empty: { text: "text-yellow-text", bar: "bg-yellow-text/90", Icon: FileX },
530
- "low-content": { text: "text-yellow-text", bar: "bg-yellow-text/90", Icon: AlertTriangle }
531
- };
532
- var DEFAULT_TONE = { text: "text-gray-text", bar: "bg-blue-text/90", Icon: Loader2 };
533
- var DEFAULT_UPLOAD_TASK_ITEM_LABELS = {
534
- delete: "Delete",
535
- deleteTitle: "Delete file",
536
- progress: (fileName) => `${fileName} progress`,
537
- status: (status, percent) => `${status.charAt(0).toUpperCase() + status.slice(1)} - ${percent}%`
538
- };
539
- function UploadTaskItem({
540
- taskId,
541
- fileName,
542
- status,
543
- progress,
544
- startTime,
545
- endTime,
546
- fileIcon,
547
- onDelete,
548
- testIds,
549
- labels,
550
- className
551
- }) {
552
- const l = { ...DEFAULT_UPLOAD_TASK_ITEM_LABELS, ...labels };
553
- const [elapsed, setElapsed] = useState("");
554
- useEffect(() => {
555
- if (!startTime) {
556
- setElapsed("");
557
- return;
558
- }
559
- if (RUNNING_STATUSES.has(status)) {
560
- const tick = () => setElapsed(formatElapsed(startTime));
561
- tick();
562
- const interval = setInterval(tick, 1e3);
563
- return () => clearInterval(interval);
564
- }
565
- setElapsed(formatElapsed(startTime, endTime));
566
- }, [startTime, endTime, status]);
567
- const percent = progress > 1 ? progress : progress * 100;
568
- const tone = TONES[status] ?? DEFAULT_TONE;
569
- const StatusIcon2 = tone.Icon;
570
- return /* @__PURE__ */ jsxs(
571
- "div",
572
- {
573
- className: cn(
574
- "mb-2 w-full max-w-full overflow-hidden rounded-md bg-muted p-2 box-border",
575
- className
576
- ),
577
- "data-testid": testIdOr(testIds?.item?.(taskId), { testId: `task-item-${taskId}` }),
578
- "data-slot": "upload-task-item",
579
- children: [
580
- /* @__PURE__ */ jsxs("div", { className: "mb-1 flex w-full max-w-full items-center gap-2 overflow-hidden", children: [
581
- /* @__PURE__ */ jsx("div", { className: cn("shrink-0", tone.text), children: /* @__PURE__ */ jsx(StatusIcon2, { className: cn("size-4", ACTIVE_STATUSES.has(status) && "animate-spin") }) }),
582
- fileIcon && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: fileIcon }),
583
- /* @__PURE__ */ jsx(
584
- "div",
585
- {
586
- className: "min-w-0 flex-1 truncate text-sm font-medium",
587
- title: fileName,
588
- "data-testid": testIdOr(testIds?.fileName?.(taskId), { testId: `task-filename-${taskId}` }),
589
- children: fileName
590
- }
591
- )
592
- ] }),
593
- /* @__PURE__ */ jsx(
594
- "div",
595
- {
596
- className: "mb-1 h-2 w-full max-w-full overflow-hidden rounded-md bg-background",
597
- "data-testid": testIdOr(testIds?.progressBar?.(taskId), {
598
- testId: `task-progress-bar-${taskId}`
599
- }),
600
- role: "progressbar",
601
- "aria-valuemin": 0,
602
- "aria-valuemax": 100,
603
- "aria-valuenow": Math.round(percent),
604
- "aria-label": l.progress(fileName),
605
- children: /* @__PURE__ */ jsx(
606
- "div",
607
- {
608
- className: cn("h-2 rounded-md transition-all duration-300 ease-in-out", tone.bar),
609
- style: { width: `${percent}%` }
610
- }
611
- )
612
- }
613
- ),
614
- /* @__PURE__ */ jsxs("div", { className: "flex w-full max-w-full items-center justify-between overflow-hidden text-sm", children: [
615
- /* @__PURE__ */ jsxs(
616
- "span",
617
- {
618
- className: "truncate text-gray-text",
619
- "data-testid": testIdOr(testIds?.status?.(taskId), { testId: `task-status-${taskId}` }),
620
- children: [
621
- l.status(status, Math.round(percent)),
622
- elapsed && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-muted-foreground", children: [
623
- "(",
624
- elapsed,
625
- ")"
626
- ] })
627
- ]
628
- }
629
- ),
630
- status === "failed" && onDelete && /* @__PURE__ */ jsx(
631
- "button",
632
- {
633
- type: "button",
634
- onClick: () => onDelete(taskId),
635
- className: "shrink-0 text-sm text-red-text hover:text-red-text/80 pointer-coarse:min-h-11 pointer-coarse:px-3",
636
- title: l.deleteTitle,
637
- "data-testid": testIdOr(testIds?.delete?.(taskId), { testId: `delete-task-${taskId}` }),
638
- children: l.delete
639
- }
640
- )
641
- ] })
642
- ]
643
- }
644
- );
645
- }
646
- var DEFAULT_UPLOAD_TASK_LIST_LABELS = {
647
- title: "Active Tasks",
648
- empty: "No documents in this category"
649
- };
650
- function UploadTaskList({
651
- tasks,
652
- filter,
653
- title,
654
- onDelete,
655
- renderFileIcon,
656
- showEmptyState = true,
657
- testIds,
658
- labels,
659
- itemLabels,
660
- className
661
- }) {
662
- const l = { ...DEFAULT_UPLOAD_TASK_LIST_LABELS, ...labels };
663
- const heading = title ?? l.title;
664
- const safeTasks = typeof tasks === "object" && tasks !== null ? tasks : {};
665
- let entries = Object.entries(safeTasks);
666
- if (filter) entries = entries.filter(([id, task]) => filter(id, task));
667
- if (entries.length === 0) {
668
- return showEmptyState ? /* @__PURE__ */ jsx(
669
- "div",
670
- {
671
- className: "text-sm italic text-muted-foreground",
672
- "data-testid": testIdOr(testIds?.empty, { testId: "task-list-empty-state" }),
673
- children: l.empty
674
- }
675
- ) : null;
676
- }
677
- return /* @__PURE__ */ jsxs(
678
- "div",
679
- {
680
- "data-testid": testIdOr(testIds?.root, { testId: "task-status-list" }),
681
- "data-slot": "upload-task-list",
682
- className: cn("box-border w-full max-w-full", className),
683
- children: [
684
- heading && /* @__PURE__ */ jsx(
685
- "h3",
686
- {
687
- className: "mb-2 truncate text-sm font-semibold",
688
- "data-testid": testIdOr(testIds?.title, { testId: "task-list-title" }),
689
- children: heading
690
- }
691
- ),
692
- /* @__PURE__ */ jsx("div", { className: "box-border w-full max-w-full overflow-hidden", children: entries.map(([taskId, task]) => /* @__PURE__ */ jsx(
693
- UploadTaskItem,
694
- {
695
- taskId,
696
- fileName: task.fileName,
697
- status: task.status,
698
- progress: task.progress,
699
- startTime: task.startTime,
700
- endTime: task.endTime,
701
- fileIcon: renderFileIcon?.(task.fileName),
702
- onDelete,
703
- labels: itemLabels,
704
- className: "box-border max-w-full"
705
- },
706
- taskId
707
- )) })
708
- ]
709
- }
710
- );
711
- }
712
- var DEFAULT_TASK_OUTPUTS_CELL_LABELS = {
713
- none: "\u2014",
714
- open: (count) => `${count} ${count === 1 ? "output" : "outputs"}`,
715
- more: (count) => `+${count}`
716
- };
717
- function TaskOutputsCell({ id, outputs, onOpen, testId, labels }) {
718
- const l = { ...DEFAULT_TASK_OUTPUTS_CELL_LABELS, ...labels };
719
- const base = testIdOr(testId, { testId: `task-outputs-${id}` });
720
- if (!outputs || outputs.length === 0) {
721
- return /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", "data-testid": base, "data-state": "none", children: l.none });
722
- }
723
- const [first] = outputs;
724
- return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
725
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
726
- Button,
727
- {
728
- variant: "ghost",
729
- size: "sm",
730
- className: "h-7 max-w-full gap-1.5 px-2 text-xs",
731
- "aria-label": l.open(outputs.length),
732
- "data-testid": base,
733
- "data-count": outputs.length,
734
- children: [
735
- /* @__PURE__ */ jsx(FileText, { className: "size-3.5 shrink-0", "aria-hidden": true }),
736
- /* @__PURE__ */ jsx("span", { className: "truncate", children: first.name }),
737
- outputs.length > 1 && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: l.more(outputs.length - 1) })
738
- ]
739
- }
740
- ) }),
741
- /* @__PURE__ */ jsx(DropdownMenuContent, { align: "start", children: outputs.map((o) => /* @__PURE__ */ jsxs(
742
- DropdownMenuItem,
743
- {
744
- onSelect: () => onOpen(o.id),
745
- "data-testid": `task-output-${id}-${o.id}`,
746
- children: [
747
- /* @__PURE__ */ jsx(FileText, { className: "mr-2 size-4", "aria-hidden": true }),
748
- /* @__PURE__ */ jsx("span", { className: "truncate", children: o.name })
749
- ]
750
- },
751
- o.id
752
- )) })
753
- ] });
754
- }
755
- function TaskMoveDialog({
756
- open,
757
- onOpenChange,
758
- options,
759
- currentParentId,
760
- onPick,
761
- labels,
762
- testId
763
- }) {
764
- const [query, setQuery] = useState("");
765
- const id = testIdOr(testId, { testId: "task-move" });
766
- const q = query.trim().toLowerCase();
767
- const shown = q ? options.filter((o) => o.label.toLowerCase().includes(q)) : options;
768
- const pick = (parentId) => {
769
- onPick(parentId);
770
- onOpenChange(false);
771
- };
772
- return /* @__PURE__ */ jsx(
773
- Dialog,
774
- {
775
- open,
776
- onOpenChange: (next) => {
777
- if (!next) setQuery("");
778
- onOpenChange(next);
779
- },
780
- children: /* @__PURE__ */ jsxs(DialogContent, { presentation: "sheet", className: "max-h-[80dvh] sm:max-w-md", "data-testid": id, children: [
781
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
782
- /* @__PURE__ */ jsx(DialogTitle, { children: labels.title }),
783
- /* @__PURE__ */ jsx(DialogDescription, { className: "sr-only", children: labels.title })
784
- ] }),
785
- /* @__PURE__ */ jsx(
786
- Input,
787
- {
788
- value: query,
789
- placeholder: labels.search,
790
- "aria-label": labels.search,
791
- onChange: (e) => setQuery(e.target.value),
792
- "data-testid": `${id}-search`
793
- }
794
- ),
795
- /* @__PURE__ */ jsxs(
796
- "ul",
797
- {
798
- className: "flex max-h-[50dvh] flex-col gap-1 overflow-y-auto",
799
- "data-testid": `${id}-list`,
800
- children: [
801
- !q && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
802
- Button,
803
- {
804
- variant: "ghost",
805
- className: "h-9 w-full justify-start gap-2 pointer-coarse:h-11",
806
- disabled: !currentParentId,
807
- onClick: () => pick(null),
808
- "data-testid": `${id}-none`,
809
- children: labels.none
810
- }
811
- ) }),
812
- shown.map((o) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
813
- Button,
814
- {
815
- variant: "ghost",
816
- className: "h-9 w-full justify-start gap-2 pointer-coarse:h-11",
817
- disabled: o.id === currentParentId,
818
- onClick: () => pick(o.id),
819
- "data-testid": `${id}-option-${o.id}`,
820
- children: [
821
- /* @__PURE__ */ jsx(CornerDownRight, { className: "size-3.5 shrink-0", "aria-hidden": true }),
822
- /* @__PURE__ */ jsx("span", { className: "truncate", children: o.label })
823
- ]
824
- }
825
- ) }, o.id)),
826
- shown.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-2 py-4 text-center text-sm text-muted-foreground", children: labels.empty })
827
- ]
828
- }
829
- ),
830
- /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: labels.cancel }) })
831
- ] })
832
- }
833
- );
834
- }
835
- function TaskRowMenu({
836
- task,
837
- status,
838
- busy,
839
- isOwner,
840
- canAdmin,
841
- canEdit,
842
- labels,
843
- canMarkDone = canEdit,
844
- canReopen = canEdit,
845
- selected = false,
846
- onToggleSelected,
847
- onMarkDone,
848
- onReopen,
849
- onAddSubtask,
850
- onEdit,
851
- onDelete,
852
- onMoveTo,
853
- onOpenActivity,
854
- onToggleShared,
855
- onGrantAccess
856
- }) {
857
- const [open, setOpen] = useState(false);
858
- const id = task.external_id;
859
- const isChat = status === "chat";
860
- const done = status === "done";
861
- return /* @__PURE__ */ jsxs(DropdownMenu, { open, onOpenChange: setOpen, children: [
862
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
863
- Button,
864
- {
865
- variant: "ghost",
866
- size: "icon",
867
- className: "size-8 pointer-coarse:size-11",
868
- "aria-label": labels.moreActions,
869
- "data-testid": `task-menu-${id}`,
870
- children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-4", "aria-hidden": true })
871
- }
872
- ) }),
873
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
874
- onToggleSelected && /* @__PURE__ */ jsx(DropdownMenuItem, { onSelect: onToggleSelected, "data-testid": `task-select-${id}`, children: selected ? "Deselect task" : "Select task" }),
875
- isOwner && onGrantAccess && task.pending_auth_request_id && /* @__PURE__ */ jsxs(
876
- DropdownMenuItem,
877
- {
878
- onSelect: onGrantAccess,
879
- "data-testid": `task-grant-${id}`,
880
- className: "text-destructive",
881
- children: [
882
- /* @__PURE__ */ jsx(KeyRound, { className: "mr-2 size-4", "aria-hidden": true }),
883
- labels.grantAccess
884
- ]
885
- }
886
- ),
887
- isChat || !canEdit ? null : done ? canReopen && /* @__PURE__ */ jsxs(
888
- DropdownMenuItem,
889
- {
890
- disabled: busy,
891
- onSelect: onReopen,
892
- "data-testid": `task-reopen-${id}`,
893
- children: [
894
- /* @__PURE__ */ jsx(RotateCcw, { className: "mr-2 size-4", "aria-hidden": true }),
895
- labels.reopen
896
- ]
897
- }
898
- ) : canMarkDone && /* @__PURE__ */ jsxs(
899
- DropdownMenuItem,
900
- {
901
- disabled: busy,
902
- onSelect: onMarkDone,
903
- "data-testid": `task-mark-done-${id}`,
904
- children: [
905
- /* @__PURE__ */ jsx(CheckCheck, { className: "mr-2 size-4", "aria-hidden": true }),
906
- labels.markDone
907
- ]
908
- }
909
- ),
910
- !isChat && onOpenActivity && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onOpenActivity, "data-testid": `task-activity-${id}`, children: [
911
- /* @__PURE__ */ jsx(History, { className: "mr-2 size-4", "aria-hidden": true }),
912
- labels.activity
913
- ] }),
914
- isOwner && onToggleShared && /* @__PURE__ */ jsx(DropdownMenuItem, { disabled: busy, onSelect: onToggleShared, children: task.is_shared ? "Make private" : "Share with workspace" }),
915
- canEdit && onAddSubtask && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onAddSubtask, "data-testid": `task-add-subtask-${id}`, children: [
916
- /* @__PURE__ */ jsx(ListPlus, { className: "mr-2 size-4", "aria-hidden": true }),
917
- labels.addSubtask
918
- ] }),
919
- canEdit && onMoveTo && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onMoveTo, "data-testid": `task-move-${id}`, children: [
920
- /* @__PURE__ */ jsx(CornerDownRight, { className: "mr-2 size-4", "aria-hidden": true }),
921
- labels.moveTo
922
- ] }),
923
- canEdit && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onEdit, "data-testid": `task-edit-${id}`, children: [
924
- /* @__PURE__ */ jsx(Pencil, { className: "mr-2 size-4", "aria-hidden": true }),
925
- isChat ? labels.rename : labels.edit
926
- ] }),
927
- canEdit && onDelete && /* @__PURE__ */ jsxs(Fragment, { children: [
928
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
929
- /* @__PURE__ */ jsxs(
930
- DropdownMenuItem,
931
- {
932
- disabled: busy || !canAdmin,
933
- onSelect: onDelete,
934
- title: canAdmin ? void 0 : labels.deleteOwnerOnly,
935
- className: "text-destructive focus:text-destructive",
936
- "data-testid": `task-delete-${id}`,
937
- children: [
938
- /* @__PURE__ */ jsx(Trash2, { className: "mr-2 size-4", "aria-hidden": true }),
939
- labels.delete
940
- ]
941
- }
942
- )
943
- ] })
944
- ] })
945
- ] });
946
- }
947
- var DEFAULT_TASK_STATUS_PILL_LABELS = {
948
- waitingForAccess: "Waiting for access",
949
- lastRunFailed: "Last run failed",
950
- failed: "Failed"
951
- };
952
- var ICON = "size-3";
953
- function StatusIcon({ status }) {
954
- switch (status) {
955
- case "generating":
956
- return /* @__PURE__ */ jsx(Loader2, { className: `${ICON} animate-spin`, "aria-hidden": true });
957
- case "done":
958
- return /* @__PURE__ */ jsx(Check, { className: ICON, "aria-hidden": true });
959
- case "chat":
960
- return /* @__PURE__ */ jsx(MessageSquare, { className: ICON, "aria-hidden": true });
961
- default:
962
- return /* @__PURE__ */ jsx(CircleDashed, { className: ICON, "aria-hidden": true });
963
- }
964
- }
965
- function useNow(active) {
966
- const [now, setNow] = useState(() => Date.now());
967
- useEffect(() => {
968
- if (!active) return;
969
- const timer = setInterval(() => setNow(Date.now()), 1e3);
970
- return () => clearInterval(timer);
971
- }, [active]);
972
- return now;
973
- }
974
- function TaskStatusPill({
975
- id,
976
- status,
977
- runStartedAt,
978
- lastError,
979
- awaitingAccess = false,
980
- testId,
981
- labels,
982
- hint
983
- }) {
984
- const l = { ...DEFAULT_TASK_STATUS_PILL_LABELS, ...labels };
985
- const key = asTaskStatus(status);
986
- const generating = key === "generating";
987
- const now = useNow(generating && Boolean(runStartedAt));
988
- const started = runStartedAt ? Date.parse(runStartedAt) : NaN;
989
- let text = STATUS_LABELS[key];
990
- let tone = STATUS_TONE[key];
991
- if (awaitingAccess) {
992
- text = l.waitingForAccess;
993
- tone = "danger";
994
- }
995
- const elapsed = generating && !Number.isNaN(started) ? formatElapsed(started, now) : null;
996
- return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", "data-slot": "task-status", title: hint, children: [
997
- /* @__PURE__ */ jsxs(
998
- ToneBadge,
999
- {
1000
- tone,
1001
- icon: /* @__PURE__ */ jsx(StatusIcon, { status: key }),
1002
- "data-status": key,
1003
- "data-testid": testIdOr(testId, { testId: `task-status-${id}` }),
1004
- children: [
1005
- text,
1006
- elapsed && /* @__PURE__ */ jsxs("span", { className: "tabular-nums opacity-70", children: [
1007
- "\xB7 ",
1008
- elapsed
1009
- ] })
1010
- ]
1011
- }
1012
- ),
1013
- lastError && !generating && /* @__PURE__ */ jsxs(
1014
- "span",
1015
- {
1016
- role: "img",
1017
- "aria-label": l.lastRunFailed,
1018
- title: `${l.lastRunFailed}: ${lastError}`,
1019
- className: "inline-flex shrink-0 items-center gap-1 text-xs font-medium text-red-text",
1020
- "data-testid": `task-error-${id}`,
1021
- children: [
1022
- /* @__PURE__ */ jsx(AlertCircle, { className: "size-3.5", "aria-hidden": true }),
1023
- l.failed
1024
- ]
1025
- }
1026
- )
1027
- ] });
1028
- }
1029
-
1030
- // src/agents/tasks/taskColumns.tsx
1031
- var ACTIONS_WIDTH = 160;
1032
- var NARROW_ACTIONS_WIDTH = 56;
1033
- var TASK_COLUMN_ORDER = [
1034
- "name",
1035
- "owner",
1036
- "due",
1037
- "status",
1038
- "outputs",
1039
- "shared",
1040
- "actions"
1041
- ];
1042
- var keepOrder = () => 0;
1043
- function cell(render) {
1044
- return (params) => params.data ? render(params.data) : null;
1045
- }
1046
- function createTaskColumns(o) {
1047
- const l = o.labels;
1048
- const sortable = {
1049
- sortable: true,
1050
- comparator: keepOrder,
1051
- sortingOrder: ["asc", "desc", null]
1052
- };
1053
- const base = {
1054
- sortable: false,
1055
- suppressMovable: true,
1056
- // Keyboard focus lands on the name cell only (arrow keys expand and collapse sub-tasks there).
1057
- suppressNavigable: true,
1058
- cellStyle: { display: "flex", alignItems: "center" }
1059
- };
1060
- const name = {
1061
- ...base,
1062
- colId: "name",
1063
- ...sortable,
1064
- suppressNavigable: false,
1065
- headerName: l.colTask,
1066
- flex: 3,
1067
- minWidth: 200,
1068
- cellRenderer: cell(o.renderName)
1069
- };
1070
- const shared = {
1071
- ...base,
1072
- colId: "shared",
1073
- headerName: l.colShared,
1074
- headerTooltip: l.shared,
1075
- width: 90,
1076
- cellStyle: { display: "flex", alignItems: "center", justifyContent: "center" },
1077
- valueGetter: (p) => p.data?.task.is_shared ?? false,
1078
- cellRenderer: cell(o.renderShared)
1079
- };
1080
- const actions = () => ({
1081
- ...base,
1082
- colId: "actions",
1083
- headerName: "",
1084
- // Always in view, even if the rest has to scroll.
1085
- pinned: "right",
1086
- lockPinned: true,
1087
- width: o.narrow ? NARROW_ACTIONS_WIDTH : ACTIONS_WIDTH,
1088
- minWidth: NARROW_ACTIONS_WIDTH,
1089
- cellStyle: { display: "flex", alignItems: "center", justifyContent: "flex-end" },
1090
- cellRenderer: cell(o.renderActions)
1091
- });
1092
- return [
1093
- name,
1094
- {
1095
- ...base,
1096
- colId: "owner",
1097
- ...sortable,
1098
- headerName: l.colOwner,
1099
- headerTooltip: l.ownerHint,
1100
- flex: 1.4,
1101
- minWidth: 150,
1102
- valueGetter: (p) => p.data ? o.getOwnerName(p.data) : "",
1103
- cellRenderer: cell(o.renderOwner)
1104
- },
1105
- {
1106
- ...base,
1107
- colId: "due",
1108
- ...sortable,
1109
- headerName: l.colDue,
1110
- flex: 1,
1111
- minWidth: 140,
1112
- valueGetter: (p) => p.data?.task.due_at ?? "",
1113
- cellRenderer: cell(o.renderDue)
1114
- },
1115
- {
1116
- ...base,
1117
- colId: "status",
1118
- ...sortable,
1119
- headerName: l.colStatus,
1120
- // Wide enough for "Generating" and "Waiting for access" in full, with the elapsed time.
1121
- flex: 1.3,
1122
- minWidth: 170,
1123
- valueGetter: (p) => p.data?.task.status ?? "",
1124
- cellRenderer: cell(o.renderStatus)
1125
- },
1126
- ...o.showOutputs ? [
1127
- {
1128
- ...base,
1129
- colId: "outputs",
1130
- headerName: l.colOutputs,
1131
- flex: 1.2,
1132
- minWidth: 130,
1133
- cellRenderer: cell(o.renderOutputs)
1134
- }
1135
- ] : [],
1136
- shared,
1137
- actions()
1138
- ];
1139
- }
1140
-
1141
- // src/agents/tasks/gridWidth.ts
1142
- function applyColumnWidthRules(api, gridWidth, rules) {
1143
- for (const rule of rules) {
1144
- if (api.getColumn(rule.colId)) api.setColumnsVisible([rule.colId], gridWidth >= rule.minWidth);
1145
- }
1146
- }
1147
-
1148
- // src/agents/tasks/taskTableLabels.ts
1149
- var DEFAULT_TASK_LIST_LABELS = {
1150
- filterAll: "All",
1151
- emptyTitle: "No tasks yet",
1152
- emptyMessage: "Use New to add one: just type what you need, or add a due date. Automations that create tasks live on the Workflows page. You always decide when a task is done.",
1153
- noMatches: "No tasks match.",
1154
- due: (when) => when,
1155
- overdue: (when) => `${when} \xB7 overdue`,
1156
- agent: "Agent",
1157
- colTask: "Task",
1158
- colOwner: "Owner",
1159
- ownerHint: "Who created the task. They are notified when its run finishes.",
1160
- colOutputs: "Outputs",
1161
- preparedBy: (agent) => `Prepared by ${agent}`,
1162
- colDue: "Due",
1163
- colStatus: "Status",
1164
- me: "Me",
1165
- someone: "Someone",
1166
- noDue: "\u2014",
1167
- colShared: "Shared",
1168
- shared: "Shared with the workspace",
1169
- sharedOwnerOnly: "Only the creator can change who can see this",
1170
- deleteOwnerOnly: "Only the creator or workspace owner can delete this",
1171
- goalMet: (met, total) => `${met}/${total} met`,
1172
- subtasksDone: (done, total) => `${done}/${total} sub-tasks done`,
1173
- expandSubtasks: "Show sub-tasks",
1174
- collapseSubtasks: "Hide sub-tasks",
1175
- selectAll: "Select all tasks",
1176
- selectTask: (name) => `Select ${name}`,
1177
- markDone: "Mark done",
1178
- reopen: "Reopen",
1179
- markDoneMany: "Mark done",
1180
- markDoneManyHint: "Marks the selected tasks done.",
1181
- deleteMany: "Delete",
1182
- moveTo: "Move to\u2026",
1183
- untitled: "Untitled task",
1184
- fromWorkflow: (name) => `From \u201C${name}\u201D`,
1185
- searchLabel: "Search tasks",
1186
- searchPlaceholder: "Search tasks\u2026",
1187
- expandAll: "Expand all",
1188
- collapseAll: "Collapse all",
1189
- moveToTitle: (name) => `Move \u201C${name}\u201D to\u2026`,
1190
- moveToNone: "No parent (top level)",
1191
- moveToSearch: "Search tasks\u2026",
1192
- moveToEmpty: "No task to move it under.",
1193
- moveToCancel: "Cancel",
1194
- subtasksSelected: (count) => `${count} ${count === 1 ? "sub-task" : "sub-tasks"} included`,
1195
- addSubtask: "Add sub-task",
1196
- edit: "Edit",
1197
- rename: "Rename",
1198
- delete: "Delete",
1199
- grantAccess: "Allow access and continue",
1200
- allowAccess: "Allow access",
1201
- moreActions: "More actions",
1202
- activity: "Activity",
1203
- statusHint: (status, ago) => `${status} \xB7 ${ago}`,
1204
- status: {}
1205
- };
1206
- var ROW_HEIGHT = 56;
1207
- var NARROW_ROW_HEIGHT = 76;
1208
- var MAX_INDENT_DEPTH = 4;
1209
- var ANIMATE_ROWS_UP_TO = 500;
1210
- var NARROW_GRID = 640;
1211
- var WIDTH_RULES = [
1212
- { colId: "shared", minWidth: 1050 },
1213
- { colId: "due", minWidth: 850 },
1214
- { colId: "outputs", minWidth: 930 },
1215
- { colId: "owner", minWidth: 730 },
1216
- { colId: "status", minWidth: NARROW_GRID }
1217
- ];
1218
- function statusHint(task, ctx) {
1219
- const { props: p, l } = ctx;
1220
- if (!p.formatRelative || !task.updated_at) return void 0;
1221
- const key = asTaskStatus(task.status);
1222
- const pill = { ...DEFAULT_TASK_STATUS_PILL_LABELS, ...p.statusPillLabels };
1223
- const label = task.pending_auth_request_id ? pill.waitingForAccess : STATUS_LABELS[key];
1224
- return l.statusHint(label, p.formatRelative(task.updated_at));
1225
- }
1226
- function TaskTable(props) {
1227
- const { tasks: allTasks, isLoading, busyId, header, testId } = props;
1228
- const l = {
1229
- ...DEFAULT_TASK_LIST_LABELS,
1230
- ...props.labels,
1231
- status: { ...STATUS_LABELS, ...props.labels?.status }
1232
- };
1233
- const [filter, setFilter] = useState("all");
1234
- const [query, setQuery] = useState("");
1235
- const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
1236
- const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
1237
- const [sort, setSort] = useState(null);
1238
- const [moving, setMoving] = useState(null);
1239
- const tasks = allTasks;
1240
- const apiRef = useRef(null);
1241
- const [narrow, setNarrow] = useState(false);
1242
- const rowCache = useRef(/* @__PURE__ */ new Map());
1243
- const tree = useMemo(() => indexTasks(tasks), [tasks]);
1244
- const peopleNames = useMemo(
1245
- () => new Map((props.people ?? []).map((p) => [p.id, p.label])),
1246
- [props.people]
1247
- );
1248
- const nameOf = (task) => task.title?.trim() || task.workflow_ext_id && props.workflowNames?.[task.workflow_ext_id] || l.untitled;
1249
- const owner = (task) => {
1250
- const id = ownerExtId(task);
1251
- const mine = task.is_owner !== false;
1252
- return {
1253
- id,
1254
- label: mine ? l.me : (id && peopleNames.get(id)) ?? l.someone,
1255
- isMe: mine
1256
- };
1257
- };
1258
- const starter = (task) => {
1259
- const id = task.created_by_ext_id ?? task.owner_ext_id;
1260
- const mine = task.is_owner !== false;
1261
- return {
1262
- id,
1263
- label: mine ? l.me : (id && peopleNames.get(id)) ?? l.someone,
1264
- isMe: mine
1265
- };
1266
- };
1267
- const renderPerson = props.renderPerson ?? ((person) => /* @__PURE__ */ jsx(PersonPill, { person }));
1268
- const q = (query.trim() || (props.filterText ?? "").trim()).toLowerCase();
1269
- const counts = useMemo(() => {
1270
- const map = new Map(STATUS_ORDER.map((s) => [s, 0]));
1271
- let all = 0;
1272
- for (const task of tasks) {
1273
- const text = `${nameOf(task)} ${task.description ?? ""}`.toLowerCase();
1274
- if (q && !text.includes(q)) continue;
1275
- all += 1;
1276
- const s = asTaskStatus(task.status);
1277
- map.set(s, (map.get(s) ?? 0) + 1);
1278
- }
1279
- map.set("all", all);
1280
- return map;
1281
- }, [tasks, q, props.workflowNames, l.untitled]);
1282
- const compare = useMemo(() => {
1283
- if (!sort) return void 0;
1284
- const keys = /* @__PURE__ */ new Map();
1285
- const compute = (t) => {
1286
- switch (sort.colId) {
1287
- case "name":
1288
- return nameOf(t).toLowerCase();
1289
- case "owner":
1290
- return owner(t).label.toLowerCase();
1291
- case "updated":
1292
- return t.updated_at ? Date.parse(t.updated_at) : 0;
1293
- case "messages":
1294
- return t.message_count ?? 0;
1295
- case "due":
1296
- return t.due_at ? Date.parse(t.due_at) : Number.POSITIVE_INFINITY;
1297
- default:
1298
- return STATUS_ORDER.indexOf(asTaskStatus(t.status));
1299
- }
1300
- };
1301
- const key = (t) => {
1302
- let k = keys.get(t);
1303
- if (k === void 0) {
1304
- k = compute(t);
1305
- keys.set(t, k);
1306
- }
1307
- return k;
1308
- };
1309
- const sign = sort.dir === "asc" ? 1 : -1;
1310
- return (a, b) => {
1311
- const [x, y] = [key(a), key(b)];
1312
- if (typeof x === "number" && typeof y === "number") return (x - y) * sign;
1313
- return String(x).localeCompare(String(y)) * sign;
1314
- };
1315
- }, [
1316
- sort,
1317
- peopleNames,
1318
- props.agentNames,
1319
- props.workflowNames,
1320
- l.me,
1321
- l.agent,
1322
- l.someone,
1323
- l.untitled
1324
- ]);
1325
- const rows = useMemo(
1326
- () => buildTaskRows(tasks, filter, collapsed, compare, {
1327
- query: query.trim() || props.filterText,
1328
- titleOf: nameOf,
1329
- cache: rowCache.current
1330
- }),
1331
- // nameOf reads the labels and workflow names the table was given.
1332
- // eslint-disable-next-line react-hooks/exhaustive-deps
1333
- [tasks, filter, collapsed, compare, query, props.filterText, props.workflowNames, l.untitled]
1334
- );
1335
- const selectedRows = useMemo(
1336
- () => rows.filter((r) => selectedIds.has(r.external_id)),
1337
- [rows, selectedIds]
1338
- );
1339
- const selectedTasks = useMemo(
1340
- () => tasks.filter((t) => selectedIds.has(t.external_id)),
1341
- [tasks, selectedIds]
1342
- );
1343
- const subtasksIncluded = useMemo(
1344
- () => selectedTasks.filter((t) => {
1345
- const parent = tree.parentOf.get(t.external_id);
1346
- return parent !== void 0 && selectedIds.has(parent);
1347
- }).length,
1348
- [selectedTasks, selectedIds, tree]
1349
- );
1350
- const markable = selectedTasks.filter(
1351
- (t) => t.status != null && (t.is_owner !== false || props.canEditShared) && isPlainTodo(t) && asTaskStatus(t.status) !== "done"
1352
- );
1353
- const deletable = selectedTasks.filter((t) => t.is_owner !== false || props.isWorkspaceOwner);
1354
- const rowsRef = useRef(rows);
1355
- rowsRef.current = rows;
1356
- const handleSelectionChange = (picked) => {
1357
- const visible = new Set(rowsRef.current.map((r) => r.external_id));
1358
- const pickedIds = new Set(picked.map((r) => r.external_id));
1359
- setSelectedIds((prev) => {
1360
- const added = [...pickedIds].filter((id) => !prev.has(id));
1361
- const removed = [...prev].filter((id) => visible.has(id) && !pickedIds.has(id));
1362
- if (added.length === 0 && removed.length === 0) return prev;
1363
- const next = new Set(prev);
1364
- for (const id of added) {
1365
- next.add(id);
1366
- for (const d of descendantIds(tree, id)) next.add(d);
1367
- }
1368
- for (const id of removed) {
1369
- next.delete(id);
1370
- for (const d of descendantIds(tree, id)) next.delete(d);
1371
- for (let at = tree.parentOf.get(id); at; at = tree.parentOf.get(at)) next.delete(at);
1372
- }
1373
- return next;
1374
- });
1375
- };
1376
- const setExpanded = (id, expanded) => setCollapsed((prev) => {
1377
- if (prev.has(id) === !expanded) return prev;
1378
- const next = new Set(prev);
1379
- if (expanded) next.delete(id);
1380
- else next.add(id);
1381
- return next;
1382
- });
1383
- const expandAll = () => setCollapsed(/* @__PURE__ */ new Set());
1384
- const collapseAll = () => setCollapsed(new Set(tree.kids.keys()));
1385
- const latest = useRef({ props, l, owner, starter, renderPerson, nameOf, setExpanded, narrow });
1386
- latest.current = { props, l, owner, starter, renderPerson, nameOf, setExpanded, narrow };
1387
- const showOutputs = Object.values(props.outputsByTask ?? {}).some((o) => o.length > 0);
1388
- const columnDefs = useMemo(
1389
- () => createTaskColumns({
1390
- narrow,
1391
- labels: l,
1392
- getOwnerName: (row) => latest.current.owner(row.task).label,
1393
- renderName: (row) => {
1394
- const { l: lb, props: p, nameOf: name } = latest.current;
1395
- const { task, depth, childStats, expanded } = row;
1396
- const id = task.external_id;
1397
- const status = asTaskStatus(task.status);
1398
- const goal = goalProgress(task);
1399
- const workflowName = task.workflow_ext_id ? p.workflowNames?.[task.workflow_ext_id] : null;
1400
- return /* @__PURE__ */ jsxs(
1401
- "div",
1402
- {
1403
- className: "flex min-w-0 flex-1 items-center gap-1.5",
1404
- style: depth > 0 ? { paddingLeft: `${Math.min(depth, MAX_INDENT_DEPTH) * 1.5}rem` } : void 0,
1405
- "data-depth": depth,
1406
- "aria-level": depth + 1,
1407
- "aria-setsize": row.setSize,
1408
- "aria-posinset": row.posInSet,
1409
- "data-context": row.context || void 0,
1410
- children: [
1411
- childStats ? /* @__PURE__ */ jsx(
1412
- "button",
1413
- {
1414
- type: "button",
1415
- onClick: () => latest.current.setExpanded(id, !expanded),
1416
- "aria-expanded": expanded,
1417
- "aria-label": expanded ? lb.collapseSubtasks : lb.expandSubtasks,
1418
- className: "flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted hover:text-foreground",
1419
- "data-testid": `task-expand-${id}`,
1420
- children: expanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-4", "aria-hidden": true }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": true })
1421
- }
1422
- ) : depth > 0 ? /* @__PURE__ */ jsx("span", { className: "size-5 shrink-0", "aria-hidden": true }) : null,
1423
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 leading-tight", children: [
1424
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
1425
- /* @__PURE__ */ jsx(
1426
- "button",
1427
- {
1428
- type: "button",
1429
- onClick: () => latest.current.props.onOpenThread(id, task),
1430
- title: task.agent_ext_id ? lb.preparedBy(p.agentNames?.[task.agent_ext_id] || lb.agent) : void 0,
1431
- className: cn(
1432
- // The title has priority: it keeps at least 12 characters before it ellipsises.
1433
- "min-w-[12ch] truncate text-left text-sm font-medium hover:underline focus-visible:underline focus-visible:outline-none",
1434
- status === "done" && "text-muted-foreground line-through"
1435
- ),
1436
- "data-testid": `task-name-${id}`,
1437
- children: name(task)
1438
- }
1439
- ),
1440
- goal && /* @__PURE__ */ jsxs(
1441
- ToneBadge,
1442
- {
1443
- tone: goal.done ? "success" : "neutral",
1444
- "data-testid": `task-goal-progress-${id}`,
1445
- children: [
1446
- goal.done ? "\u2713 " : "",
1447
- lb.goalMet(goal.met, goal.total)
1448
- ]
1449
- }
1450
- )
1451
- ] }),
1452
- latest.current.narrow && /* @__PURE__ */ jsx("div", { className: "pt-0.5", children: /* @__PURE__ */ jsx(
1453
- TaskStatusPill,
1454
- {
1455
- id,
1456
- status,
1457
- runStartedAt: task.run_started_at,
1458
- lastError: task.last_error,
1459
- awaitingAccess: Boolean(task.pending_auth_request_id),
1460
- labels: p.statusPillLabels
1461
- }
1462
- ) }),
1463
- (task.description || workflowName || childStats) && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2 text-xs text-muted-foreground", children: [
1464
- task.description ? /* @__PURE__ */ jsx("p", { className: "min-w-0 truncate", children: task.description }) : workflowName ? /* @__PURE__ */ jsxs(
1465
- "span",
1466
- {
1467
- className: "inline-flex min-w-0 items-center gap-1.5",
1468
- "data-testid": `task-workflow-${id}`,
1469
- children: [
1470
- /* @__PURE__ */ jsx(Workflow, { className: "size-3.5 shrink-0", "aria-hidden": true }),
1471
- /* @__PURE__ */ jsx("span", { className: "truncate", children: lb.fromWorkflow(workflowName) })
1472
- ]
1473
- }
1474
- ) : null,
1475
- childStats && /* @__PURE__ */ jsx(
1476
- "span",
1477
- {
1478
- className: "shrink-0 tabular-nums",
1479
- "data-testid": `task-subtask-progress-${id}`,
1480
- children: lb.subtasksDone(childStats.done, childStats.total)
1481
- }
1482
- )
1483
- ] })
1484
- ] })
1485
- ]
1486
- }
1487
- );
1488
- },
1489
- // Who has the next step: the person pill, with the "Me" treatment. A plain chat is its starter's.
1490
- renderOwner: (row) => /* @__PURE__ */ jsx("span", { "data-testid": `task-owner-${row.external_id}`, className: "flex min-w-0", children: latest.current.renderPerson(
1491
- row.task.status ? latest.current.owner(row.task) : latest.current.starter(row.task)
1492
- ) }),
1493
- renderDue: (row) => {
1494
- const { l: lb, props: p } = latest.current;
1495
- const { task } = row;
1496
- const dueMs = task.due_at ? Date.parse(task.due_at) : NaN;
1497
- const overdue = asTaskStatus(task.status) !== "done" && !Number.isNaN(dueMs) && dueMs < Date.now();
1498
- return /* @__PURE__ */ jsx(
1499
- "span",
1500
- {
1501
- className: cn(
1502
- "truncate text-sm",
1503
- overdue ? "font-medium text-red-text" : "text-muted-foreground"
1504
- ),
1505
- "data-testid": `task-due-${task.external_id}`,
1506
- "data-overdue": overdue,
1507
- children: task.due_at ? overdue ? lb.overdue(p.formatDate(task.due_at)) : lb.due(p.formatDate(task.due_at)) : lb.noDue
1508
- }
1509
- );
1510
- },
1511
- renderShared: (row) => {
1512
- const { l: lb, props: p } = latest.current;
1513
- const { task } = row;
1514
- if (!p.onToggleShared) return null;
1515
- return /* @__PURE__ */ jsx(
1516
- VisibilityToggle,
1517
- {
1518
- items: { shared: task.is_shared ?? false },
1519
- onToggle: (shouldShare) => latest.current.props.onToggleShared?.(task, shouldShare),
1520
- disabled: task.is_owner === false,
1521
- disabledTooltip: lb.sharedOwnerOnly,
1522
- showLabel: false,
1523
- "data-testid": `task-shared-${task.external_id}`
1524
- }
1525
- );
1526
- },
1527
- showOutputs,
1528
- renderOutputs: (row) => /* @__PURE__ */ jsx(
1529
- TaskOutputsCell,
1530
- {
1531
- id: row.external_id,
1532
- outputs: latest.current.props.outputsByTask?.[row.external_id],
1533
- onOpen: (docId) => latest.current.props.onOpenOutput?.(docId, row.task),
1534
- labels: latest.current.props.outputsLabels
1535
- }
1536
- ),
1537
- renderStatus: (row) => /* @__PURE__ */ jsx(
1538
- TaskStatusPill,
1539
- {
1540
- id: row.external_id,
1541
- status: asTaskStatus(row.task.status),
1542
- runStartedAt: row.task.run_started_at,
1543
- lastError: row.task.last_error,
1544
- awaitingAccess: Boolean(row.task.pending_auth_request_id),
1545
- labels: latest.current.props.statusPillLabels,
1546
- hint: statusHint(row.task, latest.current)
1547
- }
1548
- ),
1549
- renderActions: (row) => {
1550
- const { l: lb, props: p } = latest.current;
1551
- const { task } = row;
1552
- const id = task.external_id;
1553
- const status = asTaskStatus(task.status);
1554
- const busy = p.busyId === id;
1555
- const isOwner = task.is_owner !== false;
1556
- const canEdit = isOwner || Boolean(p.canEditShared);
1557
- const canAdmin = isOwner || Boolean(p.isWorkspaceOwner);
1558
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
1559
- !latest.current.narrow && isOwner && task.pending_auth_request_id && p.onGrantAccess && /* @__PURE__ */ jsxs(
1560
- Button,
1561
- {
1562
- size: "sm",
1563
- className: "h-8 gap-1.5 pointer-coarse:h-11",
1564
- onClick: () => p.onGrantAccess?.(task),
1565
- "data-testid": `task-allow-access-${id}`,
1566
- children: [
1567
- /* @__PURE__ */ jsx(KeyRound, { className: "size-3.5", "aria-hidden": true }),
1568
- lb.allowAccess
1569
- ]
1570
- }
1571
- ),
1572
- /* @__PURE__ */ jsx(
1573
- TaskRowMenu,
1574
- {
1575
- task,
1576
- status,
1577
- busy,
1578
- isOwner,
1579
- canAdmin,
1580
- canEdit,
1581
- labels: lb,
1582
- onMarkDone: () => p.onMarkDone(task),
1583
- onReopen: () => p.onReopen(task),
1584
- onAddSubtask: p.onAddSubtask ? () => p.onAddSubtask?.(task) : void 0,
1585
- onMoveTo: p.onMove ? () => setMoving(task) : void 0,
1586
- onEdit: () => p.onEdit(task),
1587
- onDelete: () => p.onDelete(task),
1588
- onOpenActivity: p.onOpenActivity ? () => p.onOpenActivity?.(task) : void 0,
1589
- onGrantAccess: p.onGrantAccess ? () => p.onGrantAccess?.(task) : void 0
1590
- }
1591
- )
1592
- ] });
1593
- }
1594
- }),
1595
- // Stable on purpose: only the header texts rebuild the columns (see `latest`).
1596
- // eslint-disable-next-line react-hooks/exhaustive-deps
1597
- [
1598
- l.colTask,
1599
- l.colOwner,
1600
- l.colDue,
1601
- l.colStatus,
1602
- l.colShared,
1603
- l.colOutputs,
1604
- l.shared,
1605
- showOutputs,
1606
- narrow
1607
- ]
1608
- );
1609
- useEffect(() => {
1610
- apiRef.current?.refreshCells({ force: true });
1611
- }, [
1612
- busyId,
1613
- props.people,
1614
- props.agentNames,
1615
- props.workflowNames,
1616
- props.outputsByTask,
1617
- props.renderPerson,
1618
- props.labels,
1619
- props.canEditShared,
1620
- props.isWorkspaceOwner,
1621
- narrow
1622
- ]);
1623
- const animate = rows.length <= ANIMATE_ROWS_UP_TO;
1624
- const gridOptions = useMemo(
1625
- () => ({
1626
- suppressClickEdit: true,
1627
- // Keyboard focus lives on the name column only (the others are not navigable): ArrowRight
1628
- // opens a task's sub-tasks and ArrowLeft closes them, or steps up to the parent.
1629
- suppressCellFocus: false,
1630
- suppressColumnVirtualisation: true,
1631
- animateRows: animate,
1632
- getRowClass: (p) => p.data?.context ? "opacity-60" : void 0,
1633
- // The grid shows and reports the sort; the table orders each level (see buildTaskRows).
1634
- onSortChanged: (event) => {
1635
- const sorted = event.api.getColumnState().find((c) => c.sort);
1636
- setSort(
1637
- sorted?.colId ? { colId: sorted.colId, dir: sorted.sort ?? "asc" } : null
1638
- );
1639
- },
1640
- onGridSizeChanged: (event) => {
1641
- applyColumnWidthRules(event.api, event.clientWidth, WIDTH_RULES);
1642
- setNarrow(event.clientWidth < NARROW_GRID);
1643
- },
1644
- onCellKeyDown: (event) => {
1645
- const row = event.data;
1646
- const key = event.event?.key;
1647
- if (!row || event.column.getColId() !== "name") return;
1648
- if (key === "ArrowRight" && row.childStats && !row.expanded) {
1649
- event.event?.preventDefault();
1650
- latest.current.setExpanded(row.external_id, true);
1651
- } else if (key === "ArrowLeft") {
1652
- if (row.childStats && row.expanded) {
1653
- event.event?.preventDefault();
1654
- latest.current.setExpanded(row.external_id, false);
1655
- } else if (row.parentId) {
1656
- event.event?.preventDefault();
1657
- const parent = event.api.getRowNode(row.parentId);
1658
- if (parent?.rowIndex != null) {
1659
- event.api.ensureIndexVisible(parent.rowIndex);
1660
- event.api.setFocusedCell(parent.rowIndex, "name");
1661
- }
1662
- }
1663
- }
1664
- }
1665
- }),
1666
- [animate]
1667
- );
1668
- const clearSelection = () => setSelectedIds(/* @__PURE__ */ new Set());
1669
- const filterKeys = ["all", ...STATUS_ORDER].filter(
1670
- (key) => key !== "chat" || (counts.get(key) ?? 0) > 0
1671
- );
1672
- const hasChildren = tree.kids.size > 0;
1673
- return /* @__PURE__ */ jsxs(
1674
- "div",
1675
- {
1676
- className: "flex min-h-0 flex-1 flex-col gap-3",
1677
- "data-testid": testIdOr(testId, { testId: "task-list" }),
1678
- children: [
1679
- header,
1680
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1681
- /* @__PURE__ */ jsx("div", { role: "tablist", "aria-label": "Filter tasks", className: "flex flex-wrap gap-1.5", children: filterKeys.map((key) => {
1682
- const count = counts.get(key) ?? 0;
1683
- const isSelected = filter === key;
1684
- return /* @__PURE__ */ jsxs(
1685
- "button",
1686
- {
1687
- type: "button",
1688
- role: "tab",
1689
- "aria-selected": isSelected,
1690
- onClick: () => setFilter(key),
1691
- className: cn(
1692
- "inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring pointer-coarse:min-h-11",
1693
- isSelected ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground hover:bg-accent"
1694
- ),
1695
- "data-testid": `task-filter-${key}`,
1696
- children: [
1697
- key === "all" ? l.filterAll : l.status[key],
1698
- /* @__PURE__ */ jsx("span", { className: "tabular-nums opacity-70", children: count })
1699
- ]
1700
- },
1701
- key
1702
- );
1703
- }) }),
1704
- /* @__PURE__ */ jsx(
1705
- Input,
1706
- {
1707
- type: "search",
1708
- value: query,
1709
- onChange: (e) => setQuery(e.target.value),
1710
- placeholder: l.searchPlaceholder,
1711
- "aria-label": l.searchLabel,
1712
- className: "h-8 max-w-56 text-xs pointer-coarse:h-11",
1713
- "data-testid": "task-search"
1714
- }
1715
- ),
1716
- hasChildren && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex gap-1", children: [
1717
- /* @__PURE__ */ jsxs(
1718
- Button,
1719
- {
1720
- type: "button",
1721
- variant: "ghost",
1722
- size: "sm",
1723
- className: "h-8 gap-1.5 text-xs pointer-coarse:h-11",
1724
- onClick: expandAll,
1725
- "data-testid": "task-expand-all",
1726
- children: [
1727
- /* @__PURE__ */ jsx(ChevronsUpDown, { className: "size-3.5", "aria-hidden": true }),
1728
- l.expandAll
1729
- ]
1730
- }
1731
- ),
1732
- /* @__PURE__ */ jsxs(
1733
- Button,
1734
- {
1735
- type: "button",
1736
- variant: "ghost",
1737
- size: "sm",
1738
- className: "h-8 gap-1.5 text-xs pointer-coarse:h-11",
1739
- onClick: collapseAll,
1740
- "data-testid": "task-collapse-all",
1741
- children: [
1742
- /* @__PURE__ */ jsx(ChevronsDownUp, { className: "size-3.5", "aria-hidden": true }),
1743
- l.collapseAll
1744
- ]
1745
- }
1746
- )
1747
- ] })
1748
- ] }),
1749
- tasks.length === 0 && !isLoading ? /* @__PURE__ */ jsx(
1750
- EmptyState,
1751
- {
1752
- icon: ListTodo,
1753
- title: l.emptyTitle,
1754
- description: l.emptyMessage,
1755
- testId: "task-list-empty"
1756
- }
1757
- ) : /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(
1758
- GridView,
1759
- {
1760
- testId: "task-grid",
1761
- gridId: props.gridId ?? "tasks-grid-v4",
1762
- items: rows,
1763
- columnDefs,
1764
- getItemId: (row) => row.external_id,
1765
- getRowTestId: (row) => `task-row-${row.external_id}`,
1766
- isLoading,
1767
- showEmpty: rows.length === 0,
1768
- emptyState: { title: l.noMatches },
1769
- rowHeight: narrow ? NARROW_ROW_HEIGHT : ROW_HEIGHT,
1770
- height: "100%",
1771
- onGridReady: ({ api }) => {
1772
- apiRef.current = api;
1773
- },
1774
- selection: {
1775
- mode: "multiple",
1776
- controlled: true,
1777
- selected: selectedRows,
1778
- onSelectionChange: handleSelectionChange
1779
- },
1780
- gridOptions
1781
- }
1782
- ) }),
1783
- /* @__PURE__ */ jsx(
1784
- SelectionBar,
1785
- {
1786
- count: selectedTasks.length,
1787
- onClear: clearSelection,
1788
- status: selectedTasks.length > 0 && subtasksIncluded > 0 ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", "data-testid": "task-selection-subtasks", children: l.subtasksSelected(subtasksIncluded) }) : void 0,
1789
- children: (isCompact) => /* @__PURE__ */ jsxs(Fragment, { children: [
1790
- props.selectionActions?.(selectedTasks, isCompact, clearSelection),
1791
- props.onMarkDoneMany && /* @__PURE__ */ jsx(
1792
- SelectionBarAction,
1793
- {
1794
- icon: CheckCheck,
1795
- label: l.markDoneMany,
1796
- onClick: () => {
1797
- props.onMarkDoneMany?.(markable);
1798
- clearSelection();
1799
- },
1800
- disabled: markable.length === 0,
1801
- isCompact,
1802
- tooltip: l.markDoneManyHint,
1803
- testId: "task-bulk-done"
1804
- }
1805
- ),
1806
- props.onDeleteMany && /* @__PURE__ */ jsx(
1807
- SelectionBarAction,
1808
- {
1809
- icon: Trash2,
1810
- label: l.deleteMany,
1811
- onClick: () => {
1812
- props.onDeleteMany?.(deletable);
1813
- clearSelection();
1814
- },
1815
- disabled: deletable.length === 0,
1816
- isCompact,
1817
- className: "border-red-border text-red-text hover:bg-red-bg hover:text-red-text",
1818
- testId: "task-bulk-delete"
1819
- }
1820
- )
1821
- ] })
1822
- }
1823
- ),
1824
- moving && props.onMove && /* @__PURE__ */ jsx(
1825
- TaskMoveDialog,
1826
- {
1827
- open: true,
1828
- onOpenChange: (open) => !open && setMoving(null),
1829
- options: moveTargets(tasks, moving).map((t) => ({ id: t.external_id, label: nameOf(t) })),
1830
- currentParentId: moving.parent_conversation_ext_id,
1831
- onPick: (parentId) => props.onMove?.(moving, parentId),
1832
- labels: {
1833
- title: l.moveToTitle(nameOf(moving)),
1834
- none: l.moveToNone,
1835
- search: l.moveToSearch,
1836
- empty: l.moveToEmpty,
1837
- cancel: l.moveToCancel
1838
- }
1839
- }
1840
- )
1841
- ]
1842
- }
1843
- );
1844
- }
1845
- function TaskInbox(p) {
1846
- const [filterOpen, setFilterOpen] = useState(false);
1847
- const [statusFilters, setStatusFilters] = useState(
1848
- () => /* @__PURE__ */ new Map([["done", "exclude"]])
1849
- );
1850
- const [localQuery, setLocalQuery] = useState("");
1851
- const [searchFields, setSearchFields] = useState(["text", "labels"]);
1852
- const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
1853
- const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
1854
- const [moving, setMoving] = useState(null);
1855
- const l = { ...DEFAULT_TASK_LIST_LABELS, ...p.labels };
1856
- const nameOf = (t) => t.title?.trim() || t.workflow_ext_id && p.workflowNames?.[t.workflow_ext_id] || l.untitled;
1857
- const all = p.tasks.filter((t) => t.status != null);
1858
- const filterText = p.filterText ?? localQuery;
1859
- const q = filterText.trim().toLowerCase();
1860
- const included = [...statusFilters].filter(([, state]) => state === "include").map(([id]) => id);
1861
- const statusMatches = (t) => statusFilters.get(asTaskStatus(t.status)) !== "exclude" && (!included.length || included.includes(asTaskStatus(t.status)));
1862
- const rows = all.filter(
1863
- (t) => statusMatches(t) && (!q || [
1864
- searchFields.includes("text") ? `${nameOf(t)} ${t.description ?? ""}` : "",
1865
- searchFields.includes("labels") && t.workflow_ext_id ? p.workflowNames?.[t.workflow_ext_id] ?? "" : ""
1866
- ].join(" ").toLowerCase().includes(q))
1867
- );
1868
- const hasActiveFilters = !!q || [...statusFilters.values()].some((state) => state != null);
1869
- const selectedTasks = all.filter((t) => selected.has(t.external_id));
1870
- const canEdit = (t) => t.is_owner !== false || Boolean(p.canEditShared);
1871
- const markable = (t) => t.status !== "done" && t.status !== "generating" && canEdit(t) && !t.in_progress;
1872
- const markableTasks = selectedTasks.filter(markable);
1873
- const deletableTasks = selectedTasks.filter((t) => t.is_owner !== false || p.isWorkspaceOwner);
1874
- const clearSelection = () => setSelected(/* @__PURE__ */ new Set());
1875
- const toggle = (set, id) => set((old) => {
1876
- const next = new Set(old);
1877
- if (next.has(id)) next.delete(id);
1878
- else next.add(id);
1879
- return next;
1880
- });
1881
- p.renderPerson ?? ((value) => /* @__PURE__ */ jsx(PersonPill, { person: value }));
1882
- const taskById = new Map(all.map((t) => [t.external_id, t]));
1883
- const visible = new Set(rows.map((t) => t.external_id));
1884
- for (const task of rows) {
1885
- let id = task.parent_conversation_ext_id;
1886
- const seen = /* @__PURE__ */ new Set();
1887
- while (id && taskById.has(id) && !seen.has(id)) {
1888
- seen.add(id);
1889
- visible.add(id);
1890
- id = taskById.get(id)?.parent_conversation_ext_id;
1891
- }
1892
- }
1893
- const listed = all.filter((t) => visible.has(t.external_id));
1894
- const roots = listed.filter(
1895
- (t) => !t.parent_conversation_ext_id || !visible.has(t.parent_conversation_ext_id)
1896
- );
1897
- const workflows = (p.workflows ?? []).filter((w) => {
1898
- const runs = roots.filter((t) => t.workflow_ext_id === w.external_id);
1899
- return runs.length > 0 || !included.length && (!q || w.name.toLowerCase().includes(q));
1900
- });
1901
- const workflowIds = new Set(workflows.map((w) => w.external_id));
1902
- const order = (a, b) => {
1903
- const weight = (t) => t.pending_auth_request_id || t.last_error ? 0 : t.status === "todo" ? 1 : t.status === "generating" ? 2 : 3;
1904
- return weight(a) - weight(b) || (b.updated_at ?? "").localeCompare(a.updated_at ?? "");
1905
- };
1906
- const renderTask = (task, depth = 0) => {
1907
- const id = task.external_id;
1908
- const status = asTaskStatus(task.status);
1909
- const busy = p.busyId === id;
1910
- const children = listed.filter((t) => t.parent_conversation_ext_id === id).sort(order);
1911
- const outputs = p.outputsByTask?.[id] ?? [];
1912
- return /* @__PURE__ */ jsxs("li", { "data-testid": `task-inbox-row-${id}`, children: [
1913
- /* @__PURE__ */ jsxs(
1914
- "div",
1915
- {
1916
- className: cn(
1917
- "relative flex cursor-pointer items-start gap-2 border-b py-3 hover:bg-muted/40",
1918
- selected.has(id) && "bg-muted/60"
1919
- ),
1920
- onClick: (event) => {
1921
- if (!event.target.closest(
1922
- 'button, a, input, select, textarea, [role="menuitem"], [role="checkbox"], [role="combobox"]'
1923
- )) {
1924
- p.onOpenThread(id, task);
1925
- }
1926
- },
1927
- style: { paddingLeft: `${Math.min(depth, 5) * 20}px` },
1928
- children: [
1929
- children.length > 0 && /* @__PURE__ */ jsx(
1930
- Button,
1931
- {
1932
- variant: "ghost",
1933
- size: "icon",
1934
- className: "size-7 shrink-0",
1935
- "aria-label": `${collapsed.has(id) ? "Expand" : "Collapse"} ${nameOf(task)}`,
1936
- onClick: () => toggle(setCollapsed, id),
1937
- children: collapsed.has(id) ? /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "size-4" })
1938
- }
1939
- ),
1940
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1941
- /* @__PURE__ */ jsx(
1942
- "button",
1943
- {
1944
- className: cn(
1945
- "block w-full text-left text-sm font-medium hover:underline",
1946
- status === "done" && "text-muted-foreground"
1947
- ),
1948
- "data-testid": `task-open-${id}`,
1949
- onClick: () => p.onOpenThread(id, task),
1950
- children: nameOf(task)
1951
- }
1952
- ),
1953
- /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
1954
- status !== "todo" && /* @__PURE__ */ jsx(
1955
- TaskStatusPill,
1956
- {
1957
- id,
1958
- status: task.status,
1959
- runStartedAt: task.run_started_at,
1960
- lastError: task.last_error,
1961
- awaitingAccess: Boolean(task.pending_auth_request_id)
1962
- }
1963
- ),
1964
- task.due_at && /* @__PURE__ */ jsxs("span", { children: [
1965
- "Due ",
1966
- p.formatDate(task.due_at)
1967
- ] }),
1968
- children.length > 0 && /* @__PURE__ */ jsxs("span", { children: [
1969
- all.filter((t) => t.parent_conversation_ext_id === id && t.status === "done").length,
1970
- "/",
1971
- all.filter((t) => t.parent_conversation_ext_id === id).length,
1972
- " steps complete"
1973
- ] })
1974
- ] }),
1975
- outputs.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-2", "aria-label": "Outputs", children: outputs.map((output) => /* @__PURE__ */ jsxs(
1976
- Button,
1977
- {
1978
- variant: "link",
1979
- size: "sm",
1980
- className: "h-auto max-w-full gap-1 px-0 py-1 text-xs",
1981
- disabled: !p.onOpenOutput,
1982
- onClick: () => p.onOpenOutput?.(output.id, task),
1983
- "data-testid": `task-output-${id}-${output.id}`,
1984
- children: [
1985
- /* @__PURE__ */ jsx(FileText, { className: "size-3.5 shrink-0" }),
1986
- /* @__PURE__ */ jsx("span", { className: "truncate", children: output.name })
1987
- ]
1988
- },
1989
- output.id
1990
- )) })
1991
- ] }),
1992
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
1993
- task.pending_auth_request_id && task.is_owner !== false && p.onGrantAccess && /* @__PURE__ */ jsx(
1994
- Button,
1995
- {
1996
- size: "sm",
1997
- variant: "outline",
1998
- disabled: busy,
1999
- onClick: () => p.onGrantAccess?.(task),
2000
- children: "Allow access"
2001
- }
2002
- ),
2003
- /* @__PURE__ */ jsx(
2004
- TaskRowMenu,
2005
- {
2006
- task,
2007
- status,
2008
- busy,
2009
- isOwner: task.is_owner !== false,
2010
- canAdmin: task.is_owner !== false || Boolean(p.isWorkspaceOwner),
2011
- canEdit: canEdit(task),
2012
- canMarkDone: markable(task),
2013
- canReopen: canEdit(task),
2014
- selected: selected.has(id),
2015
- onToggleSelected: p.selectionActions || p.onMarkDoneMany || p.onDeleteMany ? () => toggle(setSelected, id) : void 0,
2016
- labels: l,
2017
- onMarkDone: () => p.onMarkDone(task),
2018
- onNotify: p.onNotify ? () => p.onNotify?.(task) : void 0,
2019
- onReopen: () => p.onReopen(task),
2020
- onEdit: () => p.onEdit(task),
2021
- onAddSubtask: p.onAddSubtask ? () => p.onAddSubtask?.(task) : void 0,
2022
- onMoveTo: p.onMove ? () => setMoving(task) : void 0,
2023
- onOpenActivity: p.onOpenActivity ? () => p.onOpenActivity?.(task) : void 0,
2024
- onToggleShared: p.onToggleShared ? () => p.onToggleShared?.(task, !task.is_shared) : void 0
2025
- }
2026
- )
2027
- ] })
2028
- ]
2029
- }
2030
- ),
2031
- children.length > 0 && !collapsed.has(id) && /* @__PURE__ */ jsx("ul", { children: children.map((child) => renderTask(child, depth + 1)) })
2032
- ] }, id);
2033
- };
2034
- return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col", "data-testid": p.testId ?? "task-inbox", children: [
2035
- /* @__PURE__ */ jsx(
2036
- TabHeader,
2037
- {
2038
- testId: "tasks-tab-header",
2039
- tabs: [
2040
- {
2041
- id: "tasks",
2042
- label: "",
2043
- count: rows.length,
2044
- countPlacement: "leading",
2045
- countIcon: /* @__PURE__ */ jsx(ListTodo, { className: "size-3.5" }),
2046
- hasActiveFilters,
2047
- testId: "tasks-tab",
2048
- presentation: "count"
2049
- }
2050
- ],
2051
- activeTabId: "tasks",
2052
- onTabChange: () => {
2053
- },
2054
- firstTabContent: /* @__PURE__ */ jsx(
2055
- FilterInput,
2056
- {
2057
- value: filterText,
2058
- onChange: p.onFilterTextChange ?? setLocalQuery,
2059
- fields: searchFields,
2060
- onFieldsChange: setSearchFields,
2061
- labels: { fieldNames: { text: "Task", labels: "Workflow" } },
2062
- className: "w-44 max-w-[40vw]",
2063
- testIds: {
2064
- root: "tasks-filter",
2065
- input: "tasks-filter-input",
2066
- fieldsButton: "tasks-filter-fields"
2067
- },
2068
- filter: /* @__PURE__ */ jsx(
2069
- TriStateFilterMenu,
2070
- {
2071
- testId: "tasks-status-filter",
2072
- open: filterOpen,
2073
- onOpenChange: setFilterOpen,
2074
- filterStates: statusFilters,
2075
- onFiltersChange: setStatusFilters,
2076
- options: [
2077
- {
2078
- id: "todo",
2079
- label: "To do",
2080
- count: all.filter((t) => t.status === "todo").length
2081
- },
2082
- {
2083
- id: "generating",
2084
- label: "Generating",
2085
- count: all.filter((t) => t.status === "generating").length
2086
- },
2087
- {
2088
- id: "done",
2089
- label: "Completed",
2090
- count: all.filter((t) => t.status === "done").length
2091
- }
2092
- ],
2093
- groups: [{ label: "Status", options: ["todo", "generating", "done"] }],
2094
- triggerClassName: "flex h-5 items-center gap-0.5 rounded px-1 text-muted-foreground hover:text-foreground pointer-coarse:h-11! pointer-coarse:min-w-11 pointer-coarse:justify-center",
2095
- renderChevron: true,
2096
- disableHoverOpen: true
2097
- }
2098
- )
2099
- }
2100
- ),
2101
- rightContent: p.onCreate ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
2102
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { "data-testid": "new-task-button", children: [
2103
- /* @__PURE__ */ jsx(Plus, { className: "mr-1 size-4" }),
2104
- "New",
2105
- /* @__PURE__ */ jsx(ChevronDown, { className: "ml-1 size-3" })
2106
- ] }) }),
2107
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
2108
- /* @__PURE__ */ jsx(DropdownMenuItem, { onSelect: p.onCreate, children: "Task" }),
2109
- p.onCreateWorkflow && /* @__PURE__ */ jsx(DropdownMenuItem, { onSelect: p.onCreateWorkflow, children: "Saved workflow" })
2110
- ] })
2111
- ] }) : p.header
2112
- }
2113
- ),
2114
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", "aria-busy": p.isLoading, children: p.isLoading ? /* @__PURE__ */ jsx("p", { className: "py-12 text-center text-sm text-muted-foreground", children: "Loading work\u2026" }) : !listed.length && !workflows.length ? /* @__PURE__ */ jsx(
2115
- EmptyState,
2116
- {
2117
- title: q ? "No matches" : all.length ? "You\u2019re all caught up" : "No work here yet",
2118
- description: "Create a task from a prompt or a saved workflow. Keep its drafts and outputs together."
2119
- }
2120
- ) : /* @__PURE__ */ jsxs("ul", { children: [
2121
- workflows.map((w) => {
2122
- const runs = roots.filter((t) => t.workflow_ext_id === w.external_id).sort(order);
2123
- const editable = w.is_owner !== false || p.canEditShared;
2124
- return /* @__PURE__ */ jsxs("li", { "data-testid": `task-workflow-${w.external_id}`, children: [
2125
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 border-b py-3", children: [
2126
- runs.length > 0 ? /* @__PURE__ */ jsx(
2127
- Button,
2128
- {
2129
- variant: "ghost",
2130
- size: "icon",
2131
- className: "size-7",
2132
- "aria-label": `${collapsed.has(w.external_id) ? "Expand" : "Collapse"} ${w.name}`,
2133
- onClick: () => toggle(setCollapsed, w.external_id),
2134
- children: collapsed.has(w.external_id) ? /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "size-4" })
2135
- }
2136
- ) : /* @__PURE__ */ jsx(Workflow, { className: "mx-1 mt-1 size-4 shrink-0 text-muted-foreground" }),
2137
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
2138
- /* @__PURE__ */ jsx(
2139
- "button",
2140
- {
2141
- className: "block text-left text-sm font-medium hover:underline",
2142
- disabled: !editable,
2143
- onClick: () => p.onEditWorkflow?.(w.external_id),
2144
- children: w.name
2145
- }
2146
- ),
2147
- /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [
2148
- "Saved workflow \xB7 ",
2149
- w.summary ?? "Run when needed",
2150
- !w.active ? " \xB7 Paused" : "",
2151
- runs.length ? ` \xB7 ${runs.length} ${runs.length === 1 ? "task" : "tasks"}` : ""
2152
- ] }),
2153
- w.last_error && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-destructive", children: w.last_error })
2154
- ] }),
2155
- /* @__PURE__ */ jsxs(DropdownMenu, { children: [
2156
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
2157
- Button,
2158
- {
2159
- variant: "ghost",
2160
- size: "icon",
2161
- className: "size-8",
2162
- "aria-label": `Actions for ${w.name}`,
2163
- children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-4" })
2164
- }
2165
- ) }),
2166
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
2167
- p.onWorkflowHistory && /* @__PURE__ */ jsx(DropdownMenuItem, { onSelect: () => p.onWorkflowHistory?.(w.external_id), children: "Run history" }),
2168
- editable && p.onToggleWorkflow && /* @__PURE__ */ jsx(
2169
- DropdownMenuItem,
2170
- {
2171
- onSelect: () => p.onToggleWorkflow?.(w.external_id, !w.active),
2172
- children: w.active ? "Pause" : "Resume"
2173
- }
2174
- ),
2175
- (w.is_owner !== false || p.isWorkspaceOwner) && p.onDeleteWorkflow && /* @__PURE__ */ jsx(DropdownMenuItem, { onSelect: () => p.onDeleteWorkflow?.(w.external_id), children: "Delete workflow" })
2176
- ] })
2177
- ] })
2178
- ] }),
2179
- !collapsed.has(w.external_id) && runs.length > 0 && /* @__PURE__ */ jsx("ul", { children: runs.map((t) => renderTask(t, 1)) })
2180
- ] }, w.external_id);
2181
- }),
2182
- roots.filter((t) => !t.workflow_ext_id || !workflowIds.has(t.workflow_ext_id)).sort(order).map((t) => renderTask(t))
2183
- ] }) }),
2184
- /* @__PURE__ */ jsx(SelectionBar, { count: selectedTasks.length, onClear: clearSelection, children: (isCompact) => /* @__PURE__ */ jsxs(Fragment, { children: [
2185
- p.selectionActions?.(selectedTasks, isCompact, clearSelection),
2186
- p.onMarkDoneMany && /* @__PURE__ */ jsx(
2187
- SelectionBarAction,
2188
- {
2189
- icon: CheckCheck,
2190
- label: l.markDoneMany,
2191
- isCompact,
2192
- disabled: !markableTasks.length,
2193
- onClick: () => {
2194
- p.onMarkDoneMany?.(markableTasks);
2195
- clearSelection();
2196
- },
2197
- testId: "task-bulk-done"
2198
- }
2199
- ),
2200
- p.onDeleteMany && /* @__PURE__ */ jsx(
2201
- SelectionBarAction,
2202
- {
2203
- icon: Trash2,
2204
- label: l.deleteMany,
2205
- isCompact,
2206
- disabled: !deletableTasks.length,
2207
- onClick: () => {
2208
- p.onDeleteMany?.(deletableTasks);
2209
- clearSelection();
2210
- },
2211
- className: "text-red-text hover:bg-red-bg hover:text-red-text",
2212
- testId: "task-bulk-delete"
2213
- }
2214
- )
2215
- ] }) }),
2216
- moving && p.onMove && /* @__PURE__ */ jsx(
2217
- TaskMoveDialog,
2218
- {
2219
- open: true,
2220
- onOpenChange: (open) => !open && setMoving(null),
2221
- options: moveTargets(all, moving).map((t) => ({ id: t.external_id, label: nameOf(t) })),
2222
- currentParentId: moving.parent_conversation_ext_id,
2223
- onPick: (id) => p.onMove?.(moving, id),
2224
- labels: {
2225
- title: l.moveToTitle(nameOf(moving)),
2226
- none: l.moveToNone,
2227
- search: l.moveToSearch,
2228
- empty: l.moveToEmpty,
2229
- cancel: l.moveToCancel
2230
- }
2231
- }
2232
- )
2233
- ] });
2234
- }
2235
- function filterConversationRows(conversations, query, labelText, doneFilter = "exclude", visibilityFilters, meetingFilter = null) {
2236
- const q = query.trim().toLowerCase();
2237
- return [...conversations].sort(
2238
- (a, b) => (Date.parse(b.last_message_updated_at || b.updated_at || b.created_at || "") || 0) - (Date.parse(a.last_message_updated_at || a.updated_at || a.created_at || "") || 0)
2239
- ).filter(
2240
- (row) => doneFilter === "exclude" ? row.status !== "done" : doneFilter === "include" ? row.status === "done" : true
2241
- ).filter((row) => {
2242
- const state = row.is_shared ? visibilityFilters?.workspace : visibilityFilters?.me;
2243
- const hasIncluded = visibilityFilters?.me === "include" || visibilityFilters?.workspace === "include";
2244
- return state !== "exclude" && (!hasIncluded || state === "include");
2245
- }).filter(
2246
- (row) => meetingFilter === "include" ? !!row.meeting_call_ext_id : meetingFilter === "exclude" ? !row.meeting_call_ext_id : true
2247
- ).filter((row) => !q || `${row.title ?? ""} ${labelText?.(row) ?? ""}`.toLowerCase().includes(q));
2248
- }
2249
- function ConversationListView(p) {
2250
- const liveProps = useRef(p);
2251
- liveProps.current = p;
2252
- const [query, setQuery] = useState("");
2253
- const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
2254
- const q = `${query} ${p.filterText ?? ""}`.trim().toLowerCase();
2255
- const isPhone = useIsPhone();
2256
- const rows = useMemo(
2257
- () => filterConversationRows(
2258
- p.conversations,
2259
- q,
2260
- p.labelText,
2261
- p.doneFilter,
2262
- p.visibilityFilters,
2263
- p.meetingFilter
2264
- ),
2265
- [p.conversations, q, p.labelText, p.doneFilter, p.visibilityFilters, p.meetingFilter]
2266
- );
2267
- const selectedRows = p.conversations.filter((c) => selected.has(c.external_id));
2268
- const hasLabels = p.conversations.some((c) => !!p.labelText?.(c));
2269
- const columns = useMemo(
2270
- () => [
2271
- {
2272
- colId: "name",
2273
- headerName: "Thread",
2274
- flex: 2,
2275
- minWidth: 200,
2276
- valueGetter: ({ data }) => data?.title?.trim() || "Untitled thread",
2277
- cellRenderer: ({ data }) => data && /* @__PURE__ */ jsxs("div", { className: "group flex h-full min-w-0 items-center gap-2", children: [
2278
- /* @__PURE__ */ jsx("div", { className: "min-w-0", children: liveProps.current.renderThread ? liveProps.current.renderThread(data) : /* @__PURE__ */ jsx(
2279
- EntityChip,
2280
- {
2281
- icon: /* @__PURE__ */ jsx(ThreadVisibilityIcon, { shared: data.is_shared ?? false, className: "size-4" }),
2282
- label: data.title?.trim() || "Untitled thread",
2283
- pillTooltip: "Open conversation",
2284
- labelMaxWidth: "max-w-full",
2285
- onClick: () => liveProps.current.onOpen(data),
2286
- testId: `thread-open-${data.external_id}`
2287
- }
2288
- ) }),
2289
- (data.message_count ?? 0) > 0 && /* @__PURE__ */ jsxs(
2290
- "span",
2291
- {
2292
- className: "inline-flex h-5 shrink-0 items-center gap-0.5 self-center rounded-full bg-muted px-1.5 text-[10px] font-medium leading-none tabular-nums text-muted-foreground",
2293
- title: `${data.message_count} ${data.message_count === 1 ? "message" : "messages"}`,
2294
- "data-testid": `thread-messages-${data.external_id}`,
2295
- children: [
2296
- /* @__PURE__ */ jsx(MessageSquare, { className: "size-3", "aria-hidden": true }),
2297
- data.message_count
2298
- ]
2299
- }
2300
- ),
2301
- (data.is_owner !== false || liveProps.current.canEditShared) && /* @__PURE__ */ jsx(
2302
- "button",
2303
- {
2304
- type: "button",
2305
- "aria-label": `Rename ${data.title?.trim() || "Untitled thread"}`,
2306
- onClick: () => liveProps.current.onRename(data),
2307
- "data-testid": `thread-rename-${data.external_id}`,
2308
- className: "shrink-0 pointer-coarse:flex pointer-coarse:size-11 pointer-coarse:items-center pointer-coarse:justify-center",
2309
- children: /* @__PURE__ */ jsx(CellHoverIcon, {})
2310
- }
2311
- )
2312
- ] })
2313
- },
2314
- // Labels only when a thread has some (an empty column only takes the names' room), and never
2315
- // on a phone, where the status is what a row must say.
2316
- ...liveProps.current.renderLabels && hasLabels && !isPhone ? [
2317
- {
2318
- colId: "labels",
2319
- headerName: "Labels",
2320
- flex: 0.5,
2321
- minWidth: 100,
2322
- valueGetter: ({ data }) => data ? liveProps.current.labelText?.(data) ?? "" : "",
2323
- cellRenderer: ({ data }) => data && liveProps.current.renderLabels?.(data)
2324
- }
2325
- ] : [],
2326
- {
2327
- colId: "status",
2328
- headerName: "Status",
2329
- width: isPhone ? 170 : 240,
2330
- valueGetter: ({ data }) => data?.status,
2331
- cellRenderer: ({ data }) => data && (liveProps.current.renderStatus ? liveProps.current.renderStatus(data) : /* @__PURE__ */ jsx(
2332
- ThreadStatusLine,
2333
- {
2334
- state: data.status === "done" ? "done" : data.status === "todo" ? "todo" : "ready"
2335
- }
2336
- ))
2337
- },
2338
- // Who spoke last (their face) and when: the two read together, at the row's end.
2339
- ...liveProps.current.renderBy || liveProps.current.renderOwner ? [
2340
- {
2341
- colId: liveProps.current.renderBy ? "by" : "owner",
2342
- headerName: liveProps.current.renderBy ? "By" : "Owner",
2343
- width: 64,
2344
- hide: isPhone,
2345
- sortable: false,
2346
- resizable: false,
2347
- cellRenderer: ({ data }) => data && (liveProps.current.renderBy || liveProps.current.renderOwner)?.(data)
2348
- }
2349
- ] : [],
2350
- {
2351
- colId: "activity",
2352
- initialSort: "desc",
2353
- headerName: "Updated",
2354
- width: 128,
2355
- hide: isPhone,
2356
- valueGetter: ({ data }) => data?.last_message_updated_at || data?.updated_at || data?.created_at || "",
2357
- valueFormatter: ({ value }) => value ? liveProps.current.formatDate(value) : "\u2014",
2358
- cellClass: "flex items-center text-xs text-muted-foreground/80"
2359
- }
2360
- ],
2361
- [
2362
- Boolean(p.renderThread),
2363
- Boolean(p.renderLabels),
2364
- Boolean(p.renderOwner),
2365
- Boolean(p.renderBy),
2366
- Boolean(p.renderStatus),
2367
- isPhone,
2368
- hasLabels
2369
- ]
2370
- );
2371
- const centred = useMemo(
2372
- () => columns.map((c) => ({ cellClass: "flex items-center", ...c })),
2373
- [columns]
2374
- );
2375
- return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col", "data-testid": "conversation-list-view", children: [
2376
- p.showHeader !== false && /* @__PURE__ */ jsx(
2377
- TabHeader,
2378
- {
2379
- testId: "threads-tab-header",
2380
- tabs: [
2381
- {
2382
- id: "threads",
2383
- label: "",
2384
- count: rows.length,
2385
- countIcon: /* @__PURE__ */ jsx(MessageSquare, { className: "size-3.5" }),
2386
- countPlacement: "leading",
2387
- presentation: "count",
2388
- hasActiveFilters: !!q
2389
- }
2390
- ],
2391
- activeTabId: "threads",
2392
- onTabChange: () => {
2393
- },
2394
- firstTabContent: /* @__PURE__ */ jsx(
2395
- FilterInput,
2396
- {
2397
- value: query,
2398
- hideFieldPicker: true,
2399
- ariaLabel: "Search threads",
2400
- onChange: setQuery,
2401
- fields: ["text"],
2402
- onFieldsChange: () => {
2403
- },
2404
- labels: {
2405
- placeholder: "Find a thread\u2026",
2406
- fieldNames: { text: "Thread" }
2407
- },
2408
- className: "w-44 max-w-[40vw]"
2409
- }
2410
- ),
2411
- rightContent: p.header
2412
- }
2413
- ),
2414
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(
2415
- GridView,
2416
- {
2417
- testId: "threads-grid",
2418
- items: rows,
2419
- columnDefs: centred,
2420
- getItemId: (row) => row.external_id,
2421
- getRowTestId: (row) => `thread-row-${row.external_id}`,
2422
- onRowClick: p.onOpen,
2423
- height: "100%",
2424
- isLoading: p.isLoading,
2425
- loadingMessage: "Loading threads\u2026",
2426
- emptyState: {
2427
- title: q ? "No threads match" : "No conversations yet",
2428
- message: p.footer ? "Type below to ask ARBI, bring in people, or both." : "Use New Task to ask ARBI, bring in people, or both."
2429
- },
2430
- selection: p.selectionActions ? {
2431
- mode: "multiple",
2432
- controlled: true,
2433
- selected: selectedRows,
2434
- onSelectionChange: (items) => setSelected(new Set(items.map((item) => item.external_id)))
2435
- } : void 0,
2436
- gridOptions: {
2437
- suppressClickEdit: true,
2438
- onCellKeyDown: (event) => {
2439
- if (event.event?.key === "Enter" && "column" in event && event.column.getColId() === "name" && event.data)
2440
- p.onOpen(event.data);
2441
- }
2442
- }
2443
- }
2444
- ) }),
2445
- p.footer,
2446
- p.selectionActions && /* @__PURE__ */ jsx(SelectionBar, { count: selectedRows.length, onClear: () => setSelected(/* @__PURE__ */ new Set()), children: (compact) => p.selectionActions?.(selectedRows, compact, () => setSelected(/* @__PURE__ */ new Set())) })
2447
- ] });
2448
- }
2449
- var DEFAULT_TASK_THREAD_CHIP_LABELS = {
2450
- open: "Open thread",
2451
- watch: "Watch live",
2452
- none: "No thread yet"
2453
- };
2454
- function TaskThreadChip({
2455
- id,
2456
- conversationId,
2457
- generating = false,
2458
- onOpen,
2459
- testId,
2460
- labels
2461
- }) {
2462
- const l = { ...DEFAULT_TASK_THREAD_CHIP_LABELS, ...labels };
2463
- if (!conversationId) {
2464
- return /* @__PURE__ */ jsxs(
2465
- "span",
2466
- {
2467
- className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
2468
- "data-testid": testIdOr(testId, { testId: `task-thread-${id}` }),
2469
- "data-state": "none",
2470
- children: [
2471
- /* @__PURE__ */ jsx(MessageSquare, { className: "size-3.5 opacity-50", "aria-hidden": true }),
2472
- l.none
2473
- ]
2474
- }
2475
- );
2476
- }
2477
- return /* @__PURE__ */ jsxs(
2478
- Button,
2479
- {
2480
- variant: "ghost",
2481
- size: "sm",
2482
- className: cn(
2483
- "h-7 gap-1.5 px-2 text-xs pointer-coarse:h-11",
2484
- generating && "text-ai-ink hover:text-ai-ink"
2485
- ),
2486
- onClick: () => onOpen(conversationId),
2487
- "data-testid": testIdOr(testId, { testId: `task-thread-${id}` }),
2488
- "data-state": generating ? "live" : "idle",
2489
- children: [
2490
- /* @__PURE__ */ jsxs("span", { className: "relative flex size-3.5 items-center justify-center", children: [
2491
- generating && /* @__PURE__ */ jsx("span", { className: "absolute inline-flex size-full animate-ping rounded-full bg-ai-soft" }),
2492
- /* @__PURE__ */ jsx(MessageSquare, { className: "relative size-3.5", "aria-hidden": true })
2493
- ] }),
2494
- generating ? l.watch : l.open
2495
- ]
2496
- }
2497
- );
2498
- }
2499
- var DEFAULT_TASKS_CHROME_LABELS = {
2500
- tasksTab: "Tasks",
2501
- newTask: "New task"
2502
- };
2503
- function TasksToolbar({ count, onNewTask, testIds, labels }) {
2504
- const l = { ...DEFAULT_TASKS_CHROME_LABELS, ...labels };
2505
- const tabs = [
2506
- {
2507
- id: "tasks",
2508
- label: l.tasksTab,
2509
- icon: /* @__PURE__ */ jsx(ListTodo, { className: "size-4" }),
2510
- count,
2511
- testId: testIdOr(testIds?.tab, { testId: "tasks-tab" })
2512
- }
2513
- ];
2514
- return /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 bg-background", "data-slot": "tasks-toolbar", children: /* @__PURE__ */ jsx(
2515
- TabHeader,
2516
- {
2517
- tabs,
2518
- activeTabId: "tasks",
2519
- onTabChange: () => {
2520
- },
2521
- rightContent: /* @__PURE__ */ jsxs(
2522
- Button,
2523
- {
2524
- onClick: onNewTask,
2525
- "data-testid": testIdOr(testIds?.newTask, { testId: "task-new" }),
2526
- children: [
2527
- /* @__PURE__ */ jsx(Plus, { size: 16, className: "mr-1.5" }),
2528
- l.newTask
2529
- ]
2530
- }
2531
- )
2532
- }
2533
- ) });
2534
- }
2535
- var DEFAULT_WORKFLOWS_CHROME_LABELS = {
2536
- workflowsTab: "Workflows",
2537
- newWorkflow: "New workflow"
2538
- };
2539
- function WorkflowsToolbar({ count, onNewWorkflow, testIds, labels }) {
2540
- const l = { ...DEFAULT_WORKFLOWS_CHROME_LABELS, ...labels };
2541
- const tabs = [
2542
- {
2543
- id: "workflows",
2544
- label: l.workflowsTab,
2545
- icon: /* @__PURE__ */ jsx(Workflow, { className: "size-4" }),
2546
- count,
2547
- testId: testIdOr(testIds?.tab, { testId: "workflows-tab" })
2548
- }
2549
- ];
2550
- return /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 bg-background", "data-slot": "workflows-toolbar", children: /* @__PURE__ */ jsx(
2551
- TabHeader,
2552
- {
2553
- tabs,
2554
- activeTabId: "workflows",
2555
- onTabChange: () => {
2556
- },
2557
- rightContent: /* @__PURE__ */ jsxs(
2558
- Button,
2559
- {
2560
- onClick: onNewWorkflow,
2561
- "data-testid": testIdOr(testIds?.newWorkflow, { testId: "workflow-new" }),
2562
- children: [
2563
- /* @__PURE__ */ jsx(Plus, { size: 16, className: "mr-1.5" }),
2564
- l.newWorkflow
2565
- ]
2566
- }
2567
- )
2568
- }
2569
- ) });
2570
- }
2571
- function ThreadNotifyDialog({
2572
- open,
2573
- onOpenChange,
2574
- people,
2575
- isShared,
2576
- onNotify
2577
- }) {
2578
- const [selected, setSelected] = useState([]);
2579
- const [query, setQuery] = useState("");
2580
- const [busy, setBusy] = useState(false);
2581
- const [error, setError] = useState(null);
2582
- useEffect(() => {
2583
- if (open) {
2584
- setSelected([]);
2585
- setQuery("");
2586
- setError(null);
2587
- }
2588
- }, [open]);
2589
- return /* @__PURE__ */ jsx(
2590
- Dialog,
2591
- {
2592
- open,
2593
- onOpenChange: (value) => {
2594
- if (!busy) onOpenChange(value);
2595
- },
2596
- children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-sm", "data-testid": "thread-notify-dialog", children: [
2597
- /* @__PURE__ */ jsx(DialogTitle, { children: "Notify" }),
2598
- /* @__PURE__ */ jsx(DialogDescription, { children: isShared ? "Ask someone to follow up in this thread." : "Notifying someone shares this thread with your workspace." }),
2599
- /* @__PURE__ */ jsx(
2600
- Input,
2601
- {
2602
- "aria-label": "Find people",
2603
- placeholder: "Find someone\u2026",
2604
- value: query,
2605
- onChange: (event) => setQuery(event.target.value)
2606
- }
2607
- ),
2608
- /* @__PURE__ */ jsxs("div", { className: "max-h-64 overflow-y-auto space-y-1", children: [
2609
- people.filter((person) => person.label.toLowerCase().includes(query.toLowerCase())).map((person) => /* @__PURE__ */ jsxs(
2610
- "label",
2611
- {
2612
- className: "flex cursor-pointer items-center gap-2 rounded px-2 py-2 hover:bg-muted",
2613
- children: [
2614
- /* @__PURE__ */ jsx(
2615
- Checkbox,
2616
- {
2617
- disabled: busy,
2618
- checked: selected.includes(person.id),
2619
- onCheckedChange: (checked) => setSelected(
2620
- (old) => checked ? [...old, person.id] : old.filter((id) => id !== person.id)
2621
- )
2622
- }
2623
- ),
2624
- person.label
2625
- ]
2626
- },
2627
- person.id
2628
- )),
2629
- !people.length && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No other workspace members yet." })
2630
- ] }),
2631
- error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }),
2632
- /* @__PURE__ */ jsx(
2633
- Button,
2634
- {
2635
- disabled: busy || !selected.length,
2636
- "data-testid": "thread-notify-submit",
2637
- onClick: async () => {
2638
- setBusy(true);
2639
- setError(null);
2640
- try {
2641
- await onNotify(selected);
2642
- onOpenChange(false);
2643
- } catch (failure) {
2644
- setError(failure instanceof Error ? failure.message : "Could not notify people");
2645
- } finally {
2646
- setBusy(false);
2647
- }
2648
- },
2649
- children: busy ? "Notifying\u2026" : "Notify"
2650
- }
2651
- )
2652
- ] })
2653
- }
2654
- );
2655
- }
2656
- function ConversationTurnSpeaker({
2657
- role,
2658
- actor,
2659
- currentUserId,
2660
- colorClass,
2661
- platformName = "ARBI",
2662
- platformLogoSrc = ARBI_MARK_SRC,
2663
- variant = "pill"
2664
- }) {
2665
- if (!role) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
2666
- if (role === "assistant" && !actor)
2667
- return /* @__PURE__ */ jsx(
2668
- UserEntity,
2669
- {
2670
- size: "sm",
2671
- variant,
2672
- interactive: false,
2673
- picture: platformLogoSrc,
2674
- avatarImageClassName: "object-contain p-0.5",
2675
- initials: platformName.charAt(0),
2676
- displayName: platformName
2677
- }
2678
- );
2679
- if (!actor) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Unknown participant" });
2680
- const isMe = actor.external_id === currentUserId;
2681
- const displayName = isMe ? "You" : [actor.given_name, actor.family_name].filter(Boolean).join(" ");
2682
- return /* @__PURE__ */ jsx(
2683
- UserEntity,
2684
- {
2685
- size: "sm",
2686
- variant,
2687
- interactive: false,
2688
- isMe,
2689
- isAgent: actor.external_id.startsWith("agt-"),
2690
- displayName,
2691
- initials: `${actor.given_name.charAt(0)}${actor.family_name?.charAt(0) ?? ""}`.toUpperCase(),
2692
- picture: actor.picture,
2693
- colorClass
2694
- }
2695
- );
2696
- }
2697
-
2698
- export { AgentCard, AgentGallery, ConversationListView, ConversationTurnSpeaker, DEFAULT_AGENT_CARD_LABELS, DEFAULT_AGENT_GALLERY_LABELS, DEFAULT_SKILL_CARD_LABELS, DEFAULT_SKILL_ROW_LABELS, DEFAULT_TASKS_CHROME_LABELS, DEFAULT_TASK_LIST_LABELS, DEFAULT_TASK_OUTPUTS_CELL_LABELS, DEFAULT_TASK_STATUS_PILL_LABELS, DEFAULT_TASK_THREAD_CHIP_LABELS, DEFAULT_UPLOAD_TASK_ITEM_LABELS, DEFAULT_UPLOAD_TASK_LIST_LABELS, DEFAULT_WORKFLOWS_CHROME_LABELS, SkillCard, SkillRow, TASK_COLUMN_ORDER, TaskInbox, TaskMoveDialog, TaskOutputsCell, TaskStatusPill, TaskTable, TaskThreadChip, TasksToolbar, ThreadNotifyDialog, UploadTaskItem, UploadTaskList, WorkflowsToolbar, createTaskColumns, filterConversationRows, formatElapsed, useWorkflowPreview };
2699
- //# sourceMappingURL=chunk-EZSYYENN.js.map
2700
- //# sourceMappingURL=chunk-EZSYYENN.js.map