studio/frontend: scope Working fallback to tool-using responses only
Codex P2 flagged that the 'Working...' fallback fired for every streaming text-only response. For a plain 'What is 2+2?' answer the indicator briefly showed alongside the visible streaming text, which was misleading. Fix: track whether any tool-call part exists in the message (running OR completed) and only emit the 'Working...' fallback when tools have been in play. Pure text-only responses skip the fallback entirely; tool gaps and back-to-back tool calls still get the indicator.
This commit is contained in:
parent
7147f423e2
commit
d64b4b84f6
1 changed files with 17 additions and 7 deletions
|
|
@ -1020,6 +1020,11 @@ const CancelledIndicator: FC = () => {
|
||||||
// doesn't look frozen during the gap between "model wrote intent" and
|
// doesn't look frozen during the gap between "model wrote intent" and
|
||||||
// "tool actually invoked", or between back-to-back tool calls.
|
// "tool actually invoked", or between back-to-back tool calls.
|
||||||
//
|
//
|
||||||
|
// Scope: only fires for tool-using responses. Plain text-only answers
|
||||||
|
// keep using GeneratingIndicator (while content is empty) and then
|
||||||
|
// nothing -- otherwise every text streaming response would get a
|
||||||
|
// misleading "Working..." row.
|
||||||
|
//
|
||||||
// Return value is a string ("tool:<name>" | "working" | "") rather than
|
// Return value is a string ("tool:<name>" | "working" | "") rather than
|
||||||
// an object so useAuiState's referential-equality memoization holds
|
// an object so useAuiState's referential-equality memoization holds
|
||||||
// across renders -- returning a fresh object each render would invalidate
|
// across renders -- returning a fresh object each render would invalidate
|
||||||
|
|
@ -1028,19 +1033,24 @@ const RunningToolIndicator: FC = () => {
|
||||||
const signal = useAuiState(({ message }) => {
|
const signal = useAuiState(({ message }) => {
|
||||||
if (message.status?.type !== "running") return "";
|
if (message.status?.type !== "running") return "";
|
||||||
const parts = message.parts;
|
const parts = message.parts;
|
||||||
|
let sawToolCall = false;
|
||||||
|
let runningTool: string | null = null;
|
||||||
|
// Single pass: find any tool-call part (signals tools-are-in-play)
|
||||||
|
// and the most-recent running one (drives the specific indicator).
|
||||||
for (let i = parts.length - 1; i >= 0; i -= 1) {
|
for (let i = parts.length - 1; i >= 0; i -= 1) {
|
||||||
const p = parts[i] as
|
const p = parts[i] as
|
||||||
| { type?: string; toolName?: string; status?: { type?: string } }
|
| { type?: string; toolName?: string; status?: { type?: string } }
|
||||||
| undefined;
|
| undefined;
|
||||||
if (p?.type === "tool-call" && p.status?.type === "running") {
|
if (p?.type !== "tool-call") continue;
|
||||||
return `tool:${p.toolName ?? "tool"}`;
|
sawToolCall = true;
|
||||||
|
if (!runningTool && p.status?.type === "running") {
|
||||||
|
runningTool = p.toolName ?? "tool";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// No tool currently running, but message is still streaming.
|
if (runningTool) return `tool:${runningTool}`;
|
||||||
// GeneratingIndicator hides once content.length > 0, so once any
|
// Only fall back to "Working..." if tools have been in play. A pure
|
||||||
// text has rendered we lose its dots. Surface a generic "Working..."
|
// text-only response (no tool-call part ever) skips the fallback.
|
||||||
// so the chat doesn't go silent between phases.
|
if (sawToolCall) return "working";
|
||||||
if (parts.length > 0) return "working";
|
|
||||||
return "";
|
return "";
|
||||||
});
|
});
|
||||||
if (!signal) return null;
|
if (!signal) return null;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue