Fix first-run onboarding UX: naming, locale, voices, Heute route, mobile drawer
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
This commit is contained in:
parent
ba89574c83
commit
225dc84b20
76
apps/web/e2e/first-run-onboarding-ux.spec.ts
Normal file
76
apps/web/e2e/first-run-onboarding-ux.spec.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
import { completeOnboarding, rpc, signup } from "./helpers";
|
||||||
|
|
||||||
|
test("onboarding names the first Oo and shows it in chat", async ({ page }) => {
|
||||||
|
const stamp = Date.now();
|
||||||
|
await signup(page, `name-oo-${stamp}@rakazo.test`, "password12", "Namer");
|
||||||
|
await page.waitForURL(/\/onboarding/, { timeout: 20_000 });
|
||||||
|
await page.getByRole("button", { name: "Continue", exact: true }).click();
|
||||||
|
await expect(page.getByTestId("onboarding-name-step")).toBeVisible();
|
||||||
|
await page.getByTestId("onboarding-oo-name-input").fill("Luna");
|
||||||
|
await page.getByTestId("onboarding-name-step").getByRole("button", { name: "Continue" }).click();
|
||||||
|
await page.getByRole("button", { name: /Skip/i }).click();
|
||||||
|
await page.getByRole("button", { name: "Skip", exact: true }).click();
|
||||||
|
await expect(page).toHaveURL(/\/app\/g\/[^/]+/, { timeout: 30_000 });
|
||||||
|
await expect(page.getByTestId("bot-settings-trigger").getByText("Luna", { exact: true })).toBeVisible();
|
||||||
|
expect((await rpc<{ name: string }>(page, "bots/ensureMain", {})).name).toBe("Luna");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("German UI keeps Deutsch on language skip label", async ({ page }) => {
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
Object.defineProperty(navigator, "language", { value: "de-DE", configurable: true });
|
||||||
|
});
|
||||||
|
const stamp = Date.now();
|
||||||
|
await signup(page, `de-skip-${stamp}@rakazo.test`, "password12", "Deutsch");
|
||||||
|
await page.getByRole("button", { name: "Continue", exact: true }).click();
|
||||||
|
await page.getByRole("button", { name: "Skip", exact: true }).click();
|
||||||
|
await expect(page.getByRole("heading", { name: /Sprache wählen|Choose your language/i })).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: /Überspringen · Deutsch/i })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("German voice step lists German voices after choosing Deutsch", async ({ page }) => {
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
Object.defineProperty(navigator, "language", { value: "de-DE", configurable: true });
|
||||||
|
});
|
||||||
|
const stamp = Date.now();
|
||||||
|
await signup(page, `de-voice-${stamp}@rakazo.test`, "password12", "Stimme");
|
||||||
|
await page.getByRole("button", { name: "Continue", exact: true }).click();
|
||||||
|
await page.getByRole("button", { name: "Skip", exact: true }).click();
|
||||||
|
await page.getByRole("button", { name: "Deutsch", exact: true }).click();
|
||||||
|
await expect(page.getByRole("button", { name: "Eva", exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Alloy", exact: true })).toHaveCount(0);
|
||||||
|
await expect(page.getByRole("button", { name: "Eva", exact: true })).toHaveAttribute(
|
||||||
|
"aria-pressed",
|
||||||
|
"true",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("main conversation does not show a group subtitle for one Oo", async ({ page }) => {
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
Object.defineProperty(navigator, "language", { value: "de-DE", configurable: true });
|
||||||
|
});
|
||||||
|
const stamp = Date.now();
|
||||||
|
await signup(page, `main-1-1-${stamp}@rakazo.test`, "password12", "Solo");
|
||||||
|
await completeOnboarding(page, undefined, { mainConversation: true });
|
||||||
|
await expect(page.getByTestId("bot-settings-trigger")).not.toContainText("Gruppenunterhaltung");
|
||||||
|
await expect(page.getByTestId("oo-group-stage")).toContainText(/1:1/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("mobile shell", () => {
|
||||||
|
test.use({
|
||||||
|
hasTouch: true,
|
||||||
|
isMobile: true,
|
||||||
|
viewport: { width: 390, height: 844 },
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Escape closes the mobile navigation drawer", async ({ page }) => {
|
||||||
|
const stamp = Date.now();
|
||||||
|
await signup(page, `mobile-esc-${stamp}@rakazo.test`, "password12", "Escape");
|
||||||
|
await completeOnboarding(page);
|
||||||
|
await page.getByRole("button", { name: "Open navigation" }).click();
|
||||||
|
await expect(page.getByRole("button", { name: "Close navigation" })).toBeVisible();
|
||||||
|
await page.keyboard.press("Escape");
|
||||||
|
await expect(page.getByRole("button", { name: "Close navigation" })).toHaveCount(0);
|
||||||
|
await expect(page.getByTestId("open-navigation")).toBeFocused();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -95,23 +95,25 @@ async function visible(locator: Locator, timeout: number) {
|
|||||||
export async function completeOnboarding(
|
export async function completeOnboarding(
|
||||||
page: Page,
|
page: Page,
|
||||||
testInfo?: TestInfo,
|
testInfo?: TestInfo,
|
||||||
options: { mainConversation?: boolean } = {},
|
options: { mainConversation?: boolean; ooName?: string } = {},
|
||||||
) {
|
) {
|
||||||
await page.waitForURL(/\/(onboarding|app)/, { timeout: 20_000 });
|
await page.waitForURL(/\/(onboarding|app)/, { timeout: 20_000 });
|
||||||
|
|
||||||
if (page.url().includes("/onboarding")) {
|
if (page.url().includes("/onboarding")) {
|
||||||
await expect(page.locator('[data-rakazo-surface="onboarding"]')).toHaveAttribute(
|
await expect(page.locator('[data-rakazo-surface="onboarding"]')).toHaveAttribute(
|
||||||
"data-onboarding-step",
|
"data-onboarding-step",
|
||||||
/^(emoji|language|voice|integrations|scan|bot)$/,
|
/^(emoji|name|language|voice|integrations|scan|bot)$/,
|
||||||
{ timeout: 30_000 },
|
{ timeout: 30_000 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const design = page.getByRole("heading", { name: /Design your Oo/i });
|
const design = page.getByRole("heading", { name: /Design your Oo/i });
|
||||||
|
const naming = page.getByTestId("onboarding-name-step");
|
||||||
const language = page.getByRole("heading", { name: /Choose your language/i });
|
const language = page.getByRole("heading", { name: /Choose your language/i });
|
||||||
const voice = page.getByRole("heading", { name: /Pick a voice/i });
|
const voice = page.getByRole("heading", { name: /Pick a voice/i });
|
||||||
const integrations = page.getByRole("heading", { name: "Server integrations", exact: true });
|
const integrations = page.getByRole("heading", { name: "Server integrations", exact: true });
|
||||||
const chief = page.getByText("Chief").first();
|
const expectedOoName = options.ooName ?? "Chief";
|
||||||
|
const chief = page.getByText(expectedOoName).first();
|
||||||
|
|
||||||
// Scripted CI has needsModel false, so a fresh owner starts on integrations.
|
// Scripted CI has needsModel false, so a fresh owner starts on integrations.
|
||||||
// A model-gated run still opens the look picker first.
|
// A model-gated run still opens the look picker first.
|
||||||
@ -120,6 +122,13 @@ export async function completeOnboarding(
|
|||||||
|
|
||||||
if (await design.isVisible()) {
|
if (await design.isVisible()) {
|
||||||
await page.getByRole("button", { name: "Continue", exact: true }).click();
|
await page.getByRole("button", { name: "Continue", exact: true }).click();
|
||||||
|
await expect(naming).toBeVisible();
|
||||||
|
if (options.ooName) {
|
||||||
|
await page.getByTestId("onboarding-oo-name-input").fill(options.ooName);
|
||||||
|
await naming.getByRole("button", { name: "Continue", exact: true }).click();
|
||||||
|
} else {
|
||||||
|
await page.getByRole("button", { name: "Skip", exact: true }).click();
|
||||||
|
}
|
||||||
await expect(language).toBeVisible();
|
await expect(language).toBeVisible();
|
||||||
await page.getByRole("button", { name: /Skip/i }).click();
|
await page.getByRole("button", { name: /Skip/i }).click();
|
||||||
await expect(voice).toBeVisible();
|
await expect(voice).toBeVisible();
|
||||||
|
|||||||
@ -18,7 +18,10 @@ test("Heute shows native brief instead of Mission Control iframe", async ({ page
|
|||||||
await completeOnboarding(page);
|
await completeOnboarding(page);
|
||||||
|
|
||||||
await openHeute(page);
|
await openHeute(page);
|
||||||
await expect(page).toHaveURL(/\/app\/[^/]+$/);
|
await expect(page).toHaveURL(/\/app\/heute$/);
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.getByTestId("heute-brief")).toBeVisible();
|
||||||
|
await expect(page).toHaveURL(/\/app\/heute$/);
|
||||||
|
|
||||||
const brief = page.getByTestId("heute-brief");
|
const brief = page.getByTestId("heute-brief");
|
||||||
await expect(brief).toBeVisible();
|
await expect(brief).toBeVisible();
|
||||||
|
|||||||
@ -11,7 +11,7 @@ test("focus choice suggests apps and preserves a completed connection", async ({
|
|||||||
const stamp = Date.now();
|
const stamp = Date.now();
|
||||||
await signup(page, `onboarding-${stamp}@rakazo.test`, "password12", "Robin");
|
await signup(page, `onboarding-${stamp}@rakazo.test`, "password12", "Robin");
|
||||||
// The main bot greets the user in the main conversation, where onboarding lands.
|
// The main bot greets the user in the main conversation, where onboarding lands.
|
||||||
await completeOnboarding(page, undefined, { mainConversation: true });
|
await completeOnboarding(page, undefined, { mainConversation: true, ooName: "Robin" });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText("Hey Robin. Fresh start on my side, so I’ll keep this short."),
|
page.getByText("Hey Robin. Fresh start on my side, so I’ll keep this short."),
|
||||||
@ -31,12 +31,11 @@ test("focus choice suggests apps and preserves a completed connection", async ({
|
|||||||
await captureScreenshot(page, testInfo, "choice-card-onboarding");
|
await captureScreenshot(page, testInfo, "choice-card-onboarding");
|
||||||
|
|
||||||
await page.getByTestId("focus-choice-day").click();
|
await page.getByTestId("focus-choice-day").click();
|
||||||
// The focus step suggests apps but must not rename the bot: the name the
|
// The focus step must not rename the bot: onboarding keeps the chosen name.
|
||||||
// user chose during creation ("Chief") is preserved.
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId("bot-settings-trigger").getByText("Chief", { exact: true }),
|
page.getByTestId("bot-settings-trigger").getByText("Robin", { exact: true }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
expect((await rpc<{ name: string }>(page, "bots/ensureMain", {})).name).toBe("Chief");
|
expect((await rpc<{ name: string }>(page, "bots/ensureMain", {})).name).toBe("Robin");
|
||||||
await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible();
|
await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible();
|
||||||
// The app cards speak for themselves; onboarding adds no text around them.
|
// The app cards speak for themselves; onboarding adds no text around them.
|
||||||
await expect(page.getByText(/^Got it/)).toHaveCount(0);
|
await expect(page.getByText(/^Got it/)).toHaveCount(0);
|
||||||
@ -103,7 +102,7 @@ test("focus choice suggests apps and preserves a completed connection", async ({
|
|||||||
|
|
||||||
test("choice refresh failures leave options available for retry", async ({ page }) => {
|
test("choice refresh failures leave options available for retry", async ({ page }) => {
|
||||||
await signup(page, `choice-refresh-${Date.now()}@rakazo.test`, "password12", "Choice Retry");
|
await signup(page, `choice-refresh-${Date.now()}@rakazo.test`, "password12", "Choice Retry");
|
||||||
await completeOnboarding(page, undefined, { mainConversation: true });
|
await completeOnboarding(page, undefined, { mainConversation: true, ooName: "Robin" });
|
||||||
const choice = page.getByTestId("focus-choice-day");
|
const choice = page.getByTestId("focus-choice-day");
|
||||||
await expect(choice).toBeEnabled();
|
await expect(choice).toBeEnabled();
|
||||||
// Keep the existing choice rendered while its save succeeds and navigation refresh fails.
|
// Keep the existing choice rendered while its save succeeds and navigation refresh fails.
|
||||||
|
|||||||
@ -120,6 +120,10 @@ function SessionApp() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="/app" element={user ? <ShellPage /> : <Navigate to="/sign-in" replace />} />
|
<Route path="/app" element={user ? <ShellPage /> : <Navigate to="/sign-in" replace />} />
|
||||||
|
<Route
|
||||||
|
path="/app/heute"
|
||||||
|
element={user ? <ShellPage /> : <Navigate to="/sign-in" replace />}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/app/g/:groupId"
|
path="/app/g/:groupId"
|
||||||
element={user ? <ShellPage /> : <Navigate to="/sign-in" replace />}
|
element={user ? <ShellPage /> : <Navigate to="/sign-in" replace />}
|
||||||
|
|||||||
98
apps/web/src/components/onboarding/OnboardingNameStep.tsx
Normal file
98
apps/web/src/components/onboarding/OnboardingNameStep.tsx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { Trans, useLingui } from "@lingui/react/macro";
|
||||||
|
import type { EvooPose } from "@rakazo/ui-web";
|
||||||
|
import { BotAvatar, Button } from "@rakazo/ui-web";
|
||||||
|
import { useId, useState } from "react";
|
||||||
|
import type { OnboardingEmojiConfig } from "./OnboardingOoDesigner";
|
||||||
|
|
||||||
|
export const DEFAULT_FIRST_OO_NAME = "Chief";
|
||||||
|
|
||||||
|
type OnboardingNameStepProps = {
|
||||||
|
config: OnboardingEmojiConfig;
|
||||||
|
name: string;
|
||||||
|
onNameChange: (name: string) => void;
|
||||||
|
onBack: () => void;
|
||||||
|
onContinue: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function OnboardingNameStep({
|
||||||
|
config,
|
||||||
|
name,
|
||||||
|
onNameChange,
|
||||||
|
onBack,
|
||||||
|
onContinue,
|
||||||
|
}: OnboardingNameStepProps) {
|
||||||
|
const { t } = useLingui();
|
||||||
|
const inputId = useId();
|
||||||
|
const [draft, setDraft] = useState(name);
|
||||||
|
|
||||||
|
function commitAndContinue() {
|
||||||
|
const trimmed = draft.trim() || DEFAULT_FIRST_OO_NAME;
|
||||||
|
onNameChange(trimmed);
|
||||||
|
onContinue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto max-w-xl" data-testid="onboarding-name-step">
|
||||||
|
<h2 className="text-xl font-semibold tracking-tight text-foreground">
|
||||||
|
<Trans>Name your Oo</Trans>
|
||||||
|
</h2>
|
||||||
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
|
<Trans>Something short and yours — or keep the default and rename later.</Trans>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-8 flex flex-col items-center gap-4 sm:flex-row sm:items-start">
|
||||||
|
<BotAvatar
|
||||||
|
color={config.avatarValue}
|
||||||
|
size={120}
|
||||||
|
identity="onboarding-name-preview"
|
||||||
|
pose={config.pose as EvooPose}
|
||||||
|
interactive
|
||||||
|
className="shrink-0"
|
||||||
|
/>
|
||||||
|
<div className="w-full min-w-0 flex-1">
|
||||||
|
<label htmlFor={inputId} className="sr-only">
|
||||||
|
<Trans>Oo name</Trans>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id={inputId}
|
||||||
|
data-testid="onboarding-oo-name-input"
|
||||||
|
type="text"
|
||||||
|
maxLength={48}
|
||||||
|
autoComplete="off"
|
||||||
|
autoCapitalize="words"
|
||||||
|
placeholder={DEFAULT_FIRST_OO_NAME}
|
||||||
|
value={draft}
|
||||||
|
onChange={(event) => setDraft(event.target.value)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === "Enter") commitAndContinue();
|
||||||
|
}}
|
||||||
|
className="w-full rounded-2xl border-2 border-border bg-card px-4 py-3 text-lg font-semibold tracking-tight text-foreground shadow-sm outline-none transition-colors placeholder:text-muted-foreground/60 focus:border-primary"
|
||||||
|
/>
|
||||||
|
<p className="mt-2 text-xs text-muted-foreground">
|
||||||
|
{t`Press Enter or Continue — skip keeps ${DEFAULT_FIRST_OO_NAME}.`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 flex items-center justify-between">
|
||||||
|
<Button variant="ghost" size="sm" onClick={onBack}>
|
||||||
|
<Trans>Back</Trans>
|
||||||
|
</Button>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => {
|
||||||
|
onNameChange(DEFAULT_FIRST_OO_NAME);
|
||||||
|
onContinue();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trans>Skip</Trans>
|
||||||
|
</Button>
|
||||||
|
<Button onClick={commitAndContinue}>
|
||||||
|
<Trans>Continue</Trans>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,15 +1,20 @@
|
|||||||
import { Trans } from "@lingui/react/macro";
|
import { Trans } from "@lingui/react/macro";
|
||||||
import type { VoiceInfo } from "@rakazo/contracts";
|
import type { VoiceInfo } from "@rakazo/contracts";
|
||||||
import { defaultVoiceForLocale, voicesForLocale } from "@rakazo/core";
|
import { voicesForLocale } from "@rakazo/core";
|
||||||
import { Button } from "@rakazo/ui-web";
|
import { Button } from "@rakazo/ui-web";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import {
|
||||||
|
defaultOnboardingVoiceId,
|
||||||
|
offlineOnboardingVoices,
|
||||||
|
} from "../../lib/onboarding-voices";
|
||||||
import { rpc } from "../../lib/rpc";
|
import { rpc } from "../../lib/rpc";
|
||||||
import { readUiLocale } from "../../lib/ui-locale";
|
import type { UiLocale } from "../../lib/ui-locale";
|
||||||
|
|
||||||
/** Must match packages/adapters KokoroVoiceProvider placeholder. */
|
/** Must match packages/adapters KokoroVoiceProvider placeholder. */
|
||||||
export const KOKORO_LOCAL_TOKEN = "local-kokoro";
|
export const KOKORO_LOCAL_TOKEN = "local-kokoro";
|
||||||
|
|
||||||
type OnboardingVoiceStepProps = {
|
type OnboardingVoiceStepProps = {
|
||||||
|
uiLocale: UiLocale;
|
||||||
selectedVoiceId: string;
|
selectedVoiceId: string;
|
||||||
onSelectedVoiceIdChange: (voiceId: string) => void;
|
onSelectedVoiceIdChange: (voiceId: string) => void;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
@ -18,6 +23,7 @@ type OnboardingVoiceStepProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function OnboardingVoiceStep({
|
export function OnboardingVoiceStep({
|
||||||
|
uiLocale,
|
||||||
selectedVoiceId,
|
selectedVoiceId,
|
||||||
onSelectedVoiceIdChange,
|
onSelectedVoiceIdChange,
|
||||||
onBack,
|
onBack,
|
||||||
@ -36,13 +42,17 @@ export function OnboardingVoiceStep({
|
|||||||
await rpc.voice.connect({ provider: "kokoro", apiKey: KOKORO_LOCAL_TOKEN });
|
await rpc.voice.connect({ provider: "kokoro", apiKey: KOKORO_LOCAL_TOKEN });
|
||||||
const listed = await rpc.voice.voices({ provider: "kokoro" });
|
const listed = await rpc.voice.voices({ provider: "kokoro" });
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
const locale = readUiLocale();
|
const ordered = voicesForLocale(listed, uiLocale).slice(0, 6);
|
||||||
const ordered = voicesForLocale(listed, locale).slice(0, 6);
|
|
||||||
setVoiceOptions(ordered);
|
setVoiceOptions(ordered);
|
||||||
const defaultVoice = defaultVoiceForLocale(listed, locale);
|
const nextId = defaultOnboardingVoiceId(uiLocale, ordered);
|
||||||
if (defaultVoice) onSelectedVoiceIdChange(defaultVoice.id);
|
if (nextId) onSelectedVoiceIdChange(nextId);
|
||||||
} catch {
|
} catch {
|
||||||
if (!cancelled) setVoiceOptions([]);
|
if (!cancelled) {
|
||||||
|
const fallback = offlineOnboardingVoices(uiLocale);
|
||||||
|
setVoiceOptions(fallback);
|
||||||
|
const nextId = defaultOnboardingVoiceId(uiLocale, fallback);
|
||||||
|
if (nextId) onSelectedVoiceIdChange(nextId);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (!cancelled) setVoiceLoading(false);
|
if (!cancelled) setVoiceLoading(false);
|
||||||
}
|
}
|
||||||
@ -54,7 +64,7 @@ export function OnboardingVoiceStep({
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [onSelectedVoiceIdChange]);
|
}, [onSelectedVoiceIdChange, uiLocale]);
|
||||||
|
|
||||||
async function skipVoice() {
|
async function skipVoice() {
|
||||||
await previewTask.current;
|
await previewTask.current;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useLingui } from "@lingui/react/macro";
|
import { useLingui } from "@lingui/react/macro";
|
||||||
|
import { isMultiOoGroupChat } from "../../lib/group-chat";
|
||||||
import type { ThreadSnapshot } from "@rakazo/contracts";
|
import type { ThreadSnapshot } from "@rakazo/contracts";
|
||||||
import { BotAvatar } from "@rakazo/ui-web";
|
import { BotAvatar } from "@rakazo/ui-web";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
@ -317,6 +318,7 @@ export function OoGroupStage({
|
|||||||
[snapshot, speaker, enrichMember],
|
[snapshot, speaker, enrichMember],
|
||||||
);
|
);
|
||||||
const roster = snapshot?.members ?? [];
|
const roster = snapshot?.members ?? [];
|
||||||
|
const multiOo = isMultiOoGroupChat(roster.length);
|
||||||
const groupSpark = useMemo(
|
const groupSpark = useMemo(
|
||||||
() => extractGroupSpark(snapshot, workingBots.length),
|
() => extractGroupSpark(snapshot, workingBots.length),
|
||||||
[snapshot, workingBots.length],
|
[snapshot, workingBots.length],
|
||||||
@ -405,7 +407,7 @@ export function OoGroupStage({
|
|||||||
if (sparkLabel && groupSpark?.kind === "peer-reply") return sparkLabel;
|
if (sparkLabel && groupSpark?.kind === "peer-reply") return sparkLabel;
|
||||||
if (awarenessLabel) return awarenessLabel;
|
if (awarenessLabel) return awarenessLabel;
|
||||||
if (sparkLabel) return sparkLabel;
|
if (sparkLabel) return sparkLabel;
|
||||||
if (!speaker) return t`Group conversation`;
|
if (!speaker) return multiOo ? t`Group conversation` : t`1:1 conversation`;
|
||||||
if (running && workingBots.length > 1) {
|
if (running && workingBots.length > 1) {
|
||||||
return t`${workingBots.length} Oos are working — ${speaker.name} has the floor`;
|
return t`${workingBots.length} Oos are working — ${speaker.name} has the floor`;
|
||||||
}
|
}
|
||||||
@ -429,8 +431,15 @@ export function OoGroupStage({
|
|||||||
thinkingIndex,
|
thinkingIndex,
|
||||||
thinkingLines,
|
thinkingLines,
|
||||||
workingBots.length,
|
workingBots.length,
|
||||||
|
multiOo,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const headerSubtitle = multiOo
|
||||||
|
? t`Group conversation`
|
||||||
|
: speaker
|
||||||
|
? t`1:1 with ${speaker.name}`
|
||||||
|
: t`1:1 conversation`;
|
||||||
|
|
||||||
const energy = groupStageEnergy(phase, groupSpark, workingBots.length);
|
const energy = groupStageEnergy(phase, groupSpark, workingBots.length);
|
||||||
|
|
||||||
const phaseLabel = showEnsembleOnly
|
const phaseLabel = showEnsembleOnly
|
||||||
@ -454,7 +463,7 @@ export function OoGroupStage({
|
|||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
data-testid="oo-group-stage"
|
data-testid="oo-group-stage"
|
||||||
aria-label={t`Group conversation`}
|
aria-label={multiOo ? t`Group conversation` : t`1:1 conversation`}
|
||||||
className={`relative shrink-0 overflow-hidden border-b border-border/60 ${hasMessages ? "max-h-[28vh]" : ""}`}
|
className={`relative shrink-0 overflow-hidden border-b border-border/60 ${hasMessages ? "max-h-[28vh]" : ""}`}
|
||||||
>
|
>
|
||||||
<TextStageCanvas
|
<TextStageCanvas
|
||||||
@ -479,7 +488,7 @@ export function OoGroupStage({
|
|||||||
<h2 className="mt-1.5 truncate text-ui-title font-semibold tracking-tight text-foreground">
|
<h2 className="mt-1.5 truncate text-ui-title font-semibold tracking-tight text-foreground">
|
||||||
{groupName ?? t`Group`}
|
{groupName ?? t`Group`}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="truncate text-ui-body-sm text-muted-foreground">{t`Group conversation`}</p>
|
<p className="truncate text-ui-body-sm text-muted-foreground">{headerSubtitle}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{awarenessLabel && awarenessEvent && speaker ? (
|
{awarenessLabel && awarenessEvent && speaker ? (
|
||||||
|
|||||||
5
apps/web/src/lib/app-routes.ts
Normal file
5
apps/web/src/lib/app-routes.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const HEUTE_APP_PATH = "/app/heute";
|
||||||
|
|
||||||
|
export function isHeuteAppPath(pathname: string): boolean {
|
||||||
|
return pathname === HEUTE_APP_PATH || pathname.endsWith(`${HEUTE_APP_PATH}`);
|
||||||
|
}
|
||||||
14
apps/web/src/lib/group-chat.test.ts
Normal file
14
apps/web/src/lib/group-chat.test.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { isMultiOoGroupChat } from "./group-chat.js";
|
||||||
|
|
||||||
|
describe("isMultiOoGroupChat", () => {
|
||||||
|
it("treats a single Oo as 1:1", () => {
|
||||||
|
expect(isMultiOoGroupChat(0)).toBe(false);
|
||||||
|
expect(isMultiOoGroupChat(1)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats two or more Oos as a group", () => {
|
||||||
|
expect(isMultiOoGroupChat(2)).toBe(true);
|
||||||
|
expect(isMultiOoGroupChat(5)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
4
apps/web/src/lib/group-chat.ts
Normal file
4
apps/web/src/lib/group-chat.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/** True when the encounter has more than one Oo (group chat), not a 1:1 thread. */
|
||||||
|
export function isMultiOoGroupChat(memberCount: number): boolean {
|
||||||
|
return memberCount > 1;
|
||||||
|
}
|
||||||
25
apps/web/src/lib/onboarding-voices.test.ts
Normal file
25
apps/web/src/lib/onboarding-voices.test.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { defaultOnboardingVoiceId, offlineOnboardingVoices } from "./onboarding-voices.js";
|
||||||
|
|
||||||
|
describe("offlineOnboardingVoices", () => {
|
||||||
|
it("offers German voices for de", () => {
|
||||||
|
expect(offlineOnboardingVoices("de").map((voice) => voice.id)).toEqual([
|
||||||
|
"df_eva",
|
||||||
|
"dm_bernd",
|
||||||
|
"df_victoria",
|
||||||
|
"dm_martin",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("offers English voices for en", () => {
|
||||||
|
const ids = offlineOnboardingVoices("en").map((voice) => voice.id);
|
||||||
|
expect(ids).toContain("af_heart");
|
||||||
|
expect(ids).not.toContain("df_eva");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("defaultOnboardingVoiceId", () => {
|
||||||
|
it("prefers df_eva for German", () => {
|
||||||
|
expect(defaultOnboardingVoiceId("de", offlineOnboardingVoices("de"))).toBe("df_eva");
|
||||||
|
});
|
||||||
|
});
|
||||||
23
apps/web/src/lib/onboarding-voices.ts
Normal file
23
apps/web/src/lib/onboarding-voices.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import type { VoiceInfo } from "@rakazo/contracts";
|
||||||
|
import { defaultVoiceForLocale, GERMAN_VOICE_PRESETS, voicesForLocale } from "@rakazo/core";
|
||||||
|
|
||||||
|
/** English presets when the local voice server is unreachable during onboarding. */
|
||||||
|
const ENGLISH_ONBOARDING_VOICES: VoiceInfo[] = [
|
||||||
|
{ id: "af_heart", label: "Heart", description: "warm" },
|
||||||
|
{ id: "af_bella", label: "Bella", description: "expressive" },
|
||||||
|
{ id: "af_sky", label: "Sky", description: "bright" },
|
||||||
|
{ id: "af_nova", label: "Nova", description: "energetic" },
|
||||||
|
{ id: "am_adam", label: "Adam", description: "clear" },
|
||||||
|
{ id: "am_michael", label: "Michael", description: "steady" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const OFFLINE_CATALOG = [...GERMAN_VOICE_PRESETS, ...ENGLISH_ONBOARDING_VOICES];
|
||||||
|
|
||||||
|
/** Locale-aware voice choices for onboarding when listing from the server fails. */
|
||||||
|
export function offlineOnboardingVoices(locale: string): VoiceInfo[] {
|
||||||
|
return voicesForLocale(OFFLINE_CATALOG, locale).slice(0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultOnboardingVoiceId(locale: string, voices: VoiceInfo[]): string {
|
||||||
|
return defaultVoiceForLocale(voices, locale)?.id ?? "";
|
||||||
|
}
|
||||||
@ -303,6 +303,13 @@ msgstr "1:1-Unterhaltung"
|
|||||||
msgid "1:1 conversation with {title}"
|
msgid "1:1 conversation with {title}"
|
||||||
msgstr "1:1-Unterhaltung mit {title}"
|
msgstr "1:1-Unterhaltung mit {title}"
|
||||||
|
|
||||||
|
#. placeholder {0}: mainConversationEntry.bot.name
|
||||||
|
#. placeholder {0}: speaker.name
|
||||||
|
#: src/components/stage/OoStage.tsx
|
||||||
|
#: src/pages/Shell.tsx
|
||||||
|
msgid "1:1 with {0}"
|
||||||
|
msgstr "1:1 mit {0}"
|
||||||
|
|
||||||
#: src/pages/shell/bot-picker.tsx
|
#: src/pages/shell/bot-picker.tsx
|
||||||
msgid "About groups"
|
msgid "About groups"
|
||||||
msgstr "Über Gruppen"
|
msgstr "Über Gruppen"
|
||||||
@ -783,6 +790,7 @@ msgstr "Verfügbar, sobald die Routine gespeichert ist"
|
|||||||
|
|
||||||
#: src/components/browser/InAppBrowser.tsx
|
#: src/components/browser/InAppBrowser.tsx
|
||||||
#: src/components/CookingMode.tsx
|
#: src/components/CookingMode.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
#: src/pages/PluginsOverlay.tsx
|
#: src/pages/PluginsOverlay.tsx
|
||||||
@ -1423,6 +1431,7 @@ msgid "Context limit"
|
|||||||
msgstr "Kontextlimit"
|
msgstr "Kontextlimit"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
@ -3317,6 +3326,10 @@ msgstr "Gruppe benennen"
|
|||||||
msgid "Name this routine"
|
msgid "Name this routine"
|
||||||
msgstr "Routine benennen"
|
msgstr "Routine benennen"
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Name your Oo"
|
||||||
|
msgstr "Gib deinem Oo einen Namen"
|
||||||
|
|
||||||
#: src/components/OoAtelier.tsx
|
#: src/components/OoAtelier.tsx
|
||||||
msgid "Neck"
|
msgid "Neck"
|
||||||
msgstr "Hals"
|
msgstr "Hals"
|
||||||
@ -3627,6 +3640,10 @@ msgstr "Nur leere Spaces können gelöscht werden. Lösche zuerst die Bots und G
|
|||||||
msgid "Only file and folder names are read."
|
msgid "Only file and folder names are read."
|
||||||
msgstr "Gelesen werden nur Datei- und Ordnernamen."
|
msgstr "Gelesen werden nur Datei- und Ordnernamen."
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Oo name"
|
||||||
|
msgstr "Name des Oo"
|
||||||
|
|
||||||
#: src/pages/ScratchpadSection.tsx
|
#: src/pages/ScratchpadSection.tsx
|
||||||
#: src/pages/shell/computer-panel-preview.tsx
|
#: src/pages/shell/computer-panel-preview.tsx
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
@ -3833,6 +3850,10 @@ msgstr "Vorlieben"
|
|||||||
msgid "Present"
|
msgid "Present"
|
||||||
msgstr "Anwesend"
|
msgstr "Anwesend"
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
|
||||||
|
msgstr "Enter oder Weiter — Überspringen behält Chief."
|
||||||
|
|
||||||
#. placeholder {0}: props.name
|
#. placeholder {0}: props.name
|
||||||
#: src/components/ArtifactFileCard.tsx
|
#: src/components/ArtifactFileCard.tsx
|
||||||
msgid "Preview {0}"
|
msgid "Preview {0}"
|
||||||
@ -4574,6 +4595,7 @@ msgid "Skills"
|
|||||||
msgstr "Skills"
|
msgstr "Skills"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
@ -4581,9 +4603,10 @@ msgstr "Skills"
|
|||||||
msgid "Skip"
|
msgid "Skip"
|
||||||
msgstr "Überspringen"
|
msgstr "Überspringen"
|
||||||
|
|
||||||
|
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
msgid "Skip · English"
|
msgid "Skip · {0}"
|
||||||
msgstr "Überspringen · Englisch"
|
msgstr "Überspringen · {0}"
|
||||||
|
|
||||||
#: src/lib/localized-provider-hint.ts
|
#: src/lib/localized-provider-hint.ts
|
||||||
msgid "Skip or deploy key"
|
msgid "Skip or deploy key"
|
||||||
@ -4614,6 +4637,10 @@ msgstr "Software-Update"
|
|||||||
msgid "Something needs your attention."
|
msgid "Something needs your attention."
|
||||||
msgstr "Etwas braucht deine Aufmerksamkeit."
|
msgstr "Etwas braucht deine Aufmerksamkeit."
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Something short and yours — or keep the default and rename later."
|
||||||
|
msgstr "Kurz und persönlich — oder Standard behalten und später umbenennen."
|
||||||
|
|
||||||
#: src/lib/voice-persona-copy.ts
|
#: src/lib/voice-persona-copy.ts
|
||||||
msgid "Sorting priorities…"
|
msgid "Sorting priorities…"
|
||||||
msgstr "Sortiere die Prioritäten…"
|
msgstr "Sortiere die Prioritäten…"
|
||||||
|
|||||||
@ -303,6 +303,13 @@ msgstr "1:1 conversation"
|
|||||||
msgid "1:1 conversation with {title}"
|
msgid "1:1 conversation with {title}"
|
||||||
msgstr "1:1 conversation with {title}"
|
msgstr "1:1 conversation with {title}"
|
||||||
|
|
||||||
|
#. placeholder {0}: mainConversationEntry.bot.name
|
||||||
|
#. placeholder {0}: speaker.name
|
||||||
|
#: src/components/stage/OoStage.tsx
|
||||||
|
#: src/pages/Shell.tsx
|
||||||
|
msgid "1:1 with {0}"
|
||||||
|
msgstr "1:1 with {0}"
|
||||||
|
|
||||||
#: src/pages/shell/bot-picker.tsx
|
#: src/pages/shell/bot-picker.tsx
|
||||||
msgid "About groups"
|
msgid "About groups"
|
||||||
msgstr "About groups"
|
msgstr "About groups"
|
||||||
@ -783,6 +790,7 @@ msgstr "Available after the routine is saved"
|
|||||||
|
|
||||||
#: src/components/browser/InAppBrowser.tsx
|
#: src/components/browser/InAppBrowser.tsx
|
||||||
#: src/components/CookingMode.tsx
|
#: src/components/CookingMode.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
#: src/pages/PluginsOverlay.tsx
|
#: src/pages/PluginsOverlay.tsx
|
||||||
@ -1423,6 +1431,7 @@ msgid "Context limit"
|
|||||||
msgstr "Context limit"
|
msgstr "Context limit"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
@ -3317,6 +3326,10 @@ msgstr "Name this group"
|
|||||||
msgid "Name this routine"
|
msgid "Name this routine"
|
||||||
msgstr "Name this routine"
|
msgstr "Name this routine"
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Name your Oo"
|
||||||
|
msgstr "Name your Oo"
|
||||||
|
|
||||||
#: src/components/OoAtelier.tsx
|
#: src/components/OoAtelier.tsx
|
||||||
msgid "Neck"
|
msgid "Neck"
|
||||||
msgstr "Neck"
|
msgstr "Neck"
|
||||||
@ -3627,6 +3640,10 @@ msgstr "Only empty spaces can be deleted. Delete its bots and groups first."
|
|||||||
msgid "Only file and folder names are read."
|
msgid "Only file and folder names are read."
|
||||||
msgstr "Only file and folder names are read."
|
msgstr "Only file and folder names are read."
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Oo name"
|
||||||
|
msgstr "Oo name"
|
||||||
|
|
||||||
#: src/pages/ScratchpadSection.tsx
|
#: src/pages/ScratchpadSection.tsx
|
||||||
#: src/pages/shell/computer-panel-preview.tsx
|
#: src/pages/shell/computer-panel-preview.tsx
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
@ -3833,6 +3850,10 @@ msgstr "Preferences"
|
|||||||
msgid "Present"
|
msgid "Present"
|
||||||
msgstr "Present"
|
msgstr "Present"
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
|
||||||
|
msgstr "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
|
||||||
|
|
||||||
#. placeholder {0}: props.name
|
#. placeholder {0}: props.name
|
||||||
#: src/components/ArtifactFileCard.tsx
|
#: src/components/ArtifactFileCard.tsx
|
||||||
msgid "Preview {0}"
|
msgid "Preview {0}"
|
||||||
@ -4574,6 +4595,7 @@ msgid "Skills"
|
|||||||
msgstr "Skills"
|
msgstr "Skills"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
@ -4581,9 +4603,10 @@ msgstr "Skills"
|
|||||||
msgid "Skip"
|
msgid "Skip"
|
||||||
msgstr "Skip"
|
msgstr "Skip"
|
||||||
|
|
||||||
|
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
msgid "Skip · English"
|
msgid "Skip · {0}"
|
||||||
msgstr "Skip · English"
|
msgstr "Skip · {0}"
|
||||||
|
|
||||||
#: src/lib/localized-provider-hint.ts
|
#: src/lib/localized-provider-hint.ts
|
||||||
msgid "Skip or deploy key"
|
msgid "Skip or deploy key"
|
||||||
@ -4614,6 +4637,10 @@ msgstr "Software update"
|
|||||||
msgid "Something needs your attention."
|
msgid "Something needs your attention."
|
||||||
msgstr "Something needs your attention."
|
msgstr "Something needs your attention."
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Something short and yours — or keep the default and rename later."
|
||||||
|
msgstr "Something short and yours — or keep the default and rename later."
|
||||||
|
|
||||||
#: src/lib/voice-persona-copy.ts
|
#: src/lib/voice-persona-copy.ts
|
||||||
msgid "Sorting priorities…"
|
msgid "Sorting priorities…"
|
||||||
msgstr "Sorting priorities…"
|
msgstr "Sorting priorities…"
|
||||||
|
|||||||
@ -303,6 +303,13 @@ msgstr ""
|
|||||||
msgid "1:1 conversation with {title}"
|
msgid "1:1 conversation with {title}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. placeholder {0}: mainConversationEntry.bot.name
|
||||||
|
#. placeholder {0}: speaker.name
|
||||||
|
#: src/components/stage/OoStage.tsx
|
||||||
|
#: src/pages/Shell.tsx
|
||||||
|
msgid "1:1 with {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/bot-picker.tsx
|
#: src/pages/shell/bot-picker.tsx
|
||||||
msgid "About groups"
|
msgid "About groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -783,6 +790,7 @@ msgstr "Disponible después de guardar la rutina"
|
|||||||
|
|
||||||
#: src/components/browser/InAppBrowser.tsx
|
#: src/components/browser/InAppBrowser.tsx
|
||||||
#: src/components/CookingMode.tsx
|
#: src/components/CookingMode.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
#: src/pages/PluginsOverlay.tsx
|
#: src/pages/PluginsOverlay.tsx
|
||||||
@ -1423,6 +1431,7 @@ msgid "Context limit"
|
|||||||
msgstr "Límite de contexto"
|
msgstr "Límite de contexto"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
@ -3317,6 +3326,10 @@ msgstr "Nombra este grupo"
|
|||||||
msgid "Name this routine"
|
msgid "Name this routine"
|
||||||
msgstr "Nombra esta rutina"
|
msgstr "Nombra esta rutina"
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Name your Oo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/OoAtelier.tsx
|
#: src/components/OoAtelier.tsx
|
||||||
msgid "Neck"
|
msgid "Neck"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3627,6 +3640,10 @@ msgstr ""
|
|||||||
msgid "Only file and folder names are read."
|
msgid "Only file and folder names are read."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Oo name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ScratchpadSection.tsx
|
#: src/pages/ScratchpadSection.tsx
|
||||||
#: src/pages/shell/computer-panel-preview.tsx
|
#: src/pages/shell/computer-panel-preview.tsx
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
@ -3833,6 +3850,10 @@ msgstr ""
|
|||||||
msgid "Present"
|
msgid "Present"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: props.name
|
#. placeholder {0}: props.name
|
||||||
#: src/components/ArtifactFileCard.tsx
|
#: src/components/ArtifactFileCard.tsx
|
||||||
msgid "Preview {0}"
|
msgid "Preview {0}"
|
||||||
@ -4574,6 +4595,7 @@ msgid "Skills"
|
|||||||
msgstr "Habilidades"
|
msgstr "Habilidades"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
@ -4581,8 +4603,9 @@ msgstr "Habilidades"
|
|||||||
msgid "Skip"
|
msgid "Skip"
|
||||||
msgstr "Omitir"
|
msgstr "Omitir"
|
||||||
|
|
||||||
|
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
msgid "Skip · English"
|
msgid "Skip · {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/localized-provider-hint.ts
|
#: src/lib/localized-provider-hint.ts
|
||||||
@ -4614,6 +4637,10 @@ msgstr "Actualización de software"
|
|||||||
msgid "Something needs your attention."
|
msgid "Something needs your attention."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Something short and yours — or keep the default and rename later."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/voice-persona-copy.ts
|
#: src/lib/voice-persona-copy.ts
|
||||||
msgid "Sorting priorities…"
|
msgid "Sorting priorities…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -303,6 +303,13 @@ msgstr ""
|
|||||||
msgid "1:1 conversation with {title}"
|
msgid "1:1 conversation with {title}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. placeholder {0}: mainConversationEntry.bot.name
|
||||||
|
#. placeholder {0}: speaker.name
|
||||||
|
#: src/components/stage/OoStage.tsx
|
||||||
|
#: src/pages/Shell.tsx
|
||||||
|
msgid "1:1 with {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/bot-picker.tsx
|
#: src/pages/shell/bot-picker.tsx
|
||||||
msgid "About groups"
|
msgid "About groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -783,6 +790,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/browser/InAppBrowser.tsx
|
#: src/components/browser/InAppBrowser.tsx
|
||||||
#: src/components/CookingMode.tsx
|
#: src/components/CookingMode.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
#: src/pages/PluginsOverlay.tsx
|
#: src/pages/PluginsOverlay.tsx
|
||||||
@ -1423,6 +1431,7 @@ msgid "Context limit"
|
|||||||
msgstr "कॉन्टेक्स्ट सीमा"
|
msgstr "कॉन्टेक्स्ट सीमा"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
@ -3317,6 +3326,10 @@ msgstr "इस समूह को नाम दें"
|
|||||||
msgid "Name this routine"
|
msgid "Name this routine"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Name your Oo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/OoAtelier.tsx
|
#: src/components/OoAtelier.tsx
|
||||||
msgid "Neck"
|
msgid "Neck"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3627,6 +3640,10 @@ msgstr ""
|
|||||||
msgid "Only file and folder names are read."
|
msgid "Only file and folder names are read."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Oo name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ScratchpadSection.tsx
|
#: src/pages/ScratchpadSection.tsx
|
||||||
#: src/pages/shell/computer-panel-preview.tsx
|
#: src/pages/shell/computer-panel-preview.tsx
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
@ -3833,6 +3850,10 @@ msgstr ""
|
|||||||
msgid "Present"
|
msgid "Present"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: props.name
|
#. placeholder {0}: props.name
|
||||||
#: src/components/ArtifactFileCard.tsx
|
#: src/components/ArtifactFileCard.tsx
|
||||||
msgid "Preview {0}"
|
msgid "Preview {0}"
|
||||||
@ -4574,6 +4595,7 @@ msgid "Skills"
|
|||||||
msgstr "स्किल्स"
|
msgstr "स्किल्स"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
@ -4581,8 +4603,9 @@ msgstr "स्किल्स"
|
|||||||
msgid "Skip"
|
msgid "Skip"
|
||||||
msgstr "छोड़ें"
|
msgstr "छोड़ें"
|
||||||
|
|
||||||
|
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
msgid "Skip · English"
|
msgid "Skip · {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/localized-provider-hint.ts
|
#: src/lib/localized-provider-hint.ts
|
||||||
@ -4614,6 +4637,10 @@ msgstr ""
|
|||||||
msgid "Something needs your attention."
|
msgid "Something needs your attention."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Something short and yours — or keep the default and rename later."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/voice-persona-copy.ts
|
#: src/lib/voice-persona-copy.ts
|
||||||
msgid "Sorting priorities…"
|
msgid "Sorting priorities…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -303,6 +303,13 @@ msgstr ""
|
|||||||
msgid "1:1 conversation with {title}"
|
msgid "1:1 conversation with {title}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. placeholder {0}: mainConversationEntry.bot.name
|
||||||
|
#. placeholder {0}: speaker.name
|
||||||
|
#: src/components/stage/OoStage.tsx
|
||||||
|
#: src/pages/Shell.tsx
|
||||||
|
msgid "1:1 with {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/bot-picker.tsx
|
#: src/pages/shell/bot-picker.tsx
|
||||||
msgid "About groups"
|
msgid "About groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -783,6 +790,7 @@ msgstr "루틴을 저장한 후 사용할 수 있습니다"
|
|||||||
|
|
||||||
#: src/components/browser/InAppBrowser.tsx
|
#: src/components/browser/InAppBrowser.tsx
|
||||||
#: src/components/CookingMode.tsx
|
#: src/components/CookingMode.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
#: src/pages/PluginsOverlay.tsx
|
#: src/pages/PluginsOverlay.tsx
|
||||||
@ -1423,6 +1431,7 @@ msgid "Context limit"
|
|||||||
msgstr "컨텍스트 한도"
|
msgstr "컨텍스트 한도"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
@ -3317,6 +3326,10 @@ msgstr "그룹 이름"
|
|||||||
msgid "Name this routine"
|
msgid "Name this routine"
|
||||||
msgstr "이 루틴의 이름을 정하세요"
|
msgstr "이 루틴의 이름을 정하세요"
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Name your Oo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/OoAtelier.tsx
|
#: src/components/OoAtelier.tsx
|
||||||
msgid "Neck"
|
msgid "Neck"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3627,6 +3640,10 @@ msgstr ""
|
|||||||
msgid "Only file and folder names are read."
|
msgid "Only file and folder names are read."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Oo name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ScratchpadSection.tsx
|
#: src/pages/ScratchpadSection.tsx
|
||||||
#: src/pages/shell/computer-panel-preview.tsx
|
#: src/pages/shell/computer-panel-preview.tsx
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
@ -3833,6 +3850,10 @@ msgstr ""
|
|||||||
msgid "Present"
|
msgid "Present"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: props.name
|
#. placeholder {0}: props.name
|
||||||
#: src/components/ArtifactFileCard.tsx
|
#: src/components/ArtifactFileCard.tsx
|
||||||
msgid "Preview {0}"
|
msgid "Preview {0}"
|
||||||
@ -4574,6 +4595,7 @@ msgid "Skills"
|
|||||||
msgstr "스킬"
|
msgstr "스킬"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
@ -4581,8 +4603,9 @@ msgstr "스킬"
|
|||||||
msgid "Skip"
|
msgid "Skip"
|
||||||
msgstr "건너뛰기"
|
msgstr "건너뛰기"
|
||||||
|
|
||||||
|
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
msgid "Skip · English"
|
msgid "Skip · {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/localized-provider-hint.ts
|
#: src/lib/localized-provider-hint.ts
|
||||||
@ -4614,6 +4637,10 @@ msgstr "소프트웨어 업데이트"
|
|||||||
msgid "Something needs your attention."
|
msgid "Something needs your attention."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Something short and yours — or keep the default and rename later."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/voice-persona-copy.ts
|
#: src/lib/voice-persona-copy.ts
|
||||||
msgid "Sorting priorities…"
|
msgid "Sorting priorities…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -303,6 +303,13 @@ msgstr ""
|
|||||||
msgid "1:1 conversation with {title}"
|
msgid "1:1 conversation with {title}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. placeholder {0}: mainConversationEntry.bot.name
|
||||||
|
#. placeholder {0}: speaker.name
|
||||||
|
#: src/components/stage/OoStage.tsx
|
||||||
|
#: src/pages/Shell.tsx
|
||||||
|
msgid "1:1 with {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/bot-picker.tsx
|
#: src/pages/shell/bot-picker.tsx
|
||||||
msgid "About groups"
|
msgid "About groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -783,6 +790,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/browser/InAppBrowser.tsx
|
#: src/components/browser/InAppBrowser.tsx
|
||||||
#: src/components/CookingMode.tsx
|
#: src/components/CookingMode.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
#: src/pages/PluginsOverlay.tsx
|
#: src/pages/PluginsOverlay.tsx
|
||||||
@ -1423,6 +1431,7 @@ msgid "Context limit"
|
|||||||
msgstr "Limite de contexto"
|
msgstr "Limite de contexto"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
@ -3317,6 +3326,10 @@ msgstr "Dar nome ao grupo"
|
|||||||
msgid "Name this routine"
|
msgid "Name this routine"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Name your Oo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/OoAtelier.tsx
|
#: src/components/OoAtelier.tsx
|
||||||
msgid "Neck"
|
msgid "Neck"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3627,6 +3640,10 @@ msgstr ""
|
|||||||
msgid "Only file and folder names are read."
|
msgid "Only file and folder names are read."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Oo name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ScratchpadSection.tsx
|
#: src/pages/ScratchpadSection.tsx
|
||||||
#: src/pages/shell/computer-panel-preview.tsx
|
#: src/pages/shell/computer-panel-preview.tsx
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
@ -3833,6 +3850,10 @@ msgstr ""
|
|||||||
msgid "Present"
|
msgid "Present"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: props.name
|
#. placeholder {0}: props.name
|
||||||
#: src/components/ArtifactFileCard.tsx
|
#: src/components/ArtifactFileCard.tsx
|
||||||
msgid "Preview {0}"
|
msgid "Preview {0}"
|
||||||
@ -4574,6 +4595,7 @@ msgid "Skills"
|
|||||||
msgstr "Habilidades"
|
msgstr "Habilidades"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
@ -4581,8 +4603,9 @@ msgstr "Habilidades"
|
|||||||
msgid "Skip"
|
msgid "Skip"
|
||||||
msgstr "Pular"
|
msgstr "Pular"
|
||||||
|
|
||||||
|
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
msgid "Skip · English"
|
msgid "Skip · {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/localized-provider-hint.ts
|
#: src/lib/localized-provider-hint.ts
|
||||||
@ -4614,6 +4637,10 @@ msgstr ""
|
|||||||
msgid "Something needs your attention."
|
msgid "Something needs your attention."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Something short and yours — or keep the default and rename later."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/voice-persona-copy.ts
|
#: src/lib/voice-persona-copy.ts
|
||||||
msgid "Sorting priorities…"
|
msgid "Sorting priorities…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -303,6 +303,13 @@ msgstr ""
|
|||||||
msgid "1:1 conversation with {title}"
|
msgid "1:1 conversation with {title}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. placeholder {0}: mainConversationEntry.bot.name
|
||||||
|
#. placeholder {0}: speaker.name
|
||||||
|
#: src/components/stage/OoStage.tsx
|
||||||
|
#: src/pages/Shell.tsx
|
||||||
|
msgid "1:1 with {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/bot-picker.tsx
|
#: src/pages/shell/bot-picker.tsx
|
||||||
msgid "About groups"
|
msgid "About groups"
|
||||||
msgstr "О группах"
|
msgstr "О группах"
|
||||||
@ -783,6 +790,7 @@ msgstr "Доступно после сохранения задачи"
|
|||||||
|
|
||||||
#: src/components/browser/InAppBrowser.tsx
|
#: src/components/browser/InAppBrowser.tsx
|
||||||
#: src/components/CookingMode.tsx
|
#: src/components/CookingMode.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
#: src/pages/PluginsOverlay.tsx
|
#: src/pages/PluginsOverlay.tsx
|
||||||
@ -1423,6 +1431,7 @@ msgid "Context limit"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
@ -3317,6 +3326,10 @@ msgstr "Назовите эту группу"
|
|||||||
msgid "Name this routine"
|
msgid "Name this routine"
|
||||||
msgstr "Назовите эту задачу"
|
msgstr "Назовите эту задачу"
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Name your Oo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/OoAtelier.tsx
|
#: src/components/OoAtelier.tsx
|
||||||
msgid "Neck"
|
msgid "Neck"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3627,6 +3640,10 @@ msgstr "Удалить можно только пустые пространст
|
|||||||
msgid "Only file and folder names are read."
|
msgid "Only file and folder names are read."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Oo name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ScratchpadSection.tsx
|
#: src/pages/ScratchpadSection.tsx
|
||||||
#: src/pages/shell/computer-panel-preview.tsx
|
#: src/pages/shell/computer-panel-preview.tsx
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
@ -3833,6 +3850,10 @@ msgstr ""
|
|||||||
msgid "Present"
|
msgid "Present"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: props.name
|
#. placeholder {0}: props.name
|
||||||
#: src/components/ArtifactFileCard.tsx
|
#: src/components/ArtifactFileCard.tsx
|
||||||
msgid "Preview {0}"
|
msgid "Preview {0}"
|
||||||
@ -4574,6 +4595,7 @@ msgid "Skills"
|
|||||||
msgstr "Навыки"
|
msgstr "Навыки"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
@ -4581,8 +4603,9 @@ msgstr "Навыки"
|
|||||||
msgid "Skip"
|
msgid "Skip"
|
||||||
msgstr "Пропустить"
|
msgstr "Пропустить"
|
||||||
|
|
||||||
|
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
msgid "Skip · English"
|
msgid "Skip · {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/localized-provider-hint.ts
|
#: src/lib/localized-provider-hint.ts
|
||||||
@ -4614,6 +4637,10 @@ msgstr "Обновление программного обеспечения"
|
|||||||
msgid "Something needs your attention."
|
msgid "Something needs your attention."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Something short and yours — or keep the default and rename later."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/voice-persona-copy.ts
|
#: src/lib/voice-persona-copy.ts
|
||||||
msgid "Sorting priorities…"
|
msgid "Sorting priorities…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -303,6 +303,13 @@ msgstr ""
|
|||||||
msgid "1:1 conversation with {title}"
|
msgid "1:1 conversation with {title}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. placeholder {0}: mainConversationEntry.bot.name
|
||||||
|
#. placeholder {0}: speaker.name
|
||||||
|
#: src/components/stage/OoStage.tsx
|
||||||
|
#: src/pages/Shell.tsx
|
||||||
|
msgid "1:1 with {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/bot-picker.tsx
|
#: src/pages/shell/bot-picker.tsx
|
||||||
msgid "About groups"
|
msgid "About groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -783,6 +790,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/browser/InAppBrowser.tsx
|
#: src/components/browser/InAppBrowser.tsx
|
||||||
#: src/components/CookingMode.tsx
|
#: src/components/CookingMode.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
#: src/pages/PluginsOverlay.tsx
|
#: src/pages/PluginsOverlay.tsx
|
||||||
@ -1423,6 +1431,7 @@ msgid "Context limit"
|
|||||||
msgstr "Bağlam sınırı"
|
msgstr "Bağlam sınırı"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
@ -3317,6 +3326,10 @@ msgstr "Gruba bir ad ver"
|
|||||||
msgid "Name this routine"
|
msgid "Name this routine"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Name your Oo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/OoAtelier.tsx
|
#: src/components/OoAtelier.tsx
|
||||||
msgid "Neck"
|
msgid "Neck"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3627,6 +3640,10 @@ msgstr ""
|
|||||||
msgid "Only file and folder names are read."
|
msgid "Only file and folder names are read."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Oo name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ScratchpadSection.tsx
|
#: src/pages/ScratchpadSection.tsx
|
||||||
#: src/pages/shell/computer-panel-preview.tsx
|
#: src/pages/shell/computer-panel-preview.tsx
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
@ -3833,6 +3850,10 @@ msgstr ""
|
|||||||
msgid "Present"
|
msgid "Present"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: props.name
|
#. placeholder {0}: props.name
|
||||||
#: src/components/ArtifactFileCard.tsx
|
#: src/components/ArtifactFileCard.tsx
|
||||||
msgid "Preview {0}"
|
msgid "Preview {0}"
|
||||||
@ -4574,6 +4595,7 @@ msgid "Skills"
|
|||||||
msgstr "Beceriler"
|
msgstr "Beceriler"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
@ -4581,8 +4603,9 @@ msgstr "Beceriler"
|
|||||||
msgid "Skip"
|
msgid "Skip"
|
||||||
msgstr "Atla"
|
msgstr "Atla"
|
||||||
|
|
||||||
|
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
msgid "Skip · English"
|
msgid "Skip · {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/localized-provider-hint.ts
|
#: src/lib/localized-provider-hint.ts
|
||||||
@ -4614,6 +4637,10 @@ msgstr ""
|
|||||||
msgid "Something needs your attention."
|
msgid "Something needs your attention."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Something short and yours — or keep the default and rename later."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/voice-persona-copy.ts
|
#: src/lib/voice-persona-copy.ts
|
||||||
msgid "Sorting priorities…"
|
msgid "Sorting priorities…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -303,6 +303,13 @@ msgstr ""
|
|||||||
msgid "1:1 conversation with {title}"
|
msgid "1:1 conversation with {title}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. placeholder {0}: mainConversationEntry.bot.name
|
||||||
|
#. placeholder {0}: speaker.name
|
||||||
|
#: src/components/stage/OoStage.tsx
|
||||||
|
#: src/pages/Shell.tsx
|
||||||
|
msgid "1:1 with {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/bot-picker.tsx
|
#: src/pages/shell/bot-picker.tsx
|
||||||
msgid "About groups"
|
msgid "About groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -783,6 +790,7 @@ msgstr "保存例行任务后可用"
|
|||||||
|
|
||||||
#: src/components/browser/InAppBrowser.tsx
|
#: src/components/browser/InAppBrowser.tsx
|
||||||
#: src/components/CookingMode.tsx
|
#: src/components/CookingMode.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
#: src/pages/PluginsOverlay.tsx
|
#: src/pages/PluginsOverlay.tsx
|
||||||
@ -1423,6 +1431,7 @@ msgid "Context limit"
|
|||||||
msgstr "上下文限制"
|
msgstr "上下文限制"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
#: src/components/onboarding/OnboardingOoDesigner.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
@ -3317,6 +3326,10 @@ msgstr "给这个群组命名"
|
|||||||
msgid "Name this routine"
|
msgid "Name this routine"
|
||||||
msgstr "给这个例行任务命名"
|
msgstr "给这个例行任务命名"
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Name your Oo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/OoAtelier.tsx
|
#: src/components/OoAtelier.tsx
|
||||||
msgid "Neck"
|
msgid "Neck"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3627,6 +3640,10 @@ msgstr ""
|
|||||||
msgid "Only file and folder names are read."
|
msgid "Only file and folder names are read."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Oo name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ScratchpadSection.tsx
|
#: src/pages/ScratchpadSection.tsx
|
||||||
#: src/pages/shell/computer-panel-preview.tsx
|
#: src/pages/shell/computer-panel-preview.tsx
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
@ -3833,6 +3850,10 @@ msgstr ""
|
|||||||
msgid "Present"
|
msgid "Present"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: props.name
|
#. placeholder {0}: props.name
|
||||||
#: src/components/ArtifactFileCard.tsx
|
#: src/components/ArtifactFileCard.tsx
|
||||||
msgid "Preview {0}"
|
msgid "Preview {0}"
|
||||||
@ -4574,6 +4595,7 @@ msgid "Skills"
|
|||||||
msgstr "技能"
|
msgstr "技能"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
#: src/components/onboarding/OnboardingScanStep.tsx
|
#: src/components/onboarding/OnboardingScanStep.tsx
|
||||||
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
#: src/components/onboarding/OnboardingVoiceStep.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
@ -4581,8 +4603,9 @@ msgstr "技能"
|
|||||||
msgid "Skip"
|
msgid "Skip"
|
||||||
msgstr "跳过"
|
msgstr "跳过"
|
||||||
|
|
||||||
|
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
|
||||||
#: src/pages/Onboarding.tsx
|
#: src/pages/Onboarding.tsx
|
||||||
msgid "Skip · English"
|
msgid "Skip · {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/localized-provider-hint.ts
|
#: src/lib/localized-provider-hint.ts
|
||||||
@ -4614,6 +4637,10 @@ msgstr "软件更新"
|
|||||||
msgid "Something needs your attention."
|
msgid "Something needs your attention."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||||
|
msgid "Something short and yours — or keep the default and rename later."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/voice-persona-copy.ts
|
#: src/lib/voice-persona-copy.ts
|
||||||
msgid "Sorting priorities…"
|
msgid "Sorting priorities…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -5,6 +5,10 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { IntegrationSetup } from "../components/integrations/IntegrationSetup";
|
import { IntegrationSetup } from "../components/integrations/IntegrationSetup";
|
||||||
import { OnboardingDiscoveryShell } from "../components/onboarding/OnboardingDiscoveryShell";
|
import { OnboardingDiscoveryShell } from "../components/onboarding/OnboardingDiscoveryShell";
|
||||||
|
import {
|
||||||
|
DEFAULT_FIRST_OO_NAME,
|
||||||
|
OnboardingNameStep,
|
||||||
|
} from "../components/onboarding/OnboardingNameStep";
|
||||||
import {
|
import {
|
||||||
type OnboardingEmojiConfig,
|
type OnboardingEmojiConfig,
|
||||||
OnboardingOoDesigner,
|
OnboardingOoDesigner,
|
||||||
@ -23,7 +27,12 @@ import {
|
|||||||
import { findLookCategory, lookPresetToAvatarValue } from "../lib/onboarding-look-categories";
|
import { findLookCategory, lookPresetToAvatarValue } from "../lib/onboarding-look-categories";
|
||||||
import { onboardingProfileUpdate } from "../lib/onboarding-profile";
|
import { onboardingProfileUpdate } from "../lib/onboarding-profile";
|
||||||
import { rpc, selectSpace } from "../lib/rpc";
|
import { rpc, selectSpace } from "../lib/rpc";
|
||||||
import { persistUiLocale, UI_LOCALE_LABELS, type UiLocale } from "../lib/ui-locale";
|
import {
|
||||||
|
persistUiLocale,
|
||||||
|
resolveUiLocale,
|
||||||
|
UI_LOCALE_LABELS,
|
||||||
|
type UiLocale,
|
||||||
|
} from "../lib/ui-locale";
|
||||||
import { collectLocalProfileSignals, persistUserProfileHarvest } from "../lib/user-profile-harvest";
|
import { collectLocalProfileSignals, persistUserProfileHarvest } from "../lib/user-profile-harvest";
|
||||||
|
|
||||||
/** Selects the main conversation's space and returns its route. */
|
/** Selects the main conversation's space and returns its route. */
|
||||||
@ -44,8 +53,9 @@ export function OnboardingPage() {
|
|||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [step, setStep] = useState<
|
const [step, setStep] = useState<
|
||||||
"loading" | "emoji" | "language" | "voice" | "integrations" | "scan" | "bot"
|
"loading" | "emoji" | "name" | "language" | "voice" | "integrations" | "scan" | "bot"
|
||||||
>("emoji");
|
>("emoji");
|
||||||
|
const [ooName, setOoName] = useState(DEFAULT_FIRST_OO_NAME);
|
||||||
const [selectedVoiceId, setSelectedVoiceId] = useState("");
|
const [selectedVoiceId, setSelectedVoiceId] = useState("");
|
||||||
const [voiceSkipped, setVoiceSkipped] = useState(false);
|
const [voiceSkipped, setVoiceSkipped] = useState(false);
|
||||||
const [integrationSetup, setIntegrationSetup] = useState<IntegrationSetupState | null>(null);
|
const [integrationSetup, setIntegrationSetup] = useState<IntegrationSetupState | null>(null);
|
||||||
@ -57,14 +67,9 @@ export function OnboardingPage() {
|
|||||||
const [crewReady, setCrewReady] = useState(false);
|
const [crewReady, setCrewReady] = useState(false);
|
||||||
|
|
||||||
const [emojiConfig, setEmojiConfig] = useState<OnboardingEmojiConfig>(defaultEmojiConfig);
|
const [emojiConfig, setEmojiConfig] = useState<OnboardingEmojiConfig>(defaultEmojiConfig);
|
||||||
const [currentLocale, setCurrentLocale] = useState<string>(() => {
|
const [currentLocale, setCurrentLocale] = useState<UiLocale>(() => resolveUiLocale());
|
||||||
try {
|
|
||||||
return localStorage.getItem("rakazo.uiLocale") ?? "en";
|
|
||||||
} catch {
|
|
||||||
return "en";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const [languageChoice, setLanguageChoice] = useState<UiLocale | null>(null);
|
const [languageChoice, setLanguageChoice] = useState<UiLocale | null>(null);
|
||||||
|
const activeUiLocale = languageChoice ?? currentLocale;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
@ -110,7 +115,7 @@ export function OnboardingPage() {
|
|||||||
const discoveryPhase: OnboardingDiscoveryPhase =
|
const discoveryPhase: OnboardingDiscoveryPhase =
|
||||||
step === "loading"
|
step === "loading"
|
||||||
? "loading"
|
? "loading"
|
||||||
: step === "emoji" || step === "language" || step === "voice"
|
: step === "emoji" || step === "name" || step === "language" || step === "voice"
|
||||||
? "emoji"
|
? "emoji"
|
||||||
: step === "integrations"
|
: step === "integrations"
|
||||||
? "integrations"
|
? "integrations"
|
||||||
@ -145,7 +150,10 @@ export function OnboardingPage() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const bot = await rpc.bots.ensureMain();
|
const bot = await rpc.bots.ensureMain();
|
||||||
await rpc.bots.update({ botId: bot.id, color: emojiConfig.avatarValue }).catch(() => null);
|
const trimmedName = ooName.trim() || DEFAULT_FIRST_OO_NAME;
|
||||||
|
await rpc.bots
|
||||||
|
.update({ botId: bot.id, color: emojiConfig.avatarValue, name: trimmedName })
|
||||||
|
.catch(() => null);
|
||||||
setCrewReady(true);
|
setCrewReady(true);
|
||||||
|
|
||||||
await rpc.profile
|
await rpc.profile
|
||||||
@ -213,7 +221,11 @@ export function OnboardingPage() {
|
|||||||
<OnboardingDiscoveryShell
|
<OnboardingDiscoveryShell
|
||||||
items={discoveryItems}
|
items={discoveryItems}
|
||||||
compact={
|
compact={
|
||||||
step === "emoji" || step === "language" || step === "voice" || step === "integrations"
|
step === "emoji" ||
|
||||||
|
step === "name" ||
|
||||||
|
step === "language" ||
|
||||||
|
step === "voice" ||
|
||||||
|
step === "integrations"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{error && step !== "bot" ? <p className="mb-4 text-sm text-destructive">{error}</p> : null}
|
{error && step !== "bot" ? <p className="mb-4 text-sm text-destructive">{error}</p> : null}
|
||||||
@ -222,6 +234,16 @@ export function OnboardingPage() {
|
|||||||
<OnboardingOoDesigner
|
<OnboardingOoDesigner
|
||||||
config={emojiConfig}
|
config={emojiConfig}
|
||||||
onChange={setEmojiConfig}
|
onChange={setEmojiConfig}
|
||||||
|
onContinue={() => setStep("name")}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{step === "name" ? (
|
||||||
|
<OnboardingNameStep
|
||||||
|
config={emojiConfig}
|
||||||
|
name={ooName}
|
||||||
|
onNameChange={setOoName}
|
||||||
|
onBack={() => setStep("emoji")}
|
||||||
onContinue={() => setStep("language")}
|
onContinue={() => setStep("language")}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
@ -241,7 +263,7 @@ export function OnboardingPage() {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
persistUiLocale(code as UiLocale);
|
persistUiLocale(code as UiLocale);
|
||||||
setCurrentLocale(code);
|
setCurrentLocale(code as UiLocale);
|
||||||
setLanguageChoice(code as UiLocale);
|
setLanguageChoice(code as UiLocale);
|
||||||
setStep("voice");
|
setStep("voice");
|
||||||
}}
|
}}
|
||||||
@ -256,11 +278,18 @@ export function OnboardingPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-8 flex items-center justify-between">
|
<div className="mt-8 flex items-center justify-between">
|
||||||
<Button variant="ghost" size="sm" onClick={() => setStep("emoji")}>
|
<Button variant="ghost" size="sm" onClick={() => setStep("name")}>
|
||||||
<Trans>Back</Trans>
|
<Trans>Back</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setStep("voice")}>
|
<Button
|
||||||
<Trans>Skip · English</Trans>
|
onClick={() => {
|
||||||
|
const locale = resolveUiLocale();
|
||||||
|
setLanguageChoice(locale);
|
||||||
|
setCurrentLocale(locale);
|
||||||
|
setStep("voice");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t`Skip · ${UI_LOCALE_LABELS[activeUiLocale]}`}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -268,6 +297,7 @@ export function OnboardingPage() {
|
|||||||
|
|
||||||
{step === "voice" ? (
|
{step === "voice" ? (
|
||||||
<OnboardingVoiceStep
|
<OnboardingVoiceStep
|
||||||
|
uiLocale={activeUiLocale}
|
||||||
selectedVoiceId={selectedVoiceId}
|
selectedVoiceId={selectedVoiceId}
|
||||||
onSelectedVoiceIdChange={setSelectedVoiceId}
|
onSelectedVoiceIdChange={setSelectedVoiceId}
|
||||||
onBack={() => setStep("language")}
|
onBack={() => setStep("language")}
|
||||||
|
|||||||
@ -84,7 +84,9 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||||
|
import { HEUTE_APP_PATH, isHeuteAppPath } from "../lib/app-routes";
|
||||||
|
import { isMultiOoGroupChat } from "../lib/group-chat";
|
||||||
import { ArtifactPane } from "../components/ArtifactPane";
|
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";
|
||||||
@ -267,14 +269,21 @@ import { SortableGroup } from "../components/SortableGroup";
|
|||||||
export function ShellPage() {
|
export function ShellPage() {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const { botId, groupId } = useParams();
|
const { botId, groupId } = useParams();
|
||||||
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const onHeuteRoute = isHeuteAppPath(location.pathname);
|
||||||
const [activeNavPillar, setActiveNavPillar] = useState<NavPillar>(() => {
|
const [activeNavPillar, setActiveNavPillar] = useState<NavPillar>(() => {
|
||||||
|
if (isHeuteAppPath(location.pathname)) return "heute";
|
||||||
if (groupId) return "projekte";
|
if (groupId) return "projekte";
|
||||||
if (botId) return "team";
|
if (botId) return "team";
|
||||||
return "heute";
|
return "heute";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isHeuteAppPath(location.pathname)) setActiveNavPillar("heute");
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (botId) setActiveNavPillar("team");
|
if (botId) setActiveNavPillar("team");
|
||||||
}, [botId]);
|
}, [botId]);
|
||||||
@ -611,6 +620,8 @@ export function ShellPage() {
|
|||||||
const recordingSkill = activeTaughtSkills.find((skill) => skill.status === "recording") ?? null;
|
const recordingSkill = activeTaughtSkills.find((skill) => skill.status === "recording") ?? null;
|
||||||
const routeBotId = useRef<string | undefined>(botId);
|
const routeBotId = useRef<string | undefined>(botId);
|
||||||
routeBotId.current = botId;
|
routeBotId.current = botId;
|
||||||
|
const routeOnHeute = useRef(onHeuteRoute);
|
||||||
|
routeOnHeute.current = onHeuteRoute;
|
||||||
const routeGroupId = useRef<string | undefined>(groupId);
|
const routeGroupId = useRef<string | undefined>(groupId);
|
||||||
routeGroupId.current = groupId;
|
routeGroupId.current = groupId;
|
||||||
const activeBotId = useRef<string | undefined>(inGroup ? undefined : active?.id);
|
const activeBotId = useRef<string | undefined>(inGroup ? undefined : active?.id);
|
||||||
@ -745,6 +756,7 @@ export function ShellPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!currentBotId || !list.some((bot) => bot.id === currentBotId)) {
|
if (!currentBotId || !list.some((bot) => bot.id === currentBotId)) {
|
||||||
|
if (routeOnHeute.current) return;
|
||||||
navigate(firstThreadRoute(list, groupList, inProject), { replace: true });
|
navigate(firstThreadRoute(list, groupList, inProject), { replace: true });
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -1580,6 +1592,9 @@ export function ShellPage() {
|
|||||||
: snapshot?.botId === active?.id
|
: snapshot?.botId === active?.id
|
||||||
? snapshot
|
? snapshot
|
||||||
: null;
|
: null;
|
||||||
|
const activeGroupMemberCount =
|
||||||
|
activeGroup?.members?.length ?? activeSnapshot?.members?.length ?? 0;
|
||||||
|
const multiOoInGroup = inGroup && isMultiOoGroupChat(activeGroupMemberCount);
|
||||||
const activeReplyTarget =
|
const activeReplyTarget =
|
||||||
replyTarget && activeSnapshot?.messages.some((message) => message.id === replyTarget.id)
|
replyTarget && activeSnapshot?.messages.some((message) => message.id === replyTarget.id)
|
||||||
? replyTarget
|
? replyTarget
|
||||||
@ -2796,6 +2811,7 @@ export function ShellPage() {
|
|||||||
ooSound.play("interact");
|
ooSound.play("interact");
|
||||||
setActiveNavPillar("heute");
|
setActiveNavPillar("heute");
|
||||||
setMobileSidebarOpen(false);
|
setMobileSidebarOpen(false);
|
||||||
|
navigate(HEUTE_APP_PATH);
|
||||||
}}
|
}}
|
||||||
className={rosterRowClass(activeNavPillar === "heute")}
|
className={rosterRowClass(activeNavPillar === "heute")}
|
||||||
>
|
>
|
||||||
@ -3213,10 +3229,17 @@ export function ShellPage() {
|
|||||||
<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">
|
||||||
{composerRunning || active.status === "running" ? t`Busy` : active.status}
|
{composerRunning || active.status === "running" ? t`Busy` : active.status}
|
||||||
</span>
|
</span>
|
||||||
) : inGroup && 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`}
|
||||||
</span>
|
</span>
|
||||||
|
) : inGroup &&
|
||||||
|
activeGroup?.kind === "main" &&
|
||||||
|
activeGroupMemberCount <= 1 &&
|
||||||
|
mainConversationEntry ? (
|
||||||
|
<span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug">
|
||||||
|
{t`1:1 with ${mainConversationEntry.bot.name}`}
|
||||||
|
</span>
|
||||||
) : inGroup && activeGroup?.kind !== "main" ? (
|
) : inGroup && activeGroup?.kind !== "main" ? (
|
||||||
<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`Project · sessions & folders`}
|
{t`Project · sessions & folders`}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export function SidebarToggleButtons({
|
|||||||
{collapsed && desktopBridge() ? <WindowChrome /> : null}
|
{collapsed && desktopBridge() ? <WindowChrome /> : null}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-testid="open-navigation"
|
||||||
aria-label={t`Open navigation`}
|
aria-label={t`Open navigation`}
|
||||||
onClick={onOpenNavigation}
|
onClick={onOpenNavigation}
|
||||||
className="app-no-drag grid size-control-lg shrink-0 place-items-center rounded-lg text-foreground/75 hover:bg-accent md:hidden"
|
className="app-no-drag grid size-control-lg shrink-0 place-items-center rounded-lg text-foreground/75 hover:bg-accent md:hidden"
|
||||||
|
|||||||
@ -17,6 +17,21 @@ export function useMobileSidebar() {
|
|||||||
return () => desktop.removeEventListener("change", closeMobileSidebar);
|
return () => desktop.removeEventListener("change", closeMobileSidebar);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!mobileSidebarOpen) return;
|
||||||
|
function onKeyDown(event: KeyboardEvent) {
|
||||||
|
if (event.key !== "Escape" || window.matchMedia("(min-width: 768px)").matches) return;
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
setMobileSidebarOpen(false);
|
||||||
|
queueMicrotask(() => {
|
||||||
|
document.querySelector<HTMLElement>('[data-testid="open-navigation"]')?.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.addEventListener("keydown", onKeyDown, true);
|
||||||
|
return () => window.removeEventListener("keydown", onKeyDown, true);
|
||||||
|
}, [mobileSidebarOpen]);
|
||||||
|
|
||||||
function onMobileSidebarTouchStart(event: React.TouchEvent<HTMLDivElement>) {
|
function onMobileSidebarTouchStart(event: React.TouchEvent<HTMLDivElement>) {
|
||||||
if (
|
if (
|
||||||
mobileSidebarOpen ||
|
mobileSidebarOpen ||
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user