streaming round: thinking collapses by default, live tool-input cards, one glimmer grammar

This commit is contained in:
amosblomqvist
2026-10-02 02:09:38 +03:00
parent a99cbf3d90
commit 0751fec9d8
6 changed files with 159 additions and 26 deletions
+20 -10
View File
@@ -15,6 +15,7 @@ import {
import { Message, MessageContent, MessageResponse } from "@/components/ai-elements/message";
import { Reasoning, ReasoningContent, ReasoningTrigger } from "@/components/ai-elements/reasoning";
import { Shimmer } from "@/components/ai-elements/shimmer";
import { LiveToolCard } from "@/components/artifacts/live-tool-card";
import { Separator } from "@/components/ui/separator";
import { AnsweredCard } from "@/components/answered-card";
import { artifactRenderers } from "@/components/artifacts";
@@ -36,7 +37,7 @@ import type { ArtifactPartProps, JobRecord, Phase3, QuestionAnswerData, TurnUsag
import type { LhViewMessage } from "@/src/core";
import { PhaseChip, PhaseDivider } from "@/components/phase-divider";
import { CoverageRing } from "@/components/extraction-coverage";
import { findPendingToolCall, phaseAt3, phaseTimeline, streamingToolActivity } from "@/src/core";
import { findPendingToolCall, phaseAt3, phaseTimeline } from "@/src/core";
import { slotsConfigured, type ModelsSettings } from "@/src/core";
import { useSettingsSaved } from "@/components/settings-signal";
@@ -375,13 +376,6 @@ export function Chat({
// session's current phase.
const phases = useMemo(() => phaseTimeline(messages), [messages]);
// A tool call still streaming its input: the teacher is mid-activity. The
// label names the ACTUAL tool (a long grep used to shimmer as “Writing a
// question…” for minutes — a part stays input-streaming through its whole
// execution, see streamingToolActivity); shown as the transcript shimmer,
// exactly like the thinking indicator.
const streamingActivity = !pending ? streamingToolActivity(messages) : null;
const [text, setText] = useState("");
/* The composer's value MIRROR ("nothing gets lost" round, owner
* 2026-10-01): a dictation flush lands via setState, which a closure
@@ -1170,6 +1164,14 @@ export function Chat({
return null;
}
default: {
// A tool call being WRITTEN (owner, streaming round 2026-10-01):
// input-streaming spans the whole execution, and the live card —
// spinner + shimmered activity label, the input watchable on expand
// — replaces the retired transcript bottom line. On settling, the
// registry's card takes over below.
if (part.type.startsWith("tool-") && part.state === "input-streaming") {
return <LiveToolCard key={pi} part={part as ArtifactPartProps["part"]} />;
}
// Artifact extension point (contracts.ts): job
// parts and future artifacts render via the
// registry — add renderers there, not here.
@@ -1187,8 +1189,16 @@ export function Chat({
</Fragment>
))}
{(status === "submitted" || turnInFlight) && <Shimmer className="text-sm">Thinking…</Shimmer>}
{streamingActivity && <Shimmer className="text-sm">{streamingActivity}</Shimmer>}
{/* The pre-first-chunk line only (owner, streaming round
2026-10-01): once a tool call starts writing, its LIVE card
carries the activity inline — the old bottom tool-activity
line retired rather than double-render. Same glimmer recipe
as the thinking row: duration 1. */}
{(status === "submitted" || turnInFlight) && (
<Shimmer duration={1} className="text-sm">
Thinking…
</Shimmer>
)}
{error && (
<div className="rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm">
Something went wrong: {error.message}