mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-20 08:32:25 +00:00
fix(webui): keep reasoning scoped to the current user turn
The post-hoc reasoning fix allowed late reasoning frames to attach back to the nearest assistant message, but the scan crossed a newer user message. That made the next turn's Thinking bubble render above the previous assistant reply. Treat the latest user message as a hard boundary: reasoning after it must start a new assistant placeholder and can no longer attach to earlier assistant turns. Add a regression covering previous assistant -> new user -> reasoning_delta. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
9829cf66d2
commit
0033a8a185
@ -30,6 +30,9 @@ interface StreamBuffer {
|
|||||||
function attachReasoningChunk(prev: UIMessage[], chunk: string): UIMessage[] {
|
function attachReasoningChunk(prev: UIMessage[], chunk: string): UIMessage[] {
|
||||||
for (let i = prev.length - 1; i >= 0; i -= 1) {
|
for (let i = prev.length - 1; i >= 0; i -= 1) {
|
||||||
const candidate = prev[i];
|
const candidate = prev[i];
|
||||||
|
// A user turn is a hard boundary: reasoning after it belongs to the new
|
||||||
|
// assistant turn, never to an earlier assistant reply.
|
||||||
|
if (candidate.role === "user") break;
|
||||||
if (candidate.role !== "assistant" || candidate.kind === "trace") continue;
|
if (candidate.role !== "assistant" || candidate.kind === "trace") continue;
|
||||||
const hasAnswer = candidate.content.length > 0;
|
const hasAnswer = candidate.content.length > 0;
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -238,6 +238,44 @@ describe("useNanobotStream", () => {
|
|||||||
expect(result.current.messages[0].reasoningStreaming).toBe(false);
|
expect(result.current.messages[0].reasoningStreaming).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not attach a new turn's reasoning across the latest user boundary", () => {
|
||||||
|
const fake = fakeClient();
|
||||||
|
const initialMessages = [
|
||||||
|
{
|
||||||
|
id: "a-prev",
|
||||||
|
role: "assistant" as const,
|
||||||
|
content: "Previous answer.",
|
||||||
|
reasoning: "Previous thought.",
|
||||||
|
createdAt: Date.now(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "u-next",
|
||||||
|
role: "user" as const,
|
||||||
|
content: "Next question",
|
||||||
|
createdAt: Date.now(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const { result } = renderHook(
|
||||||
|
() => useNanobotStream("chat-r6", initialMessages),
|
||||||
|
{ wrapper: wrap(fake.client) },
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fake.emit("chat-r6", {
|
||||||
|
event: "reasoning_delta",
|
||||||
|
chat_id: "chat-r6",
|
||||||
|
text: "New turn thinking.",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.messages).toHaveLength(3);
|
||||||
|
expect(result.current.messages[0].reasoning).toBe("Previous thought.");
|
||||||
|
expect(result.current.messages[2].role).toBe("assistant");
|
||||||
|
expect(result.current.messages[2].content).toBe("");
|
||||||
|
expect(result.current.messages[2].reasoning).toBe("New turn thinking.");
|
||||||
|
expect(result.current.messages[2].reasoningStreaming).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("attaches assistant media_urls to complete messages", () => {
|
it("attaches assistant media_urls to complete messages", () => {
|
||||||
const fake = fakeClient();
|
const fake = fakeClient();
|
||||||
const { result } = renderHook(() => useNanobotStream("chat-m", EMPTY_MESSAGES), {
|
const { result } = renderHook(() => useNanobotStream("chat-m", EMPTY_MESSAGES), {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user