@springbrand/message-panel 0.2.0-alpha.55 → 0.3.0-alpha.1

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.
@@ -72,8 +72,6 @@ export interface CloudOsChatMessagesProps {
72
72
  /** 权威 Turn 生命周期;提供时替代本地 status 推断。 */
73
73
  turnActive?: boolean;
74
74
  isRecovering?: boolean;
75
- recoveryAttempt?: number;
76
- recoveryMax?: number;
77
75
  recoveryStatusLabel?: string;
78
76
  awaitingApproval?: boolean;
79
77
  /** 有 pending steer 时,活动占位说明「正在等当前步骤收尾」。 */
@@ -375,8 +373,6 @@ export function CloudOsChatMessages({
375
373
  status,
376
374
  turnActive,
377
375
  isRecovering = false,
378
- recoveryAttempt,
379
- recoveryMax,
380
376
  recoveryStatusLabel,
381
377
  awaitingApproval = false,
382
378
  hasPendingSteer = false,
@@ -550,8 +546,6 @@ export function CloudOsChatMessages({
550
546
  const activity = deriveTurnActivity(entries, {
551
547
  isActive,
552
548
  isRecovering,
553
- recoveryAttempt,
554
- recoveryMax,
555
549
  recoveryStatusLabel,
556
550
  awaitingApproval,
557
551
  hasPendingSteer,
@@ -722,8 +716,7 @@ export function CloudOsChatMessages({
722
716
  );
723
717
  const actionResultBlocks = entry.blocks.filter(
724
718
  (block): block is Extract<AssistantBlock, { kind: "actionPresentation" }> =>
725
- block.kind === "actionPresentation" &&
726
- !(block.presentation.state === "ready" && block.presentation.content.length === 0),
719
+ block.kind === "actionPresentation",
727
720
  );
728
721
  const finalMediaBlocks = entry.blocks.filter(
729
722
  (block): block is Extract<AssistantBlock, { kind: "image" | "file" }> =>
@@ -4,7 +4,7 @@ import {
4
4
  useSmooth,
5
5
  type SmoothOptions,
6
6
  } from "@assistant-ui/react";
7
- import { memo, useMemo, type ReactNode } from "react";
7
+ import { memo, useMemo, useRef, type ReactNode } from "react";
8
8
  import {
9
9
  defaultRehypePlugins,
10
10
  Streamdown,
@@ -135,8 +135,13 @@ function MarkdownMessageContent({
135
135
  resolveArtifactId?: CloudOsArtifactResolver;
136
136
  onOpenArtifact?: (artifactId: string) => void;
137
137
  }) {
138
- const part = useSmooth(useMessagePartText(), SMOOTH_STREAMING);
139
- const streaming = part.status.type === "running";
138
+ const received = useMessagePartText();
139
+ const initialText = useRef(received.text);
140
+ // A mounted snapshot is already visible history; only later deltas need a reveal.
141
+ const part = useSmooth(received.text === initialText.current
142
+ ? { ...received, status: { type: "complete" } }
143
+ : received, SMOOTH_STREAMING);
144
+ const streaming = received.status.type === "running" || part.status.type === "running";
140
145
  const rehypePlugins = useMemo(
141
146
  () =>
142
147
  resolveUrl
@@ -152,7 +152,7 @@ interface AskQuestion {
152
152
 
153
153
  function questionsOf(input: Record<string, unknown>): AskQuestion[] {
154
154
  if (!Array.isArray(input.questions)) return [];
155
- return input.questions.map((item) => {
155
+ return input.questions.flatMap((item) => {
156
156
  const question = (item ?? {}) as Record<string, unknown>;
157
157
  const options = Array.isArray(question.options)
158
158
  ? question.options.filter((option): option is string =>
@@ -164,14 +164,9 @@ function questionsOf(input: Record<string, unknown>): AskQuestion[] {
164
164
  question.responseType === "multi_select" ||
165
165
  question.responseType === "attachment"
166
166
  ? question.responseType
167
- : question.kind === "attachment"
168
- ? "attachment"
169
- : options.length === 0
170
- ? "text"
171
- : question.multiSelect === true
172
- ? "multi_select"
173
- : "single_select";
174
- return {
167
+ : undefined;
168
+ if (!responseType) return [];
169
+ return [{
175
170
  prompt: typeof question.question === "string"
176
171
  ? question.question
177
172
  : "Choose an option",
@@ -179,7 +174,7 @@ function questionsOf(input: Record<string, unknown>): AskQuestion[] {
179
174
  allowCustom: responseType === "text" || question.allowCustom === true,
180
175
  required: question.required === true,
181
176
  responseType,
182
- };
177
+ }];
183
178
  });
184
179
  }
185
180
 
@@ -431,13 +431,18 @@ function assistantBlocks(
431
431
  if (type === "text") {
432
432
  const text = String(record.text ?? "").trim();
433
433
  if (!text) return;
434
+ // Clarification prose belongs with its ask_user, not the later delivery.
435
+ const nextBoundary = message.parts.slice(partIndex + 1).find((next) =>
436
+ next.type === "step-start" || toolNameOf(next) === "ask_user"
437
+ );
434
438
  flush();
435
439
  blocks.push({
436
440
  kind: "text",
437
441
  key,
438
442
  text,
439
443
  final: false,
440
- substantive: Array.from(text).length > 400,
444
+ substantive: Array.from(text).length > 400 &&
445
+ toolNameOf(nextBoundary) !== "ask_user",
441
446
  });
442
447
  return;
443
448
  }
@@ -968,8 +973,6 @@ export function deriveTurnActivity(
968
973
  options: {
969
974
  isActive: boolean;
970
975
  isRecovering?: boolean;
971
- recoveryAttempt?: number;
972
- recoveryMax?: number;
973
976
  recoveryStatusLabel?: string;
974
977
  awaitingApproval?: boolean;
975
978
  hasPendingSteer?: boolean;
@@ -978,10 +981,7 @@ export function deriveTurnActivity(
978
981
  if (options.isRecovering) {
979
982
  return {
980
983
  state: "connecting",
981
- label: options.recoveryStatusLabel ??
982
- (options.recoveryAttempt === undefined || options.recoveryMax === undefined
983
- ? "Recovering model response…"
984
- : `Recovering model response ${options.recoveryAttempt}/${options.recoveryMax}…`),
984
+ label: options.recoveryStatusLabel ?? "Recovering model response…",
985
985
  };
986
986
  }
987
987
  if (!options.isActive) return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/message-panel",
3
- "version": "0.2.0-alpha.55",
3
+ "version": "0.3.0-alpha.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -673,11 +673,7 @@ function askUserResponseType(question: Record<string, unknown>) {
673
673
  question.responseType === "multi_select" ||
674
674
  question.responseType === "attachment"
675
675
  ) return question.responseType;
676
- if (question.kind === "attachment") return "attachment";
677
- if (!Array.isArray(question.options) || question.options.length === 0) {
678
- return "text";
679
- }
680
- return question.multiSelect === true ? "multi_select" : "single_select";
676
+ return undefined;
681
677
  }
682
678
 
683
679
  function CamelAskUser({
@@ -840,17 +840,6 @@ export interface AskQuestion {
840
840
  required?: boolean;
841
841
  }
842
842
 
843
- function askQuestionResponseType(question: AskQuestion) {
844
- if (question.responseType) return question.responseType;
845
- const legacy = question as AskQuestion & {
846
- kind?: string;
847
- multiSelect?: boolean;
848
- };
849
- if (legacy.kind === "attachment") return "attachment";
850
- if (!question.options?.length) return "text";
851
- return legacy.multiSelect ? "multi_select" : "single_select";
852
- }
853
-
854
843
  interface AskAnswer {
855
844
  selections: string[];
856
845
  text: string;
@@ -878,7 +867,7 @@ export function AskUserPart({
878
867
  const resolvedSubmitted = submittedAnswers.length > 0;
879
868
  const valid = questions.length > 0 && questions.every(
880
869
  (question, index) => {
881
- const responseType = askQuestionResponseType(question);
870
+ const responseType = question.responseType;
882
871
  const answer = answerAt(index);
883
872
  if (
884
873
  answer.selections.length === 0 && !answer.text.trim()
@@ -898,7 +887,7 @@ export function AskUserPart({
898
887
  const answer = next[questionIndex]!;
899
888
  next[questionIndex] = {
900
889
  ...answer,
901
- selections: askQuestionResponseType(questions[questionIndex]!) ===
890
+ selections: questions[questionIndex]!.responseType ===
902
891
  "multi_select"
903
892
  ? answer.selections.includes(option)
904
893
  ? answer.selections.filter((item) => item !== option)
@@ -930,7 +919,7 @@ export function AskUserPart({
930
919
  data-state={resolvedSubmitted ? "submitted" : "pending"}
931
920
  >
932
921
  {questions.map((question, questionIndex) => {
933
- const responseType = askQuestionResponseType(question);
922
+ const responseType = question.responseType;
934
923
  const choice = responseType === "single_select" ||
935
924
  responseType === "multi_select";
936
925
  return (