Compare commits

...

3 Commits

Author SHA1 Message Date
Ubbe
ab06840bdb Merge branch 'dev' into otto/secrt-2035-thinking-indicator-between-streams 2026-02-25 23:46:32 +08:00
Lluis Agusti
592e967600 fix(frontend/copilot): use correct ToolUIPart state for thinking indicator
Change ToolUIPart.state check from "result" (invalid) to "output-available"
(the actual AI SDK state for a completed tool call). This fixes the build
and makes the thinking-between-tool-calls feature functional.

Also simplifies the IIFE into a plain local variable for readability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 21:52:38 +08:00
Otto
675218c1c0 feat(frontend): show thinking indicator between CoPilot tool calls
When CoPilot executes multiple tool calls in sequence, the UI now shows
the thinking indicator (e.g. 'Reasoning...', 'On it...') between completed
tool calls while the LLM processes the result. Previously, the indicator
only appeared before the first response.

Detects when the last message part is a completed tool call (state=result)
and re-shows the shimmer indicator with a fresh random phrase.

Fixes SECRT-2035
2026-02-25 09:54:02 +00:00

View File

@@ -11,7 +11,7 @@ import {
} from "@/components/ai-elements/message";
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
import { ToolUIPart, UIDataTypes, UIMessage, UITools } from "ai";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { CreateAgentTool } from "../../tools/CreateAgent/CreateAgent";
import { EditAgentTool } from "../../tools/EditAgent/EditAgent";
import {
@@ -146,12 +146,7 @@ export const ChatMessagesContainer = ({
headerSlot,
}: ChatMessagesContainerProps) => {
const [thinkingPhrase, setThinkingPhrase] = useState(getRandomPhrase);
useEffect(() => {
if (status === "submitted") {
setThinkingPhrase(getRandomPhrase());
}
}, [status]);
const wasThinkingAfterTool = useRef(false);
const lastMessage = messages[messages.length - 1];
const lastAssistantHasVisibleContent =
@@ -162,9 +157,36 @@ export const ChatMessagesContainer = ({
p.type.startsWith("tool-"),
);
// Detect when the LLM is thinking after a completed tool call
const lastPart =
lastMessage?.role === "assistant" && lastMessage.parts.length > 0
? lastMessage.parts[lastMessage.parts.length - 1]
: undefined;
const isThinkingAfterToolCall =
status === "streaming" &&
lastPart !== undefined &&
lastPart.type.startsWith("tool-") &&
(lastPart as ToolUIPart).state === "output-available";
const showThinking =
status === "submitted" ||
(status === "streaming" && !lastAssistantHasVisibleContent);
(status === "streaming" && !lastAssistantHasVisibleContent) ||
isThinkingAfterToolCall;
useEffect(() => {
if (status === "submitted") {
setThinkingPhrase(getRandomPhrase());
}
}, [status]);
// Pick a new phrase when entering "thinking after tool call" state
useEffect(() => {
if (isThinkingAfterToolCall && !wasThinkingAfterTool.current) {
setThinkingPhrase(getRandomPhrase());
}
wasThinkingAfterTool.current = isThinkingAfterToolCall;
}, [isThinkingAfterToolCall]);
return (
<Conversation className="min-h-0 flex-1">
@@ -296,8 +318,8 @@ export const ChatMessagesContainer = ({
}
})}
{isLastAssistant &&
!messageHasVisibleContent &&
showThinking && (
showThinking &&
(!messageHasVisibleContent || isThinkingAfterToolCall) && (
<span className="inline-block animate-shimmer bg-gradient-to-r from-neutral-400 via-neutral-600 to-neutral-400 bg-[length:200%_100%] bg-clip-text text-transparent">
{thinkingPhrase}
</span>