This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new ef9b98269 fix: prevent unread badge from inflating when streaming 
fails (#4715)
ef9b98269 is described below

commit ef9b98269ad85ae0cf230c1f687f6ae366c926fc
Author: cyberslack_lee <[email protected]>
AuthorDate: Mon Sep 21 21:10:39 2026 +0800

    fix: prevent unread badge from inflating when streaming fails (#4715)
    
    `ChatThread.tsx` counted unread messages with an effect keyed on 
`bubbleCount` that incremented on any change, although its own comment says it 
counts what arrives while the reader is away. `bubbleCount` is `bubbles.length` 
plus one whenever live blocks exist, so it goes down too: when an SSE read 
fails, `useAgentRun` finishes the stream and refetches the timeline, and if the 
run has not written an assistant row yet the refetch clears the live blocks and 
the count drops. A reader who  [...]
    
    The effect now keeps the previous count in a ref and returns early unless 
the count actually grew, so only arrivals increment the badge. That is the 
component's only increment site — the other three write points clear it — and a 
conversation switch already resets `atBottomRef` before this effect runs, so 
nothing is counted across a switch.
---
 web/src/pages/ai/components/ChatThread.tsx         |  7 +++++-
 .../ai/components/__tests__/ChatThread.test.tsx    | 27 ++++++++++++++++++++++
 2 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/web/src/pages/ai/components/ChatThread.tsx 
b/web/src/pages/ai/components/ChatThread.tsx
index b350070b5..e954163e5 100644
--- a/web/src/pages/ai/components/ChatThread.tsx
+++ b/web/src/pages/ai/components/ChatThread.tsx
@@ -100,6 +100,7 @@ const ChatThread = ({
   const positionedRef = useRef(false);
   const [atBottom, setAtBottom] = useState(true);
   const [unread, setUnread] = useState(0);
+  const prevBubbleCountRef = useRef(bubbles.length);
 
   const bubbleCount = bubbles.length + (liveBlocks && liveBlocks.length > 0 ? 
1 : 0);
 
@@ -149,8 +150,12 @@ const ChatThread = ({
   }, [bubbles, followToBottom, liveBlocks]);
 
   // Count what arrives while the reader is away: one per message, not one per 
streamed token.
+  // Only a genuine increase in bubble count is a new message — a streaming 
failure that removes
+  // the live bubble must not inflate the badge.
   useEffect(() => {
-    if (atBottomRef.current) return;
+    const previous = prevBubbleCountRef.current;
+    prevBubbleCountRef.current = bubbleCount;
+    if (atBottomRef.current || bubbleCount <= previous) return;
     setUnread((count) => count + 1);
   }, [bubbleCount]);
 
diff --git a/web/src/pages/ai/components/__tests__/ChatThread.test.tsx 
b/web/src/pages/ai/components/__tests__/ChatThread.test.tsx
index 75f909b64..a1d0f7773 100644
--- a/web/src/pages/ai/components/__tests__/ChatThread.test.tsx
+++ b/web/src/pages/ai/components/__tests__/ChatThread.test.tsx
@@ -222,6 +222,33 @@ describe('ChatThread', () => {
     
expect(screen.queryByTestId('ai-thread-jump-to-latest')).not.toBeInTheDocument();
   });
 
+  it('doesNotInflateUnreadWhenStreamingFailsAndLiveBlocksDisappearTest', () => 
{
+    const { rerender } = renderThread({ bubbles: [userBubble('检查集群状态')] });
+    placeReader(600);
+    fireEvent.scroll(screen.getByTestId('ai-thread-scroll'));
+
+    // The answer starts streaming: one new message arrives.
+    rerender(
+      <LangProvider>
+        <ChatThread
+          bubbles={[userBubble('检查集群状态')]}
+          liveBlocks={appendText([], '部分')}
+          streaming
+        />
+      </LangProvider>,
+    );
+    expect(screen.getByTestId('ai-thread-unread')).toHaveTextContent('1');
+
+    // Streaming fails / is aborted and the live bubble disappears without 
being persisted.
+    // The unread count must not increase when a bubble is removed.
+    rerender(
+      <LangProvider>
+        <ChatThread bubbles={[userBubble('检查集群状态')]} />
+      </LangProvider>,
+    );
+    expect(screen.getByTestId('ai-thread-unread')).toHaveTextContent('1');
+  });
+
   it('rendersTheEmptySlotWhenThereIsNoTranscriptTest', () => {
     render(
       <LangProvider>

Reply via email to