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:
parent
a746783c48
commit
81c4bfd7e3
30
apps/web/e2e/oo-chat-header.spec.ts
Normal file
30
apps/web/e2e/oo-chat-header.spec.ts
Normal 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");
|
||||
});
|
||||
@ -26,7 +26,9 @@ import { personaIdlePrompt, personaThinkingLines } from "../../lib/voice-persona
|
||||
import { VoiceAwarenessStrip } from "../voice/VoiceAwarenessStrip";
|
||||
import { StageHero } from "./StageHero";
|
||||
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({
|
||||
botId,
|
||||
botName,
|
||||
@ -36,7 +38,6 @@ export function OoStage({
|
||||
botStatus,
|
||||
snapshot,
|
||||
running,
|
||||
hasMessages,
|
||||
}: {
|
||||
botId: string;
|
||||
botName: string;
|
||||
@ -46,117 +47,24 @@ export function OoStage({
|
||||
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;
|
||||
|
||||
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`;
|
||||
const { persona, phase, pose, subtitle, energy, reducedMotion, awarenessEvent, awarenessLabel } =
|
||||
useOoPresence({
|
||||
botName,
|
||||
botTitle,
|
||||
botDescription,
|
||||
botStatus,
|
||||
snapshot,
|
||||
running,
|
||||
hasMessages: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<section
|
||||
data-testid="oo-stage"
|
||||
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
|
||||
phase={phase}
|
||||
@ -165,50 +73,7 @@ export function OoStage({
|
||||
energy={energy}
|
||||
/>
|
||||
|
||||
<div
|
||||
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}
|
||||
|
||||
<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">
|
||||
{awarenessLabel && awarenessEvent ? (
|
||||
<div className="mb-3 w-full">
|
||||
<VoiceAwarenessStrip
|
||||
@ -220,36 +85,94 @@ export function OoStage({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!hasMessages ? (
|
||||
<>
|
||||
<StageHero
|
||||
botId={botId}
|
||||
botColor={botColor}
|
||||
botStatus={botStatus}
|
||||
pose={pose}
|
||||
phase={phase}
|
||||
energy={energy}
|
||||
reducedMotion={reducedMotion}
|
||||
glowRgb={personaBloomGlow(persona.bloomAccent, energy)}
|
||||
cadenceMs={cadencePulseMs(persona.cadence)}
|
||||
/>
|
||||
<p
|
||||
key={subtitle}
|
||||
className={`mt-4 max-w-xl text-center text-ui-body leading-relaxed text-foreground/90 ${reducedMotion ? "" : "animate-blur-in"}`}
|
||||
>
|
||||
{subtitle}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<p className="mt-1 w-full max-w-xl truncate text-center text-ui-caption text-muted-foreground">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
<StageHero
|
||||
botId={botId}
|
||||
botColor={botColor}
|
||||
botStatus={botStatus}
|
||||
pose={pose}
|
||||
phase={phase}
|
||||
energy={energy}
|
||||
reducedMotion={reducedMotion}
|
||||
glowRgb={personaBloomGlow(persona.bloomAccent, energy)}
|
||||
cadenceMs={cadencePulseMs(persona.cadence)}
|
||||
/>
|
||||
<p
|
||||
key={subtitle}
|
||||
className={`mt-4 max-w-xl text-center text-ui-body leading-relaxed text-foreground/90 ${reducedMotion ? "" : "animate-blur-in"}`}
|
||||
>
|
||||
{subtitle}
|
||||
</p>
|
||||
</div>
|
||||
</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({
|
||||
members,
|
||||
workingBotIds,
|
||||
|
||||
133
apps/web/src/components/stage/use-oo-presence.ts
Normal file
133
apps/web/src/components/stage/use-oo-presence.ts
Normal 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;
|
||||
}
|
||||
@ -1040,10 +1040,6 @@ msgstr "Hase"
|
||||
msgid "Bushy"
|
||||
msgstr "Buschig"
|
||||
|
||||
#: src/pages/Shell.tsx
|
||||
msgid "Busy"
|
||||
msgstr "Beschäftigt"
|
||||
|
||||
#: src/pages/VoiceSettingsOverlay.tsx
|
||||
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."
|
||||
@ -1409,7 +1405,7 @@ msgstr "Kommunikationsstil"
|
||||
msgid "Complete"
|
||||
msgstr "Abschließen"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Composing a reply…"
|
||||
msgstr "Schreibt eine Antwort…"
|
||||
|
||||
@ -4855,7 +4851,7 @@ msgstr "Zart"
|
||||
msgid "Software update"
|
||||
msgstr "Software-Update"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Something needs your attention."
|
||||
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…"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Waiting for your input…"
|
||||
msgstr "Wartet auf deine Eingabe…"
|
||||
|
||||
@ -5706,7 +5703,7 @@ msgstr "Arbeitet"
|
||||
msgid "Working in my lane…"
|
||||
msgstr "Arbeite in meinem Fachgebiet…"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Working on your request…"
|
||||
msgstr "Arbeitet an deiner Anfrage…"
|
||||
|
||||
|
||||
@ -1040,10 +1040,6 @@ msgstr "Bunny"
|
||||
msgid "Bushy"
|
||||
msgstr "Bushy"
|
||||
|
||||
#: src/pages/Shell.tsx
|
||||
msgid "Busy"
|
||||
msgstr "Busy"
|
||||
|
||||
#: src/pages/VoiceSettingsOverlay.tsx
|
||||
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."
|
||||
@ -1409,7 +1405,7 @@ msgstr "Communication style"
|
||||
msgid "Complete"
|
||||
msgstr "Complete"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Composing a reply…"
|
||||
msgstr "Composing a reply…"
|
||||
|
||||
@ -4855,7 +4851,7 @@ msgstr "Soft"
|
||||
msgid "Software update"
|
||||
msgstr "Software update"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "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…"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Waiting for your input…"
|
||||
msgstr "Waiting for your input…"
|
||||
|
||||
@ -5706,7 +5703,7 @@ msgstr "Working"
|
||||
msgid "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…"
|
||||
msgstr "Working on your request…"
|
||||
|
||||
|
||||
@ -1040,10 +1040,6 @@ msgstr ""
|
||||
msgid "Bushy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/Shell.tsx
|
||||
msgid "Busy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/VoiceSettingsOverlay.tsx
|
||||
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
|
||||
msgstr ""
|
||||
@ -1409,7 +1405,7 @@ msgstr ""
|
||||
msgid "Complete"
|
||||
msgstr "Completado"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Composing a reply…"
|
||||
msgstr ""
|
||||
|
||||
@ -4855,7 +4851,7 @@ msgstr ""
|
||||
msgid "Software update"
|
||||
msgstr "Actualización de software"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Something needs your attention."
|
||||
msgstr ""
|
||||
|
||||
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
|
||||
msgstr "Esperando a que vuelva la API…"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Waiting for your input…"
|
||||
msgstr ""
|
||||
|
||||
@ -5706,7 +5703,7 @@ msgstr ""
|
||||
msgid "Working in my lane…"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Working on your request…"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@ -1040,10 +1040,6 @@ msgstr ""
|
||||
msgid "Bushy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/Shell.tsx
|
||||
msgid "Busy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/VoiceSettingsOverlay.tsx
|
||||
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
|
||||
msgstr ""
|
||||
@ -1409,7 +1405,7 @@ msgstr ""
|
||||
msgid "Complete"
|
||||
msgstr "पूर्ण"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Composing a reply…"
|
||||
msgstr ""
|
||||
|
||||
@ -4855,7 +4851,7 @@ msgstr ""
|
||||
msgid "Software update"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Something needs your attention."
|
||||
msgstr ""
|
||||
|
||||
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Waiting for your input…"
|
||||
msgstr ""
|
||||
|
||||
@ -5706,7 +5703,7 @@ msgstr ""
|
||||
msgid "Working in my lane…"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Working on your request…"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@ -1040,10 +1040,6 @@ msgstr ""
|
||||
msgid "Bushy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/Shell.tsx
|
||||
msgid "Busy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/VoiceSettingsOverlay.tsx
|
||||
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
|
||||
msgstr ""
|
||||
@ -1409,7 +1405,7 @@ msgstr ""
|
||||
msgid "Complete"
|
||||
msgstr "완료"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Composing a reply…"
|
||||
msgstr ""
|
||||
|
||||
@ -4855,7 +4851,7 @@ msgstr ""
|
||||
msgid "Software update"
|
||||
msgstr "소프트웨어 업데이트"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Something needs your attention."
|
||||
msgstr ""
|
||||
|
||||
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
|
||||
msgstr "API가 다시 작동하기를 기다리는 중…"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Waiting for your input…"
|
||||
msgstr ""
|
||||
|
||||
@ -5706,7 +5703,7 @@ msgstr ""
|
||||
msgid "Working in my lane…"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Working on your request…"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@ -1040,10 +1040,6 @@ msgstr ""
|
||||
msgid "Bushy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/Shell.tsx
|
||||
msgid "Busy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/VoiceSettingsOverlay.tsx
|
||||
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
|
||||
msgstr ""
|
||||
@ -1409,7 +1405,7 @@ msgstr ""
|
||||
msgid "Complete"
|
||||
msgstr "Concluir"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Composing a reply…"
|
||||
msgstr ""
|
||||
|
||||
@ -4855,7 +4851,7 @@ msgstr ""
|
||||
msgid "Software update"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Something needs your attention."
|
||||
msgstr ""
|
||||
|
||||
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Waiting for your input…"
|
||||
msgstr ""
|
||||
|
||||
@ -5706,7 +5703,7 @@ msgstr ""
|
||||
msgid "Working in my lane…"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Working on your request…"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@ -1040,10 +1040,6 @@ msgstr ""
|
||||
msgid "Bushy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/Shell.tsx
|
||||
msgid "Busy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/VoiceSettingsOverlay.tsx
|
||||
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
|
||||
msgstr ""
|
||||
@ -1409,7 +1405,7 @@ msgstr ""
|
||||
msgid "Complete"
|
||||
msgstr "Завершить"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Composing a reply…"
|
||||
msgstr ""
|
||||
|
||||
@ -4855,7 +4851,7 @@ msgstr ""
|
||||
msgid "Software update"
|
||||
msgstr "Обновление программного обеспечения"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Something needs your attention."
|
||||
msgstr ""
|
||||
|
||||
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
|
||||
msgstr "Ждём возвращения API…"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Waiting for your input…"
|
||||
msgstr ""
|
||||
|
||||
@ -5706,7 +5703,7 @@ msgstr ""
|
||||
msgid "Working in my lane…"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Working on your request…"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@ -1040,10 +1040,6 @@ msgstr ""
|
||||
msgid "Bushy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/Shell.tsx
|
||||
msgid "Busy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/VoiceSettingsOverlay.tsx
|
||||
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
|
||||
msgstr ""
|
||||
@ -1409,7 +1405,7 @@ msgstr ""
|
||||
msgid "Complete"
|
||||
msgstr "Tamamlandı"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Composing a reply…"
|
||||
msgstr ""
|
||||
|
||||
@ -4855,7 +4851,7 @@ msgstr ""
|
||||
msgid "Software update"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Something needs your attention."
|
||||
msgstr ""
|
||||
|
||||
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Waiting for your input…"
|
||||
msgstr ""
|
||||
|
||||
@ -5706,7 +5703,7 @@ msgstr ""
|
||||
msgid "Working in my lane…"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Working on your request…"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@ -1040,10 +1040,6 @@ msgstr ""
|
||||
msgid "Bushy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/Shell.tsx
|
||||
msgid "Busy"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/VoiceSettingsOverlay.tsx
|
||||
msgid "Calm audio cues for finished work, voice phases, and handoffs. Respects reduced motion."
|
||||
msgstr ""
|
||||
@ -1409,7 +1405,7 @@ msgstr ""
|
||||
msgid "Complete"
|
||||
msgstr "完成"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Composing a reply…"
|
||||
msgstr ""
|
||||
|
||||
@ -4855,7 +4851,7 @@ msgstr ""
|
||||
msgid "Software update"
|
||||
msgstr "软件更新"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Something needs your attention."
|
||||
msgstr ""
|
||||
|
||||
@ -5553,6 +5549,7 @@ msgid "Waiting for the API to come back…"
|
||||
msgstr "正在等待 API 恢复…"
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Waiting for your input…"
|
||||
msgstr ""
|
||||
|
||||
@ -5706,7 +5703,7 @@ msgstr ""
|
||||
msgid "Working in my lane…"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/stage/OoStage.tsx
|
||||
#: src/components/stage/use-oo-presence.ts
|
||||
msgid "Working on your request…"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@ -91,7 +91,7 @@ import { ArtifactPane } from "../components/ArtifactPane";
|
||||
import { InAppBrowser } from "../components/browser/InAppBrowser";
|
||||
import { ComputerUpdateProgress } from "../components/ComputerUpdateProgress";
|
||||
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 { useWideLayout } from "../hooks/useWideLayout";
|
||||
import { readActivityMode, writeActivityMode } from "../lib/activity-mode";
|
||||
@ -3357,10 +3357,16 @@ export function ShellPage() {
|
||||
? (activeGroup?.name ?? activeSnapshot?.groupName ?? t`Group`)
|
||||
: (active?.name ?? t`Choose a conversation`)}
|
||||
</span>
|
||||
{!inGroup && active && (composerRunning || active.status !== "idle") ? (
|
||||
<span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug">
|
||||
{composerRunning || active.status === "running" ? t`Busy` : active.status}
|
||||
</span>
|
||||
{!inGroup && active ? (
|
||||
<OoHeaderStatus
|
||||
botName={active.name}
|
||||
botTitle={active.title}
|
||||
botDescription={active.description}
|
||||
botStatus={stageWaitingStatus ?? active.status}
|
||||
snapshot={activeSnapshot}
|
||||
running={sending || stageRunning}
|
||||
hasMessages={transcriptMessages.length > 0}
|
||||
/>
|
||||
) : multiOoInGroup && projectSessionKey ? (
|
||||
<span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug">
|
||||
{t`Group conversation`}
|
||||
@ -3459,7 +3465,7 @@ export function ShellPage() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{!callOpen && active && !inGroup ? (
|
||||
{!callOpen && active && !inGroup && transcriptMessages.length === 0 ? (
|
||||
<OoStage
|
||||
botId={active.id}
|
||||
botName={active.name}
|
||||
@ -3469,7 +3475,6 @@ export function ShellPage() {
|
||||
botStatus={stageWaitingStatus ?? active.status}
|
||||
snapshot={activeSnapshot}
|
||||
running={stageRunning}
|
||||
hasMessages={transcriptMessages.length > 0}
|
||||
/>
|
||||
) : null}
|
||||
{!callOpen && inGroup ? (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user