@springbrand/message-panel 0.1.3-alpha.10 → 0.1.3-alpha.12
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 -2
- package/cloud-os/chat/cloud-os-chat-messages.tsx +22 -14
- package/cloud-os/chat/rich-blocks.tsx +265 -108
- package/cloud-os/chat/tool-presentation.ts +26 -10
- package/cloud-os/styles/cloud-os.css +32 -0
- package/demo/camel-chat-showcase.tsx +11 -12
- package/demo/chat-scenarios.ts +48 -31
- package/demo/cloud-os-chat-showcase.tsx +11 -9
- package/demo/fixtures.ts +4 -5
- package/demo/message-panel-gallery.tsx +16 -2
- package/package.json +1 -1
- package/src/camel/camel-chat-messages.tsx +77 -77
- package/src/camel/camel-tool-presentation.tsx +17 -13
- package/src/camel/camel-turn.ts +1 -17
- package/src/contracts.ts +0 -2
- package/src/message-panel.tsx +0 -23
- package/src/parts/index.tsx +102 -63
package/demo/chat-scenarios.ts
CHANGED
|
@@ -239,35 +239,54 @@ function userMessage(prompt: string): UIMessage {
|
|
|
239
239
|
} as UIMessage;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
input: {
|
|
251
|
-
question: "这份建议优先面向哪个读者?",
|
|
252
|
-
options: ["产品团队", "管理层", "设计团队"],
|
|
253
|
-
multiSelect: false,
|
|
254
|
-
},
|
|
255
|
-
output: { asked: true },
|
|
256
|
-
},
|
|
242
|
+
export interface DemoAskUserResponse {
|
|
243
|
+
answers: Array<{ selections: string[]; text?: string }>;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export const demoAskUserResponse: DemoAskUserResponse = {
|
|
247
|
+
answers: [
|
|
248
|
+
{ selections: ["管理层"], text: "" },
|
|
249
|
+
{ selections: ["报告"], text: "" },
|
|
257
250
|
],
|
|
258
|
-
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const askUserQuestions = [
|
|
254
|
+
{
|
|
255
|
+
question: "这份建议优先面向哪个读者?",
|
|
256
|
+
options: ["产品团队", "管理层", "设计团队"],
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
question: "需要哪些交付物?",
|
|
260
|
+
options: ["报告", "演示文稿", "执行清单"],
|
|
261
|
+
multiSelect: true,
|
|
262
|
+
allowCustom: true,
|
|
263
|
+
},
|
|
264
|
+
];
|
|
259
265
|
|
|
260
|
-
function
|
|
266
|
+
function askUserRequest(response?: DemoAskUserResponse): UIMessage {
|
|
261
267
|
return {
|
|
262
|
-
id: "camel-demo-
|
|
263
|
-
role: "
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
268
|
+
id: "camel-demo-ask-user",
|
|
269
|
+
role: "assistant",
|
|
270
|
+
parts: [
|
|
271
|
+
{
|
|
272
|
+
type: "tool-ask_user",
|
|
273
|
+
toolCallId: "camel-demo-ask-user-1",
|
|
274
|
+
state: response ? "output-available" : "input-available",
|
|
275
|
+
input: { questions: askUserQuestions },
|
|
276
|
+
...(response
|
|
277
|
+
? {
|
|
278
|
+
output: {
|
|
279
|
+
answers: response.answers.map((answer, index) => ({
|
|
280
|
+
question: askUserQuestions[index]!.question,
|
|
281
|
+
selections: answer.selections,
|
|
282
|
+
text: answer.text?.trim() ?? "",
|
|
283
|
+
})),
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
: {}),
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
} as unknown as UIMessage;
|
|
271
290
|
}
|
|
272
291
|
|
|
273
292
|
const planningWork = {
|
|
@@ -827,7 +846,7 @@ const failedWork = {
|
|
|
827
846
|
export function messagesForScenario(
|
|
828
847
|
scenario: DemoScenario,
|
|
829
848
|
prompt: string,
|
|
830
|
-
humanAnswer:
|
|
849
|
+
humanAnswer: DemoAskUserResponse,
|
|
831
850
|
approval: boolean | undefined,
|
|
832
851
|
): UIMessage[] {
|
|
833
852
|
if (scenario === "empty") return [];
|
|
@@ -835,15 +854,13 @@ export function messagesForScenario(
|
|
|
835
854
|
return [noticeMessage(scenario), ...commandMessages];
|
|
836
855
|
}
|
|
837
856
|
const conversation = [noticeMessage(scenario), userMessage(prompt)];
|
|
838
|
-
const answer = humanAnswer || "管理层";
|
|
839
857
|
const decidedConversation = [
|
|
840
858
|
...conversation,
|
|
841
|
-
askUserRequest,
|
|
842
|
-
humanReplyMessage(answer),
|
|
859
|
+
askUserRequest(humanAnswer),
|
|
843
860
|
];
|
|
844
861
|
if (scenario === "hydrating") return conversation;
|
|
845
862
|
if (scenario === "submitted") return conversation;
|
|
846
|
-
if (scenario === "ask") return [...conversation, askUserRequest];
|
|
863
|
+
if (scenario === "ask") return [...conversation, askUserRequest()];
|
|
847
864
|
if (scenario === "planning") {
|
|
848
865
|
return [...decidedConversation, planningWork];
|
|
849
866
|
}
|
|
@@ -20,11 +20,13 @@ import {
|
|
|
20
20
|
} from "../cloud-os";
|
|
21
21
|
import {
|
|
22
22
|
defaultPrompt,
|
|
23
|
+
demoAskUserResponse,
|
|
23
24
|
demoModels,
|
|
24
25
|
flowSteps,
|
|
25
26
|
messagesForScenario,
|
|
26
27
|
scenarios,
|
|
27
28
|
type DemoScenario,
|
|
29
|
+
type DemoAskUserResponse,
|
|
28
30
|
type DemoScenarioMeta,
|
|
29
31
|
} from "./chat-scenarios";
|
|
30
32
|
|
|
@@ -122,7 +124,7 @@ export function CloudOsChatShowcase() {
|
|
|
122
124
|
const [mode, setMode] = useState<CloudOsMode>("light");
|
|
123
125
|
const [scenario, setScenario] = useState<DemoScenario>(initialScenario);
|
|
124
126
|
const [prompt, setPrompt] = useState(defaultPrompt);
|
|
125
|
-
const [humanAnswer, setHumanAnswer] = useState(
|
|
127
|
+
const [humanAnswer, setHumanAnswer] = useState(demoAskUserResponse);
|
|
126
128
|
const [model, setModel] = useState<string | null>(cloudOsModels[0].id);
|
|
127
129
|
const [value, setValue] = useState("");
|
|
128
130
|
const [attachments, setAttachments] = useState<CloudOsAttachmentView[]>([]);
|
|
@@ -389,17 +391,17 @@ export function CloudOsChatShowcase() {
|
|
|
389
391
|
onRespond={async (_toolCallId, response) => {
|
|
390
392
|
const data =
|
|
391
393
|
response && typeof response === "object"
|
|
392
|
-
? (response as
|
|
393
|
-
: {};
|
|
394
|
-
const answer = [
|
|
395
|
-
...
|
|
396
|
-
|
|
397
|
-
]
|
|
394
|
+
? (response as DemoAskUserResponse)
|
|
395
|
+
: { answers: [] };
|
|
396
|
+
const answer = data.answers.flatMap((item) => [
|
|
397
|
+
...item.selections,
|
|
398
|
+
item.text?.trim() ?? "",
|
|
399
|
+
])
|
|
398
400
|
.filter(Boolean)
|
|
399
401
|
.join(" / ");
|
|
400
|
-
if (
|
|
402
|
+
if (data.answers.length > 0) setHumanAnswer(data);
|
|
401
403
|
selectScenario("planning");
|
|
402
|
-
setLastEvent(`已回答并继续:${answer
|
|
404
|
+
setLastEvent(`已回答并继续:${answer}`);
|
|
403
405
|
return true;
|
|
404
406
|
}}
|
|
405
407
|
/>
|
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
|
@@ -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(
|
|
@@ -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) {
|
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;
|
package/src/message-panel.tsx
CHANGED
|
@@ -93,7 +93,6 @@ interface MessageRowProps<T> {
|
|
|
93
93
|
slots: MessagePanelSlots;
|
|
94
94
|
status: MessagePanelProps<T>["status"];
|
|
95
95
|
latestActivityByKind: ReadonlyMap<string, string>;
|
|
96
|
-
replyText?: string;
|
|
97
96
|
}
|
|
98
97
|
|
|
99
98
|
function MessageRow<T>({
|
|
@@ -107,7 +106,6 @@ function MessageRow<T>({
|
|
|
107
106
|
slots,
|
|
108
107
|
status,
|
|
109
108
|
latestActivityByKind,
|
|
110
|
-
replyText,
|
|
111
109
|
}: MessageRowProps<T>) {
|
|
112
110
|
const role = roleOf(adapter, message);
|
|
113
111
|
const parts = adapter.getParts(message);
|
|
@@ -186,7 +184,6 @@ function MessageRow<T>({
|
|
|
186
184
|
isLatestOfKind:
|
|
187
185
|
key === undefined ||
|
|
188
186
|
latestActivityByKind.get(key) === activityId,
|
|
189
|
-
replyText,
|
|
190
187
|
approval: adapter.getToolApproval?.(part),
|
|
191
188
|
status,
|
|
192
189
|
actions,
|
|
@@ -255,25 +252,6 @@ function MessagePanelInner<T>(
|
|
|
255
252
|
});
|
|
256
253
|
return latest;
|
|
257
254
|
}, [adapter, ids, messages]);
|
|
258
|
-
const replyTextByMessageId = useMemo(() => {
|
|
259
|
-
const replies = new Map<string, string>();
|
|
260
|
-
messages.forEach((message, messageIndex) => {
|
|
261
|
-
if (roleOf(adapter, message) !== "assistant") return;
|
|
262
|
-
for (
|
|
263
|
-
let replyIndex = messageIndex + 1;
|
|
264
|
-
replyIndex < messages.length;
|
|
265
|
-
replyIndex += 1
|
|
266
|
-
) {
|
|
267
|
-
const reply = messages[replyIndex];
|
|
268
|
-
if (roleOf(adapter, reply) !== "user") continue;
|
|
269
|
-
const text = messageText(adapter, reply).trim();
|
|
270
|
-
if (text) replies.set(ids[messageIndex], text);
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
return replies;
|
|
275
|
-
}, [adapter, ids, messages]);
|
|
276
|
-
|
|
277
255
|
return (
|
|
278
256
|
<div
|
|
279
257
|
ref={ref}
|
|
@@ -332,7 +310,6 @@ function MessagePanelInner<T>(
|
|
|
332
310
|
slots={slots}
|
|
333
311
|
status={status}
|
|
334
312
|
latestActivityByKind={latestActivityByKind}
|
|
335
|
-
replyText={replyTextByMessageId.get(ids[index])}
|
|
336
313
|
/>
|
|
337
314
|
))}
|
|
338
315
|
{showPending && (
|