Thinking and fix the loader after chat completed, and minimize after streaming message.
This commit is contained in:
parent
2f788ad4a2
commit
4a291e7901
@ -516,9 +516,8 @@ function groupToolSteps(tools: ToolPart[]): VisualItem[] {
|
|||||||
|
|
||||||
/* ─── Main component ─── */
|
/* ─── Main component ─── */
|
||||||
|
|
||||||
export function ChainOfThought({ parts }: { parts: ChainPart[] }) {
|
export function ChainOfThought({ parts, isStreaming }: { parts: ChainPart[]; isStreaming?: boolean }) {
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
const prevActiveRef = useRef(true);
|
|
||||||
|
|
||||||
const isActive = parts.some(
|
const isActive = parts.some(
|
||||||
(p) =>
|
(p) =>
|
||||||
@ -556,12 +555,17 @@ export function ChainOfThought({ parts }: { parts: ChainPart[] }) {
|
|||||||
return rem > 0 ? `${m}m ${rem}s` : `${m}m`;
|
return rem > 0 ? `${m}m ${rem}s` : `${m}m`;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Collapse only when the parent stream truly ends — not on intermediate
|
||||||
|
// isActive flickers (e.g. gap between reasoning end and tool start).
|
||||||
|
const wasStreamingRef = useRef(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prevActiveRef.current && !isActive && parts.length > 0) {
|
if (isStreaming) {
|
||||||
|
wasStreamingRef.current = true;
|
||||||
|
} else if (wasStreamingRef.current && parts.length > 0) {
|
||||||
|
wasStreamingRef.current = false;
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
}
|
||||||
prevActiveRef.current = isActive;
|
}, [isStreaming, parts.length]);
|
||||||
}, [isActive, parts.length]);
|
|
||||||
|
|
||||||
const statusParts = parts.filter(
|
const statusParts = parts.filter(
|
||||||
(p): p is Extract<ChainPart, { kind: "status" }> =>
|
(p): p is Extract<ChainPart, { kind: "status" }> =>
|
||||||
|
|||||||
@ -56,8 +56,17 @@ function groupParts(parts: UIMessage["parts"]): MessageSegment[] {
|
|||||||
const segments: MessageSegment[] = [];
|
const segments: MessageSegment[] = [];
|
||||||
let chain: ChainPart[] = [];
|
let chain: ChainPart[] = [];
|
||||||
|
|
||||||
const flush = () => {
|
const flush = (textFollows?: boolean) => {
|
||||||
if (chain.length > 0) {
|
if (chain.length > 0) {
|
||||||
|
// If text content follows this chain, all tools must have
|
||||||
|
// completed — force any stuck "running" tools to "done".
|
||||||
|
if (textFollows) {
|
||||||
|
for (const cp of chain) {
|
||||||
|
if (cp.kind === "tool" && cp.status === "running") {
|
||||||
|
cp.status = "done";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
segments.push({ type: "chain", parts: [...chain] });
|
segments.push({ type: "chain", parts: [...chain] });
|
||||||
chain = [];
|
chain = [];
|
||||||
}
|
}
|
||||||
@ -65,7 +74,7 @@ function groupParts(parts: UIMessage["parts"]): MessageSegment[] {
|
|||||||
|
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
if (part.type === "text") {
|
if (part.type === "text") {
|
||||||
flush();
|
flush(true);
|
||||||
const text = (part as { type: "text"; text: string }).text;
|
const text = (part as { type: "text"; text: string }).text;
|
||||||
if (hasReportBlocks(text)) {
|
if (hasReportBlocks(text)) {
|
||||||
segments.push(
|
segments.push(
|
||||||
@ -504,7 +513,7 @@ const mdComponents: Components = {
|
|||||||
|
|
||||||
/* ─── Chat message ─── */
|
/* ─── Chat message ─── */
|
||||||
|
|
||||||
export function ChatMessage({ message }: { message: UIMessage }) {
|
export function ChatMessage({ message, isStreaming }: { message: UIMessage; isStreaming?: boolean }) {
|
||||||
const isUser = message.role === "user";
|
const isUser = message.role === "user";
|
||||||
const segments = groupParts(message.parts);
|
const segments = groupParts(message.parts);
|
||||||
|
|
||||||
@ -649,6 +658,7 @@ export function ChatMessage({ message }: { message: UIMessage }) {
|
|||||||
<ChainOfThought
|
<ChainOfThought
|
||||||
key={index}
|
key={index}
|
||||||
parts={segment.parts}
|
parts={segment.parts}
|
||||||
|
isStreaming={isStreaming}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -1315,10 +1315,11 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(
|
|||||||
<div
|
<div
|
||||||
className={`${compact ? "" : "max-w-3xl mx-auto"} py-3`}
|
className={`${compact ? "" : "max-w-3xl mx-auto"} py-3`}
|
||||||
>
|
>
|
||||||
{messages.map((message) => (
|
{messages.map((message, i) => (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
key={message.id}
|
key={message.id}
|
||||||
message={message}
|
message={message}
|
||||||
|
isStreaming={isStreaming && i === messages.length - 1}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
|
|||||||
@ -64,6 +64,28 @@ export function handleMessageUpdate(
|
|||||||
: undefined;
|
: undefined;
|
||||||
const evtType = typeof assistantRecord?.type === "string" ? assistantRecord.type : "";
|
const evtType = typeof assistantRecord?.type === "string" ? assistantRecord.type : "";
|
||||||
|
|
||||||
|
// Handle native extended thinking events (Anthropic API thinking blocks).
|
||||||
|
// These arrive as thinking_delta / thinking_start / thinking_end from the
|
||||||
|
// provider adapter and must be forwarded to the agent event bus so that
|
||||||
|
// active-runs.ts (web UI) and other consumers receive them.
|
||||||
|
if (evtType === "thinking_delta" || evtType === "thinking_start" || evtType === "thinking_end") {
|
||||||
|
if (evtType === "thinking_delta") {
|
||||||
|
const thinkingDelta = typeof assistantRecord?.delta === "string" ? assistantRecord.delta : "";
|
||||||
|
if (thinkingDelta) {
|
||||||
|
emitAgentEvent({
|
||||||
|
runId: ctx.params.runId,
|
||||||
|
stream: "thinking",
|
||||||
|
data: { delta: thinkingDelta },
|
||||||
|
});
|
||||||
|
void ctx.params.onAgentEvent?.({
|
||||||
|
stream: "thinking",
|
||||||
|
data: { delta: thinkingDelta },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (evtType !== "text_delta" && evtType !== "text_start" && evtType !== "text_end") {
|
if (evtType !== "text_delta" && evtType !== "text_start" && evtType !== "text_end") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user