@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.
@@ -239,35 +239,54 @@ function userMessage(prompt: string): UIMessage {
239
239
  } as UIMessage;
240
240
  }
241
241
 
242
- const askUserRequest = {
243
- id: "camel-demo-ask-user",
244
- role: "assistant",
245
- parts: [
246
- {
247
- type: "tool-ask_user",
248
- toolCallId: "camel-demo-ask-user-1",
249
- state: "output-available",
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
- } as unknown as UIMessage;
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 humanReplyMessage(answer: string): UIMessage {
266
+ function askUserRequest(response?: DemoAskUserResponse): UIMessage {
261
267
  return {
262
- id: "camel-demo-human-reply",
263
- role: "user",
264
- metadata: {
265
- authorDisplayName: "You",
266
- createdAt: demoTurnStartedAt + 2_000,
267
- messageSource: "Web",
268
- },
269
- parts: [{ type: "text", text: answer }],
270
- } as UIMessage;
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: string,
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 { selections?: string[]; text?: string })
393
- : {};
394
- const answer = [
395
- ...(data.selections ?? []),
396
- data.text?.trim() ?? "",
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 (answer) setHumanAnswer(answer);
402
+ if (data.answers.length > 0) setHumanAnswer(data);
401
403
  selectScenario("planning");
402
- setLastEvent(`已回答并继续:${answer || humanAnswer}`);
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
- prompt: "报告通过什么渠道发送?",
170
- kind: "single",
169
+ question: "报告通过什么渠道发送?",
171
170
  options: ["Email", "Slack", "Both"],
172
171
  },
173
172
  {
174
- prompt: "需要包含哪些内容?",
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={[basicMessages[0], allPartsMessage]}
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: noOp,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/message-panel",
3
- "version": "0.1.3-alpha.10",
3
+ "version": "0.1.3-alpha.12",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -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
- : typeof data.question === "string"
684
- ? [{
685
- prompt: data.question,
686
- kind: data.multiSelect === true ? "multi" : "single",
687
- options: Array.isArray(data.options)
688
- ? data.options.filter(
689
- (option): option is string => typeof option === "string",
690
- )
691
- : [],
692
- }]
693
- : [];
694
- const [selections, setSelections] = useState<string[][]>(() =>
695
- questions.map(() => []),
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.prompt ?? "Choose an option");
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.kind === "multi";
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 = externalAnswer
739
- ? externalSelections.has(option)
740
- : selections[questionIndex]?.includes(option);
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
- setSelections((current) =>
750
- current.map((values, index) => {
751
- if (index !== questionIndex) return values;
752
- if (!multi) return [option];
753
- return values.includes(option)
754
- ? values.filter((value) => value !== option)
755
- : [...values, option];
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({ selections, text });
783
- setSubmitted(true);
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
- if (Array.isArray(input.questions)) return input.questions.map(recordOf);
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 answer = presentation.outputText;
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
- {answer || "—"}
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
  ))
@@ -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
- const reply = messages
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;
@@ -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 && (