@springbrand/message-panel 0.1.3-alpha.9 → 0.2.0-alpha.38
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 +4 -3
- package/cloud-os/assets/followup-arrow.svg +3 -0
- package/cloud-os/assets/loading-corner.svg +3 -0
- package/cloud-os/assets/loading-mark.svg +16 -0
- package/cloud-os/assets/loading-spark.svg +3 -0
- package/cloud-os/assets/model-selected.svg +5 -0
- package/cloud-os/assets/thinking-star.svg +9 -0
- package/cloud-os/capability-chip.tsx +1 -1
- package/cloud-os/chat/cloud-os-chat-messages.tsx +441 -98
- package/cloud-os/chat/code-runner.tsx +106 -0
- package/cloud-os/chat/image-generation.tsx +76 -0
- package/cloud-os/chat/loading-state.tsx +25 -0
- package/cloud-os/chat/markdown-message.tsx +144 -41
- package/cloud-os/chat/range.ts +8 -0
- package/cloud-os/chat/rich-blocks.tsx +285 -227
- package/cloud-os/chat/surfaces.tsx +90 -0
- package/cloud-os/chat/tool-call.tsx +94 -0
- package/cloud-os/chat/tool-presentation.ts +31 -22
- package/cloud-os/chat/tool-rows.tsx +195 -143
- package/cloud-os/chat/tool-timeline.tsx +123 -0
- package/cloud-os/chat/transcript-model.ts +258 -20
- package/cloud-os/chat/web-search.tsx +135 -0
- package/cloud-os/composer/cloud-os-chat-input.tsx +29 -88
- package/cloud-os/composer/cloud-os-model-select.tsx +105 -0
- package/cloud-os/file-view.tsx +195 -12
- package/cloud-os/index.ts +24 -3
- package/cloud-os/layout/cloud-os-workspace-split.tsx +60 -3
- package/cloud-os/primitives/collapsible.tsx +21 -0
- package/cloud-os/primitives/workshop-controls.tsx +2 -2
- package/cloud-os/styles/cloud-os.css +114 -1
- package/demo/camel-chat-showcase.tsx +21 -13
- package/demo/chat-scenarios.ts +214 -40
- package/demo/cloud-os-chat-showcase.tsx +37 -14
- package/demo/fixtures.ts +4 -5
- package/demo/message-panel-gallery.tsx +16 -2
- package/package.json +8 -4
- package/springbrand-pet/LICENSE.bloub +21 -0
- package/springbrand-pet/bot/cycles.test.ts +221 -0
- package/springbrand-pet/bot/cycles.ts +225 -0
- package/springbrand-pet/bot/decor.ts +285 -0
- package/springbrand-pet/bot/engine.test.ts +543 -0
- package/springbrand-pet/bot/engine.ts +558 -0
- package/springbrand-pet/bot/expressions.test.ts +135 -0
- package/springbrand-pet/bot/expressions.ts +192 -0
- package/springbrand-pet/bot/eyefit.ts +455 -0
- package/springbrand-pet/bot/face.test.ts +101 -0
- package/springbrand-pet/bot/face.ts +179 -0
- package/springbrand-pet/bot/math.ts +42 -0
- package/springbrand-pet/bot/profiles.ts +22 -0
- package/springbrand-pet/bot/repere.ts +31 -0
- package/springbrand-pet/bot/shape.test.ts +97 -0
- package/springbrand-pet/bot/shape.ts +310 -0
- package/springbrand-pet/bot/skins.test.ts +325 -0
- package/springbrand-pet/bot/skins.ts +161 -0
- package/springbrand-pet/bot/states.ts +616 -0
- package/springbrand-pet/drag.test.ts +19 -0
- package/springbrand-pet/index.tsx +569 -0
- package/springbrand-pet/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/springbrand-pet/vitest.config.ts +9 -0
- package/src/camel/camel-chat-messages.tsx +78 -78
- package/src/camel/camel-tool-presentation.tsx +19 -15
- package/src/camel/camel-turn.ts +1 -17
- package/src/composer/composer-attachments.tsx +1 -1
- package/src/composer/composer.tsx +2 -2
- package/src/contracts.ts +0 -2
- package/src/message-panel.tsx +1 -24
- package/src/parts/index.tsx +103 -64
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
Bell,
|
|
3
3
|
CaretRight,
|
|
4
4
|
Check,
|
|
5
|
+
CheckCircle,
|
|
5
6
|
CircleNotch,
|
|
6
7
|
Clock,
|
|
7
8
|
Key,
|
|
@@ -10,10 +11,20 @@ import {
|
|
|
10
11
|
WarningCircle,
|
|
11
12
|
} from "@phosphor-icons/react";
|
|
12
13
|
import { useState } from "react";
|
|
14
|
+
import {
|
|
15
|
+
Collapsible,
|
|
16
|
+
CollapsibleContent,
|
|
17
|
+
CollapsibleTrigger,
|
|
18
|
+
} from "../primitives/collapsible";
|
|
13
19
|
import { Tooltip } from "../primitives/tooltip";
|
|
14
20
|
import { WorkshopButton } from "../primitives/workshop-controls";
|
|
15
21
|
import { MarkdownMessage } from "./markdown-message";
|
|
16
|
-
import {
|
|
22
|
+
import { collapsePanel } from "./surfaces";
|
|
23
|
+
import {
|
|
24
|
+
DISCLOSURE_ICON,
|
|
25
|
+
DISCLOSURE_TITLE_WITH_ICON,
|
|
26
|
+
WorkIcon,
|
|
27
|
+
} from "./tool-rows";
|
|
17
28
|
import type {
|
|
18
29
|
ParallelTool,
|
|
19
30
|
PlanStep,
|
|
@@ -29,7 +40,7 @@ import {
|
|
|
29
40
|
/**
|
|
30
41
|
* UIMessage 协议里存在、gadgets 那套没有的几类 part —— 计划快照、原位提问、
|
|
31
42
|
* 后续建议、定时任务、并行/子 Agent、原位授权。视觉语言全部沿用 cloud-os:
|
|
32
|
-
* 折叠工作行用 `
|
|
43
|
+
* 折叠工作行用 `py-1` 的行式布局,卡片用
|
|
33
44
|
* `themed-surface-inset rounded-2xl border-kumo-line/70 bg-kumo-elevated/45 p-3`,
|
|
34
45
|
* 授权卡沿用 renderActionCard 的 blocking callout(brand/40 边 + brand/10 底)。
|
|
35
46
|
*/
|
|
@@ -39,73 +50,78 @@ import {
|
|
|
39
50
|
export function PlanBlock({
|
|
40
51
|
steps,
|
|
41
52
|
running,
|
|
53
|
+
defaultOpen = true,
|
|
42
54
|
}: {
|
|
43
55
|
steps: PlanStep[];
|
|
44
56
|
running: boolean;
|
|
57
|
+
defaultOpen?: boolean;
|
|
45
58
|
}) {
|
|
46
59
|
const done = steps.filter((step) => step.status === "done").length;
|
|
47
60
|
const current = steps.find((step) => step.status === "in_progress");
|
|
48
|
-
const [open, setOpen] = useState(
|
|
61
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
49
62
|
|
|
50
63
|
return (
|
|
51
|
-
<
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
onClick={() => setOpen((value) => !value)}
|
|
55
|
-
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]"
|
|
56
|
-
aria-expanded={open}
|
|
64
|
+
<Collapsible open={open} onOpenChange={setOpen} className="group">
|
|
65
|
+
<CollapsibleTrigger
|
|
66
|
+
className={`${DISCLOSURE_TITLE_WITH_ICON} w-full`}
|
|
57
67
|
>
|
|
58
|
-
<span
|
|
59
|
-
|
|
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" />
|
|
60
80
|
</span>
|
|
61
|
-
<span className="min-w-0 flex-1">
|
|
62
|
-
<span className=
|
|
63
|
-
|
|
64
|
-
Plan · {done}/{steps.length}
|
|
65
|
-
</span>
|
|
66
|
-
<CaretRight
|
|
67
|
-
size={13}
|
|
68
|
-
weight="bold"
|
|
69
|
-
className={`flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${open ? "rotate-90" : ""}`}
|
|
70
|
-
/>
|
|
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}
|
|
71
84
|
</span>
|
|
72
85
|
{!open && current && (
|
|
73
|
-
<span className="
|
|
86
|
+
<span className="min-w-0 flex-1 truncate text-[12px] leading-4 text-[rgba(0,0,0,0.3)]">
|
|
74
87
|
{current.text}
|
|
75
88
|
</span>
|
|
76
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
|
+
/>
|
|
77
94
|
</span>
|
|
78
|
-
</
|
|
79
|
-
{
|
|
80
|
-
<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">
|
|
81
98
|
{steps.map((step, index) => (
|
|
82
99
|
<div
|
|
83
100
|
key={`${step.text}-${index}`}
|
|
84
|
-
className="flex items-
|
|
101
|
+
className="flex items-center gap-2 text-[14px] leading-[1.4] font-normal tracking-normal"
|
|
85
102
|
>
|
|
86
103
|
<span
|
|
87
|
-
className="
|
|
104
|
+
className="flex size-4 shrink-0 items-center justify-center"
|
|
88
105
|
aria-hidden="true"
|
|
89
106
|
>
|
|
90
107
|
{step.status === "done" ? (
|
|
91
|
-
<
|
|
108
|
+
<CheckCircle size={16} weight="fill" className="text-[#00bf75]" />
|
|
92
109
|
) : step.status === "in_progress" ? (
|
|
93
110
|
<CircleNotch
|
|
94
|
-
size={
|
|
95
|
-
|
|
96
|
-
className="animate-spin text-kumo-brand motion-reduce:animate-none"
|
|
111
|
+
size={16}
|
|
112
|
+
className="animate-spin text-[#ff9938] motion-reduce:animate-none"
|
|
97
113
|
/>
|
|
98
114
|
) : (
|
|
99
|
-
<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" />
|
|
100
116
|
)}
|
|
101
117
|
</span>
|
|
102
118
|
<span
|
|
103
119
|
className={
|
|
104
120
|
step.status === "done"
|
|
105
|
-
? "min-w-0 flex-1 text-kumo-
|
|
121
|
+
? "min-w-0 flex-1 text-kumo-default/30 line-through"
|
|
106
122
|
: step.status === "in_progress"
|
|
107
|
-
? "min-w-0 flex-1
|
|
108
|
-
: "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"
|
|
109
125
|
}
|
|
110
126
|
>
|
|
111
127
|
{step.text}
|
|
@@ -113,8 +129,8 @@ export function PlanBlock({
|
|
|
113
129
|
</div>
|
|
114
130
|
))}
|
|
115
131
|
</div>
|
|
116
|
-
|
|
117
|
-
</
|
|
132
|
+
</CollapsibleContent>
|
|
133
|
+
</Collapsible>
|
|
118
134
|
);
|
|
119
135
|
}
|
|
120
136
|
|
|
@@ -124,33 +140,32 @@ interface AskQuestion {
|
|
|
124
140
|
prompt: string;
|
|
125
141
|
options: string[];
|
|
126
142
|
multi: boolean;
|
|
143
|
+
allowCustom: boolean;
|
|
127
144
|
}
|
|
128
145
|
|
|
129
146
|
function questionsOf(input: Record<string, unknown>): AskQuestion[] {
|
|
130
|
-
if (Array.isArray(input.questions))
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
return [];
|
|
147
|
+
if (!Array.isArray(input.questions)) return [];
|
|
148
|
+
return input.questions.map((item) => {
|
|
149
|
+
const question = (item ?? {}) as Record<string, unknown>;
|
|
150
|
+
const options = Array.isArray(question.options)
|
|
151
|
+
? question.options.filter((option): option is string =>
|
|
152
|
+
typeof option === "string"
|
|
153
|
+
)
|
|
154
|
+
: [];
|
|
155
|
+
return {
|
|
156
|
+
prompt: typeof question.question === "string"
|
|
157
|
+
? question.question
|
|
158
|
+
: "Choose an option",
|
|
159
|
+
options,
|
|
160
|
+
multi: question.multiSelect === true,
|
|
161
|
+
allowCustom: question.allowCustom === true || options.length === 0,
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface DraftAnswer {
|
|
167
|
+
selections: string[];
|
|
168
|
+
text: string;
|
|
154
169
|
}
|
|
155
170
|
|
|
156
171
|
export function AskUserBlock({
|
|
@@ -162,124 +177,171 @@ export function AskUserBlock({
|
|
|
162
177
|
onRespond?: (toolCallId: string, response: unknown) => Promise<boolean>;
|
|
163
178
|
}) {
|
|
164
179
|
const questions = questionsOf(call.input);
|
|
165
|
-
// 不能用 `useState(() => questions.map(
|
|
166
|
-
// 首帧 questions 往往还是空数组,那之后每次
|
|
180
|
+
// 不能用 `useState(() => questions.map(...))` 初始化:工具入参是**流式**到达的,
|
|
181
|
+
// 首帧 questions 往往还是空数组,那之后每次 setAnswers 都在空数组上 map,
|
|
167
182
|
// 选择永远存不进去 → Submit 永久禁用。改成稀疏存 + 按当前 questions 补齐。
|
|
168
|
-
const [
|
|
169
|
-
const
|
|
183
|
+
const [answers, setAnswers] = useState<DraftAnswer[]>([]);
|
|
184
|
+
const answerAt = (index: number): DraftAnswer =>
|
|
185
|
+
answers[index] ?? { selections: [], text: "" };
|
|
170
186
|
const toggle = (questionIndex: number, option: string, multi: boolean) =>
|
|
171
|
-
|
|
172
|
-
const next = questions.map((_, index) =>
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
187
|
+
setAnswers((current) => {
|
|
188
|
+
const next = questions.map((_, index) =>
|
|
189
|
+
current[index] ?? { selections: [], text: "" }
|
|
190
|
+
);
|
|
191
|
+
const answer = next[questionIndex]!;
|
|
192
|
+
next[questionIndex] = {
|
|
193
|
+
...answer,
|
|
194
|
+
selections: !multi
|
|
195
|
+
? [option]
|
|
196
|
+
: answer.selections.includes(option)
|
|
197
|
+
? answer.selections.filter((value) => value !== option)
|
|
198
|
+
: [...answer.selections, option],
|
|
199
|
+
};
|
|
200
|
+
return next;
|
|
201
|
+
});
|
|
202
|
+
const setText = (questionIndex: number, text: string) =>
|
|
203
|
+
setAnswers((current) => {
|
|
204
|
+
const next = questions.map((_, index) =>
|
|
205
|
+
current[index] ?? { selections: [], text: "" }
|
|
206
|
+
);
|
|
207
|
+
next[questionIndex] = { ...next[questionIndex]!, text };
|
|
179
208
|
return next;
|
|
180
209
|
});
|
|
181
210
|
// 在途只用于按钮转圈;权威状态永远是 part 的 state。
|
|
182
211
|
const [inFlight, setInFlight] = useState(false);
|
|
183
212
|
const outcome = askUserOutcome(call);
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
+
};
|
|
190
221
|
// 服务端已经结算 = 这张卡永久不可操作,不管结算成什么。
|
|
191
222
|
const resolved = outcome.kind !== "pending" || inFlight;
|
|
192
223
|
const valid = questions.length > 0 && questions.every(
|
|
193
|
-
(
|
|
194
|
-
|
|
224
|
+
(_question, index) =>
|
|
225
|
+
answerAt(index).selections.length > 0 || answerAt(index).text.trim(),
|
|
195
226
|
);
|
|
196
|
-
const buttonLabel =
|
|
197
|
-
|
|
198
|
-
: outcome.kind === "closed"
|
|
199
|
-
? "Closed"
|
|
200
|
-
: inFlight
|
|
201
|
-
? "Submitting…"
|
|
202
|
-
: "Submit";
|
|
203
|
-
const footnote = outcome.kind === "answered"
|
|
204
|
-
? answeredText && `Answered: ${answeredText}`
|
|
205
|
-
: outcome.kind === "closed"
|
|
227
|
+
const buttonLabel = inFlight ? "Submitting…" : "Submit";
|
|
228
|
+
const footnote = outcome.kind === "closed"
|
|
206
229
|
? outcome.reason === "user_replied_freeform"
|
|
207
230
|
? "Answered in the chat instead."
|
|
208
231
|
: "Closed without an answer."
|
|
209
232
|
: "";
|
|
210
233
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
text: "",
|
|
225
|
-
}).then((ok) => {
|
|
226
|
-
if (!ok) setInFlight(false);
|
|
227
|
-
});
|
|
228
|
-
}}
|
|
229
|
-
className="ml-auto !h-7 flex-shrink-0 gap-1 !rounded-lg px-2.5 text-[12px]"
|
|
230
|
-
>
|
|
231
|
-
{buttonLabel}
|
|
232
|
-
</WorkshopButton>
|
|
233
|
-
);
|
|
234
|
+
const submit = () => {
|
|
235
|
+
if (!onRespond) return;
|
|
236
|
+
setInFlight(true);
|
|
237
|
+
// 失败就把按钮放回可点:权威状态由 part 决定,这里只管别把用户锁死。
|
|
238
|
+
void onRespond(call.toolCallId, {
|
|
239
|
+
answers: questions.map((_, index) => ({
|
|
240
|
+
selections: answerAt(index).selections,
|
|
241
|
+
text: answerAt(index).text.trim(),
|
|
242
|
+
})),
|
|
243
|
+
}).then((ok) => {
|
|
244
|
+
if (!ok) setInFlight(false);
|
|
245
|
+
}, () => setInFlight(false));
|
|
246
|
+
};
|
|
234
247
|
|
|
235
248
|
return (
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
<
|
|
249
|
-
{question.prompt}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
249
|
+
<div
|
|
250
|
+
className="w-full"
|
|
251
|
+
data-part-type="ask_user"
|
|
252
|
+
data-state={outcome.kind === "answered" ? "submitted" : outcome.kind}
|
|
253
|
+
>
|
|
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}
|
|
260
307
|
disabled={resolved || !onRespond}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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()}
|
|
322
|
+
</p>
|
|
323
|
+
)}
|
|
272
324
|
</div>
|
|
273
|
-
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
+
)}
|
|
283
345
|
</div>
|
|
284
346
|
</div>
|
|
285
347
|
);
|
|
@@ -295,23 +357,26 @@ export function SuggestionsBlock({
|
|
|
295
357
|
onSuggestion: (text: string) => void;
|
|
296
358
|
}) {
|
|
297
359
|
return (
|
|
298
|
-
<div className="max-w-[860px]
|
|
299
|
-
|
|
300
|
-
<
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
360
|
+
<div className="flex max-w-[860px] flex-col items-start gap-3">
|
|
361
|
+
{items.map((item) => (
|
|
362
|
+
<button
|
|
363
|
+
key={item}
|
|
364
|
+
type="button"
|
|
365
|
+
onClick={() => onSuggestion(item)}
|
|
366
|
+
className="inline-flex cursor-pointer items-center gap-2 text-[14px] leading-[1.4] text-kumo-inactive transition-colors duration-150 ease-out hover:text-kumo-brand focus-visible:text-kumo-brand focus-visible:outline-none active:scale-[0.98]"
|
|
367
|
+
>
|
|
368
|
+
<span className="grid size-5 flex-shrink-0 place-items-center" aria-hidden="true">
|
|
369
|
+
<span className="rotate-180 -scale-y-100">
|
|
370
|
+
<img
|
|
371
|
+
src={new URL("../assets/followup-arrow.svg", import.meta.url).href}
|
|
372
|
+
alt=""
|
|
373
|
+
className="h-[8.33333px] w-[11.6667px]"
|
|
374
|
+
/>
|
|
375
|
+
</span>
|
|
376
|
+
</span>
|
|
377
|
+
<span className="whitespace-nowrap">{item}</span>
|
|
378
|
+
</button>
|
|
379
|
+
))}
|
|
315
380
|
</div>
|
|
316
381
|
);
|
|
317
382
|
}
|
|
@@ -356,7 +421,7 @@ function scheduleCadence(input: Record<string, unknown>): string {
|
|
|
356
421
|
return timezone ? `${cadence} · ${timezone}` : cadence;
|
|
357
422
|
}
|
|
358
423
|
|
|
359
|
-
/** `schedule` 工具的 details 是 `{ scheduled: true, id }`,id
|
|
424
|
+
/** `schedule` 工具的 details 是 `{ scheduled: true, id }`,id 即 Inbox Schedule Definition 主键。 */
|
|
360
425
|
function scheduleIdOf(output: unknown): string {
|
|
361
426
|
if (typeof output !== "object" || output === null) return "";
|
|
362
427
|
const id = (output as Record<string, unknown>).id;
|
|
@@ -382,7 +447,7 @@ export function ScheduleConfirmation({
|
|
|
382
447
|
return (
|
|
383
448
|
<section
|
|
384
449
|
data-slot="schedule-card"
|
|
385
|
-
aria-label={
|
|
450
|
+
aria-label={`Confirm scheduled task: ${name}`}
|
|
386
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)]"
|
|
387
452
|
>
|
|
388
453
|
<div className="flex items-center gap-2.5">
|
|
@@ -413,10 +478,10 @@ export function ScheduleConfirmation({
|
|
|
413
478
|
|
|
414
479
|
<div className="flex items-center justify-end gap-2">
|
|
415
480
|
<WorkshopButton disabled={disabled} onClick={onCancel}>
|
|
416
|
-
|
|
481
|
+
Cancel
|
|
417
482
|
</WorkshopButton>
|
|
418
483
|
<WorkshopButton tone="primary" disabled={disabled} onClick={onConfirm}>
|
|
419
|
-
{disabled ? "
|
|
484
|
+
{disabled ? "Confirming…" : "Confirm schedule"}
|
|
420
485
|
</WorkshopButton>
|
|
421
486
|
</div>
|
|
422
487
|
</section>
|
|
@@ -520,22 +585,22 @@ export function PermissionGrant({
|
|
|
520
585
|
"aria-label"?: string;
|
|
521
586
|
}) {
|
|
522
587
|
const actionClassName =
|
|
523
|
-
"inline-flex h-
|
|
588
|
+
"inline-flex h-10 w-[120px] items-center justify-center rounded-lg border border-kumo-line px-4 text-[14px] leading-[1.4] font-medium transition-[background-color,color,opacity,transform] duration-150 ease-out active:scale-[0.96] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kumo-ring/40 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100";
|
|
524
589
|
|
|
525
590
|
return (
|
|
526
591
|
<section
|
|
527
592
|
aria-label={ariaLabel}
|
|
528
|
-
className="themed-surface-inset w-full
|
|
593
|
+
className="themed-surface-inset w-full rounded-xl border border-kumo-line bg-kumo-elevated p-4 text-kumo-default"
|
|
529
594
|
>
|
|
530
|
-
<div className="flex items-center gap-2
|
|
595
|
+
<div className="flex items-center gap-2">
|
|
531
596
|
<span
|
|
532
|
-
className="flex size-
|
|
597
|
+
className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-kumo-fill text-kumo-subtle"
|
|
533
598
|
aria-hidden="true"
|
|
534
599
|
>
|
|
535
600
|
<Key size={14} weight="bold" />
|
|
536
601
|
</span>
|
|
537
602
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
538
|
-
<span className="truncate text-[
|
|
603
|
+
<span className="truncate text-[14px] leading-[1.4] font-medium">
|
|
539
604
|
{capability}
|
|
540
605
|
</span>
|
|
541
606
|
<span className="truncate text-[12px] leading-4 text-kumo-inactive">
|
|
@@ -544,41 +609,22 @@ export function PermissionGrant({
|
|
|
544
609
|
</div>
|
|
545
610
|
</div>
|
|
546
611
|
|
|
547
|
-
<div className="mt-
|
|
548
|
-
<span className="
|
|
612
|
+
<div className="mt-4 flex max-h-[200px] flex-col gap-2 overflow-y-auto">
|
|
613
|
+
<span className="text-[12px] leading-4 text-kumo-inactive">
|
|
549
614
|
this grants
|
|
550
615
|
</span>
|
|
551
616
|
{reach.map((item, index) => (
|
|
552
617
|
<span
|
|
553
618
|
key={`${index}:${item}`}
|
|
554
|
-
className="flex items-baseline gap-2 text-[
|
|
619
|
+
className="flex items-baseline gap-2 text-[14px] leading-[1.4] text-kumo-subtle"
|
|
555
620
|
>
|
|
556
|
-
<span
|
|
557
|
-
className="size-1 shrink-0 rounded-full bg-kumo-interact"
|
|
558
|
-
aria-hidden="true"
|
|
559
|
-
/>
|
|
621
|
+
<span aria-hidden="true">•</span>
|
|
560
622
|
<span className="min-w-0 break-words whitespace-pre-wrap">{item}</span>
|
|
561
623
|
</span>
|
|
562
624
|
))}
|
|
563
625
|
</div>
|
|
564
626
|
|
|
565
|
-
<div className="mt-
|
|
566
|
-
<button
|
|
567
|
-
type="button"
|
|
568
|
-
disabled={disabled}
|
|
569
|
-
onClick={() => onGrant("deny")}
|
|
570
|
-
className={`${actionClassName} text-kumo-subtle enabled:hover:bg-kumo-fill enabled:hover:text-kumo-default`}
|
|
571
|
-
>
|
|
572
|
-
Deny
|
|
573
|
-
</button>
|
|
574
|
-
<button
|
|
575
|
-
type="button"
|
|
576
|
-
disabled={disabled}
|
|
577
|
-
onClick={() => onGrant("allow_once")}
|
|
578
|
-
className={`${actionClassName} text-kumo-subtle enabled:hover:bg-kumo-fill enabled:hover:text-kumo-default`}
|
|
579
|
-
>
|
|
580
|
-
Allow once
|
|
581
|
-
</button>
|
|
627
|
+
<div className="mt-4 flex flex-wrap items-center gap-2">
|
|
582
628
|
<Tooltip
|
|
583
629
|
content="Allow every action at this execution level, without future prompts."
|
|
584
630
|
asChild
|
|
@@ -587,11 +633,27 @@ export function PermissionGrant({
|
|
|
587
633
|
type="button"
|
|
588
634
|
disabled={disabled}
|
|
589
635
|
onClick={() => onGrant("allow_level")}
|
|
590
|
-
className={`${actionClassName} bg-
|
|
636
|
+
className={`${actionClassName} border-0 bg-[linear-gradient(90deg,#ffd077_0%,#ff9938_100%)] text-kumo-inverse enabled:hover:brightness-95`}
|
|
591
637
|
>
|
|
592
638
|
Allow level
|
|
593
639
|
</button>
|
|
594
640
|
</Tooltip>
|
|
641
|
+
<button
|
|
642
|
+
type="button"
|
|
643
|
+
disabled={disabled}
|
|
644
|
+
onClick={() => onGrant("allow_once")}
|
|
645
|
+
className={`${actionClassName} text-kumo-subtle enabled:hover:bg-kumo-fill enabled:hover:text-kumo-default`}
|
|
646
|
+
>
|
|
647
|
+
Allow once
|
|
648
|
+
</button>
|
|
649
|
+
<button
|
|
650
|
+
type="button"
|
|
651
|
+
disabled={disabled}
|
|
652
|
+
onClick={() => onGrant("deny")}
|
|
653
|
+
className={`${actionClassName} text-kumo-subtle enabled:hover:bg-kumo-fill enabled:hover:text-kumo-default`}
|
|
654
|
+
>
|
|
655
|
+
Deny
|
|
656
|
+
</button>
|
|
595
657
|
</div>
|
|
596
658
|
</section>
|
|
597
659
|
);
|
|
@@ -659,15 +721,15 @@ export function ParallelBlock({
|
|
|
659
721
|
const done = tools.filter((tool) => tool.status === "done").length;
|
|
660
722
|
return (
|
|
661
723
|
<div className="max-w-[860px]">
|
|
662
|
-
<div className="flex items-
|
|
663
|
-
<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}>
|
|
664
726
|
<WorkIcon Icon={ListChecks} />
|
|
665
727
|
</span>
|
|
666
728
|
<span className="min-w-0 truncate">
|
|
667
729
|
{label} · {done}/{tools.length}
|
|
668
730
|
</span>
|
|
669
731
|
</div>
|
|
670
|
-
<div className="
|
|
732
|
+
<div className="mt-1 space-y-1">
|
|
671
733
|
{tools.map((tool, index) => (
|
|
672
734
|
<div
|
|
673
735
|
key={`${tool.label}-${index}`}
|
|
@@ -690,15 +752,15 @@ export function ParallelBlock({
|
|
|
690
752
|
export function SubAgentsBlock({ agents }: { agents: SubAgentView[] }) {
|
|
691
753
|
return (
|
|
692
754
|
<div className="max-w-[860px]">
|
|
693
|
-
<div className="flex items-
|
|
694
|
-
<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}>
|
|
695
757
|
<WorkIcon Icon={UsersThree} />
|
|
696
758
|
</span>
|
|
697
759
|
<span className="min-w-0 truncate">
|
|
698
760
|
{agents.length === 1 ? "Sub-agent" : `${agents.length} sub-agents`}
|
|
699
761
|
</span>
|
|
700
762
|
</div>
|
|
701
|
-
<div className="
|
|
763
|
+
<div className="mt-1 space-y-1">
|
|
702
764
|
{agents.map((agent, index) => (
|
|
703
765
|
<div
|
|
704
766
|
key={`${agent.name}-${index}`}
|
|
@@ -736,28 +798,24 @@ export function ErrorBlock({
|
|
|
736
798
|
const [expanded, setExpanded] = useState(false);
|
|
737
799
|
return (
|
|
738
800
|
<div className="group/work max-w-[860px] text-[14px] leading-5 tracking-[-0.25px] text-kumo-subtle">
|
|
739
|
-
<div className="flex w-full items-
|
|
801
|
+
<div className="flex w-full items-start gap-2">
|
|
740
802
|
<button
|
|
741
803
|
type="button"
|
|
742
804
|
onClick={() => setExpanded((value) => !value)}
|
|
743
|
-
className=
|
|
805
|
+
className={`${DISCLOSURE_TITLE_WITH_ICON} flex-1`}
|
|
744
806
|
aria-expanded={expanded}
|
|
745
807
|
>
|
|
746
808
|
<span
|
|
747
|
-
className=
|
|
809
|
+
className={`${DISCLOSURE_ICON} text-kumo-danger`}
|
|
748
810
|
aria-hidden="true"
|
|
749
811
|
>
|
|
750
|
-
<WarningCircle size={
|
|
812
|
+
<WarningCircle size={15} weight="fill" />
|
|
751
813
|
</span>
|
|
752
814
|
<span className="flex min-w-0 flex-1 items-center gap-1">
|
|
753
|
-
<span className="min-w-0 truncate">
|
|
754
|
-
<span className="font-medium text-kumo-danger">{title}: </span>
|
|
755
|
-
<span className="text-kumo-subtle">{body}</span>
|
|
756
|
-
</span>
|
|
815
|
+
<span className="min-w-0 truncate text-kumo-danger">{title}</span>
|
|
757
816
|
{body && (
|
|
758
817
|
<CaretRight
|
|
759
|
-
size={
|
|
760
|
-
weight="bold"
|
|
818
|
+
size={16}
|
|
761
819
|
className={`flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${expanded ? "rotate-90" : ""}`}
|
|
762
820
|
/>
|
|
763
821
|
)}
|
|
@@ -776,7 +834,7 @@ export function ErrorBlock({
|
|
|
776
834
|
)}
|
|
777
835
|
</div>
|
|
778
836
|
{expanded && body && (
|
|
779
|
-
<div className="
|
|
837
|
+
<div className="mt-1">
|
|
780
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">
|
|
781
839
|
{body}
|
|
782
840
|
</pre>
|