web: move the 1:1 Oo status into the chat header (#193)

The strip under the header of a running 1:1 chat repeated what the header
already carried (a "Present" pill plus a persona line). The header's second
line now shows what the Oo is doing (Working, Thinking, waiting for input,
needs attention, or a delegation), and the bot's role while it rests. The
stage only appears in an empty chat and no longer repeats the name.


Claude-Session: https://claude.ai/code/session_01H3buE7EyYCfz8nYccLCcct

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
cynarAI 2026-10-10 09:55:01 +02:00 committed by GitHub
parent a746783c48
commit 81c4bfd7e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 308 additions and 244 deletions

View File

@ -0,0 +1,30 @@
import { expect, test } from "@playwright/test";
import { captureScreenshot, completeOnboarding, signup } from "./helpers";
test("a 1:1 chat keeps name and status in the header instead of a banner", async ({
page,
}, testInfo) => {
await page.setViewportSize({ width: 390, height: 844 });
await signup(page, `oo-header-${Date.now()}@rakazo.test`, "password12", "Header QA");
await completeOnboarding(page);
const composer = page.getByRole("combobox", { name: /^Message/ });
await composer.fill("Please review `shared/PROJECT_CHECKPOINT_WRAPUP.md`.");
await composer.press("Enter");
await expect(
page
.getByTestId("transcript")
.locator("[data-message-id]")
.filter({ hasText: "done. i handled:" }),
).toBeVisible({ timeout: 30_000 });
await expect(page.getByTestId("oo-stage")).toHaveCount(0);
await expect(page.getByTestId("bot-settings-trigger")).toBeVisible();
await page.emulateMedia({ colorScheme: "light" });
await expect(page.locator("html")).toHaveAttribute("data-theme", "light");
await captureScreenshot(page, testInfo, "oo-chat-header-light");
await page.emulateMedia({ colorScheme: "dark" });
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
await captureScreenshot(page, testInfo, "oo-chat-header-dark");
});

View File

@ -26,7 +26,9 @@ import { personaIdlePrompt, personaThinkingLines } from "../../lib/voice-persona
import { VoiceAwarenessStrip } from "../voice/VoiceAwarenessStrip"; import { VoiceAwarenessStrip } from "../voice/VoiceAwarenessStrip";
import { StageHero } from "./StageHero"; import { StageHero } from "./StageHero";
import { TextStageCanvas } from "./TextStageCanvas"; import { TextStageCanvas } from "./TextStageCanvas";
import { useOoPresence } from "./use-oo-presence";
/** The empty 1:1 chat: the Oo greets. Name and live status live in the chat header. */
export function OoStage({ export function OoStage({
botId, botId,
botName, botName,
@ -36,7 +38,6 @@ export function OoStage({
botStatus, botStatus,
snapshot, snapshot,
running, running,
hasMessages,
}: { }: {
botId: string; botId: string;
botName: string; botName: string;
@ -46,117 +47,24 @@ export function OoStage({
botStatus?: string; botStatus?: string;
snapshot: ThreadSnapshot | null; snapshot: ThreadSnapshot | null;
running: boolean; running: boolean;
hasMessages: boolean;
}) { }) {
const { t } = useLingui(); const { t } = useLingui();
const [reducedMotion, setReducedMotion] = useState(false); const { persona, phase, pose, subtitle, energy, reducedMotion, awarenessEvent, awarenessLabel } =
const [thinkingIndex, setThinkingIndex] = useState(0); useOoPresence({
botName,
const persona = useMemo( botTitle,
() => botDescription,
resolveBotPersona({ botStatus,
name: botName, snapshot,
title: botTitle, running,
description: botDescription, hasMessages: false,
}), });
[botName, botTitle, botDescription],
);
const streaming = useMemo(
() =>
(snapshot?.messages ?? []).some(
(message) =>
message.id.startsWith("progress:") &&
message.blocks.some((block) => block.kind === "progress" || block.kind === "text"),
),
[snapshot?.messages],
);
const awarenessEvent = useMemo(
() => extractVoiceAwareness(snapshot, botName),
[snapshot, botName],
);
const collaborating = Boolean(awarenessEvent);
const phase = resolveTextStagePhase({
running,
botStatus,
streaming,
hasMessages,
awareness: awarenessEvent,
});
const pose =
phase === "idle" && !hasMessages
? "greeting"
: personaTextPose(
persona,
phase,
collaborating &&
(awarenessEvent?.kind === "subagent" ||
awarenessEvent?.kind === "handoff" ||
awarenessEvent?.kind === "delegation"),
);
const thinkingLines = useMemo(
() => personaThinkingLines(persona.archetype).map((line) => t(line)),
[persona.archetype, t],
);
useEffect(() => {
const media = window.matchMedia("(prefers-reduced-motion: reduce)");
function sync() {
setReducedMotion(media.matches);
}
sync();
media.addEventListener("change", sync);
return () => media.removeEventListener("change", sync);
}, []);
useEffect(() => {
if (phase !== "working" && phase !== "thinking") return;
const interval = setInterval(() => {
setThinkingIndex((index) => (index + 1) % Math.max(thinkingLines.length, 1));
}, cadencePulseMs(persona.cadence));
return () => clearInterval(interval);
}, [phase, persona.cadence, thinkingLines.length]);
const awarenessLabel = awarenessEvent
? narrateAwarenessForPersona(persona, awarenessEvent)
: null;
const subtitle = useMemo(() => {
if (awarenessLabel) return awarenessLabel;
if (phase === "error") return t`Something needs your attention.`;
if (phase === "working" || phase === "thinking") {
return thinkingLines[thinkingIndex] ?? t`Working on your request…`;
}
if (phase === "listening") return t`Waiting for your input…`;
if (phase === "speaking") return t`Composing a reply…`;
return t(personaIdlePrompt(persona.archetype));
}, [awarenessLabel, phase, persona.archetype, t, thinkingIndex, thinkingLines]);
const energy =
phase === "speaking" ? 0.65 : phase === "working" || phase === "thinking" ? 0.48 : 0.18;
const phaseLabel =
phase === "working"
? t`Working`
: phase === "listening"
? t`Listening`
: phase === "thinking"
? t`Thinking`
: phase === "speaking"
? t`Speaking`
: phase === "error"
? t`Needs attention`
: t`Present`;
return ( return (
<section <section
data-testid="oo-stage" data-testid="oo-stage"
aria-label={t`1:1 conversation`} aria-label={t`1:1 conversation`}
className={`relative shrink-0 overflow-hidden border-b border-border/60 ${hasMessages ? "max-h-[34vh]" : ""}`} className="relative shrink-0 overflow-hidden border-b border-border/60"
> >
<TextStageCanvas <TextStageCanvas
phase={phase} phase={phase}
@ -165,50 +73,7 @@ export function OoStage({
energy={energy} energy={energy}
/> />
<div <div className="relative z-10 mx-auto flex w-full max-w-3xl flex-col items-center px-4 py-5 md:px-8 md:py-6">
className={`relative z-10 mx-auto flex w-full max-w-3xl flex-col items-center px-4 md:px-8 ${hasMessages ? "py-2 md:py-3" : "py-5 md:py-6"}`}
>
{(hasMessages || phase !== "idle") && (
<div className={`flex w-full justify-center ${hasMessages ? "mb-1" : "mb-3"}`}>
<div
role="status"
className="inline-flex items-center gap-2 rounded-full border border-border bg-foreground/5 px-2.5 py-0.5 text-ui-label font-semibold uppercase tracking-wider text-muted-foreground backdrop-blur-sm"
style={
reducedMotion
? undefined
: {
animation: `voicePrimaryPulse ${cadencePulseMs(persona.cadence)}ms ease-in-out infinite`,
}
}
>
<span
className={`size-1.5 rounded-full ${
phase === "listening"
? "bg-foreground"
: phase === "speaking"
? "bg-success"
: phase === "error"
? "bg-destructive"
: "bg-foreground/70"
} ${reducedMotion ? "" : "animate-pulse"}`}
/>
<span>{phaseLabel}</span>
</div>
</div>
)}
{!hasMessages ? (
<div className="mb-3 text-center">
<h2 className="truncate text-ui-title font-semibold tracking-tight text-foreground">
{botName}
</h2>
{persona.roleLine ? (
<p className="mt-0.5 truncate text-ui-body-sm text-muted-foreground">
{persona.roleLine}
</p>
) : null}
</div>
) : null}
{awarenessLabel && awarenessEvent ? ( {awarenessLabel && awarenessEvent ? (
<div className="mb-3 w-full"> <div className="mb-3 w-full">
<VoiceAwarenessStrip <VoiceAwarenessStrip
@ -220,36 +85,94 @@ export function OoStage({
</div> </div>
) : null} ) : null}
{!hasMessages ? ( <StageHero
<> botId={botId}
<StageHero botColor={botColor}
botId={botId} botStatus={botStatus}
botColor={botColor} pose={pose}
botStatus={botStatus} phase={phase}
pose={pose} energy={energy}
phase={phase} reducedMotion={reducedMotion}
energy={energy} glowRgb={personaBloomGlow(persona.bloomAccent, energy)}
reducedMotion={reducedMotion} cadenceMs={cadencePulseMs(persona.cadence)}
glowRgb={personaBloomGlow(persona.bloomAccent, energy)} />
cadenceMs={cadencePulseMs(persona.cadence)} <p
/> key={subtitle}
<p className={`mt-4 max-w-xl text-center text-ui-body leading-relaxed text-foreground/90 ${reducedMotion ? "" : "animate-blur-in"}`}
key={subtitle} >
className={`mt-4 max-w-xl text-center text-ui-body leading-relaxed text-foreground/90 ${reducedMotion ? "" : "animate-blur-in"}`} {subtitle}
> </p>
{subtitle}
</p>
</>
) : (
<p className="mt-1 w-full max-w-xl truncate text-center text-ui-caption text-muted-foreground">
{subtitle}
</p>
)}
</div> </div>
</section> </section>
); );
} }
/** Second header line of a 1:1 chat: what the Oo is doing, or its role while it rests. */
export function OoHeaderStatus({
botName,
botTitle,
botDescription,
botStatus,
snapshot,
running,
hasMessages,
}: {
botName: string;
botTitle: string;
botDescription: string;
botStatus?: string;
snapshot: ThreadSnapshot | null;
running: boolean;
hasMessages: boolean;
}) {
const { t } = useLingui();
const { phase, awarenessLabel, reducedMotion } = useOoPresence({
botName,
botTitle,
botDescription,
botStatus,
snapshot,
running,
hasMessages,
});
const text =
phase === "idle"
? botTitle.trim()
: (awarenessLabel ??
(phase === "working"
? t`Working`
: phase === "thinking"
? t`Thinking`
: phase === "speaking"
? t`Speaking`
: phase === "listening"
? t`Waiting for your input…`
: t`Needs attention`));
if (!text) return null;
return (
<span
role="status"
data-testid="oo-header-status"
data-phase={phase}
className="flex min-w-0 items-center gap-1.5 text-ui-body-sm font-medium text-muted-foreground/80 leading-snug"
>
{phase === "idle" ? null : (
<span
aria-hidden="true"
className={`size-1.5 shrink-0 rounded-full ${
phase === "speaking"
? "bg-success"
: phase === "error"
? "bg-destructive"
: "bg-foreground/70"
} ${reducedMotion ? "" : "animate-pulse"}`}
/>
)}
<span className="truncate">{text}</span>
</span>
);
}
function StageEnsemble({ function StageEnsemble({
members, members,
workingBotIds, workingBotIds,

View File

@ -0,0 +1,133 @@
import { useLingui } from "@lingui/react/macro";
import type { ThreadSnapshot } from "@rakazo/contracts";
import { useEffect, useMemo, useState } from "react";
import {
cadencePulseMs,
extractVoiceAwareness,
narrateAwarenessForPersona,
personaTextPose,
resolveBotPersona,
resolveTextStagePhase,
} from "../../lib/bot-persona";
import { personaIdlePrompt, personaThinkingLines } from "../../lib/voice-persona-copy";
/** What a 1:1 Oo is doing right now, shared by the chat header and the empty-chat stage. */
export function useOoPresence({
botName,
botTitle,
botDescription,
botStatus,
snapshot,
running,
hasMessages,
}: {
botName: string;
botTitle: string;
botDescription: string;
botStatus?: string;
snapshot: ThreadSnapshot | null;
running: boolean;
hasMessages: boolean;
}) {
const { t } = useLingui();
const [reducedMotion, setReducedMotion] = useState(false);
const [thinkingIndex, setThinkingIndex] = useState(0);
const persona = useMemo(
() =>
resolveBotPersona({
name: botName,
title: botTitle,
description: botDescription,
}),
[botName, botTitle, botDescription],
);
const streaming = useMemo(
() =>
(snapshot?.messages ?? []).some(
(message) =>
message.id.startsWith("progress:") &&
message.blocks.some((block) => block.kind === "progress" || block.kind === "text"),
),
[snapshot?.messages],
);
const awarenessEvent = useMemo(
() => extractVoiceAwareness(snapshot, botName),
[snapshot, botName],
);
const collaborating = Boolean(awarenessEvent);
const phase = resolveTextStagePhase({
running,
botStatus,
streaming,
hasMessages,
awareness: awarenessEvent,
});
const pose =
phase === "idle" && !hasMessages
? "greeting"
: personaTextPose(
persona,
phase,
collaborating &&
(awarenessEvent?.kind === "subagent" ||
awarenessEvent?.kind === "handoff" ||
awarenessEvent?.kind === "delegation"),
);
const thinkingLines = useMemo(
() => personaThinkingLines(persona.archetype).map((line) => t(line)),
[persona.archetype, t],
);
useEffect(() => {
const media = window.matchMedia("(prefers-reduced-motion: reduce)");
function sync() {
setReducedMotion(media.matches);
}
sync();
media.addEventListener("change", sync);
return () => media.removeEventListener("change", sync);
}, []);
useEffect(() => {
if (phase !== "working" && phase !== "thinking") return;
const interval = setInterval(() => {
setThinkingIndex((index) => (index + 1) % Math.max(thinkingLines.length, 1));
}, cadencePulseMs(persona.cadence));
return () => clearInterval(interval);
}, [phase, persona.cadence, thinkingLines.length]);
const awarenessLabel = awarenessEvent
? narrateAwarenessForPersona(persona, awarenessEvent)
: null;
const subtitle = useMemo(() => {
if (awarenessLabel) return awarenessLabel;
if (phase === "error") return t`Something needs your attention.`;
if (phase === "working" || phase === "thinking") {
return thinkingLines[thinkingIndex] ?? t`Working on your request…`;
}
if (phase === "listening") return t`Waiting for your input…`;
if (phase === "speaking") return t`Composing a reply…`;
return t(personaIdlePrompt(persona.archetype));
}, [awarenessLabel, phase, persona.archetype, t, thinkingIndex, thinkingLines]);
const energy =
phase === "speaking" ? 0.65 : phase === "working" || phase === "thinking" ? 0.48 : 0.18;
return {
persona,
phase,
pose,
subtitle,
energy,
reducedMotion,
awarenessEvent,
awarenessLabel,
} as const;
}

View File

@ -1040,10 +1040,6 @@ msgstr "Hase"
msgid "Bushy" msgid "Bushy"
msgstr "Buschig" msgstr "Buschig"
#: src/pages/Shell.tsx
msgid "Busy"
msgstr "Beschäftigt"
#: src/pages/VoiceSettingsOverlay.tsx #: src/pages/VoiceSettingsOverlay.tsx
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion." msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
msgstr "Ruhige Hinweistöne für erledigte Arbeit, Sprachphasen und Übergaben. Berücksichtigt reduzierte Bewegung." msgstr "Ruhige Hinweistöne für erledigte Arbeit, Sprachphasen und Übergaben. Berücksichtigt reduzierte Bewegung."
@ -1409,7 +1405,7 @@ msgstr "Kommunikationsstil"
msgid "Complete" msgid "Complete"
msgstr "Abschließen" msgstr "Abschließen"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Composing a reply…" msgid "Composing a reply…"
msgstr "Schreibt eine Antwort…" msgstr "Schreibt eine Antwort…"
@ -4855,7 +4851,7 @@ msgstr "Zart"
msgid "Software update" msgid "Software update"
msgstr "Software-Update" msgstr "Software-Update"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "Etwas braucht deine Aufmerksamkeit." msgstr "Etwas braucht deine Aufmerksamkeit."
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
msgstr "Warte, bis die API wieder da ist…" msgstr "Warte, bis die API wieder da ist…"
#: src/components/stage/OoStage.tsx #: src/components/stage/OoStage.tsx
#: src/components/stage/use-oo-presence.ts
msgid "Waiting for your input…" msgid "Waiting for your input…"
msgstr "Wartet auf deine Eingabe…" msgstr "Wartet auf deine Eingabe…"
@ -5706,7 +5703,7 @@ msgstr "Arbeitet"
msgid "Working in my lane…" msgid "Working in my lane…"
msgstr "Arbeite in meinem Fachgebiet…" msgstr "Arbeite in meinem Fachgebiet…"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Working on your request…" msgid "Working on your request…"
msgstr "Arbeitet an deiner Anfrage…" msgstr "Arbeitet an deiner Anfrage…"

View File

@ -1040,10 +1040,6 @@ msgstr "Bunny"
msgid "Bushy" msgid "Bushy"
msgstr "Bushy" msgstr "Bushy"
#: src/pages/Shell.tsx
msgid "Busy"
msgstr "Busy"
#: src/pages/VoiceSettingsOverlay.tsx #: src/pages/VoiceSettingsOverlay.tsx
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion." msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
msgstr "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion." msgstr "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
@ -1409,7 +1405,7 @@ msgstr "Communication style"
msgid "Complete" msgid "Complete"
msgstr "Complete" msgstr "Complete"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Composing a reply…" msgid "Composing a reply…"
msgstr "Composing a reply…" msgstr "Composing a reply…"
@ -4855,7 +4851,7 @@ msgstr "Soft"
msgid "Software update" msgid "Software update"
msgstr "Software update" msgstr "Software update"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "Something needs your attention." msgstr "Something needs your attention."
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
msgstr "Waiting for the API to come back…" msgstr "Waiting for the API to come back…"
#: src/components/stage/OoStage.tsx #: src/components/stage/OoStage.tsx
#: src/components/stage/use-oo-presence.ts
msgid "Waiting for your input…" msgid "Waiting for your input…"
msgstr "Waiting for your input…" msgstr "Waiting for your input…"
@ -5706,7 +5703,7 @@ msgstr "Working"
msgid "Working in my lane…" msgid "Working in my lane…"
msgstr "Working in my lane…" msgstr "Working in my lane…"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Working on your request…" msgid "Working on your request…"
msgstr "Working on your request…" msgstr "Working on your request…"

View File

@ -1040,10 +1040,6 @@ msgstr ""
msgid "Bushy" msgid "Bushy"
msgstr "" msgstr ""
#: src/pages/Shell.tsx
msgid "Busy"
msgstr ""
#: src/pages/VoiceSettingsOverlay.tsx #: src/pages/VoiceSettingsOverlay.tsx
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion." msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
msgstr "" msgstr ""
@ -1409,7 +1405,7 @@ msgstr ""
msgid "Complete" msgid "Complete"
msgstr "Completado" msgstr "Completado"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Composing a reply…" msgid "Composing a reply…"
msgstr "" msgstr ""
@ -4855,7 +4851,7 @@ msgstr ""
msgid "Software update" msgid "Software update"
msgstr "Actualización de software" msgstr "Actualización de software"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
msgstr "Esperando a que vuelva la API…" msgstr "Esperando a que vuelva la API…"
#: src/components/stage/OoStage.tsx #: src/components/stage/OoStage.tsx
#: src/components/stage/use-oo-presence.ts
msgid "Waiting for your input…" msgid "Waiting for your input…"
msgstr "" msgstr ""
@ -5706,7 +5703,7 @@ msgstr ""
msgid "Working in my lane…" msgid "Working in my lane…"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Working on your request…" msgid "Working on your request…"
msgstr "" msgstr ""

View File

@ -1040,10 +1040,6 @@ msgstr ""
msgid "Bushy" msgid "Bushy"
msgstr "" msgstr ""
#: src/pages/Shell.tsx
msgid "Busy"
msgstr ""
#: src/pages/VoiceSettingsOverlay.tsx #: src/pages/VoiceSettingsOverlay.tsx
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion." msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
msgstr "" msgstr ""
@ -1409,7 +1405,7 @@ msgstr ""
msgid "Complete" msgid "Complete"
msgstr "पूर्ण" msgstr "पूर्ण"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Composing a reply…" msgid "Composing a reply…"
msgstr "" msgstr ""
@ -4855,7 +4851,7 @@ msgstr ""
msgid "Software update" msgid "Software update"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/OoStage.tsx
#: src/components/stage/use-oo-presence.ts
msgid "Waiting for your input…" msgid "Waiting for your input…"
msgstr "" msgstr ""
@ -5706,7 +5703,7 @@ msgstr ""
msgid "Working in my lane…" msgid "Working in my lane…"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Working on your request…" msgid "Working on your request…"
msgstr "" msgstr ""

View File

@ -1040,10 +1040,6 @@ msgstr ""
msgid "Bushy" msgid "Bushy"
msgstr "" msgstr ""
#: src/pages/Shell.tsx
msgid "Busy"
msgstr ""
#: src/pages/VoiceSettingsOverlay.tsx #: src/pages/VoiceSettingsOverlay.tsx
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion." msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
msgstr "" msgstr ""
@ -1409,7 +1405,7 @@ msgstr ""
msgid "Complete" msgid "Complete"
msgstr "완료" msgstr "완료"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Composing a reply…" msgid "Composing a reply…"
msgstr "" msgstr ""
@ -4855,7 +4851,7 @@ msgstr ""
msgid "Software update" msgid "Software update"
msgstr "소프트웨어 업데이트" msgstr "소프트웨어 업데이트"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
msgstr "API가 다시 작동하기를 기다리는 중…" msgstr "API가 다시 작동하기를 기다리는 중…"
#: src/components/stage/OoStage.tsx #: src/components/stage/OoStage.tsx
#: src/components/stage/use-oo-presence.ts
msgid "Waiting for your input…" msgid "Waiting for your input…"
msgstr "" msgstr ""
@ -5706,7 +5703,7 @@ msgstr ""
msgid "Working in my lane…" msgid "Working in my lane…"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Working on your request…" msgid "Working on your request…"
msgstr "" msgstr ""

View File

@ -1040,10 +1040,6 @@ msgstr ""
msgid "Bushy" msgid "Bushy"
msgstr "" msgstr ""
#: src/pages/Shell.tsx
msgid "Busy"
msgstr ""
#: src/pages/VoiceSettingsOverlay.tsx #: src/pages/VoiceSettingsOverlay.tsx
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion." msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
msgstr "" msgstr ""
@ -1409,7 +1405,7 @@ msgstr ""
msgid "Complete" msgid "Complete"
msgstr "Concluir" msgstr "Concluir"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Composing a reply…" msgid "Composing a reply…"
msgstr "" msgstr ""
@ -4855,7 +4851,7 @@ msgstr ""
msgid "Software update" msgid "Software update"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/OoStage.tsx
#: src/components/stage/use-oo-presence.ts
msgid "Waiting for your input…" msgid "Waiting for your input…"
msgstr "" msgstr ""
@ -5706,7 +5703,7 @@ msgstr ""
msgid "Working in my lane…" msgid "Working in my lane…"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Working on your request…" msgid "Working on your request…"
msgstr "" msgstr ""

View File

@ -1040,10 +1040,6 @@ msgstr ""
msgid "Bushy" msgid "Bushy"
msgstr "" msgstr ""
#: src/pages/Shell.tsx
msgid "Busy"
msgstr ""
#: src/pages/VoiceSettingsOverlay.tsx #: src/pages/VoiceSettingsOverlay.tsx
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion." msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
msgstr "" msgstr ""
@ -1409,7 +1405,7 @@ msgstr ""
msgid "Complete" msgid "Complete"
msgstr "Завершить" msgstr "Завершить"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Composing a reply…" msgid "Composing a reply…"
msgstr "" msgstr ""
@ -4855,7 +4851,7 @@ msgstr ""
msgid "Software update" msgid "Software update"
msgstr "Обновление программного обеспечения" msgstr "Обновление программного обеспечения"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
msgstr "Ждём возвращения API…" msgstr "Ждём возвращения API…"
#: src/components/stage/OoStage.tsx #: src/components/stage/OoStage.tsx
#: src/components/stage/use-oo-presence.ts
msgid "Waiting for your input…" msgid "Waiting for your input…"
msgstr "" msgstr ""
@ -5706,7 +5703,7 @@ msgstr ""
msgid "Working in my lane…" msgid "Working in my lane…"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Working on your request…" msgid "Working on your request…"
msgstr "" msgstr ""

View File

@ -1040,10 +1040,6 @@ msgstr ""
msgid "Bushy" msgid "Bushy"
msgstr "" msgstr ""
#: src/pages/Shell.tsx
msgid "Busy"
msgstr ""
#: src/pages/VoiceSettingsOverlay.tsx #: src/pages/VoiceSettingsOverlay.tsx
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion." msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
msgstr "" msgstr ""
@ -1409,7 +1405,7 @@ msgstr ""
msgid "Complete" msgid "Complete"
msgstr "Tamamlandı" msgstr "Tamamlandı"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Composing a reply…" msgid "Composing a reply…"
msgstr "" msgstr ""
@ -4855,7 +4851,7 @@ msgstr ""
msgid "Software update" msgid "Software update"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/OoStage.tsx
#: src/components/stage/use-oo-presence.ts
msgid "Waiting for your input…" msgid "Waiting for your input…"
msgstr "" msgstr ""
@ -5706,7 +5703,7 @@ msgstr ""
msgid "Working in my lane…" msgid "Working in my lane…"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Working on your request…" msgid "Working on your request…"
msgstr "" msgstr ""

View File

@ -1040,10 +1040,6 @@ msgstr ""
msgid "Bushy" msgid "Bushy"
msgstr "" msgstr ""
#: src/pages/Shell.tsx
msgid "Busy"
msgstr ""
#: src/pages/VoiceSettingsOverlay.tsx #: src/pages/VoiceSettingsOverlay.tsx
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion." msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
msgstr "" msgstr ""
@ -1409,7 +1405,7 @@ msgstr ""
msgid "Complete" msgid "Complete"
msgstr "完成" msgstr "完成"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Composing a reply…" msgid "Composing a reply…"
msgstr "" msgstr ""
@ -4855,7 +4851,7 @@ msgstr ""
msgid "Software update" msgid "Software update"
msgstr "软件更新" msgstr "软件更新"
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
msgstr "正在等待 API 恢复…" msgstr "正在等待 API 恢复…"
#: src/components/stage/OoStage.tsx #: src/components/stage/OoStage.tsx
#: src/components/stage/use-oo-presence.ts
msgid "Waiting for your input…" msgid "Waiting for your input…"
msgstr "" msgstr ""
@ -5706,7 +5703,7 @@ msgstr ""
msgid "Working in my lane…" msgid "Working in my lane…"
msgstr "" msgstr ""
#: src/components/stage/OoStage.tsx #: src/components/stage/use-oo-presence.ts
msgid "Working on your request…" msgid "Working on your request…"
msgstr "" msgstr ""

View File

@ -91,7 +91,7 @@ import { ArtifactPane } from "../components/ArtifactPane";
import { InAppBrowser } from "../components/browser/InAppBrowser"; import { InAppBrowser } from "../components/browser/InAppBrowser";
import { ComputerUpdateProgress } from "../components/ComputerUpdateProgress"; import { ComputerUpdateProgress } from "../components/ComputerUpdateProgress";
import { MainInbox } from "../components/MainInbox"; import { MainInbox } from "../components/MainInbox";
import { OoGroupStage, OoStage } from "../components/stage/OoStage"; import { OoGroupStage, OoHeaderStatus, OoStage } from "../components/stage/OoStage";
import { useSidebarResize } from "../hooks/useSidebarResize"; import { useSidebarResize } from "../hooks/useSidebarResize";
import { useWideLayout } from "../hooks/useWideLayout"; import { useWideLayout } from "../hooks/useWideLayout";
import { readActivityMode, writeActivityMode } from "../lib/activity-mode"; import { readActivityMode, writeActivityMode } from "../lib/activity-mode";
@ -3357,10 +3357,16 @@ export function ShellPage() {
? (activeGroup?.name ?? activeSnapshot?.groupName ?? t`Group`) ? (activeGroup?.name ?? activeSnapshot?.groupName ?? t`Group`)
: (active?.name ?? t`Choose a conversation`)} : (active?.name ?? t`Choose a conversation`)}
</span> </span>
{!inGroup && active && (composerRunning || active.status !== "idle") ? ( {!inGroup && active ? (
<span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug"> <OoHeaderStatus
{composerRunning || active.status === "running" ? t`Busy` : active.status} botName={active.name}
</span> botTitle={active.title}
botDescription={active.description}
botStatus={stageWaitingStatus ?? active.status}
snapshot={activeSnapshot}
running={sending || stageRunning}
hasMessages={transcriptMessages.length > 0}
/>
) : multiOoInGroup && projectSessionKey ? ( ) : multiOoInGroup && projectSessionKey ? (
<span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug"> <span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug">
{t`Group conversation`} {t`Group conversation`}
@ -3459,7 +3465,7 @@ export function ShellPage() {
</div> </div>
) : ( ) : (
<> <>
{!callOpen && active && !inGroup ? ( {!callOpen && active && !inGroup && transcriptMessages.length === 0 ? (
<OoStage <OoStage
botId={active.id} botId={active.id}
botName={active.name} botName={active.name}
@ -3469,7 +3475,6 @@ export function ShellPage() {
botStatus={stageWaitingStatus ?? active.status} botStatus={stageWaitingStatus ?? active.status}
snapshot={activeSnapshot} snapshot={activeSnapshot}
running={stageRunning} running={stageRunning}
hasMessages={transcriptMessages.length > 0}
/> />
) : null} ) : null}
{!callOpen && inGroup ? ( {!callOpen && inGroup ? (