@springbrand/message-panel 0.1.3-alpha.2 → 0.1.3-alpha.22
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 +130 -70
- package/cloud-os/chat/markdown-message.tsx +85 -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
package/src/parts/index.tsx
CHANGED
|
@@ -833,59 +833,67 @@ export function DashboardPart(data: DashboardData) {
|
|
|
833
833
|
}
|
|
834
834
|
|
|
835
835
|
export interface AskQuestion {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
836
|
+
question: string;
|
|
837
|
+
options?: string[];
|
|
838
|
+
multiSelect?: boolean;
|
|
839
|
+
allowCustom?: boolean;
|
|
839
840
|
}
|
|
840
841
|
|
|
841
842
|
export function AskUserPart({
|
|
842
843
|
questions,
|
|
843
|
-
|
|
844
|
-
submittedAnswer,
|
|
844
|
+
submittedAnswers = [],
|
|
845
845
|
onSubmit,
|
|
846
846
|
}: {
|
|
847
847
|
questions: readonly AskQuestion[];
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
848
|
+
submittedAnswers?: ReadonlyArray<{ selections: string[]; text?: string }>;
|
|
849
|
+
onSubmit?: (payload: {
|
|
850
|
+
answers: Array<{ selections: string[]; text: string }>;
|
|
851
|
+
}) => void | Promise<void>;
|
|
851
852
|
}) {
|
|
852
|
-
const [
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
const
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
),
|
|
863
|
-
[externalAnswer],
|
|
864
|
-
);
|
|
865
|
-
const resolvedSubmitted = submitted || Boolean(externalAnswer);
|
|
866
|
-
const valid = questions.every(
|
|
867
|
-
(question, index) => question.kind !== "single" || selections[index].length === 1,
|
|
853
|
+
const [answers, setAnswers] = useState<Array<{
|
|
854
|
+
selections: string[];
|
|
855
|
+
text: string;
|
|
856
|
+
}>>([]);
|
|
857
|
+
const answerAt = (index: number) =>
|
|
858
|
+
answers[index] ?? { selections: [], text: "" };
|
|
859
|
+
const [inFlight, setInFlight] = useState(false);
|
|
860
|
+
const resolvedSubmitted = submittedAnswers.length > 0;
|
|
861
|
+
const valid = questions.length > 0 && questions.every(
|
|
862
|
+
(_question, index) =>
|
|
863
|
+
answerAt(index).selections.length > 0 || answerAt(index).text.trim(),
|
|
868
864
|
);
|
|
869
865
|
const toggle = (questionIndex: number, option: string) => {
|
|
870
|
-
if (resolvedSubmitted) return;
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
866
|
+
if (resolvedSubmitted || inFlight || !onSubmit) return;
|
|
867
|
+
setAnswers((current) => {
|
|
868
|
+
const next = questions.map((_, index) =>
|
|
869
|
+
current[index] ?? { selections: [], text: "" }
|
|
870
|
+
);
|
|
871
|
+
const answer = next[questionIndex]!;
|
|
872
|
+
next[questionIndex] = {
|
|
873
|
+
...answer,
|
|
874
|
+
selections: questions[questionIndex]!.multiSelect
|
|
875
|
+
? answer.selections.includes(option)
|
|
876
|
+
? answer.selections.filter((item) => item !== option)
|
|
877
|
+
: [...answer.selections, option]
|
|
878
|
+
: [option],
|
|
879
|
+
};
|
|
880
|
+
return next;
|
|
881
|
+
});
|
|
880
882
|
};
|
|
881
883
|
const submit = async () => {
|
|
882
|
-
if (!valid || resolvedSubmitted) return;
|
|
883
|
-
|
|
884
|
-
|
|
884
|
+
if (!valid || resolvedSubmitted || !onSubmit) return;
|
|
885
|
+
setInFlight(true);
|
|
886
|
+
try {
|
|
887
|
+
await onSubmit({
|
|
888
|
+
answers: questions.map((_, index) => ({
|
|
889
|
+
selections: answerAt(index).selections,
|
|
890
|
+
text: answerAt(index).text.trim(),
|
|
891
|
+
})),
|
|
892
|
+
});
|
|
893
|
+
} catch {
|
|
894
|
+
setInFlight(false);
|
|
895
|
+
}
|
|
885
896
|
};
|
|
886
|
-
const resolvedAnswer =
|
|
887
|
-
externalAnswer ||
|
|
888
|
-
[...selections.flat(), text.trim()].filter(Boolean).join("、");
|
|
889
897
|
return (
|
|
890
898
|
<section
|
|
891
899
|
className="sb-ask-user"
|
|
@@ -894,15 +902,15 @@ export function AskUserPart({
|
|
|
894
902
|
>
|
|
895
903
|
{questions.map((question, questionIndex) => (
|
|
896
904
|
<fieldset
|
|
897
|
-
key={`${questionIndex}:${question.
|
|
898
|
-
disabled={resolvedSubmitted}
|
|
905
|
+
key={`${questionIndex}:${question.question}`}
|
|
906
|
+
disabled={resolvedSubmitted || inFlight || !onSubmit}
|
|
899
907
|
>
|
|
900
|
-
<legend>{question.
|
|
908
|
+
<legend>{question.question}</legend>
|
|
901
909
|
<div>
|
|
902
|
-
{question.options.map((option) => {
|
|
903
|
-
const selected =
|
|
904
|
-
?
|
|
905
|
-
:
|
|
910
|
+
{(question.options ?? []).map((option) => {
|
|
911
|
+
const selected = submittedAnswers[questionIndex]
|
|
912
|
+
? submittedAnswers[questionIndex]!.selections.includes(option)
|
|
913
|
+
: answerAt(questionIndex).selections.includes(option);
|
|
906
914
|
return (
|
|
907
915
|
<button
|
|
908
916
|
key={option}
|
|
@@ -910,27 +918,53 @@ export function AskUserPart({
|
|
|
910
918
|
aria-pressed={selected}
|
|
911
919
|
onClick={() => toggle(questionIndex, option)}
|
|
912
920
|
>
|
|
913
|
-
{question.
|
|
914
|
-
{selected && question.
|
|
921
|
+
{question.multiSelect && <span>{selected && <CheckIcon size={12} />}</span>}
|
|
922
|
+
{selected && !question.multiSelect && <CheckIcon size={14} />}
|
|
915
923
|
{option}
|
|
916
924
|
</button>
|
|
917
925
|
);
|
|
918
926
|
})}
|
|
919
927
|
</div>
|
|
928
|
+
{!resolvedSubmitted &&
|
|
929
|
+
(question.allowCustom === true || !question.options?.length) && (
|
|
930
|
+
<textarea
|
|
931
|
+
value={answerAt(questionIndex).text}
|
|
932
|
+
maxLength={2_000}
|
|
933
|
+
aria-label={`Custom answer: ${question.question}`}
|
|
934
|
+
onChange={(event) =>
|
|
935
|
+
setAnswers((current) => {
|
|
936
|
+
const next = questions.map((_, index) =>
|
|
937
|
+
current[index] ?? { selections: [], text: "" }
|
|
938
|
+
);
|
|
939
|
+
next[questionIndex] = {
|
|
940
|
+
...next[questionIndex]!,
|
|
941
|
+
text: event.currentTarget.value,
|
|
942
|
+
};
|
|
943
|
+
return next;
|
|
944
|
+
})}
|
|
945
|
+
placeholder={question.options?.length ? "Other" : "Type your answer"}
|
|
946
|
+
/>
|
|
947
|
+
)}
|
|
948
|
+
{submittedAnswers[questionIndex] && (
|
|
949
|
+
<small>
|
|
950
|
+
{[
|
|
951
|
+
...submittedAnswers[questionIndex]!.selections,
|
|
952
|
+
...(submittedAnswers[questionIndex]!.text?.trim()
|
|
953
|
+
? [submittedAnswers[questionIndex]!.text!.trim()]
|
|
954
|
+
: []),
|
|
955
|
+
].join(" / ")}
|
|
956
|
+
</small>
|
|
957
|
+
)}
|
|
920
958
|
</fieldset>
|
|
921
959
|
))}
|
|
922
|
-
{freeTextPlaceholder && !resolvedSubmitted && (
|
|
923
|
-
<textarea value={text} onChange={(event) => setText(event.currentTarget.value)} placeholder={freeTextPlaceholder} />
|
|
924
|
-
)}
|
|
925
960
|
{resolvedSubmitted ? (
|
|
926
961
|
<div className="sb-ask-user__submitted">
|
|
927
962
|
<CheckIcon size={15} />
|
|
928
963
|
<span>Submitted</span>
|
|
929
|
-
{resolvedAnswer && <small>{resolvedAnswer}</small>}
|
|
930
964
|
</div>
|
|
931
965
|
) : (
|
|
932
|
-
<button type="button" className="sb-button" disabled={!valid} onClick={() => void submit()}>
|
|
933
|
-
|
|
966
|
+
<button type="button" className="sb-button" disabled={!valid || inFlight || !onSubmit} onClick={() => void submit()}>
|
|
967
|
+
{inFlight ? "Submitting…" : "提交"}<ArrowRightIcon size={15} />
|
|
934
968
|
</button>
|
|
935
969
|
)}
|
|
936
970
|
</section>
|
|
@@ -1128,17 +1162,22 @@ export function DefaultPartRenderer({
|
|
|
1128
1162
|
if (kind === "ask_user") {
|
|
1129
1163
|
const questions: AskQuestion[] = Array.isArray(data.questions)
|
|
1130
1164
|
? data.questions as AskQuestion[]
|
|
1131
|
-
: [
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1165
|
+
: [];
|
|
1166
|
+
const output = objectOf(record.output);
|
|
1167
|
+
const submittedAnswers = Array.isArray(output.answers)
|
|
1168
|
+
? output.answers.map((answer) => {
|
|
1169
|
+
const value = objectOf(answer);
|
|
1170
|
+
return {
|
|
1171
|
+
selections: strings(value.selections),
|
|
1172
|
+
text: typeof value.text === "string" ? value.text : "",
|
|
1173
|
+
};
|
|
1174
|
+
})
|
|
1175
|
+
: [];
|
|
1136
1176
|
return withToolApproval(
|
|
1137
1177
|
<AskUserPart
|
|
1138
1178
|
questions={questions}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
onSubmit={(payload) => context.actions.onAnswer?.(payload)}
|
|
1179
|
+
submittedAnswers={submittedAnswers}
|
|
1180
|
+
onSubmit={context.actions.onAnswer}
|
|
1142
1181
|
/>,
|
|
1143
1182
|
);
|
|
1144
1183
|
}
|
package/src/styles/index.css
CHANGED
|
@@ -2128,7 +2128,10 @@
|
|
|
2128
2128
|
min-height: 120px;
|
|
2129
2129
|
gap: 0;
|
|
2130
2130
|
border-radius: 16px;
|
|
2131
|
-
|
|
2131
|
+
/* 这里曾写死 #1c1c1a,于是 light 档下输入框还是近黑的。
|
|
2132
|
+
表面色一律走 token:--mp-card → --card(light #fff / dark #1c1c1c)。
|
|
2133
|
+
本节其余几个写死色是状态/图标强调色(琥珀、蓝),两档背景上都读得出来,不在此列。 */
|
|
2134
|
+
background: var(--mp-card);
|
|
2132
2135
|
box-shadow: none;
|
|
2133
2136
|
}
|
|
2134
2137
|
|