import { Icon } from "./Icon";
import { useEffect, useState } from "react";
import { fmtCompact, useT } from "../i18n";
import { useStore, type Draft } from "../store";
import type { Part } from "../types";
/** Animated three-dot pulse. */
export function Dots() {
return (
);
}
export function Spinner({ small }: { small?: boolean }) {
return ;
}
/** Animated spark (SVG, looks the same on every system): rays light up in turn while it turns. */
export function Star() {
const rays = Array.from({ length: 8 }, (_, i) => i);
return (
);
}
/** Thought bubble icon for model reasoning (SVG instead of an emoji). */
export function ThinkIcon() {
return (
);
}
function useNow(active: boolean) {
const [now, setNow] = useState(Date.now());
useEffect(() => {
if (!active) return;
const h = setInterval(() => setNow(Date.now()), 1000);
return () => clearInterval(h);
}, [active]);
return now;
}
function fmtElapsed(ms: number) {
const s = Math.max(0, Math.floor(ms / 1000));
return s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${String(s % 60).padStart(2, "0")}s`;
}
/** Generated tokens so far, estimated from the streamed characters. */
function generatedTokens(draft?: Draft) {
if (!draft) return 0;
let chars = 0;
for (const p of draft.parts) {
if (p.type === "text" || p.type === "reasoning") chars += p.text.length;
else if (p.type === "tool_call") chars += JSON.stringify(p.input ?? {}).length;
}
return Math.round(chars / 4);
}
/** What the agent is doing right now – playful text, elapsed time and tokens. */
export function ActivityLine({
sessionId,
draft,
pendingTool,
waiting,
permissionPending,
}: {
sessionId: string;
draft?: Draft;
/** A tool call of the last (finished) model step that has no result yet. */
pendingTool?: { name: string; input: any; since: number };
waiting?: string;
permissionPending: boolean;
}) {
const t = useT();
const step = useStore((s) => s.steps[sessionId]);
const now = useNow((!!step || !!pendingTool) && !permissionPending);
if (permissionPending) {
return (
{t("activity.waitingForYou")}
);
}
if (waiting) {
const [kind, ...rest] = waiting.split(":");
const what = rest.join(":");
return (
{kind === "review"
? t("activity.reviewing", { round: what })
: kind === "model"
? t("chat.waitingModel", { model: what })
: t("chat.waitingSubagent", { agent: what === "*" ? t("chat.anySubagent") : what })}
);
}
// A tool is running between two model steps.
if (!draft && pendingTool) {
const label = pendingTool.name === "task" ? t("activity.subagent", { name: pendingTool.input?.description ?? "" }) : t("activity.tool", { tool: pendingTool.name });
return (