@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
|
@@ -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
|
|
@@ -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;
|
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}
|
|
@@ -299,7 +277,7 @@ function MessagePanelInner<T>(
|
|
|
299
277
|
? slots.error(error)
|
|
300
278
|
: slots.error ?? (
|
|
301
279
|
<PanelStatus kind="error">
|
|
302
|
-
{error instanceof Error ? error.message : "
|
|
280
|
+
{error instanceof Error ? error.message : "This turn failed. Please try again."}
|
|
303
281
|
</PanelStatus>
|
|
304
282
|
))}
|
|
305
283
|
<Conversation>
|
|
@@ -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 && (
|