@springbrand/message-panel 0.1.3-alpha.34 → 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 +262 -77
- package/cloud-os/chat/code-runner.tsx +106 -0
- package/cloud-os/chat/range.ts +8 -0
- package/cloud-os/chat/rich-blocks.tsx +161 -260
- 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 -141
- package/cloud-os/chat/tool-timeline.tsx +123 -0
- package/cloud-os/chat/transcript-model.ts +92 -15
- 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 -0
- package/cloud-os/primitives/collapsible.tsx +21 -0
- package/cloud-os/styles/cloud-os.css +18 -0
- package/package.json +3 -1
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ArrowLeft,
|
|
3
|
-
ArrowRight,
|
|
4
2
|
Bell,
|
|
5
3
|
CaretRight,
|
|
6
4
|
Check,
|
|
5
|
+
CheckCircle,
|
|
7
6
|
CircleNotch,
|
|
8
7
|
Clock,
|
|
9
8
|
Key,
|
|
@@ -11,11 +10,21 @@ import {
|
|
|
11
10
|
UsersThree,
|
|
12
11
|
WarningCircle,
|
|
13
12
|
} from "@phosphor-icons/react";
|
|
14
|
-
import {
|
|
13
|
+
import { useState } from "react";
|
|
14
|
+
import {
|
|
15
|
+
Collapsible,
|
|
16
|
+
CollapsibleContent,
|
|
17
|
+
CollapsibleTrigger,
|
|
18
|
+
} from "../primitives/collapsible";
|
|
15
19
|
import { Tooltip } from "../primitives/tooltip";
|
|
16
20
|
import { WorkshopButton } from "../primitives/workshop-controls";
|
|
17
21
|
import { MarkdownMessage } from "./markdown-message";
|
|
18
|
-
import {
|
|
22
|
+
import { collapsePanel } from "./surfaces";
|
|
23
|
+
import {
|
|
24
|
+
DISCLOSURE_ICON,
|
|
25
|
+
DISCLOSURE_TITLE_WITH_ICON,
|
|
26
|
+
WorkIcon,
|
|
27
|
+
} from "./tool-rows";
|
|
19
28
|
import type {
|
|
20
29
|
ParallelTool,
|
|
21
30
|
PlanStep,
|
|
@@ -31,7 +40,7 @@ import {
|
|
|
31
40
|
/**
|
|
32
41
|
* UIMessage 协议里存在、gadgets 那套没有的几类 part —— 计划快照、原位提问、
|
|
33
42
|
* 后续建议、定时任务、并行/子 Agent、原位授权。视觉语言全部沿用 cloud-os:
|
|
34
|
-
* 折叠工作行用 `
|
|
43
|
+
* 折叠工作行用 `py-1` 的行式布局,卡片用
|
|
35
44
|
* `themed-surface-inset rounded-2xl border-kumo-line/70 bg-kumo-elevated/45 p-3`,
|
|
36
45
|
* 授权卡沿用 renderActionCard 的 blocking callout(brand/40 边 + brand/10 底)。
|
|
37
46
|
*/
|
|
@@ -41,73 +50,78 @@ import {
|
|
|
41
50
|
export function PlanBlock({
|
|
42
51
|
steps,
|
|
43
52
|
running,
|
|
53
|
+
defaultOpen = true,
|
|
44
54
|
}: {
|
|
45
55
|
steps: PlanStep[];
|
|
46
56
|
running: boolean;
|
|
57
|
+
defaultOpen?: boolean;
|
|
47
58
|
}) {
|
|
48
59
|
const done = steps.filter((step) => step.status === "done").length;
|
|
49
60
|
const current = steps.find((step) => step.status === "in_progress");
|
|
50
|
-
const [open, setOpen] = useState(
|
|
61
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
51
62
|
|
|
52
63
|
return (
|
|
53
|
-
<
|
|
54
|
-
<
|
|
55
|
-
|
|
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]"
|
|
58
|
-
aria-expanded={open}
|
|
64
|
+
<Collapsible open={open} onOpenChange={setOpen} className="group">
|
|
65
|
+
<CollapsibleTrigger
|
|
66
|
+
className={`${DISCLOSURE_TITLE_WITH_ICON} w-full`}
|
|
59
67
|
>
|
|
60
|
-
<span
|
|
61
|
-
|
|
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" />
|
|
62
80
|
</span>
|
|
63
|
-
<span className="min-w-0 flex-1">
|
|
64
|
-
<span className=
|
|
65
|
-
|
|
66
|
-
Plan · {done}/{steps.length}
|
|
67
|
-
</span>
|
|
68
|
-
<CaretRight
|
|
69
|
-
size={13}
|
|
70
|
-
weight="bold"
|
|
71
|
-
className={`flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${open ? "rotate-90" : ""}`}
|
|
72
|
-
/>
|
|
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}
|
|
73
84
|
</span>
|
|
74
85
|
{!open && current && (
|
|
75
|
-
<span className="
|
|
86
|
+
<span className="min-w-0 flex-1 truncate text-[12px] leading-4 text-[rgba(0,0,0,0.3)]">
|
|
76
87
|
{current.text}
|
|
77
88
|
</span>
|
|
78
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
|
+
/>
|
|
79
94
|
</span>
|
|
80
|
-
</
|
|
81
|
-
{
|
|
82
|
-
<div className="
|
|
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">
|
|
83
98
|
{steps.map((step, index) => (
|
|
84
99
|
<div
|
|
85
100
|
key={`${step.text}-${index}`}
|
|
86
|
-
className="flex items-
|
|
101
|
+
className="flex items-center gap-2 text-[14px] leading-[1.4] font-normal tracking-normal"
|
|
87
102
|
>
|
|
88
103
|
<span
|
|
89
|
-
className="
|
|
104
|
+
className="flex size-4 shrink-0 items-center justify-center"
|
|
90
105
|
aria-hidden="true"
|
|
91
106
|
>
|
|
92
107
|
{step.status === "done" ? (
|
|
93
|
-
<
|
|
108
|
+
<CheckCircle size={16} weight="fill" className="text-[#00bf75]" />
|
|
94
109
|
) : step.status === "in_progress" ? (
|
|
95
110
|
<CircleNotch
|
|
96
|
-
size={
|
|
97
|
-
|
|
98
|
-
className="animate-spin text-kumo-brand motion-reduce:animate-none"
|
|
111
|
+
size={16}
|
|
112
|
+
className="animate-spin text-[#ff9938] motion-reduce:animate-none"
|
|
99
113
|
/>
|
|
100
114
|
) : (
|
|
101
|
-
<span className="h-1.5 w-1.5 rounded-full bg-kumo-
|
|
115
|
+
<span className="h-1.5 w-1.5 rounded-full bg-[color:var(--text-color-kumo-default)] opacity-20" />
|
|
102
116
|
)}
|
|
103
117
|
</span>
|
|
104
118
|
<span
|
|
105
119
|
className={
|
|
106
120
|
step.status === "done"
|
|
107
|
-
? "min-w-0 flex-1 text-kumo-
|
|
121
|
+
? "min-w-0 flex-1 text-kumo-default/30 line-through"
|
|
108
122
|
: step.status === "in_progress"
|
|
109
|
-
? "min-w-0 flex-1
|
|
110
|
-
: "min-w-0 flex-1 text-kumo-
|
|
123
|
+
? "min-w-0 flex-1 text-kumo-default"
|
|
124
|
+
: "min-w-0 flex-1 text-kumo-default/70"
|
|
111
125
|
}
|
|
112
126
|
>
|
|
113
127
|
{step.text}
|
|
@@ -115,8 +129,8 @@ export function PlanBlock({
|
|
|
115
129
|
</div>
|
|
116
130
|
))}
|
|
117
131
|
</div>
|
|
118
|
-
|
|
119
|
-
</
|
|
132
|
+
</CollapsibleContent>
|
|
133
|
+
</Collapsible>
|
|
120
134
|
);
|
|
121
135
|
}
|
|
122
136
|
|
|
@@ -167,8 +181,6 @@ export function AskUserBlock({
|
|
|
167
181
|
// 首帧 questions 往往还是空数组,那之后每次 setAnswers 都在空数组上 map,
|
|
168
182
|
// 选择永远存不进去 → Submit 永久禁用。改成稀疏存 + 按当前 questions 补齐。
|
|
169
183
|
const [answers, setAnswers] = useState<DraftAnswer[]>([]);
|
|
170
|
-
const [current, setCurrent] = useState(0);
|
|
171
|
-
const direction = useRef<"back" | "forward">("forward");
|
|
172
184
|
const answerAt = (index: number): DraftAnswer =>
|
|
173
185
|
answers[index] ?? { selections: [], text: "" };
|
|
174
186
|
const toggle = (questionIndex: number, option: string, multi: boolean) =>
|
|
@@ -198,26 +210,21 @@ export function AskUserBlock({
|
|
|
198
210
|
// 在途只用于按钮转圈;权威状态永远是 part 的 state。
|
|
199
211
|
const [inFlight, setInFlight] = useState(false);
|
|
200
212
|
const outcome = askUserOutcome(call);
|
|
201
|
-
const
|
|
213
|
+
const displayAnswerAt = (index: number): DraftAnswer => {
|
|
214
|
+
if (outcome.kind !== "answered") return answerAt(index);
|
|
215
|
+
const answer = outcome.answers[index];
|
|
216
|
+
return {
|
|
217
|
+
selections: answer?.selections ?? [],
|
|
218
|
+
text: answer?.text ?? "",
|
|
219
|
+
};
|
|
220
|
+
};
|
|
202
221
|
// 服务端已经结算 = 这张卡永久不可操作,不管结算成什么。
|
|
203
222
|
const resolved = outcome.kind !== "pending" || inFlight;
|
|
204
223
|
const valid = questions.length > 0 && questions.every(
|
|
205
224
|
(_question, index) =>
|
|
206
225
|
answerAt(index).selections.length > 0 || answerAt(index).text.trim(),
|
|
207
226
|
);
|
|
208
|
-
const
|
|
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";
|
|
227
|
+
const buttonLabel = inFlight ? "Submitting…" : "Submit";
|
|
221
228
|
const footnote = outcome.kind === "closed"
|
|
222
229
|
? outcome.reason === "user_replied_freeform"
|
|
223
230
|
? "Answered in the chat instead."
|
|
@@ -238,204 +245,102 @@ export function AskUserBlock({
|
|
|
238
245
|
}, () => setInFlight(false));
|
|
239
246
|
};
|
|
240
247
|
|
|
241
|
-
const settledButton = (
|
|
242
|
-
<WorkshopButton
|
|
243
|
-
tone="primary"
|
|
244
|
-
disabled
|
|
245
|
-
className="ml-auto !h-7 flex-shrink-0 gap-1 !rounded-lg px-2.5 text-[12px]"
|
|
246
|
-
>
|
|
247
|
-
{buttonLabel}
|
|
248
|
-
</WorkshopButton>
|
|
249
|
-
);
|
|
250
|
-
|
|
251
248
|
return (
|
|
252
|
-
// 收成 w-fit:短问题不该撑成一条横贯全栏的长条,长问题到 560px 再换行。
|
|
253
249
|
<div
|
|
254
|
-
className="w-
|
|
250
|
+
className="w-full"
|
|
255
251
|
data-part-type="ask_user"
|
|
256
252
|
data-state={outcome.kind === "answered" ? "submitted" : outcome.kind}
|
|
257
253
|
>
|
|
258
|
-
<div className="flex flex-col gap-
|
|
259
|
-
{outcome.kind
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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}
|
|
320
|
-
disabled={resolved || !onRespond}
|
|
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}
|
|
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}
|
|
345
277
|
disabled={resolved || !onRespond}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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]"
|
|
368
|
-
>
|
|
369
|
-
<ArrowLeft size={13} weight="bold" />
|
|
370
|
-
Back
|
|
371
|
-
</WorkshopButton>
|
|
372
|
-
)}
|
|
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)
|
|
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>
|
|
384
296
|
);
|
|
385
|
-
|
|
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
|
-
)}
|
|
297
|
+
})}
|
|
428
298
|
</div>
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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>
|
|
317
|
+
)}
|
|
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()}
|
|
434
322
|
</p>
|
|
435
323
|
)}
|
|
436
|
-
{settledButton}
|
|
437
324
|
</div>
|
|
438
|
-
|
|
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>
|
|
439
344
|
)}
|
|
440
345
|
</div>
|
|
441
346
|
</div>
|
|
@@ -816,15 +721,15 @@ export function ParallelBlock({
|
|
|
816
721
|
const done = tools.filter((tool) => tool.status === "done").length;
|
|
817
722
|
return (
|
|
818
723
|
<div className="max-w-[860px]">
|
|
819
|
-
<div className="flex items-
|
|
820
|
-
<span className=
|
|
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}>
|
|
821
726
|
<WorkIcon Icon={ListChecks} />
|
|
822
727
|
</span>
|
|
823
728
|
<span className="min-w-0 truncate">
|
|
824
729
|
{label} · {done}/{tools.length}
|
|
825
730
|
</span>
|
|
826
731
|
</div>
|
|
827
|
-
<div className="
|
|
732
|
+
<div className="mt-1 space-y-1">
|
|
828
733
|
{tools.map((tool, index) => (
|
|
829
734
|
<div
|
|
830
735
|
key={`${tool.label}-${index}`}
|
|
@@ -847,15 +752,15 @@ export function ParallelBlock({
|
|
|
847
752
|
export function SubAgentsBlock({ agents }: { agents: SubAgentView[] }) {
|
|
848
753
|
return (
|
|
849
754
|
<div className="max-w-[860px]">
|
|
850
|
-
<div className="flex items-
|
|
851
|
-
<span className=
|
|
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}>
|
|
852
757
|
<WorkIcon Icon={UsersThree} />
|
|
853
758
|
</span>
|
|
854
759
|
<span className="min-w-0 truncate">
|
|
855
760
|
{agents.length === 1 ? "Sub-agent" : `${agents.length} sub-agents`}
|
|
856
761
|
</span>
|
|
857
762
|
</div>
|
|
858
|
-
<div className="
|
|
763
|
+
<div className="mt-1 space-y-1">
|
|
859
764
|
{agents.map((agent, index) => (
|
|
860
765
|
<div
|
|
861
766
|
key={`${agent.name}-${index}`}
|
|
@@ -893,28 +798,24 @@ export function ErrorBlock({
|
|
|
893
798
|
const [expanded, setExpanded] = useState(false);
|
|
894
799
|
return (
|
|
895
800
|
<div className="group/work max-w-[860px] text-[14px] leading-5 tracking-[-0.25px] text-kumo-subtle">
|
|
896
|
-
<div className="flex w-full items-
|
|
801
|
+
<div className="flex w-full items-start gap-2">
|
|
897
802
|
<button
|
|
898
803
|
type="button"
|
|
899
804
|
onClick={() => setExpanded((value) => !value)}
|
|
900
|
-
className=
|
|
805
|
+
className={`${DISCLOSURE_TITLE_WITH_ICON} flex-1`}
|
|
901
806
|
aria-expanded={expanded}
|
|
902
807
|
>
|
|
903
808
|
<span
|
|
904
|
-
className=
|
|
809
|
+
className={`${DISCLOSURE_ICON} text-kumo-danger`}
|
|
905
810
|
aria-hidden="true"
|
|
906
811
|
>
|
|
907
|
-
<WarningCircle size={
|
|
812
|
+
<WarningCircle size={15} weight="fill" />
|
|
908
813
|
</span>
|
|
909
814
|
<span className="flex min-w-0 flex-1 items-center gap-1">
|
|
910
|
-
<span className="min-w-0 truncate">
|
|
911
|
-
<span className="font-medium text-kumo-danger">{title}: </span>
|
|
912
|
-
<span className="text-kumo-subtle">{body}</span>
|
|
913
|
-
</span>
|
|
815
|
+
<span className="min-w-0 truncate text-kumo-danger">{title}</span>
|
|
914
816
|
{body && (
|
|
915
817
|
<CaretRight
|
|
916
|
-
size={
|
|
917
|
-
weight="bold"
|
|
818
|
+
size={16}
|
|
918
819
|
className={`flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${expanded ? "rotate-90" : ""}`}
|
|
919
820
|
/>
|
|
920
821
|
)}
|
|
@@ -933,7 +834,7 @@ export function ErrorBlock({
|
|
|
933
834
|
)}
|
|
934
835
|
</div>
|
|
935
836
|
{expanded && body && (
|
|
936
|
-
<div className="
|
|
837
|
+
<div className="mt-1">
|
|
937
838
|
<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">
|
|
938
839
|
{body}
|
|
939
840
|
</pre>
|