@springbrand/message-panel 0.1.3-alpha.2 → 0.1.3-alpha.21
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/capability-chip.tsx +35 -0
- package/cloud-os/chat/activity-indicator.tsx +29 -0
- package/cloud-os/chat/cloud-os-chat-messages.tsx +123 -69
- package/cloud-os/chat/markdown-message.tsx +82 -40
- package/cloud-os/chat/rich-blocks.tsx +494 -190
- package/cloud-os/chat/tool-presentation.ts +67 -6
- package/cloud-os/chat/tool-rows.tsx +87 -43
- package/cloud-os/chat/transcript-model.ts +215 -23
- package/cloud-os/composer/cloud-os-chat-input.tsx +242 -127
- package/cloud-os/composer/cloud-os-model-select.tsx +105 -0
- package/cloud-os/file-view.tsx +266 -0
- package/cloud-os/index.ts +29 -2
- package/cloud-os/layout/cloud-os-workspace-split.tsx +107 -24
- package/cloud-os/primitives/workshop-controls.tsx +2 -2
- package/cloud-os/styles/cloud-os.css +93 -19
- package/demo/camel-chat-showcase.tsx +21 -13
- package/demo/chat-scenarios.ts +100 -40
- package/demo/cloud-os-chat-showcase.tsx +51 -17
- 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 +82 -77
- package/src/camel/camel-prompt-input.tsx +2 -1
- package/src/camel/camel-tool-presentation.tsx +19 -15
- package/src/camel/camel-turn.ts +1 -17
- package/src/chat-summary-panel.tsx +1 -1
- package/src/composer/chat-composer.tsx +2 -1
- package/src/composer/composer-trigger-popover.tsx +1 -1
- package/src/composer/composer.tsx +2 -1
- package/src/composer/index.ts +1 -0
- package/src/composer/key-rules.ts +12 -0
- package/src/contracts.ts +0 -2
- package/src/message-panel.tsx +0 -23
- package/src/parts/index.tsx +102 -63
- package/src/styles/index.css +4 -1
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// camel 是 dev-only 的展示变体(App.tsx 的 `/__camel-chat` 路由),没有接生产宿主。
|
|
2
|
+
// 它的交互卡片仍用旧的 `onAnswer(payload)` 回调,**没有**迁到 client-settled tool 的
|
|
3
|
+
// `onRespond(toolCallId, response)` —— 生产聊天面板走的是 cloud-os,已经迁完。
|
|
4
|
+
// 哪天 camel 要上生产,这里必须一起改,否则卡片点了不会把答案回写给那次工具调用。
|
|
5
|
+
|
|
1
6
|
import type { UIMessage } from "ai";
|
|
2
7
|
import {
|
|
3
8
|
ArrowDownIcon,
|
|
@@ -663,53 +668,30 @@ function CamelSchedule({
|
|
|
663
668
|
|
|
664
669
|
function CamelAskUser({
|
|
665
670
|
part,
|
|
666
|
-
submittedAnswer,
|
|
667
671
|
onAnswer,
|
|
668
672
|
}: {
|
|
669
673
|
part: unknown;
|
|
670
|
-
submittedAnswer?: string;
|
|
671
674
|
onAnswer: (payload: unknown) => void;
|
|
672
675
|
}) {
|
|
673
676
|
const record = recordOf(part);
|
|
674
677
|
const data = dataOf(part);
|
|
675
|
-
const output = record.output;
|
|
676
678
|
const questions = Array.isArray(data.questions)
|
|
677
679
|
? data.questions.map(recordOf)
|
|
678
|
-
:
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const [text, setText] = useState("");
|
|
693
|
-
const [submitted, setSubmitted] = useState(false);
|
|
694
|
-
const outputRecord = recordOf(output);
|
|
695
|
-
const externalAnswer =
|
|
696
|
-
submittedAnswer?.trim() ||
|
|
697
|
-
(typeof output === "string"
|
|
698
|
-
? output.trim()
|
|
699
|
-
: typeof outputRecord.text === "string"
|
|
700
|
-
? outputRecord.text.trim()
|
|
701
|
-
: "");
|
|
702
|
-
const externalSelections = new Set(
|
|
703
|
-
externalAnswer
|
|
704
|
-
.split("、")
|
|
705
|
-
.map((value) => value.trim())
|
|
706
|
-
.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()
|
|
707
694
|
);
|
|
708
|
-
const resolvedSubmitted = submitted || Boolean(externalAnswer);
|
|
709
|
-
const valid = questions.every((question, index) => {
|
|
710
|
-
const options = Array.isArray(question.options) ? question.options : [];
|
|
711
|
-
return options.length === 0 || (selections[index]?.length ?? 0) > 0;
|
|
712
|
-
});
|
|
713
695
|
|
|
714
696
|
return (
|
|
715
697
|
<section
|
|
@@ -718,21 +700,28 @@ function CamelAskUser({
|
|
|
718
700
|
data-state={resolvedSubmitted ? "submitted" : "idle"}
|
|
719
701
|
>
|
|
720
702
|
{questions.map((question, questionIndex) => {
|
|
721
|
-
const prompt = String(question.
|
|
703
|
+
const prompt = String(question.question ?? "Choose an option");
|
|
722
704
|
const options = Array.isArray(question.options)
|
|
723
705
|
? question.options.filter(
|
|
724
706
|
(option): option is string => typeof option === "string",
|
|
725
707
|
)
|
|
726
708
|
: [];
|
|
727
|
-
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
|
+
: [];
|
|
728
717
|
return (
|
|
729
718
|
<div className="space-y-2" key={`${prompt}:${questionIndex}`}>
|
|
730
719
|
<p className="text-sm text-foreground">{prompt}</p>
|
|
731
720
|
<div className="flex flex-wrap gap-2">
|
|
732
721
|
{options.map((option) => {
|
|
733
|
-
const selected =
|
|
734
|
-
?
|
|
735
|
-
: selections
|
|
722
|
+
const selected = settledAnswer
|
|
723
|
+
? settledSelections.includes(option)
|
|
724
|
+
: answerAt(questionIndex).selections.includes(option);
|
|
736
725
|
return (
|
|
737
726
|
<Button
|
|
738
727
|
key={option}
|
|
@@ -741,15 +730,21 @@ function CamelAskUser({
|
|
|
741
730
|
variant={selected ? "default" : "outline"}
|
|
742
731
|
disabled={resolvedSubmitted}
|
|
743
732
|
onClick={() =>
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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
|
+
})
|
|
753
748
|
}
|
|
754
749
|
>
|
|
755
750
|
{option}
|
|
@@ -757,34 +752,54 @@ function CamelAskUser({
|
|
|
757
752
|
);
|
|
758
753
|
})}
|
|
759
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
|
+
)}
|
|
760
785
|
</div>
|
|
761
786
|
);
|
|
762
787
|
})}
|
|
763
|
-
{typeof data.freeTextPlaceholder === "string" && (
|
|
764
|
-
<input
|
|
765
|
-
value={text}
|
|
766
|
-
disabled={resolvedSubmitted}
|
|
767
|
-
onChange={(event) => setText(event.currentTarget.value)}
|
|
768
|
-
placeholder={data.freeTextPlaceholder}
|
|
769
|
-
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"
|
|
770
|
-
/>
|
|
771
|
-
)}
|
|
772
788
|
<Button
|
|
773
789
|
type="button"
|
|
774
790
|
size="sm"
|
|
775
791
|
disabled={resolvedSubmitted || !valid}
|
|
776
792
|
onClick={() => {
|
|
777
|
-
onAnswer({
|
|
778
|
-
|
|
793
|
+
onAnswer({
|
|
794
|
+
answers: questions.map((_, index) => ({
|
|
795
|
+
selections: answerAt(index).selections,
|
|
796
|
+
text: answerAt(index).text.trim(),
|
|
797
|
+
})),
|
|
798
|
+
});
|
|
779
799
|
}}
|
|
780
800
|
>
|
|
781
801
|
{resolvedSubmitted ? "Submitted" : "Submit"}
|
|
782
802
|
</Button>
|
|
783
|
-
{externalAnswer && (
|
|
784
|
-
<p className="text-xs text-muted-foreground">
|
|
785
|
-
已回答:{externalAnswer}
|
|
786
|
-
</p>
|
|
787
|
-
)}
|
|
788
803
|
</section>
|
|
789
804
|
);
|
|
790
805
|
}
|
|
@@ -795,7 +810,6 @@ function CamelRichPart({
|
|
|
795
810
|
onApprove,
|
|
796
811
|
onSuggestion,
|
|
797
812
|
onAnswer,
|
|
798
|
-
replyText,
|
|
799
813
|
getToolApproval,
|
|
800
814
|
}: {
|
|
801
815
|
part: unknown;
|
|
@@ -803,7 +817,6 @@ function CamelRichPart({
|
|
|
803
817
|
onApprove: (approvalId: string, decision: ToolApprovalDecision) => void;
|
|
804
818
|
onSuggestion: (text: string) => void;
|
|
805
819
|
onAnswer: (payload: unknown) => void;
|
|
806
|
-
replyText?: string;
|
|
807
820
|
getToolApproval?: (part: unknown) => ToolApprovalView | undefined;
|
|
808
821
|
}) {
|
|
809
822
|
const kind = normalizedPartType(part);
|
|
@@ -819,7 +832,6 @@ function CamelRichPart({
|
|
|
819
832
|
return (
|
|
820
833
|
<CamelAskUser
|
|
821
834
|
part={part}
|
|
822
|
-
submittedAnswer={replyText}
|
|
823
835
|
onAnswer={onAnswer}
|
|
824
836
|
/>
|
|
825
837
|
);
|
|
@@ -919,7 +931,6 @@ function AssistantPart({
|
|
|
919
931
|
onApprove,
|
|
920
932
|
onSuggestion,
|
|
921
933
|
onAnswer,
|
|
922
|
-
replyText,
|
|
923
934
|
getToolApproval,
|
|
924
935
|
resolveUrl,
|
|
925
936
|
}: {
|
|
@@ -928,7 +939,6 @@ function AssistantPart({
|
|
|
928
939
|
onApprove: (approvalId: string, decision: ToolApprovalDecision) => void;
|
|
929
940
|
onSuggestion: (text: string) => void;
|
|
930
941
|
onAnswer: (payload: unknown) => void;
|
|
931
|
-
replyText?: string;
|
|
932
942
|
getToolApproval?: (part: unknown) => ToolApprovalView | undefined;
|
|
933
943
|
resolveUrl?: MessageUrlResolver;
|
|
934
944
|
}) {
|
|
@@ -955,7 +965,6 @@ function AssistantPart({
|
|
|
955
965
|
onApprove={onApprove}
|
|
956
966
|
onSuggestion={onSuggestion}
|
|
957
967
|
onAnswer={onAnswer}
|
|
958
|
-
replyText={replyText}
|
|
959
968
|
getToolApproval={getToolApproval}
|
|
960
969
|
/>
|
|
961
970
|
);
|
|
@@ -1132,7 +1141,6 @@ function RenderAssistantPart({
|
|
|
1132
1141
|
onApprove,
|
|
1133
1142
|
onSuggestion,
|
|
1134
1143
|
onAnswer,
|
|
1135
|
-
replyText,
|
|
1136
1144
|
getToolApproval,
|
|
1137
1145
|
resolveUrl,
|
|
1138
1146
|
}: {
|
|
@@ -1141,7 +1149,6 @@ function RenderAssistantPart({
|
|
|
1141
1149
|
onApprove: (approvalId: string, decision: ToolApprovalDecision) => void;
|
|
1142
1150
|
onSuggestion: (text: string) => void;
|
|
1143
1151
|
onAnswer: (payload: unknown) => void;
|
|
1144
|
-
replyText?: string;
|
|
1145
1152
|
getToolApproval?: (part: unknown) => ToolApprovalView | undefined;
|
|
1146
1153
|
resolveUrl?: MessageUrlResolver;
|
|
1147
1154
|
}) {
|
|
@@ -1155,7 +1162,6 @@ function RenderAssistantPart({
|
|
|
1155
1162
|
onApprove={onApprove}
|
|
1156
1163
|
onSuggestion={onSuggestion}
|
|
1157
1164
|
onAnswer={onAnswer}
|
|
1158
|
-
replyText={replyText}
|
|
1159
1165
|
getToolApproval={getToolApproval}
|
|
1160
1166
|
resolveUrl={resolveUrl}
|
|
1161
1167
|
/>
|
|
@@ -1195,7 +1201,6 @@ function AssistantTurn({
|
|
|
1195
1201
|
onApprove={onApprove}
|
|
1196
1202
|
onSuggestion={onSuggestion}
|
|
1197
1203
|
onAnswer={onAnswer}
|
|
1198
|
-
replyText={turn.replyText}
|
|
1199
1204
|
getToolApproval={getToolApproval}
|
|
1200
1205
|
resolveUrl={resolveUrl}
|
|
1201
1206
|
/>
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
type ReactNode,
|
|
40
40
|
} from "react";
|
|
41
41
|
import { ComposerTriggerPopover } from "../composer/composer-trigger-popover";
|
|
42
|
+
import { isComposingKeyEvent } from "../composer/key-rules";
|
|
42
43
|
import type { ComposerCommand } from "../composer/types";
|
|
43
44
|
import { cn } from "../internal/cn";
|
|
44
45
|
|
|
@@ -345,7 +346,7 @@ function CamelPromptInputContent({
|
|
|
345
346
|
if (
|
|
346
347
|
event.key === "Enter" &&
|
|
347
348
|
!event.shiftKey &&
|
|
348
|
-
!event.nativeEvent
|
|
349
|
+
!isComposingKeyEvent(event.nativeEvent)
|
|
349
350
|
) {
|
|
350
351
|
event.preventDefault();
|
|
351
352
|
submit();
|
|
@@ -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) {
|
|
@@ -103,7 +103,7 @@ export function ChatSummaryPanel({
|
|
|
103
103
|
return (
|
|
104
104
|
<aside
|
|
105
105
|
className={cn(
|
|
106
|
-
"w-[19rem] min-w-0 max-w-
|
|
106
|
+
"absolute right-3 top-11 z-30 w-[19rem] min-w-0 max-w-[calc(100%-1.5rem)] overflow-hidden rounded-2xl border border-border bg-popover shadow-card",
|
|
107
107
|
"animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-200 motion-reduce:animate-none",
|
|
108
108
|
className,
|
|
109
109
|
)}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type KeyboardEvent,
|
|
11
11
|
} from "react";
|
|
12
12
|
import { cn } from "../internal/cn";
|
|
13
|
+
import { isComposingKeyEvent } from "./key-rules";
|
|
13
14
|
import { Composer } from "./composer";
|
|
14
15
|
import type {
|
|
15
16
|
ChatComposerProps,
|
|
@@ -152,7 +153,7 @@ function ChatComposerInner<TAttachment>(
|
|
|
152
153
|
if (
|
|
153
154
|
(event.key === "Enter" &&
|
|
154
155
|
!event.shiftKey &&
|
|
155
|
-
!event.nativeEvent
|
|
156
|
+
!isComposingKeyEvent(event.nativeEvent)) ||
|
|
156
157
|
event.key === "Tab"
|
|
157
158
|
) {
|
|
158
159
|
const command = filtered[active] ?? filtered[0];
|
|
@@ -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
|
)}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
Attachments,
|
|
27
27
|
} from "./composer-attachments";
|
|
28
28
|
import { useComposerDrop } from "./drop-controller";
|
|
29
|
+
import { isComposingKeyEvent } from "./key-rules";
|
|
29
30
|
import type {
|
|
30
31
|
ComposerAttachmentView,
|
|
31
32
|
ComposerProps,
|
|
@@ -247,7 +248,7 @@ function ComposerInner<TAttachment>(
|
|
|
247
248
|
if (
|
|
248
249
|
event.key === "Enter" &&
|
|
249
250
|
!event.shiftKey &&
|
|
250
|
-
!event.nativeEvent
|
|
251
|
+
!isComposingKeyEvent(event.nativeEvent)
|
|
251
252
|
) {
|
|
252
253
|
event.preventDefault();
|
|
253
254
|
if (canSend) void submit();
|
package/src/composer/index.ts
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether Enter belongs to an input method rather than to the Composer.
|
|
3
|
+
*
|
|
4
|
+
* Both signals are honoured on their own: some Chinese input methods report only the
|
|
5
|
+
* legacy `keyCode === 229`, and either one alone means a word is still being composed.
|
|
6
|
+
*/
|
|
7
|
+
export function isComposingKeyEvent(event: {
|
|
8
|
+
isComposing?: boolean;
|
|
9
|
+
keyCode?: number;
|
|
10
|
+
}): boolean {
|
|
11
|
+
return event.isComposing === true || event.keyCode === 229;
|
|
12
|
+
}
|
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 && (
|