From 54658ee689b99dc4453f456f0e29a6c5afb42835 Mon Sep 17 00:00:00 2001 From: cynarAI <155274210+cynarAI@users.noreply.github.com> Date: Mon, 5 Oct 2026 04:45:49 +0200 Subject: [PATCH] feat: autopilot progress, composer start and morning summary (#146) The autopilot's start line counts its steps and folds open to the latest checkpoints, and its result reads as a card. The composer's plus menu starts an autopilot with the draft as the order and a choice of until done, tomorrow morning or a time. Today lists the autopilots that run, wait or ended within a day, and their step runs no longer show one by one. Autopilot runs send no completion or failure push; only a pause notifies. Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1 Co-authored-by: Claude --- VISION.md | 2 + apps/api/src/autopilot.ts | 29 ++++- apps/api/src/router.ts | 4 + apps/api/src/runs.test.ts | 10 ++ apps/api/src/runs.ts | 4 + apps/mobile/app/thread.tsx | 33 ++++++ apps/mobile/components/autopilot-line.tsx | 66 ++++++++++- apps/mobile/lib/locales/ru.ts | 6 + apps/mobile/lib/locales/zh.ts | 6 + apps/web/e2e/autopilot.spec.ts | 48 ++++++++ apps/web/e2e/bot-organization.spec.ts | 2 +- apps/web/scripts/translations-de.json | 10 +- apps/web/src/lib/heute-brief.test.ts | 19 ++++ apps/web/src/lib/heute-brief.ts | 8 +- apps/web/src/locales/de/messages.po | 40 +++++++ apps/web/src/locales/en/messages.po | 40 +++++++ apps/web/src/locales/es/messages.po | 40 +++++++ apps/web/src/locales/hi/messages.po | 40 +++++++ apps/web/src/locales/ko/messages.po | 40 +++++++ apps/web/src/locales/pt-BR/messages.po | 40 +++++++ apps/web/src/locales/ru/messages.po | 40 +++++++ apps/web/src/locales/tr/messages.po | 40 +++++++ apps/web/src/locales/zh-CN/messages.po | 40 +++++++ apps/web/src/pages/HeuteBrief.tsx | 93 ++++++++++++++- apps/web/src/pages/Shell.tsx | 13 +++ apps/web/src/pages/shell/autopilot-dialog.tsx | 106 ++++++++++++++++++ apps/web/src/pages/shell/autopilot-line.tsx | 64 ++++++++++- apps/web/src/pages/shell/composer.tsx | 76 +++++++++++-- .../adapters/src/autopilot.postgres.test.ts | 7 +- packages/adapters/src/autopilot.ts | 53 ++++++--- packages/adapters/src/executor.ts | 47 ++++++-- packages/contracts/src/autopilot.ts | 17 +++ packages/contracts/src/rpc.ts | 4 +- packages/core/src/autopilot.test.ts | 29 +++++ packages/core/src/autopilot.ts | 45 +++++++- packages/db/src/autopilot-lines.ts | 40 ++++++- 36 files changed, 1142 insertions(+), 59 deletions(-) create mode 100644 apps/web/src/pages/shell/autopilot-dialog.tsx diff --git a/VISION.md b/VISION.md index 996e025..f8f188a 100644 --- a/VISION.md +++ b/VISION.md @@ -60,6 +60,8 @@ One bot has one continuous visible thread because continuity is part of its iden Team Computers share a persisted browser identity and isolate simultaneous live desktops when supported because shared login state and safe concurrency are both required. Private Computers isolate the entire working home because some trust boundaries must be stronger than collaboration convenience. Connections belong to the user or space rather than requiring repetitive per-bot setup because bots differ through role and state, not account plumbing. +Bots answer with cards from a fixed catalog of sections rather than generated interface because the backend must validate every card and apply every tap, and a small catalog stays calm and consistent on every surface. +An autopilot works on an order with no time or cost limit, but pauses when runs stop reporting progress and always shows when it started and ended, because long unattended work must stay visible and must never spin silently. ## Scope diff --git a/apps/api/src/autopilot.ts b/apps/api/src/autopilot.ts index eb21f4c..a12097f 100644 --- a/apps/api/src/autopilot.ts +++ b/apps/api/src/autopilot.ts @@ -8,7 +8,8 @@ import { resumeAutopilot, startAutopilot, } from "@rakazo/adapters"; -import type { Actor, Autopilot } from "@rakazo/contracts"; +import type { Actor, Autopilot, AutopilotRecent } from "@rakazo/contracts"; +import { AUTOPILOT_RECENT_MS } from "@rakazo/contracts"; import type { PrismaClient, ThreadEvents } from "@rakazo/db"; import { IsolationError } from "@rakazo/db"; import type { ThreadTarget } from "./thread-target.js"; @@ -77,3 +78,29 @@ export async function resumeThreadAutopilot( } return { ok: true }; } + +/** Today's view: the user's open autopilots in this space and those that ended within a day. */ +export async function listRecentAutopilots( + deps: Pick, + actor: Actor, + now = new Date(), +): Promise { + const rows = await deps.prisma.autopilot.findMany({ + where: { + spaceId: actor.spaceId, + userId: actor.userId, + OR: [ + { status: { in: ["active", "paused"] } }, + { endedAt: { gte: new Date(now.getTime() - AUTOPILOT_RECENT_MS) } }, + ], + }, + include: { thread: { select: { groupId: true } } }, + orderBy: { startedAt: "desc" }, + take: 10, + }); + return rows.map((row) => ({ + ...autopilotView(row), + spaceId: row.spaceId, + groupId: row.thread.groupId, + })); +} diff --git a/apps/api/src/router.ts b/apps/api/src/router.ts index 1c19685..76abe30 100644 --- a/apps/api/src/router.ts +++ b/apps/api/src/router.ts @@ -173,6 +173,7 @@ import { aiConsentStatus, allowAiConsent } from "./ai-consent.js"; import { createOwnedArtifact, getOwnedArtifact, getSpaceArtifact } from "./artifacts.js"; import { getThreadAutopilot, + listRecentAutopilots, resumeThreadAutopilot, startThreadAutopilot, stopThreadAutopilot, @@ -5173,6 +5174,9 @@ export function createRouter(deps: RouterDeps) { const target = await resolveThreadTarget(deps.prisma, context.actor, input); return resumeThreadAutopilot(deps, context.actor, target); }), + recent: authed.autopilot.recent.handler(async ({ context }) => ({ + items: await listRecentAutopilots(deps, context.actor), + })), }, voice: { catalog: authed.voice.catalog.handler(async () => listVoiceCatalog()), diff --git a/apps/api/src/runs.test.ts b/apps/api/src/runs.test.ts index 87479ad..0d4004b 100644 --- a/apps/api/src/runs.test.ts +++ b/apps/api/src/runs.test.ts @@ -20,6 +20,16 @@ describe("run activity copy", () => { ).toBe("Maya asked: Please check the release workflow."); }); + it("names an autopilot step by the user's order", () => { + expect( + activityPromptSnippet({ + trigger: "autopilot", + prompt: + "Autopilot: continue the user's order on your own.\nOrder: Tidy the tickets\nUntil: done", + }), + ).toBe("Tidy the tickets"); + }); + it("names a delegated task by its title instead of its wake prompt", () => { expect( activityPromptSnippet({ diff --git a/apps/api/src/runs.ts b/apps/api/src/runs.ts index 93b5d8f..786eae1 100644 --- a/apps/api/src/runs.ts +++ b/apps/api/src/runs.ts @@ -26,6 +26,10 @@ export function activityPromptSnippet( const task = parsed.success ? taskBlockOf(parsed.data) : undefined; return promptSnippet(task?.title ?? input.prompt, max); } + if (input.trigger === "autopilot") { + // The continuation prompt is for the model; people know the run by its order. + return promptSnippet(/^Order: (.*)$/m.exec(input.prompt)?.[1] ?? input.prompt, max); + } if (input.trigger !== "bot_message") return promptSnippet(input.prompt, max); const parsed = MessageBlock.array().safeParse(input.sourceBlocks); const message = parsed.success ? botMessageContext(parsed.data) : undefined; diff --git a/apps/mobile/app/thread.tsx b/apps/mobile/app/thread.tsx index dad5654..6274252 100644 --- a/apps/mobile/app/thread.tsx +++ b/apps/mobile/app/thread.tsx @@ -9,13 +9,16 @@ import type { Routine, } from "@rakazo/contracts"; import { + AUTOPILOT_LIMITS, canReactToThreadMessage, MESSAGE_REACTIONS, type MessageReaction, } from "@rakazo/contracts"; +import type { AutopilotUntilChoice } from "@rakazo/core"; import { abortableDelay, attachmentsForThread, + autopilotUntil, buildComposerMentionOptions, type ComposerMention, cloudAgentHttpsUrl, @@ -1277,6 +1280,8 @@ function Thread() { ); function showAttachMenu() { + // The autopilot takes the typed order, so it is offered once there is one. + const order = draft.trim(); Alert.alert(t("Attach"), undefined, [ { text: t("Photo library"), @@ -1284,6 +1289,34 @@ function Thread() { }, { text: t("Camera"), onPress: () => void addAttachments(takePhoto) }, { text: t("File"), onPress: () => void addAttachments(pickDocuments) }, + ...(order ? [{ text: t("Autopilot"), onPress: () => askAutopilotUntil(order) }] : []), + { text: t("Cancel"), style: "cancel" as const }, + ]); + } + + function askAutopilotUntil(order: string) { + const start = (choice: AutopilotUntilChoice) => { + const targetBotId = botId; + const targetGroupId = groupId; + if (!targetBotId && !targetGroupId) return; + const target = targetGroupId ? { groupId: targetGroupId } : { botId: targetBotId! }; + const until = autopilotUntil(choice, new Date()); + void rpc("autopilot/start", { + ...target, + goal: order.slice(0, AUTOPILOT_LIMITS.goal), + ...(until ? { until: until.toISOString() } : {}), + }) + .then(async () => { + setDraft(""); + if (isCurrentTarget(targetBotId, targetGroupId)) await refresh(); + }) + .catch((err) => + Alert.alert(t("Autopilot"), err instanceof Error ? err.message : t("Try again.")), + ); + }; + Alert.alert(t("Until when?"), order, [ + { text: t("Until done"), onPress: () => start({ kind: "done" }) }, + { text: t("Until tomorrow morning"), onPress: () => start({ kind: "morning" }) }, { text: t("Cancel"), style: "cancel" }, ]); } diff --git a/apps/mobile/components/autopilot-line.tsx b/apps/mobile/components/autopilot-line.tsx index 9a56d4c..858081b 100644 --- a/apps/mobile/components/autopilot-line.tsx +++ b/apps/mobile/components/autopilot-line.tsx @@ -15,7 +15,10 @@ const ICONS = { "autopilot.finished": { ios: "checkmark.circle", android: "checkmark-circle-outline" }, } as const; -/** One quiet line when the autopilot starts, pauses, stops or finishes. */ +/** + * One quiet line when the autopilot starts, pauses, stops or finishes. The start line counts the + * steps and folds open to the latest checkpoints; a finished autopilot's result reads as a card. + */ export function AutopilotLine({ block, onAction, @@ -27,6 +30,7 @@ export function AutopilotLine({ const { t, locale } = useI18n(); const [busy, setBusy] = useState(false); const [failed, setFailed] = useState(false); + const [expanded, setExpanded] = useState(false); const open = !block.ended && Boolean(onAction); const muted = { color: tokens.mutedForeground, fontSize: 13.5 }; @@ -66,6 +70,12 @@ export function AutopilotLine({ if (block.durationMs !== undefined) parts.push(formatAutopilotDuration(block.durationMs)); } const icon = ICONS[block.event]; + const checkpoints = block.event === "autopilot.started" ? (block.checkpoints ?? []) : []; + const latest = checkpoints.at(-1)?.text; + const time = new Intl.DateTimeFormat(dateLocaleForUi(locale), { + hour: "numeric", + minute: "2-digit", + }); const link = (text: string, action: AutopilotAction) => ( ))} + {block.steps && block.event === "autopilot.started" ? ( + <> + · + setExpanded((value) => !value)} + > + + {block.steps === 1 ? t("1 step") : t("{count} steps", { count: block.steps })} + + + {latest && !expanded ? ( + + · {t("last: {latest}", { latest })} + + ) : null} + + ) : null} {open && block.event === "autopilot.paused" ? ( <> · @@ -115,10 +145,38 @@ export function AutopilotLine({ ) : null} + {expanded && checkpoints.length > 0 ? ( + + {checkpoints.map((checkpoint, index) => ( + + + {time.format(new Date(checkpoint.at))} + + + {checkpoint.text} + + + ))} + + ) : null} {block.event === "autopilot.finished" && block.text ? ( - - {block.text} - + + {block.text} + ) : null} ); diff --git a/apps/mobile/lib/locales/ru.ts b/apps/mobile/lib/locales/ru.ts index 6b28246..ed39e27 100644 --- a/apps/mobile/lib/locales/ru.ts +++ b/apps/mobile/lib/locales/ru.ts @@ -691,6 +691,12 @@ export const RU_MESSAGES: Record = { Expired: "Истекло", Autopilot: "Автопилот", "Autopilot finished": "Автопилот завершён", + "Until when?": "До какого времени?", + "Until done": "До готовности", + "Until tomorrow morning": "До завтрашнего утра", + "1 step": "1 шаг", + "{count} steps": "Шагов: {count}", + "last: {latest}": "последний: {latest}", "Autopilot paused": "Автопилот на паузе", "Autopilot stopped": "Автопилот остановлен", "allowed: {allowed}": "разрешено: {allowed}", diff --git a/apps/mobile/lib/locales/zh.ts b/apps/mobile/lib/locales/zh.ts index cba1ef0..09e38b9 100644 --- a/apps/mobile/lib/locales/zh.ts +++ b/apps/mobile/lib/locales/zh.ts @@ -675,6 +675,12 @@ export const ZH_MESSAGES: Record = { Expired: "已过期", Autopilot: "自动驾驶", "Autopilot finished": "自动驾驶已完成", + "Until when?": "持续到什么时候?", + "Until done": "直到完成", + "Until tomorrow morning": "直到明天早上", + "1 step": "1 步", + "{count} steps": "{count} 步", + "last: {latest}": "最近:{latest}", "Autopilot paused": "自动驾驶已暂停", "Autopilot stopped": "自动驾驶已停止", "allowed: {allowed}": "已允许:{allowed}", diff --git a/apps/web/e2e/autopilot.spec.ts b/apps/web/e2e/autopilot.spec.ts index 8b27eca..7be2d51 100644 --- a/apps/web/e2e/autopilot.spec.ts +++ b/apps/web/e2e/autopilot.spec.ts @@ -60,3 +60,51 @@ test("an autopilot without progress pauses and stops on request", async ({ page await stopped.scrollIntoViewIfNeeded(); await captureScreenshot(page, testInfo, "autopilot-paused-light"); }); + +test("an autopilot started from the composer works overnight and reports in Today", async ({ + page, +}, testInfo) => { + const stamp = Date.now(); + await signup(page, `autopilot-night-${stamp}@rakazo.test`, "password12", "Autopilot Night"); + await completeOnboarding(page); + + await page.getByPlaceholder(/Message/).fill("Tidy the tickets"); + await page.getByRole("button", { name: "Add" }).click(); + await page.getByRole("menuitem", { name: "Autopilot" }).click(); + const dialog = page.getByTestId("autopilot-dialog"); + await expect(dialog.getByRole("textbox", { name: "Order" })).toHaveValue("Tidy the tickets"); + await dialog.getByRole("radio", { name: "Until tomorrow morning" }).click(); + await dialog.getByRole("button", { name: "Start" }).click(); + await expect(dialog).toBeHidden(); + await expect(page.getByPlaceholder(/Message/)).toHaveValue(""); + + const start = page.locator('[data-testid="autopilot-line"][data-event="autopilot.started"]'); + await expect(start).toContainText("Tidy the tickets", { timeout: 60_000 }); + await expect(start).toContainText(/until 8:00/); + const finished = page.locator('[data-testid="autopilot-line"][data-event="autopilot.finished"]'); + await expect(finished).toBeVisible({ timeout: 120_000 }); + await expect(finished.getByTestId("autopilot-result")).toHaveText( + "Tidied 5 tickets; nothing waits on you.", + ); + await start.getByRole("button", { name: "5 steps" }).click(); + await expect(start.getByTestId("autopilot-steps").locator("li")).toHaveCount(5); + await expect(start.getByTestId("autopilot-steps")).toContainText("Ticket 5 tidied"); + + await useAppearance(page, "light"); + await start.scrollIntoViewIfNeeded(); + await captureScreenshot(page, testInfo, "autopilot-night-light"); + await useAppearance(page, "dark"); + await start.scrollIntoViewIfNeeded(); + await captureScreenshot(page, testInfo, "autopilot-night-dark"); + + // The morning summary: what the autopilot did, in Today. + await page.getByRole("button", { name: "Today", exact: true }).click(); + const row = page.getByTestId("heute-autopilot-row"); + await expect(row).toHaveAttribute("data-status", "finished", { timeout: 30_000 }); + await expect(row).toContainText("Tidy the tickets"); + await expect(row).toContainText("Tidied 5 tickets; nothing waits on you."); + await expect(row).toContainText("5 steps"); + await captureScreenshot(page, testInfo, "autopilot-today-dark"); + await row.click(); + await expect(start).toBeVisible(); +}); diff --git a/apps/web/e2e/bot-organization.spec.ts b/apps/web/e2e/bot-organization.spec.ts index 99dbe16..510cb52 100644 --- a/apps/web/e2e/bot-organization.spec.ts +++ b/apps/web/e2e/bot-organization.spec.ts @@ -196,7 +196,7 @@ test("chat composer controls are vertically centered", async ({ page }) => { await completeOnboarding(page); const centers = await page.getByTestId("composer-bar").evaluate((composer) => - ["Attach file", "Message", "Voice", "Send"].map((label) => { + ["Add", "Message", "Voice", "Send"].map((label) => { const element = composer.querySelector(`[aria-label="${label}"]`); if (!element) throw new Error(`Missing composer control: ${label}`); const box = element.getBoundingClientRect(); diff --git a/apps/web/scripts/translations-de.json b/apps/web/scripts/translations-de.json index e6058c0..2eeb5a8 100644 --- a/apps/web/scripts/translations-de.json +++ b/apps/web/scripts/translations-de.json @@ -645,5 +645,13 @@ "Autopilot stopped": "Autopilot gestoppt", "Autopilot finished": "Autopilot beendet", "until {until}": "bis {until}", - "allowed: {allowed}": "erlaubt: {allowed}" + "allowed: {allowed}": "erlaubt: {allowed}", + "{steps, plural, one {# step} other {# steps}}": "{steps, plural, one {# Schritt} other {# Schritte}}", + "last: {latest}": "zuletzt: {latest}", + "Order": "Auftrag", + "Time": "Uhrzeit", + "Until a time": "Bis zu einer Uhrzeit", + "Until done": "Bis fertig", + "Until tomorrow morning": "Bis morgen früh", + "Until when?": "Bis wann?" } diff --git a/apps/web/src/lib/heute-brief.test.ts b/apps/web/src/lib/heute-brief.test.ts index 18e85e9..f376f89 100644 --- a/apps/web/src/lib/heute-brief.test.ts +++ b/apps/web/src/lib/heute-brief.test.ts @@ -134,6 +134,25 @@ describe("buildHeuteBrief", () => { expect(brief.done[0]?.run.runId).toBe("run-4"); }); + it("leaves an autopilot's step runs to its own row", () => { + const brief = buildHeuteBrief({ + bots: [], + groups: [], + activeRuns: [run({ runId: "step-2", status: "running", trigger: "autopilot" })], + recentRuns: [ + run({ + runId: "step-1", + status: "completed", + trigger: "autopilot", + updatedAt: "2026-09-22T09:00:00.000Z", + }), + ], + now, + }); + expect(brief.doing).toHaveLength(0); + expect(brief.done).toHaveLength(0); + }); + it("caps needs-you at three items", () => { const bots = Array.from({ length: 5 }, (_, index) => bot({ id: `bot-${index}`, unread: true, name: `Bot ${index}` }), diff --git a/apps/web/src/lib/heute-brief.ts b/apps/web/src/lib/heute-brief.ts index a102370..cd9c1df 100644 --- a/apps/web/src/lib/heute-brief.ts +++ b/apps/web/src/lib/heute-brief.ts @@ -117,12 +117,16 @@ export function buildHeuteBrief(params: { coveredTargets.add(key); } + // An autopilot's runs show as its one row in Today, not one row per step. const doing: DoingItem[] = activeRuns - .filter((run) => isDoingStatus(run.status)) + .filter((run) => isDoingStatus(run.status) && run.trigger !== "autopilot") .map((run) => ({ kind: "run" as const, run })); const done: DoneItem[] = recentRuns - .filter((run) => run.status === "completed" && isToday(run.updatedAt, now)) + .filter( + (run) => + run.status === "completed" && run.trigger !== "autopilot" && isToday(run.updatedAt, now), + ) .slice(0, 5) .map((run) => ({ kind: "run" as const, run })); diff --git a/apps/web/src/locales/de/messages.po b/apps/web/src/locales/de/messages.po index 9e63777..3a4c2c7 100644 --- a/apps/web/src/locales/de/messages.po +++ b/apps/web/src/locales/de/messages.po @@ -264,6 +264,11 @@ msgstr "Noch {remaining} · Bot beobachtet nur" msgid "{size} B" msgstr "{size} B" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "{steps, plural, one {# step} other {# steps}}" +msgstr "{steps, plural, one {# Schritt} other {# Schritte}}" + #. placeholder {0}: runActivityLabel(run.status) #: src/pages/HeuteBrief.tsx msgid "{title}, {0}" @@ -366,6 +371,7 @@ msgstr "Aktivität" #: src/components/onboarding/OnboardingScanStep.tsx #: src/pages/PluginsOverlay.tsx #: src/pages/ScratchpadSection.tsx +#: src/pages/shell/composer.tsx msgid "Add" msgstr "Hinzufügen" @@ -764,7 +770,9 @@ msgstr "Automatische Absender" msgid "Automated senders will appear after they post here." msgstr "Automatische Absender erscheinen, sobald sie hier etwas posten." +#: src/pages/HeuteBrief.tsx #: src/pages/shell/autopilot-line.tsx +#: src/pages/shell/composer.tsx msgid "Autopilot" msgstr "Autopilot" @@ -2898,6 +2906,11 @@ msgstr "Sprache" msgid "Lashes" msgstr "Wimpern" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "last: {latest}" +msgstr "zuletzt: {latest}" + #: src/components/DesktopUpdates.tsx msgid "Later" msgstr "Später" @@ -3743,6 +3756,10 @@ msgstr "Optionaler Header-Wert" msgid "Or connect an API key" msgstr "Oder einen API-Schlüssel verbinden" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Order" +msgstr "Auftrag" + #: src/pages/memory-providers/SupermemorySettingsForm.tsx msgid "Organization API key" msgstr "Organisations-API-Schlüssel" @@ -3803,6 +3820,7 @@ msgstr "Füge die OpenAI-kompatible Adresse deines Servers ein. Rakazo ergänzt msgid "Paste your API key" msgstr "Füge deinen API-Schlüssel ein" +#: src/pages/HeuteBrief.tsx #: src/pages/RoutineEditor.tsx msgid "Paused" msgstr "Pausiert" @@ -4760,6 +4778,7 @@ msgid "Star" msgstr "Stern" #: src/pages/Shell.tsx +#: src/pages/shell/autopilot-dialog.tsx msgid "Start" msgstr "Starten" @@ -5042,6 +5061,10 @@ msgstr "Stufen" msgid "Tightening the copy…" msgstr "Straffe den Text…" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Time" +msgstr "Uhrzeit" + #: src/pages/RoutineSchedule.tsx msgid "Time of day" msgstr "Uhrzeit" @@ -5109,6 +5132,7 @@ msgid "Triage in progress…" msgstr "Triage läuft…" #: src/pages/Onboarding.tsx +#: src/pages/shell/autopilot-dialog.tsx #: src/pages/shell/autopilot-line.tsx #: src/pages/shell/routine-event-line.tsx msgid "Try again" @@ -5163,6 +5187,22 @@ msgstr "Nicht unterstützter Dateityp: {0}" msgid "until {until}" msgstr "bis {until}" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until a time" +msgstr "Bis zu einer Uhrzeit" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until done" +msgstr "Bis fertig" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until tomorrow morning" +msgstr "Bis morgen früh" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until when?" +msgstr "Bis wann?" + #: src/components/DesktopUpdates.tsx #: src/components/SoftwareUpdateSection.tsx msgid "Up to date" diff --git a/apps/web/src/locales/en/messages.po b/apps/web/src/locales/en/messages.po index a36cb4f..85a0267 100644 --- a/apps/web/src/locales/en/messages.po +++ b/apps/web/src/locales/en/messages.po @@ -264,6 +264,11 @@ msgstr "{remaining} left · bot is watching, not acting" msgid "{size} B" msgstr "{size} B" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "{steps, plural, one {# step} other {# steps}}" +msgstr "{steps, plural, one {# step} other {# steps}}" + #. placeholder {0}: runActivityLabel(run.status) #: src/pages/HeuteBrief.tsx msgid "{title}, {0}" @@ -366,6 +371,7 @@ msgstr "Activity" #: src/components/onboarding/OnboardingScanStep.tsx #: src/pages/PluginsOverlay.tsx #: src/pages/ScratchpadSection.tsx +#: src/pages/shell/composer.tsx msgid "Add" msgstr "Add" @@ -764,7 +770,9 @@ msgstr "Automated senders" msgid "Automated senders will appear after they post here." msgstr "Automated senders will appear after they post here." +#: src/pages/HeuteBrief.tsx #: src/pages/shell/autopilot-line.tsx +#: src/pages/shell/composer.tsx msgid "Autopilot" msgstr "Autopilot" @@ -2898,6 +2906,11 @@ msgstr "Language" msgid "Lashes" msgstr "Lashes" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "last: {latest}" +msgstr "last: {latest}" + #: src/components/DesktopUpdates.tsx msgid "Later" msgstr "Later" @@ -3743,6 +3756,10 @@ msgstr "Optional header value" msgid "Or connect an API key" msgstr "Or connect an API key" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Order" +msgstr "Order" + #: src/pages/memory-providers/SupermemorySettingsForm.tsx msgid "Organization API key" msgstr "Organization API key" @@ -3803,6 +3820,7 @@ msgstr "Paste the OpenAI-compatible address from your server. Rakazo adds /v1 if msgid "Paste your API key" msgstr "Paste your API key" +#: src/pages/HeuteBrief.tsx #: src/pages/RoutineEditor.tsx msgid "Paused" msgstr "Paused" @@ -4760,6 +4778,7 @@ msgid "Star" msgstr "Star" #: src/pages/Shell.tsx +#: src/pages/shell/autopilot-dialog.tsx msgid "Start" msgstr "Start" @@ -5042,6 +5061,10 @@ msgstr "Tiers" msgid "Tightening the copy…" msgstr "Tightening the copy…" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Time" +msgstr "Time" + #: src/pages/RoutineSchedule.tsx msgid "Time of day" msgstr "Time of day" @@ -5109,6 +5132,7 @@ msgid "Triage in progress…" msgstr "Triage in progress…" #: src/pages/Onboarding.tsx +#: src/pages/shell/autopilot-dialog.tsx #: src/pages/shell/autopilot-line.tsx #: src/pages/shell/routine-event-line.tsx msgid "Try again" @@ -5163,6 +5187,22 @@ msgstr "Unsupported file type: {0}" msgid "until {until}" msgstr "until {until}" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until a time" +msgstr "Until a time" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until done" +msgstr "Until done" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until tomorrow morning" +msgstr "Until tomorrow morning" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until when?" +msgstr "Until when?" + #: src/components/DesktopUpdates.tsx #: src/components/SoftwareUpdateSection.tsx msgid "Up to date" diff --git a/apps/web/src/locales/es/messages.po b/apps/web/src/locales/es/messages.po index d905071..214143b 100644 --- a/apps/web/src/locales/es/messages.po +++ b/apps/web/src/locales/es/messages.po @@ -264,6 +264,11 @@ msgstr "Quedan {remaining} · el bot observa, no actúa" msgid "{size} B" msgstr "{size} B" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "{steps, plural, one {# step} other {# steps}}" +msgstr "" + #. placeholder {0}: runActivityLabel(run.status) #: src/pages/HeuteBrief.tsx msgid "{title}, {0}" @@ -366,6 +371,7 @@ msgstr "Actividad" #: src/components/onboarding/OnboardingScanStep.tsx #: src/pages/PluginsOverlay.tsx #: src/pages/ScratchpadSection.tsx +#: src/pages/shell/composer.tsx msgid "Add" msgstr "Agregar" @@ -764,7 +770,9 @@ msgstr "" msgid "Automated senders will appear after they post here." msgstr "" +#: src/pages/HeuteBrief.tsx #: src/pages/shell/autopilot-line.tsx +#: src/pages/shell/composer.tsx msgid "Autopilot" msgstr "" @@ -2898,6 +2906,11 @@ msgstr "Idioma" msgid "Lashes" msgstr "" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "last: {latest}" +msgstr "" + #: src/components/DesktopUpdates.tsx msgid "Later" msgstr "" @@ -3743,6 +3756,10 @@ msgstr "Valor de encabezado opcional" msgid "Or connect an API key" msgstr "O conecta una clave de API" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Order" +msgstr "" + #: src/pages/memory-providers/SupermemorySettingsForm.tsx msgid "Organization API key" msgstr "Clave de API de organización" @@ -3803,6 +3820,7 @@ msgstr "Pega la dirección compatible con OpenAI de tu servidor. Rakazo agrega / msgid "Paste your API key" msgstr "Pega tu clave de API" +#: src/pages/HeuteBrief.tsx #: src/pages/RoutineEditor.tsx msgid "Paused" msgstr "En pausa" @@ -4760,6 +4778,7 @@ msgid "Star" msgstr "" #: src/pages/Shell.tsx +#: src/pages/shell/autopilot-dialog.tsx msgid "Start" msgstr "" @@ -5042,6 +5061,10 @@ msgstr "" msgid "Tightening the copy…" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Time" +msgstr "" + #: src/pages/RoutineSchedule.tsx msgid "Time of day" msgstr "Hora del día" @@ -5109,6 +5132,7 @@ msgid "Triage in progress…" msgstr "" #: src/pages/Onboarding.tsx +#: src/pages/shell/autopilot-dialog.tsx #: src/pages/shell/autopilot-line.tsx #: src/pages/shell/routine-event-line.tsx msgid "Try again" @@ -5163,6 +5187,22 @@ msgstr "Tipo de archivo no compatible: {0}" msgid "until {until}" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until a time" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until done" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until tomorrow morning" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until when?" +msgstr "" + #: src/components/DesktopUpdates.tsx #: src/components/SoftwareUpdateSection.tsx msgid "Up to date" diff --git a/apps/web/src/locales/hi/messages.po b/apps/web/src/locales/hi/messages.po index 891d64c..51e1b09 100644 --- a/apps/web/src/locales/hi/messages.po +++ b/apps/web/src/locales/hi/messages.po @@ -264,6 +264,11 @@ msgstr "{remaining} शेष · बॉट देख रहा है, कार msgid "{size} B" msgstr "{size} B" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "{steps, plural, one {# step} other {# steps}}" +msgstr "" + #. placeholder {0}: runActivityLabel(run.status) #: src/pages/HeuteBrief.tsx msgid "{title}, {0}" @@ -366,6 +371,7 @@ msgstr "गतिविधि" #: src/components/onboarding/OnboardingScanStep.tsx #: src/pages/PluginsOverlay.tsx #: src/pages/ScratchpadSection.tsx +#: src/pages/shell/composer.tsx msgid "Add" msgstr "जोड़ें" @@ -764,7 +770,9 @@ msgstr "" msgid "Automated senders will appear after they post here." msgstr "" +#: src/pages/HeuteBrief.tsx #: src/pages/shell/autopilot-line.tsx +#: src/pages/shell/composer.tsx msgid "Autopilot" msgstr "" @@ -2898,6 +2906,11 @@ msgstr "भाषा" msgid "Lashes" msgstr "" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "last: {latest}" +msgstr "" + #: src/components/DesktopUpdates.tsx msgid "Later" msgstr "" @@ -3743,6 +3756,10 @@ msgstr "वैकल्पिक हेडर मान" msgid "Or connect an API key" msgstr "या एक API कुंजी कनेक्ट करें" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Order" +msgstr "" + #: src/pages/memory-providers/SupermemorySettingsForm.tsx msgid "Organization API key" msgstr "संगठन API कुंजी" @@ -3803,6 +3820,7 @@ msgstr "अपने सर्वर से OpenAI-संगत पता पे msgid "Paste your API key" msgstr "अपनी API कुंजी पेस्ट करें" +#: src/pages/HeuteBrief.tsx #: src/pages/RoutineEditor.tsx msgid "Paused" msgstr "" @@ -4760,6 +4778,7 @@ msgid "Star" msgstr "" #: src/pages/Shell.tsx +#: src/pages/shell/autopilot-dialog.tsx msgid "Start" msgstr "" @@ -5042,6 +5061,10 @@ msgstr "" msgid "Tightening the copy…" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Time" +msgstr "" + #: src/pages/RoutineSchedule.tsx msgid "Time of day" msgstr "दिन का समय" @@ -5109,6 +5132,7 @@ msgid "Triage in progress…" msgstr "" #: src/pages/Onboarding.tsx +#: src/pages/shell/autopilot-dialog.tsx #: src/pages/shell/autopilot-line.tsx #: src/pages/shell/routine-event-line.tsx msgid "Try again" @@ -5163,6 +5187,22 @@ msgstr "असमर्थित फ़ाइल प्रकार: {0}" msgid "until {until}" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until a time" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until done" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until tomorrow morning" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until when?" +msgstr "" + #: src/components/DesktopUpdates.tsx #: src/components/SoftwareUpdateSection.tsx msgid "Up to date" diff --git a/apps/web/src/locales/ko/messages.po b/apps/web/src/locales/ko/messages.po index 16a6a6e..478118c 100644 --- a/apps/web/src/locales/ko/messages.po +++ b/apps/web/src/locales/ko/messages.po @@ -264,6 +264,11 @@ msgstr "남은 시간 {remaining} · Bot은 화면만 보고 직접 조작하지 msgid "{size} B" msgstr "{size} B" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "{steps, plural, one {# step} other {# steps}}" +msgstr "" + #. placeholder {0}: runActivityLabel(run.status) #: src/pages/HeuteBrief.tsx msgid "{title}, {0}" @@ -366,6 +371,7 @@ msgstr "활동" #: src/components/onboarding/OnboardingScanStep.tsx #: src/pages/PluginsOverlay.tsx #: src/pages/ScratchpadSection.tsx +#: src/pages/shell/composer.tsx msgid "Add" msgstr "추가" @@ -764,7 +770,9 @@ msgstr "" msgid "Automated senders will appear after they post here." msgstr "" +#: src/pages/HeuteBrief.tsx #: src/pages/shell/autopilot-line.tsx +#: src/pages/shell/composer.tsx msgid "Autopilot" msgstr "" @@ -2898,6 +2906,11 @@ msgstr "언어" msgid "Lashes" msgstr "" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "last: {latest}" +msgstr "" + #: src/components/DesktopUpdates.tsx msgid "Later" msgstr "" @@ -3743,6 +3756,10 @@ msgstr "선택 사항인 헤더 값" msgid "Or connect an API key" msgstr "또는 API 키 연결" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Order" +msgstr "" + #: src/pages/memory-providers/SupermemorySettingsForm.tsx msgid "Organization API key" msgstr "조직 API 키" @@ -3803,6 +3820,7 @@ msgstr "서버의 OpenAI 호환 주소를 붙여넣으세요. 필요하면 Rakaz msgid "Paste your API key" msgstr "API 키를 붙여넣으세요" +#: src/pages/HeuteBrief.tsx #: src/pages/RoutineEditor.tsx msgid "Paused" msgstr "일시 중지됨" @@ -4760,6 +4778,7 @@ msgid "Star" msgstr "" #: src/pages/Shell.tsx +#: src/pages/shell/autopilot-dialog.tsx msgid "Start" msgstr "" @@ -5042,6 +5061,10 @@ msgstr "" msgid "Tightening the copy…" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Time" +msgstr "" + #: src/pages/RoutineSchedule.tsx msgid "Time of day" msgstr "실행 시각" @@ -5109,6 +5132,7 @@ msgid "Triage in progress…" msgstr "" #: src/pages/Onboarding.tsx +#: src/pages/shell/autopilot-dialog.tsx #: src/pages/shell/autopilot-line.tsx #: src/pages/shell/routine-event-line.tsx msgid "Try again" @@ -5163,6 +5187,22 @@ msgstr "지원하지 않는 파일 형식: {0}" msgid "until {until}" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until a time" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until done" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until tomorrow morning" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until when?" +msgstr "" + #: src/components/DesktopUpdates.tsx #: src/components/SoftwareUpdateSection.tsx msgid "Up to date" diff --git a/apps/web/src/locales/pt-BR/messages.po b/apps/web/src/locales/pt-BR/messages.po index 61cbdd3..0b3193e 100644 --- a/apps/web/src/locales/pt-BR/messages.po +++ b/apps/web/src/locales/pt-BR/messages.po @@ -264,6 +264,11 @@ msgstr "{remaining} restantes · o bot está observando, não agindo" msgid "{size} B" msgstr "{size} B" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "{steps, plural, one {# step} other {# steps}}" +msgstr "" + #. placeholder {0}: runActivityLabel(run.status) #: src/pages/HeuteBrief.tsx msgid "{title}, {0}" @@ -366,6 +371,7 @@ msgstr "Atividade" #: src/components/onboarding/OnboardingScanStep.tsx #: src/pages/PluginsOverlay.tsx #: src/pages/ScratchpadSection.tsx +#: src/pages/shell/composer.tsx msgid "Add" msgstr "Adicionar" @@ -764,7 +770,9 @@ msgstr "" msgid "Automated senders will appear after they post here." msgstr "" +#: src/pages/HeuteBrief.tsx #: src/pages/shell/autopilot-line.tsx +#: src/pages/shell/composer.tsx msgid "Autopilot" msgstr "" @@ -2898,6 +2906,11 @@ msgstr "Idioma" msgid "Lashes" msgstr "" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "last: {latest}" +msgstr "" + #: src/components/DesktopUpdates.tsx msgid "Later" msgstr "" @@ -3743,6 +3756,10 @@ msgstr "Valor de cabeçalho opcional" msgid "Or connect an API key" msgstr "Ou conecte uma chave de API" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Order" +msgstr "" + #: src/pages/memory-providers/SupermemorySettingsForm.tsx msgid "Organization API key" msgstr "Chave da API da organização" @@ -3803,6 +3820,7 @@ msgstr "Cole o endereço compatível com OpenAI do seu servidor. Rakazo adiciona msgid "Paste your API key" msgstr "Cole sua chave API" +#: src/pages/HeuteBrief.tsx #: src/pages/RoutineEditor.tsx msgid "Paused" msgstr "" @@ -4760,6 +4778,7 @@ msgid "Star" msgstr "" #: src/pages/Shell.tsx +#: src/pages/shell/autopilot-dialog.tsx msgid "Start" msgstr "" @@ -5042,6 +5061,10 @@ msgstr "" msgid "Tightening the copy…" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Time" +msgstr "" + #: src/pages/RoutineSchedule.tsx msgid "Time of day" msgstr "Hora do dia" @@ -5109,6 +5132,7 @@ msgid "Triage in progress…" msgstr "" #: src/pages/Onboarding.tsx +#: src/pages/shell/autopilot-dialog.tsx #: src/pages/shell/autopilot-line.tsx #: src/pages/shell/routine-event-line.tsx msgid "Try again" @@ -5163,6 +5187,22 @@ msgstr "Tipo de arquivo não suportado: {0}" msgid "until {until}" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until a time" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until done" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until tomorrow morning" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until when?" +msgstr "" + #: src/components/DesktopUpdates.tsx #: src/components/SoftwareUpdateSection.tsx msgid "Up to date" diff --git a/apps/web/src/locales/ru/messages.po b/apps/web/src/locales/ru/messages.po index 4d0801e..026f751 100644 --- a/apps/web/src/locales/ru/messages.po +++ b/apps/web/src/locales/ru/messages.po @@ -264,6 +264,11 @@ msgstr "Осталось: {remaining} · бот наблюдает, но не д msgid "{size} B" msgstr "{size} Б" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "{steps, plural, one {# step} other {# steps}}" +msgstr "" + #. placeholder {0}: runActivityLabel(run.status) #: src/pages/HeuteBrief.tsx msgid "{title}, {0}" @@ -366,6 +371,7 @@ msgstr "Активность" #: src/components/onboarding/OnboardingScanStep.tsx #: src/pages/PluginsOverlay.tsx #: src/pages/ScratchpadSection.tsx +#: src/pages/shell/composer.tsx msgid "Add" msgstr "Добавить" @@ -764,7 +770,9 @@ msgstr "Автоматические отправители" msgid "Automated senders will appear after they post here." msgstr "Автоматические отправители появятся после публикации здесь." +#: src/pages/HeuteBrief.tsx #: src/pages/shell/autopilot-line.tsx +#: src/pages/shell/composer.tsx msgid "Autopilot" msgstr "" @@ -2898,6 +2906,11 @@ msgstr "Язык" msgid "Lashes" msgstr "" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "last: {latest}" +msgstr "" + #: src/components/DesktopUpdates.tsx msgid "Later" msgstr "Позже" @@ -3743,6 +3756,10 @@ msgstr "Необязательное значение заголовка" msgid "Or connect an API key" msgstr "Или подключить API-ключ" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Order" +msgstr "" + #: src/pages/memory-providers/SupermemorySettingsForm.tsx msgid "Organization API key" msgstr "Ключ API организации" @@ -3803,6 +3820,7 @@ msgstr "Вставьте OpenAI-совместимый адрес со свое msgid "Paste your API key" msgstr "Вставьте свой ключ API" +#: src/pages/HeuteBrief.tsx #: src/pages/RoutineEditor.tsx msgid "Paused" msgstr "Приостановлено" @@ -4760,6 +4778,7 @@ msgid "Star" msgstr "" #: src/pages/Shell.tsx +#: src/pages/shell/autopilot-dialog.tsx msgid "Start" msgstr "" @@ -5042,6 +5061,10 @@ msgstr "" msgid "Tightening the copy…" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Time" +msgstr "" + #: src/pages/RoutineSchedule.tsx msgid "Time of day" msgstr "Время суток" @@ -5109,6 +5132,7 @@ msgid "Triage in progress…" msgstr "" #: src/pages/Onboarding.tsx +#: src/pages/shell/autopilot-dialog.tsx #: src/pages/shell/autopilot-line.tsx #: src/pages/shell/routine-event-line.tsx msgid "Try again" @@ -5163,6 +5187,22 @@ msgstr "Неподдерживаемый тип файла: {0}." msgid "until {until}" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until a time" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until done" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until tomorrow morning" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until when?" +msgstr "" + #: src/components/DesktopUpdates.tsx #: src/components/SoftwareUpdateSection.tsx msgid "Up to date" diff --git a/apps/web/src/locales/tr/messages.po b/apps/web/src/locales/tr/messages.po index a54d287..9b782c4 100644 --- a/apps/web/src/locales/tr/messages.po +++ b/apps/web/src/locales/tr/messages.po @@ -264,6 +264,11 @@ msgstr "{remaining} kaldı · bot yalnızca izliyor" msgid "{size} B" msgstr "{size} B" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "{steps, plural, one {# step} other {# steps}}" +msgstr "" + #. placeholder {0}: runActivityLabel(run.status) #: src/pages/HeuteBrief.tsx msgid "{title}, {0}" @@ -366,6 +371,7 @@ msgstr "Etkinlik" #: src/components/onboarding/OnboardingScanStep.tsx #: src/pages/PluginsOverlay.tsx #: src/pages/ScratchpadSection.tsx +#: src/pages/shell/composer.tsx msgid "Add" msgstr "Ekle" @@ -764,7 +770,9 @@ msgstr "" msgid "Automated senders will appear after they post here." msgstr "" +#: src/pages/HeuteBrief.tsx #: src/pages/shell/autopilot-line.tsx +#: src/pages/shell/composer.tsx msgid "Autopilot" msgstr "" @@ -2898,6 +2906,11 @@ msgstr "Dil" msgid "Lashes" msgstr "" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "last: {latest}" +msgstr "" + #: src/components/DesktopUpdates.tsx msgid "Later" msgstr "" @@ -3743,6 +3756,10 @@ msgstr "İsteğe bağlı üstbilgi değeri" msgid "Or connect an API key" msgstr "Veya bir API anahtarı bağla" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Order" +msgstr "" + #: src/pages/memory-providers/SupermemorySettingsForm.tsx msgid "Organization API key" msgstr "Kuruluş API anahtarı" @@ -3803,6 +3820,7 @@ msgstr "Sunucundaki OpenAI uyumlu adresi yapıştır. Gerekirse Rakazo /v1 ekler msgid "Paste your API key" msgstr "API anahtarını yapıştır" +#: src/pages/HeuteBrief.tsx #: src/pages/RoutineEditor.tsx msgid "Paused" msgstr "" @@ -4760,6 +4778,7 @@ msgid "Star" msgstr "" #: src/pages/Shell.tsx +#: src/pages/shell/autopilot-dialog.tsx msgid "Start" msgstr "" @@ -5042,6 +5061,10 @@ msgstr "" msgid "Tightening the copy…" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Time" +msgstr "" + #: src/pages/RoutineSchedule.tsx msgid "Time of day" msgstr "Günün saati" @@ -5109,6 +5132,7 @@ msgid "Triage in progress…" msgstr "" #: src/pages/Onboarding.tsx +#: src/pages/shell/autopilot-dialog.tsx #: src/pages/shell/autopilot-line.tsx #: src/pages/shell/routine-event-line.tsx msgid "Try again" @@ -5163,6 +5187,22 @@ msgstr "Desteklenmeyen dosya türü: {0}" msgid "until {until}" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until a time" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until done" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until tomorrow morning" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until when?" +msgstr "" + #: src/components/DesktopUpdates.tsx #: src/components/SoftwareUpdateSection.tsx msgid "Up to date" diff --git a/apps/web/src/locales/zh-CN/messages.po b/apps/web/src/locales/zh-CN/messages.po index b18c72a..4932ef1 100644 --- a/apps/web/src/locales/zh-CN/messages.po +++ b/apps/web/src/locales/zh-CN/messages.po @@ -264,6 +264,11 @@ msgstr "剩余 {remaining} · Bot 只看不操作" msgid "{size} B" msgstr "{size} B" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "{steps, plural, one {# step} other {# steps}}" +msgstr "" + #. placeholder {0}: runActivityLabel(run.status) #: src/pages/HeuteBrief.tsx msgid "{title}, {0}" @@ -366,6 +371,7 @@ msgstr "动态" #: src/components/onboarding/OnboardingScanStep.tsx #: src/pages/PluginsOverlay.tsx #: src/pages/ScratchpadSection.tsx +#: src/pages/shell/composer.tsx msgid "Add" msgstr "添加" @@ -764,7 +770,9 @@ msgstr "" msgid "Automated senders will appear after they post here." msgstr "" +#: src/pages/HeuteBrief.tsx #: src/pages/shell/autopilot-line.tsx +#: src/pages/shell/composer.tsx msgid "Autopilot" msgstr "" @@ -2898,6 +2906,11 @@ msgstr "语言" msgid "Lashes" msgstr "" +#: src/pages/HeuteBrief.tsx +#: src/pages/shell/autopilot-line.tsx +msgid "last: {latest}" +msgstr "" + #: src/components/DesktopUpdates.tsx msgid "Later" msgstr "" @@ -3743,6 +3756,10 @@ msgstr "可选请求头值" msgid "Or connect an API key" msgstr "或连接 API 密钥" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Order" +msgstr "" + #: src/pages/memory-providers/SupermemorySettingsForm.tsx msgid "Organization API key" msgstr "组织 API 密钥" @@ -3803,6 +3820,7 @@ msgstr "粘贴服务器的 OpenAI 兼容地址。需要时 Rakazo 会补上 /v1 msgid "Paste your API key" msgstr "粘贴你的 API 密钥" +#: src/pages/HeuteBrief.tsx #: src/pages/RoutineEditor.tsx msgid "Paused" msgstr "已暂停" @@ -4760,6 +4778,7 @@ msgid "Star" msgstr "" #: src/pages/Shell.tsx +#: src/pages/shell/autopilot-dialog.tsx msgid "Start" msgstr "" @@ -5042,6 +5061,10 @@ msgstr "" msgid "Tightening the copy…" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Time" +msgstr "" + #: src/pages/RoutineSchedule.tsx msgid "Time of day" msgstr "时间" @@ -5109,6 +5132,7 @@ msgid "Triage in progress…" msgstr "" #: src/pages/Onboarding.tsx +#: src/pages/shell/autopilot-dialog.tsx #: src/pages/shell/autopilot-line.tsx #: src/pages/shell/routine-event-line.tsx msgid "Try again" @@ -5163,6 +5187,22 @@ msgstr "不支持的文件类型:{0}" msgid "until {until}" msgstr "" +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until a time" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until done" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until tomorrow morning" +msgstr "" + +#: src/pages/shell/autopilot-dialog.tsx +msgid "Until when?" +msgstr "" + #: src/components/DesktopUpdates.tsx #: src/components/SoftwareUpdateSection.tsx msgid "Up to date" diff --git a/apps/web/src/pages/HeuteBrief.tsx b/apps/web/src/pages/HeuteBrief.tsx index 4bef9db..1ad79a4 100644 --- a/apps/web/src/pages/HeuteBrief.tsx +++ b/apps/web/src/pages/HeuteBrief.tsx @@ -1,8 +1,16 @@ import { i18n } from "@lingui/core"; import { Plural, Trans, useLingui } from "@lingui/react/macro"; -import type { Bot, CoachToday, Group, HabitToday, RunActivityRow } from "@rakazo/contracts"; +import type { + AutopilotRecent, + Bot, + CoachToday, + Group, + HabitToday, + RunActivityRow, +} from "@rakazo/contracts"; import { botColors } from "@rakazo/ui-tokens"; -import { BotAvatar, Button, EvooMascot, GroupAvatar } from "@rakazo/ui-web"; +import { BotAvatar, Button, EvooMascot, GroupAvatar, Icon } from "@rakazo/ui-web"; +import { CircleCheck, Pause, Play, Square } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { CoachRhythmControl } from "../components/heute/CoachRhythmControl"; import { HeuteCoach } from "../components/heute/HeuteCoach"; @@ -39,6 +47,7 @@ export function HeuteBrief({ }: HeuteBriefProps) { const [activeRuns, setActiveRuns] = useState([]); const [recentRuns, setRecentRuns] = useState([]); + const [autopilots, setAutopilots] = useState([]); const [loading, setLoading] = useState(true); const [coach, setCoach] = useState(null); const [coachSettled, setCoachSettled] = useState(false); @@ -50,13 +59,15 @@ export function HeuteBrief({ const tick = async () => { try { - const [active, recent] = await Promise.all([ + const [active, recent, autopilot] = await Promise.all([ rpc.runs.list({ filter: "active" }), rpc.runs.list({ filter: "recent" }), + rpc.autopilot.recent().catch(() => ({ items: [] })), ]); if (cancelled) return; setActiveRuns(active.runs); setRecentRuns(recent.runs); + setAutopilots(autopilot.items); } catch { if (cancelled) return; } finally { @@ -139,7 +150,11 @@ export function HeuteBrief({ coach && (coach.brief || coach.habits.length > 0 || coach.goals.length > 0), ); const hasContent = - hasCoachContent || brief.needsYou.length > 0 || brief.doing.length > 0 || brief.done.length > 0; + hasCoachContent || + autopilots.length > 0 || + brief.needsYou.length > 0 || + brief.doing.length > 0 || + brief.done.length > 0; return (
@@ -170,6 +185,27 @@ export function HeuteBrief({
{coach ? : null} + {autopilots.length > 0 ? ( +
+

+ Autopilot +

+
+ {autopilots.map((autopilot) => ( + + autopilot.groupId + ? onOpenGroup(autopilot.groupId, autopilot.spaceId) + : onOpenBot(autopilot.botId, autopilot.spaceId) + } + /> + ))} +
+
+ ) : null} + {brief.needsYou.length > 0 ? (

@@ -446,6 +482,55 @@ function DoneRow({ ); } +/** One autopilot: what it works on, how far it got, and how it ended; opens its thread. */ +function AutopilotRow({ autopilot, onOpen }: { autopilot: AutopilotRecent; onOpen: () => void }) { + const { t } = useLingui(); + const steps = autopilot.stepCount; + const latest = autopilot.lastCheckpoint; + const icon = { active: Play, paused: Pause, finished: CircleCheck, stopped: Square }[ + autopilot.status + ]; + const detail = + autopilot.status === "finished" + ? autopilot.result + : autopilot.status === "paused" + ? autopilot.pauseReason + : latest + ? t`last: ${latest}` + : null; + + return ( + + ); +} + function jetztRowKey(item: JetztItem): string { if (item.kind === "run") return `run:${item.run.runId}`; return `unread:${item.chatKind}:${item.chat.id}`; diff --git a/apps/web/src/pages/Shell.tsx b/apps/web/src/pages/Shell.tsx index a6a23c2..e023af9 100644 --- a/apps/web/src/pages/Shell.tsx +++ b/apps/web/src/pages/Shell.tsx @@ -1993,6 +1993,18 @@ export function ShellPage() { await refreshThreadRef.current(botId); } }, []); + const startAutopilot = useCallback(async (goal: string, until?: string) => { + const botId = activeBotId.current; + const groupId = activeGroupId.current; + if (!botId && !groupId) return; + const target = groupId ? { groupId } : { botId: botId! }; + await rpc.autopilot.start({ ...target, goal, ...(until ? { until } : {}) }); + if (groupId && activeGroupId.current === groupId) { + await refreshGroupThreadRef.current(groupId); + } else if (botId && activeBotId.current === botId) { + await refreshThreadRef.current(botId); + } + }, []); const reactToMessage = useCallback( async (message: ThreadMessage, reaction: MessageReaction) => { const botId = activeBotId.current; @@ -3418,6 +3430,7 @@ export function ShellPage() { agentSkills={agentSkills} onSlashOpen={refreshAgentSkills} onConnectModel={bootstrapMe?.needsModel ? () => openSettings("models") : undefined} + onAutopilot={startAutopilot} onSlashAction={(action) => { if (action === "chat-settings") { setPanel(inGroup ? "group-settings" : "settings"); diff --git a/apps/web/src/pages/shell/autopilot-dialog.tsx b/apps/web/src/pages/shell/autopilot-dialog.tsx new file mode 100644 index 0000000..e8dfc98 --- /dev/null +++ b/apps/web/src/pages/shell/autopilot-dialog.tsx @@ -0,0 +1,106 @@ +import { useLingui } from "@lingui/react/macro"; +import type { AutopilotUntilChoice } from "@rakazo/core"; +import { autopilotUntil } from "@rakazo/core"; +import { + Button, + Dialog, + DialogContent, + DialogFooter, + DialogTitle, + Input, + Textarea, +} from "@rakazo/ui-web"; +import { useState } from "react"; + +type Choice = AutopilotUntilChoice["kind"]; + +/** + * Start the autopilot from the composer: the order (the draft so far) and until when it may + * work. Mounted only while open, so it always starts from the current draft. + */ +export function AutopilotDialog({ + draft, + onClose, + onStart, +}: { + draft: string; + onClose: () => void; + onStart: (goal: string, until?: string) => Promise; +}) { + const { t } = useLingui(); + const [goal, setGoal] = useState(draft); + const [choice, setChoice] = useState("done"); + const [time, setTime] = useState("06:00"); + const [busy, setBusy] = useState(false); + const [failed, setFailed] = useState(false); + + const options: { kind: Choice; label: string }[] = [ + { kind: "done", label: t`Until done` }, + { kind: "morning", label: t`Until tomorrow morning` }, + { kind: "time", label: t`Until a time` }, + ]; + + async function start() { + const order = goal.trim(); + if (!order) return; + setBusy(true); + setFailed(false); + try { + const until = autopilotUntil( + choice === "time" ? { kind: "time", time } : { kind: choice }, + new Date(), + ); + await onStart(order, until?.toISOString()); + onClose(); + } catch { + setFailed(true); + } finally { + setBusy(false); + } + } + + return ( + !next && onClose()}> + + {t`Until when?`} +