fix(web,core,db): resolve responsive layout overflows, dialog clipping, i18n gaps, and runtime stability
This commit is contained in:
parent
ac9fcce0fb
commit
66dbf0046e
@ -14,6 +14,13 @@ test("conversations and projects stay separate; + opens bot picker", async ({ pa
|
|||||||
await expect(unterhaltungen.getByText("Talk to your Oos", { exact: true })).toBeVisible();
|
await expect(unterhaltungen.getByText("Talk to your Oos", { exact: true })).toBeVisible();
|
||||||
await expect(projekte.getByText("Projects", { exact: true })).toBeVisible();
|
await expect(projekte.getByText("Projects", { exact: true })).toBeVisible();
|
||||||
await expect(unterhaltungen.getByRole("button", { name: /^Chief/ })).toBeVisible();
|
await expect(unterhaltungen.getByRole("button", { name: /^Chief/ })).toBeVisible();
|
||||||
|
const [projekteBox, unterhaltungenBox] = await Promise.all([
|
||||||
|
projekte.boundingBox(),
|
||||||
|
unterhaltungen.boundingBox(),
|
||||||
|
]);
|
||||||
|
expect(projekteBox).not.toBeNull();
|
||||||
|
expect(unterhaltungenBox).not.toBeNull();
|
||||||
|
expect(projekteBox!.y).toBeLessThan(unterhaltungenBox!.y);
|
||||||
|
|
||||||
await unterhaltungen.getByTestId("conversation-picker-trigger").click();
|
await unterhaltungen.getByTestId("conversation-picker-trigger").click();
|
||||||
const picker = page.getByTestId("conversation-bot-picker");
|
const picker = page.getByTestId("conversation-bot-picker");
|
||||||
|
|||||||
@ -254,10 +254,10 @@ export function OoAtelierDialog({
|
|||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
showCloseButton={false}
|
showCloseButton={false}
|
||||||
className="max-h-[92vh] w-[420px] max-w-full gap-3 overflow-y-auto rounded-3xl p-5 sm:max-w-[420px]"
|
className="flex max-h-[90dvh] w-[calc(100vw-2rem)] max-w-[420px] flex-col gap-3 overflow-hidden rounded-3xl p-5 sm:max-w-[420px]"
|
||||||
data-testid="oo-atelier"
|
data-testid="oo-atelier"
|
||||||
>
|
>
|
||||||
<DialogHeader className="flex-row items-center justify-between space-y-0">
|
<DialogHeader className="shrink-0 flex-row items-center justify-between space-y-0">
|
||||||
<DialogTitle className="text-ui-body font-semibold tracking-tight">
|
<DialogTitle className="text-ui-body font-semibold tracking-tight">
|
||||||
<Trans>Atelier</Trans>
|
<Trans>Atelier</Trans>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
@ -274,19 +274,19 @@ export function OoAtelierDialog({
|
|||||||
</button>
|
</button>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="flex justify-center py-1" data-testid="oo-atelier-preview">
|
<div className="flex shrink-0 justify-center py-1" data-testid="oo-atelier-preview">
|
||||||
<BotAvatar
|
<BotAvatar
|
||||||
color={isPicture && !draft ? value : encoded}
|
color={isPicture && !draft ? value : encoded}
|
||||||
identity={identity}
|
identity={identity}
|
||||||
status={status}
|
status={status}
|
||||||
size={128}
|
size={120}
|
||||||
moment={preview}
|
moment={preview}
|
||||||
interactive
|
interactive
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs defaultValue="body">
|
<Tabs defaultValue="body" className="flex min-h-0 flex-1 flex-col">
|
||||||
<TabsList className="w-full">
|
<TabsList className="w-full shrink-0">
|
||||||
<TabsTrigger value="body">
|
<TabsTrigger value="body">
|
||||||
<Trans>Body</Trans>
|
<Trans>Body</Trans>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
@ -303,7 +303,11 @@ export function OoAtelierDialog({
|
|||||||
) : null}
|
) : null}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="body" className="space-y-4 pt-2" data-testid="oo-atelier-body">
|
<TabsContent
|
||||||
|
value="body"
|
||||||
|
className="rk-scroll min-h-0 flex-1 space-y-4 overflow-y-auto pt-2 pr-1"
|
||||||
|
data-testid="oo-atelier-body"
|
||||||
|
>
|
||||||
<OptionGroup
|
<OptionGroup
|
||||||
title={t`Species`}
|
title={t`Species`}
|
||||||
values={OO_SPECIES}
|
values={OO_SPECIES}
|
||||||
@ -397,7 +401,11 @@ export function OoAtelierDialog({
|
|||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="eyes" className="space-y-4 pt-2" data-testid="oo-atelier-eyes">
|
<TabsContent
|
||||||
|
value="eyes"
|
||||||
|
className="rk-scroll min-h-0 flex-1 space-y-4 overflow-y-auto pt-2 pr-1"
|
||||||
|
data-testid="oo-atelier-eyes"
|
||||||
|
>
|
||||||
<OptionGroup
|
<OptionGroup
|
||||||
title={t`Lashes`}
|
title={t`Lashes`}
|
||||||
values={OO_LASHES}
|
values={OO_LASHES}
|
||||||
@ -456,7 +464,11 @@ export function OoAtelierDialog({
|
|||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="extras" className="space-y-4 pt-2" data-testid="oo-atelier-extras">
|
<TabsContent
|
||||||
|
value="extras"
|
||||||
|
className="rk-scroll min-h-0 flex-1 space-y-4 overflow-y-auto pt-2 pr-1"
|
||||||
|
data-testid="oo-atelier-extras"
|
||||||
|
>
|
||||||
<OptionGroup
|
<OptionGroup
|
||||||
title={t`Glasses`}
|
title={t`Glasses`}
|
||||||
values={OO_EYEWEAR}
|
values={OO_EYEWEAR}
|
||||||
@ -492,7 +504,7 @@ export function OoAtelierDialog({
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{allowUpload ? (
|
{allowUpload ? (
|
||||||
<TabsContent value="upload" className="pt-2">
|
<TabsContent value="upload" className="min-h-0 flex-1 overflow-y-auto pt-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={t`Image upload area`}
|
aria-label={t`Image upload area`}
|
||||||
@ -534,7 +546,7 @@ export function OoAtelierDialog({
|
|||||||
) : null}
|
) : null}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<DialogFooter className="flex-row items-center justify-between border-border sm:justify-between">
|
<DialogFooter className="-mx-5 -mb-5 shrink-0 flex-row items-center justify-between border-border px-5 py-3.5 sm:justify-between">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@ -15,14 +15,14 @@ type OnboardingDiscoveryShellProps = {
|
|||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function sourceLabel(id: DiscoverySourceId, t: ReturnType<typeof useLingui>["t"]): string {
|
function sourceLabel(id: DiscoverySourceId): ReactNode {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case "profile-basics":
|
case "profile-basics":
|
||||||
return t`Your profile`;
|
return <Trans>Your profile</Trans>;
|
||||||
case "connected-tools":
|
case "connected-tools":
|
||||||
return t`Connected tools`;
|
return <Trans>Connected tools</Trans>;
|
||||||
case "specialist-crew":
|
case "specialist-crew":
|
||||||
return t`Specialist crew`;
|
return <Trans>Specialist crew</Trans>;
|
||||||
default: {
|
default: {
|
||||||
const never: never = id;
|
const never: never = id;
|
||||||
return never;
|
return never;
|
||||||
@ -83,7 +83,7 @@ export function OnboardingDiscoveryShell({
|
|||||||
>
|
>
|
||||||
{active ? (
|
{active ? (
|
||||||
<>
|
<>
|
||||||
{sourceLabel(active.id, t)}
|
{sourceLabel(active.id)}
|
||||||
{active.status === "found" && active.detail === DISCOVERY_MAIN_BOT_ROLE ? (
|
{active.status === "found" && active.detail === DISCOVERY_MAIN_BOT_ROLE ? (
|
||||||
<>
|
<>
|
||||||
{" · "}
|
{" · "}
|
||||||
|
|||||||
@ -120,7 +120,7 @@ export function OnboardingOoDesigner({ config, onChange, onContinue }: Onboardin
|
|||||||
<Trans>What feels like you?</Trans>
|
<Trans>What feels like you?</Trans>
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
className="mt-3 flex gap-2 overflow-x-auto pb-1 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
className="mt-3 flex flex-wrap gap-2 pb-1"
|
||||||
role="tablist"
|
role="tablist"
|
||||||
aria-label={t`Look families`}
|
aria-label={t`Look families`}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -429,7 +429,7 @@ msgstr "Über Gruppen"
|
|||||||
|
|
||||||
#: src/pages/shell/bot-picker.tsx
|
#: src/pages/shell/bot-picker.tsx
|
||||||
msgid "About spaces"
|
msgid "About spaces"
|
||||||
msgstr "Über Spaces"
|
msgstr "Über Projekte"
|
||||||
|
|
||||||
#: src/components/UserProfileSettings.tsx
|
#: src/components/UserProfileSettings.tsx
|
||||||
msgid "About you"
|
msgid "About you"
|
||||||
@ -1001,7 +1001,7 @@ msgstr "Körper"
|
|||||||
msgid "Booting live desktop…"
|
msgid "Booting live desktop…"
|
||||||
msgstr "Live-Desktop wird gestartet…"
|
msgstr "Live-Desktop wird gestartet…"
|
||||||
|
|
||||||
#. placeholder {0}: active?.name
|
#. placeholder {0}: (active ?? coordinatorBot)?.name
|
||||||
#: src/pages/Shell.tsx
|
#: src/pages/Shell.tsx
|
||||||
msgid "Booting up {0}’s computer"
|
msgid "Booting up {0}’s computer"
|
||||||
msgstr "Computer von {0} wird gestartet"
|
msgstr "Computer von {0} wird gestartet"
|
||||||
@ -1494,6 +1494,10 @@ msgstr "Computer"
|
|||||||
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
||||||
msgstr "Computer sind aus. Setze SANDBOX_PROVIDER=docker mit SANDBOX_SUPERVISOR_TOKEN, oder setze SANDBOX_PROVIDER auf e2b, daytona oder box mit dem passenden API-Key. Stack nach der Änderung von .env neu erstellen."
|
msgstr "Computer sind aus. Setze SANDBOX_PROVIDER=docker mit SANDBOX_SUPERVISOR_TOKEN, oder setze SANDBOX_PROVIDER auf e2b, daytona oder box mit dem passenden API-Key. Stack nach der Änderung von .env neu erstellen."
|
||||||
|
|
||||||
|
#: src/pages/PluginsOverlay.tsx
|
||||||
|
msgid "Configure a plugin catalog on the server to connect apps."
|
||||||
|
msgstr "Richte auf dem Server einen Plugin-Katalog ein, um Apps zu verbinden."
|
||||||
|
|
||||||
#: src/pages/ModelSettingsOverlay.tsx
|
#: src/pages/ModelSettingsOverlay.tsx
|
||||||
msgid "Configured by deployment"
|
msgid "Configured by deployment"
|
||||||
msgstr "Durch die Bereitstellung konfiguriert"
|
msgstr "Durch die Bereitstellung konfiguriert"
|
||||||
@ -1585,6 +1589,10 @@ msgstr "Verbunden, {0} wird verwendet."
|
|||||||
msgid "Connected app"
|
msgid "Connected app"
|
||||||
msgstr "Verbundene App"
|
msgstr "Verbundene App"
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Connected tools"
|
||||||
|
msgstr "Verbundene Tools"
|
||||||
|
|
||||||
#: src/pages/shell/message-cards.tsx
|
#: src/pages/shell/message-cards.tsx
|
||||||
msgid "Connected. Its tools are available from your next message."
|
msgid "Connected. Its tools are available from your next message."
|
||||||
msgstr "Verbunden. Die Tools sind ab deiner nächsten Nachricht verfügbar."
|
msgstr "Verbunden. Die Tools sind ab deiner nächsten Nachricht verfügbar."
|
||||||
@ -2078,7 +2086,7 @@ msgstr "Neue Gruppe erstellen"
|
|||||||
|
|
||||||
#: src/pages/shell/bot-picker.tsx
|
#: src/pages/shell/bot-picker.tsx
|
||||||
msgid "Create new Space"
|
msgid "Create new Space"
|
||||||
msgstr "Neuen Space erstellen"
|
msgstr "Neues Projekt erstellen"
|
||||||
|
|
||||||
#: src/pages/shell/dialogs.tsx
|
#: src/pages/shell/dialogs.tsx
|
||||||
msgid "Create project"
|
msgid "Create project"
|
||||||
@ -2087,7 +2095,7 @@ msgstr "Projekt erstellen"
|
|||||||
#: src/components/AskCard.tsx
|
#: src/components/AskCard.tsx
|
||||||
#: src/pages/shell/dialogs.tsx
|
#: src/pages/shell/dialogs.tsx
|
||||||
msgid "Create space"
|
msgid "Create space"
|
||||||
msgstr "Space erstellen"
|
msgstr "Projekt erstellen"
|
||||||
|
|
||||||
#: src/components/integrations/GithubConnect.tsx
|
#: src/components/integrations/GithubConnect.tsx
|
||||||
msgid "Create token on GitHub"
|
msgid "Create token on GitHub"
|
||||||
@ -2242,7 +2250,7 @@ msgstr "Erinnerungen ebenfalls löschen"
|
|||||||
|
|
||||||
#: src/pages/Shell.tsx
|
#: src/pages/Shell.tsx
|
||||||
msgid "Delete space"
|
msgid "Delete space"
|
||||||
msgstr "Space löschen"
|
msgstr "Projekt löschen"
|
||||||
|
|
||||||
#: src/pages/PrivacySettingsOverlay.tsx
|
#: src/pages/PrivacySettingsOverlay.tsx
|
||||||
msgid "Delete your account?"
|
msgid "Delete your account?"
|
||||||
@ -3777,7 +3785,7 @@ msgstr "Neuer Skill"
|
|||||||
|
|
||||||
#: src/pages/shell/dialogs.tsx
|
#: src/pages/shell/dialogs.tsx
|
||||||
msgid "New space"
|
msgid "New space"
|
||||||
msgstr "Neuer Space"
|
msgstr "Neues Projekt"
|
||||||
|
|
||||||
#: src/components/CookingMode.tsx
|
#: src/components/CookingMode.tsx
|
||||||
msgid "Next"
|
msgid "Next"
|
||||||
@ -3891,6 +3899,10 @@ msgstr "Noch keine Projekte."
|
|||||||
msgid "No providers found."
|
msgid "No providers found."
|
||||||
msgstr "Keine Anbieter gefunden."
|
msgstr "Keine Anbieter gefunden."
|
||||||
|
|
||||||
|
#: src/pages/ActivityList.tsx
|
||||||
|
msgid "No recent activity"
|
||||||
|
msgstr "Keine letzte Aktivität"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
msgid "No remote MCP servers found"
|
msgid "No remote MCP servers found"
|
||||||
msgstr "Keine Remote-MCP-Server gefunden"
|
msgstr "Keine Remote-MCP-Server gefunden"
|
||||||
@ -5149,7 +5161,7 @@ msgstr "Space-Standard"
|
|||||||
|
|
||||||
#: src/pages/shell/dialogs.tsx
|
#: src/pages/shell/dialogs.tsx
|
||||||
msgid "Spaces"
|
msgid "Spaces"
|
||||||
msgstr "Spaces"
|
msgstr "Projekte"
|
||||||
|
|
||||||
#: src/components/OoAtelier.tsx
|
#: src/components/OoAtelier.tsx
|
||||||
msgid "Sparkle"
|
msgid "Sparkle"
|
||||||
@ -5185,6 +5197,7 @@ msgstr "Vorlesen an oder aus"
|
|||||||
msgid "Speaking with intent…"
|
msgid "Speaking with intent…"
|
||||||
msgstr "Spreche mit Bedacht…"
|
msgstr "Spreche mit Bedacht…"
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
msgid "Specialist crew"
|
msgid "Specialist crew"
|
||||||
msgstr "Spezialisten-Team"
|
msgstr "Spezialisten-Team"
|
||||||
@ -5526,10 +5539,6 @@ msgstr "Diese Abonnementanmeldung ist in Rakazo noch nicht verfügbar. Verwende
|
|||||||
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
||||||
msgstr "Dadurch werden dieses Projekt und alle zugehörigen Unterhaltungen, Bots und Erinnerungen dauerhaft gelöscht."
|
msgstr "Dadurch werden dieses Projekt und alle zugehörigen Unterhaltungen, Bots und Erinnerungen dauerhaft gelöscht."
|
||||||
|
|
||||||
#: src/pages/Shell.tsx
|
|
||||||
msgid "Threads by topic · folders"
|
|
||||||
msgstr "Unterhaltungen nach Thema · Ordner"
|
|
||||||
|
|
||||||
#: src/pages/ModelTierSettings.tsx
|
#: src/pages/ModelTierSettings.tsx
|
||||||
#: src/pages/shell/bot-panel.tsx
|
#: src/pages/shell/bot-panel.tsx
|
||||||
msgid "Tiers"
|
msgid "Tiers"
|
||||||
@ -5715,6 +5724,7 @@ msgstr "Bis wann?"
|
|||||||
|
|
||||||
#: src/components/DesktopUpdates.tsx
|
#: src/components/DesktopUpdates.tsx
|
||||||
#: src/components/SoftwareUpdateSection.tsx
|
#: src/components/SoftwareUpdateSection.tsx
|
||||||
|
#: src/pages/AccountSettingsOverlay.tsx
|
||||||
msgid "Up to date"
|
msgid "Up to date"
|
||||||
msgstr "Aktuell"
|
msgstr "Aktuell"
|
||||||
|
|
||||||
@ -6100,6 +6110,10 @@ msgstr "Wenn das an ist, spricht dein Oo ganz natürlich mit dir."
|
|||||||
msgid "Your Oo will communicate in this language. You can change it later."
|
msgid "Your Oo will communicate in this language. You can change it later."
|
||||||
msgstr "Dein Oo kommuniziert in dieser Sprache. Du kannst sie später ändern."
|
msgstr "Dein Oo kommuniziert in dieser Sprache. Du kannst sie später ändern."
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Your profile"
|
||||||
|
msgstr "Dein Profil"
|
||||||
|
|
||||||
#: src/pages/Welcome.tsx
|
#: src/pages/Welcome.tsx
|
||||||
msgid "Your specialists. One calm place to talk."
|
msgid "Your specialists. One calm place to talk."
|
||||||
msgstr "Deine Spezialisten. Ein ruhiger Ort zum Reden."
|
msgstr "Deine Spezialisten. Ein ruhiger Ort zum Reden."
|
||||||
|
|||||||
@ -1001,7 +1001,7 @@ msgstr "Body"
|
|||||||
msgid "Booting live desktop…"
|
msgid "Booting live desktop…"
|
||||||
msgstr "Booting live desktop…"
|
msgstr "Booting live desktop…"
|
||||||
|
|
||||||
#. placeholder {0}: active?.name
|
#. placeholder {0}: (active ?? coordinatorBot)?.name
|
||||||
#: src/pages/Shell.tsx
|
#: src/pages/Shell.tsx
|
||||||
msgid "Booting up {0}’s computer"
|
msgid "Booting up {0}’s computer"
|
||||||
msgstr "Booting up {0}’s computer"
|
msgstr "Booting up {0}’s computer"
|
||||||
@ -1494,6 +1494,10 @@ msgstr "Computers"
|
|||||||
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
||||||
msgstr "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
msgstr "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
||||||
|
|
||||||
|
#: src/pages/PluginsOverlay.tsx
|
||||||
|
msgid "Configure a plugin catalog on the server to connect apps."
|
||||||
|
msgstr "Configure a plugin catalog on the server to connect apps."
|
||||||
|
|
||||||
#: src/pages/ModelSettingsOverlay.tsx
|
#: src/pages/ModelSettingsOverlay.tsx
|
||||||
msgid "Configured by deployment"
|
msgid "Configured by deployment"
|
||||||
msgstr "Configured by deployment"
|
msgstr "Configured by deployment"
|
||||||
@ -1585,6 +1589,10 @@ msgstr "Connected and using {0}."
|
|||||||
msgid "Connected app"
|
msgid "Connected app"
|
||||||
msgstr "Connected app"
|
msgstr "Connected app"
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Connected tools"
|
||||||
|
msgstr "Connected tools"
|
||||||
|
|
||||||
#: src/pages/shell/message-cards.tsx
|
#: src/pages/shell/message-cards.tsx
|
||||||
msgid "Connected. Its tools are available from your next message."
|
msgid "Connected. Its tools are available from your next message."
|
||||||
msgstr "Connected. Its tools are available from your next message."
|
msgstr "Connected. Its tools are available from your next message."
|
||||||
@ -3891,6 +3899,10 @@ msgstr "No projects yet."
|
|||||||
msgid "No providers found."
|
msgid "No providers found."
|
||||||
msgstr "No providers found."
|
msgstr "No providers found."
|
||||||
|
|
||||||
|
#: src/pages/ActivityList.tsx
|
||||||
|
msgid "No recent activity"
|
||||||
|
msgstr "No recent activity"
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
msgid "No remote MCP servers found"
|
msgid "No remote MCP servers found"
|
||||||
msgstr "No remote MCP servers found"
|
msgstr "No remote MCP servers found"
|
||||||
@ -5185,6 +5197,7 @@ msgstr "Speaking on or off"
|
|||||||
msgid "Speaking with intent…"
|
msgid "Speaking with intent…"
|
||||||
msgstr "Speaking with intent…"
|
msgstr "Speaking with intent…"
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
msgid "Specialist crew"
|
msgid "Specialist crew"
|
||||||
msgstr "Specialist crew"
|
msgstr "Specialist crew"
|
||||||
@ -5526,10 +5539,6 @@ msgstr "This subscription sign-in is not available in Rakazo yet. Use a deployme
|
|||||||
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
||||||
msgstr "This will permanently delete this project and all its conversations, bots, and memory."
|
msgstr "This will permanently delete this project and all its conversations, bots, and memory."
|
||||||
|
|
||||||
#: src/pages/Shell.tsx
|
|
||||||
msgid "Threads by topic · folders"
|
|
||||||
msgstr "Threads by topic · folders"
|
|
||||||
|
|
||||||
#: src/pages/ModelTierSettings.tsx
|
#: src/pages/ModelTierSettings.tsx
|
||||||
#: src/pages/shell/bot-panel.tsx
|
#: src/pages/shell/bot-panel.tsx
|
||||||
msgid "Tiers"
|
msgid "Tiers"
|
||||||
@ -5715,6 +5724,7 @@ msgstr "Until when?"
|
|||||||
|
|
||||||
#: src/components/DesktopUpdates.tsx
|
#: src/components/DesktopUpdates.tsx
|
||||||
#: src/components/SoftwareUpdateSection.tsx
|
#: src/components/SoftwareUpdateSection.tsx
|
||||||
|
#: src/pages/AccountSettingsOverlay.tsx
|
||||||
msgid "Up to date"
|
msgid "Up to date"
|
||||||
msgstr "Up to date"
|
msgstr "Up to date"
|
||||||
|
|
||||||
@ -6100,6 +6110,10 @@ msgstr "Your Oo talks with you naturally when this is on."
|
|||||||
msgid "Your Oo will communicate in this language. You can change it later."
|
msgid "Your Oo will communicate in this language. You can change it later."
|
||||||
msgstr "Your Oo will communicate in this language. You can change it later."
|
msgstr "Your Oo will communicate in this language. You can change it later."
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Your profile"
|
||||||
|
msgstr "Your profile"
|
||||||
|
|
||||||
#: src/pages/Welcome.tsx
|
#: src/pages/Welcome.tsx
|
||||||
msgid "Your specialists. One calm place to talk."
|
msgid "Your specialists. One calm place to talk."
|
||||||
msgstr "Your specialists. One calm place to talk."
|
msgstr "Your specialists. One calm place to talk."
|
||||||
|
|||||||
@ -1001,7 +1001,7 @@ msgstr ""
|
|||||||
msgid "Booting live desktop…"
|
msgid "Booting live desktop…"
|
||||||
msgstr "Iniciando escritorio en vivo…"
|
msgstr "Iniciando escritorio en vivo…"
|
||||||
|
|
||||||
#. placeholder {0}: active?.name
|
#. placeholder {0}: (active ?? coordinatorBot)?.name
|
||||||
#: src/pages/Shell.tsx
|
#: src/pages/Shell.tsx
|
||||||
msgid "Booting up {0}’s computer"
|
msgid "Booting up {0}’s computer"
|
||||||
msgstr "Iniciando la computadora de {0}"
|
msgstr "Iniciando la computadora de {0}"
|
||||||
@ -1494,6 +1494,10 @@ msgstr "Computadoras"
|
|||||||
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
||||||
msgstr "Las computadoras están apagadas. Configura SANDBOX_PROVIDER=docker con SANDBOX_SUPERVISOR_TOKEN, o establece SANDBOX_PROVIDER en e2b, daytona o box con su clave de API. Recrea el stack después de cambiar .env."
|
msgstr "Las computadoras están apagadas. Configura SANDBOX_PROVIDER=docker con SANDBOX_SUPERVISOR_TOKEN, o establece SANDBOX_PROVIDER en e2b, daytona o box con su clave de API. Recrea el stack después de cambiar .env."
|
||||||
|
|
||||||
|
#: src/pages/PluginsOverlay.tsx
|
||||||
|
msgid "Configure a plugin catalog on the server to connect apps."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ModelSettingsOverlay.tsx
|
#: src/pages/ModelSettingsOverlay.tsx
|
||||||
msgid "Configured by deployment"
|
msgid "Configured by deployment"
|
||||||
msgstr "Configurado por el despliegue"
|
msgstr "Configurado por el despliegue"
|
||||||
@ -1585,6 +1589,10 @@ msgstr "Conectado y usando {0}."
|
|||||||
msgid "Connected app"
|
msgid "Connected app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Connected tools"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/message-cards.tsx
|
#: src/pages/shell/message-cards.tsx
|
||||||
msgid "Connected. Its tools are available from your next message."
|
msgid "Connected. Its tools are available from your next message."
|
||||||
msgstr "Conectado: sus herramientas estarán disponibles desde tu próximo mensaje."
|
msgstr "Conectado: sus herramientas estarán disponibles desde tu próximo mensaje."
|
||||||
@ -3891,6 +3899,10 @@ msgstr ""
|
|||||||
msgid "No providers found."
|
msgid "No providers found."
|
||||||
msgstr "No se encontraron proveedores."
|
msgstr "No se encontraron proveedores."
|
||||||
|
|
||||||
|
#: src/pages/ActivityList.tsx
|
||||||
|
msgid "No recent activity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
msgid "No remote MCP servers found"
|
msgid "No remote MCP servers found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5185,6 +5197,7 @@ msgstr ""
|
|||||||
msgid "Speaking with intent…"
|
msgid "Speaking with intent…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
msgid "Specialist crew"
|
msgid "Specialist crew"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5526,10 +5539,6 @@ msgstr "Este inicio de sesión por suscripción aún no está disponible en Raka
|
|||||||
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Shell.tsx
|
|
||||||
msgid "Threads by topic · folders"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/ModelTierSettings.tsx
|
#: src/pages/ModelTierSettings.tsx
|
||||||
#: src/pages/shell/bot-panel.tsx
|
#: src/pages/shell/bot-panel.tsx
|
||||||
msgid "Tiers"
|
msgid "Tiers"
|
||||||
@ -5715,6 +5724,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/DesktopUpdates.tsx
|
#: src/components/DesktopUpdates.tsx
|
||||||
#: src/components/SoftwareUpdateSection.tsx
|
#: src/components/SoftwareUpdateSection.tsx
|
||||||
|
#: src/pages/AccountSettingsOverlay.tsx
|
||||||
msgid "Up to date"
|
msgid "Up to date"
|
||||||
msgstr "Actualizado"
|
msgstr "Actualizado"
|
||||||
|
|
||||||
@ -6100,6 +6110,10 @@ msgstr ""
|
|||||||
msgid "Your Oo will communicate in this language. You can change it later."
|
msgid "Your Oo will communicate in this language. You can change it later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Your profile"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Welcome.tsx
|
#: src/pages/Welcome.tsx
|
||||||
msgid "Your specialists. One calm place to talk."
|
msgid "Your specialists. One calm place to talk."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -1001,7 +1001,7 @@ msgstr ""
|
|||||||
msgid "Booting live desktop…"
|
msgid "Booting live desktop…"
|
||||||
msgstr "लाइव डेस्कटॉप शुरू हो रहा है…"
|
msgstr "लाइव डेस्कटॉप शुरू हो रहा है…"
|
||||||
|
|
||||||
#. placeholder {0}: active?.name
|
#. placeholder {0}: (active ?? coordinatorBot)?.name
|
||||||
#: src/pages/Shell.tsx
|
#: src/pages/Shell.tsx
|
||||||
msgid "Booting up {0}’s computer"
|
msgid "Booting up {0}’s computer"
|
||||||
msgstr "{0} का कंप्यूटर शुरू हो रहा है"
|
msgstr "{0} का कंप्यूटर शुरू हो रहा है"
|
||||||
@ -1494,6 +1494,10 @@ msgstr "कंप्यूटर"
|
|||||||
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
||||||
msgstr "कंप्यूटर बंद हैं। SANDBOX_PROVIDER=docker और SANDBOX_SUPERVISOR_TOKEN सेट करें, या SANDBOX_PROVIDER को e2b, daytona, या box पर सेट कर उसका API key दें। .env बदलने के बाद स्टैक फिर बनाएँ।"
|
msgstr "कंप्यूटर बंद हैं। SANDBOX_PROVIDER=docker और SANDBOX_SUPERVISOR_TOKEN सेट करें, या SANDBOX_PROVIDER को e2b, daytona, या box पर सेट कर उसका API key दें। .env बदलने के बाद स्टैक फिर बनाएँ।"
|
||||||
|
|
||||||
|
#: src/pages/PluginsOverlay.tsx
|
||||||
|
msgid "Configure a plugin catalog on the server to connect apps."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ModelSettingsOverlay.tsx
|
#: src/pages/ModelSettingsOverlay.tsx
|
||||||
msgid "Configured by deployment"
|
msgid "Configured by deployment"
|
||||||
msgstr "डिप्लॉयमेंट द्वारा कॉन्फ़िगर"
|
msgstr "डिप्लॉयमेंट द्वारा कॉन्फ़िगर"
|
||||||
@ -1585,6 +1589,10 @@ msgstr "{0} कनेक्ट किया गया और उपयोग म
|
|||||||
msgid "Connected app"
|
msgid "Connected app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Connected tools"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/message-cards.tsx
|
#: src/pages/shell/message-cards.tsx
|
||||||
msgid "Connected. Its tools are available from your next message."
|
msgid "Connected. Its tools are available from your next message."
|
||||||
msgstr "कनेक्टेड। इसके टूल आपके अगले संदेश से उपलब्ध हैं।"
|
msgstr "कनेक्टेड। इसके टूल आपके अगले संदेश से उपलब्ध हैं।"
|
||||||
@ -3891,6 +3899,10 @@ msgstr ""
|
|||||||
msgid "No providers found."
|
msgid "No providers found."
|
||||||
msgstr "कोई प्रोवाइडर नहीं मिला।"
|
msgstr "कोई प्रोवाइडर नहीं मिला।"
|
||||||
|
|
||||||
|
#: src/pages/ActivityList.tsx
|
||||||
|
msgid "No recent activity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
msgid "No remote MCP servers found"
|
msgid "No remote MCP servers found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5185,6 +5197,7 @@ msgstr ""
|
|||||||
msgid "Speaking with intent…"
|
msgid "Speaking with intent…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
msgid "Specialist crew"
|
msgid "Specialist crew"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5526,10 +5539,6 @@ msgstr "यह सब्सक्रिप्शन साइन-इन अभ
|
|||||||
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Shell.tsx
|
|
||||||
msgid "Threads by topic · folders"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/ModelTierSettings.tsx
|
#: src/pages/ModelTierSettings.tsx
|
||||||
#: src/pages/shell/bot-panel.tsx
|
#: src/pages/shell/bot-panel.tsx
|
||||||
msgid "Tiers"
|
msgid "Tiers"
|
||||||
@ -5715,6 +5724,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/DesktopUpdates.tsx
|
#: src/components/DesktopUpdates.tsx
|
||||||
#: src/components/SoftwareUpdateSection.tsx
|
#: src/components/SoftwareUpdateSection.tsx
|
||||||
|
#: src/pages/AccountSettingsOverlay.tsx
|
||||||
msgid "Up to date"
|
msgid "Up to date"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -6100,6 +6110,10 @@ msgstr ""
|
|||||||
msgid "Your Oo will communicate in this language. You can change it later."
|
msgid "Your Oo will communicate in this language. You can change it later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Your profile"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Welcome.tsx
|
#: src/pages/Welcome.tsx
|
||||||
msgid "Your specialists. One calm place to talk."
|
msgid "Your specialists. One calm place to talk."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -1001,7 +1001,7 @@ msgstr ""
|
|||||||
msgid "Booting live desktop…"
|
msgid "Booting live desktop…"
|
||||||
msgstr "라이브 데스크톱 시작 중…"
|
msgstr "라이브 데스크톱 시작 중…"
|
||||||
|
|
||||||
#. placeholder {0}: active?.name
|
#. placeholder {0}: (active ?? coordinatorBot)?.name
|
||||||
#: src/pages/Shell.tsx
|
#: src/pages/Shell.tsx
|
||||||
msgid "Booting up {0}’s computer"
|
msgid "Booting up {0}’s computer"
|
||||||
msgstr "{0}의 컴퓨터를 켜는 중"
|
msgstr "{0}의 컴퓨터를 켜는 중"
|
||||||
@ -1494,6 +1494,10 @@ msgstr "컴퓨터"
|
|||||||
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
||||||
msgstr "컴퓨터가 꺼져 있습니다. SANDBOX_PROVIDER=docker와 SANDBOX_SUPERVISOR_TOKEN을 설정하거나, SANDBOX_PROVIDER를 e2b, daytona, box 중 하나로 두고 해당 API 키를 넣으세요. .env 변경 후 스택을 다시 만드세요."
|
msgstr "컴퓨터가 꺼져 있습니다. SANDBOX_PROVIDER=docker와 SANDBOX_SUPERVISOR_TOKEN을 설정하거나, SANDBOX_PROVIDER를 e2b, daytona, box 중 하나로 두고 해당 API 키를 넣으세요. .env 변경 후 스택을 다시 만드세요."
|
||||||
|
|
||||||
|
#: src/pages/PluginsOverlay.tsx
|
||||||
|
msgid "Configure a plugin catalog on the server to connect apps."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ModelSettingsOverlay.tsx
|
#: src/pages/ModelSettingsOverlay.tsx
|
||||||
msgid "Configured by deployment"
|
msgid "Configured by deployment"
|
||||||
msgstr "서버에서 설정됨"
|
msgstr "서버에서 설정됨"
|
||||||
@ -1585,6 +1589,10 @@ msgstr "{0}에 연결하고 기본 모델로 설정했습니다."
|
|||||||
msgid "Connected app"
|
msgid "Connected app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Connected tools"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/message-cards.tsx
|
#: src/pages/shell/message-cards.tsx
|
||||||
msgid "Connected. Its tools are available from your next message."
|
msgid "Connected. Its tools are available from your next message."
|
||||||
msgstr "연결되었습니다. 다음 메시지부터 이 도구를 사용할 수 있습니다."
|
msgstr "연결되었습니다. 다음 메시지부터 이 도구를 사용할 수 있습니다."
|
||||||
@ -3891,6 +3899,10 @@ msgstr ""
|
|||||||
msgid "No providers found."
|
msgid "No providers found."
|
||||||
msgstr "검색된 AI 서비스가 없습니다."
|
msgstr "검색된 AI 서비스가 없습니다."
|
||||||
|
|
||||||
|
#: src/pages/ActivityList.tsx
|
||||||
|
msgid "No recent activity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
msgid "No remote MCP servers found"
|
msgid "No remote MCP servers found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5185,6 +5197,7 @@ msgstr ""
|
|||||||
msgid "Speaking with intent…"
|
msgid "Speaking with intent…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
msgid "Specialist crew"
|
msgid "Specialist crew"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5526,10 +5539,6 @@ msgstr "이 구독 로그인 방식은 아직 Rakazo에서 지원되지 않습
|
|||||||
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Shell.tsx
|
|
||||||
msgid "Threads by topic · folders"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/ModelTierSettings.tsx
|
#: src/pages/ModelTierSettings.tsx
|
||||||
#: src/pages/shell/bot-panel.tsx
|
#: src/pages/shell/bot-panel.tsx
|
||||||
msgid "Tiers"
|
msgid "Tiers"
|
||||||
@ -5715,6 +5724,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/DesktopUpdates.tsx
|
#: src/components/DesktopUpdates.tsx
|
||||||
#: src/components/SoftwareUpdateSection.tsx
|
#: src/components/SoftwareUpdateSection.tsx
|
||||||
|
#: src/pages/AccountSettingsOverlay.tsx
|
||||||
msgid "Up to date"
|
msgid "Up to date"
|
||||||
msgstr "최신 상태"
|
msgstr "최신 상태"
|
||||||
|
|
||||||
@ -6100,6 +6110,10 @@ msgstr ""
|
|||||||
msgid "Your Oo will communicate in this language. You can change it later."
|
msgid "Your Oo will communicate in this language. You can change it later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Your profile"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Welcome.tsx
|
#: src/pages/Welcome.tsx
|
||||||
msgid "Your specialists. One calm place to talk."
|
msgid "Your specialists. One calm place to talk."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -1001,7 +1001,7 @@ msgstr ""
|
|||||||
msgid "Booting live desktop…"
|
msgid "Booting live desktop…"
|
||||||
msgstr "Iniciando a área de trabalho ao vivo..."
|
msgstr "Iniciando a área de trabalho ao vivo..."
|
||||||
|
|
||||||
#. placeholder {0}: active?.name
|
#. placeholder {0}: (active ?? coordinatorBot)?.name
|
||||||
#: src/pages/Shell.tsx
|
#: src/pages/Shell.tsx
|
||||||
msgid "Booting up {0}’s computer"
|
msgid "Booting up {0}’s computer"
|
||||||
msgstr "Inicializando o computador do {0}"
|
msgstr "Inicializando o computador do {0}"
|
||||||
@ -1494,6 +1494,10 @@ msgstr "Computadores"
|
|||||||
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
||||||
msgstr "Computadores estão desligados. Defina SANDBOX_PROVIDER=docker com SANDBOX_SUPERVISOR_TOKEN, ou SANDBOX_PROVIDER como e2b, daytona ou box com a chave de API. Recrie o stack após alterar o .env."
|
msgstr "Computadores estão desligados. Defina SANDBOX_PROVIDER=docker com SANDBOX_SUPERVISOR_TOKEN, ou SANDBOX_PROVIDER como e2b, daytona ou box com a chave de API. Recrie o stack após alterar o .env."
|
||||||
|
|
||||||
|
#: src/pages/PluginsOverlay.tsx
|
||||||
|
msgid "Configure a plugin catalog on the server to connect apps."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ModelSettingsOverlay.tsx
|
#: src/pages/ModelSettingsOverlay.tsx
|
||||||
msgid "Configured by deployment"
|
msgid "Configured by deployment"
|
||||||
msgstr "Configurado por implantação"
|
msgstr "Configurado por implantação"
|
||||||
@ -1585,6 +1589,10 @@ msgstr "Conectado e usando {0}."
|
|||||||
msgid "Connected app"
|
msgid "Connected app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Connected tools"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/message-cards.tsx
|
#: src/pages/shell/message-cards.tsx
|
||||||
msgid "Connected. Its tools are available from your next message."
|
msgid "Connected. Its tools are available from your next message."
|
||||||
msgstr "Conectado. Suas ferramentas estão disponíveis na sua próxima mensagem."
|
msgstr "Conectado. Suas ferramentas estão disponíveis na sua próxima mensagem."
|
||||||
@ -3891,6 +3899,10 @@ msgstr ""
|
|||||||
msgid "No providers found."
|
msgid "No providers found."
|
||||||
msgstr "Nenhum provedor encontrado"
|
msgstr "Nenhum provedor encontrado"
|
||||||
|
|
||||||
|
#: src/pages/ActivityList.tsx
|
||||||
|
msgid "No recent activity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
msgid "No remote MCP servers found"
|
msgid "No remote MCP servers found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5185,6 +5197,7 @@ msgstr ""
|
|||||||
msgid "Speaking with intent…"
|
msgid "Speaking with intent…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
msgid "Specialist crew"
|
msgid "Specialist crew"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5526,10 +5539,6 @@ msgstr "Este login de assinatura ainda não está disponível no Rakazo. Use uma
|
|||||||
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Shell.tsx
|
|
||||||
msgid "Threads by topic · folders"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/ModelTierSettings.tsx
|
#: src/pages/ModelTierSettings.tsx
|
||||||
#: src/pages/shell/bot-panel.tsx
|
#: src/pages/shell/bot-panel.tsx
|
||||||
msgid "Tiers"
|
msgid "Tiers"
|
||||||
@ -5715,6 +5724,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/DesktopUpdates.tsx
|
#: src/components/DesktopUpdates.tsx
|
||||||
#: src/components/SoftwareUpdateSection.tsx
|
#: src/components/SoftwareUpdateSection.tsx
|
||||||
|
#: src/pages/AccountSettingsOverlay.tsx
|
||||||
msgid "Up to date"
|
msgid "Up to date"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -6100,6 +6110,10 @@ msgstr ""
|
|||||||
msgid "Your Oo will communicate in this language. You can change it later."
|
msgid "Your Oo will communicate in this language. You can change it later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Your profile"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Welcome.tsx
|
#: src/pages/Welcome.tsx
|
||||||
msgid "Your specialists. One calm place to talk."
|
msgid "Your specialists. One calm place to talk."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -1001,7 +1001,7 @@ msgstr ""
|
|||||||
msgid "Booting live desktop…"
|
msgid "Booting live desktop…"
|
||||||
msgstr "Загрузка рабочего стола…"
|
msgstr "Загрузка рабочего стола…"
|
||||||
|
|
||||||
#. placeholder {0}: active?.name
|
#. placeholder {0}: (active ?? coordinatorBot)?.name
|
||||||
#: src/pages/Shell.tsx
|
#: src/pages/Shell.tsx
|
||||||
msgid "Booting up {0}’s computer"
|
msgid "Booting up {0}’s computer"
|
||||||
msgstr "Загрузка компьютера {0}"
|
msgstr "Загрузка компьютера {0}"
|
||||||
@ -1494,6 +1494,10 @@ msgstr "Компьютеры"
|
|||||||
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
||||||
msgstr "Компьютеры выключены. Установите SANDBOX_PROVIDER=docker с помощью SANDBOX_SUPERVISOR_TOKEN или установите для SANDBOX_PROVIDER значение e2b, Daytona или Box с его ключом API. Воссоздайте стек после изменения .env."
|
msgstr "Компьютеры выключены. Установите SANDBOX_PROVIDER=docker с помощью SANDBOX_SUPERVISOR_TOKEN или установите для SANDBOX_PROVIDER значение e2b, Daytona или Box с его ключом API. Воссоздайте стек после изменения .env."
|
||||||
|
|
||||||
|
#: src/pages/PluginsOverlay.tsx
|
||||||
|
msgid "Configure a plugin catalog on the server to connect apps."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ModelSettingsOverlay.tsx
|
#: src/pages/ModelSettingsOverlay.tsx
|
||||||
msgid "Configured by deployment"
|
msgid "Configured by deployment"
|
||||||
msgstr "Настраивается путем развертывания"
|
msgstr "Настраивается путем развертывания"
|
||||||
@ -1585,6 +1589,10 @@ msgstr "Подключено и используется {0}."
|
|||||||
msgid "Connected app"
|
msgid "Connected app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Connected tools"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/message-cards.tsx
|
#: src/pages/shell/message-cards.tsx
|
||||||
msgid "Connected. Its tools are available from your next message."
|
msgid "Connected. Its tools are available from your next message."
|
||||||
msgstr "Подключено. Его инструменты доступны из вашего следующего сообщения."
|
msgstr "Подключено. Его инструменты доступны из вашего следующего сообщения."
|
||||||
@ -3891,6 +3899,10 @@ msgstr ""
|
|||||||
msgid "No providers found."
|
msgid "No providers found."
|
||||||
msgstr "Поставщики не найдены."
|
msgstr "Поставщики не найдены."
|
||||||
|
|
||||||
|
#: src/pages/ActivityList.tsx
|
||||||
|
msgid "No recent activity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
msgid "No remote MCP servers found"
|
msgid "No remote MCP servers found"
|
||||||
msgstr "Удаленные MCP-серверы не найдены"
|
msgstr "Удаленные MCP-серверы не найдены"
|
||||||
@ -5185,6 +5197,7 @@ msgstr ""
|
|||||||
msgid "Speaking with intent…"
|
msgid "Speaking with intent…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
msgid "Specialist crew"
|
msgid "Specialist crew"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5526,10 +5539,6 @@ msgstr "Вход по этой подписке пока недоступен в
|
|||||||
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Shell.tsx
|
|
||||||
msgid "Threads by topic · folders"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/ModelTierSettings.tsx
|
#: src/pages/ModelTierSettings.tsx
|
||||||
#: src/pages/shell/bot-panel.tsx
|
#: src/pages/shell/bot-panel.tsx
|
||||||
msgid "Tiers"
|
msgid "Tiers"
|
||||||
@ -5715,6 +5724,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/DesktopUpdates.tsx
|
#: src/components/DesktopUpdates.tsx
|
||||||
#: src/components/SoftwareUpdateSection.tsx
|
#: src/components/SoftwareUpdateSection.tsx
|
||||||
|
#: src/pages/AccountSettingsOverlay.tsx
|
||||||
msgid "Up to date"
|
msgid "Up to date"
|
||||||
msgstr "Актуально"
|
msgstr "Актуально"
|
||||||
|
|
||||||
@ -6100,6 +6110,10 @@ msgstr ""
|
|||||||
msgid "Your Oo will communicate in this language. You can change it later."
|
msgid "Your Oo will communicate in this language. You can change it later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Your profile"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Welcome.tsx
|
#: src/pages/Welcome.tsx
|
||||||
msgid "Your specialists. One calm place to talk."
|
msgid "Your specialists. One calm place to talk."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -1001,7 +1001,7 @@ msgstr ""
|
|||||||
msgid "Booting live desktop…"
|
msgid "Booting live desktop…"
|
||||||
msgstr "Canlı masaüstü başlatılıyor…"
|
msgstr "Canlı masaüstü başlatılıyor…"
|
||||||
|
|
||||||
#. placeholder {0}: active?.name
|
#. placeholder {0}: (active ?? coordinatorBot)?.name
|
||||||
#: src/pages/Shell.tsx
|
#: src/pages/Shell.tsx
|
||||||
msgid "Booting up {0}’s computer"
|
msgid "Booting up {0}’s computer"
|
||||||
msgstr "{0} adlı botun bilgisayarı başlatılıyor"
|
msgstr "{0} adlı botun bilgisayarı başlatılıyor"
|
||||||
@ -1494,6 +1494,10 @@ msgstr "Bilgisayarlar"
|
|||||||
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
||||||
msgstr "Bilgisayarlar kapalı. SANDBOX_PROVIDER=docker ve SANDBOX_SUPERVISOR_TOKEN ayarlayın; ya da SANDBOX_PROVIDER değerini e2b, daytona veya box yapıp API anahtarını ekleyin. .env değişince stacki yeniden oluşturun."
|
msgstr "Bilgisayarlar kapalı. SANDBOX_PROVIDER=docker ve SANDBOX_SUPERVISOR_TOKEN ayarlayın; ya da SANDBOX_PROVIDER değerini e2b, daytona veya box yapıp API anahtarını ekleyin. .env değişince stacki yeniden oluşturun."
|
||||||
|
|
||||||
|
#: src/pages/PluginsOverlay.tsx
|
||||||
|
msgid "Configure a plugin catalog on the server to connect apps."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ModelSettingsOverlay.tsx
|
#: src/pages/ModelSettingsOverlay.tsx
|
||||||
msgid "Configured by deployment"
|
msgid "Configured by deployment"
|
||||||
msgstr "Dağıtım tarafından yapılandırıldı"
|
msgstr "Dağıtım tarafından yapılandırıldı"
|
||||||
@ -1585,6 +1589,10 @@ msgstr "Bağlandı, {0} kullanılıyor."
|
|||||||
msgid "Connected app"
|
msgid "Connected app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Connected tools"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/message-cards.tsx
|
#: src/pages/shell/message-cards.tsx
|
||||||
msgid "Connected. Its tools are available from your next message."
|
msgid "Connected. Its tools are available from your next message."
|
||||||
msgstr "Bağlandı. Araçları bir sonraki mesajından itibaren kullanılabilir."
|
msgstr "Bağlandı. Araçları bir sonraki mesajından itibaren kullanılabilir."
|
||||||
@ -3891,6 +3899,10 @@ msgstr ""
|
|||||||
msgid "No providers found."
|
msgid "No providers found."
|
||||||
msgstr "Sağlayıcı bulunamadı."
|
msgstr "Sağlayıcı bulunamadı."
|
||||||
|
|
||||||
|
#: src/pages/ActivityList.tsx
|
||||||
|
msgid "No recent activity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
msgid "No remote MCP servers found"
|
msgid "No remote MCP servers found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5185,6 +5197,7 @@ msgstr ""
|
|||||||
msgid "Speaking with intent…"
|
msgid "Speaking with intent…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
msgid "Specialist crew"
|
msgid "Specialist crew"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5526,10 +5539,6 @@ msgstr "Bu abonelik girişi Rakazo'da henüz kullanılamıyor. Bir dağıtım ki
|
|||||||
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Shell.tsx
|
|
||||||
msgid "Threads by topic · folders"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/ModelTierSettings.tsx
|
#: src/pages/ModelTierSettings.tsx
|
||||||
#: src/pages/shell/bot-panel.tsx
|
#: src/pages/shell/bot-panel.tsx
|
||||||
msgid "Tiers"
|
msgid "Tiers"
|
||||||
@ -5715,6 +5724,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/DesktopUpdates.tsx
|
#: src/components/DesktopUpdates.tsx
|
||||||
#: src/components/SoftwareUpdateSection.tsx
|
#: src/components/SoftwareUpdateSection.tsx
|
||||||
|
#: src/pages/AccountSettingsOverlay.tsx
|
||||||
msgid "Up to date"
|
msgid "Up to date"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -6100,6 +6110,10 @@ msgstr ""
|
|||||||
msgid "Your Oo will communicate in this language. You can change it later."
|
msgid "Your Oo will communicate in this language. You can change it later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Your profile"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Welcome.tsx
|
#: src/pages/Welcome.tsx
|
||||||
msgid "Your specialists. One calm place to talk."
|
msgid "Your specialists. One calm place to talk."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -1001,7 +1001,7 @@ msgstr ""
|
|||||||
msgid "Booting live desktop…"
|
msgid "Booting live desktop…"
|
||||||
msgstr "正在启动实时桌面…"
|
msgstr "正在启动实时桌面…"
|
||||||
|
|
||||||
#. placeholder {0}: active?.name
|
#. placeholder {0}: (active ?? coordinatorBot)?.name
|
||||||
#: src/pages/Shell.tsx
|
#: src/pages/Shell.tsx
|
||||||
msgid "Booting up {0}’s computer"
|
msgid "Booting up {0}’s computer"
|
||||||
msgstr "正在启动 {0} 的电脑"
|
msgstr "正在启动 {0} 的电脑"
|
||||||
@ -1494,6 +1494,10 @@ msgstr "电脑"
|
|||||||
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
msgid "Computers are off. Set SANDBOX_PROVIDER=docker with SANDBOX_SUPERVISOR_TOKEN, or set SANDBOX_PROVIDER to e2b, daytona, or box with its API key. Recreate the stack after changing .env."
|
||||||
msgstr "电脑未开启。请设置 SANDBOX_PROVIDER=docker 并提供 SANDBOX_SUPERVISOR_TOKEN,或将 SANDBOX_PROVIDER 设为 e2b、daytona 或 box 并提供对应 API 密钥。更改 .env 后请重建环境。"
|
msgstr "电脑未开启。请设置 SANDBOX_PROVIDER=docker 并提供 SANDBOX_SUPERVISOR_TOKEN,或将 SANDBOX_PROVIDER 设为 e2b、daytona 或 box 并提供对应 API 密钥。更改 .env 后请重建环境。"
|
||||||
|
|
||||||
|
#: src/pages/PluginsOverlay.tsx
|
||||||
|
msgid "Configure a plugin catalog on the server to connect apps."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/ModelSettingsOverlay.tsx
|
#: src/pages/ModelSettingsOverlay.tsx
|
||||||
msgid "Configured by deployment"
|
msgid "Configured by deployment"
|
||||||
msgstr "由部署配置"
|
msgstr "由部署配置"
|
||||||
@ -1585,6 +1589,10 @@ msgstr "已连接并使用 {0}。"
|
|||||||
msgid "Connected app"
|
msgid "Connected app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Connected tools"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/shell/message-cards.tsx
|
#: src/pages/shell/message-cards.tsx
|
||||||
msgid "Connected. Its tools are available from your next message."
|
msgid "Connected. Its tools are available from your next message."
|
||||||
msgstr "已连接,下一条消息起即可使用其工具。"
|
msgstr "已连接,下一条消息起即可使用其工具。"
|
||||||
@ -3891,6 +3899,10 @@ msgstr ""
|
|||||||
msgid "No providers found."
|
msgid "No providers found."
|
||||||
msgstr "未找到提供方。"
|
msgstr "未找到提供方。"
|
||||||
|
|
||||||
|
#: src/pages/ActivityList.tsx
|
||||||
|
msgid "No recent activity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/integrations/IntegrationSetup.tsx
|
#: src/components/integrations/IntegrationSetup.tsx
|
||||||
msgid "No remote MCP servers found"
|
msgid "No remote MCP servers found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5185,6 +5197,7 @@ msgstr ""
|
|||||||
msgid "Speaking with intent…"
|
msgid "Speaking with intent…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
#: src/lib/onboarding-copy.ts
|
#: src/lib/onboarding-copy.ts
|
||||||
msgid "Specialist crew"
|
msgid "Specialist crew"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5526,10 +5539,6 @@ msgstr "此订阅登录在 Rakazo 中尚不可用。请使用部署凭据或选
|
|||||||
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
msgid "This will permanently delete this project and all its conversations, bots, and memory."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Shell.tsx
|
|
||||||
msgid "Threads by topic · folders"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/pages/ModelTierSettings.tsx
|
#: src/pages/ModelTierSettings.tsx
|
||||||
#: src/pages/shell/bot-panel.tsx
|
#: src/pages/shell/bot-panel.tsx
|
||||||
msgid "Tiers"
|
msgid "Tiers"
|
||||||
@ -5715,6 +5724,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/DesktopUpdates.tsx
|
#: src/components/DesktopUpdates.tsx
|
||||||
#: src/components/SoftwareUpdateSection.tsx
|
#: src/components/SoftwareUpdateSection.tsx
|
||||||
|
#: src/pages/AccountSettingsOverlay.tsx
|
||||||
msgid "Up to date"
|
msgid "Up to date"
|
||||||
msgstr "已是最新"
|
msgstr "已是最新"
|
||||||
|
|
||||||
@ -6100,6 +6110,10 @@ msgstr ""
|
|||||||
msgid "Your Oo will communicate in this language. You can change it later."
|
msgid "Your Oo will communicate in this language. You can change it later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/onboarding/OnboardingDiscoveryShell.tsx
|
||||||
|
msgid "Your profile"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/Welcome.tsx
|
#: src/pages/Welcome.tsx
|
||||||
msgid "Your specialists. One calm place to talk."
|
msgid "Your specialists. One calm place to talk."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -188,6 +188,11 @@ export function UpdatesSettingsPanel({
|
|||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<DesktopUpdateSection />
|
<DesktopUpdateSection />
|
||||||
<SoftwareUpdateSection isDeploymentOwner={isDeploymentOwner} />
|
<SoftwareUpdateSection isDeploymentOwner={isDeploymentOwner} />
|
||||||
|
<section className="hidden rounded-xl border border-border px-4 py-4 only:block">
|
||||||
|
<p className="text-ui-body-sm text-muted-foreground">
|
||||||
|
<Trans>Up to date</Trans>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,13 @@ export function ActivityList({ onOpenRun }: ActivityListProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeRuns.length === 0 && recentRuns.length === 0) return null;
|
if (activeRuns.length === 0 && recentRuns.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="mb-2 border-b border-border px-2.5 py-2 text-ui-body-sm text-muted-foreground/80">
|
||||||
|
<Trans>No recent activity</Trans>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-2 border-b border-border pb-2">
|
<div className="mb-2 border-b border-border pb-2">
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import {
|
|||||||
abortableDelay,
|
abortableDelay,
|
||||||
buildFeaturedConnectorTiles,
|
buildFeaturedConnectorTiles,
|
||||||
CONNECTION_CATALOG_PAGE_SIZE,
|
CONNECTION_CATALOG_PAGE_SIZE,
|
||||||
EMPTY_PLUGIN_CATALOG_MESSAGE,
|
|
||||||
filterConnectionCatalogItems,
|
filterConnectionCatalogItems,
|
||||||
humanizeToolName,
|
humanizeToolName,
|
||||||
isGithubInstall,
|
isGithubInstall,
|
||||||
@ -730,7 +729,7 @@ export function PluginsOverlay({
|
|||||||
<div className="mb-6" data-testid="featured-connectors">
|
<div className="mb-6" data-testid="featured-connectors">
|
||||||
{!loading && catalog.length === 0 ? (
|
{!loading && catalog.length === 0 ? (
|
||||||
<p className="text-ui-body leading-6 text-muted-foreground/80">
|
<p className="text-ui-body leading-6 text-muted-foreground/80">
|
||||||
{EMPTY_PLUGIN_CATALOG_MESSAGE}
|
<Trans>Configure a plugin catalog on the server to connect apps.</Trans>
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
|||||||
@ -176,7 +176,7 @@ export function SettingsOverlay({
|
|||||||
<nav
|
<nav
|
||||||
data-testid="settings-nav"
|
data-testid="settings-nav"
|
||||||
aria-label={t`Settings`}
|
aria-label={t`Settings`}
|
||||||
className="flex shrink-0 flex-row gap-1 overflow-x-auto border-b border-border px-3 py-3 md:w-[200px] md:flex-col md:overflow-y-auto md:border-b-0 md:border-e md:px-3 md:py-4"
|
className="flex shrink-0 flex-row gap-1 overflow-x-auto border-b border-border px-3 py-3 md:w-[220px] md:flex-col md:overflow-y-auto md:border-b-0 md:border-e md:px-3 md:py-4"
|
||||||
>
|
>
|
||||||
{navItems.map((item) => {
|
{navItems.map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
|
|||||||
@ -2937,15 +2937,20 @@ export function ShellPage() {
|
|||||||
overlay: boolean;
|
overlay: boolean;
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
}) {
|
}) {
|
||||||
if (!active) return;
|
const targetBot = active ?? coordinatorBot;
|
||||||
|
if (!targetBot) return;
|
||||||
const needsBoot = force || computer?.state !== "running" || !screenUrl;
|
const needsBoot = force || computer?.state !== "running" || !screenUrl;
|
||||||
if (overlay && needsBoot) setBooting(true);
|
if (overlay && needsBoot) setBooting(true);
|
||||||
setComputerError(null);
|
setComputerError(null);
|
||||||
setComputerErrorFromScreen(false);
|
setComputerErrorFromScreen(false);
|
||||||
try {
|
try {
|
||||||
if (needsBoot) await rpc.computer.boot({ botId: active.id });
|
if (needsBoot) await rpc.computer.boot({ botId: targetBot.id });
|
||||||
if (takeControl) await rpc.computer.takeover({ botId: active.id });
|
if (takeControl) await rpc.computer.takeover({ botId: targetBot.id });
|
||||||
await refreshThread(active.id);
|
if (active) {
|
||||||
|
await refreshThread(targetBot.id);
|
||||||
|
} else {
|
||||||
|
await refreshComputerScreen(targetBot.id);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setComputerError(error instanceof Error ? error.message : t`Could not take control`);
|
setComputerError(error instanceof Error ? error.message : t`Could not take control`);
|
||||||
setComputerErrorFromScreen(false);
|
setComputerErrorFromScreen(false);
|
||||||
@ -3080,8 +3085,9 @@ export function ShellPage() {
|
|||||||
}, [panel, computerOpen, active?.id, computer?.state]);
|
}, [panel, computerOpen, active?.id, computer?.state]);
|
||||||
|
|
||||||
async function openComputer() {
|
async function openComputer() {
|
||||||
if (!active) return;
|
const targetBot = active ?? coordinatorBot;
|
||||||
const needsTakeover = !userHoldsComputerControl(computer, active.id);
|
if (!targetBot) return;
|
||||||
|
const needsTakeover = !userHoldsComputerControl(computer, targetBot.id);
|
||||||
const blocked = computerTakeoverBlocked(computer, snapshot?.run?.status);
|
const blocked = computerTakeoverBlocked(computer, snapshot?.run?.status);
|
||||||
try {
|
try {
|
||||||
await bootComputer({
|
await bootComputer({
|
||||||
@ -3290,12 +3296,7 @@ export function ShellPage() {
|
|||||||
<div className="sidebar-projekte">
|
<div className="sidebar-projekte">
|
||||||
{/* PROJEKTE SECTION */}
|
{/* PROJEKTE SECTION */}
|
||||||
<div className="flex items-center justify-between pt-4 pb-1 px-2.5">
|
<div className="flex items-center justify-between pt-4 pb-1 px-2.5">
|
||||||
<div>
|
<div className="rk-shell-section-label">{t`Projects`}</div>
|
||||||
<div className="rk-shell-section-label">{t`Projects`}</div>
|
|
||||||
<p className="mt-0.5 px-0.5 text-ui-caption rk-sidebar-muted">
|
|
||||||
{t`Threads by topic · folders`}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -3309,18 +3310,9 @@ export function ShellPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{projectSidebarGroups.length === 0 ? (
|
{projectSidebarGroups.length === 0 ? (
|
||||||
<div className="p-4 text-center text-ui-label rk-sidebar-muted">
|
<p className="px-2.5 py-1.5 text-ui-label rk-sidebar-muted">
|
||||||
<p>{t`No projects yet.`}</p>
|
{t`No projects yet.`}
|
||||||
<Button
|
</p>
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="mt-2 text-ui-label"
|
|
||||||
onClick={() => openNewSpaceDialog("project")}
|
|
||||||
>
|
|
||||||
<Plus className="mr-1 size-icon-sm" />
|
|
||||||
{t`Create a project`}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
<DndContext
|
<DndContext
|
||||||
sensors={sensors}
|
sensors={sensors}
|
||||||
@ -3481,6 +3473,7 @@ export function ShellPage() {
|
|||||||
{conversationPickerOpen ? (
|
{conversationPickerOpen ? (
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
align="end"
|
align="end"
|
||||||
|
collisionPadding={12}
|
||||||
className="w-auto gap-0 overflow-hidden p-0 data-closed:animate-none"
|
className="w-auto gap-0 overflow-hidden p-0 data-closed:animate-none"
|
||||||
>
|
>
|
||||||
<ConversationBotPicker
|
<ConversationBotPicker
|
||||||
@ -3677,8 +3670,8 @@ export function ShellPage() {
|
|||||||
: "100%",
|
: "100%",
|
||||||
display: dualSurface.layoutMode === "canvas" ? "none" : "flex",
|
display: dualSurface.layoutMode === "canvas" ? "none" : "flex",
|
||||||
flexDirection: "column" as const,
|
flexDirection: "column" as const,
|
||||||
minWidth: dualSurface.layoutMode === "canvas" ? 0 : 360,
|
minWidth: 0,
|
||||||
flexShrink: 0,
|
flexShrink: 1,
|
||||||
transition: "width 200ms ease",
|
transition: "width 200ms ease",
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
@ -3693,8 +3686,8 @@ export function ShellPage() {
|
|||||||
: "0%",
|
: "0%",
|
||||||
opacity: showCanvasOnDesktop ? 1 : 0,
|
opacity: showCanvasOnDesktop ? 1 : 0,
|
||||||
pointerEvents: showCanvasOnDesktop ? ("auto" as const) : ("none" as const),
|
pointerEvents: showCanvasOnDesktop ? ("auto" as const) : ("none" as const),
|
||||||
minWidth: showCanvasOnDesktop ? 360 : 0,
|
minWidth: 0,
|
||||||
flexShrink: 0,
|
flexShrink: 1,
|
||||||
transition: "width 200ms ease, opacity 150ms ease",
|
transition: "width 200ms ease, opacity 150ms ease",
|
||||||
overflow: showCanvasOnDesktop ? ("visible" as const) : ("hidden" as const),
|
overflow: showCanvasOnDesktop ? ("visible" as const) : ("hidden" as const),
|
||||||
}
|
}
|
||||||
@ -3706,9 +3699,7 @@ export function ShellPage() {
|
|||||||
panel === "create" ||
|
panel === "create" ||
|
||||||
panel === "create-group" ||
|
panel === "create-group" ||
|
||||||
panel === "browser")
|
panel === "browser")
|
||||||
? workspacePaneOpen
|
? "100%"
|
||||||
? workspacePaneWidth
|
|
||||||
: rightSidebarWidth
|
|
||||||
: 0,
|
: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -4243,9 +4234,9 @@ export function ShellPage() {
|
|||||||
data-panel={panel ?? "closed"}
|
data-panel={panel ?? "closed"}
|
||||||
data-auto={panel !== null && panel === autoPanel ? "" : undefined}
|
data-auto={panel !== null && panel === autoPanel ? "" : undefined}
|
||||||
style={canvasStyle}
|
style={canvasStyle}
|
||||||
className={`absolute inset-y-0 end-0 z-20 flex min-h-0 shrink-0 flex-col overflow-hidden bg-background md:relative md:border-0 md:rounded-none md:shadow-none ${
|
className={`absolute inset-0 z-30 flex min-h-0 shrink-0 flex-col overflow-hidden bg-background md:relative md:inset-auto md:z-20 md:border-0 md:rounded-none md:shadow-none ${
|
||||||
isPanelVisible
|
isPanelVisible
|
||||||
? "max-w-[384px] md:max-w-none opacity-100"
|
? "w-full max-w-none md:max-w-none opacity-100"
|
||||||
: "pointer-events-none w-0 opacity-0 md:opacity-0 md:w-0"
|
: "pointer-events-none w-0 opacity-0 md:opacity-0 md:w-0"
|
||||||
} ${!(workspacePaneOpen ? isResizingWorkspacePane : isResizingRight) ? "transition-[width,opacity] duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] motion-reduce:transition-none" : ""}`}
|
} ${!(workspacePaneOpen ? isResizingWorkspacePane : isResizingRight) ? "transition-[width,opacity] duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] motion-reduce:transition-none" : ""}`}
|
||||||
>
|
>
|
||||||
@ -4399,6 +4390,7 @@ export function ShellPage() {
|
|||||||
) : panel === "browser" || (effectiveTab === "browser" && !isOverlayPanel) ? (
|
) : panel === "browser" || (effectiveTab === "browser" && !isOverlayPanel) ? (
|
||||||
<div className="flex-1 min-h-0 flex flex-col">
|
<div className="flex-1 min-h-0 flex flex-col">
|
||||||
<InAppBrowser
|
<InAppBrowser
|
||||||
|
hideCloseButton
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setPanel(null);
|
setPanel(null);
|
||||||
if (!inProject) dualSurface.setLayoutMode("stream");
|
if (!inProject) dualSurface.setLayoutMode("stream");
|
||||||
@ -5292,15 +5284,15 @@ export function ShellPage() {
|
|||||||
{booting ? (
|
{booting ? (
|
||||||
<div className="absolute inset-0 z-30 flex flex-col items-center justify-center gap-[22px] bg-background/95">
|
<div className="absolute inset-0 z-30 flex flex-col items-center justify-center gap-[22px] bg-background/95">
|
||||||
<div className="text-[19px] font-medium text-foreground">
|
<div className="text-[19px] font-medium text-foreground">
|
||||||
<Trans>Booting up {active?.name}’s computer</Trans>
|
<Trans>Booting up {(active ?? coordinatorBot)?.name}’s computer</Trans>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-[5px] w-[min(420px,70%)] overflow-hidden rounded-full bg-accent">
|
<div className="h-[5px] w-[min(420px,70%)] overflow-hidden rounded-full bg-accent">
|
||||||
<div className="h-full w-2/3 rounded-full bg-primary" />
|
<div className="h-full w-2/3 rounded-full bg-primary" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : computerOpen && active ? (
|
) : computerOpen && (active ?? coordinatorBot) ? (
|
||||||
<FullscreenComputerOverlay
|
<FullscreenComputerOverlay
|
||||||
active={active}
|
active={(active ?? coordinatorBot)!}
|
||||||
computer={computer}
|
computer={computer}
|
||||||
computerViewport={computerViewport}
|
computerViewport={computerViewport}
|
||||||
embeddedScreenUrl={embeddedScreenUrl}
|
embeddedScreenUrl={embeddedScreenUrl}
|
||||||
|
|||||||
@ -46,14 +46,14 @@ export function ComputerPanelPreview({
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
data-testid="computer-preview"
|
data-testid="computer-preview"
|
||||||
className="group relative aspect-[16/10] overflow-hidden rounded-[14px] bg-background"
|
className="group relative aspect-[16/10] overflow-hidden rounded-[14px] border border-border bg-muted/35"
|
||||||
>
|
>
|
||||||
{computerOpen ? (
|
{computerOpen ? (
|
||||||
<div className="grid h-full place-items-center text-sm text-muted-foreground/80">
|
<div className="grid h-full place-items-center text-sm text-muted-foreground/80">
|
||||||
<Trans>Open in full window</Trans>
|
<Trans>Open in full window</Trans>
|
||||||
</div>
|
</div>
|
||||||
) : computer?.kind === "desktop" ? (
|
) : computer?.kind === "desktop" ? (
|
||||||
<DesktopKindEmptyState className="grid h-full place-items-center px-6 text-center text-sm text-muted-foreground/80" />
|
<DesktopKindEmptyState className="grid h-full place-items-center px-6 pb-10 text-center text-sm text-muted-foreground/80" />
|
||||||
) : computer?.state === "running" && embeddedScreenUrl && !computerScreenError ? (
|
) : computer?.state === "running" && embeddedScreenUrl && !computerScreenError ? (
|
||||||
<>
|
<>
|
||||||
<iframe
|
<iframe
|
||||||
@ -67,7 +67,11 @@ export function ComputerPanelPreview({
|
|||||||
<AgentCursor botId={active.id} color={active.color} name={active.name} />
|
<AgentCursor botId={active.id} color={active.color} name={active.name} />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid h-full place-items-center px-6 text-center text-sm text-muted-foreground/80">
|
<div
|
||||||
|
className={`grid h-full place-items-center px-6 text-center text-sm text-muted-foreground/80 ${
|
||||||
|
resting && !computerScreenError ? "pb-11" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{computerScreenError ??
|
{computerScreenError ??
|
||||||
(computersAreUnavailable(sandboxProvider) ? (
|
(computersAreUnavailable(sandboxProvider) ? (
|
||||||
<ComputersUnavailableHint />
|
<ComputersUnavailableHint />
|
||||||
@ -85,7 +89,7 @@ export function ComputerPanelPreview({
|
|||||||
type="button"
|
type="button"
|
||||||
data-testid="computer-preview-open"
|
data-testid="computer-preview-open"
|
||||||
data-resting={resting ? "" : undefined}
|
data-resting={resting ? "" : undefined}
|
||||||
className="absolute inset-0 flex cursor-pointer items-center justify-center opacity-0 transition-opacity group-hover:bg-overlay/40 group-hover:opacity-100 focus-visible:bg-overlay/40 focus-visible:opacity-100 data-resting:items-end data-resting:pb-4 data-resting:opacity-100"
|
className="absolute inset-0 flex cursor-pointer items-center justify-center opacity-0 transition-opacity group-hover:bg-overlay/40 group-hover:opacity-100 focus-visible:bg-overlay/40 focus-visible:opacity-100 data-resting:items-end data-resting:pb-3 data-resting:opacity-100"
|
||||||
aria-label={t`Open`}
|
aria-label={t`Open`}
|
||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -108,14 +108,20 @@ export function NewSpaceDialog({
|
|||||||
<Dialog open onOpenChange={closeUnlessBusy(saving, onCancel)}>
|
<Dialog open onOpenChange={closeUnlessBusy(saving, onCancel)}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
showCloseButton={false}
|
showCloseButton={false}
|
||||||
className={isProject ? "max-h-[90vh] overflow-y-auto max-w-xl" : undefined}
|
className={
|
||||||
|
isProject
|
||||||
|
? "flex max-h-[88dvh] w-[calc(100vw-2rem)] max-w-xl flex-col gap-0 overflow-hidden p-0 sm:max-w-xl"
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader
|
||||||
<DialogTitle className="flex items-center gap-2.5">
|
className={isProject ? "shrink-0 border-b border-border px-5 py-4" : undefined}
|
||||||
|
>
|
||||||
|
<DialogTitle className="flex min-w-0 items-center gap-2.5">
|
||||||
{isProject ? (
|
{isProject ? (
|
||||||
<>
|
<>
|
||||||
<ProjectIcon icon={icon} color={color} size={30} />
|
<ProjectIcon icon={icon} color={color} size={30} />
|
||||||
<span>
|
<span className="truncate">
|
||||||
<Trans>New project</Trans>
|
<Trans>New project</Trans>
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@ -133,7 +139,13 @@ export function NewSpaceDialog({
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-4 pt-1">
|
<div
|
||||||
|
className={
|
||||||
|
isProject
|
||||||
|
? "rk-scroll min-h-0 min-w-0 flex-1 space-y-4 overflow-y-auto px-5 py-4"
|
||||||
|
: "min-w-0 space-y-4 pt-1"
|
||||||
|
}
|
||||||
|
>
|
||||||
<label htmlFor={nameId} className="block text-ui-body text-foreground/75">
|
<label htmlFor={nameId} className="block text-ui-body text-foreground/75">
|
||||||
<Trans>Name</Trans>
|
<Trans>Name</Trans>
|
||||||
<Input
|
<Input
|
||||||
@ -179,7 +191,7 @@ export function NewSpaceDialog({
|
|||||||
<div
|
<div
|
||||||
role="radiogroup"
|
role="radiogroup"
|
||||||
aria-label={t`Icon`}
|
aria-label={t`Icon`}
|
||||||
className="grid max-h-36 grid-cols-8 gap-1 overflow-y-auto rounded-lg border border-border p-1"
|
className="grid max-h-36 grid-cols-6 gap-1 overflow-y-auto rounded-lg border border-border p-1.5 sm:grid-cols-8"
|
||||||
>
|
>
|
||||||
{PROJECT_ICONS.map((option) => {
|
{PROJECT_ICONS.map((option) => {
|
||||||
const Glyph = PROJECT_ICON_COMPONENTS[option.id];
|
const Glyph = PROJECT_ICON_COMPONENTS[option.id];
|
||||||
@ -304,8 +316,12 @@ export function NewSpaceDialog({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error ? <p className="text-ui-body text-destructive mt-2">{error}</p> : null}
|
{error ? (
|
||||||
<DialogFooter className="mt-4">
|
<p className={`text-ui-body text-destructive ${isProject ? "px-5 pt-2" : "mt-2"}`}>
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
<DialogFooter className={isProject ? "mx-0 mb-0 mt-0 shrink-0 px-5 py-3.5" : "mt-4"}>
|
||||||
<Button variant="outline" disabled={saving} onClick={onCancel}>
|
<Button variant="outline" disabled={saving} onClick={onCancel}>
|
||||||
<Trans>Cancel</Trans>
|
<Trans>Cancel</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -84,11 +84,11 @@ export function FullscreenComputerOverlay({
|
|||||||
)}
|
)}
|
||||||
{!recordingSkill && hasControl ? (
|
{!recordingSkill && hasControl ? (
|
||||||
computer?.takeoverRequested ? (
|
computer?.takeoverRequested ? (
|
||||||
<span className="rounded-full bg-warning/15 px-[11px] py-1 text-ui-body-sm text-warning">
|
<span className="shrink-0 whitespace-nowrap rounded-full bg-warning/15 px-[11px] py-1 text-ui-body-sm text-warning">
|
||||||
<Trans>Needs you</Trans>
|
<Trans>Needs you</Trans>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="rounded-full bg-success/15 px-[11px] py-1 text-ui-body-sm text-success">
|
<span className="shrink-0 whitespace-nowrap rounded-full bg-success/15 px-[11px] py-1 text-ui-body-sm text-success">
|
||||||
<Trans>You have control</Trans>
|
<Trans>You have control</Trans>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -95,16 +95,16 @@ export function ProjectLookDialog({
|
|||||||
<DialogContent
|
<DialogContent
|
||||||
showCloseButton={false}
|
showCloseButton={false}
|
||||||
data-testid="project-look-dialog"
|
data-testid="project-look-dialog"
|
||||||
className="max-h-[90vh] overflow-y-auto max-w-xl"
|
className="flex max-h-[88dvh] w-[calc(100vw-2rem)] max-w-xl flex-col gap-0 overflow-hidden p-0 sm:max-w-xl"
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader className="shrink-0 border-b border-border px-5 py-4">
|
||||||
<DialogTitle className="flex items-center gap-3">
|
<DialogTitle className="flex min-w-0 items-center gap-3">
|
||||||
<ProjectIcon icon={icon} color={color} size={36} />
|
<ProjectIcon icon={icon} color={color} size={36} />
|
||||||
<span className="min-w-0 truncate">{name.trim() || initial.name}</span>
|
<span className="min-w-0 truncate">{name.trim() || initial.name}</span>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-4 pt-1">
|
<div className="rk-scroll min-h-0 min-w-0 flex-1 space-y-4 overflow-y-auto px-5 py-4">
|
||||||
<label htmlFor={nameId} className="block text-ui-body text-foreground/75">
|
<label htmlFor={nameId} className="block text-ui-body text-foreground/75">
|
||||||
<Trans>Name</Trans>
|
<Trans>Name</Trans>
|
||||||
<Input
|
<Input
|
||||||
@ -147,7 +147,7 @@ export function ProjectLookDialog({
|
|||||||
<div
|
<div
|
||||||
role="radiogroup"
|
role="radiogroup"
|
||||||
aria-label={t`Icon`}
|
aria-label={t`Icon`}
|
||||||
className="grid max-h-40 grid-cols-8 gap-1 overflow-y-auto rounded-lg border border-border p-1"
|
className="grid max-h-40 grid-cols-6 gap-1 overflow-y-auto rounded-lg border border-border p-1.5 sm:grid-cols-8"
|
||||||
>
|
>
|
||||||
{PROJECT_ICONS.map((option) => {
|
{PROJECT_ICONS.map((option) => {
|
||||||
const Glyph = PROJECT_ICON_COMPONENTS[option.id];
|
const Glyph = PROJECT_ICON_COMPONENTS[option.id];
|
||||||
@ -243,8 +243,8 @@ export function ProjectLookDialog({
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error ? <p className="text-ui-body text-destructive mt-2">{error}</p> : null}
|
{error ? <p className="px-5 pt-2 text-ui-body text-destructive">{error}</p> : null}
|
||||||
<DialogFooter className="mt-4">
|
<DialogFooter className="mx-0 mb-0 mt-0 shrink-0 px-5 py-3.5">
|
||||||
<Button variant="outline" disabled={saving} onClick={onCancel}>
|
<Button variant="outline" disabled={saving} onClick={onCancel}>
|
||||||
<Trans>Cancel</Trans>
|
<Trans>Cancel</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -77,6 +77,7 @@ export function SidebarHeader({
|
|||||||
{createMenuOpen ? (
|
{createMenuOpen ? (
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
align="end"
|
align="end"
|
||||||
|
collisionPadding={12}
|
||||||
className="app-no-drag w-auto gap-0 overflow-hidden p-0 data-closed:animate-none"
|
className="app-no-drag w-auto gap-0 overflow-hidden p-0 data-closed:animate-none"
|
||||||
>
|
>
|
||||||
<BotCreatePicker
|
<BotCreatePicker
|
||||||
@ -132,7 +133,7 @@ export function SidebarHeader({
|
|||||||
}
|
}
|
||||||
onCollapseSidebar();
|
onCollapseSidebar();
|
||||||
}}
|
}}
|
||||||
className="app-no-drag rk-touch-target flex size-control-sm items-center justify-center rounded-full rk-sidebar-muted hover:text-foreground/75"
|
className="app-no-drag rk-touch-target flex size-control-sm items-center justify-center rounded-full rk-sidebar-muted hover:text-foreground/75 md:hidden"
|
||||||
>
|
>
|
||||||
<PanelLeftClose strokeWidth={1.8} aria-hidden="true" className="size-icon-md" />
|
<PanelLeftClose strokeWidth={1.8} aria-hidden="true" className="size-icon-md" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -935,21 +935,9 @@ const MessageView = memo(function MessageView({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<span className="text-ui-label font-mono text-muted-foreground tabular-nums">
|
|
||||||
{new Date(message.createdAt).toLocaleTimeString([], {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
) : message.role === "user" && !encounterMode ? (
|
) : message.role === "user" && !encounterMode ? (
|
||||||
<div className="mb-1.5 flex items-center justify-end gap-2">
|
<div className="mb-1.5 flex items-center justify-end gap-2">
|
||||||
<span className="text-ui-label font-mono text-muted-foreground tabular-nums">
|
|
||||||
{new Date(message.createdAt).toLocaleTimeString([], {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
<span className="text-ui-body-sm font-semibold text-foreground tracking-tight">
|
<span className="text-ui-body-sm font-semibold text-foreground tracking-tight">
|
||||||
{t`You`}
|
{t`You`}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import path from "node:path";
|
|||||||
import type Docker from "dockerode";
|
import type Docker from "dockerode";
|
||||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
|
assertProjectWorkspaceBindPath,
|
||||||
assertVolumeSubpathSupport,
|
assertVolumeSubpathSupport,
|
||||||
COMPUTER_IMAGE,
|
COMPUTER_IMAGE,
|
||||||
computerHomeStorage,
|
computerHomeStorage,
|
||||||
@ -873,4 +874,12 @@ describe("computer home storage", () => {
|
|||||||
// Steady state waits on Xvfb instead of polling, so the trap runs immediately.
|
// Steady state waits on Xvfb instead of polling, so the trap runs immediately.
|
||||||
expect(start).toMatch(/^wait "\$XVFB_PID"$/m);
|
expect(start).toMatch(/^wait "\$XVFB_PID"$/m);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("skips local statSync when the supervisor runs inside a container", () => {
|
||||||
|
const missingHostPath = "/srv/host-only/workspace-not-in-container";
|
||||||
|
expect(() => assertProjectWorkspaceBindPath(missingHostPath, false)).toThrow(
|
||||||
|
/does not exist on the host/,
|
||||||
|
);
|
||||||
|
expect(assertProjectWorkspaceBindPath(missingHostPath, true)).toBe(missingHostPath);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import { statSync } from "node:fs";
|
import { existsSync, statSync } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { MAX_DESKTOP_DISPLAY, screenPorts } from "@rakazo/core/node/desktop-runtime";
|
import { MAX_DESKTOP_DISPLAY, screenPorts } from "@rakazo/core/node/desktop-runtime";
|
||||||
import type Docker from "dockerode";
|
import type Docker from "dockerode";
|
||||||
@ -217,9 +217,17 @@ export function homeVolumeMatches(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Validates a host folder before bind-mounting it into a project computer. */
|
/**
|
||||||
export function assertProjectWorkspaceBindPath(hostPath: string): string {
|
* Validates a host folder before bind-mounting it into a project computer.
|
||||||
|
* When the supervisor itself runs inside a container, host workspace folders are
|
||||||
|
* validated by the API on the host and by the Docker daemon when mounting.
|
||||||
|
*/
|
||||||
|
export function assertProjectWorkspaceBindPath(
|
||||||
|
hostPath: string,
|
||||||
|
inContainer = existsSync("/.dockerenv"),
|
||||||
|
): string {
|
||||||
const resolved = path.resolve(hostPath);
|
const resolved = path.resolve(hostPath);
|
||||||
|
if (inContainer) return resolved;
|
||||||
let stat: ReturnType<typeof statSync>;
|
let stat: ReturnType<typeof statSync>;
|
||||||
try {
|
try {
|
||||||
stat = statSync(resolved);
|
stat = statSync(resolved);
|
||||||
|
|||||||
@ -322,7 +322,10 @@ app.post("/computers", async (c) => {
|
|||||||
: hostGid;
|
: hostGid;
|
||||||
await assertComputerHomeWritable(serviceHomePath, effectiveUid, effectiveGid);
|
await assertComputerHomeWritable(serviceHomePath, effectiveUid, effectiveGid);
|
||||||
const projectWorkspacePath = body.projectWorkspacePath
|
const projectWorkspacePath = body.projectWorkspacePath
|
||||||
? assertProjectWorkspaceBindPath(body.projectWorkspacePath)
|
? assertProjectWorkspaceBindPath(
|
||||||
|
body.projectWorkspacePath,
|
||||||
|
Boolean(runtimeInfo) || existsSync("/.dockerenv"),
|
||||||
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
const name = containerNameFor(body.botId);
|
const name = containerNameFor(body.botId);
|
||||||
const createdNetwork =
|
const createdNetwork =
|
||||||
|
|||||||
@ -80,6 +80,7 @@ import type { McpServer, Prisma, PrismaClient, ThreadEvents } from "@rakazo/db";
|
|||||||
import {
|
import {
|
||||||
createSpaceForMember,
|
createSpaceForMember,
|
||||||
effectiveMemoryScope,
|
effectiveMemoryScope,
|
||||||
|
ensureComputerRecord,
|
||||||
ensureMainConversation,
|
ensureMainConversation,
|
||||||
findDefaultModelCredential,
|
findDefaultModelCredential,
|
||||||
findModelCredential,
|
findModelCredential,
|
||||||
@ -1039,7 +1040,20 @@ export function createRunExecutor(deps: ExecutorDeps) {
|
|||||||
where: { id: run.botId },
|
where: { id: run.botId },
|
||||||
select: { computerId: true, computerSwitching: true },
|
select: { computerId: true, computerSwitching: true },
|
||||||
});
|
});
|
||||||
if (!leaseTarget.computerId) throw new Error("Bot has no computer");
|
let computerId = leaseTarget.computerId;
|
||||||
|
if (!computerId) {
|
||||||
|
const teamComputer = await ensureComputerRecord(deps.prisma, {
|
||||||
|
mode: "team",
|
||||||
|
spaceId: run.spaceId,
|
||||||
|
userId: run.userId,
|
||||||
|
kind: deps.sandbox.describe().id,
|
||||||
|
});
|
||||||
|
await deps.prisma.bot.update({
|
||||||
|
where: { id: run.botId },
|
||||||
|
data: { computerId: teamComputer.id },
|
||||||
|
});
|
||||||
|
computerId = teamComputer.id;
|
||||||
|
}
|
||||||
if (leaseTarget.computerSwitching) {
|
if (leaseTarget.computerSwitching) {
|
||||||
await requeueComputerRun(deps, runId, workerId, fence, resumeCheckpoint, heldForTakeover);
|
await requeueComputerRun(deps, runId, workerId, fence, resumeCheckpoint, heldForTakeover);
|
||||||
return;
|
return;
|
||||||
@ -1047,7 +1061,7 @@ export function createRunExecutor(deps: ExecutorDeps) {
|
|||||||
let computerLease: ComputerExecutionLease | null = null;
|
let computerLease: ComputerExecutionLease | null = null;
|
||||||
try {
|
try {
|
||||||
computerLease = await acquireComputerExecutionLease(deps.prisma, {
|
computerLease = await acquireComputerExecutionLease(deps.prisma, {
|
||||||
computerId: leaseTarget.computerId,
|
computerId,
|
||||||
runId,
|
runId,
|
||||||
botId: run.botId,
|
botId: run.botId,
|
||||||
resumeHeldLease,
|
resumeHeldLease,
|
||||||
|
|||||||
@ -798,11 +798,14 @@ function toAgentTool(tool: ConnectorTool, host: ToolHost, exposedName: string):
|
|||||||
|
|
||||||
let steeringPoll: NodeJS.Timeout | undefined;
|
let steeringPoll: NodeJS.Timeout | undefined;
|
||||||
if (host.request.claimSteering) {
|
if (host.request.claimSteering) {
|
||||||
|
let steeringPollInFlight = false;
|
||||||
steeringPoll = setInterval(async () => {
|
steeringPoll = setInterval(async () => {
|
||||||
if (toolController.signal.aborted) {
|
if (toolController.signal.aborted) {
|
||||||
if (steeringPoll) clearInterval(steeringPoll);
|
if (steeringPoll) clearInterval(steeringPoll);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (steeringPollInFlight) return;
|
||||||
|
steeringPollInFlight = true;
|
||||||
try {
|
try {
|
||||||
const steering = await host.request.claimSteering?.([...(host.seenSteeringIds ?? [])]);
|
const steering = await host.request.claimSteering?.([...(host.seenSteeringIds ?? [])]);
|
||||||
if (steering && steering.length > 0) {
|
if (steering && steering.length > 0) {
|
||||||
@ -822,6 +825,8 @@ function toAgentTool(tool: ConnectorTool, host: ToolHost, exposedName: string):
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Background poll failure is non-fatal
|
// Background poll failure is non-fatal
|
||||||
|
} finally {
|
||||||
|
steeringPollInFlight = false;
|
||||||
}
|
}
|
||||||
}, 80);
|
}, 80);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,6 +80,28 @@ describe("runFailureError", () => {
|
|||||||
expect(long).toHaveLength(301);
|
expect(long).toHaveLength(301);
|
||||||
expect(long?.endsWith("…")).toBe(true);
|
expect(long?.endsWith("…")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("strips Prisma source frames and file paths while keeping the database error reason", () => {
|
||||||
|
const prismaError = [
|
||||||
|
"Invalid `tx.event.create()` invocation in",
|
||||||
|
"/workspace/app/packages/db/src/events.ts:1235:19",
|
||||||
|
"",
|
||||||
|
" 1232 await assertRunCanWriteHistory(tx, input.runId);",
|
||||||
|
" 1233 // Unpaired UTF-16 surrogates (e.g. a split emoji high half) are invalid JSON for Postgres.",
|
||||||
|
" 1234 const payload = sanitizeJsonValue(input.payload);",
|
||||||
|
"→ 1235 return tx.event.create(",
|
||||||
|
"Unsupported Unicode escape sequence \\u0000 cannot be converted to text.",
|
||||||
|
].join("\n");
|
||||||
|
expect(runFailureError({ type: "run.failed", payload: { error: prismaError } })).toBe(
|
||||||
|
"Invalid `tx.event.create()` invocation: Unsupported Unicode escape sequence \\u0000 cannot be converted to text.",
|
||||||
|
);
|
||||||
|
|
||||||
|
const preTruncated =
|
||||||
|
"Invalid `tx.event.create()` invocation in /workspace/app/packages/db/src/events.ts:1235:19 1232 await assertRunCanWriteHistory(tx, input.runId); 1233 // Unpaired UTF-16 surrogates (e.g. a split emoji high half) are invalid JSON for Postgres. 1234 const payload = sanitizeJsonV…";
|
||||||
|
expect(runFailureError({ type: "run.failed", payload: { error: preTruncated } })).toBe(
|
||||||
|
"Invalid `tx.event.create()` invocation",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("projectMessages", () => {
|
describe("projectMessages", () => {
|
||||||
@ -732,4 +754,24 @@ describe("sanitizeUtf16ForJson", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("strips NUL bytes and normalizes Date and non-finite numbers for Postgres jsonb", () => {
|
||||||
|
const timestamp = new Date("2026-10-18T21:00:00.000Z");
|
||||||
|
expect(sanitizeUtf16ForJson("hello\u0000world")).toBe("helloworld");
|
||||||
|
expect(
|
||||||
|
sanitizeJsonValue({
|
||||||
|
delta: "line1\u0000line2",
|
||||||
|
scheduledFor: timestamp,
|
||||||
|
invalidDate: new Date(Number.NaN),
|
||||||
|
nanValue: Number.NaN,
|
||||||
|
infValue: Number.POSITIVE_INFINITY,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
delta: "line1line2",
|
||||||
|
scheduledFor: "2026-10-18T21:00:00.000Z",
|
||||||
|
invalidDate: null,
|
||||||
|
nanValue: null,
|
||||||
|
infValue: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -171,6 +171,37 @@ export function isRunTerminalEvent(event: { type: string }): boolean {
|
|||||||
|
|
||||||
const RUN_FAILURE_ERROR_MAX = 300;
|
const RUN_FAILURE_ERROR_MAX = 300;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip file paths and numbered source-code frames from Prisma invocation errors so
|
||||||
|
* user-visible run failure messages keep the actual database error reason at the end
|
||||||
|
* without leaking server filesystem paths.
|
||||||
|
*/
|
||||||
|
export function stripPrismaInvocationFrame(message: string): string {
|
||||||
|
const match = /^Invalid `([^`]+)` invocation\b/.exec(message);
|
||||||
|
if (!match) return message;
|
||||||
|
const fallback = `Invalid \`${match[1]}\` invocation`;
|
||||||
|
const lines = message
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
if (lines.length > 1) {
|
||||||
|
const arrowIndex = lines.findIndex((line) => /^→\s*\d+\b/.test(line));
|
||||||
|
const tail = arrowIndex >= 0 ? lines.slice(arrowIndex + 1) : lines.slice(1);
|
||||||
|
const reason = [...tail]
|
||||||
|
.reverse()
|
||||||
|
.find(
|
||||||
|
(line) =>
|
||||||
|
!/^(?:→\s*)?\d+\b/.test(line) &&
|
||||||
|
!/^(?:\/|[A-Za-z]:\\).+:\d+:\d+$/.test(line) &&
|
||||||
|
!/^[{}()[\],.:;]+$/.test(line),
|
||||||
|
);
|
||||||
|
return reason ? `${fallback}: ${reason}` : fallback;
|
||||||
|
}
|
||||||
|
const inlineReason = /^Invalid `[^`]+` invocation:\s*(.+)$/.exec(lines[0] ?? "");
|
||||||
|
if (inlineReason?.[1]) return `${fallback}: ${inlineReason[1]}`;
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
/** Reason a run failed, clamped for display, or null when there is no usable error to show. */
|
/** Reason a run failed, clamped for display, or null when there is no usable error to show. */
|
||||||
export function runFailureError(event: {
|
export function runFailureError(event: {
|
||||||
type: string;
|
type: string;
|
||||||
@ -179,7 +210,7 @@ export function runFailureError(event: {
|
|||||||
if (event.type !== "run.failed") return null;
|
if (event.type !== "run.failed") return null;
|
||||||
const error = event.payload?.error;
|
const error = event.payload?.error;
|
||||||
if (typeof error !== "string" || !error.trim()) return null;
|
if (typeof error !== "string" || !error.trim()) return null;
|
||||||
const message = error.trim();
|
const message = stripPrismaInvocationFrame(error.trim());
|
||||||
// A provider can fail with a stack or a whole response body; the run record keeps the full
|
// A provider can fail with a stack or a whole response body; the run record keeps the full
|
||||||
// text while the UI shows a bounded first line.
|
// text while the UI shows a bounded first line.
|
||||||
return message.length > RUN_FAILURE_ERROR_MAX
|
return message.length > RUN_FAILURE_ERROR_MAX
|
||||||
@ -384,12 +415,16 @@ function endsWithHighSurrogate(value: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace unpaired UTF-16 surrogates so the string is safe for JSON (Postgres rejects them).
|
* Replace unpaired UTF-16 surrogates and strip NUL bytes so the string is safe for JSON
|
||||||
|
* (Postgres jsonb and text columns reject both).
|
||||||
*/
|
*/
|
||||||
export function sanitizeUtf16ForJson(value: string): string {
|
export function sanitizeUtf16ForJson(value: string): string {
|
||||||
let result = "";
|
let result = "";
|
||||||
for (let i = 0; i < value.length; i++) {
|
for (let i = 0; i < value.length; i++) {
|
||||||
const code = value.charCodeAt(i);
|
const code = value.charCodeAt(i);
|
||||||
|
if (code === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (isHighSurrogate(code)) {
|
if (isHighSurrogate(code)) {
|
||||||
const next = i + 1 < value.length ? value.charCodeAt(i + 1) : -1;
|
const next = i + 1 < value.length ? value.charCodeAt(i + 1) : -1;
|
||||||
if (isLowSurrogate(next)) {
|
if (isLowSurrogate(next)) {
|
||||||
@ -407,9 +442,13 @@ export function sanitizeUtf16ForJson(value: string): string {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Deep-sanitize string leaves so event payloads never carry unpaired surrogates into JSON. */
|
/** Deep-sanitize JSON values so payloads never carry NUL bytes, unpaired surrogates, or raw Dates into Postgres jsonb. */
|
||||||
export function sanitizeJsonValue<T>(value: T): T {
|
export function sanitizeJsonValue<T>(value: T): T {
|
||||||
if (typeof value === "string") return sanitizeUtf16ForJson(value) as T;
|
if (typeof value === "string") return sanitizeUtf16ForJson(value) as T;
|
||||||
|
if (typeof value === "number") return (Number.isFinite(value) ? value : null) as T;
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return (Number.isFinite(value.getTime()) ? value.toISOString() : null) as T;
|
||||||
|
}
|
||||||
if (Array.isArray(value)) return value.map((item) => sanitizeJsonValue(item)) as T;
|
if (Array.isArray(value)) return value.map((item) => sanitizeJsonValue(item)) as T;
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
const out: Record<string, unknown> = {};
|
const out: Record<string, unknown> = {};
|
||||||
|
|||||||
@ -2092,4 +2092,49 @@ describe("appendEvent", () => {
|
|||||||
expect(persisted.delta).not.toMatch(/[\uD800-\uDFFF]/);
|
expect(persisted.delta).not.toMatch(/[\uD800-\uDFFF]/);
|
||||||
expect(() => JSON.stringify(persisted)).not.toThrow();
|
expect(() => JSON.stringify(persisted)).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("strips NUL bytes and retries transient serialization conflicts when appending events", async () => {
|
||||||
|
const conflict = Object.assign(new Error("serialization conflict"), { code: "P2034" });
|
||||||
|
const fanout = new TestFanout();
|
||||||
|
const created = {
|
||||||
|
...event(4),
|
||||||
|
type: "thread.progress",
|
||||||
|
runId: "run-4",
|
||||||
|
payload: { delta: "clean text", streaming: true },
|
||||||
|
};
|
||||||
|
const tx = {
|
||||||
|
thread: { update: vi.fn().mockResolvedValue({ nextEventSeq: 5 }) },
|
||||||
|
run: { findUnique: vi.fn().mockResolvedValue({ status: "running" }) },
|
||||||
|
event: { create: vi.fn().mockResolvedValue(created) },
|
||||||
|
};
|
||||||
|
const transaction = vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValueOnce(conflict)
|
||||||
|
.mockImplementation(async (callback: (client: typeof tx) => unknown) => callback(tx));
|
||||||
|
const prisma = {
|
||||||
|
$transaction: transaction,
|
||||||
|
} as unknown as PrismaClient;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
appendEvent(
|
||||||
|
prisma,
|
||||||
|
{
|
||||||
|
spaceId: "workspace-1",
|
||||||
|
threadId: "thread-1",
|
||||||
|
botId: "bot-1",
|
||||||
|
type: "thread.progress",
|
||||||
|
runId: "run-4",
|
||||||
|
payload: { delta: "clean\u0000 text", streaming: true },
|
||||||
|
},
|
||||||
|
fanout,
|
||||||
|
),
|
||||||
|
).resolves.toMatchObject({ type: "thread.progress", runId: "run-4" });
|
||||||
|
|
||||||
|
expect(transaction).toHaveBeenCalledTimes(2);
|
||||||
|
expect(tx.event.create).toHaveBeenCalledWith({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
payload: { delta: "clean text", streaming: true },
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -476,99 +476,101 @@ export async function claimSteering(
|
|||||||
input: ClaimSteeringInput,
|
input: ClaimSteeringInput,
|
||||||
realtime?: RealtimeFanout,
|
realtime?: RealtimeFanout,
|
||||||
): Promise<ClaimedSteeringMessage[]> {
|
): Promise<ClaimedSteeringMessage[]> {
|
||||||
const result = await prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
const result = await withTransactionRetry(() =>
|
||||||
await tx.$queryRaw`SELECT id FROM threads WHERE id = ${input.threadId} FOR UPDATE`;
|
prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||||
const run = await tx.run.findFirst({
|
await tx.$queryRaw`SELECT id FROM threads WHERE id = ${input.threadId} FOR UPDATE`;
|
||||||
where: {
|
const run = await tx.run.findFirst({
|
||||||
id: input.runId,
|
where: {
|
||||||
threadId: input.threadId,
|
id: input.runId,
|
||||||
botId: input.botId,
|
|
||||||
status: "running",
|
|
||||||
leaseOwner: input.leaseOwner,
|
|
||||||
leaseFence: input.leaseFence,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
spaceId: true,
|
|
||||||
trigger: true,
|
|
||||||
sourceMessage: { select: { blocks: true } },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!run) return null;
|
|
||||||
const channelId =
|
|
||||||
run.trigger === "messaging"
|
|
||||||
? messagingChannelId(run.sourceMessage?.blocks as MessageBlock[] | undefined)
|
|
||||||
: undefined;
|
|
||||||
const steering = await tx.steeringMessage.findMany({
|
|
||||||
where: {
|
|
||||||
botId: input.botId,
|
|
||||||
claimedAt: null,
|
|
||||||
id: input.seenIds.length ? { notIn: input.seenIds } : undefined,
|
|
||||||
OR: [{ runId: null }, { runId: input.runId }],
|
|
||||||
message: {
|
|
||||||
threadId: input.threadId,
|
threadId: input.threadId,
|
||||||
// Private follow-ups remain unclaimed for the existing private continuation.
|
botId: input.botId,
|
||||||
...(channelId
|
status: "running",
|
||||||
? { blocks: { array_contains: [{ kind: "channel_message", channelId }] } }
|
leaseOwner: input.leaseOwner,
|
||||||
: {}),
|
leaseFence: input.leaseFence,
|
||||||
},
|
},
|
||||||
},
|
select: {
|
||||||
include: { message: { select: { blocks: true, seq: true } } },
|
id: true,
|
||||||
orderBy: [{ message: { seq: "asc" } }, { id: "asc" }],
|
spaceId: true,
|
||||||
});
|
trigger: true,
|
||||||
if (steering.length === 0) return null;
|
sourceMessage: { select: { blocks: true } },
|
||||||
const claimed = await tx.steeringMessage.updateMany({
|
},
|
||||||
where: { id: { in: steering.map((item) => item.id) }, claimedAt: null },
|
});
|
||||||
data: { runId: input.runId, claimedAt: new Date() },
|
if (!run) return null;
|
||||||
});
|
const channelId =
|
||||||
if (claimed.count === 0) return null;
|
run.trigger === "messaging"
|
||||||
|
? messagingChannelId(run.sourceMessage?.blocks as MessageBlock[] | undefined)
|
||||||
|
: undefined;
|
||||||
|
const steering = await tx.steeringMessage.findMany({
|
||||||
|
where: {
|
||||||
|
botId: input.botId,
|
||||||
|
claimedAt: null,
|
||||||
|
id: input.seenIds.length ? { notIn: input.seenIds } : undefined,
|
||||||
|
OR: [{ runId: null }, { runId: input.runId }],
|
||||||
|
message: {
|
||||||
|
threadId: input.threadId,
|
||||||
|
// Private follow-ups remain unclaimed for the existing private continuation.
|
||||||
|
...(channelId
|
||||||
|
? { blocks: { array_contains: [{ kind: "channel_message", channelId }] } }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: { message: { select: { blocks: true, seq: true } } },
|
||||||
|
orderBy: [{ message: { seq: "asc" } }, { id: "asc" }],
|
||||||
|
});
|
||||||
|
if (steering.length === 0) return null;
|
||||||
|
const claimed = await tx.steeringMessage.updateMany({
|
||||||
|
where: { id: { in: steering.map((item) => item.id) }, claimedAt: null },
|
||||||
|
data: { runId: input.runId, claimedAt: new Date() },
|
||||||
|
});
|
||||||
|
if (claimed.count === 0) return null;
|
||||||
|
|
||||||
const notifiedSeqs: number[] = [];
|
const notifiedSeqs: number[] = [];
|
||||||
if ((tx as any).message?.findUnique && (tx as any).message?.create && tx.thread && tx.event) {
|
if ((tx as any).message?.findUnique && (tx as any).message?.create && tx.thread && tx.event) {
|
||||||
for (const item of steering) {
|
for (const item of steering) {
|
||||||
const clientNonce = `bot-ack:${input.runId}:${item.id}`;
|
const clientNonce = `bot-ack:${input.runId}:${item.id}`;
|
||||||
const existing = await tx.message.findUnique({
|
const existing = await tx.message.findUnique({
|
||||||
where: { threadId_clientNonce: { threadId: input.threadId, clientNonce } },
|
where: { threadId_clientNonce: { threadId: input.threadId, clientNonce } },
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
|
||||||
if (!existing) {
|
|
||||||
const blocks: MessageBlock[] = [{ kind: "text", text: "👀" }];
|
|
||||||
const message = await createThreadMessageInTransaction(tx, {
|
|
||||||
threadId: input.threadId,
|
|
||||||
role: "bot",
|
|
||||||
botId: input.botId,
|
|
||||||
blocks,
|
|
||||||
replyToMessageId: item.messageId,
|
|
||||||
clientNonce,
|
|
||||||
markUnread: false,
|
|
||||||
});
|
});
|
||||||
const event = await appendEventInTransaction(tx, {
|
if (!existing) {
|
||||||
spaceId: run.spaceId ?? "default",
|
const blocks: MessageBlock[] = [{ kind: "text", text: "👀" }];
|
||||||
threadId: input.threadId,
|
const message = await createThreadMessageInTransaction(tx, {
|
||||||
botId: input.botId,
|
threadId: input.threadId,
|
||||||
type: "thread.message.created",
|
|
||||||
payload: {
|
|
||||||
messageId: message.id,
|
|
||||||
role: "bot",
|
role: "bot",
|
||||||
|
botId: input.botId,
|
||||||
blocks,
|
blocks,
|
||||||
replyToMessageId: item.messageId,
|
replyToMessageId: item.messageId,
|
||||||
},
|
clientNonce,
|
||||||
});
|
markUnread: false,
|
||||||
notifiedSeqs.push(event.seq);
|
});
|
||||||
|
const event = await appendEventInTransaction(tx, {
|
||||||
|
spaceId: run.spaceId ?? "default",
|
||||||
|
threadId: input.threadId,
|
||||||
|
botId: input.botId,
|
||||||
|
type: "thread.message.created",
|
||||||
|
payload: {
|
||||||
|
messageId: message.id,
|
||||||
|
role: "bot",
|
||||||
|
blocks,
|
||||||
|
replyToMessageId: item.messageId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
notifiedSeqs.push(event.seq);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claimedMessages: steering.map((item) => ({
|
claimedMessages: steering.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
messageId: item.messageId,
|
messageId: item.messageId,
|
||||||
text: blocksToAgentHistoryText(item.message.blocks as MessageBlock[]),
|
text: blocksToAgentHistoryText(item.message.blocks as MessageBlock[]),
|
||||||
blocks: item.message.blocks as MessageBlock[],
|
blocks: item.message.blocks as MessageBlock[],
|
||||||
})),
|
})),
|
||||||
notifiedSeqs,
|
notifiedSeqs,
|
||||||
};
|
};
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
if (!result) return [];
|
if (!result) return [];
|
||||||
for (const seq of result.notifiedSeqs) {
|
for (const seq of result.notifiedSeqs) {
|
||||||
@ -1008,8 +1010,8 @@ export async function appendEvent(
|
|||||||
input: AppendEventInput,
|
input: AppendEventInput,
|
||||||
realtime?: RealtimeFanout,
|
realtime?: RealtimeFanout,
|
||||||
): Promise<ProductEvent> {
|
): Promise<ProductEvent> {
|
||||||
const event = await prisma.$transaction((tx: Prisma.TransactionClient) =>
|
const event = await withTransactionRetry(() =>
|
||||||
appendEventInTransaction(tx, input),
|
prisma.$transaction((tx: Prisma.TransactionClient) => appendEventInTransaction(tx, input)),
|
||||||
);
|
);
|
||||||
const productEvent = mapProductEvent(event);
|
const productEvent = mapProductEvent(event);
|
||||||
await notifyRealtime(realtime, event.threadId, event.seq);
|
await notifyRealtime(realtime, event.threadId, event.seq);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { MessageBlock } from "@rakazo/contracts";
|
import type { MessageBlock } from "@rakazo/contracts";
|
||||||
|
import { sanitizeJsonValue, sanitizeUtf16ForJson } from "@rakazo/core";
|
||||||
import type { Prisma, PrismaClient } from "./client.js";
|
import type { Prisma, PrismaClient } from "./client.js";
|
||||||
|
|
||||||
/** Group turns use channel inputs and their own outputs, never private thread history. */
|
/** Group turns use channel inputs and their own outputs, never private thread history. */
|
||||||
@ -70,15 +71,16 @@ export async function createThreadMessageInTransaction(
|
|||||||
select: { nextMessageSeq: true },
|
select: { nextMessageSeq: true },
|
||||||
});
|
});
|
||||||
await assertRunCanWriteHistory(tx, input.runId);
|
await assertRunCanWriteHistory(tx, input.runId);
|
||||||
|
const blocks = sanitizeJsonValue(input.blocks);
|
||||||
return tx.message.create({
|
return tx.message.create({
|
||||||
data: {
|
data: {
|
||||||
threadId: input.threadId,
|
threadId: input.threadId,
|
||||||
seq: thread.nextMessageSeq - 1,
|
seq: thread.nextMessageSeq - 1,
|
||||||
role: input.role,
|
role: input.role,
|
||||||
blocks: input.blocks as Prisma.InputJsonValue,
|
blocks: blocks as Prisma.InputJsonValue,
|
||||||
botId: input.botId,
|
botId: input.botId,
|
||||||
replyToMessageId: input.replyToMessageId,
|
replyToMessageId: input.replyToMessageId,
|
||||||
replyQuote: input.replyQuote,
|
replyQuote: input.replyQuote ? sanitizeUtf16ForJson(input.replyQuote) : undefined,
|
||||||
runId: input.runId,
|
runId: input.runId,
|
||||||
clientNonce: input.clientNonce,
|
clientNonce: input.clientNonce,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync } from "node:fs";
|
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync } from "node:fs";
|
||||||
import { tmpdir } from "node:os";
|
import { homedir, tmpdir } from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import type { PrismaClient } from "./client.js";
|
import type { PrismaClient } from "./client.js";
|
||||||
@ -36,9 +36,13 @@ describe("project workspace wiring", () => {
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
it("normalizes absolute workspace paths", () => {
|
it("normalizes absolute workspace paths and expands leading ~", () => {
|
||||||
const root = tempDir("project-workspace-normalize-");
|
const root = tempDir("project-workspace-normalize-");
|
||||||
expect(normalizeProjectWorkspacePath(root)).toBe(path.resolve(root));
|
expect(normalizeProjectWorkspacePath(root)).toBe(path.resolve(root));
|
||||||
|
expect(normalizeProjectWorkspacePath("~")).toBe(path.resolve(homedir()));
|
||||||
|
expect(normalizeProjectWorkspacePath("~/Projects/demo")).toBe(
|
||||||
|
path.resolve(homedir(), "Projects/demo"),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects missing folders", async () => {
|
it("rejects missing folders", async () => {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { realpath, stat } from "node:fs/promises";
|
import { realpath, stat } from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { readGitCheckout } from "@rakazo/core/git-checkout";
|
import { readGitCheckout } from "@rakazo/core/git-checkout";
|
||||||
import type { PrismaClient } from "./client.js";
|
import type { PrismaClient } from "./client.js";
|
||||||
@ -19,7 +20,13 @@ export function normalizeProjectWorkspacePath(input: string): string {
|
|||||||
if (trimmed.includes("\0")) {
|
if (trimmed.includes("\0")) {
|
||||||
throw new InvalidProjectWorkspacePathError("That folder path is not valid.");
|
throw new InvalidProjectWorkspacePathError("That folder path is not valid.");
|
||||||
}
|
}
|
||||||
return path.resolve(trimmed);
|
const expanded =
|
||||||
|
trimmed === "~"
|
||||||
|
? os.homedir()
|
||||||
|
: trimmed.startsWith("~/") || trimmed.startsWith("~\\")
|
||||||
|
? path.join(os.homedir(), trimmed.slice(2))
|
||||||
|
: trimmed;
|
||||||
|
return path.resolve(expanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The real folder behind a path, so a symlink cannot later point the link somewhere else. */
|
/** The real folder behind a path, so a symlink cannot later point the link somewhere else. */
|
||||||
|
|||||||
@ -16,16 +16,21 @@ function PopoverContent({
|
|||||||
className,
|
className,
|
||||||
align = "center",
|
align = "center",
|
||||||
alignOffset = 0,
|
alignOffset = 0,
|
||||||
|
collisionPadding = 12,
|
||||||
side = "bottom",
|
side = "bottom",
|
||||||
sideOffset = 4,
|
sideOffset = 4,
|
||||||
...props
|
...props
|
||||||
}: PopoverPrimitive.Popup.Props &
|
}: PopoverPrimitive.Popup.Props &
|
||||||
Pick<PopoverPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">) {
|
Pick<
|
||||||
|
PopoverPrimitive.Positioner.Props,
|
||||||
|
"align" | "alignOffset" | "collisionPadding" | "side" | "sideOffset"
|
||||||
|
>) {
|
||||||
return (
|
return (
|
||||||
<PopoverPrimitive.Portal>
|
<PopoverPrimitive.Portal>
|
||||||
<PopoverPrimitive.Positioner
|
<PopoverPrimitive.Positioner
|
||||||
align={align}
|
align={align}
|
||||||
alignOffset={alignOffset}
|
alignOffset={alignOffset}
|
||||||
|
collisionPadding={collisionPadding}
|
||||||
side={side}
|
side={side}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className="isolate z-50"
|
className="isolate z-50"
|
||||||
@ -33,7 +38,7 @@ function PopoverContent({
|
|||||||
<PopoverPrimitive.Popup
|
<PopoverPrimitive.Popup
|
||||||
data-slot="popover-content"
|
data-slot="popover-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
"z-50 flex w-72 max-w-[calc(100vw-1.5rem)] origin-(--transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -314,7 +314,7 @@ export const EvooMascot = memo(function EvooMascot({
|
|||||||
viewBox="0 0 140 120"
|
viewBox="0 0 140 120"
|
||||||
width={size}
|
width={size}
|
||||||
height={size * 0.86}
|
height={size * 0.86}
|
||||||
className="overflow-visible"
|
className="overflow-visible drop-shadow-[0_1px_2px_rgba(15,23,42,0.18)] dark:drop-shadow-none"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user