@springbrand/message-panel 0.1.3-alpha.33 → 0.1.3-alpha.36
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.
- package/cloud-os/README.md +2 -1
- package/cloud-os/assets/thinking-star.svg +9 -0
- package/cloud-os/chat/cloud-os-chat-messages.tsx +290 -239
- package/cloud-os/chat/code-runner.tsx +106 -0
- package/cloud-os/chat/range.ts +8 -0
- package/cloud-os/chat/rich-blocks.tsx +186 -256
- package/cloud-os/chat/surfaces.tsx +90 -0
- package/cloud-os/chat/tool-call.tsx +94 -0
- package/cloud-os/chat/tool-presentation.ts +3 -11
- package/cloud-os/chat/tool-rows.tsx +145 -378
- package/cloud-os/chat/tool-timeline.tsx +123 -0
- package/cloud-os/chat/transcript-model.ts +86 -116
- package/cloud-os/chat/web-search.tsx +135 -0
- package/cloud-os/composer/cloud-os-chat-input.tsx +1 -1
- package/cloud-os/index.ts +10 -5
- package/cloud-os/primitives/collapsible.tsx +21 -0
- package/cloud-os/styles/cloud-os.css +18 -12
- package/demo/chat-scenarios.ts +3 -9
- package/package.json +3 -1
- package/cloud-os/assets/thinking-spark.svg +0 -9
- package/cloud-os/chat/use-lifecycle-disclosure.ts +0 -65
|
@@ -10,14 +10,19 @@ import {
|
|
|
10
10
|
UsersThree,
|
|
11
11
|
WarningCircle,
|
|
12
12
|
} from "@phosphor-icons/react";
|
|
13
|
-
import {
|
|
13
|
+
import { useState } from "react";
|
|
14
|
+
import {
|
|
15
|
+
Collapsible,
|
|
16
|
+
CollapsibleContent,
|
|
17
|
+
CollapsibleTrigger,
|
|
18
|
+
} from "../primitives/collapsible";
|
|
14
19
|
import { Tooltip } from "../primitives/tooltip";
|
|
15
20
|
import { WorkshopButton } from "../primitives/workshop-controls";
|
|
16
21
|
import { MarkdownMessage } from "./markdown-message";
|
|
22
|
+
import { collapsePanel } from "./surfaces";
|
|
17
23
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
SECONDARY_DISCLOSURE_TITLE_WITHOUT_ICON,
|
|
24
|
+
DISCLOSURE_ICON,
|
|
25
|
+
DISCLOSURE_TITLE_WITH_ICON,
|
|
21
26
|
WorkIcon,
|
|
22
27
|
} from "./tool-rows";
|
|
23
28
|
import type {
|
|
@@ -31,13 +36,12 @@ import {
|
|
|
31
36
|
safeStringify,
|
|
32
37
|
type CloudOsToolCall,
|
|
33
38
|
} from "./tool-presentation";
|
|
34
|
-
import { useLifecycleDisclosure } from "./use-lifecycle-disclosure";
|
|
35
39
|
|
|
36
40
|
/**
|
|
37
41
|
* UIMessage 协议里存在、gadgets 那套没有的几类 part —— 计划快照、原位提问、
|
|
38
42
|
* 后续建议、定时任务、并行/子 Agent、原位授权。视觉语言全部沿用 cloud-os:
|
|
39
|
-
* 折叠工作行用 `
|
|
40
|
-
* `rounded-2xl border-kumo-line/70 bg-kumo-elevated/45 p-3`,
|
|
43
|
+
* 折叠工作行用 `py-1` 的行式布局,卡片用
|
|
44
|
+
* `themed-surface-inset rounded-2xl border-kumo-line/70 bg-kumo-elevated/45 p-3`,
|
|
41
45
|
* 授权卡沿用 renderActionCard 的 blocking callout(brand/40 边 + brand/10 底)。
|
|
42
46
|
*/
|
|
43
47
|
|
|
@@ -46,53 +50,58 @@ import { useLifecycleDisclosure } from "./use-lifecycle-disclosure";
|
|
|
46
50
|
export function PlanBlock({
|
|
47
51
|
steps,
|
|
48
52
|
running,
|
|
49
|
-
|
|
53
|
+
defaultOpen = true,
|
|
50
54
|
}: {
|
|
51
55
|
steps: PlanStep[];
|
|
52
56
|
running: boolean;
|
|
53
|
-
|
|
57
|
+
defaultOpen?: boolean;
|
|
54
58
|
}) {
|
|
55
59
|
const done = steps.filter((step) => step.status === "done").length;
|
|
56
60
|
const current = steps.find((step) => step.status === "in_progress");
|
|
57
|
-
const [open,
|
|
61
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
58
62
|
|
|
59
63
|
return (
|
|
60
|
-
<
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
onClick={toggle}
|
|
64
|
-
className={`${SECONDARY_DISCLOSURE_TITLE_WITH_ICON} w-full`}
|
|
65
|
-
aria-expanded={open}
|
|
64
|
+
<Collapsible open={open} onOpenChange={setOpen} className="group">
|
|
65
|
+
<CollapsibleTrigger
|
|
66
|
+
className={`${DISCLOSURE_TITLE_WITH_ICON} w-full`}
|
|
66
67
|
>
|
|
67
|
-
<span
|
|
68
|
-
|
|
68
|
+
<span
|
|
69
|
+
className={`${DISCLOSURE_ICON} rounded-full`}
|
|
70
|
+
role="progressbar"
|
|
71
|
+
aria-label="Plan progress"
|
|
72
|
+
aria-valuemin={0}
|
|
73
|
+
aria-valuemax={steps.length}
|
|
74
|
+
aria-valuenow={done}
|
|
75
|
+
style={{
|
|
76
|
+
background: `conic-gradient(var(--color-kumo-brand) ${(done / steps.length) * 100}%, var(--color-kumo-fill) 0)`,
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
<span className="size-2.5 rounded-full bg-kumo-base" />
|
|
69
80
|
</span>
|
|
70
|
-
<span className="min-w-0 flex-1">
|
|
71
|
-
<span className=
|
|
72
|
-
|
|
73
|
-
Plan · {done}/{steps.length}
|
|
74
|
-
</span>
|
|
75
|
-
<CaretRight
|
|
76
|
-
size={16}
|
|
77
|
-
className={`flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${open ? "rotate-90" : ""}`}
|
|
78
|
-
/>
|
|
81
|
+
<span className="flex min-w-0 flex-1 items-center gap-2">
|
|
82
|
+
<span className={`shrink-0 ${running ? "cos-thinking-shimmer" : ""}`}>
|
|
83
|
+
Plan · {done}/{steps.length}
|
|
79
84
|
</span>
|
|
80
|
-
{!open &&
|
|
81
|
-
<span className="
|
|
85
|
+
{!open && current && (
|
|
86
|
+
<span className="min-w-0 flex-1 truncate text-[12px] leading-4 text-[rgba(0,0,0,0.3)]">
|
|
82
87
|
{current.text}
|
|
83
88
|
</span>
|
|
84
89
|
)}
|
|
90
|
+
<CaretRight
|
|
91
|
+
size={16}
|
|
92
|
+
className={`ml-auto flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${open ? "rotate-90" : ""}`}
|
|
93
|
+
/>
|
|
85
94
|
</span>
|
|
86
|
-
</
|
|
87
|
-
{
|
|
88
|
-
<div className="mt-1 flex flex-col gap-2 rounded-xl border border-
|
|
95
|
+
</CollapsibleTrigger>
|
|
96
|
+
<CollapsibleContent className={collapsePanel}>
|
|
97
|
+
<div className="-mx-2.5 mt-1 flex flex-col gap-2 rounded-xl border border-[rgba(0,0,0,0.07)] px-4 py-3">
|
|
89
98
|
{steps.map((step, index) => (
|
|
90
99
|
<div
|
|
91
100
|
key={`${step.text}-${index}`}
|
|
92
101
|
className="flex items-center gap-2 text-[14px] leading-[1.4] font-normal tracking-normal"
|
|
93
102
|
>
|
|
94
103
|
<span
|
|
95
|
-
className="flex size-4
|
|
104
|
+
className="flex size-4 shrink-0 items-center justify-center"
|
|
96
105
|
aria-hidden="true"
|
|
97
106
|
>
|
|
98
107
|
{step.status === "done" ? (
|
|
@@ -120,8 +129,8 @@ export function PlanBlock({
|
|
|
120
129
|
</div>
|
|
121
130
|
))}
|
|
122
131
|
</div>
|
|
123
|
-
|
|
124
|
-
</
|
|
132
|
+
</CollapsibleContent>
|
|
133
|
+
</Collapsible>
|
|
125
134
|
);
|
|
126
135
|
}
|
|
127
136
|
|
|
@@ -172,7 +181,6 @@ export function AskUserBlock({
|
|
|
172
181
|
// 首帧 questions 往往还是空数组,那之后每次 setAnswers 都在空数组上 map,
|
|
173
182
|
// 选择永远存不进去 → Submit 永久禁用。改成稀疏存 + 按当前 questions 补齐。
|
|
174
183
|
const [answers, setAnswers] = useState<DraftAnswer[]>([]);
|
|
175
|
-
const contentId = useId();
|
|
176
184
|
const answerAt = (index: number): DraftAnswer =>
|
|
177
185
|
answers[index] ?? { selections: [], text: "" };
|
|
178
186
|
const toggle = (questionIndex: number, option: string, multi: boolean) =>
|
|
@@ -202,9 +210,6 @@ export function AskUserBlock({
|
|
|
202
210
|
// 在途只用于按钮转圈;权威状态永远是 part 的 state。
|
|
203
211
|
const [inFlight, setInFlight] = useState(false);
|
|
204
212
|
const outcome = askUserOutcome(call);
|
|
205
|
-
const [open, toggleDisclosure] = useLifecycleDisclosure(
|
|
206
|
-
outcome.kind === "pending",
|
|
207
|
-
);
|
|
208
213
|
const displayAnswerAt = (index: number): DraftAnswer => {
|
|
209
214
|
if (outcome.kind !== "answered") return answerAt(index);
|
|
210
215
|
const answer = outcome.answers[index];
|
|
@@ -219,26 +224,12 @@ export function AskUserBlock({
|
|
|
219
224
|
(_question, index) =>
|
|
220
225
|
answerAt(index).selections.length > 0 || answerAt(index).text.trim(),
|
|
221
226
|
);
|
|
222
|
-
const titleBase = questions.length === 1
|
|
223
|
-
? "Clarifying question"
|
|
224
|
-
: "Clarifying questions";
|
|
225
|
-
const disclosureTitle = outcome.kind === "pending"
|
|
226
|
-
? questions.length === 1 && questions[0]
|
|
227
|
-
? `${titleBase} — ${questions[0].prompt}`
|
|
228
|
-
: questions.length > 1
|
|
229
|
-
? `${titleBase} · ${questions.length} questions`
|
|
230
|
-
: titleBase
|
|
231
|
-
: outcome.kind === "answered"
|
|
232
|
-
? questions.length > 1
|
|
233
|
-
? `${titleBase} · ${questions.length} answered`
|
|
234
|
-
: `${titleBase} · Answered`
|
|
235
|
-
: `${titleBase} · Closed`;
|
|
236
227
|
const buttonLabel = inFlight ? "Submitting…" : "Submit";
|
|
237
228
|
const footnote = outcome.kind === "closed"
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
229
|
+
? outcome.reason === "user_replied_freeform"
|
|
230
|
+
? "Answered in the chat instead."
|
|
231
|
+
: "Closed without an answer."
|
|
232
|
+
: "";
|
|
242
233
|
|
|
243
234
|
const submit = () => {
|
|
244
235
|
if (!onRespond) return;
|
|
@@ -254,129 +245,103 @@ export function AskUserBlock({
|
|
|
254
245
|
}, () => setInFlight(false));
|
|
255
246
|
};
|
|
256
247
|
|
|
257
|
-
const closedButton = (
|
|
258
|
-
<WorkshopButton
|
|
259
|
-
tone="primary"
|
|
260
|
-
disabled
|
|
261
|
-
className="ml-auto !h-7 flex-shrink-0 gap-1 !rounded-lg px-2.5 text-[12px]"
|
|
262
|
-
>
|
|
263
|
-
Closed
|
|
264
|
-
</WorkshopButton>
|
|
265
|
-
);
|
|
266
|
-
|
|
267
248
|
return (
|
|
268
249
|
<div
|
|
269
250
|
className="w-full"
|
|
270
251
|
data-part-type="ask_user"
|
|
271
252
|
data-state={outcome.kind === "answered" ? "submitted" : outcome.kind}
|
|
272
253
|
>
|
|
273
|
-
<div className="flex flex-col gap-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
<span className="m-0 text-[14px] leading-[1.4] font-normal text-black">
|
|
337
|
-
{question.options.length > 0 ? "Other" : "Answer"}
|
|
338
|
-
</span>
|
|
339
|
-
<input
|
|
340
|
-
type="text"
|
|
341
|
-
value={answer.text}
|
|
342
|
-
maxLength={2_000}
|
|
343
|
-
disabled={resolved || !onRespond}
|
|
344
|
-
aria-label={`自定义回答:${question.prompt}`}
|
|
345
|
-
placeholder={question.options.length > 0
|
|
346
|
-
? "Add a custom answer"
|
|
347
|
-
: "Type your answer"}
|
|
348
|
-
onChange={(event) =>
|
|
349
|
-
setText(questionIndex, event.currentTarget.value)}
|
|
350
|
-
className="rounded-lg border border-[rgba(0,0,0,0.07)] bg-[#fff] px-4 py-2 text-[14px] leading-[1.4] font-normal text-[rgba(0,0,0,0.7)] outline-none placeholder:text-kumo-inactive focus:border-[#ff9938] focus:ring-2 focus:ring-[#ff9938]/30 disabled:cursor-default"
|
|
351
|
-
/>
|
|
352
|
-
</label>
|
|
353
|
-
)}
|
|
354
|
-
</div>
|
|
355
|
-
);
|
|
356
|
-
})}
|
|
357
|
-
</div>
|
|
358
|
-
{outcome.kind === "pending" && (
|
|
359
|
-
<button
|
|
360
|
-
type="button"
|
|
361
|
-
disabled={resolved || !onRespond || !valid}
|
|
362
|
-
onClick={submit}
|
|
363
|
-
className="inline-flex h-10 min-w-[120px] self-start items-center justify-center rounded-lg bg-gradient-to-r from-[#ff9938] to-[#ffe46c] px-5 py-2.5 text-[14px] leading-5 font-medium text-black transition-opacity duration-150 ease-out hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#ff9938]/30 disabled:cursor-default disabled:opacity-50"
|
|
364
|
-
>
|
|
365
|
-
{buttonLabel}
|
|
366
|
-
</button>
|
|
254
|
+
<div className="flex flex-col gap-4 py-2.5">
|
|
255
|
+
{outcome.kind !== "closed" && questions.map((question, questionIndex) => {
|
|
256
|
+
const answer = displayAnswerAt(questionIndex);
|
|
257
|
+
const options = outcome.kind === "answered"
|
|
258
|
+
? [...new Set([...question.options, ...answer.selections])]
|
|
259
|
+
: question.options;
|
|
260
|
+
return (
|
|
261
|
+
<div
|
|
262
|
+
key={`${question.prompt}:${questionIndex}`}
|
|
263
|
+
className="flex flex-col items-start gap-2"
|
|
264
|
+
>
|
|
265
|
+
<p className="m-0 text-[14px] leading-[1.4] font-normal text-kumo-default">
|
|
266
|
+
{question.prompt}
|
|
267
|
+
</p>
|
|
268
|
+
{options.length > 0 && (
|
|
269
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
270
|
+
{options.map((option) => {
|
|
271
|
+
const selected = answer.selections.includes(option);
|
|
272
|
+
return (
|
|
273
|
+
<button
|
|
274
|
+
key={option}
|
|
275
|
+
type="button"
|
|
276
|
+
aria-pressed={selected}
|
|
277
|
+
disabled={resolved || !onRespond}
|
|
278
|
+
onClick={() =>
|
|
279
|
+
toggle(questionIndex, option, question.multi)}
|
|
280
|
+
style={selected
|
|
281
|
+
? {
|
|
282
|
+
background:
|
|
283
|
+
"linear-gradient(#fff,#fff) padding-box, linear-gradient(90deg,#ff9938,#ffe46c) border-box",
|
|
284
|
+
}
|
|
285
|
+
: undefined}
|
|
286
|
+
className={`inline-flex w-fit items-center justify-center rounded-lg border px-4 py-2 text-left text-[14px] leading-[1.4] font-normal text-[rgba(0,0,0,0.7)] transition-[background,border-color] duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#ff9938]/30 ${
|
|
287
|
+
selected
|
|
288
|
+
? "border-solid border-transparent"
|
|
289
|
+
: resolved || !onRespond
|
|
290
|
+
? "border-[rgba(0,0,0,0.07)] bg-kumo-base opacity-60"
|
|
291
|
+
: "cursor-pointer border-[rgba(0,0,0,0.07)] bg-kumo-base hover:border-[#ff9938]"
|
|
292
|
+
}`}
|
|
293
|
+
>
|
|
294
|
+
{option}
|
|
295
|
+
</button>
|
|
296
|
+
);
|
|
297
|
+
})}
|
|
298
|
+
</div>
|
|
299
|
+
)}
|
|
300
|
+
{question.allowCustom && (
|
|
301
|
+
<label className="flex w-full flex-col gap-2 text-[14px] leading-[1.4] text-kumo-default">
|
|
302
|
+
<span>{question.options.length > 0 ? "Other" : "Answer"}</span>
|
|
303
|
+
<input
|
|
304
|
+
type="text"
|
|
305
|
+
value={answer.text}
|
|
306
|
+
maxLength={2_000}
|
|
307
|
+
disabled={resolved || !onRespond}
|
|
308
|
+
aria-label={`自定义回答:${question.prompt}`}
|
|
309
|
+
placeholder={question.options.length > 0
|
|
310
|
+
? "Add a custom answer"
|
|
311
|
+
: "Type your answer"}
|
|
312
|
+
onChange={(event) =>
|
|
313
|
+
setText(questionIndex, event.currentTarget.value)}
|
|
314
|
+
className="rounded-lg border border-[rgba(0,0,0,0.07)] bg-kumo-base px-4 py-2 text-[14px] leading-[1.4] text-kumo-subtle outline-none placeholder:text-kumo-inactive focus:border-[#ff9938] focus:ring-2 focus:ring-[#ff9938]/20 disabled:cursor-default disabled:opacity-60"
|
|
315
|
+
/>
|
|
316
|
+
</label>
|
|
367
317
|
)}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
<p className="m-0 min-w-0 text-[12px] leading-4 text-kumo-inactive">
|
|
373
|
-
{footnote}
|
|
318
|
+
{outcome.kind === "answered" &&
|
|
319
|
+
!question.allowCustom && answer.text.trim() && (
|
|
320
|
+
<p className="m-0 text-[14px] leading-[1.4] text-kumo-subtle">
|
|
321
|
+
{answer.text.trim()}
|
|
374
322
|
</p>
|
|
375
323
|
)}
|
|
376
|
-
{closedButton}
|
|
377
324
|
</div>
|
|
378
|
-
)
|
|
379
|
-
|
|
325
|
+
);
|
|
326
|
+
})}
|
|
327
|
+
{outcome.kind === "pending" && (
|
|
328
|
+
<button
|
|
329
|
+
type="button"
|
|
330
|
+
disabled={resolved || !onRespond || !valid}
|
|
331
|
+
onClick={submit}
|
|
332
|
+
className="inline-flex h-10 min-w-[120px] self-start items-center justify-center rounded-lg bg-gradient-to-r from-[#ff9938] to-[#ffe46c] px-5 py-2.5 text-[14px] leading-5 font-medium text-black transition-opacity duration-150 ease-out hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#ff9938]/30 disabled:cursor-default disabled:opacity-50"
|
|
333
|
+
>
|
|
334
|
+
{buttonLabel}
|
|
335
|
+
</button>
|
|
336
|
+
)}
|
|
337
|
+
{outcome.kind === "answered" && (
|
|
338
|
+
<p className="m-0 text-[12px] leading-4 text-kumo-inactive">Submitted</p>
|
|
339
|
+
)}
|
|
340
|
+
{outcome.kind === "closed" && (
|
|
341
|
+
<p className="m-0 text-[12px] leading-4 text-kumo-inactive">
|
|
342
|
+
Closed{footnote ? ` · ${footnote}` : ""}
|
|
343
|
+
</p>
|
|
344
|
+
)}
|
|
380
345
|
</div>
|
|
381
346
|
</div>
|
|
382
347
|
);
|
|
@@ -483,7 +448,7 @@ export function ScheduleConfirmation({
|
|
|
483
448
|
<section
|
|
484
449
|
data-slot="schedule-card"
|
|
485
450
|
aria-label={`Confirm scheduled task: ${name}`}
|
|
486
|
-
className="flex w-full max-w-sm flex-col gap-3 rounded-2xl border border-kumo-line/70 bg-
|
|
451
|
+
className="themed-surface-inset flex w-full max-w-sm flex-col gap-3 rounded-2xl border border-kumo-line/70 bg-kumo-elevated p-4 text-kumo-default shadow-[0_1px_2px_rgba(0,0,0,0.04),0_12px_32px_-16px_rgba(0,0,0,0.12)]"
|
|
487
452
|
>
|
|
488
453
|
<div className="flex items-center gap-2.5">
|
|
489
454
|
<span
|
|
@@ -547,7 +512,7 @@ export function ScheduleBlock({
|
|
|
547
512
|
"aria-label": `Open scheduled task: ${String(call.input.label ?? call.input.title ?? "Scheduled task")}`,
|
|
548
513
|
}
|
|
549
514
|
: {})}
|
|
550
|
-
className={`w-full rounded-2xl border border-kumo-line bg-
|
|
515
|
+
className={`w-full rounded-2xl border border-kumo-line bg-kumo-base px-4 py-3 text-left${
|
|
551
516
|
clickable
|
|
552
517
|
? " cursor-pointer transition-colors duration-150 ease-out hover:bg-kumo-tint focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kumo-brand/50"
|
|
553
518
|
: ""
|
|
@@ -625,7 +590,7 @@ export function PermissionGrant({
|
|
|
625
590
|
return (
|
|
626
591
|
<section
|
|
627
592
|
aria-label={ariaLabel}
|
|
628
|
-
className="w-full rounded-xl border border-kumo-line bg-
|
|
593
|
+
className="themed-surface-inset w-full rounded-xl border border-kumo-line bg-kumo-elevated p-4 text-kumo-default"
|
|
629
594
|
>
|
|
630
595
|
<div className="flex items-center gap-2">
|
|
631
596
|
<span
|
|
@@ -749,104 +714,72 @@ function StatusDot({ status }: { status: ParallelTool["status"] }) {
|
|
|
749
714
|
export function ParallelBlock({
|
|
750
715
|
label,
|
|
751
716
|
tools,
|
|
752
|
-
running = tools.some((tool) => tool.status === "running"),
|
|
753
717
|
}: {
|
|
754
718
|
label: string;
|
|
755
719
|
tools: ParallelTool[];
|
|
756
|
-
running?: boolean;
|
|
757
720
|
}) {
|
|
758
721
|
const done = tools.filter((tool) => tool.status === "done").length;
|
|
759
|
-
const [open, toggle] = useLifecycleDisclosure(running);
|
|
760
722
|
return (
|
|
761
723
|
<div className="max-w-[860px]">
|
|
762
|
-
<
|
|
763
|
-
|
|
764
|
-
onClick={toggle}
|
|
765
|
-
className={SECONDARY_DISCLOSURE_TITLE_WITH_ICON}
|
|
766
|
-
aria-expanded={open}
|
|
767
|
-
>
|
|
768
|
-
<span className={SECONDARY_DISCLOSURE_ICON}>
|
|
724
|
+
<div className="flex items-start gap-2 px-1.5 py-1 text-[14px] leading-[1.4] text-kumo-inactive">
|
|
725
|
+
<span className={DISCLOSURE_ICON}>
|
|
769
726
|
<WorkIcon Icon={ListChecks} />
|
|
770
727
|
</span>
|
|
771
728
|
<span className="min-w-0 truncate">
|
|
772
729
|
{label} · {done}/{tools.length}
|
|
773
730
|
</span>
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
<
|
|
783
|
-
|
|
784
|
-
className="flex
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
</span>
|
|
792
|
-
)}
|
|
793
|
-
</div>
|
|
794
|
-
))}
|
|
795
|
-
</div>
|
|
796
|
-
)}
|
|
731
|
+
</div>
|
|
732
|
+
<div className="mt-1 space-y-1">
|
|
733
|
+
{tools.map((tool, index) => (
|
|
734
|
+
<div
|
|
735
|
+
key={`${tool.label}-${index}`}
|
|
736
|
+
className="flex items-center gap-2 px-1.5 text-[13px] leading-[19px] tracking-[-0.25px] text-kumo-subtle"
|
|
737
|
+
>
|
|
738
|
+
<StatusDot status={tool.status} />
|
|
739
|
+
<span className="min-w-0 flex-1 truncate">{tool.label}</span>
|
|
740
|
+
{tool.meta && (
|
|
741
|
+
<span className="flex-shrink-0 font-mono text-[11px] leading-4 text-kumo-inactive">
|
|
742
|
+
{tool.meta}
|
|
743
|
+
</span>
|
|
744
|
+
)}
|
|
745
|
+
</div>
|
|
746
|
+
))}
|
|
747
|
+
</div>
|
|
797
748
|
</div>
|
|
798
749
|
);
|
|
799
750
|
}
|
|
800
751
|
|
|
801
|
-
export function SubAgentsBlock({
|
|
802
|
-
agents,
|
|
803
|
-
running = agents.some((agent) => agent.status === "running"),
|
|
804
|
-
}: {
|
|
805
|
-
agents: SubAgentView[];
|
|
806
|
-
running?: boolean;
|
|
807
|
-
}) {
|
|
808
|
-
const [open, toggle] = useLifecycleDisclosure(running);
|
|
752
|
+
export function SubAgentsBlock({ agents }: { agents: SubAgentView[] }) {
|
|
809
753
|
return (
|
|
810
754
|
<div className="max-w-[860px]">
|
|
811
|
-
<
|
|
812
|
-
|
|
813
|
-
onClick={toggle}
|
|
814
|
-
className={SECONDARY_DISCLOSURE_TITLE_WITH_ICON}
|
|
815
|
-
aria-expanded={open}
|
|
816
|
-
>
|
|
817
|
-
<span className={SECONDARY_DISCLOSURE_ICON}>
|
|
755
|
+
<div className="flex items-start gap-2 px-1.5 py-1 text-[14px] leading-[1.4] text-kumo-inactive">
|
|
756
|
+
<span className={DISCLOSURE_ICON}>
|
|
818
757
|
<WorkIcon Icon={UsersThree} />
|
|
819
758
|
</span>
|
|
820
759
|
<span className="min-w-0 truncate">
|
|
821
760
|
{agents.length === 1 ? "Sub-agent" : `${agents.length} sub-agents`}
|
|
822
761
|
</span>
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
<StatusDot status={agent.status} />
|
|
837
|
-
</span>
|
|
838
|
-
<span className="min-w-0 flex-1">
|
|
839
|
-
<span className="block truncate font-medium text-kumo-default">
|
|
840
|
-
{agent.name}
|
|
841
|
-
</span>
|
|
842
|
-
{agent.summary && (
|
|
843
|
-
<span className="block text-kumo-subtle">{agent.summary}</span>
|
|
844
|
-
)}
|
|
762
|
+
</div>
|
|
763
|
+
<div className="mt-1 space-y-1">
|
|
764
|
+
{agents.map((agent, index) => (
|
|
765
|
+
<div
|
|
766
|
+
key={`${agent.name}-${index}`}
|
|
767
|
+
className="flex items-start gap-2 px-1.5 text-[13px] leading-[19px] tracking-[-0.25px]"
|
|
768
|
+
>
|
|
769
|
+
<span className="mt-1.5 flex">
|
|
770
|
+
<StatusDot status={agent.status} />
|
|
771
|
+
</span>
|
|
772
|
+
<span className="min-w-0 flex-1">
|
|
773
|
+
<span className="block truncate font-medium text-kumo-default">
|
|
774
|
+
{agent.name}
|
|
845
775
|
</span>
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
776
|
+
{agent.summary && (
|
|
777
|
+
<span className="block text-kumo-subtle">{agent.summary}</span>
|
|
778
|
+
)}
|
|
779
|
+
</span>
|
|
780
|
+
</div>
|
|
781
|
+
))}
|
|
782
|
+
</div>
|
|
850
783
|
</div>
|
|
851
784
|
);
|
|
852
785
|
}
|
|
@@ -862,27 +795,24 @@ export function ErrorBlock({
|
|
|
862
795
|
body?: string;
|
|
863
796
|
onRetry?: () => void;
|
|
864
797
|
}) {
|
|
865
|
-
const [expanded,
|
|
798
|
+
const [expanded, setExpanded] = useState(false);
|
|
866
799
|
return (
|
|
867
800
|
<div className="group/work max-w-[860px] text-[14px] leading-5 tracking-[-0.25px] text-kumo-subtle">
|
|
868
|
-
<div className="flex w-full items-
|
|
801
|
+
<div className="flex w-full items-start gap-2">
|
|
869
802
|
<button
|
|
870
803
|
type="button"
|
|
871
|
-
onClick={
|
|
872
|
-
className={`${
|
|
804
|
+
onClick={() => setExpanded((value) => !value)}
|
|
805
|
+
className={`${DISCLOSURE_TITLE_WITH_ICON} flex-1`}
|
|
873
806
|
aria-expanded={expanded}
|
|
874
807
|
>
|
|
875
808
|
<span
|
|
876
|
-
className={`${
|
|
809
|
+
className={`${DISCLOSURE_ICON} text-kumo-danger`}
|
|
877
810
|
aria-hidden="true"
|
|
878
811
|
>
|
|
879
812
|
<WarningCircle size={15} weight="fill" />
|
|
880
813
|
</span>
|
|
881
814
|
<span className="flex min-w-0 flex-1 items-center gap-1">
|
|
882
|
-
<span className="min-w-0 truncate">
|
|
883
|
-
<span>{title}: </span>
|
|
884
|
-
<span>{body}</span>
|
|
885
|
-
</span>
|
|
815
|
+
<span className="min-w-0 truncate text-kumo-danger">{title}</span>
|
|
886
816
|
{body && (
|
|
887
817
|
<CaretRight
|
|
888
818
|
size={16}
|