@springbrand/message-panel 0.1.3-alpha.3 → 0.1.3-alpha.30
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 +6 -6
- 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-spark.svg +9 -0
- package/cloud-os/capability-chip.tsx +35 -0
- package/cloud-os/chat/activity-indicator.tsx +2 -2
- package/cloud-os/chat/cloud-os-chat-messages.tsx +374 -63
- 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/rich-blocks.tsx +548 -246
- package/cloud-os/chat/tool-presentation.ts +35 -16
- package/cloud-os/chat/tool-rows.tsx +330 -85
- package/cloud-os/chat/transcript-model.ts +428 -56
- package/cloud-os/composer/cloud-os-chat-input.tsx +228 -142
- package/cloud-os/composer/cloud-os-model-select.tsx +105 -0
- package/cloud-os/file-view.tsx +441 -0
- package/cloud-os/index.ts +35 -3
- package/cloud-os/layout/cloud-os-workspace-split.tsx +58 -15
- package/cloud-os/primitives/workshop-controls.tsx +2 -2
- package/cloud-os/styles/cloud-os.css +100 -20
- package/demo/camel-chat-showcase.tsx +21 -13
- package/demo/chat-scenarios.ts +223 -43
- package/demo/cloud-os-chat-showcase.tsx +47 -14
- package/demo/fixtures.ts +4 -5
- package/demo/message-panel-gallery.tsx +16 -2
- package/package.json +3 -4
- 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-trigger-popover.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
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
CloudOsRoot,
|
|
14
14
|
CloudOsWorkspacePanel,
|
|
15
15
|
CloudOsWorkspaceSplit,
|
|
16
|
+
ScheduleConfirmation,
|
|
16
17
|
WorkshopIconButton,
|
|
17
18
|
buildCloudOsEntries,
|
|
18
19
|
type CloudOsAttachmentView,
|
|
@@ -20,17 +21,19 @@ import {
|
|
|
20
21
|
} from "../cloud-os";
|
|
21
22
|
import {
|
|
22
23
|
defaultPrompt,
|
|
24
|
+
demoAskUserResponse,
|
|
23
25
|
demoModels,
|
|
24
26
|
flowSteps,
|
|
25
27
|
messagesForScenario,
|
|
26
28
|
scenarios,
|
|
27
29
|
type DemoScenario,
|
|
30
|
+
type DemoAskUserResponse,
|
|
28
31
|
type DemoScenarioMeta,
|
|
29
32
|
} from "./chat-scenarios";
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
35
|
* cloud-os 版快速预览。场景表和 camel-chat-showcase 共用 `chat-scenarios.ts`,
|
|
33
|
-
* 所以两边的状态逐条对齐(同样
|
|
36
|
+
* 所以两边的状态逐条对齐(同样 23 个场景、同样的 7 步流程条、同样的初始 URL 参数)。
|
|
34
37
|
* 差别只在外壳:这里是 cloudflare-os 的左聊天 / 右工作区双栏。
|
|
35
38
|
*/
|
|
36
39
|
|
|
@@ -122,7 +125,7 @@ export function CloudOsChatShowcase() {
|
|
|
122
125
|
const [mode, setMode] = useState<CloudOsMode>("light");
|
|
123
126
|
const [scenario, setScenario] = useState<DemoScenario>(initialScenario);
|
|
124
127
|
const [prompt, setPrompt] = useState(defaultPrompt);
|
|
125
|
-
const [humanAnswer, setHumanAnswer] = useState(
|
|
128
|
+
const [humanAnswer, setHumanAnswer] = useState(demoAskUserResponse);
|
|
126
129
|
const [model, setModel] = useState<string | null>(cloudOsModels[0].id);
|
|
127
130
|
const [value, setValue] = useState("");
|
|
128
131
|
const [attachments, setAttachments] = useState<CloudOsAttachmentView[]>([]);
|
|
@@ -153,7 +156,8 @@ export function CloudOsChatShowcase() {
|
|
|
153
156
|
? (part as { type?: unknown; state?: unknown })
|
|
154
157
|
: {};
|
|
155
158
|
return (record.type === "tool-write_file" ||
|
|
156
|
-
record.type === "tool-cancel_schedule"
|
|
159
|
+
record.type === "tool-cancel_schedule" ||
|
|
160
|
+
record.type === "tool-schedule") &&
|
|
157
161
|
record.state === "approval-requested"
|
|
158
162
|
? "cloud-os-demo-approval"
|
|
159
163
|
: undefined;
|
|
@@ -283,7 +287,7 @@ export function CloudOsChatShowcase() {
|
|
|
283
287
|
|
|
284
288
|
<CloudOsWorkspaceSplit
|
|
285
289
|
workspaceOpen={workspaceOpen}
|
|
286
|
-
|
|
290
|
+
isLoading={scenario === "hydrating"}
|
|
287
291
|
chat={
|
|
288
292
|
<div className="flex h-full min-h-0 flex-col bg-kumo-base">
|
|
289
293
|
<aside
|
|
@@ -348,11 +352,40 @@ export function CloudOsChatShowcase() {
|
|
|
348
352
|
<CloudOsChatMessages
|
|
349
353
|
messages={messages}
|
|
350
354
|
status={status}
|
|
351
|
-
|
|
355
|
+
turnActive={
|
|
356
|
+
status === "submitted" ||
|
|
357
|
+
status === "streaming" ||
|
|
358
|
+
scenario === "ask" ||
|
|
359
|
+
scenario === "schedule_create" ||
|
|
360
|
+
((scenario === "approval" || scenario === "schedule_cancel") &&
|
|
361
|
+
approval === undefined)
|
|
362
|
+
}
|
|
352
363
|
isRecovering={scenario === "recovering"}
|
|
353
|
-
recoveryStatusLabel="
|
|
364
|
+
recoveryStatusLabel="Model response interrupted. Retrying…"
|
|
365
|
+
awaitingApproval={
|
|
366
|
+
(scenario === "approval" ||
|
|
367
|
+
scenario === "schedule_cancel" ||
|
|
368
|
+
scenario === "schedule_create") &&
|
|
369
|
+
approval === undefined
|
|
370
|
+
}
|
|
354
371
|
showThinkingTraces={showThinkingTraces}
|
|
355
372
|
approvalIdOf={approvalIdOf}
|
|
373
|
+
renderApproval={({ toolName, input }) =>
|
|
374
|
+
toolName === "schedule"
|
|
375
|
+
? (
|
|
376
|
+
<ScheduleConfirmation
|
|
377
|
+
input={input}
|
|
378
|
+
onCancel={() => {
|
|
379
|
+
selectScenario("submitted");
|
|
380
|
+
setLastEvent("Schedule creation cancelled");
|
|
381
|
+
}}
|
|
382
|
+
onConfirm={() => {
|
|
383
|
+
selectScenario("schedule");
|
|
384
|
+
setLastEvent("Scheduled task created");
|
|
385
|
+
}}
|
|
386
|
+
/>
|
|
387
|
+
)
|
|
388
|
+
: undefined}
|
|
356
389
|
error={statusError}
|
|
357
390
|
constrainWidth={!workspaceOpen}
|
|
358
391
|
onRetry={() => {
|
|
@@ -379,17 +412,17 @@ export function CloudOsChatShowcase() {
|
|
|
379
412
|
onRespond={async (_toolCallId, response) => {
|
|
380
413
|
const data =
|
|
381
414
|
response && typeof response === "object"
|
|
382
|
-
? (response as
|
|
383
|
-
: {};
|
|
384
|
-
const answer = [
|
|
385
|
-
...
|
|
386
|
-
|
|
387
|
-
]
|
|
415
|
+
? (response as DemoAskUserResponse)
|
|
416
|
+
: { answers: [] };
|
|
417
|
+
const answer = data.answers.flatMap((item) => [
|
|
418
|
+
...item.selections,
|
|
419
|
+
item.text?.trim() ?? "",
|
|
420
|
+
])
|
|
388
421
|
.filter(Boolean)
|
|
389
422
|
.join(" / ");
|
|
390
|
-
if (
|
|
423
|
+
if (data.answers.length > 0) setHumanAnswer(data);
|
|
391
424
|
selectScenario("planning");
|
|
392
|
-
setLastEvent(`已回答并继续:${answer
|
|
425
|
+
setLastEvent(`已回答并继续:${answer}`);
|
|
393
426
|
return true;
|
|
394
427
|
}}
|
|
395
428
|
/>
|
package/demo/fixtures.ts
CHANGED
|
@@ -166,17 +166,16 @@ export const allPartsMessage: DemoMessage = {
|
|
|
166
166
|
type: "ask_user",
|
|
167
167
|
questions: [
|
|
168
168
|
{
|
|
169
|
-
|
|
170
|
-
kind: "single",
|
|
169
|
+
question: "报告通过什么渠道发送?",
|
|
171
170
|
options: ["Email", "Slack", "Both"],
|
|
172
171
|
},
|
|
173
172
|
{
|
|
174
|
-
|
|
175
|
-
kind: "multi",
|
|
173
|
+
question: "需要包含哪些内容?",
|
|
176
174
|
options: ["定价", "功能", "动态"],
|
|
175
|
+
multiSelect: true,
|
|
176
|
+
allowCustom: true,
|
|
177
177
|
},
|
|
178
178
|
],
|
|
179
|
-
freeTextPlaceholder: "补充竞争对手名称…",
|
|
180
179
|
},
|
|
181
180
|
{
|
|
182
181
|
type: "suggestions",
|
|
@@ -220,6 +220,7 @@ function ComposerInteractionHarness() {
|
|
|
220
220
|
|
|
221
221
|
export function MessagePanelGallery() {
|
|
222
222
|
const [theme, setTheme] = useState<"light" | "dark">("dark");
|
|
223
|
+
const [askUserOutput, setAskUserOutput] = useState<unknown>();
|
|
223
224
|
const rootClass = theme === "dark" ? "dark" : "";
|
|
224
225
|
useEffect(() => {
|
|
225
226
|
document.documentElement.classList.toggle("dark", theme === "dark");
|
|
@@ -309,12 +310,25 @@ export function MessagePanelGallery() {
|
|
|
309
310
|
/>
|
|
310
311
|
<div style={{ height: 1100, overflow: "hidden", border: "1px solid var(--border)", borderRadius: 18 }}>
|
|
311
312
|
<MessagePanel
|
|
312
|
-
messages={[
|
|
313
|
+
messages={[
|
|
314
|
+
basicMessages[0],
|
|
315
|
+
{
|
|
316
|
+
...allPartsMessage,
|
|
317
|
+
parts: allPartsMessage.parts.map((part) =>
|
|
318
|
+
part.type === "ask_user" && askUserOutput
|
|
319
|
+
? { ...part, output: askUserOutput }
|
|
320
|
+
: part
|
|
321
|
+
),
|
|
322
|
+
},
|
|
323
|
+
]}
|
|
313
324
|
status="ready"
|
|
314
325
|
adapter={demoMessageAdapter}
|
|
315
326
|
actions={{
|
|
316
327
|
onSuggestion: noOp,
|
|
317
|
-
onAnswer:
|
|
328
|
+
onAnswer: async (output) => {
|
|
329
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
330
|
+
setAskUserOutput(output);
|
|
331
|
+
},
|
|
318
332
|
onApprove: noOp,
|
|
319
333
|
onPreview: noOp,
|
|
320
334
|
onDownload: noOp,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springbrand/message-panel",
|
|
3
|
-
"version": "0.1.3-alpha.
|
|
3
|
+
"version": "0.1.3-alpha.30",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -31,11 +31,10 @@
|
|
|
31
31
|
"@streamdown/math": "^1.0.2",
|
|
32
32
|
"@streamdown/mermaid": "^1.0.2",
|
|
33
33
|
"lucide-react": "^1.24.0",
|
|
34
|
+
"motion": "^13.1.0",
|
|
34
35
|
"radix-ui": "^1.6.2",
|
|
35
|
-
"react-markdown": "^10.1.0",
|
|
36
|
-
"remark-gfm": "^4.0.1",
|
|
37
36
|
"streamdown": "^2.5.0",
|
|
38
|
-
"thinking-orbs": "0.
|
|
37
|
+
"thinking-orbs": "0.2.0",
|
|
39
38
|
"use-stick-to-bottom": "^1.1.6"
|
|
40
39
|
},
|
|
41
40
|
"devDependencies": {
|
|
@@ -167,7 +167,7 @@ export function CamelChatError({
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
export function CamelChatRecovery({
|
|
170
|
-
label = "
|
|
170
|
+
label = "The model response was interrupted. Retrying…",
|
|
171
171
|
}: {
|
|
172
172
|
label?: string;
|
|
173
173
|
}) {
|
|
@@ -668,53 +668,30 @@ function CamelSchedule({
|
|
|
668
668
|
|
|
669
669
|
function CamelAskUser({
|
|
670
670
|
part,
|
|
671
|
-
submittedAnswer,
|
|
672
671
|
onAnswer,
|
|
673
672
|
}: {
|
|
674
673
|
part: unknown;
|
|
675
|
-
submittedAnswer?: string;
|
|
676
674
|
onAnswer: (payload: unknown) => void;
|
|
677
675
|
}) {
|
|
678
676
|
const record = recordOf(part);
|
|
679
677
|
const data = dataOf(part);
|
|
680
|
-
const output = record.output;
|
|
681
678
|
const questions = Array.isArray(data.questions)
|
|
682
679
|
? data.questions.map(recordOf)
|
|
683
|
-
:
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const [text, setText] = useState("");
|
|
698
|
-
const [submitted, setSubmitted] = useState(false);
|
|
699
|
-
const outputRecord = recordOf(output);
|
|
700
|
-
const externalAnswer =
|
|
701
|
-
submittedAnswer?.trim() ||
|
|
702
|
-
(typeof output === "string"
|
|
703
|
-
? output.trim()
|
|
704
|
-
: typeof outputRecord.text === "string"
|
|
705
|
-
? outputRecord.text.trim()
|
|
706
|
-
: "");
|
|
707
|
-
const externalSelections = new Set(
|
|
708
|
-
externalAnswer
|
|
709
|
-
.split("、")
|
|
710
|
-
.map((value) => value.trim())
|
|
711
|
-
.filter(Boolean),
|
|
680
|
+
: [];
|
|
681
|
+
const output = recordOf(record.output);
|
|
682
|
+
const settledAnswers = Array.isArray(output.answers)
|
|
683
|
+
? output.answers.map(recordOf)
|
|
684
|
+
: [];
|
|
685
|
+
const [answers, setAnswers] = useState<Array<{
|
|
686
|
+
selections: string[];
|
|
687
|
+
text: string;
|
|
688
|
+
}>>([]);
|
|
689
|
+
const answerAt = (index: number) =>
|
|
690
|
+
answers[index] ?? { selections: [], text: "" };
|
|
691
|
+
const resolvedSubmitted = settledAnswers.length > 0;
|
|
692
|
+
const valid = questions.length > 0 && questions.every((_, index) =>
|
|
693
|
+
answerAt(index).selections.length > 0 || answerAt(index).text.trim()
|
|
712
694
|
);
|
|
713
|
-
const resolvedSubmitted = submitted || Boolean(externalAnswer);
|
|
714
|
-
const valid = questions.every((question, index) => {
|
|
715
|
-
const options = Array.isArray(question.options) ? question.options : [];
|
|
716
|
-
return options.length === 0 || (selections[index]?.length ?? 0) > 0;
|
|
717
|
-
});
|
|
718
695
|
|
|
719
696
|
return (
|
|
720
697
|
<section
|
|
@@ -723,21 +700,28 @@ function CamelAskUser({
|
|
|
723
700
|
data-state={resolvedSubmitted ? "submitted" : "idle"}
|
|
724
701
|
>
|
|
725
702
|
{questions.map((question, questionIndex) => {
|
|
726
|
-
const prompt = String(question.
|
|
703
|
+
const prompt = String(question.question ?? "Choose an option");
|
|
727
704
|
const options = Array.isArray(question.options)
|
|
728
705
|
? question.options.filter(
|
|
729
706
|
(option): option is string => typeof option === "string",
|
|
730
707
|
)
|
|
731
708
|
: [];
|
|
732
|
-
const multi = question.
|
|
709
|
+
const multi = question.multiSelect === true;
|
|
710
|
+
const allowCustom = question.allowCustom === true || options.length === 0;
|
|
711
|
+
const settledAnswer = settledAnswers[questionIndex];
|
|
712
|
+
const settledSelections = Array.isArray(settledAnswer?.selections)
|
|
713
|
+
? settledAnswer.selections.filter(
|
|
714
|
+
(value): value is string => typeof value === "string",
|
|
715
|
+
)
|
|
716
|
+
: [];
|
|
733
717
|
return (
|
|
734
718
|
<div className="space-y-2" key={`${prompt}:${questionIndex}`}>
|
|
735
719
|
<p className="text-sm text-foreground">{prompt}</p>
|
|
736
720
|
<div className="flex flex-wrap gap-2">
|
|
737
721
|
{options.map((option) => {
|
|
738
|
-
const selected =
|
|
739
|
-
?
|
|
740
|
-
: selections
|
|
722
|
+
const selected = settledAnswer
|
|
723
|
+
? settledSelections.includes(option)
|
|
724
|
+
: answerAt(questionIndex).selections.includes(option);
|
|
741
725
|
return (
|
|
742
726
|
<Button
|
|
743
727
|
key={option}
|
|
@@ -746,15 +730,21 @@ function CamelAskUser({
|
|
|
746
730
|
variant={selected ? "default" : "outline"}
|
|
747
731
|
disabled={resolvedSubmitted}
|
|
748
732
|
onClick={() =>
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
733
|
+
setAnswers((current) => {
|
|
734
|
+
const next = questions.map((_, index) =>
|
|
735
|
+
current[index] ?? { selections: [], text: "" }
|
|
736
|
+
);
|
|
737
|
+
const answer = next[questionIndex]!;
|
|
738
|
+
next[questionIndex] = {
|
|
739
|
+
...answer,
|
|
740
|
+
selections: !multi
|
|
741
|
+
? [option]
|
|
742
|
+
: answer.selections.includes(option)
|
|
743
|
+
? answer.selections.filter((value) => value !== option)
|
|
744
|
+
: [...answer.selections, option],
|
|
745
|
+
};
|
|
746
|
+
return next;
|
|
747
|
+
})
|
|
758
748
|
}
|
|
759
749
|
>
|
|
760
750
|
{option}
|
|
@@ -762,34 +752,54 @@ function CamelAskUser({
|
|
|
762
752
|
);
|
|
763
753
|
})}
|
|
764
754
|
</div>
|
|
755
|
+
{!resolvedSubmitted && allowCustom && (
|
|
756
|
+
<input
|
|
757
|
+
value={answerAt(questionIndex).text}
|
|
758
|
+
maxLength={2_000}
|
|
759
|
+
aria-label={`Custom answer: ${prompt}`}
|
|
760
|
+
onChange={(event) =>
|
|
761
|
+
setAnswers((current) => {
|
|
762
|
+
const next = questions.map((_, index) =>
|
|
763
|
+
current[index] ?? { selections: [], text: "" }
|
|
764
|
+
);
|
|
765
|
+
next[questionIndex] = {
|
|
766
|
+
...next[questionIndex]!,
|
|
767
|
+
text: event.currentTarget.value,
|
|
768
|
+
};
|
|
769
|
+
return next;
|
|
770
|
+
})}
|
|
771
|
+
placeholder={options.length > 0 ? "Other" : "Type your answer"}
|
|
772
|
+
className="h-8 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring focus:ring-2 focus:ring-ring/30"
|
|
773
|
+
/>
|
|
774
|
+
)}
|
|
775
|
+
{settledAnswer && (
|
|
776
|
+
<p className="text-xs text-muted-foreground">
|
|
777
|
+
Answered: {[
|
|
778
|
+
...settledSelections,
|
|
779
|
+
...(typeof settledAnswer.text === "string" && settledAnswer.text.trim()
|
|
780
|
+
? [settledAnswer.text.trim()]
|
|
781
|
+
: []),
|
|
782
|
+
].join(" / ")}
|
|
783
|
+
</p>
|
|
784
|
+
)}
|
|
765
785
|
</div>
|
|
766
786
|
);
|
|
767
787
|
})}
|
|
768
|
-
{typeof data.freeTextPlaceholder === "string" && (
|
|
769
|
-
<input
|
|
770
|
-
value={text}
|
|
771
|
-
disabled={resolvedSubmitted}
|
|
772
|
-
onChange={(event) => setText(event.currentTarget.value)}
|
|
773
|
-
placeholder={data.freeTextPlaceholder}
|
|
774
|
-
className="h-8 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring focus:ring-2 focus:ring-ring/30"
|
|
775
|
-
/>
|
|
776
|
-
)}
|
|
777
788
|
<Button
|
|
778
789
|
type="button"
|
|
779
790
|
size="sm"
|
|
780
791
|
disabled={resolvedSubmitted || !valid}
|
|
781
792
|
onClick={() => {
|
|
782
|
-
onAnswer({
|
|
783
|
-
|
|
793
|
+
onAnswer({
|
|
794
|
+
answers: questions.map((_, index) => ({
|
|
795
|
+
selections: answerAt(index).selections,
|
|
796
|
+
text: answerAt(index).text.trim(),
|
|
797
|
+
})),
|
|
798
|
+
});
|
|
784
799
|
}}
|
|
785
800
|
>
|
|
786
801
|
{resolvedSubmitted ? "Submitted" : "Submit"}
|
|
787
802
|
</Button>
|
|
788
|
-
{externalAnswer && (
|
|
789
|
-
<p className="text-xs text-muted-foreground">
|
|
790
|
-
已回答:{externalAnswer}
|
|
791
|
-
</p>
|
|
792
|
-
)}
|
|
793
803
|
</section>
|
|
794
804
|
);
|
|
795
805
|
}
|
|
@@ -800,7 +810,6 @@ function CamelRichPart({
|
|
|
800
810
|
onApprove,
|
|
801
811
|
onSuggestion,
|
|
802
812
|
onAnswer,
|
|
803
|
-
replyText,
|
|
804
813
|
getToolApproval,
|
|
805
814
|
}: {
|
|
806
815
|
part: unknown;
|
|
@@ -808,7 +817,6 @@ function CamelRichPart({
|
|
|
808
817
|
onApprove: (approvalId: string, decision: ToolApprovalDecision) => void;
|
|
809
818
|
onSuggestion: (text: string) => void;
|
|
810
819
|
onAnswer: (payload: unknown) => void;
|
|
811
|
-
replyText?: string;
|
|
812
820
|
getToolApproval?: (part: unknown) => ToolApprovalView | undefined;
|
|
813
821
|
}) {
|
|
814
822
|
const kind = normalizedPartType(part);
|
|
@@ -824,7 +832,6 @@ function CamelRichPart({
|
|
|
824
832
|
return (
|
|
825
833
|
<CamelAskUser
|
|
826
834
|
part={part}
|
|
827
|
-
submittedAnswer={replyText}
|
|
828
835
|
onAnswer={onAnswer}
|
|
829
836
|
/>
|
|
830
837
|
);
|
|
@@ -924,7 +931,6 @@ function AssistantPart({
|
|
|
924
931
|
onApprove,
|
|
925
932
|
onSuggestion,
|
|
926
933
|
onAnswer,
|
|
927
|
-
replyText,
|
|
928
934
|
getToolApproval,
|
|
929
935
|
resolveUrl,
|
|
930
936
|
}: {
|
|
@@ -933,7 +939,6 @@ function AssistantPart({
|
|
|
933
939
|
onApprove: (approvalId: string, decision: ToolApprovalDecision) => void;
|
|
934
940
|
onSuggestion: (text: string) => void;
|
|
935
941
|
onAnswer: (payload: unknown) => void;
|
|
936
|
-
replyText?: string;
|
|
937
942
|
getToolApproval?: (part: unknown) => ToolApprovalView | undefined;
|
|
938
943
|
resolveUrl?: MessageUrlResolver;
|
|
939
944
|
}) {
|
|
@@ -960,7 +965,6 @@ function AssistantPart({
|
|
|
960
965
|
onApprove={onApprove}
|
|
961
966
|
onSuggestion={onSuggestion}
|
|
962
967
|
onAnswer={onAnswer}
|
|
963
|
-
replyText={replyText}
|
|
964
968
|
getToolApproval={getToolApproval}
|
|
965
969
|
/>
|
|
966
970
|
);
|
|
@@ -1137,7 +1141,6 @@ function RenderAssistantPart({
|
|
|
1137
1141
|
onApprove,
|
|
1138
1142
|
onSuggestion,
|
|
1139
1143
|
onAnswer,
|
|
1140
|
-
replyText,
|
|
1141
1144
|
getToolApproval,
|
|
1142
1145
|
resolveUrl,
|
|
1143
1146
|
}: {
|
|
@@ -1146,7 +1149,6 @@ function RenderAssistantPart({
|
|
|
1146
1149
|
onApprove: (approvalId: string, decision: ToolApprovalDecision) => void;
|
|
1147
1150
|
onSuggestion: (text: string) => void;
|
|
1148
1151
|
onAnswer: (payload: unknown) => void;
|
|
1149
|
-
replyText?: string;
|
|
1150
1152
|
getToolApproval?: (part: unknown) => ToolApprovalView | undefined;
|
|
1151
1153
|
resolveUrl?: MessageUrlResolver;
|
|
1152
1154
|
}) {
|
|
@@ -1160,7 +1162,6 @@ function RenderAssistantPart({
|
|
|
1160
1162
|
onApprove={onApprove}
|
|
1161
1163
|
onSuggestion={onSuggestion}
|
|
1162
1164
|
onAnswer={onAnswer}
|
|
1163
|
-
replyText={replyText}
|
|
1164
1165
|
getToolApproval={getToolApproval}
|
|
1165
1166
|
resolveUrl={resolveUrl}
|
|
1166
1167
|
/>
|
|
@@ -1200,7 +1201,6 @@ function AssistantTurn({
|
|
|
1200
1201
|
onApprove={onApprove}
|
|
1201
1202
|
onSuggestion={onSuggestion}
|
|
1202
1203
|
onAnswer={onAnswer}
|
|
1203
|
-
replyText={turn.replyText}
|
|
1204
1204
|
getToolApproval={getToolApproval}
|
|
1205
1205
|
resolveUrl={resolveUrl}
|
|
1206
1206
|
/>
|
|
@@ -77,6 +77,12 @@ function stringField(record: UnknownRecord, ...keys: string[]): string {
|
|
|
77
77
|
return "";
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function stringArray(value: unknown): string[] {
|
|
81
|
+
return Array.isArray(value)
|
|
82
|
+
? value.filter((item): item is string => typeof item === "string")
|
|
83
|
+
: [];
|
|
84
|
+
}
|
|
85
|
+
|
|
80
86
|
function numberField(record: UnknownRecord, ...keys: string[]): number | undefined {
|
|
81
87
|
for (const key of keys) {
|
|
82
88
|
const value = record[key];
|
|
@@ -342,17 +348,7 @@ function fileSummary(
|
|
|
342
348
|
}
|
|
343
349
|
|
|
344
350
|
function questionsOf(input: UnknownRecord): UnknownRecord[] {
|
|
345
|
-
|
|
346
|
-
if (typeof input.question === "string") {
|
|
347
|
-
return [
|
|
348
|
-
{
|
|
349
|
-
question: input.question,
|
|
350
|
-
header: input.header,
|
|
351
|
-
options: input.options,
|
|
352
|
-
},
|
|
353
|
-
];
|
|
354
|
-
}
|
|
355
|
-
return [];
|
|
351
|
+
return Array.isArray(input.questions) ? input.questions.map(recordOf) : [];
|
|
356
352
|
}
|
|
357
353
|
|
|
358
354
|
function summaryFor(
|
|
@@ -387,7 +383,7 @@ function summaryFor(
|
|
|
387
383
|
complete: "Edited",
|
|
388
384
|
});
|
|
389
385
|
case "bash": {
|
|
390
|
-
const command = stringField(input, "command", "cmd");
|
|
386
|
+
const command = stringField(input, "script", "command", "cmd");
|
|
391
387
|
const description = stringField(input, "description");
|
|
392
388
|
const label = description || truncate(command || "command", 30);
|
|
393
389
|
return running
|
|
@@ -822,7 +818,7 @@ function EditDetails({ presentation }: CamelToolDetailsProps) {
|
|
|
822
818
|
|
|
823
819
|
function BashDetails({ presentation }: CamelToolDetailsProps) {
|
|
824
820
|
const { input, output, outputText } = presentation;
|
|
825
|
-
const command = stringField(input, "command", "cmd");
|
|
821
|
+
const command = stringField(input, "script", "command", "cmd");
|
|
826
822
|
const outputRecord = recordOf(output);
|
|
827
823
|
const parsedFromText = (() => {
|
|
828
824
|
if (!outputText.trim().startsWith("{")) return {};
|
|
@@ -1113,7 +1109,10 @@ function SkillDetails({
|
|
|
1113
1109
|
|
|
1114
1110
|
function AskUserDetails({ presentation }: CamelToolDetailsProps) {
|
|
1115
1111
|
const questions = questionsOf(presentation.input);
|
|
1116
|
-
const
|
|
1112
|
+
const output = recordOf(presentation.output);
|
|
1113
|
+
const answers = Array.isArray(output.answers)
|
|
1114
|
+
? output.answers.map(recordOf)
|
|
1115
|
+
: [];
|
|
1117
1116
|
return (
|
|
1118
1117
|
<div className="space-y-2">
|
|
1119
1118
|
{questions.length > 0 ? (
|
|
@@ -1123,7 +1122,12 @@ function AskUserDetails({ presentation }: CamelToolDetailsProps) {
|
|
|
1123
1122
|
{stringField(question, "question", "prompt") || "Question"}
|
|
1124
1123
|
</p>
|
|
1125
1124
|
<p className="text-muted-foreground/60">
|
|
1126
|
-
{
|
|
1125
|
+
{[
|
|
1126
|
+
...stringArray(answers[index]?.selections),
|
|
1127
|
+
...(typeof answers[index]?.text === "string" && answers[index].text
|
|
1128
|
+
? [answers[index].text]
|
|
1129
|
+
: []),
|
|
1130
|
+
].join(" / ") || "—"}
|
|
1127
1131
|
</p>
|
|
1128
1132
|
</div>
|
|
1129
1133
|
))
|
package/src/camel/camel-turn.ts
CHANGED
|
@@ -41,7 +41,6 @@ export interface CamelAssistantTurn {
|
|
|
41
41
|
};
|
|
42
42
|
stepCount: number;
|
|
43
43
|
copyText: string;
|
|
44
|
-
replyText?: string;
|
|
45
44
|
durationMs?: number;
|
|
46
45
|
completedAt?: number;
|
|
47
46
|
}
|
|
@@ -115,10 +114,6 @@ function standaloneKind(message: UIMessage): CamelStandaloneKind | null {
|
|
|
115
114
|
return message.role === "user" ? "user" : null;
|
|
116
115
|
}
|
|
117
116
|
|
|
118
|
-
function isDirectUserMessage(message: UIMessage): boolean {
|
|
119
|
-
return message.role === "user" && standaloneKind(message) === "user";
|
|
120
|
-
}
|
|
121
|
-
|
|
122
117
|
export function camelMessageText(message: UIMessage): string {
|
|
123
118
|
return message.parts
|
|
124
119
|
.filter(isVisibleText)
|
|
@@ -228,7 +223,6 @@ function turnDuration(message: UIMessage): number | undefined {
|
|
|
228
223
|
export function projectCamelAssistantTurn(
|
|
229
224
|
message: UIMessage,
|
|
230
225
|
finalToolNames: readonly string[] = [],
|
|
231
|
-
replyText?: string,
|
|
232
226
|
): CamelAssistantTurn {
|
|
233
227
|
const parts = message.parts.map((part, partIndex) => ({
|
|
234
228
|
messageId: message.id,
|
|
@@ -294,7 +288,6 @@ export function projectCamelAssistantTurn(
|
|
|
294
288
|
parts.filter(({ part }) => part.type === "step-start").length,
|
|
295
289
|
),
|
|
296
290
|
copyText: camelMessageText(message),
|
|
297
|
-
replyText,
|
|
298
291
|
durationMs: turnDuration(message),
|
|
299
292
|
completedAt: camelCompletedAt(message),
|
|
300
293
|
};
|
|
@@ -309,16 +302,7 @@ export function projectCamelMessages(
|
|
|
309
302
|
if (message.role === "system") return;
|
|
310
303
|
const displayKind = standaloneKind(message);
|
|
311
304
|
if (message.role === "assistant" && displayKind === null) {
|
|
312
|
-
|
|
313
|
-
.slice(index + 1)
|
|
314
|
-
.find(isDirectUserMessage);
|
|
315
|
-
items.push(
|
|
316
|
-
projectCamelAssistantTurn(
|
|
317
|
-
message,
|
|
318
|
-
finalToolNames,
|
|
319
|
-
reply ? camelMessageText(reply).trim() || undefined : undefined,
|
|
320
|
-
),
|
|
321
|
-
);
|
|
305
|
+
items.push(projectCamelAssistantTurn(message, finalToolNames));
|
|
322
306
|
return;
|
|
323
307
|
}
|
|
324
308
|
if (displayKind !== null) {
|
|
@@ -59,7 +59,7 @@ export const AttachmentPreview = forwardRef<
|
|
|
59
59
|
attachment.status === "uploading"
|
|
60
60
|
? "上传中…"
|
|
61
61
|
: attachment.status === "error"
|
|
62
|
-
? "
|
|
62
|
+
? "Upload failed"
|
|
63
63
|
: [attachment.note, formatSize(attachment.size)].filter(Boolean).join(" · ");
|
|
64
64
|
return (
|
|
65
65
|
<div
|
|
@@ -156,7 +156,7 @@ function Items({
|
|
|
156
156
|
{item.label}
|
|
157
157
|
</span>
|
|
158
158
|
{item.description && (
|
|
159
|
-
<span className="ms-5.5 text-xs leading-tight text-muted-foreground">
|
|
159
|
+
<span className="ms-5.5 w-full truncate text-xs leading-tight text-muted-foreground">
|
|
160
160
|
{item.description}
|
|
161
161
|
</span>
|
|
162
162
|
)}
|
|
@@ -211,10 +211,10 @@ function ComposerInner<TAttachment>(
|
|
|
211
211
|
);
|
|
212
212
|
const invalid = selected.filter((file) => !valid.includes(file));
|
|
213
213
|
if (all.length > selected.length) {
|
|
214
|
-
onValidationError?.(
|
|
214
|
+
onValidationError?.(`You can add up to ${maxFiles ?? 1} attachments`, all);
|
|
215
215
|
}
|
|
216
216
|
if (invalid.length) {
|
|
217
|
-
onValidationError?.("
|
|
217
|
+
onValidationError?.("Some files are unsupported or exceed the size limit", invalid);
|
|
218
218
|
}
|
|
219
219
|
if (valid.length) void onFilesAdded?.(valid);
|
|
220
220
|
},
|
package/src/contracts.ts
CHANGED
|
@@ -47,8 +47,6 @@ export interface PartRendererContext {
|
|
|
47
47
|
activityId: string;
|
|
48
48
|
isActive: boolean;
|
|
49
49
|
isLatestOfKind: boolean;
|
|
50
|
-
/** First user message after the assistant message that owns this part. */
|
|
51
|
-
replyText?: string;
|
|
52
50
|
approval?: ToolApprovalView;
|
|
53
51
|
status: MessagePanelStatus;
|
|
54
52
|
actions: MessagePanelActions;
|