@assistant-ui/react 0.14.6 → 0.14.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"useSmooth.d.ts","sourceRoot":"","sources":["../../../src/utils/smooth/useSmooth.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EACjB,MAAM,oBAAoB,CAAC;AA+D5B,eAAO,MAAM,SAAS,GACpB,OAAO,gBAAgB,GAAG,CAAC,eAAe,GAAG,oBAAoB,CAAC,EAClE,SAAQ,OAAe,KACtB,gBAAgB,GAAG,CAAC,eAAe,GAAG,oBAAoB,CAmF5D,CAAC"}
1
+ {"version":3,"file":"useSmooth.d.ts","sourceRoot":"","sources":["../../../src/utils/smooth/useSmooth.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EACjB,MAAM,oBAAoB,CAAC;AA+D5B,eAAO,MAAM,SAAS,GACpB,OAAO,gBAAgB,GAAG,CAAC,eAAe,GAAG,oBAAoB,CAAC,EAClE,SAAQ,OAAe,KACtB,gBAAgB,GAAG,CAAC,eAAe,GAAG,oBAAoB,CAmG5D,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
- import { useAuiState } from "@assistant-ui/store";
3
+ import { useAui, useAuiState } from "@assistant-ui/store";
4
4
  import { useCallbackRef } from "@radix-ui/react-use-callback-ref";
5
5
  import { useSmoothStatusStore } from "./SmoothContext.js";
6
6
  import { writableStore } from "../../context/ReadonlyStore.js";
@@ -55,9 +55,23 @@ const SMOOTH_STATUS = Object.freeze({
55
55
  });
56
56
  export const useSmooth = (state, smooth = false) => {
57
57
  const { text } = state;
58
- const id = useAuiState((s) => s.message.id);
59
- const idRef = useRef(id);
60
58
  const [displayedText, setDisplayedText] = useState(state.status.type === "running" ? "" : text);
59
+ // Render-phase resync on part flip or text discontinuity, so the
60
+ // first paint after a thread switch never shows the previous
61
+ // part's text (#4051). `displayedText` is already a prefix of
62
+ // `text` during normal streaming, so use it as the previous-text
63
+ // reference instead of carrying separate state — avoids the
64
+ // double render per streaming token. Read part identity through
65
+ // `useAuiState` so we actually subscribe to its changes instead
66
+ // of relying on a render-time proxy reference that may be stable
67
+ // across thread swaps.
68
+ const aui = useAui();
69
+ const part = useAuiState(() => aui.part());
70
+ const [prevPart, setPrevPart] = useState(part);
71
+ if (part !== prevPart || !text.startsWith(displayedText)) {
72
+ setPrevPart(part);
73
+ setDisplayedText(state.status.type === "running" ? "" : text);
74
+ }
61
75
  const smoothStatusStore = useSmoothStatusStore({ optional: true });
62
76
  const setText = useCallbackRef((text) => {
63
77
  setDisplayedText(text);
@@ -78,23 +92,26 @@ export const useSmooth = (state, smooth = false) => {
78
92
  }
79
93
  }, [smoothStatusStore, smooth, text, displayedText, state.status]);
80
94
  const [animatorRef] = useState(new TextStreamAnimator(displayedText, setText));
95
+ const animatorPartRef = useRef(part);
81
96
  useEffect(() => {
82
97
  if (!smooth) {
83
98
  animatorRef.stop();
84
99
  return;
85
100
  }
86
- if (idRef.current !== id || !text.startsWith(animatorRef.targetText)) {
87
- idRef.current = id;
101
+ // Discontinuity: part flipped, or new text breaks continuation
102
+ // of the animator's current target. Either case requires
103
+ // resetting the cursor — without the part check, a new part
104
+ // whose text happens to share a prefix with the previous target
105
+ // would keep the stale cursor and flicker.
106
+ const partChanged = animatorPartRef.current !== part;
107
+ animatorPartRef.current = part;
108
+ if (partChanged || !text.startsWith(animatorRef.targetText)) {
88
109
  if (state.status.type === "running") {
89
- // New streaming message → animate from empty string
90
- setText("");
91
110
  animatorRef.currentText = "";
92
111
  animatorRef.targetText = text;
93
112
  animatorRef.start();
94
113
  }
95
114
  else {
96
- // Completed message → display immediately
97
- setText(text);
98
115
  animatorRef.currentText = text;
99
116
  animatorRef.targetText = text;
100
117
  animatorRef.stop();
@@ -103,7 +120,7 @@ export const useSmooth = (state, smooth = false) => {
103
120
  }
104
121
  animatorRef.targetText = text;
105
122
  animatorRef.start();
106
- }, [setText, animatorRef, id, smooth, text, state.status.type]);
123
+ }, [animatorRef, smooth, text, state.status.type, part]);
107
124
  useEffect(() => {
108
125
  return () => {
109
126
  animatorRef.stop();
@@ -1 +1 @@
1
- {"version":3,"file":"useSmooth.js","sourceRoot":"","sources":["../../../src/utils/smooth/useSmooth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useSmooth.js","sourceRoot":"","sources":["../../../src/utils/smooth/useSmooth.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react",
3
- "version": "0.14.6",
3
+ "version": "0.14.7",
4
4
  "description": "Open-source TypeScript/React library for building production-grade AI chat experiences",
5
5
  "keywords": [
6
6
  "radix-ui",
@@ -48,7 +48,7 @@
48
48
  ],
49
49
  "sideEffects": false,
50
50
  "dependencies": {
51
- "@assistant-ui/core": "^0.2.3",
51
+ "@assistant-ui/core": "^0.2.4",
52
52
  "@assistant-ui/store": "^0.2.11",
53
53
  "@assistant-ui/tap": "^0.5.11",
54
54
  "@radix-ui/primitive": "^1.1.3",
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useEffect, useMemo, useRef, useState } from "react";
4
- import { useAuiState } from "@assistant-ui/store";
4
+ import { useAui, useAuiState } from "@assistant-ui/store";
5
5
  import type {
6
6
  MessagePartStatus,
7
7
  ReasoningMessagePart,
@@ -75,13 +75,28 @@ export const useSmooth = (
75
75
  smooth: boolean = false,
76
76
  ): MessagePartState & (TextMessagePart | ReasoningMessagePart) => {
77
77
  const { text } = state;
78
- const id = useAuiState((s) => s.message.id);
79
78
 
80
- const idRef = useRef(id);
81
79
  const [displayedText, setDisplayedText] = useState(
82
80
  state.status.type === "running" ? "" : text,
83
81
  );
84
82
 
83
+ // Render-phase resync on part flip or text discontinuity, so the
84
+ // first paint after a thread switch never shows the previous
85
+ // part's text (#4051). `displayedText` is already a prefix of
86
+ // `text` during normal streaming, so use it as the previous-text
87
+ // reference instead of carrying separate state — avoids the
88
+ // double render per streaming token. Read part identity through
89
+ // `useAuiState` so we actually subscribe to its changes instead
90
+ // of relying on a render-time proxy reference that may be stable
91
+ // across thread swaps.
92
+ const aui = useAui();
93
+ const part = useAuiState(() => aui.part());
94
+ const [prevPart, setPrevPart] = useState(part);
95
+ if (part !== prevPart || !text.startsWith(displayedText)) {
96
+ setPrevPart(part);
97
+ setDisplayedText(state.status.type === "running" ? "" : text);
98
+ }
99
+
85
100
  const smoothStatusStore = useSmoothStatusStore({ optional: true });
86
101
  const setText = useCallbackRef((text: string) => {
87
102
  setDisplayedText(text);
@@ -109,35 +124,36 @@ export const useSmooth = (
109
124
  new TextStreamAnimator(displayedText, setText),
110
125
  );
111
126
 
127
+ const animatorPartRef = useRef(part);
112
128
  useEffect(() => {
113
129
  if (!smooth) {
114
130
  animatorRef.stop();
115
131
  return;
116
132
  }
117
133
 
118
- if (idRef.current !== id || !text.startsWith(animatorRef.targetText)) {
119
- idRef.current = id;
120
-
134
+ // Discontinuity: part flipped, or new text breaks continuation
135
+ // of the animator's current target. Either case requires
136
+ // resetting the cursor — without the part check, a new part
137
+ // whose text happens to share a prefix with the previous target
138
+ // would keep the stale cursor and flicker.
139
+ const partChanged = animatorPartRef.current !== part;
140
+ animatorPartRef.current = part;
141
+ if (partChanged || !text.startsWith(animatorRef.targetText)) {
121
142
  if (state.status.type === "running") {
122
- // New streaming message → animate from empty string
123
- setText("");
124
143
  animatorRef.currentText = "";
125
144
  animatorRef.targetText = text;
126
145
  animatorRef.start();
127
146
  } else {
128
- // Completed message → display immediately
129
- setText(text);
130
147
  animatorRef.currentText = text;
131
148
  animatorRef.targetText = text;
132
149
  animatorRef.stop();
133
150
  }
134
-
135
151
  return;
136
152
  }
137
153
 
138
154
  animatorRef.targetText = text;
139
155
  animatorRef.start();
140
- }, [setText, animatorRef, id, smooth, text, state.status.type]);
156
+ }, [animatorRef, smooth, text, state.status.type, part]);
141
157
 
142
158
  useEffect(() => {
143
159
  return () => {