@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.
Files changed (67) hide show
  1. package/cloud-os/README.md +4 -3
  2. package/cloud-os/assets/followup-arrow.svg +3 -0
  3. package/cloud-os/assets/loading-corner.svg +3 -0
  4. package/cloud-os/assets/loading-mark.svg +16 -0
  5. package/cloud-os/assets/loading-spark.svg +3 -0
  6. package/cloud-os/assets/model-selected.svg +5 -0
  7. package/cloud-os/assets/thinking-star.svg +9 -0
  8. package/cloud-os/capability-chip.tsx +1 -1
  9. package/cloud-os/chat/cloud-os-chat-messages.tsx +441 -98
  10. package/cloud-os/chat/code-runner.tsx +106 -0
  11. package/cloud-os/chat/image-generation.tsx +76 -0
  12. package/cloud-os/chat/loading-state.tsx +25 -0
  13. package/cloud-os/chat/markdown-message.tsx +144 -41
  14. package/cloud-os/chat/range.ts +8 -0
  15. package/cloud-os/chat/rich-blocks.tsx +285 -227
  16. package/cloud-os/chat/surfaces.tsx +90 -0
  17. package/cloud-os/chat/tool-call.tsx +94 -0
  18. package/cloud-os/chat/tool-presentation.ts +31 -22
  19. package/cloud-os/chat/tool-rows.tsx +195 -143
  20. package/cloud-os/chat/tool-timeline.tsx +123 -0
  21. package/cloud-os/chat/transcript-model.ts +258 -20
  22. package/cloud-os/chat/web-search.tsx +135 -0
  23. package/cloud-os/composer/cloud-os-chat-input.tsx +29 -88
  24. package/cloud-os/composer/cloud-os-model-select.tsx +105 -0
  25. package/cloud-os/file-view.tsx +195 -12
  26. package/cloud-os/index.ts +24 -3
  27. package/cloud-os/layout/cloud-os-workspace-split.tsx +60 -3
  28. package/cloud-os/primitives/collapsible.tsx +21 -0
  29. package/cloud-os/primitives/workshop-controls.tsx +2 -2
  30. package/cloud-os/styles/cloud-os.css +114 -1
  31. package/demo/camel-chat-showcase.tsx +21 -13
  32. package/demo/chat-scenarios.ts +214 -40
  33. package/demo/cloud-os-chat-showcase.tsx +37 -14
  34. package/demo/fixtures.ts +4 -5
  35. package/demo/message-panel-gallery.tsx +16 -2
  36. package/package.json +8 -4
  37. package/springbrand-pet/LICENSE.bloub +21 -0
  38. package/springbrand-pet/bot/cycles.test.ts +221 -0
  39. package/springbrand-pet/bot/cycles.ts +225 -0
  40. package/springbrand-pet/bot/decor.ts +285 -0
  41. package/springbrand-pet/bot/engine.test.ts +543 -0
  42. package/springbrand-pet/bot/engine.ts +558 -0
  43. package/springbrand-pet/bot/expressions.test.ts +135 -0
  44. package/springbrand-pet/bot/expressions.ts +192 -0
  45. package/springbrand-pet/bot/eyefit.ts +455 -0
  46. package/springbrand-pet/bot/face.test.ts +101 -0
  47. package/springbrand-pet/bot/face.ts +179 -0
  48. package/springbrand-pet/bot/math.ts +42 -0
  49. package/springbrand-pet/bot/profiles.ts +22 -0
  50. package/springbrand-pet/bot/repere.ts +31 -0
  51. package/springbrand-pet/bot/shape.test.ts +97 -0
  52. package/springbrand-pet/bot/shape.ts +310 -0
  53. package/springbrand-pet/bot/skins.test.ts +325 -0
  54. package/springbrand-pet/bot/skins.ts +161 -0
  55. package/springbrand-pet/bot/states.ts +616 -0
  56. package/springbrand-pet/drag.test.ts +19 -0
  57. package/springbrand-pet/index.tsx +569 -0
  58. package/springbrand-pet/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  59. package/springbrand-pet/vitest.config.ts +9 -0
  60. package/src/camel/camel-chat-messages.tsx +78 -78
  61. package/src/camel/camel-tool-presentation.tsx +19 -15
  62. package/src/camel/camel-turn.ts +1 -17
  63. package/src/composer/composer-attachments.tsx +1 -1
  64. package/src/composer/composer.tsx +2 -2
  65. package/src/contracts.ts +0 -2
  66. package/src/message-panel.tsx +1 -24
  67. package/src/parts/index.tsx +103 -64
@@ -833,59 +833,67 @@ export function DashboardPart(data: DashboardData) {
833
833
  }
834
834
 
835
835
  export interface AskQuestion {
836
- prompt: string;
837
- kind: "single" | "multi";
838
- options: string[];
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
- freeTextPlaceholder,
844
- submittedAnswer,
844
+ submittedAnswers = [],
845
845
  onSubmit,
846
846
  }: {
847
847
  questions: readonly AskQuestion[];
848
- freeTextPlaceholder?: string;
849
- submittedAnswer?: string;
850
- onSubmit?: (payload: { selections: string[][]; text: string }) => void | Promise<void>;
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 [selections, setSelections] = useState<string[][]>(() => questions.map(() => []));
853
- const [text, setText] = useState("");
854
- const [submitted, setSubmitted] = useState(false);
855
- const externalAnswer = submittedAnswer?.trim() ?? "";
856
- const externalSelections = useMemo(
857
- () =>
858
- new Set(
859
- externalAnswer
860
- ? externalAnswer.split("、").map((item) => item.trim()).filter(Boolean)
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
- setSelections((current) =>
872
- current.map((selected, index) => {
873
- if (index !== questionIndex) return selected;
874
- if (questions[index].kind === "single") return [option];
875
- return selected.includes(option)
876
- ? selected.filter((item) => item !== option)
877
- : [...selected, option];
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
- await onSubmit?.({ selections, text: text.trim() });
884
- setSubmitted(true);
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.prompt}`}
898
- disabled={resolvedSubmitted}
905
+ key={`${questionIndex}:${question.question}`}
906
+ disabled={resolvedSubmitted || inFlight || !onSubmit}
899
907
  >
900
- <legend>{question.prompt}</legend>
908
+ <legend>{question.question}</legend>
901
909
  <div>
902
- {question.options.map((option) => {
903
- const selected = externalAnswer
904
- ? externalSelections.has(option)
905
- : selections[questionIndex].includes(option);
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.kind === "multi" && <span>{selected && <CheckIcon size={12} />}</span>}
914
- {selected && question.kind === "single" && <CheckIcon size={14} />}
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
- 提交<ArrowRightIcon size={15} />
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>
@@ -1081,7 +1115,7 @@ export function DefaultPartRenderer({
1081
1115
  );
1082
1116
  }
1083
1117
  if (kind === "error") {
1084
- return <ErrorPart title={String(data.title ?? "Error")} body={String(data.body ?? data.message ?? "请求失败")} />;
1118
+ return <ErrorPart title={String(data.title ?? "Error")} body={String(data.body ?? data.message ?? "Request failed")} />;
1085
1119
  }
1086
1120
  if (kind === "image") {
1087
1121
  return <ImagePart src={String(record.src ?? record.url ?? data.src ?? data.url ?? "")} alt={String(record.alt ?? record.filename ?? data.alt ?? data.filename ?? "图片")} onPreview={() => context.actions.onPreview?.(part)} />;
@@ -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
- prompt: String(data.question ?? "请选择"),
1133
- kind: data.multiSelect === true ? "multi" : "single",
1134
- options: strings(data.options),
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
- freeTextPlaceholder={typeof data.freeTextPlaceholder === "string" ? data.freeTextPlaceholder : undefined}
1140
- submittedAnswer={context.replyText}
1141
- onSubmit={(payload) => context.actions.onAnswer?.(payload)}
1179
+ submittedAnswers={submittedAnswers}
1180
+ onSubmit={context.actions.onAnswer}
1142
1181
  />,
1143
1182
  );
1144
1183
  }