@springbrand/message-panel 0.1.3-alpha.32 → 0.1.3-alpha.34
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/chat/cloud-os-chat-messages.tsx +44 -178
- package/cloud-os/chat/rich-blocks.tsx +276 -247
- package/cloud-os/chat/tool-rows.tsx +69 -306
- package/cloud-os/chat/transcript-model.ts +1 -108
- package/cloud-os/index.ts +0 -5
- package/cloud-os/styles/cloud-os.css +0 -12
- package/demo/chat-scenarios.ts +3 -9
- package/package.json +1 -1
- package/cloud-os/assets/thinking-spark.svg +0 -9
- package/cloud-os/chat/use-lifecycle-disclosure.ts +0 -65
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ArrowLeft,
|
|
3
|
+
ArrowRight,
|
|
2
4
|
Bell,
|
|
3
5
|
CaretRight,
|
|
4
6
|
Check,
|
|
5
|
-
CheckCircle,
|
|
6
7
|
CircleNotch,
|
|
7
8
|
Clock,
|
|
8
9
|
Key,
|
|
@@ -10,16 +11,11 @@ import {
|
|
|
10
11
|
UsersThree,
|
|
11
12
|
WarningCircle,
|
|
12
13
|
} from "@phosphor-icons/react";
|
|
13
|
-
import {
|
|
14
|
+
import { useRef, useState } from "react";
|
|
14
15
|
import { Tooltip } from "../primitives/tooltip";
|
|
15
16
|
import { WorkshopButton } from "../primitives/workshop-controls";
|
|
16
17
|
import { MarkdownMessage } from "./markdown-message";
|
|
17
|
-
import {
|
|
18
|
-
SECONDARY_DISCLOSURE_ICON,
|
|
19
|
-
SECONDARY_DISCLOSURE_TITLE_WITH_ICON,
|
|
20
|
-
SECONDARY_DISCLOSURE_TITLE_WITHOUT_ICON,
|
|
21
|
-
WorkIcon,
|
|
22
|
-
} from "./tool-rows";
|
|
18
|
+
import { WorkIcon } from "./tool-rows";
|
|
23
19
|
import type {
|
|
24
20
|
ParallelTool,
|
|
25
21
|
PlanStep,
|
|
@@ -31,13 +27,12 @@ import {
|
|
|
31
27
|
safeStringify,
|
|
32
28
|
type CloudOsToolCall,
|
|
33
29
|
} from "./tool-presentation";
|
|
34
|
-
import { useLifecycleDisclosure } from "./use-lifecycle-disclosure";
|
|
35
30
|
|
|
36
31
|
/**
|
|
37
32
|
* UIMessage 协议里存在、gadgets 那套没有的几类 part —— 计划快照、原位提问、
|
|
38
33
|
* 后续建议、定时任务、并行/子 Agent、原位授权。视觉语言全部沿用 cloud-os:
|
|
39
34
|
* 折叠工作行用 `px-1.5 py-1` 的行式布局,卡片用
|
|
40
|
-
* `rounded-2xl border-kumo-line/70 bg-kumo-elevated/45 p-3`,
|
|
35
|
+
* `themed-surface-inset rounded-2xl border-kumo-line/70 bg-kumo-elevated/45 p-3`,
|
|
41
36
|
* 授权卡沿用 renderActionCard 的 blocking callout(brand/40 边 + brand/10 底)。
|
|
42
37
|
*/
|
|
43
38
|
|
|
@@ -46,38 +41,37 @@ import { useLifecycleDisclosure } from "./use-lifecycle-disclosure";
|
|
|
46
41
|
export function PlanBlock({
|
|
47
42
|
steps,
|
|
48
43
|
running,
|
|
49
|
-
showCollapsedSummary = true,
|
|
50
44
|
}: {
|
|
51
45
|
steps: PlanStep[];
|
|
52
46
|
running: boolean;
|
|
53
|
-
showCollapsedSummary?: boolean;
|
|
54
47
|
}) {
|
|
55
48
|
const done = steps.filter((step) => step.status === "done").length;
|
|
56
49
|
const current = steps.find((step) => step.status === "in_progress");
|
|
57
|
-
const [open,
|
|
50
|
+
const [open, setOpen] = useState(true);
|
|
58
51
|
|
|
59
52
|
return (
|
|
60
|
-
<div className="group">
|
|
53
|
+
<div className="group -ml-0.5">
|
|
61
54
|
<button
|
|
62
55
|
type="button"
|
|
63
|
-
onClick={
|
|
64
|
-
className=
|
|
56
|
+
onClick={() => setOpen((value) => !value)}
|
|
57
|
+
className="flex w-full cursor-pointer items-center gap-3 rounded-xl px-1.5 py-1 text-left text-kumo-subtle transition-colors duration-150 ease-out hover:text-kumo-default focus-visible:text-kumo-default focus-visible:outline-none active:scale-[0.995]"
|
|
65
58
|
aria-expanded={open}
|
|
66
59
|
>
|
|
67
|
-
<span className=
|
|
60
|
+
<span className="flex h-5 w-5 flex-shrink-0 items-center justify-center">
|
|
68
61
|
<WorkIcon Icon={ListChecks} />
|
|
69
62
|
</span>
|
|
70
63
|
<span className="min-w-0 flex-1">
|
|
71
|
-
<span className="flex min-w-0 items-center gap-2">
|
|
64
|
+
<span className="flex min-w-0 items-center gap-2 text-[14px] leading-5 tracking-[-0.25px]">
|
|
72
65
|
<span className={`min-w-0 truncate ${running ? "cos-thinking-shimmer" : ""}`}>
|
|
73
66
|
Plan · {done}/{steps.length}
|
|
74
67
|
</span>
|
|
75
68
|
<CaretRight
|
|
76
|
-
size={
|
|
69
|
+
size={13}
|
|
70
|
+
weight="bold"
|
|
77
71
|
className={`flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${open ? "rotate-90" : ""}`}
|
|
78
72
|
/>
|
|
79
73
|
</span>
|
|
80
|
-
{!open &&
|
|
74
|
+
{!open && current && (
|
|
81
75
|
<span className="mt-1 block truncate font-mono text-[12px] leading-4 text-kumo-inactive">
|
|
82
76
|
{current.text}
|
|
83
77
|
</span>
|
|
@@ -85,34 +79,35 @@ export function PlanBlock({
|
|
|
85
79
|
</span>
|
|
86
80
|
</button>
|
|
87
81
|
{open && (
|
|
88
|
-
<div className="
|
|
82
|
+
<div className="themed-surface-inset ml-8 mt-1 space-y-1.5 rounded-2xl border border-kumo-line/70 bg-kumo-elevated/45 p-3">
|
|
89
83
|
{steps.map((step, index) => (
|
|
90
84
|
<div
|
|
91
85
|
key={`${step.text}-${index}`}
|
|
92
|
-
className="flex items-
|
|
86
|
+
className="flex items-start gap-2.5 text-[13px] leading-[19px] tracking-[-0.25px]"
|
|
93
87
|
>
|
|
94
88
|
<span
|
|
95
|
-
className="flex
|
|
89
|
+
className="mt-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center"
|
|
96
90
|
aria-hidden="true"
|
|
97
91
|
>
|
|
98
92
|
{step.status === "done" ? (
|
|
99
|
-
<
|
|
93
|
+
<Check size={12} weight="bold" className="text-kumo-success" />
|
|
100
94
|
) : step.status === "in_progress" ? (
|
|
101
95
|
<CircleNotch
|
|
102
|
-
size={
|
|
103
|
-
|
|
96
|
+
size={12}
|
|
97
|
+
weight="bold"
|
|
98
|
+
className="animate-spin text-kumo-brand motion-reduce:animate-none"
|
|
104
99
|
/>
|
|
105
100
|
) : (
|
|
106
|
-
<span className="h-1.5 w-1.5 rounded-full bg-
|
|
101
|
+
<span className="h-1.5 w-1.5 rounded-full bg-kumo-fill" />
|
|
107
102
|
)}
|
|
108
103
|
</span>
|
|
109
104
|
<span
|
|
110
105
|
className={
|
|
111
106
|
step.status === "done"
|
|
112
|
-
? "min-w-0 flex-1 text-kumo-
|
|
107
|
+
? "min-w-0 flex-1 text-kumo-inactive line-through"
|
|
113
108
|
: step.status === "in_progress"
|
|
114
|
-
? "min-w-0 flex-1 text-kumo-default"
|
|
115
|
-
: "min-w-0 flex-1 text-kumo-
|
|
109
|
+
? "min-w-0 flex-1 font-medium text-kumo-default"
|
|
110
|
+
: "min-w-0 flex-1 text-kumo-subtle"
|
|
116
111
|
}
|
|
117
112
|
>
|
|
118
113
|
{step.text}
|
|
@@ -172,7 +167,8 @@ export function AskUserBlock({
|
|
|
172
167
|
// 首帧 questions 往往还是空数组,那之后每次 setAnswers 都在空数组上 map,
|
|
173
168
|
// 选择永远存不进去 → Submit 永久禁用。改成稀疏存 + 按当前 questions 补齐。
|
|
174
169
|
const [answers, setAnswers] = useState<DraftAnswer[]>([]);
|
|
175
|
-
const
|
|
170
|
+
const [current, setCurrent] = useState(0);
|
|
171
|
+
const direction = useRef<"back" | "forward">("forward");
|
|
176
172
|
const answerAt = (index: number): DraftAnswer =>
|
|
177
173
|
answers[index] ?? { selections: [], text: "" };
|
|
178
174
|
const toggle = (questionIndex: number, option: string, multi: boolean) =>
|
|
@@ -202,43 +198,31 @@ export function AskUserBlock({
|
|
|
202
198
|
// 在途只用于按钮转圈;权威状态永远是 part 的 state。
|
|
203
199
|
const [inFlight, setInFlight] = useState(false);
|
|
204
200
|
const outcome = askUserOutcome(call);
|
|
205
|
-
const
|
|
206
|
-
outcome.kind === "pending",
|
|
207
|
-
);
|
|
208
|
-
const displayAnswerAt = (index: number): DraftAnswer => {
|
|
209
|
-
if (outcome.kind !== "answered") return answerAt(index);
|
|
210
|
-
const answer = outcome.answers[index];
|
|
211
|
-
return {
|
|
212
|
-
selections: answer?.selections ?? [],
|
|
213
|
-
text: answer?.text ?? "",
|
|
214
|
-
};
|
|
215
|
-
};
|
|
201
|
+
const settledAnswers = outcome.kind === "answered" ? outcome.answers : [];
|
|
216
202
|
// 服务端已经结算 = 这张卡永久不可操作,不管结算成什么。
|
|
217
203
|
const resolved = outcome.kind !== "pending" || inFlight;
|
|
218
204
|
const valid = questions.length > 0 && questions.every(
|
|
219
205
|
(_question, index) =>
|
|
220
206
|
answerAt(index).selections.length > 0 || answerAt(index).text.trim(),
|
|
221
207
|
);
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
?
|
|
234
|
-
:
|
|
235
|
-
: `${titleBase} · Closed`;
|
|
236
|
-
const buttonLabel = inFlight ? "Submitting…" : "Submit";
|
|
208
|
+
const currentIndex = Math.min(current, Math.max(questions.length - 1, 0));
|
|
209
|
+
const currentQuestion = questions[currentIndex];
|
|
210
|
+
const currentAnswer = answerAt(currentIndex);
|
|
211
|
+
const currentValid = currentAnswer.selections.length > 0 ||
|
|
212
|
+
Boolean(currentAnswer.text.trim());
|
|
213
|
+
const isLast = currentIndex === questions.length - 1;
|
|
214
|
+
const buttonLabel = outcome.kind === "answered"
|
|
215
|
+
? "Submitted"
|
|
216
|
+
: outcome.kind === "closed"
|
|
217
|
+
? "Closed"
|
|
218
|
+
: inFlight
|
|
219
|
+
? "Submitting…"
|
|
220
|
+
: "Submit";
|
|
237
221
|
const footnote = outcome.kind === "closed"
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
222
|
+
? outcome.reason === "user_replied_freeform"
|
|
223
|
+
? "Answered in the chat instead."
|
|
224
|
+
: "Closed without an answer."
|
|
225
|
+
: "";
|
|
242
226
|
|
|
243
227
|
const submit = () => {
|
|
244
228
|
if (!onRespond) return;
|
|
@@ -254,129 +238,205 @@ export function AskUserBlock({
|
|
|
254
238
|
}, () => setInFlight(false));
|
|
255
239
|
};
|
|
256
240
|
|
|
257
|
-
const
|
|
241
|
+
const settledButton = (
|
|
258
242
|
<WorkshopButton
|
|
259
243
|
tone="primary"
|
|
260
244
|
disabled
|
|
261
245
|
className="ml-auto !h-7 flex-shrink-0 gap-1 !rounded-lg px-2.5 text-[12px]"
|
|
262
246
|
>
|
|
263
|
-
|
|
247
|
+
{buttonLabel}
|
|
264
248
|
</WorkshopButton>
|
|
265
249
|
);
|
|
266
250
|
|
|
267
251
|
return (
|
|
252
|
+
// 收成 w-fit:短问题不该撑成一条横贯全栏的长条,长问题到 560px 再换行。
|
|
268
253
|
<div
|
|
269
|
-
className="w-
|
|
254
|
+
className="w-fit min-w-[15rem] max-w-[560px]"
|
|
270
255
|
data-part-type="ask_user"
|
|
271
256
|
data-state={outcome.kind === "answered" ? "submitted" : outcome.kind}
|
|
272
257
|
>
|
|
273
|
-
<div className="flex flex-col gap-3 py-2.5">
|
|
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
|
-
|
|
258
|
+
<div className="flex flex-col gap-3 rounded-xl border border-kumo-line bg-kumo-base px-3 py-2.5">
|
|
259
|
+
{outcome.kind === "pending" ? (
|
|
260
|
+
<>
|
|
261
|
+
{questions.length > 1 && (
|
|
262
|
+
<div className="flex flex-col gap-2">
|
|
263
|
+
<div className="flex items-center justify-between text-[11px] font-medium leading-4 text-kumo-inactive">
|
|
264
|
+
<span>Clarifying questions</span>
|
|
265
|
+
<span className="tabular-nums">
|
|
266
|
+
{currentIndex + 1}/{questions.length}
|
|
267
|
+
</span>
|
|
268
|
+
</div>
|
|
269
|
+
<div className="flex gap-1" aria-hidden="true">
|
|
270
|
+
{questions.map((question, index) => (
|
|
271
|
+
<span
|
|
272
|
+
key={`${question.prompt}:${index}`}
|
|
273
|
+
className={`h-1 flex-1 rounded-full ${
|
|
274
|
+
index < currentIndex
|
|
275
|
+
? "bg-kumo-brand"
|
|
276
|
+
: index === currentIndex
|
|
277
|
+
? "bg-kumo-brand/60"
|
|
278
|
+
: "bg-kumo-fill"
|
|
279
|
+
}`}
|
|
280
|
+
/>
|
|
281
|
+
))}
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
{currentQuestion && (
|
|
286
|
+
<div className="overflow-hidden">
|
|
287
|
+
<div
|
|
288
|
+
key={currentIndex}
|
|
289
|
+
className="cos-ask-user-question flex flex-col gap-2.5"
|
|
290
|
+
data-direction={direction.current}
|
|
291
|
+
>
|
|
292
|
+
<p className="m-0 text-[13px] leading-[18px] font-medium tracking-[-0.25px] text-kumo-default">
|
|
293
|
+
{currentQuestion.prompt}
|
|
294
|
+
</p>
|
|
295
|
+
{currentQuestion.options.length > 0 && (
|
|
296
|
+
<ul className="m-0 flex list-none flex-col gap-1.5 p-0">
|
|
297
|
+
{currentQuestion.options.map((option) => {
|
|
298
|
+
const selected = currentAnswer.selections.includes(
|
|
299
|
+
option,
|
|
300
|
+
);
|
|
301
|
+
return (
|
|
302
|
+
<li key={option} className="m-0 p-0">
|
|
303
|
+
<label
|
|
304
|
+
className={`flex min-h-8 items-center gap-2 rounded-lg border px-2.5 py-1.5 text-[13px] leading-[18px] tracking-[-0.25px] transition-colors duration-150 ease-out ${
|
|
305
|
+
resolved || !onRespond
|
|
306
|
+
? "cursor-default text-kumo-inactive"
|
|
307
|
+
: "cursor-pointer text-kumo-subtle hover:bg-kumo-tint hover:text-kumo-default"
|
|
308
|
+
} ${
|
|
309
|
+
selected
|
|
310
|
+
? "border-kumo-brand/45 bg-kumo-brand/10 text-kumo-default"
|
|
311
|
+
: "border-kumo-line bg-kumo-base"
|
|
312
|
+
}`}
|
|
313
|
+
>
|
|
314
|
+
<input
|
|
315
|
+
type={currentQuestion.multi
|
|
316
|
+
? "checkbox"
|
|
317
|
+
: "radio"}
|
|
318
|
+
name={`${call.toolCallId}-${currentIndex}`}
|
|
319
|
+
checked={selected}
|
|
315
320
|
disabled={resolved || !onRespond}
|
|
316
|
-
|
|
317
|
-
toggle(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
</label>
|
|
353
|
-
)}
|
|
354
|
-
</div>
|
|
355
|
-
);
|
|
356
|
-
})}
|
|
321
|
+
onChange={() =>
|
|
322
|
+
toggle(
|
|
323
|
+
currentIndex,
|
|
324
|
+
option,
|
|
325
|
+
currentQuestion.multi,
|
|
326
|
+
)}
|
|
327
|
+
className="h-4 w-4 shrink-0 accent-[var(--color-kumo-brand)]"
|
|
328
|
+
/>
|
|
329
|
+
<span>{option}</span>
|
|
330
|
+
</label>
|
|
331
|
+
</li>
|
|
332
|
+
);
|
|
333
|
+
})}
|
|
334
|
+
</ul>
|
|
335
|
+
)}
|
|
336
|
+
{currentQuestion.allowCustom && (
|
|
337
|
+
<label className="flex flex-col gap-1 text-[12px] leading-4 text-kumo-subtle">
|
|
338
|
+
<span>
|
|
339
|
+
{currentQuestion.options.length > 0 ? "Other" : "Answer"}
|
|
340
|
+
</span>
|
|
341
|
+
<input
|
|
342
|
+
type="text"
|
|
343
|
+
value={currentAnswer.text}
|
|
344
|
+
maxLength={2_000}
|
|
345
|
+
disabled={resolved || !onRespond}
|
|
346
|
+
aria-label={`自定义回答:${currentQuestion.prompt}`}
|
|
347
|
+
placeholder={currentQuestion.options.length > 0
|
|
348
|
+
? "Add a custom answer"
|
|
349
|
+
: "Type your answer"}
|
|
350
|
+
onChange={(event) =>
|
|
351
|
+
setText(currentIndex, event.currentTarget.value)}
|
|
352
|
+
className="h-8 rounded-lg border border-kumo-line bg-kumo-base px-2.5 text-[13px] text-kumo-default outline-none placeholder:text-kumo-inactive focus:border-kumo-brand focus:ring-2 focus:ring-kumo-brand/20 disabled:cursor-default"
|
|
353
|
+
/>
|
|
354
|
+
</label>
|
|
355
|
+
)}
|
|
356
|
+
</div>
|
|
357
357
|
</div>
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
358
|
+
)}
|
|
359
|
+
<div className="flex items-center gap-2">
|
|
360
|
+
{questions.length > 1 && (
|
|
361
|
+
<WorkshopButton
|
|
362
|
+
disabled={currentIndex === 0 || resolved}
|
|
363
|
+
onClick={() => {
|
|
364
|
+
direction.current = "back";
|
|
365
|
+
setCurrent((index) => Math.max(0, index - 1));
|
|
366
|
+
}}
|
|
367
|
+
className="!h-7 gap-1 !rounded-lg px-2.5 text-[12px]"
|
|
364
368
|
>
|
|
365
|
-
{
|
|
366
|
-
|
|
369
|
+
<ArrowLeft size={13} weight="bold" />
|
|
370
|
+
Back
|
|
371
|
+
</WorkshopButton>
|
|
367
372
|
)}
|
|
368
|
-
|
|
369
|
-
|
|
373
|
+
<WorkshopButton
|
|
374
|
+
tone="primary"
|
|
375
|
+
disabled={
|
|
376
|
+
resolved || !onRespond ||
|
|
377
|
+
(questions.length > 1 && !isLast ? !currentValid : !valid)
|
|
378
|
+
}
|
|
379
|
+
onClick={() => {
|
|
380
|
+
if (questions.length > 1 && !isLast) {
|
|
381
|
+
direction.current = "forward";
|
|
382
|
+
setCurrent((index) =>
|
|
383
|
+
Math.min(questions.length - 1, index + 1)
|
|
384
|
+
);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
submit();
|
|
388
|
+
}}
|
|
389
|
+
className="ml-auto !h-7 gap-1 !rounded-lg px-2.5 text-[12px]"
|
|
390
|
+
>
|
|
391
|
+
{questions.length > 1 && !isLast ? (
|
|
392
|
+
<>
|
|
393
|
+
Next
|
|
394
|
+
<ArrowRight size={13} weight="bold" />
|
|
395
|
+
</>
|
|
396
|
+
) : buttonLabel}
|
|
397
|
+
</WorkshopButton>
|
|
398
|
+
</div>
|
|
399
|
+
</>
|
|
400
|
+
) : (
|
|
401
|
+
<>
|
|
402
|
+
{outcome.kind === "answered" &&
|
|
403
|
+
questions.map((question, questionIndex) => (
|
|
404
|
+
<div
|
|
405
|
+
key={`${question.prompt}:${questionIndex}`}
|
|
406
|
+
className="flex flex-col gap-1.5"
|
|
407
|
+
>
|
|
408
|
+
<p className="m-0 text-[13px] leading-[18px] font-medium tracking-[-0.25px] text-kumo-default">
|
|
409
|
+
{question.prompt}
|
|
410
|
+
</p>
|
|
411
|
+
{settledAnswers[questionIndex] && (
|
|
412
|
+
<div className="flex flex-col gap-1 text-[12px] leading-4 text-kumo-inactive">
|
|
413
|
+
{settledAnswers[questionIndex]!.selections.length > 0 && (
|
|
414
|
+
<p className="m-0">
|
|
415
|
+
Answered: {settledAnswers[questionIndex]!.selections
|
|
416
|
+
.join(" / ")}
|
|
417
|
+
</p>
|
|
418
|
+
)}
|
|
419
|
+
{settledAnswers[questionIndex]!.text.trim() && (
|
|
420
|
+
<p className="m-0">
|
|
421
|
+
{settledAnswers[questionIndex]!.selections.length > 0
|
|
422
|
+
? `补充:${settledAnswers[questionIndex]!.text.trim()}`
|
|
423
|
+
: `Answered: ${settledAnswers[questionIndex]!.text.trim()}`}
|
|
424
|
+
</p>
|
|
425
|
+
)}
|
|
426
|
+
</div>
|
|
427
|
+
)}
|
|
428
|
+
</div>
|
|
429
|
+
))}
|
|
370
430
|
<div className="flex items-center gap-3">
|
|
371
431
|
{footnote && (
|
|
372
432
|
<p className="m-0 min-w-0 text-[12px] leading-4 text-kumo-inactive">
|
|
373
433
|
{footnote}
|
|
374
434
|
</p>
|
|
375
435
|
)}
|
|
376
|
-
{
|
|
436
|
+
{settledButton}
|
|
377
437
|
</div>
|
|
378
|
-
|
|
379
|
-
|
|
438
|
+
</>
|
|
439
|
+
)}
|
|
380
440
|
</div>
|
|
381
441
|
</div>
|
|
382
442
|
);
|
|
@@ -483,7 +543,7 @@ export function ScheduleConfirmation({
|
|
|
483
543
|
<section
|
|
484
544
|
data-slot="schedule-card"
|
|
485
545
|
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-
|
|
546
|
+
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
547
|
>
|
|
488
548
|
<div className="flex items-center gap-2.5">
|
|
489
549
|
<span
|
|
@@ -547,7 +607,7 @@ export function ScheduleBlock({
|
|
|
547
607
|
"aria-label": `Open scheduled task: ${String(call.input.label ?? call.input.title ?? "Scheduled task")}`,
|
|
548
608
|
}
|
|
549
609
|
: {})}
|
|
550
|
-
className={`w-full rounded-2xl border border-kumo-line bg-
|
|
610
|
+
className={`w-full rounded-2xl border border-kumo-line bg-kumo-base px-4 py-3 text-left${
|
|
551
611
|
clickable
|
|
552
612
|
? " 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
613
|
: ""
|
|
@@ -625,7 +685,7 @@ export function PermissionGrant({
|
|
|
625
685
|
return (
|
|
626
686
|
<section
|
|
627
687
|
aria-label={ariaLabel}
|
|
628
|
-
className="w-full rounded-xl border border-kumo-line bg-
|
|
688
|
+
className="themed-surface-inset w-full rounded-xl border border-kumo-line bg-kumo-elevated p-4 text-kumo-default"
|
|
629
689
|
>
|
|
630
690
|
<div className="flex items-center gap-2">
|
|
631
691
|
<span
|
|
@@ -749,104 +809,72 @@ function StatusDot({ status }: { status: ParallelTool["status"] }) {
|
|
|
749
809
|
export function ParallelBlock({
|
|
750
810
|
label,
|
|
751
811
|
tools,
|
|
752
|
-
running = tools.some((tool) => tool.status === "running"),
|
|
753
812
|
}: {
|
|
754
813
|
label: string;
|
|
755
814
|
tools: ParallelTool[];
|
|
756
|
-
running?: boolean;
|
|
757
815
|
}) {
|
|
758
816
|
const done = tools.filter((tool) => tool.status === "done").length;
|
|
759
|
-
const [open, toggle] = useLifecycleDisclosure(running);
|
|
760
817
|
return (
|
|
761
818
|
<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}>
|
|
819
|
+
<div className="flex items-center gap-3 px-1.5 py-1 text-[14px] leading-5 tracking-[-0.25px] text-kumo-subtle">
|
|
820
|
+
<span className="flex h-5 w-5 flex-shrink-0 items-center justify-center">
|
|
769
821
|
<WorkIcon Icon={ListChecks} />
|
|
770
822
|
</span>
|
|
771
823
|
<span className="min-w-0 truncate">
|
|
772
824
|
{label} · {done}/{tools.length}
|
|
773
825
|
</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
|
-
)}
|
|
826
|
+
</div>
|
|
827
|
+
<div className="ml-8 mt-1 space-y-1">
|
|
828
|
+
{tools.map((tool, index) => (
|
|
829
|
+
<div
|
|
830
|
+
key={`${tool.label}-${index}`}
|
|
831
|
+
className="flex items-center gap-2 px-1.5 text-[13px] leading-[19px] tracking-[-0.25px] text-kumo-subtle"
|
|
832
|
+
>
|
|
833
|
+
<StatusDot status={tool.status} />
|
|
834
|
+
<span className="min-w-0 flex-1 truncate">{tool.label}</span>
|
|
835
|
+
{tool.meta && (
|
|
836
|
+
<span className="flex-shrink-0 font-mono text-[11px] leading-4 text-kumo-inactive">
|
|
837
|
+
{tool.meta}
|
|
838
|
+
</span>
|
|
839
|
+
)}
|
|
840
|
+
</div>
|
|
841
|
+
))}
|
|
842
|
+
</div>
|
|
797
843
|
</div>
|
|
798
844
|
);
|
|
799
845
|
}
|
|
800
846
|
|
|
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);
|
|
847
|
+
export function SubAgentsBlock({ agents }: { agents: SubAgentView[] }) {
|
|
809
848
|
return (
|
|
810
849
|
<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}>
|
|
850
|
+
<div className="flex items-center gap-3 px-1.5 py-1 text-[14px] leading-5 tracking-[-0.25px] text-kumo-subtle">
|
|
851
|
+
<span className="flex h-5 w-5 flex-shrink-0 items-center justify-center">
|
|
818
852
|
<WorkIcon Icon={UsersThree} />
|
|
819
853
|
</span>
|
|
820
854
|
<span className="min-w-0 truncate">
|
|
821
855
|
{agents.length === 1 ? "Sub-agent" : `${agents.length} sub-agents`}
|
|
822
856
|
</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
|
-
)}
|
|
857
|
+
</div>
|
|
858
|
+
<div className="ml-8 mt-1 space-y-1">
|
|
859
|
+
{agents.map((agent, index) => (
|
|
860
|
+
<div
|
|
861
|
+
key={`${agent.name}-${index}`}
|
|
862
|
+
className="flex items-start gap-2 px-1.5 text-[13px] leading-[19px] tracking-[-0.25px]"
|
|
863
|
+
>
|
|
864
|
+
<span className="mt-1.5 flex">
|
|
865
|
+
<StatusDot status={agent.status} />
|
|
866
|
+
</span>
|
|
867
|
+
<span className="min-w-0 flex-1">
|
|
868
|
+
<span className="block truncate font-medium text-kumo-default">
|
|
869
|
+
{agent.name}
|
|
845
870
|
</span>
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
871
|
+
{agent.summary && (
|
|
872
|
+
<span className="block text-kumo-subtle">{agent.summary}</span>
|
|
873
|
+
)}
|
|
874
|
+
</span>
|
|
875
|
+
</div>
|
|
876
|
+
))}
|
|
877
|
+
</div>
|
|
850
878
|
</div>
|
|
851
879
|
);
|
|
852
880
|
}
|
|
@@ -862,30 +890,31 @@ export function ErrorBlock({
|
|
|
862
890
|
body?: string;
|
|
863
891
|
onRetry?: () => void;
|
|
864
892
|
}) {
|
|
865
|
-
const [expanded,
|
|
893
|
+
const [expanded, setExpanded] = useState(false);
|
|
866
894
|
return (
|
|
867
895
|
<div className="group/work max-w-[860px] text-[14px] leading-5 tracking-[-0.25px] text-kumo-subtle">
|
|
868
|
-
<div className="flex w-full items-center gap-2">
|
|
896
|
+
<div className="flex w-full items-center gap-2 px-1.5 py-1">
|
|
869
897
|
<button
|
|
870
898
|
type="button"
|
|
871
|
-
onClick={
|
|
872
|
-
className=
|
|
899
|
+
onClick={() => setExpanded((value) => !value)}
|
|
900
|
+
className="flex min-w-0 flex-1 cursor-pointer items-center gap-3 rounded-md text-left transition-colors duration-150 ease-out hover:text-kumo-default focus-visible:text-kumo-default focus-visible:outline-none active:scale-[0.995]"
|
|
873
901
|
aria-expanded={expanded}
|
|
874
902
|
>
|
|
875
903
|
<span
|
|
876
|
-
className=
|
|
904
|
+
className="flex h-5 w-5 flex-shrink-0 items-center justify-center text-kumo-danger"
|
|
877
905
|
aria-hidden="true"
|
|
878
906
|
>
|
|
879
|
-
<WarningCircle size={
|
|
907
|
+
<WarningCircle size={16} weight="fill" />
|
|
880
908
|
</span>
|
|
881
909
|
<span className="flex min-w-0 flex-1 items-center gap-1">
|
|
882
910
|
<span className="min-w-0 truncate">
|
|
883
|
-
<span>{title}: </span>
|
|
884
|
-
<span>{body}</span>
|
|
911
|
+
<span className="font-medium text-kumo-danger">{title}: </span>
|
|
912
|
+
<span className="text-kumo-subtle">{body}</span>
|
|
885
913
|
</span>
|
|
886
914
|
{body && (
|
|
887
915
|
<CaretRight
|
|
888
|
-
size={
|
|
916
|
+
size={13}
|
|
917
|
+
weight="bold"
|
|
889
918
|
className={`flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${expanded ? "rotate-90" : ""}`}
|
|
890
919
|
/>
|
|
891
920
|
)}
|
|
@@ -904,7 +933,7 @@ export function ErrorBlock({
|
|
|
904
933
|
)}
|
|
905
934
|
</div>
|
|
906
935
|
{expanded && body && (
|
|
907
|
-
<div className="mt-1">
|
|
936
|
+
<div className="ml-8 mt-1">
|
|
908
937
|
<pre className="max-h-48 overflow-auto rounded-xl border border-kumo-line/70 bg-kumo-base p-3 font-mono text-[12px] leading-[18px] text-kumo-subtle whitespace-pre-wrap">
|
|
909
938
|
{body}
|
|
910
939
|
</pre>
|