diff --git a/apps/desktop/e2e/composer-skill-invocation.spec.ts b/apps/desktop/e2e/composer-skill-invocation.spec.ts index d807d86ea2..b63da95f1d 100644 --- a/apps/desktop/e2e/composer-skill-invocation.spec.ts +++ b/apps/desktop/e2e/composer-skill-invocation.spec.ts @@ -30,7 +30,7 @@ test('staged Skills come back as chips after leaving and returning', async ({ await expect(workspaceChip).toContainText('Workspace Only'); await page.getByRole('button', { name: '展开侧边栏' }).click(); - const sidebar = page.getByRole('navigation', { name: '对话列表' }); + const sidebar = page.getByRole('navigation', { name: '任务列表' }); await sidebar.getByRole('button', { name: '新任务', exact: true }).click(); await expect(composer).toHaveText(''); diff --git a/apps/desktop/e2e/session-workbar.spec.ts b/apps/desktop/e2e/session-workbar.spec.ts index 1d21a2a232..4151e22bab 100644 --- a/apps/desktop/e2e/session-workbar.spec.ts +++ b/apps/desktop/e2e/session-workbar.spec.ts @@ -8,7 +8,7 @@ async function openGitChanges(page: Page) { await composer.fill('create review session'); await composer.press('Enter'); await expect(page.getByText(/Fake backend received: create review session/)).toBeVisible(); - await page.getByRole('button', { name: '展开会话工作栏' }).click(); + await page.getByRole('button', { name: '展开任务工作栏' }).click(); await expect(page.getByRole('list', { name: '打开工具' })).toBeVisible(); await page.getByRole('button', { name: /变更.*查看当前 Git 工作区变化/ }).click(); return page.getByRole('region', { name: 'Git 变更' }); @@ -20,14 +20,14 @@ test('titlebar workbar action restores an existing tool instead of the picker', const page = gitReviewWindow.page; const workspaceActions = page.getByRole('toolbar', { name: '工作区辅助操作' }); const panel = await openGitChanges(page); - await expect(workspaceActions.getByRole('button', { name: '收起会话工作栏' })).toBeVisible(); + await expect(workspaceActions.getByRole('button', { name: '收起任务工作栏' })).toBeVisible(); await expect(workspaceActions.getByRole('button', { name: '打开工作栏工具' })).toHaveCount(0); await page.getByRole('button', { name: '打开工作栏标签' }).click(); const picker = page.getByRole('list', { name: '打开工具' }); await expect(picker).toBeVisible(); - await workspaceActions.getByRole('button', { name: '收起会话工作栏' }).click(); - await workspaceActions.getByRole('button', { name: '展开会话工作栏' }).click(); + await workspaceActions.getByRole('button', { name: '收起任务工作栏' }).click(); + await workspaceActions.getByRole('button', { name: '展开任务工作栏' }).click(); await expect(panel).toBeVisible(); await expect(picker).not.toBeVisible(); diff --git a/apps/desktop/e2e/settings.spec.ts b/apps/desktop/e2e/settings.spec.ts index 0b87917d0b..65c675f11c 100644 --- a/apps/desktop/e2e/settings.spec.ts +++ b/apps/desktop/e2e/settings.spec.ts @@ -8,8 +8,8 @@ test('opening settings commits an active titlebar rename', async ({ window: page const identity = page.locator('[data-maka-contract="titlebar-identity"]'); await expect(identity).toBeVisible(); await page.getByRole('button', { name: '展开侧边栏' }).click(); - await identity.getByRole('button', { name: /重命名对话/ }).click(); - await page.getByRole('textbox', { name: '重命名对话' }).fill('renamed before settings'); + await identity.getByRole('button', { name: /重命名任务/ }).click(); + await page.getByRole('textbox', { name: '重命名任务' }).fill('renamed before settings'); // Programmatic activation preserves input focus, matching the macOS // application-menu command that opens Settings before Chromium can blur it. diff --git a/apps/desktop/e2e/sidebar-project-row.spec.ts b/apps/desktop/e2e/sidebar-project-row.spec.ts index 4f8fe7b2de..168fefad0c 100644 --- a/apps/desktop/e2e/sidebar-project-row.spec.ts +++ b/apps/desktop/e2e/sidebar-project-row.spec.ts @@ -10,7 +10,7 @@ test('project navigation and actions remain adjacent keyboard controls', async ( await page.keyboard.press('Escape'); await expect(page.locator('[data-maka-contract="search-modal"]')).not.toBeVisible(); - const sidebar = page.getByRole('navigation', { name: '对话列表' }); + const sidebar = page.getByRole('navigation', { name: '任务列表' }); await sidebar.getByRole('radio', { name: '按项目', exact: true }).click(); const projectRow = sidebar.locator( diff --git a/apps/desktop/e2e/streaming-remount.spec.ts b/apps/desktop/e2e/streaming-remount.spec.ts index 81aa04f385..72d6d2aa3b 100644 --- a/apps/desktop/e2e/streaming-remount.spec.ts +++ b/apps/desktop/e2e/streaming-remount.spec.ts @@ -18,11 +18,20 @@ test('remounting a live surface leaves accumulated output settled', async ({ const liveBubble = page.locator('.maka-bubble-streaming'); await expect(liveBubble).toContainText(accumulatedOutput); - const sidebar = page.getByRole('navigation', { name: '对话列表' }); + const sidebar = page.getByRole('navigation', { name: '任务列表' }); await sidebar.getByRole('button', { name: '扩展' }).click(); await expect(page.locator('[data-module="skills"]')).toBeVisible(); await expect(liveBubble).toHaveCount(0); - await sidebar.getByRole('button', { name: '会话', exact: true }).click(); + // Back the way the product actually offers: the rail is collapsed here, so + // the task rows are not rendered and there is no 任务 row to press (#2984). + // Widening it is the titlebar's job, and the task left behind is still + // `activeId`, so it comes back marked and one click away. + await page.getByRole('button', { name: '展开侧边栏' }).click(); + const currentTaskRow = sidebar.locator( + '[data-maka-contract="session-row"] [aria-current="page"]', + ); + await expect(currentTaskRow).toHaveCount(1); + await currentTaskRow.click(); await expect(liveBubble).toHaveCount(1); await expect(liveBubble).toContainText(accumulatedOutput); @@ -75,7 +84,7 @@ test('returning to a live conversation settles output accumulated while away', a const liveBubble = page.locator('.maka-bubble-streaming'); await expect(liveBubble).toContainText(accumulatedOutput); - const sidebar = page.getByRole('navigation', { name: '对话列表' }); + const sidebar = page.getByRole('navigation', { name: '任务列表' }); await page.getByRole('button', { name: '展开侧边栏' }).click(); await expect(page.locator('[data-agents-page]')).toHaveAttribute( 'data-sidebar-state', diff --git a/apps/desktop/src/main/__tests__/app-shell-session-purge.test.ts b/apps/desktop/src/main/__tests__/app-shell-session-purge.test.ts index 2ed8dcfd37..8abe78c452 100644 --- a/apps/desktop/src/main/__tests__/app-shell-session-purge.test.ts +++ b/apps/desktop/src/main/__tests__/app-shell-session-purge.test.ts @@ -11,7 +11,7 @@ function summary(id: string, overrides: Partial = {}): SessionSu isArchived: true, labels: [], hasUnread: false, - status: 'done', + status: 'archived', backend: 'fake', llmConnectionSlug: 'test', connectionLocked: true, @@ -21,6 +21,16 @@ function summary(id: string, overrides: Partial = {}): SessionSu }; } +/** + * A task that left the archive. Both fields move together because that is what + * `SessionStore.unarchive` writes; flipping only `isArchived` would build a row + * the store cannot produce, and the sweep would then be tested against a state + * it will never meet. + */ +function restored(id: string): SessionSummary { + return summary(id, { isArchived: false, status: 'active' }); +} + type SweepHarness = { removed: string[]; cleared: string[]; @@ -128,7 +138,7 @@ describe('purgeSessions', () => { // dialog was up has left it, and a sweep that deleted it anyway would be // acting outside what was agreed to. const h = harness(); - const sessions = [summary('kept', { isArchived: false }), summary('doomed')]; + const sessions = [restored('kept'), summary('doomed')]; const restore = installWindow(h); const actions = createActions({ harness: h, sessions, activeIdRef: { current: undefined } }); @@ -148,7 +158,7 @@ describe('purgeSessions', () => { const sessions = [summary('first'), summary('second')]; const restore = installWindow(h, { onRemove: (id) => { - if (id === 'first') sessions[1] = summary('second', { isArchived: false }); + if (id === 'first') sessions[1] = restored('second'); }, }); const actions = createActions({ harness: h, sessions, activeIdRef: { current: undefined } }); diff --git a/apps/desktop/src/main/__tests__/app-shell-session-settings-actions.test.ts b/apps/desktop/src/main/__tests__/app-shell-session-settings-actions.test.ts index a26297bcf0..760d6a2ea8 100644 --- a/apps/desktop/src/main/__tests__/app-shell-session-settings-actions.test.ts +++ b/apps/desktop/src/main/__tests__/app-shell-session-settings-actions.test.ts @@ -167,7 +167,7 @@ describe('AppShell session settings actions', () => { assert.deepEqual(harness.successes, [ { - title: '已切换当前会话模型', + title: '已切换当前任务模型', description: 'claude-haiku → claude-opus', }, ]); diff --git a/apps/desktop/src/main/__tests__/bot-incoming-project-cwd.test.ts b/apps/desktop/src/main/__tests__/bot-incoming-project-cwd.test.ts index e0eef7ec61..d874c07fd5 100644 --- a/apps/desktop/src/main/__tests__/bot-incoming-project-cwd.test.ts +++ b/apps/desktop/src/main/__tests__/bot-incoming-project-cwd.test.ts @@ -42,7 +42,7 @@ describe('bot incoming new-session cwd', () => { } as unknown as BotIncomingMessage); assert.deepEqual(createInput, { - name: 'Telegram 对话', + name: 'Telegram 任务', labels: ['bot', 'telegram'], }); }); diff --git a/apps/desktop/src/main/__tests__/external-session-import-lifecycle.test.ts b/apps/desktop/src/main/__tests__/external-session-import-lifecycle.test.ts deleted file mode 100644 index e0d37d0261..0000000000 --- a/apps/desktop/src/main/__tests__/external-session-import-lifecycle.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { ExternalSessionImportLifecycle } from '../../renderer/external-session-import-lifecycle.js'; - -test('blocks dialog close while an import is active', () => { - const lifecycle = new ExternalSessionImportLifecycle(); - - assert.equal(lifecycle.canClose(), true); - const token = lifecycle.begin(); - assert.equal(lifecycle.canClose(), false); - assert.equal(lifecycle.isCurrent(token), true); - assert.equal(lifecycle.finish(token), true); - assert.equal(lifecycle.canClose(), true); -}); - -test('invalidates an old completion before a later dialog lifetime starts', () => { - const lifecycle = new ExternalSessionImportLifecycle(); - const oldToken = lifecycle.begin(); - - lifecycle.invalidate(); - const newToken = lifecycle.begin(); - - assert.equal(lifecycle.isCurrent(oldToken), false); - assert.equal(lifecycle.finish(oldToken), false); - assert.equal(lifecycle.isCurrent(newToken), true); - assert.equal(lifecycle.canClose(), false); -}); diff --git a/apps/desktop/src/main/__tests__/runtime-host-external-sessions-ipc-main.test.ts b/apps/desktop/src/main/__tests__/runtime-host-external-sessions-ipc-main.test.ts index 718c763d91..8baeb06b63 100644 --- a/apps/desktop/src/main/__tests__/runtime-host-external-sessions-ipc-main.test.ts +++ b/apps/desktop/src/main/__tests__/runtime-host-external-sessions-ipc-main.test.ts @@ -59,7 +59,7 @@ test('forwards bounded external Session requests and publishes imported Sessions assert.deepEqual(events, [{ reason: 'created', sessionId: 'imported-1' }]); }); -test('preserves commit uncertainty as a structured non-retryable IPC result', async () => { +test('an uncertain commit still asks the shell to re-read the catalog', async () => { const events: unknown[] = []; const ipc = ipcHarness(); registerRuntimeHostExternalSessionsIpc( @@ -85,7 +85,12 @@ test('preserves commit uncertainty as a structured non-retryable IPC result', as }), { ok: false, reason: 'commit_outcome_unknown' }, ); - assert.deepEqual(events, []); + // The task may be in the catalog, so the shell has to look. The import page's + // own banner cannot be the only trace: 导入任务 is a Settings page, and the + // moment the user leaves it the banner is unmounted -- which is exactly when + // they come back and import the same conversation again. No id, because not + // knowing which task landed is what `commit_outcome_unknown` means. + assert.deepEqual(events, [{ reason: 'created', sessionId: undefined }]); }); test('rejects malformed renderer requests before they reach the Host client', async () => { diff --git a/apps/desktop/src/main/__tests__/thread-search.test.ts b/apps/desktop/src/main/__tests__/thread-search.test.ts index 456055475d..c5ec7779ba 100644 --- a/apps/desktop/src/main/__tests__/thread-search.test.ts +++ b/apps/desktop/src/main/__tests__/thread-search.test.ts @@ -215,7 +215,7 @@ describe('runThreadSearch', () => { await runThreadSearch({ source: 'thread', query: 'roadmap', limit: 5 }, makeDeps(entries)), )[0]!; assert.deepEqual(titleHit.target, { kind: 'thread', sessionId: 's1' }); - assert.equal(titleHit.summary, '会话标题'); + assert.equal(titleHit.summary, '任务标题'); assert.equal(titleHit.url, undefined); assert.match(titleHit.snippet ?? '', /\[redacted\]/); assert.equal(titleHit.snippet?.includes('sk-ant-test-secret-token-12345'), false); diff --git a/apps/desktop/src/main/bot-incoming-main.ts b/apps/desktop/src/main/bot-incoming-main.ts index c038f7a61b..69a54feeea 100644 --- a/apps/desktop/src/main/bot-incoming-main.ts +++ b/apps/desktop/src/main/bot-incoming-main.ts @@ -183,7 +183,7 @@ export function createBotIncomingMainService(deps: BotIncomingMainServiceDeps): noticeTtlMs: number, ): Promise { if (botConversationSessions.size >= BOT_CONVERSATION_SESSION_LIMIT) { - await sendTransientBotNotice(message, 'Maka 当前机器人会话数量已达上限,请重置或清理旧会话后再试。', noticeTtlMs); + await sendTransientBotNotice(message, 'Maka 当前机器人任务数量已达上限,请重置或清理旧任务后再试。', noticeTtlMs); return undefined; } if (!consumeBotConversationToken(conversationKey)) { @@ -191,7 +191,7 @@ export function createBotIncomingMainService(deps: BotIncomingMainServiceDeps): return undefined; } const sessionId = await deps.sessions.createSession({ - name: `${botDisplayLabel(message.platform)} 对话`, + name: `${botDisplayLabel(message.platform)} 任务`, labels: ['bot', message.platform], }); botConversationSessions.set(conversationKey, sessionId); @@ -240,8 +240,8 @@ export function createBotIncomingMainService(deps: BotIncomingMainServiceDeps): ephemeralTtlMs: SYSTEM_NOTICE_TTL_MS, }; const ack = had - ? '会话已重置,下一条消息会开新对话。' - : '当前没有进行中的对话;下一条消息会开新对话。'; + ? '任务已重置,下一条消息会开新任务。' + : '当前没有进行中的任务;下一条消息会开新任务。'; await deps.botRegistry.sendMessage(message.platform, message.chatId, ack, replyOptions).catch(() => null); return; } @@ -396,7 +396,7 @@ export function createBotIncomingMainService(deps: BotIncomingMainServiceDeps): if ((await deps.sessions.prepareSession(sessionId)) === 'ready') return true; await sendTransientBotNotice( message, - 'Maka 已拒绝这条机器人消息:绑定会话当前不是只读探索模式,请先在桌面端切回 explore 后再试。', + 'Maka 已拒绝这条机器人消息:绑定任务当前不是只读探索模式,请先在桌面端切回 explore 后再试。', noticeTtlMs, ); return false; diff --git a/apps/desktop/src/main/capability-snapshot.ts b/apps/desktop/src/main/capability-snapshot.ts index 9c0c94bfe5..4acf8748f6 100644 --- a/apps/desktop/src/main/capability-snapshot.ts +++ b/apps/desktop/src/main/capability-snapshot.ts @@ -61,7 +61,7 @@ export function buildCapabilitySnapshotCollection(input: { feature: { state: 'partial', source: 'runtime', - reason: 'Daily Review 已聚合本地会话 / 工具 / 模型活动;当前不包含屏幕与应用级录制', + reason: 'Daily Review 已聚合本地任务 / 工具 / 模型活动;当前不包含屏幕与应用级录制', }, requiredPermissions: [ { id: 'screen_recording', required: false, status: permissions.screen_recording.status }, diff --git a/apps/desktop/src/main/chat-readiness.ts b/apps/desktop/src/main/chat-readiness.ts index 28174f87c4..1aa1616225 100644 --- a/apps/desktop/src/main/chat-readiness.ts +++ b/apps/desktop/src/main/chat-readiness.ts @@ -106,6 +106,16 @@ export async function requireReadyConnection( * so the copy stays close to its existing semantics (PR110a refactor * is behavior-preserving — only the judgment moved to core). */ +/** + * Two paths reach `fake_backend`: the reason table below, and the header check + * in `assertSessionCanSend`, which never gets far enough to look a connection + * up. They are the same sentence to the user, so they are the same string here + * — the rename that moved 会话 to 任务 had to be applied twice, which is what a + * second copy costs. + */ +const FAKE_BACKEND_MESSAGE = + '当前任务来自旧的本地模拟连接,不能直接发送。请到 设置 · 模型 添加真实模型后新建任务。'; + function messageForReason( reason: ChatConfigurationReason, connection: LlmConnection, @@ -132,7 +142,7 @@ function messageForReason( return `模型 "${model}" 不能用于聊天。请到 设置 · 模型 选择支持聊天的模型。`; } case 'fake_backend': - return '当前会话来自旧的本地模拟连接,不能直接发送。请到 设置 · 模型 添加真实模型后新建会话。'; + return FAKE_BACKEND_MESSAGE; case 'missing_default_connection': case 'connection_missing': // These reasons are handled before we reach isConnectionReady, @@ -146,10 +156,7 @@ export async function assertSessionCanSend( deps: ReadyConnectionDeps, ): Promise { if (header.backend === 'fake') { - throw chatConfigurationError( - '当前会话来自旧的本地模拟连接,不能直接发送。请到 设置 · 模型 添加真实模型后新建会话。', - 'fake_backend', - ); + throw chatConfigurationError(FAKE_BACKEND_MESSAGE, 'fake_backend'); } await requireReadyConnection(header.llmConnectionSlug, deps, header.model); } diff --git a/apps/desktop/src/main/computer-use/status-item.ts b/apps/desktop/src/main/computer-use/status-item.ts index 94608adb9c..7a603953cf 100644 --- a/apps/desktop/src/main/computer-use/status-item.ts +++ b/apps/desktop/src/main/computer-use/status-item.ts @@ -114,7 +114,7 @@ const COPY: UiCatalog = { zh: { stopUsing: (appName) => `停止操作 ${appName}`, stopUnnamed: '停止 Computer Use', - empty: '没有正在进行的会话', + empty: '没有正在进行的任务', }, en: { stopUsing: (appName) => `Stop Using ${appName}`, diff --git a/apps/desktop/src/main/e2e-fixture/scenarios-sessions.ts b/apps/desktop/src/main/e2e-fixture/scenarios-sessions.ts index 539e194a66..9045aea86b 100644 --- a/apps/desktop/src/main/e2e-fixture/scenarios-sessions.ts +++ b/apps/desktop/src/main/e2e-fixture/scenarios-sessions.ts @@ -17,7 +17,7 @@ export function longSidebarSessions( return { header: header({ id: sessionId, - name: `会话 ${suffix}`, + name: `任务 ${suffix}`, connection: 'zai-live', model: 'glm-5.1', now, diff --git a/apps/desktop/src/main/e2e-fixture/scenarios-settings.ts b/apps/desktop/src/main/e2e-fixture/scenarios-settings.ts index fc1fd97f29..fc2d855667 100644 --- a/apps/desktop/src/main/e2e-fixture/scenarios-settings.ts +++ b/apps/desktop/src/main/e2e-fixture/scenarios-settings.ts @@ -183,7 +183,7 @@ export async function writeScheduledTasks(workspaceRoot: string, now: number): P for (const [index, title] of [ '每日站会前汇总阻塞项', '每月依赖许可证审计', - '季度收尾清点未归档会话', + '季度收尾清点未归档任务', '发布前跑一轮回归', ].entries()) { await create( diff --git a/apps/desktop/src/main/notifications-policy.ts b/apps/desktop/src/main/notifications-policy.ts index 72995d59e2..87a8d88924 100644 --- a/apps/desktop/src/main/notifications-policy.ts +++ b/apps/desktop/src/main/notifications-policy.ts @@ -58,7 +58,7 @@ export interface RunNotificationCopy { */ function runNotificationCopy(kind: RunNotificationKind): RunNotificationCopy { if (kind === 'errored') { - return { title: '对话出错', body: '本轮回答未能完成,点击查看详情。' }; + return { title: '任务出错', body: '本轮回答未能完成,点击查看详情。' }; } return { title: '回答已生成', body: 'Maka 已完成本轮回答,点击查看。' }; } diff --git a/apps/desktop/src/main/runtime-host-external-sessions-ipc-main.ts b/apps/desktop/src/main/runtime-host-external-sessions-ipc-main.ts index 24fd9a4d32..c74c19bd7b 100644 --- a/apps/desktop/src/main/runtime-host-external-sessions-ipc-main.ts +++ b/apps/desktop/src/main/runtime-host-external-sessions-ipc-main.ts @@ -68,6 +68,13 @@ export function registerRuntimeHostExternalSessionsIpc( error.operation === 'external-session.import' && error.code === 'commit_outcome_unknown' ) { + // "Unknown" means the task may well be in the catalog, so tell the + // shell to read it again. Without this, the only trace of a maybe- + // committed import is the banner on the page, and the page is gone the + // moment the user leaves Settings -- which is exactly when they come + // back and import the same conversation a second time. No id: the + // whole point is that we do not know which task, if any, landed. + deps.emitSessionsChanged('created'); return { ok: false, reason: 'commit_outcome_unknown', diff --git a/apps/desktop/src/main/search/thread-search.ts b/apps/desktop/src/main/search/thread-search.ts index b9dcc31ecf..c8b4e39fd5 100644 --- a/apps/desktop/src/main/search/thread-search.ts +++ b/apps/desktop/src/main/search/thread-search.ts @@ -202,7 +202,7 @@ export async function runThreadSearch( results.push({ source: THREAD_SOURCE, title: session.name, - summary: '会话标题', + summary: '任务标题', snippet, target: { kind: 'thread', diff --git a/apps/desktop/src/preload/preload.ts b/apps/desktop/src/preload/preload.ts index 8c95941771..4b1bf2ae40 100644 --- a/apps/desktop/src/preload/preload.ts +++ b/apps/desktop/src/preload/preload.ts @@ -421,7 +421,7 @@ function executeWebSearchQuery(input: { return Promise.resolve({ ok: false, reason: 'unsupported_provider', - message: '原生联网搜索由对话中的主模型请求执行,不支持从设置页单独调用。', + message: '原生联网搜索由任务中的主模型请求执行,不支持从设置页单独调用。', }); } const query = normalizeWebSearchQuery(input.query); @@ -448,7 +448,7 @@ function executeWebSearchTest(input: { return Promise.resolve({ ok: false, reason: 'unsupported_provider', - message: '原生联网搜索由对话中的主模型请求执行,不需要单独测试搜索凭据。', + message: '原生联网搜索由任务中的主模型请求执行,不需要单独测试搜索凭据。', }); } const apiKey = webSearchCredentialOverride(input.apiKey); diff --git a/apps/desktop/src/renderer/agent-graph-panel.tsx b/apps/desktop/src/renderer/agent-graph-panel.tsx index 58a4026a91..d6d0f61227 100644 --- a/apps/desktop/src/renderer/agent-graph-panel.tsx +++ b/apps/desktop/src/renderer/agent-graph-panel.tsx @@ -53,7 +53,7 @@ export function getAgentGraphPanelCopy(locale: UiLocale): GraphPanelCopy { stopping: '停止中…', stopFailed: '停止 Graph 失败,请重试。', loadFailed: 'Graph 状态刷新失败。', - openSession: '打开子会话', + openSession: '打开子任务', operators: 'Operators', selectedResults: '已选择结果', noOperators: '等待主 Agent 创建 operator…', @@ -96,7 +96,7 @@ export function getAgentGraphPanelCopy(locale: UiLocale): GraphPanelCopy { stopping: 'Stopping…', stopFailed: 'Could not stop the graph. Try again.', loadFailed: 'Could not refresh graph state.', - openSession: 'Open child session', + openSession: 'Open child task', operators: 'Operators', selectedResults: 'Selected results', noOperators: 'Waiting for the main agent to create an operator…', diff --git a/apps/desktop/src/renderer/app-shell-chat-actions.ts b/apps/desktop/src/renderer/app-shell-chat-actions.ts index 06dc12950c..83c35ef9a7 100644 --- a/apps/desktop/src/renderer/app-shell-chat-actions.ts +++ b/apps/desktop/src/renderer/app-shell-chat-actions.ts @@ -386,7 +386,7 @@ export function createAppShellChatActions(deps: { showSkillInvocationFeedback(uiLocale, toastApi, sendResult.skillInvocation); } if (newChatOwner && isNewChatSendSurfaceActive(newChatOwner)) { - setNavSelection({ section: 'sessions', filter: 'chats' }); + setNavSelection({ section: 'sessions' }); setActiveId(session.id); showOptimisticUserMessage( session.id, diff --git a/apps/desktop/src/renderer/app-shell-e2e-fixture.ts b/apps/desktop/src/renderer/app-shell-e2e-fixture.ts index 1d65d6b556..f21784c742 100644 --- a/apps/desktop/src/renderer/app-shell-e2e-fixture.ts +++ b/apps/desktop/src/renderer/app-shell-e2e-fixture.ts @@ -134,7 +134,7 @@ export function createAppShellE2eFixtureActions(options: { } else if (state.sidebarSection === 'daily-review') { setNavSelection({ section: 'automations', module: 'daily-review' }); } else if (state.sidebarSection === 'sessions') { - setNavSelection({ section: 'sessions', filter: 'chats' }); + setNavSelection({ section: 'sessions' }); } } diff --git a/apps/desktop/src/renderer/app-shell-overlays.tsx b/apps/desktop/src/renderer/app-shell-overlays.tsx index 37ebbdf5f7..8f6a549a8b 100644 --- a/apps/desktop/src/renderer/app-shell-overlays.tsx +++ b/apps/desktop/src/renderer/app-shell-overlays.tsx @@ -11,7 +11,6 @@ import { useAppShellCommands, type AppShellCommandListOptions } from './app-shel import type { ArchivedTasksBridge } from './settings/tasks-settings-page'; import type { UiLocaleUpdateGate } from './settings/ui-locale-update-gate'; import { getShellRemainingCopy } from './locales/shell-remaining-copy.js'; -import { ExternalSessionImportDialog } from './external-session-import-dialog.js'; const SettingsModal = lazy(() => import('./settings/settings-modal').then((m) => ({ default: m.SettingsModal }))); @@ -65,8 +64,6 @@ export function AppShellOverlays(props: { paletteOpen: boolean; closePalette(): void; commandOptions: AppShellCommandListOptions; - externalImportOpen: boolean; - onExternalImportOpenChange(open: boolean): void; onExternalSessionImported(session: SessionSummary): void; }) { const { @@ -96,8 +93,6 @@ export function AppShellOverlays(props: { setDefaultPermissionMode, themePalette, themePref, - externalImportOpen, - onExternalImportOpenChange, onExternalSessionImported, } = props; @@ -129,6 +124,7 @@ export function AppShellOverlays(props: { onOpenKeyboardHelp={props.onOpenKeyboardHelp} onOpenSession={props.onOpenSettingsSession} archivedTasks={props.archivedTasks} + onTaskImported={onExternalSessionImported} /> )} @@ -153,11 +149,6 @@ export function AppShellOverlays(props: { }} commands={commands} /> - ); } diff --git a/apps/desktop/src/renderer/app-shell.tsx b/apps/desktop/src/renderer/app-shell.tsx index d71c178d2b..491fa2e6b7 100644 --- a/apps/desktop/src/renderer/app-shell.tsx +++ b/apps/desktop/src/renderer/app-shell.tsx @@ -121,7 +121,7 @@ import { useAppShellTurnPresentation } from './app-shell-turn-view-model'; import { readScrollMotionBehavior } from './scroll-motion-policy'; import { deriveBranchBanner } from './branch-banner'; import { readNavigationState, selectNavigation } from './nav-selection'; -import { sessionMatchesNavSelection } from './session-nav-filter'; +import { sessionMatchesRail } from './session-nav-filter'; import { deriveSessionRevisionNavigation } from './session-revisions'; import { deriveDesktopExecutionBoundarySurface } from './desktop-execution-boundary-surface'; import { useActiveExecutionBoundary } from './use-active-execution-boundary'; @@ -595,7 +595,6 @@ function AppShellContent({ const persistedComposerDefaults = loadComposerDefaults(); const [helpOpen, closeHelp, openHelp] = useKeyboardHelp(); const [paletteOpen, openPalette, closePalette] = useCommandPalette(); - const [externalImportOpen, setExternalImportOpen] = useState(false); const [viewMode, setViewMode] = useState('conversation'); const composerRef = useRef(null); // The rail's toggle has to reach Astryx's resizable state, not just this @@ -699,11 +698,9 @@ function AppShellContent({ } = useMemo( () => deriveSessionRail(sessions, activeId, (session) => - !hiddenCompanionForkIds.has(session.id) - ? sessionMatchesNavSelection(session, navSelection) - : false, + !hiddenCompanionForkIds.has(session.id) && sessionMatchesRail(session), ), - [sessions, activeId, navSelection, hiddenCompanionForkIds], + [sessions, activeId, hiddenCompanionForkIds], ); // PR-DAILY-REVIEW-MVP-0: bridge for the main Daily Review module. // Memoized so the panel's `useEffect` cleanup keys @@ -1089,7 +1086,7 @@ function AppShellContent({ } function openSessionInChat(sessionId: string, turnId?: string, sequence?: number): void { - setNavSelection({ section: 'sessions', filter: 'chats' }); + setNavSelection({ section: 'sessions' }); setActiveId(sessionId); if (turnId) { setSearchScrollTarget({ sessionId, turnId, sequence, nonce: Date.now() }); @@ -1127,7 +1124,7 @@ function AppShellContent({ * path is unchanged while a half-written message is no longer clobbered. */ const useSkillInChat = useCallback( (_skillId: string, skillName: string) => { - setNavSelection({ section: 'sessions', filter: 'chats' }); + setNavSelection({ section: 'sessions' }); const seed = () => { composerRef.current?.appendText(shellCopy.useSkillPrompt(skillName)); composerRef.current?.focus(); @@ -2240,7 +2237,7 @@ function AppShellContent({ return () => window.clearTimeout(timer); }, [activeId, activeStreamingMessageId, messages, settleAssistantStreaming]); - const hasModalOpen = helpOpen || paletteOpen || searchModalOpen || externalImportOpen; + const hasModalOpen = helpOpen || paletteOpen || searchModalOpen; const shellObscured = hasModalOpen || settingsOpen; useEffect(() => { @@ -2494,7 +2491,7 @@ function AppShellContent({ function openNewTaskSurface() { startNewSession(); setNewChatPlanModeActive(false); - setNavSelection({ section: 'sessions', filter: 'chats' }); + setNavSelection({ section: 'sessions' }); setSearchScrollTarget(null); // New-task affordances reset to the empty-state composer; move focus // there so the user can start typing immediately. @@ -2806,7 +2803,6 @@ function AppShellContent({ updateReminder={updateReminder} onOpenUpdate={openUpdateDownload} onNew={createSession} - onImport={() => setExternalImportOpen(true)} rowActions={sessionRowActions} projectActions={projectRowActions} /> @@ -3380,10 +3376,14 @@ function AppShellContent({ paletteOpen={paletteOpen} closePalette={closePalette} commandOptions={commandOptions} - externalImportOpen={externalImportOpen} - onExternalImportOpenChange={setExternalImportOpen} + /* Seeding is for the navigation, not for correctness: the import IPC + already emits `sessions:changed`, so the task reaches the rail on its + own even if the user closes Settings mid-import. Seeding it here just + means `openSessionInChat` has something to open without waiting for + the refresh to land. */ onExternalSessionImported={(session) => { upsertSessionSummary(session); + closeSettings(); openSessionInChat(session.id); }} /> diff --git a/apps/desktop/src/renderer/command-palette-commands.ts b/apps/desktop/src/renderer/command-palette-commands.ts index 6adf258327..18312694da 100644 --- a/apps/desktop/src/renderer/command-palette-commands.ts +++ b/apps/desktop/src/renderer/command-palette-commands.ts @@ -234,7 +234,7 @@ export function buildCommandList(args: { ...staticCopy('nav:sessions'), Icon: MessageSquare, keywords: [...copy.staticKeywords['nav:sessions']], - run: () => select({ section: 'sessions', filter: 'chats' }), + run: () => select({ section: 'sessions' }), }); cmds.push({ id: 'nav:automations', diff --git a/apps/desktop/src/renderer/external-session-import-dialog.tsx b/apps/desktop/src/renderer/external-session-import-dialog.tsx deleted file mode 100644 index 073fd02781..0000000000 --- a/apps/desktop/src/renderer/external-session-import-dialog.tsx +++ /dev/null @@ -1,398 +0,0 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { Banner } from '@astryxdesign/core/Banner'; -import { Button } from '@astryxdesign/core/Button'; -import { CheckboxInput } from '@astryxdesign/core/CheckboxInput'; -import { Dialog, DialogHeader } from '@astryxdesign/core/Dialog'; -import { EmptyState } from '@astryxdesign/core/EmptyState'; -import { Item } from '@astryxdesign/core/Item'; -import { Layout, LayoutContent, LayoutFooter } from '@astryxdesign/core/Layout'; -import { SegmentedControl, SegmentedControlItem } from '@astryxdesign/core/SegmentedControl'; -import { HStack, VStack } from '@astryxdesign/core/Stack'; -import { Text } from '@astryxdesign/core/Text'; -import { uiLocaleToIntlLocale } from '@maka/core/ui-locale'; -import { type ExternalSessionSummary } from '@maka/core/external-session'; -import { type SessionSummary } from '@maka/core/session'; -import { Spinner, useUiLocale } from '@maka/ui'; -import { ICON_SIZE, Upload } from '@maka/ui/icons'; -import { getExternalSessionImportCopy } from './locales/external-session-import-copy.js'; -import { localizedShellErrorMessage } from './locales/shell-copy.js'; -import { ExternalSessionImportLifecycle } from './external-session-import-lifecycle.js'; - -type CatalogState = { - sessions: ExternalSessionSummary[]; - nextCursor: string | null; -}; - -const EMPTY_CATALOG: CatalogState = { sessions: [], nextCursor: null }; - -export function ExternalSessionImportDialog(props: { - isOpen: boolean; - onOpenChange(open: boolean): void; - onImported(session: SessionSummary): void; -}) { - const locale = useUiLocale(); - const copy = getExternalSessionImportCopy(locale); - const [adapterIds, setAdapterIds] = useState([]); - const [adapterId, setAdapterId] = useState(null); - const [includeArchived, setIncludeArchived] = useState(false); - const [catalog, setCatalog] = useState(EMPTY_CATALOG); - const [selectedId, setSelectedId] = useState(null); - const [sourceLoading, setSourceLoading] = useState(false); - const [sourceResolved, setSourceResolved] = useState(false); - const [catalogLoading, setCatalogLoading] = useState(false); - const [loadingMore, setLoadingMore] = useState(false); - const [importing, setImporting] = useState(false); - const [sourceError, setSourceError] = useState(null); - const [catalogError, setCatalogError] = useState(null); - const [importError, setImportError] = useState(null); - const [uncertainSourceId, setUncertainSourceId] = useState(null); - const requestGeneration = useRef(0); - const importLifecycle = useRef(new ExternalSessionImportLifecycle()); - const isOpenRef = useRef(props.isOpen); - isOpenRef.current = props.isOpen; - - const loadSources = useCallback(async () => { - const generation = ++requestGeneration.current; - setSourceLoading(true); - setSourceResolved(false); - setSourceError(null); - setCatalogError(null); - setImportError(null); - setAdapterIds([]); - setAdapterId(null); - setCatalog(EMPTY_CATALOG); - setSelectedId(null); - try { - const result = await window.maka.externalSessions.listSources(); - if (generation !== requestGeneration.current) return; - setAdapterIds(result.adapterIds); - setAdapterId(result.adapterIds[0] ?? null); - } catch (error) { - if (generation !== requestGeneration.current) return; - setSourceError(localizedShellErrorMessage(error, copy.loadFailedFallback, locale)); - } finally { - if (generation === requestGeneration.current) { - setSourceLoading(false); - setSourceResolved(true); - } - } - }, [copy.loadFailedFallback, locale]); - - const loadCatalog = useCallback( - async (sourceId: string, cursor?: string) => { - const generation = ++requestGeneration.current; - const append = cursor !== undefined; - if (append) setLoadingMore(true); - else { - setCatalogLoading(true); - setCatalog(EMPTY_CATALOG); - setSelectedId(null); - } - setCatalogError(null); - setImportError(null); - try { - const result = await window.maka.externalSessions.list({ - adapterId: sourceId, - includeArchived, - ...(cursor === undefined ? {} : { cursor }), - }); - if (generation !== requestGeneration.current) return; - setCatalog((current) => ({ - sessions: append ? [...current.sessions, ...result.sessions] : result.sessions, - nextCursor: result.nextCursor, - })); - } catch (error) { - if (generation !== requestGeneration.current) return; - setCatalogError(localizedShellErrorMessage(error, copy.loadFailedFallback, locale)); - } finally { - if (generation === requestGeneration.current) { - setCatalogLoading(false); - setLoadingMore(false); - } - } - }, - [copy.loadFailedFallback, includeArchived, locale], - ); - - useEffect(() => { - if (!props.isOpen) { - requestGeneration.current += 1; - importLifecycle.current.invalidate(); - setSourceResolved(false); - setSourceLoading(false); - setCatalogLoading(false); - setLoadingMore(false); - setImporting(false); - setAdapterIds([]); - setAdapterId(null); - setCatalog(EMPTY_CATALOG); - setSelectedId(null); - setSourceError(null); - setCatalogError(null); - setImportError(null); - setUncertainSourceId(null); - return; - } - void loadSources(); - }, [loadSources, props.isOpen]); - - useEffect(() => { - if (!props.isOpen || adapterId === null) return; - void loadCatalog(adapterId); - }, [adapterId, includeArchived, loadCatalog, props.isOpen]); - - useEffect( - () => () => { - importLifecycle.current.invalidate(); - }, - [], - ); - - const dateFormatter = useMemo( - () => - new Intl.DateTimeFormat(uiLocaleToIntlLocale(locale), { - dateStyle: 'medium', - timeStyle: 'short', - }), - [locale], - ); - - const importSelected = useCallback(async () => { - if ( - adapterId === null || - selectedId === null || - importing || - !importLifecycle.current.canClose() - ) { - return; - } - const requestToken = importLifecycle.current.begin(); - setImporting(true); - setImportError(null); - try { - const outcome = await window.maka.externalSessions.import({ - adapterId, - sourceSessionId: selectedId, - }); - if (!isOpenRef.current || !importLifecycle.current.isCurrent(requestToken)) return; - if (!outcome.ok) { - setUncertainSourceId(selectedId); - return; - } - props.onImported(outcome.session); - props.onOpenChange(false); - } catch (error) { - if (!isOpenRef.current || !importLifecycle.current.isCurrent(requestToken)) return; - setImportError(localizedShellErrorMessage(error, copy.importFailedFallback, locale)); - } finally { - if (importLifecycle.current.finish(requestToken)) setImporting(false); - } - }, [adapterId, copy.importFailedFallback, importing, locale, props, selectedId]); - - const handleOpenChange = useCallback( - (open: boolean) => { - if (!open && !importLifecycle.current.canClose()) return; - props.onOpenChange(open); - }, - [props.onOpenChange], - ); - - const noSource = sourceResolved && !sourceLoading && !sourceError && adapterIds.length === 0; - const catalogEmpty = - adapterId !== null && !catalogLoading && !catalogError && catalog.sessions.length === 0; - - return ( - - - ); -} - -function sourceLabel(adapterId: string, codexLabel: string): string { - return adapterId === 'codex' ? codexLabel : adapterId; -} diff --git a/apps/desktop/src/renderer/external-session-import-lifecycle.ts b/apps/desktop/src/renderer/external-session-import-lifecycle.ts deleted file mode 100644 index d507e613ce..0000000000 --- a/apps/desktop/src/renderer/external-session-import-lifecycle.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Keeps an in-flight import attached to exactly one dialog lifetime. - * - * UI close requests are blocked while active. An external unmount/close can - * still invalidate the token, ensuring the old completion cannot navigate or - * close a later dialog instance. - */ -export class ExternalSessionImportLifecycle { - #generation = 0; - #activeToken: number | null = null; - - begin(): number { - if (this.#activeToken !== null) throw new Error('External Session import is already active'); - const token = ++this.#generation; - this.#activeToken = token; - return token; - } - - canClose(): boolean { - return this.#activeToken === null; - } - - isCurrent(token: number): boolean { - return this.#activeToken === token; - } - - finish(token: number): boolean { - if (!this.isCurrent(token)) return false; - this.#activeToken = null; - return true; - } - - invalidate(): void { - this.#generation += 1; - this.#activeToken = null; - } -} diff --git a/apps/desktop/src/renderer/locales/conversation-copy.ts b/apps/desktop/src/renderer/locales/conversation-copy.ts index 3e9ed6caf9..33e28af1fe 100644 --- a/apps/desktop/src/renderer/locales/conversation-copy.ts +++ b/apps/desktop/src/renderer/locales/conversation-copy.ts @@ -315,8 +315,8 @@ const ZH_CALL_KIND: CallKindCopy = { semantic_compact: '语义压缩', history_compact: '历史压缩', goal_evaluation: '目标评估', - session_title: '生成会话标题', - session_recap: '会话回顾', + session_title: '生成任务标题', + session_recap: '任务回顾', daily_review: '每日回顾', }; @@ -325,8 +325,8 @@ const EN_CALL_KIND: CallKindCopy = { semantic_compact: 'Semantic compaction', history_compact: 'History compaction', goal_evaluation: 'Goal evaluation', - session_title: 'Session title', - session_recap: 'Session recap', + session_title: 'Task title', + session_recap: 'Task recap', daily_review: 'Daily review', }; @@ -367,7 +367,7 @@ function enDetail(parts: readonly string[]): string { const COPY = { zh: { - actions: { stopFailedTitle: '停止失败', stopFailedFallback: '会话操作失败,请稍后重试。', refreshSessionsFailedTitle: '刷新会话列表失败', refreshSessionsFailedFallback: '刷新会话列表失败,请稍后重试。', conversationErrorTitle: '对话出错', conversationErrorFallback: '对话运行失败,请稍后重试。', regenerateStartedTitle: '已发起重新生成', regenerateStartedDescription: '正在生成新的一轮回答', branchCreatedTitle: '已创建分支', branchCreatedDescription: (name) => `新会话 ${name}`, revisionStartedTitle: '已创建修改版草稿', revisionStartedDescription: '原对话仍会保留;修改后发送将在新版本中继续', revisionReadyTitle: '可以修改并重发了', revisionReadyDescription: '已回到该消息之前;编辑后发送即可', revisionUnavailableTitle: '暂时无法编辑这条消息', revisionAttachmentsUnsupported: '包含附件的历史消息暂不支持编辑并重发,请复制文字后新建消息。', revisionTransformedTextUnsupported: '通过显式技能发送的历史消息暂不支持编辑并重发,请复制文字后重新选择技能。', revisionDraftAttachmentConflict: 'Composer 中已有待发送附件,请先发送或移除附件,再编辑历史消息。', revisionCommandUnsupported: '修改消息时不能执行 /compact、/side 或编排命令,请取消修改后再试。', revisionAlreadyActive: '已有一条消息正在修改,请先发送或取消当前修改。', revisionCancelLabel: '取消', revisionBannerTitle: '正在修改已发送消息', revisionBannerDetail: '· 发送后创建新版本', revisionUnchanged: '内容没有变化。如需重新回答,请使用“重新生成”。', operationFailedTitle: '操作失败', operationFailedFallback: '对话操作失败,请稍后重试。', attachmentFailedTitle: '添加附件失败', tryAgain: '请稍后重试。', modelReboundTitle: '已切换到可用模型', modelReboundDescription: (modelId) => `原会话使用的连接已不可用${modelId ? ` · ${modelId}` : ''}`, messageReadFailedTitle: '读取对话失败', returnLatest: '返回最新消息' }, + actions: { stopFailedTitle: '停止失败', stopFailedFallback: '任务操作失败,请稍后重试。', refreshSessionsFailedTitle: '刷新任务列表失败', refreshSessionsFailedFallback: '刷新任务列表失败,请稍后重试。', conversationErrorTitle: '任务出错', conversationErrorFallback: '任务运行失败,请稍后重试。', regenerateStartedTitle: '已发起重新生成', regenerateStartedDescription: '正在生成新的一轮回答', branchCreatedTitle: '已创建分支', branchCreatedDescription: (name) => `新任务 ${name}`, revisionStartedTitle: '已创建修改版草稿', revisionStartedDescription: '原任务仍会保留;修改后发送将在新版本中继续', revisionReadyTitle: '可以修改并重发了', revisionReadyDescription: '已回到该消息之前;编辑后发送即可', revisionUnavailableTitle: '暂时无法编辑这条消息', revisionAttachmentsUnsupported: '包含附件的历史消息暂不支持编辑并重发,请复制文字后新建消息。', revisionTransformedTextUnsupported: '通过显式技能发送的历史消息暂不支持编辑并重发,请复制文字后重新选择技能。', revisionDraftAttachmentConflict: 'Composer 中已有待发送附件,请先发送或移除附件,再编辑历史消息。', revisionCommandUnsupported: '修改消息时不能执行 /compact、/side 或编排命令,请取消修改后再试。', revisionAlreadyActive: '已有一条消息正在修改,请先发送或取消当前修改。', revisionCancelLabel: '取消', revisionBannerTitle: '正在修改已发送消息', revisionBannerDetail: '· 发送后创建新版本', revisionUnchanged: '内容没有变化。如需重新回答,请使用“重新生成”。', operationFailedTitle: '操作失败', operationFailedFallback: '任务操作失败,请稍后重试。', attachmentFailedTitle: '添加附件失败', tryAgain: '请稍后重试。', modelReboundTitle: '已切换到可用模型', modelReboundDescription: (modelId) => `原任务使用的连接已不可用${modelId ? ` · ${modelId}` : ''}`, messageReadFailedTitle: '读取任务失败', returnLatest: '返回最新消息' }, attachments: { tooMany: '附件数量超过 8 个', tooLarge: '附件大小超过 50MB', duplicate: '附件来源重复,请勿重复添加同一文件。' }, model: { fakeBackendLabel: '本地模拟连接', @@ -376,25 +376,25 @@ const COPY = { configurationFallback: '模型连接暂时无法用于发送,请到 设置 · 模型 检查后重试。', configurationReason: { missing_default_connection: '等待配置默认模型。请到 设置 · 模型 添加一个可用模型连接后再发送。', - connection_missing: '该会话依赖的模型连接已删除,请到 设置 · 模型 重新选择或重建连接。', + connection_missing: '该任务依赖的模型连接已删除,请到 设置 · 模型 重新选择或重建连接。', connection_disabled: '当前模型连接已禁用。请到 设置 · 模型 启用或选择其他默认模型。', missing_api_key: '当前模型连接还没有可用凭据。请到 设置 · 模型 补齐 API key 或重新登录后再发送。', missing_model: '当前模型连接还没有可用模型。请到 设置 · 模型 选择默认模型后再发送。', empty_model_list: '当前模型连接没有启用模型。请到 设置 · 模型 添加或启用模型后再发送。', - model_not_enabled: '当前会话选择的模型未启用。请到 设置 · 模型 重新选择可用模型后再发送。', - model_not_chat_capable: '当前会话选择的模型不能用于聊天。请到 设置 · 模型 重新选择支持聊天的模型后再发送。', - fake_backend: '当前会话来自旧的本地模拟连接。请到 设置 · 模型 添加真实模型后新建会话。', + model_not_enabled: '当前任务选择的模型未启用。请到 设置 · 模型 重新选择可用模型后再发送。', + model_not_chat_capable: '当前任务选择的模型不能用于聊天。请到 设置 · 模型 重新选择支持聊天的模型后再发送。', + fake_backend: '当前任务来自旧的本地模拟连接。请到 设置 · 模型 添加真实模型后新建任务。', }, }, - footer: { labels: { regenerate: '重新生成', branch: '分支', copy: '复制', info: '详情' }, pending: '正在处理…', regenerateRunning: '当前回答仍在进行中,结束后再重新生成', regenerateAgain: '已重新生成过,再次点击将创建新的并行回答', regenerate: '让模型重新生成本轮回答', branchRunning: '当前回答仍在进行中,结束后再分支', branchAborted: '从中断前的上下文分支出新对话', branch: '基于此回答的上下文分支出新对话', copy: '复制回答到剪贴板', copyEmpty: '此回答尚无可复制的内容' }, + footer: { labels: { regenerate: '重新生成', branch: '分支', copy: '复制', info: '详情' }, pending: '正在处理…', regenerateRunning: '当前回答仍在进行中,结束后再重新生成', regenerateAgain: '已重新生成过,再次点击将创建新的并行回答', regenerate: '让模型重新生成本轮回答', branchRunning: '当前回答仍在进行中,结束后再分支', branchAborted: '从中断前的上下文分支出新任务', branch: '基于此回答的上下文分支出新任务', copy: '复制回答到剪贴板', copyEmpty: '此回答尚无可复制的内容' }, lineage: { regeneratedFrom: '重新生成自旧回答', regeneratedFromTooltip: '这是重新生成的并行回答,点击查看被保留的旧回答', regeneratedTo: '已重新生成 → 新回答', regeneratedToTooltip: '点击跳转到重新生成的新回答' }, workbar: { - ariaLabel: '会话工作栏', - sectionsAriaLabel: '会话工作栏标签', + ariaLabel: '任务工作栏', + sectionsAriaLabel: '任务工作栏标签', review: '变更', terminal: '终端', terminalNumbered: (index) => `终端 ${index}`, - tasks: '任务', + tasks: '待办', browser: '浏览器', files: '生成文件', inspector: '追踪', @@ -415,11 +415,11 @@ const COPY = { closeToRight: '关闭右侧标签', launcher: { review: '查看当前 Git 工作区变化', - terminal: '查看当前会话的终端运行和实时输出', - tasks: '查看和维护当前会话的任务台账', + terminal: '查看当前任务的终端运行和实时输出', + tasks: '查看和维护这个任务的待办台账', browser: '打开内置浏览器并保留当前页面', - files: '浏览当前会话生成的文件', - inspector: '检查会话调用、工具与耗时记录', + files: '浏览当前任务生成的文件', + inspector: '检查任务调用、工具与耗时记录', sideChat: '在不打断主任务的情况下追问和只读探索', }, }, @@ -427,8 +427,8 @@ const COPY = { ariaLabel: 'Git 变更', empty: '当前 Git 工作区没有变化', emptyHelp: '提交、暂存或修改文件后,变化会显示在这里。', - notGitRepository: '当前会话目录不是 Git 仓库', - workspaceUnavailable: '当前会话目录已不可用', + notGitRepository: '当前任务目录不是 Git 仓库', + workspaceUnavailable: '当前任务目录已不可用', unbornRepository: 'Git 仓库还没有可比较的提交', gitFailed: '无法读取 Git 工作区变化', invalidBaseBranch: '选择的比较分支已不可用', @@ -444,13 +444,13 @@ const COPY = { retry: '重试', }, terminalPanel: { - ariaLabel: '会话终端', - empty: '当前会话还没有终端运行', - emptyHelp: '会话启动终端后会显示在这里。', + ariaLabel: '任务终端', + empty: '当前任务还没有终端运行', + emptyHelp: '任务启动终端后会显示在这里。', loadFailed: '无法读取终端运行', retry: '重试', refresh: '刷新终端', - readOnly: '显示代理和你在当前会话中启动的终端运行', + readOnly: '显示代理和你在当前任务中启动的终端运行', runCount: (count) => `${count} 个终端运行`, newTerminal: '新建终端', commandPlaceholder: '输入命令并回车', @@ -462,7 +462,7 @@ const COPY = { stopFailed: '无法停止终端', }, inspector: { - ariaLabel: '会话追踪', + ariaLabel: '任务追踪', recordFile: '记录文件', copyPath: '复制文件路径', pathCopied: '已复制文件路径', @@ -470,8 +470,8 @@ const COPY = { copyFailedDetail: '剪贴板不可用或被系统拒绝。', loadFailed: '追踪读取失败', retry: '重试', - empty: '这个会话还没有可追踪的活动', - emptyHelp: '会话尚无活动记录。', + empty: '这个任务还没有可追踪的活动', + emptyHelp: '任务尚无活动记录。', costUnavailable: '费用未知', totals: { duration: '总耗时', @@ -537,24 +537,24 @@ const COPY = { confirm: '关闭侧边对话', }, errors: { - forkSetupFailed: '无法创建追问会话,请稍后重试。', + forkSetupFailed: '无法创建侧边对话,请稍后重试。', sendRejected: '追问未能开始,请稍后重试。', sendFailed: '追问失败,请稍后重试。', - settlementFailed: '对话已结束,但消息加载失败。请重试或重新打开侧边对话。', + settlementFailed: '运行已结束,但消息加载失败。请重试或重新打开侧边对话。', respondFailed: '响应失败,请稍后重试。', }, }, health: { blocked: { - fake_backend: { label: '会话已过期 · 请先配置真实模型', tooltip: () => '原会话使用旧的本地模拟连接,需要先到 设置 · 模型 添加并启用一个真实模型才能发送。' }, - missing_default_connection: { label: '未配置可用模型', tooltip: () => '当前会话没有可用的模型连接,发送会失败。请到 设置 · 模型 添加并启用一个模型。' }, - connection_missing: { label: '连接已删除', tooltip: () => '此会话依赖的模型连接已被删除,发送会失败。请到 设置 · 模型 检查连接配置。' }, - connection_disabled: { label: '连接已禁用', tooltip: (name) => `会话绑定的连接 "${name}" 已禁用,发送会失败。请到 设置 · 模型 启用它或选择其他连接。` }, + fake_backend: { label: '任务已过期 · 请先配置真实模型', tooltip: () => '原任务使用旧的本地模拟连接,需要先到 设置 · 模型 添加并启用一个真实模型才能发送。' }, + missing_default_connection: { label: '未配置可用模型', tooltip: () => '当前任务没有可用的模型连接,发送会失败。请到 设置 · 模型 添加并启用一个模型。' }, + connection_missing: { label: '连接已删除', tooltip: () => '此任务依赖的模型连接已被删除,发送会失败。请到 设置 · 模型 检查连接配置。' }, + connection_disabled: { label: '连接已禁用', tooltip: (name) => `任务绑定的连接 "${name}" 已禁用,发送会失败。请到 设置 · 模型 启用它或选择其他连接。` }, missing_api_key: { label: '连接缺少密钥', tooltip: (name) => `连接 "${name}" 未填写 API key 或未完成登录,发送会失败。请到 设置 · 模型 补齐凭据。` }, missing_model: { label: '连接未选择模型', tooltip: (name) => `连接 "${name}" 没有默认模型,发送会失败。请到 设置 · 模型 选择一个模型。` }, empty_model_list: { label: '连接没有启用模型', tooltip: (name) => `连接 "${name}" 没有启用任何模型,发送会失败。请到 设置 · 模型 先添加模型。` }, - model_not_enabled: { label: '会话模型未启用', tooltip: (name, model) => `模型 "${model}" 不在连接 "${name}" 的启用列表中,发送会失败。请到 设置 · 模型 重新选择。` }, - model_not_chat_capable: { label: '会话模型不支持聊天', tooltip: (name, model) => `模型 "${model}" 不能用于聊天,发送会失败。请到 设置 · 模型 选择支持聊天的模型。` }, + model_not_enabled: { label: '任务模型未启用', tooltip: (name, model) => `模型 "${model}" 不在连接 "${name}" 的启用列表中,发送会失败。请到 设置 · 模型 重新选择。` }, + model_not_chat_capable: { label: '任务模型不支持聊天', tooltip: (name, model) => `模型 "${model}" 不能用于聊天,发送会失败。请到 设置 · 模型 选择支持聊天的模型。` }, }, reauth: { label: '上次连接测试鉴权失败', tooltip: '最近一次连接测试返回鉴权失败(401 / 403),密钥可能已过期或被吊销。这不会拦截发送,但若发送失败请到 设置 · 模型 重新登录。' }, testError: { label: '上次连接测试失败', tooltip: '最近一次连接测试因网络 / 超时 / 5xx 失败。这不会拦截发送,但若问题持续请到 设置 · 模型 检查 Base URL / 代理。' }, @@ -562,7 +562,7 @@ const COPY = { turnError: { unknown: '未知错误', contextOverflow: '上下文窗口已超出限制', timeout: '请求超时', auth: '鉴权失败', providerBilling: '模型服务计费受限', rateLimit: '触发模型速率限制', network: '网络错误', provider: '模型服务返回错误', stepCap: '达到工具步骤上限', tool: '工具调用失败', permission: '等待权限确认', restarted: '本地应用重启,上一轮没有完成', sandboxBoundaryClosed: '本地应用重启,等待确认的「允许访问工作区以外的内容」请求已按拒绝关闭', recovery: { safeResume: '检查当前状态后,可尝试安全恢复', stepCap: '任务可能尚未完成,可以继续', toolError: '先检查工具结果,再决定是否重试', connection: '先检查模型连接或登录状态', partial: '已保留部分输出,可从这里继续', toolRecord: '工具记录已保留,重试前先看结果', retry: '没有执行工具,可直接重试', sandboxBoundaryClosed: '访问范围没有放开,重试本轮后可重新决定' } }, }, en: { - actions: { stopFailedTitle: 'Failed to stop', stopFailedFallback: 'The conversation action failed. Try again later.', refreshSessionsFailedTitle: 'Failed to refresh conversations', refreshSessionsFailedFallback: 'The conversation list could not be refreshed. Try again later.', conversationErrorTitle: 'Conversation error', conversationErrorFallback: 'The conversation run failed. Try again later.', regenerateStartedTitle: 'Regeneration started', regenerateStartedDescription: 'Generating a new response', branchCreatedTitle: 'Branch created', branchCreatedDescription: (name) => `New conversation: ${name}`, revisionStartedTitle: 'Edit draft ready', revisionStartedDescription: 'The original conversation is kept; sending creates a new version', revisionReadyTitle: 'Ready to edit and resend', revisionReadyDescription: 'Rewound to before that message; edit and send when ready', revisionUnavailableTitle: 'This message cannot be edited yet', revisionAttachmentsUnsupported: 'Edit & resend does not yet support historical attachments. Copy the text into a new message instead.', revisionTransformedTextUnsupported: 'Edit & resend does not yet support messages sent with an explicit skill. Copy the text and select the skill again instead.', revisionDraftAttachmentConflict: 'The composer already has pending attachments. Send or remove them before editing a sent message.', revisionCommandUnsupported: 'You cannot run /compact, /side, or orchestration commands while editing a sent message. Cancel the edit first.', revisionAlreadyActive: 'Another message is already being edited. Send or cancel that edit first.', revisionCancelLabel: 'Cancel', revisionBannerTitle: 'Editing sent message', revisionBannerDetail: '· New version on send', revisionUnchanged: 'Nothing changed. Use Regenerate if you only want a new answer.', operationFailedTitle: 'Action failed', operationFailedFallback: 'The conversation action failed. Try again later.', attachmentFailedTitle: 'Failed to add attachment', tryAgain: 'Try again later.', modelReboundTitle: 'Switched to an available model', modelReboundDescription: (modelId) => `The previous connection is unavailable${modelId ? ` · ${modelId}` : ''}`, messageReadFailedTitle: 'Failed to load conversation', returnLatest: 'Return to latest' }, + actions: { stopFailedTitle: 'Failed to stop', stopFailedFallback: 'The task action failed. Try again later.', refreshSessionsFailedTitle: 'Failed to refresh tasks', refreshSessionsFailedFallback: 'The task list could not be refreshed. Try again later.', conversationErrorTitle: 'Task error', conversationErrorFallback: 'The task run failed. Try again later.', regenerateStartedTitle: 'Regeneration started', regenerateStartedDescription: 'Generating a new response', branchCreatedTitle: 'Branch created', branchCreatedDescription: (name) => `New task: ${name}`, revisionStartedTitle: 'Edit draft ready', revisionStartedDescription: 'The original task is kept; sending creates a new version', revisionReadyTitle: 'Ready to edit and resend', revisionReadyDescription: 'Rewound to before that message; edit and send when ready', revisionUnavailableTitle: 'This message cannot be edited yet', revisionAttachmentsUnsupported: 'Edit & resend does not yet support historical attachments. Copy the text into a new message instead.', revisionTransformedTextUnsupported: 'Edit & resend does not yet support messages sent with an explicit skill. Copy the text and select the skill again instead.', revisionDraftAttachmentConflict: 'The composer already has pending attachments. Send or remove them before editing a sent message.', revisionCommandUnsupported: 'You cannot run /compact, /side, or orchestration commands while editing a sent message. Cancel the edit first.', revisionAlreadyActive: 'Another message is already being edited. Send or cancel that edit first.', revisionCancelLabel: 'Cancel', revisionBannerTitle: 'Editing sent message', revisionBannerDetail: '· New version on send', revisionUnchanged: 'Nothing changed. Use Regenerate if you only want a new answer.', operationFailedTitle: 'Action failed', operationFailedFallback: 'The task action failed. Try again later.', attachmentFailedTitle: 'Failed to add attachment', tryAgain: 'Try again later.', modelReboundTitle: 'Switched to an available model', modelReboundDescription: (modelId) => `The previous connection is unavailable${modelId ? ` · ${modelId}` : ''}`, messageReadFailedTitle: 'Failed to load task', returnLatest: 'Return to latest' }, attachments: { tooMany: 'You can attach at most 8 files', tooLarge: 'Attachments must be 50 MB or smaller', duplicate: 'This attachment was already added.' }, model: { fakeBackendLabel: 'Local simulation', @@ -571,25 +571,25 @@ const COPY = { configurationFallback: 'This model connection cannot send right now. Check it in Settings · Models and try again.', configurationReason: { missing_default_connection: 'Set a default model in Settings · Models before sending.', - connection_missing: 'The model connection used by this conversation was deleted. Select or create one in Settings · Models.', + connection_missing: 'The model connection used by this task was deleted. Select or create one in Settings · Models.', connection_disabled: 'The current model connection is disabled. Enable it or choose another default in Settings · Models.', missing_api_key: 'The current model connection has no usable credentials. Add an API key or sign in again under Settings · Models.', missing_model: 'The current connection has no usable model. Select a default model in Settings · Models.', empty_model_list: 'The current connection has no enabled models. Add or enable one in Settings · Models.', - model_not_enabled: 'The model selected for this conversation is disabled. Choose an enabled model in Settings · Models.', - model_not_chat_capable: 'The model selected for this conversation cannot chat. Choose a chat-capable model in Settings · Models.', - fake_backend: 'This conversation used the retired local simulation. Add a real model in Settings · Models, then start a new conversation.', + model_not_enabled: 'The model selected for this task is disabled. Choose an enabled model in Settings · Models.', + model_not_chat_capable: 'The model selected for this task cannot chat. Choose a chat-capable model in Settings · Models.', + fake_backend: 'This task used the retired local simulation. Add a real model in Settings · Models, then start a new task.', }, }, - footer: { labels: { regenerate: 'Regenerate', branch: 'Branch', copy: 'Copy', info: 'Details' }, pending: 'Working…', regenerateRunning: 'Wait for the current response to finish before regenerating', regenerateAgain: 'A regenerated response already exists; click again to create another parallel response', regenerate: 'Generate another response to this turn', branchRunning: 'Wait for the current response to finish before branching', branchAborted: 'Branch from the context before the interruption', branch: 'Branch a new conversation from this response', copy: 'Copy response to clipboard', copyEmpty: 'This response has no content to copy' }, + footer: { labels: { regenerate: 'Regenerate', branch: 'Branch', copy: 'Copy', info: 'Details' }, pending: 'Working…', regenerateRunning: 'Wait for the current response to finish before regenerating', regenerateAgain: 'A regenerated response already exists; click again to create another parallel response', regenerate: 'Generate another response to this turn', branchRunning: 'Wait for the current response to finish before branching', branchAborted: 'Branch from the context before the interruption', branch: 'Branch a new task from this response', copy: 'Copy response to clipboard', copyEmpty: 'This response has no content to copy' }, lineage: { regeneratedFrom: 'Regenerated from previous response', regeneratedFromTooltip: 'This is a parallel regenerated response; click to view the retained previous response', regeneratedTo: 'Regenerated → New response', regeneratedToTooltip: 'Jump to the regenerated response' }, workbar: { - ariaLabel: 'Conversation workbar', - sectionsAriaLabel: 'Conversation workbar tabs', + ariaLabel: 'Task workbar', + sectionsAriaLabel: 'Task workbar tabs', review: 'Changes', terminal: 'Terminal', terminalNumbered: (index) => `Terminal ${index}`, - tasks: 'Tasks', + tasks: 'To-do', browser: 'Browser', files: 'Generated files', inspector: 'Trace', @@ -610,10 +610,10 @@ const COPY = { closeToRight: 'Close tabs to the right', launcher: { review: 'View changes in the current Git workspace', - terminal: 'Inspect terminal runs and live output for this conversation', - tasks: 'View and maintain the task ledger for this conversation', + terminal: 'Inspect terminal runs and live output for this task', + tasks: "View and maintain this task's to-do ledger", browser: 'Open the embedded browser and keep the current page', - files: 'Browse files generated by this conversation', + files: 'Browse files generated by this task', inspector: 'Inspect model calls, tools, and timing', sideChat: 'Ask and explore read-only without interrupting the main task', }, @@ -622,8 +622,8 @@ const COPY = { ariaLabel: 'Git changes', empty: 'No changes in the current Git workspace', emptyHelp: 'Committed, staged, and modified files appear here.', - notGitRepository: 'This conversation directory is not a Git repository', - workspaceUnavailable: 'This conversation directory is unavailable', + notGitRepository: 'This task directory is not a Git repository', + workspaceUnavailable: 'This task directory is unavailable', unbornRepository: 'This Git repository has no commit to compare yet', gitFailed: 'Could not read Git workspace changes', invalidBaseBranch: 'The selected comparison branch is unavailable', @@ -641,13 +641,13 @@ const COPY = { retry: 'Retry', }, terminalPanel: { - ariaLabel: 'Conversation terminal', - empty: 'No terminal runs in this conversation yet', - emptyHelp: "The session's terminal appears here once it starts.", + ariaLabel: 'Task terminal', + empty: 'No terminal runs in this task yet', + emptyHelp: "This task's terminal appears here once it starts.", loadFailed: 'Could not read terminal runs', retry: 'Retry', refresh: 'Refresh terminal', - readOnly: 'Shows terminal runs started by the agent or you in this conversation', + readOnly: 'Shows terminal runs started by the agent or you in this task', runCount: (count) => `${count} terminal run${count === 1 ? '' : 's'}`, newTerminal: 'New terminal', commandPlaceholder: 'Enter a command and press Enter', @@ -659,7 +659,7 @@ const COPY = { stopFailed: 'Could not stop terminal', }, inspector: { - ariaLabel: 'Session trace', + ariaLabel: 'Task trace', recordFile: 'Record file', copyPath: 'Copy file path', pathCopied: 'File path copied', @@ -667,8 +667,8 @@ const COPY = { copyFailedDetail: 'The clipboard is unavailable or access was denied by the system.', loadFailed: 'Could not read the trace', retry: 'Retry', - empty: 'Nothing to trace in this session yet', - emptyHelp: 'No activity recorded for this session yet.', + empty: 'Nothing to trace in this task yet', + emptyHelp: 'No activity recorded for this task yet.', costUnavailable: 'cost unknown', totals: { duration: 'Duration', @@ -736,7 +736,7 @@ const COPY = { confirm: 'Close side chat', }, errors: { - forkSetupFailed: 'Could not create the companion conversation. Please try again.', + forkSetupFailed: 'Could not open the side chat. Please try again.', sendRejected: 'The companion could not start. Please try again.', sendFailed: 'The companion request failed. Please try again.', settlementFailed: 'The run ended, but its messages could not be loaded. Retry or reopen the side chat.', @@ -745,15 +745,15 @@ const COPY = { }, health: { blocked: { - fake_backend: { label: 'Stale conversation · Configure a real model', tooltip: () => 'This conversation used the retired local simulation. Add and enable a real model in Settings · Models before sending.' }, - missing_default_connection: { label: 'No model configured', tooltip: () => 'This conversation has no available model connection. Add and enable one in Settings · Models.' }, - connection_missing: { label: 'Connection deleted', tooltip: () => 'The model connection used by this conversation was deleted. Check Settings · Models.' }, + fake_backend: { label: 'Stale task · Configure a real model', tooltip: () => 'This task used the retired local simulation. Add and enable a real model in Settings · Models before sending.' }, + missing_default_connection: { label: 'No model configured', tooltip: () => 'This task has no available model connection. Add and enable one in Settings · Models.' }, + connection_missing: { label: 'Connection deleted', tooltip: () => 'The model connection used by this task was deleted. Check Settings · Models.' }, connection_disabled: { label: 'Connection disabled', tooltip: (name) => `Connection "${name}" is disabled. Enable it or choose another connection in Settings · Models.` }, missing_api_key: { label: 'Connection credentials missing', tooltip: (name) => `Connection "${name}" has no API key or completed sign-in. Add credentials in Settings · Models.` }, missing_model: { label: 'No model selected', tooltip: (name) => `Connection "${name}" has no default model. Select one in Settings · Models.` }, empty_model_list: { label: 'No models enabled', tooltip: (name) => `Connection "${name}" has no enabled models. Add one in Settings · Models.` }, - model_not_enabled: { label: 'Conversation model disabled', tooltip: (name, model) => `Model "${model}" is not enabled for connection "${name}". Choose another model in Settings · Models.` }, - model_not_chat_capable: { label: 'Conversation model cannot chat', tooltip: (_name, model) => `Model "${model}" cannot be used for chat. Choose a chat-capable model in Settings · Models.` }, + model_not_enabled: { label: 'Task model disabled', tooltip: (name, model) => `Model "${model}" is not enabled for connection "${name}". Choose another model in Settings · Models.` }, + model_not_chat_capable: { label: 'Task model cannot chat', tooltip: (_name, model) => `Model "${model}" cannot be used for chat. Choose a chat-capable model in Settings · Models.` }, }, reauth: { label: 'Last connection test failed authentication', tooltip: 'The latest test returned 401 / 403. Sending is not blocked, but sign in again under Settings · Models if it fails.' }, testError: { label: 'Last connection test failed', tooltip: 'The latest test failed because of a network, timeout, or 5xx error. Sending is not blocked; check Base URL or proxy settings if it persists.' }, diff --git a/apps/desktop/src/renderer/locales/external-session-import-copy.ts b/apps/desktop/src/renderer/locales/external-session-import-copy.ts index 686578a5a5..b5497a97a8 100644 --- a/apps/desktop/src/renderer/locales/external-session-import-copy.ts +++ b/apps/desktop/src/renderer/locales/external-session-import-copy.ts @@ -1,12 +1,19 @@ import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +/** + * 设置 › 活动 › 导入任务. + * + * The source items are another agent's conversations, so they stay 对话 / + * conversation; what lands in Maka is a 任务 / task. The page's own title and + * one-line description live in the settings navigation copy, like every other + * settings page's. + */ type ExternalSessionImportCopy = { - title: string; - description: string; sourceLabel: string; codex: string; includeArchived: string; loading: string; + listAria: string; emptyTitle: string; emptyDescription: string; unavailableTitle: string; @@ -18,8 +25,8 @@ type ExternalSessionImportCopy = { loadMore: string; loadingMore: string; duplicateNote: string; - cancel: string; import: string; + importTask: (name: string) => string; importing: string; importFailedTitle: string; importFailedFallback: string; @@ -29,58 +36,56 @@ type ExternalSessionImportCopy = { const COPY = { zh: { - title: '导入外部会话', - description: '选择一个本机 Agent 会话,将原始对话记录转换为 Maka 会话。', sourceLabel: '来源', codex: 'Codex', - includeArchived: '包含已归档会话', - loading: '正在读取外部会话…', - emptyTitle: '没有可导入的会话', - emptyDescription: '当前来源中没有找到符合条件的根会话。', + includeArchived: '包含已归档的对话', + loading: '正在读取外部对话…', + listAria: '可导入的对话', + emptyTitle: '没有可导入的对话', + emptyDescription: '当前来源中没有找到符合条件的根对话。', unavailableTitle: '没有检测到支持的 Agent', - unavailableDescription: 'Maka 会在本机读取 Codex 的会话目录,不会修改其中的文件。', - loadFailedTitle: '无法读取外部会话', - loadFailedFallback: '外部会话目录暂时无法读取,请重试。', + unavailableDescription: 'Maka 会在本机读取 Codex 的对话目录,不会修改其中的文件。', + loadFailedTitle: '无法读取外部对话', + loadFailedFallback: '外部对话目录暂时无法读取,请重试。', retry: '重试', archived: '已归档', loadMore: '加载更多', loadingMore: '正在加载…', - duplicateNote: '再次导入同一会话会创建一个独立副本。', - cancel: '取消', - import: '导入会话', + duplicateNote: '再次导入同一个对话会创建一个独立的任务。', + import: '导入', + importTask: (name) => `导入「${name}」`, importing: '正在导入…', importFailedTitle: '导入失败', - importFailedFallback: '该会话无法转换或保存。请检查来源后重试。', + importFailedFallback: '该对话无法转换或保存。请检查来源后重试。', importOutcomeUnknownTitle: '需要确认导入结果', importOutcomeUnknownDescription: - '导入结果暂时无法确认。请先关闭此窗口并检查 Maka 会话列表;如果会话已经出现,请不要再次导入。', + '导入结果暂时无法确认。请先在任务列表中查找这个对话;如果它已经出现,请不要再次导入。', }, en: { - title: 'Import external conversation', - description: 'Choose a local Agent conversation and convert its raw history into a Maka Session.', sourceLabel: 'Source', codex: 'Codex', includeArchived: 'Include archived conversations', loading: 'Reading external conversations…', + listAria: 'Conversations available to import', emptyTitle: 'No conversations to import', emptyDescription: 'No matching root conversations were found in this source.', unavailableTitle: 'No supported Agent detected', - unavailableDescription: "Maka reads Codex's local Session directory without modifying its files.", + unavailableDescription: "Maka reads Codex's local session directory without modifying its files.", loadFailedTitle: 'Could not read external conversations', - loadFailedFallback: 'The external Session directory is temporarily unavailable. Try again.', + loadFailedFallback: 'The external session directory is temporarily unavailable. Try again.', retry: 'Retry', archived: 'Archived', loadMore: 'Load more', loadingMore: 'Loading…', - duplicateNote: 'Importing the same conversation again creates an independent copy.', - cancel: 'Cancel', - import: 'Import conversation', + duplicateNote: 'Importing the same conversation again creates an independent task.', + import: 'Import', + importTask: (name) => `Import ${name}`, importing: 'Importing…', importFailedTitle: 'Import failed', importFailedFallback: 'This conversation could not be converted or saved. Check the source and try again.', importOutcomeUnknownTitle: 'Check the import result', importOutcomeUnknownDescription: - 'Maka could not confirm whether the import completed. Close this window and check the Maka Session list. If the Session appears there, do not import it again.', + 'Maka could not confirm whether the import completed. Look for this conversation in the task list first; if it is already there, do not import it again.', }, } satisfies UiCatalog; diff --git a/apps/desktop/src/renderer/locales/permission-center-copy.ts b/apps/desktop/src/renderer/locales/permission-center-copy.ts index ca5ab108f6..9065b6d841 100644 --- a/apps/desktop/src/renderer/locales/permission-center-copy.ts +++ b/apps/desktop/src/renderer/locales/permission-center-copy.ts @@ -116,7 +116,7 @@ const PERMISSION_CENTER_COPY = { aria: (label) => `${label}能力状态明细`, feature: '功能开关', configuration: '配置', approval: '操作审批', memory: '记忆写入', runtime: '运行态探测', featureStates: { enabled: '已开启', partial: '部分可用', disabled: '已关闭', not_available: '未开放' }, configurationStates: { not_required: '不需要配置', missing: '等待补齐配置', present: '已填写' }, - approvalStates: { not_required: '不需要审批', required_per_action: '每次调用都需审批', required_scoped_lease: '按目标与动作类别授权', pending: '审批挂起', approved: '当前会话已批准', denied: '当前会话已拒绝' }, + approvalStates: { not_required: '不需要审批', required_per_action: '每次调用都需审批', required_scoped_lease: '按目标与动作类别授权', pending: '审批挂起', approved: '当前任务已批准', denied: '当前任务已拒绝' }, memoryStates: { not_applicable: '不涉及记忆写入', disabled: '记忆写入已关闭', draft_required: '需要先草拟 memory 协议', accepted: '记忆写入已接受' }, runtimeStates: { not_available: '尚无运行态探测', not_run: '探测未运行', healthy: '探测通过', degraded: '探测降级' }, }, @@ -163,7 +163,7 @@ const PERMISSION_CENTER_COPY = { aria: (label) => `${label} capability state details`, feature: 'Feature toggle', configuration: 'Configuration', approval: 'Action approval', memory: 'Memory writes', runtime: 'Runtime probe', featureStates: { enabled: 'Enabled', partial: 'Partially available', disabled: 'Disabled', not_available: 'Unavailable' }, configurationStates: { not_required: 'No configuration needed', missing: 'Configuration required', present: 'Configured' }, - approvalStates: { not_required: 'No approval needed', required_per_action: 'Approval required for every call', required_scoped_lease: 'Authorized by target and action category', pending: 'Approval pending', approved: 'Approved for this session', denied: 'Denied for this session' }, + approvalStates: { not_required: 'No approval needed', required_per_action: 'Approval required for every call', required_scoped_lease: 'Authorized by target and action category', pending: 'Approval pending', approved: 'Approved for this task', denied: 'Denied for this task' }, memoryStates: { not_applicable: 'No memory writes', disabled: 'Memory writes disabled', draft_required: 'Draft a memory protocol first', accepted: 'Memory writes accepted' }, runtimeStates: { not_available: 'No runtime probe available', not_run: 'Probe not run', healthy: 'Probe passed', degraded: 'Probe degraded' }, }, diff --git a/apps/desktop/src/renderer/locales/settings-daily-review-copy.ts b/apps/desktop/src/renderer/locales/settings-daily-review-copy.ts index 24eb1a3233..14328bde9d 100644 --- a/apps/desktop/src/renderer/locales/settings-daily-review-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-daily-review-copy.ts @@ -22,7 +22,7 @@ export type DailyReviewSettingsCopy = { const SETTINGS_DAILY_REVIEW_COPY = { zh: { - defaultModel: '跟随对话默认', + defaultModel: '跟随任务默认', saveFailed: '保存每日回顾设置失败', aria: '每日回顾', unavailable: '当前版本无法读取每日回顾设置。', @@ -38,10 +38,10 @@ const SETTINGS_DAILY_REVIEW_COPY = { analysisTitle: '分析', analysisDescription: '选择用于生成固定结构报告的模型。', model: '分析模型', - modelHelp: '未指定时跟随当前对话的默认模型。', + modelHelp: '未指定时跟随当前任务的默认模型。', }, en: { - defaultModel: 'Follow conversation default', + defaultModel: 'Follow task default', saveFailed: 'Failed to save Daily Review settings', aria: 'Daily Review', unavailable: 'Daily Review settings are unavailable in this build.', @@ -57,7 +57,7 @@ const SETTINGS_DAILY_REVIEW_COPY = { analysisTitle: 'Analysis', analysisDescription: 'Choose the model used to generate the fixed report structure.', model: 'Analysis model', - modelHelp: 'Follows the current conversation default when unspecified.', + modelHelp: 'Follows the current task default when unspecified.', }, } satisfies UiCatalog; diff --git a/apps/desktop/src/renderer/locales/settings-data-copy.ts b/apps/desktop/src/renderer/locales/settings-data-copy.ts index 6aaa64c740..3e72d8c5aa 100644 --- a/apps/desktop/src/renderer/locales/settings-data-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-data-copy.ts @@ -38,7 +38,7 @@ const SETTINGS_DATA_COPY = { exported: '已导出配置', exportedDetail: (items) => `包含:${items.join('、')}`, exportFailed: '导出失败', noCategories: '未选择任何类别', tryAgain: '请稍后重试', imported: '已导入配置', importFailed: '导入失败', invalidFile: '文件无效或版本不受支持。', rows: { - workspace: '工作区路径', workspaceDetail: '会话、设置、凭据和技能文件都存在这个目录下。', loadValueFailed: '载入失败', loading: '正在加载…', + workspace: '工作区路径', workspaceDetail: '任务、设置、凭据和技能文件都存在这个目录下。', loadValueFailed: '载入失败', loading: '正在加载…', history: '输入历史', historyDetail: '上箭头 / 下箭头调出的已发送提示词记录,保存在本机、重启后仍在。清空后无法恢复。', }, actionsAria: '工作区数据操作', opening: '打开中…', openWorkspace: '打开工作区文件夹', copying: '复制中…', copyPath: '复制路径', clearing: '清空中…', clearHistory: '清空输入历史', @@ -65,7 +65,7 @@ const SETTINGS_DATA_COPY = { exported: 'Configuration exported', exportedDetail: (items) => `Included: ${items.join(', ')}`, exportFailed: 'Export failed', noCategories: 'No categories selected', tryAgain: 'Try again later', imported: 'Configuration imported', importFailed: 'Import failed', invalidFile: 'The file is invalid or its version is unsupported.', rows: { - workspace: 'Workspace path', workspaceDetail: 'Sessions, settings, credentials, and skill files are stored in this directory.', loadValueFailed: 'Failed to load', loading: 'Loading…', + workspace: 'Workspace path', workspaceDetail: 'Tasks, settings, credentials, and skill files are stored in this directory.', loadValueFailed: 'Failed to load', loading: 'Loading…', history: 'Input history', historyDetail: 'Previously sent prompts recalled with the Up and Down arrows are kept on this machine and persist across restarts. Clearing them cannot be undone.', }, actionsAria: 'Workspace data actions', opening: 'Opening…', openWorkspace: 'Open workspace folder', copying: 'Copying…', copyPath: 'Copy path', clearing: 'Clearing…', clearHistory: 'Clear input history', diff --git a/apps/desktop/src/renderer/locales/settings-memory-copy.ts b/apps/desktop/src/renderer/locales/settings-memory-copy.ts index 94cbe96f25..512caaabaf 100644 --- a/apps/desktop/src/renderer/locales/settings-memory-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-memory-copy.ts @@ -59,7 +59,7 @@ export type MemorySettingsCopy = { }; const zhText = { - localFile: '本地 MEMORY.md', localFileHelp: '透明 Markdown 文件,保存在当前本机工作区。这里的内容不会自动从聊天里抽取。', enableLocalFile: '启用本地 MEMORY.md', agentReadable: '模型上下文可读取', agentReadableHelp: '默认关闭。开启后,发送消息时会把本地记忆一并提供给模型;隐身模式下仍然不提供。', enableAgentRead: '允许模型上下文读取本地记忆', waitingFile: '等待创建 MEMORY.md', waitingBackup: '等待生成上一版备份', dirty: '有未保存修改', savedDraft: '草稿已保存', backupCandidates: '备份候选', backupCandidatesAria: '本地记忆备份候选列表', opening: '打开中…', open: '打开', restoring: '恢复中…', restore: '恢复', copying: '复制中…', copyReference: '复制引用', backupHelp: '上一版操作会使用最近的候选;这里只显示 metadata,不展示备份正文。', savedAt: '保存于 ', previewPaused: '草稿条目预览暂停', filterAria: '筛选本地记忆', filterPlaceholder: '筛选标题、内容、ID 或标签', clear: '清除', filterEmpty: '没有匹配的记忆条目', filterEmptyHelp: '筛选不会修改 MEMORY.md;清除筛选后会恢复显示全部条目。', activeMemories: '生效记忆', archivedMemories: '已归档记忆', waitingEntry: '等待添加记忆条目', waitingEntryHelp: '在对话里确认记忆,或点右上角「添加记忆」。', manualAddAria: '手动添加本地记忆', manualAdd: '手动添加记忆', manualAddHelp: '填写后立即写入本机 MEMORY.md。', title: '记忆标题', titlePlaceholder: '标题', tags: '记忆标签', tagsPlaceholder: '标签(逗号分隔,可选)', content: '记忆内容', contentPlaceholder: '内容', addDraft: '添加记忆', sensitiveDraft: '草稿含疑似敏感字段', sensitiveDraftHelp: '保存时会先遮蔽疑似 token、API key 或密码,再写入 MEMORY.md。', fileContent: 'MEMORY.md 内容', fileActionsAria: 'MEMORY.md 文件操作', saving: '保存中…', save: '保存', saved: '已保存', openFile: '打开 MEMORY.md', openFolder: '打开所在目录', loading: '载入中…', reload: '重新载入', openPrevious: '打开上一版', copyPath: '复制路径', copyPrevious: '复制上一版引用', resetting: '重置中…', resetBackup: '重置并备份', restorePrevious: '恢复上一版', archiveDraftNotice: '当前归档/恢复操作只更新草稿,保存后才会写入 MEMORY.md。', noMatchEntry: '无匹配条目。', noEntry: '暂无条目。', created: '创建 ', updated: '更新 ', archivedNoPrompt: '已归档,不会提供给模型', activePrompt: '生效条目,发送时会提供给模型', locateDraft: '定位草稿', promptPreview: '模型上下文预览', willInject: '发送时会提供', willNotInject: '当前不会提供', copyContext: '复制上下文', promptPreviewHelp: '这里是发送时会提供给模型的内容;已归档条目不在其中,疑似密钥会遮蔽。', safeModePreview: 'MEMORY.md 过大,当前不会生成模型上下文预览。', emptyPromptPreview: '没有生效记忆会提供给模型。', loadFailed: '载入本地记忆失败', reloaded: '已重新载入 MEMORY.md', reloadDiscarded: '未保存的草稿修改已丢弃。', toggleFailed: '更新本地记忆开关失败', agentReadFailed: '更新模型读取权限失败', saveBlocked: '保存被拦截', safeMode: 'MEMORY.md 内容过大,已进入安全模式。', savedRedacted: '已保存并遮蔽敏感字段', savedFile: '已保存 MEMORY.md', saveFailed: '保存 MEMORY.md 失败', resetDone: '已重置 MEMORY.md', resetDoneDetail: '上一版已保存为备份文件。', resetFailed: '重置 MEMORY.md 失败', noBackup: '没有可恢复备份', noBackupDetail: '保存或重置 MEMORY.md 后才会生成上一版备份。', restoreLatestTitle: '恢复上一版 MEMORY.md?', restoreCandidateTitle: '恢复这个 MEMORY.md 备份?', confirmRestore: '恢复', cancel: '取消', restoredLatest: '已恢复上一版 MEMORY.md', restoredCandidate: '已恢复 MEMORY.md 备份候选', restoredDetail: '恢复前的当前文件已保存为 restore.bak。', restoreFailed: '恢复失败', restoreLatestFailed: '恢复上一版失败', restoreCandidateFailed: '恢复备份失败', openFailed: '打开失败', openPreviousFailed: '打开上一版失败', pathCopied: '已复制路径', copyFailed: '复制失败', copyFailedDetail: '剪贴板不可用或被系统拒绝。', backupReferenceCopied: '已复制上一版引用', entryReferenceCopied: '已复制记忆引用', locateFailed: '无法定位记忆', locateFailedDetail: '当前草稿里找不到这条记忆;请先保存或刷新后重试。', emptyTitle: '标题不能为空', emptyTitleDetail: '给这条记忆起一个短标题。', emptyContent: '内容不能为空', emptyContentDetail: '写下要保留的偏好或事实。', draftOversize: '草稿过大', oversizeDetail: 'MEMORY.md 超出安全上限,请先删减旧内容。', addedDraft: '已添加记忆', addedDraftDetail: '已写入 MEMORY.md。', updateFailed: '无法更新记忆', invalidIdDetail: '这条记忆没有可识别 ID,已停止更新。', archivedDraft: '已在草稿中归档记忆', restoredDraft: '已在草稿中恢复记忆', updateBlocked: '更新被拦截', archived: '已归档记忆', restored: '已恢复记忆', archiveFailed: '归档记忆失败', entryRestoreFailed: '恢复记忆失败', promptCopied: '已复制模型上下文预览', promptCopiedDetail: '使用同一条 prompt 预览和遮蔽路径。', restoreDraftAction: '恢复到草稿', archiveDraftAction: '归档到草稿', restoreAction: '恢复', archiveAction: '归档', + localFile: '本地 MEMORY.md', localFileHelp: '透明 Markdown 文件,保存在当前本机工作区。这里的内容不会自动从聊天里抽取。', enableLocalFile: '启用本地 MEMORY.md', agentReadable: '模型上下文可读取', agentReadableHelp: '默认关闭。开启后,发送消息时会把本地记忆一并提供给模型;隐身模式下仍然不提供。', enableAgentRead: '允许模型上下文读取本地记忆', waitingFile: '等待创建 MEMORY.md', waitingBackup: '等待生成上一版备份', dirty: '有未保存修改', savedDraft: '草稿已保存', backupCandidates: '备份候选', backupCandidatesAria: '本地记忆备份候选列表', opening: '打开中…', open: '打开', restoring: '恢复中…', restore: '恢复', copying: '复制中…', copyReference: '复制引用', backupHelp: '上一版操作会使用最近的候选;这里只显示 metadata,不展示备份正文。', savedAt: '保存于 ', previewPaused: '草稿条目预览暂停', filterAria: '筛选本地记忆', filterPlaceholder: '筛选标题、内容、ID 或标签', clear: '清除', filterEmpty: '没有匹配的记忆条目', filterEmptyHelp: '筛选不会修改 MEMORY.md;清除筛选后会恢复显示全部条目。', activeMemories: '生效记忆', archivedMemories: '已归档记忆', waitingEntry: '等待添加记忆条目', waitingEntryHelp: '在任务里确认记忆,或点右上角「添加记忆」。', manualAddAria: '手动添加本地记忆', manualAdd: '手动添加记忆', manualAddHelp: '填写后立即写入本机 MEMORY.md。', title: '记忆标题', titlePlaceholder: '标题', tags: '记忆标签', tagsPlaceholder: '标签(逗号分隔,可选)', content: '记忆内容', contentPlaceholder: '内容', addDraft: '添加记忆', sensitiveDraft: '草稿含疑似敏感字段', sensitiveDraftHelp: '保存时会先遮蔽疑似 token、API key 或密码,再写入 MEMORY.md。', fileContent: 'MEMORY.md 内容', fileActionsAria: 'MEMORY.md 文件操作', saving: '保存中…', save: '保存', saved: '已保存', openFile: '打开 MEMORY.md', openFolder: '打开所在目录', loading: '载入中…', reload: '重新载入', openPrevious: '打开上一版', copyPath: '复制路径', copyPrevious: '复制上一版引用', resetting: '重置中…', resetBackup: '重置并备份', restorePrevious: '恢复上一版', archiveDraftNotice: '当前归档/恢复操作只更新草稿,保存后才会写入 MEMORY.md。', noMatchEntry: '无匹配条目。', noEntry: '暂无条目。', created: '创建 ', updated: '更新 ', archivedNoPrompt: '已归档,不会提供给模型', activePrompt: '生效条目,发送时会提供给模型', locateDraft: '定位草稿', promptPreview: '模型上下文预览', willInject: '发送时会提供', willNotInject: '当前不会提供', copyContext: '复制上下文', promptPreviewHelp: '这里是发送时会提供给模型的内容;已归档条目不在其中,疑似密钥会遮蔽。', safeModePreview: 'MEMORY.md 过大,当前不会生成模型上下文预览。', emptyPromptPreview: '没有生效记忆会提供给模型。', loadFailed: '载入本地记忆失败', reloaded: '已重新载入 MEMORY.md', reloadDiscarded: '未保存的草稿修改已丢弃。', toggleFailed: '更新本地记忆开关失败', agentReadFailed: '更新模型读取权限失败', saveBlocked: '保存被拦截', safeMode: 'MEMORY.md 内容过大,已进入安全模式。', savedRedacted: '已保存并遮蔽敏感字段', savedFile: '已保存 MEMORY.md', saveFailed: '保存 MEMORY.md 失败', resetDone: '已重置 MEMORY.md', resetDoneDetail: '上一版已保存为备份文件。', resetFailed: '重置 MEMORY.md 失败', noBackup: '没有可恢复备份', noBackupDetail: '保存或重置 MEMORY.md 后才会生成上一版备份。', restoreLatestTitle: '恢复上一版 MEMORY.md?', restoreCandidateTitle: '恢复这个 MEMORY.md 备份?', confirmRestore: '恢复', cancel: '取消', restoredLatest: '已恢复上一版 MEMORY.md', restoredCandidate: '已恢复 MEMORY.md 备份候选', restoredDetail: '恢复前的当前文件已保存为 restore.bak。', restoreFailed: '恢复失败', restoreLatestFailed: '恢复上一版失败', restoreCandidateFailed: '恢复备份失败', openFailed: '打开失败', openPreviousFailed: '打开上一版失败', pathCopied: '已复制路径', copyFailed: '复制失败', copyFailedDetail: '剪贴板不可用或被系统拒绝。', backupReferenceCopied: '已复制上一版引用', entryReferenceCopied: '已复制记忆引用', locateFailed: '无法定位记忆', locateFailedDetail: '当前草稿里找不到这条记忆;请先保存或刷新后重试。', emptyTitle: '标题不能为空', emptyTitleDetail: '给这条记忆起一个短标题。', emptyContent: '内容不能为空', emptyContentDetail: '写下要保留的偏好或事实。', draftOversize: '草稿过大', oversizeDetail: 'MEMORY.md 超出安全上限,请先删减旧内容。', addedDraft: '已添加记忆', addedDraftDetail: '已写入 MEMORY.md。', updateFailed: '无法更新记忆', invalidIdDetail: '这条记忆没有可识别 ID,已停止更新。', archivedDraft: '已在草稿中归档记忆', restoredDraft: '已在草稿中恢复记忆', updateBlocked: '更新被拦截', archived: '已归档记忆', restored: '已恢复记忆', archiveFailed: '归档记忆失败', entryRestoreFailed: '恢复记忆失败', promptCopied: '已复制模型上下文预览', promptCopiedDetail: '使用同一条 prompt 预览和遮蔽路径。', restoreDraftAction: '恢复到草稿', archiveDraftAction: '归档到草稿', restoreAction: '恢复', archiveAction: '归档', } satisfies Record; const enText = { diff --git a/apps/desktop/src/renderer/locales/settings-navigation-copy.ts b/apps/desktop/src/renderer/locales/settings-navigation-copy.ts index c79df2240f..79833e923a 100644 --- a/apps/desktop/src/renderer/locales/settings-navigation-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-navigation-copy.ts @@ -16,15 +16,16 @@ const SETTINGS_NAVIGATION_COPY_BY_LOCALE = { system: '系统', }, sections: { - general: { label: '通用', description: '显示名称与界面语言、隐私与通知、对话默认与网络代理。' }, + general: { label: '通用', description: '显示名称与界面语言、隐私与通知、任务默认与网络代理。' }, appearance: { label: '外观', description: '界面主题与调色板。' }, projects: { label: '工作区', description: '选择 Runtime Host,并管理该 Host 上的项目。' }, models: { label: '模型', description: '模型连接、API key 与 OAuth 订阅管理。' }, subagents: { label: '子 Agent', description: '配置主 Agent 可以自动选择的子 Agent、能力边界与模型。' }, usage: { label: '使用统计', description: 'token、模型、工具使用走势与配额追踪。' }, 'archived-tasks': { label: '已归档任务', description: '恢复或彻底删除已归档的任务。' }, + 'import-tasks': { label: '导入任务', description: '把本机其他 Agent 的对话记录转换成 Maka 任务。' }, memory: { label: '记忆', description: 'Maka 记住的内容,以及本地 MEMORY.md 文件。' }, - 'daily-review': { label: '每日回顾', description: '每天分析本机对话,生成摘要、遗漏提醒和建议。' }, + 'daily-review': { label: '每日回顾', description: '每天分析本机任务,生成摘要、遗漏提醒和建议。' }, 'bot-chat': { label: '远程接入', description: '通过 Telegram、飞书、微信等平台从其他设备与 Maka 对话。' }, search: { label: '联网搜索', description: '联网搜索供应商(如 Tavily)凭据与隐私边界。' }, data: { label: '数据', description: '本地工作区路径、备份与恢复。' }, @@ -41,15 +42,16 @@ const SETTINGS_NAVIGATION_COPY_BY_LOCALE = { system: 'System', }, sections: { - general: { label: 'General', description: 'Display name and interface language, privacy and notifications, conversation defaults, and network proxy.' }, + general: { label: 'General', description: 'Display name and interface language, privacy and notifications, task defaults, and network proxy.' }, appearance: { label: 'Appearance', description: 'Interface theme and color palette.' }, projects: { label: 'Workspace', description: 'Choose the Runtime Host and manage the projects available on it.' }, models: { label: 'Models', description: 'Model connections, API keys, and OAuth subscriptions.' }, subagents: { label: 'Subagents', description: 'Configure the subagents, capability boundaries, and models the main agent may select.' }, usage: { label: 'Usage', description: 'Token, model, tool usage trends, and quota tracking.' }, 'archived-tasks': { label: 'Archived tasks', description: 'Restore or permanently delete archived tasks.' }, + 'import-tasks': { label: 'Import tasks', description: 'Convert conversations from another local agent into Maka tasks.' }, memory: { label: 'Memory', description: 'What Maka remembers, and the local MEMORY.md file.' }, - 'daily-review': { label: 'Daily Review', description: 'Analyze local conversations for summaries, reminders, and suggestions.' }, + 'daily-review': { label: 'Daily Review', description: 'Analyze local tasks for summaries, reminders, and suggestions.' }, 'bot-chat': { label: 'Remote Access', description: 'Chat with Maka from other devices through Telegram, Feishu, or WeChat.' }, search: { label: 'Web Search', description: 'Credentials and privacy boundaries for providers such as Tavily.' }, data: { label: 'Data', description: 'Local workspace paths, backup, and restore.' }, diff --git a/apps/desktop/src/renderer/locales/settings-preferences-copy.ts b/apps/desktop/src/renderer/locales/settings-preferences-copy.ts index a7824cb897..8dd807e29a 100644 --- a/apps/desktop/src/renderer/locales/settings-preferences-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-preferences-copy.ts @@ -194,7 +194,7 @@ const SETTINGS_PREFERENCES_COPY_BY_LOCALE = { sections: { identity: '身份', identityHelp: 'Maka 如何称呼你,以及界面语言和回答语气。', privacy: '隐私与通知', privacyHelp: '本地数据的读写范围,以及桌面通知时机。', - chatDefaults: '对话默认', chatDefaultsHelp: '新对话的起始模型、权限模式与思考级别。', + chatDefaults: '任务默认', chatDefaultsHelp: '新任务的起始模型、权限模式与思考级别。', network: '网络', networkHelp: 'AI 模型请求走的网络通道。', theme: '主题', themeHelp: '界面跟随系统,还是固定浅色或深色。', palette: '调色板', paletteHelp: '强调色与画布色调;切换会立即生效并保存在本地。', @@ -220,11 +220,11 @@ const SETTINGS_PREFERENCES_COPY_BY_LOCALE = { }, general: { incognito: '隐身模式', incognitoHelp: '开启后暂停本地记忆读写、联网搜索和定时任务触发。', enableIncognito: '启用隐身模式', incognitoFailed: '隐身模式切换失败', notifications: '完成时发送系统通知', notificationsHelp: '窗口不在前台时,在回答完成或出错后发送桌面通知。', notificationsFailed: '通知设置切换失败', workspaceInstructions: '遵循项目指令', workspaceInstructionsHelp: '自动读取每个项目中已有的 AGENTS.md、CLAUDE.md 或 GEMINI.md;文件仍由各自项目管理。', workspaceInstructionsFailed: '项目指令设置切换失败', updateFailed: '设置未生效,请稍后重试。', - defaultModel: '默认模型', defaultModelHelp: '新对话默认使用的模型。', notSet: '未设置', saveDefaultModelFailed: '保存默认模型失败', defaultPermission: '默认权限模式', defaultPermissionHelp: '新对话默认使用的权限模式;可在对话内随时切换。', saveDefaultPermissionFailed: '保存默认权限模式失败', defaultThinking: '默认思考级别', defaultThinkingHelp: '新对话的思考级别;当前模型不支持所选级别时用模型默认。', followModelDefault: '跟随模型默认', saveDefaultThinkingFailed: '保存默认思考级别失败', + defaultModel: '默认模型', defaultModelHelp: '新任务默认使用的模型。', notSet: '未设置', saveDefaultModelFailed: '保存默认模型失败', defaultPermission: '默认权限模式', defaultPermissionHelp: '新任务默认使用的权限模式;可在任务内随时切换。', saveDefaultPermissionFailed: '保存默认权限模式失败', defaultThinking: '默认思考级别', defaultThinkingHelp: '新任务的思考级别;当前模型不支持所选级别时用模型默认。', followModelDefault: '跟随模型默认', saveDefaultThinkingFailed: '保存默认思考级别失败', proxy: '代理服务器', proxyHelp: '为 AI 模型请求配置网络代理', enableProxy: '启用代理服务器', saveNetworkFailed: '保存网络设置失败', proxyProtocol: '代理协议', serverAddress: '服务器地址', port: '端口', proxyAuth: '代理认证', proxyAuthHelp: '需要用户名和密码时开启。', enableProxyAuth: '启用代理认证', username: '用户名', password: '密码', bypassList: '代理白名单', bypassHelp: '这些域名将绕过代理直连,多个用逗号分隔。', autoBypass: (count) => `已自动添加 ${count} 个域名。代理仅作用于 AI 模型请求。`, testing: '测试中…', testCurrent: '测试当前配置', proxyReachable: '代理可达', proxyTestFailed: '代理测试失败', proxyTestError: '代理测试出错', }, about: { - loadFailed: '载入关于信息失败', loading: '正在加载关于页', unavailable: '无法载入关于信息', copied: '已复制环境信息', pasteHint: '可直接粘贴到问题报告', copyFailed: '复制失败', clipboardUnavailable: '剪贴板不可用或被系统拒绝。', devBuild: '本地开发版', packagedBuild: '正式版', subtitle: '本地优先的 AI 助手 · 桌面端运行环境', privacyLabel: '隐私与安全', privacyTitle: '本地优先 · 隐私默认', privacyPoints: ['所有会话、设置、凭据和 Skill 指令文件都保留在本机工作区。', '模型密钥保存在本机凭据文件内;订阅账号令牌使用系统安全存储。', 'Maka 不发送使用遥测;只在你显式启用时与所选模型供应商通信。', '高风险工具操作需要在对话内明示授权。', '每个会话都会在本机保留消息、工具调用、权限决策与模式变更记录。'], copying: '复制中…', copyEnvironment: '复制环境信息', copyHelp: '复制当前版本与平台信息以便定位问题;内容不包含工作区路径。', keyboardShortcuts: '键盘快捷键', keyboardShortcutsHelp: 'Maka 支持的全部快捷键一览。', keyboardShortcutsOpen: '查看', reportIssueLabel: '报告问题', + loadFailed: '载入关于信息失败', loading: '正在加载关于页', unavailable: '无法载入关于信息', copied: '已复制环境信息', pasteHint: '可直接粘贴到问题报告', copyFailed: '复制失败', clipboardUnavailable: '剪贴板不可用或被系统拒绝。', devBuild: '本地开发版', packagedBuild: '正式版', subtitle: '本地优先的 AI 助手 · 桌面端运行环境', privacyLabel: '隐私与安全', privacyTitle: '本地优先 · 隐私默认', privacyPoints: ['所有任务、设置、凭据和 Skill 指令文件都保留在本机工作区。', '模型密钥保存在本机凭据文件内;订阅账号令牌使用系统安全存储。', 'Maka 不发送使用遥测;只在你显式启用时与所选模型供应商通信。', '高风险工具操作需要在任务内明示授权。', '每个任务都会在本机保留消息、工具调用、权限决策与模式变更记录。'], copying: '复制中…', copyEnvironment: '复制环境信息', copyHelp: '复制当前版本与平台信息以便定位问题;内容不包含工作区路径。', keyboardShortcuts: '键盘快捷键', keyboardShortcutsHelp: 'Maka 支持的全部快捷键一览。', keyboardShortcutsOpen: '查看', reportIssueLabel: '报告问题', updatesTitle: '软件更新', checkForUpdates: '检查更新', checkingForUpdates: '检查中…', @@ -248,7 +248,7 @@ const SETTINGS_PREFERENCES_COPY_BY_LOCALE = { sections: { identity: 'Identity', identityHelp: 'How Maka addresses you, plus interface language and response tone.', privacy: 'Privacy and notifications', privacyHelp: 'What Maka may read and write locally, and when it notifies you.', - chatDefaults: 'Conversation defaults', chatDefaultsHelp: 'The model, permission mode, and thinking level a new conversation starts on.', + chatDefaults: 'Task defaults', chatDefaultsHelp: 'The model, permission mode, and thinking level a new task starts on.', network: 'Network', networkHelp: 'The network path AI model requests take.', theme: 'Theme', themeHelp: 'Follow the system appearance, or stay on light or dark.', palette: 'Color palette', paletteHelp: 'Accent and canvas colors. Changes apply immediately and are saved locally.', @@ -269,10 +269,10 @@ const SETTINGS_PREFERENCES_COPY_BY_LOCALE = { removeErrors: { invalid_id: 'The pet ID is invalid.', remove_failed: 'The local pet pack could not be removed.' }, }, general: { - incognito: 'Incognito mode', incognitoHelp: 'Pause local memory, web search, and scheduled task triggers.', enableIncognito: 'Enable incognito mode', incognitoFailed: 'Could not change incognito mode', notifications: 'Send a system notification when finished', notificationsHelp: 'Notify when a response finishes or fails while the window is in the background.', notificationsFailed: 'Could not change notification settings', workspaceInstructions: 'Follow project instructions', workspaceInstructionsHelp: 'Automatically read existing AGENTS.md, CLAUDE.md, or GEMINI.md files in each project. Manage the files in their respective projects.', workspaceInstructionsFailed: 'Could not change project instruction settings', updateFailed: 'The setting was not applied. Try again later.', defaultModel: 'Default model', defaultModelHelp: 'Model used by new conversations.', notSet: 'Not set', saveDefaultModelFailed: 'Could not save the default model', defaultPermission: 'Default permission mode', defaultPermissionHelp: 'Initial permission mode for new conversations; it can be changed at any time.', saveDefaultPermissionFailed: 'Could not save the default permission mode', defaultThinking: 'Default thinking level', defaultThinkingHelp: 'Thinking level for new conversations; models that do not offer the chosen level use their own default.', followModelDefault: 'Follow model default', saveDefaultThinkingFailed: 'Could not save the default thinking level', proxy: 'Proxy server', proxyHelp: 'Configure a network proxy for AI model requests', enableProxy: 'Enable proxy server', saveNetworkFailed: 'Could not save network settings', proxyProtocol: 'Proxy protocol', serverAddress: 'Server address', port: 'Port', proxyAuth: 'Proxy authentication', proxyAuthHelp: 'Enable this when a username and password are required.', enableProxyAuth: 'Enable proxy authentication', username: 'Username', password: 'Password', bypassList: 'Proxy bypass list', bypassHelp: 'These domains connect directly. Separate multiple domains with commas.', autoBypass: (count) => `${count} ${count === 1 ? 'domain was' : 'domains were'} added automatically. The proxy applies to AI model requests only.`, testing: 'Testing…', testCurrent: 'Test current configuration', proxyReachable: 'Proxy is reachable', proxyTestFailed: 'Proxy test failed', proxyTestError: 'Could not test proxy', + incognito: 'Incognito mode', incognitoHelp: 'Pause local memory, web search, and scheduled task triggers.', enableIncognito: 'Enable incognito mode', incognitoFailed: 'Could not change incognito mode', notifications: 'Send a system notification when finished', notificationsHelp: 'Notify when a response finishes or fails while the window is in the background.', notificationsFailed: 'Could not change notification settings', workspaceInstructions: 'Follow project instructions', workspaceInstructionsHelp: 'Automatically read existing AGENTS.md, CLAUDE.md, or GEMINI.md files in each project. Manage the files in their respective projects.', workspaceInstructionsFailed: 'Could not change project instruction settings', updateFailed: 'The setting was not applied. Try again later.', defaultModel: 'Default model', defaultModelHelp: 'Model used by new tasks.', notSet: 'Not set', saveDefaultModelFailed: 'Could not save the default model', defaultPermission: 'Default permission mode', defaultPermissionHelp: 'Initial permission mode for new tasks; it can be changed at any time.', saveDefaultPermissionFailed: 'Could not save the default permission mode', defaultThinking: 'Default thinking level', defaultThinkingHelp: 'Thinking level for new tasks; models that do not offer the chosen level use their own default.', followModelDefault: 'Follow model default', saveDefaultThinkingFailed: 'Could not save the default thinking level', proxy: 'Proxy server', proxyHelp: 'Configure a network proxy for AI model requests', enableProxy: 'Enable proxy server', saveNetworkFailed: 'Could not save network settings', proxyProtocol: 'Proxy protocol', serverAddress: 'Server address', port: 'Port', proxyAuth: 'Proxy authentication', proxyAuthHelp: 'Enable this when a username and password are required.', enableProxyAuth: 'Enable proxy authentication', username: 'Username', password: 'Password', bypassList: 'Proxy bypass list', bypassHelp: 'These domains connect directly. Separate multiple domains with commas.', autoBypass: (count) => `${count} ${count === 1 ? 'domain was' : 'domains were'} added automatically. The proxy applies to AI model requests only.`, testing: 'Testing…', testCurrent: 'Test current configuration', proxyReachable: 'Proxy is reachable', proxyTestFailed: 'Proxy test failed', proxyTestError: 'Could not test proxy', }, about: { - loadFailed: 'Could not load About information', loading: 'Loading About', unavailable: 'About information is unavailable', copied: 'Environment info copied', pasteHint: 'Paste it directly into an issue report', copyFailed: 'Copy failed', clipboardUnavailable: 'The clipboard is unavailable or access was denied.', devBuild: 'Local development build', packagedBuild: 'Release build', subtitle: 'A local-first AI assistant · Desktop runtime', privacyLabel: 'Privacy and security', privacyTitle: 'Local first · Private by default', privacyPoints: ['Conversations, settings, credentials, and Skill instructions stay in the local workspace.', 'Model keys stay in a local credential file; subscription tokens use secure system storage.', 'Maka sends no usage telemetry and contacts a model provider only when you enable it.', 'High-risk tool operations require explicit permission in the conversation.', 'Messages, tool calls, permission decisions, and mode changes are retained locally for each session.'], copying: 'Copying…', copyEnvironment: 'Copy environment info', copyHelp: 'Copy version and platform details to help diagnose an issue. The workspace path is excluded.', keyboardShortcuts: 'Keyboard shortcuts', keyboardShortcutsHelp: 'Every shortcut Maka responds to.', keyboardShortcutsOpen: 'View', reportIssueLabel: 'Report an issue', + loadFailed: 'Could not load About information', loading: 'Loading About', unavailable: 'About information is unavailable', copied: 'Environment info copied', pasteHint: 'Paste it directly into an issue report', copyFailed: 'Copy failed', clipboardUnavailable: 'The clipboard is unavailable or access was denied.', devBuild: 'Local development build', packagedBuild: 'Release build', subtitle: 'A local-first AI assistant · Desktop runtime', privacyLabel: 'Privacy and security', privacyTitle: 'Local first · Private by default', privacyPoints: ['Tasks, settings, credentials, and Skill instructions stay in the local workspace.', 'Model keys stay in a local credential file; subscription tokens use secure system storage.', 'Maka sends no usage telemetry and contacts a model provider only when you enable it.', 'High-risk tool operations require explicit permission in the task.', 'Messages, tool calls, permission decisions, and mode changes are retained locally for each task.'], copying: 'Copying…', copyEnvironment: 'Copy environment info', copyHelp: 'Copy version and platform details to help diagnose an issue. The workspace path is excluded.', keyboardShortcuts: 'Keyboard shortcuts', keyboardShortcutsHelp: 'Every shortcut Maka responds to.', keyboardShortcutsOpen: 'View', reportIssueLabel: 'Report an issue', updatesTitle: 'Software updates', checkForUpdates: 'Check for updates', checkingForUpdates: 'Checking…', diff --git a/apps/desktop/src/renderer/locales/settings-projects-copy.ts b/apps/desktop/src/renderer/locales/settings-projects-copy.ts index eb760d6835..78f433455a 100644 --- a/apps/desktop/src/renderer/locales/settings-projects-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-projects-copy.ts @@ -93,7 +93,7 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = { zh: { runtimeHost: { title: 'Runtime Host', - description: '选择运行会话、自动化和后台工作的 Host。Local 使用这台设备。', + description: '选择运行任务、自动化和后台工作的 Host。Local 使用这台设备。', selected: 'Host', selectedHelp: '切换会立即生效;连接失败时继续使用当前 Host', remoteTitle: '远程 Host', @@ -147,11 +147,11 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = { // Says all three layers of the rule in one sentence, because a help line // that only mentions the default would leave the user guessing what // happens before they set one. - sectionHelp: '新对话默认打开此项目;未设置时沿用上次使用的项目。任何对话都能在输入框旁临时切换。', + sectionHelp: '新任务默认打开此项目;未设置时沿用上次使用的项目。任何任务都能在输入框旁临时切换。', addProject: '添加项目', defaultBadge: '默认', setDefault: '设为默认', - setDefaultTitle: '新对话默认打开这个项目', + setDefaultTitle: '新任务默认打开这个项目', setDefaultDisabledTitle: '目录不可用,无法设为默认', setDefaultFailed: '设置默认项目失败', rename: '重命名', @@ -167,20 +167,20 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = { remove: '从 Maka 移除', removeConfirmTitle: '从 Maka 移除这个项目?', // The one thing a user actually fears here, stated first and plainly. - removeConfirmBody: '仅从 Maka 的项目列表移除,磁盘上的文件不受影响。该项目下已有的对话会移到"未归属"分组,不会被删除。', + removeConfirmBody: '仅从 Maka 的项目列表移除,磁盘上的文件不受影响。该项目下已有的任务会移到"未归属"分组,不会被删除。', removeConfirm: '移除', removeCancel: '取消', actionFailed: '操作失败', unavailable: '目录不可用', - defaultUnavailable: '原来的默认项目已不可用,新对话暂时沿用上次使用的项目。', + defaultUnavailable: '原来的默认项目已不可用,新任务暂时沿用上次使用的项目。', emptyTitle: '还没有项目', - emptyBody: '添加一个项目目录后,新对话就能默认从它打开,侧边栏也会按项目归类对话。', + emptyBody: '添加一个项目目录后,新任务就能默认从它打开,侧边栏也会按项目归类任务。', moreActions: (projectName: string) => `更多操作:${projectName}`, }, en: { runtimeHost: { title: 'Runtime Host', - description: 'Choose the Host that runs sessions, automations, and background work. Local uses this device.', + description: 'Choose the Host that runs tasks, automations, and background work. Local uses this device.', selected: 'Host', selectedHelp: 'Switches immediately; if connection fails, Desktop keeps using the current Host', remoteTitle: 'Remote Hosts', @@ -233,11 +233,11 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = { }, section: 'Workspace', sectionHelp: - 'New conversations open in the default project; without one, they reuse the project you last used. Any conversation can switch next to the input box.', + 'New tasks open in the default project; without one, they reuse the project you last used. You can switch any task to a different project next to the input box.', addProject: 'Add project', defaultBadge: 'Default', setDefault: 'Set as default', - setDefaultTitle: 'Open new conversations in this project', + setDefaultTitle: 'Open new tasks in this project', setDefaultDisabledTitle: 'The folder is unavailable, so this cannot be the default', setDefaultFailed: 'Could not set the default project', rename: 'Rename', @@ -251,16 +251,16 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = { remove: 'Remove from Maka', removeConfirmTitle: 'Remove this project from Maka?', removeConfirmBody: - 'This only removes it from Maka’s project list; the files on disk are untouched. Conversations under this project move to “Ungrouped” and are not deleted.', + 'This only removes it from Maka’s project list; the files on disk are untouched. Tasks under this project move to “Ungrouped” and are not deleted.', removeConfirm: 'Remove', removeCancel: 'Cancel', actionFailed: 'Action failed', unavailable: 'Folder unavailable', defaultUnavailable: - 'The default project is no longer available, so new conversations reuse the project you last used.', + 'The default project is no longer available, so new tasks reuse the project you last used.', emptyTitle: 'No projects yet', emptyBody: - 'Add a project folder and new conversations can start in it, with the sidebar grouping conversations by project.', + 'Add a project folder and new tasks can start in it, with the sidebar grouping tasks by project.', moreActions: (projectName: string) => `More actions for ${projectName}`, }, } satisfies UiCatalog; diff --git a/apps/desktop/src/renderer/locales/settings-provider-copy.ts b/apps/desktop/src/renderer/locales/settings-provider-copy.ts index 254797918d..cb09b62e64 100644 --- a/apps/desktop/src/renderer/locales/settings-provider-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-provider-copy.ts @@ -61,7 +61,7 @@ const zhCopy = { credentials: '连接', dangerZone: '删除连接', deleteRowHelp: '此操作不可撤销。', credentialsHelp: '密钥只保存在本机。', credentialsHelpAccount: '登录令牌只保存在本机。', - modelManagementHelp: '这些模型会出现在对话的模型选择器里。', + modelManagementHelp: '这些模型会出现在任务的模型选择器里。', ...zhCapabilitiesCopy, capabilitiesHelp: '声明每个已启用模型的思考档位、视觉与上下文窗口;保存后生效。', // Row affordances (settings-sidebar 的 InfoRow / ExpandableRow 语言):一行 @@ -89,7 +89,7 @@ const zhCopy = { ? '这会退出本机账号、删除 OAuth 凭据和模型连接;刷新后不会自动重新创建。' : '这会删除模型连接及其本机凭据;如需再次使用,需要重新添加。', isDefault - ? '它当前是默认连接;删除后默认模型会变成未设置,已有对话可能需要重新选择模型。' + ? '它当前是默认连接;删除后默认模型会变成未设置,已有任务可能需要重新选择模型。' : '', ].filter(Boolean).join(' '), connectionSuccess: (name: string) => `连接成功 · ${name}`, connectionFailed: (name: string) => `连接失败 · ${name}`, @@ -118,7 +118,7 @@ const zhCopy = { tabs: { all: '全部', recommended: '推荐', accounts: '账号', plans: '模型计划', api: 'API', aggregators: '聚合服务', local: '本地' }, loadFailed: '载入模型连接失败', loadingAria: '正在加载模型供应商', connections: '模型连接', retry: '点击重试。', empty: '还没有模型连接', - emptyHelp: '从下方选择一种连接方式开始。', default: '默认', setDefault: '设为默认', setDefaultTitle: '让新对话默认使用这个连接', setDefaultPending: '设置中…', setDefaultFailed: '设为默认失败', addHelp: '选择账号登录、模型计划、API、聚合服务或本地运行时。', + emptyHelp: '从下方选择一种连接方式开始。', default: '默认', setDefault: '设为默认', setDefaultTitle: '让新任务默认使用这个连接', setDefaultPending: '设置中…', setDefaultFailed: '设为默认失败', addHelp: '选择账号登录、模型计划、API、聚合服务或本地运行时。', categoriesAria: '模型供应商分类', searchPlaceholder: '搜索服务商', searchAria: '搜索模型服务商', noMatch: '没有匹配的服务商', clearSearch: '清除搜索', createSubtitle: '完成必要配置后,连接会出现在模型页上方。', connection: '模型连接', count: (value: number) => `· ${value}`, connectTitle: (name: string) => `连接 ${name}`, diff --git a/apps/desktop/src/renderer/locales/settings-shared-copy.ts b/apps/desktop/src/renderer/locales/settings-shared-copy.ts index 55d9069064..02589153e4 100644 --- a/apps/desktop/src/renderer/locales/settings-shared-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-shared-copy.ts @@ -72,7 +72,7 @@ const SETTINGS_SHARED_COPY_BY_LOCALE = { ready: '就绪', groups: { memorySources: '记忆', - memorySourcesHelp: 'Maka 会在对话中记住你确认过的信息,用于之后的回答。', + memorySourcesHelp: 'Maka 会在任务中记住你确认过的信息,用于之后的回答。', memoryDocument: '记忆文件与备份', memoryDocumentHelp: '记忆保存在本机 MEMORY.md 里;这里可以直接编辑原文或恢复备份。', memoryEntries: '已记住的内容', @@ -82,7 +82,7 @@ const SETTINGS_SHARED_COPY_BY_LOCALE = { searchBehavior: '搜索行为', searchBehaviorHelp: '什么时候发起搜索,以及每次取回多少结果。', dataLocation: '数据位置', - dataLocationHelp: '会话、设置、使用统计与凭据都以文件形式存放在本机的这个位置。', + dataLocationHelp: '任务、设置、使用统计与凭据都以文件形式存放在本机的这个位置。', reviewSchedule: '回顾计划', reviewScheduleHelp: '每日回顾的生成时间与使用的模型。', buildInfo: '版本信息', @@ -122,7 +122,7 @@ const SETTINGS_SHARED_COPY_BY_LOCALE = { searchBehavior: 'Search behavior', searchBehaviorHelp: 'When a search runs, and how many results it returns.', dataLocation: 'Data location', - dataLocationHelp: 'Conversations, settings, usage statistics, and credentials are stored as files in this location on your machine.', + dataLocationHelp: 'Tasks, settings, usage statistics, and credentials are stored as files in this location on your machine.', reviewSchedule: 'Review schedule', reviewScheduleHelp: 'When the daily review runs, and which model writes it.', buildInfo: 'Build info', diff --git a/apps/desktop/src/renderer/locales/settings-subagents-copy.ts b/apps/desktop/src/renderer/locales/settings-subagents-copy.ts index 03a27da730..3d26b4c94b 100644 --- a/apps/desktop/src/renderer/locales/settings-subagents-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-subagents-copy.ts @@ -114,7 +114,7 @@ const SETTINGS_SUBAGENTS_COPY_BY_LOCALE = { name: '显示名称', namePlaceholder: '快速代码阅读', id: 'subagent_id', - idDescription: '创建后保持不变,主 Agent 和历史会话会用它识别此配置。', + idDescription: '创建后保持不变,主 Agent 和历史任务会用它识别此配置。', idPlaceholder: 'fast-reader', description: '适用场景', descriptionPlaceholder: '适合快速、低成本地阅读大型仓库', @@ -138,7 +138,7 @@ const SETTINGS_SUBAGENTS_COPY_BY_LOCALE = { }, remove: { title: (name) => `删除“${name}”?`, - description: '主 Agent 将不再看到这个配置。已创建的子会话不会被删除。', + description: '主 Agent 将不再看到这个配置。已创建的子任务不会被删除。', confirm: '删除', cancel: '取消', }, @@ -195,7 +195,7 @@ const SETTINGS_SUBAGENTS_COPY_BY_LOCALE = { name: 'Display name', namePlaceholder: 'Fast code reader', id: 'subagent_id', - idDescription: 'Stable after creation. The main agent and session history use it to identify this preset.', + idDescription: 'Stable after creation. The main agent and task history use it to identify this preset.', idPlaceholder: 'fast-reader', description: 'When to use', descriptionPlaceholder: 'Fast, low-cost exploration of large repositories', @@ -219,7 +219,7 @@ const SETTINGS_SUBAGENTS_COPY_BY_LOCALE = { }, remove: { title: (name) => `Remove “${name}”?`, - description: 'The main agent will no longer see this preset. Existing child sessions are not deleted.', + description: 'The main agent will no longer see this preset. Existing child tasks are not deleted.', confirm: 'Remove', cancel: 'Cancel', }, diff --git a/apps/desktop/src/renderer/locales/settings-usage-copy.ts b/apps/desktop/src/renderer/locales/settings-usage-copy.ts index b1f64dacdd..c4c58c0dc4 100644 --- a/apps/desktop/src/renderer/locales/settings-usage-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-usage-copy.ts @@ -29,7 +29,7 @@ const SETTINGS_USAGE_COPY = { tables: { providersAria: '使用统计供应商统计表', modelsAria: '使用统计模型统计表', toolsAria: '使用统计工具统计表', pricingAria: '使用统计定价配置表', requestsAria: '使用统计请求日志表', providerHeaders: ['供应商', '请求', 'Token', '费用'], modelHeaders: ['模型', '请求', 'Token', '费用'], toolHeaders: ['工具', '调用', '成功', '错误', '平均耗时'], - pricingHeaders: ['供应商', '模型', '输入 / 1M', '输出 / 1M'], requestHeaders: ['时间', '类型', '对象', '会话', 'Token', '费用', '延迟', '状态'], + pricingHeaders: ['供应商', '模型', '输入 / 1M', '输出 / 1M'], requestHeaders: ['时间', '类型', '对象', '任务', 'Token', '费用', '延迟', '状态'], noPricing: '暂无定价覆盖配置', modelKind: '模型', toolKind: '工具', openSession: (label) => `打开 ${label}`, success: '成功', error: '错误', providerEmptyTitle: '暂无供应商用量', providerEmptyBody: '完成一次模型请求后,这里会按供应商聚合请求数、Token 与费用。', modelEmptyTitle: '暂无模型用量', modelEmptyBody: '完成一次模型请求后,这里会按模型聚合请求数、Token 与费用。', @@ -49,7 +49,7 @@ const SETTINGS_USAGE_COPY = { tables: { providersAria: 'Usage by provider', modelsAria: 'Usage by model', toolsAria: 'Usage by tool', pricingAria: 'Usage pricing configuration', requestsAria: 'Usage request log', providerHeaders: ['Provider', 'Requests', 'Tokens', 'Cost'], modelHeaders: ['Model', 'Requests', 'Tokens', 'Cost'], toolHeaders: ['Tool', 'Calls', 'Success', 'Errors', 'Average duration'], - pricingHeaders: ['Provider', 'Model', 'Input / 1M', 'Output / 1M'], requestHeaders: ['Time', 'Type', 'Target', 'Session', 'Tokens', 'Cost', 'Latency', 'Status'], + pricingHeaders: ['Provider', 'Model', 'Input / 1M', 'Output / 1M'], requestHeaders: ['Time', 'Type', 'Target', 'Task', 'Tokens', 'Cost', 'Latency', 'Status'], noPricing: 'No pricing overrides', modelKind: 'Model', toolKind: 'Tool', openSession: (label) => `Open ${label}`, success: 'Success', error: 'Error', providerEmptyTitle: 'No provider usage', providerEmptyBody: 'After a model request, provider request counts, tokens, and costs appear here.', modelEmptyTitle: 'No model usage', modelEmptyBody: 'After a model request, request counts, tokens, and costs appear here by model.', diff --git a/apps/desktop/src/renderer/locales/settings-web-search-copy.ts b/apps/desktop/src/renderer/locales/settings-web-search-copy.ts index 9e1b403c7b..c434795069 100644 --- a/apps/desktop/src/renderer/locales/settings-web-search-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-web-search-copy.ts @@ -24,15 +24,15 @@ const SETTINGS_WEB_SEARCH_COPY = { credentialsCleared: '已清空 Tavily 凭据', credentialsClearedDetail: '联网搜索已自动关闭。', credentialValid: 'Tavily 凭据可用', resultCount: (count) => `返回 ${count} 条结果。`, testFailed: '联网搜索测试失败', testError: '联网搜索测试出错', enabled: '启用联网搜索', enabledHelp: '启用后,Maka 可以在需要最新外部信息时调用所选搜索来源。', provider: '搜索来源', providerHelp: '优先复用当前模型的服务端搜索;不支持时可显式改用 Tavily。', providerModel: '当前模型', providerTavily: 'Tavily', - modelCredential: '主模型原生搜索', modelCredentialHelp: 'Maka 会在每个对话回合开始时,根据当前连接与精确模型决定是否把原生 web_search 注入同一次模型请求。不保存第二份搜索密钥,也不会从设置页另发一次模型调用。', + modelCredential: '主模型原生搜索', modelCredentialHelp: 'Maka 会在每个任务回合开始时,根据当前连接与精确模型决定是否把原生 web_search 注入同一次模型请求。不保存第二份搜索密钥,也不会从设置页另发一次模型调用。', statusAria: '联网搜索凭据状态', lastTest: '最近测试 ', enabledAria: '启用联网搜索', key: 'Tavily 密钥', envKeyHelp: '当前使用环境变量 TAVILY_API_KEY / MAKA_TAVILY_API_KEY;如需改用保存的密钥,请移除环境变量后重启。', savedKeyHelp: '密钥只保存在本机。申请地址:', envPlaceholder: '由环境变量提供', storedPlaceholder: '已保存(输入新密钥可替换)', keyPlaceholder: 'tvly-xxxxxxxx', keyAria: 'Tavily 密钥', actions: '凭据操作', actionsHelp: '保存后可以测试一次真实请求;清空凭据会同步关闭联网搜索。', saving: '保存中…', saveKey: '保存密钥', testing: '测试中…', testKey: '测试凭据', clearing: '清空中…', clearKey: '清空密钥', - testSearch: '测试搜索', testSearchHelp: '发一条真实查询,确认所选联网搜索来源是否配置可用。结果只显示在这里,不写入会话。', queryPlaceholder: '例如:本周 AI 产品发布动态', + testSearch: '测试搜索', testSearchHelp: '发一条真实查询,确认所选联网搜索来源是否配置可用。结果只显示在这里,不写入任务。', queryPlaceholder: '例如:本周 AI 产品发布动态', searching: '搜索中…', search: '搜索', queryFailed: (error) => `查询失败:${error}`, noResults: '没有结果。', resultsAria: '联网搜索真实查询结果', disabledReasons: { noKey: '先配置所选搜索来源', disabled: '先启用联网搜索', noQuery: '输入查询后再搜索' }, - statuses: { valid: '已验证', invalid_credentials: '密钥无效', rate_limited: '服务限流', timeout: '测试超时', network_error: '网络异常', not_configured: '等待配置', untested: '未测试', validEnabled: '已验证 · 已启用', validDisabled: '已验证 · 未启用', unknownEnabled: '未测试 · 已启用', modelEnabled: '已启用 · 按会话模型判定', modelDisabled: '当前模型来源 · 未启用' }, + statuses: { valid: '已验证', invalid_credentials: '密钥无效', rate_limited: '服务限流', timeout: '测试超时', network_error: '网络异常', not_configured: '等待配置', untested: '未测试', validEnabled: '已验证 · 已启用', validDisabled: '已验证 · 未启用', unknownEnabled: '未测试 · 已启用', modelEnabled: '已启用 · 按任务模型判定', modelDisabled: '当前模型来源 · 未启用' }, sources: { model: '来源:当前模型连接', envWithSaved: '来源:环境变量(已保存密钥备用)', env: '来源:环境变量', saved: '来源:本机已保存密钥', none: '来源:未配置' }, errors: { invalid_query: '请输入有效的搜索内容。', incognito_active: '无痕模式下无法使用联网搜索。', not_configured: '所选搜索来源尚未配置完成。', invalid_credentials: '搜索来源拒绝了当前凭据,请更新后重试。', rate_limited: '搜索请求过于频繁,请稍后重试。', network_error: '网络请求失败,请检查网络后重试。', timeout: '搜索请求超时,请重试。', unsupported_provider: '当前模型不支持服务端搜索,或 Maka 尚未实现它的协议;可改用 Tavily。', experimental_disabled: '联网搜索实验功能当前已关闭。' }, }, @@ -46,10 +46,10 @@ const SETTINGS_WEB_SEARCH_COPY = { envKeyHelp: 'Currently using TAVILY_API_KEY / MAKA_TAVILY_API_KEY from the environment. Remove the environment variable and restart to use a saved key.', savedKeyHelp: 'The key is stored only on this machine. Apply at:', envPlaceholder: 'Provided by environment variable', storedPlaceholder: 'Saved (enter a new key to replace)', keyPlaceholder: 'tvly-xxxxxxxx', keyAria: 'Tavily key', actions: 'Credential actions', actionsHelp: 'After saving, test with a real request. Clearing credentials also disables web search.', saving: 'Saving…', saveKey: 'Save key', testing: 'Testing…', testKey: 'Test credentials', clearing: 'Clearing…', clearKey: 'Clear key', - testSearch: 'Test search', testSearchHelp: 'Send a real query to confirm the selected web search source is configured and working. Results appear here only and are not written to the conversation.', queryPlaceholder: 'For example: AI product launches this week', + testSearch: 'Test search', testSearchHelp: 'Send a real query to confirm the selected web search source is configured and working. Results appear here only and are not written to the task.', queryPlaceholder: 'For example: AI product launches this week', searching: 'Searching…', search: 'Search', queryFailed: (error) => `Query failed: ${error}`, noResults: 'No results.', resultsAria: 'Web search live query results', disabledReasons: { noKey: 'Configure the selected search source first', disabled: 'Enable web search first', noQuery: 'Enter a query before searching' }, - statuses: { valid: 'Verified', invalid_credentials: 'Invalid key', rate_limited: 'Rate limited', timeout: 'Test timed out', network_error: 'Network error', not_configured: 'Needs setup', untested: 'Not tested', validEnabled: 'Verified · enabled', validDisabled: 'Verified · disabled', unknownEnabled: 'Not tested · enabled', modelEnabled: 'Enabled · checked per session model', modelDisabled: 'Current model source · disabled' }, + statuses: { valid: 'Verified', invalid_credentials: 'Invalid key', rate_limited: 'Rate limited', timeout: 'Test timed out', network_error: 'Network error', not_configured: 'Needs setup', untested: 'Not tested', validEnabled: 'Verified · enabled', validDisabled: 'Verified · disabled', unknownEnabled: 'Not tested · enabled', modelEnabled: 'Enabled · checked per task model', modelDisabled: 'Current model source · disabled' }, sources: { model: 'Source: current model connection', envWithSaved: 'Source: environment variable (saved key available as backup)', env: 'Source: environment variable', saved: 'Source: key saved on this device', none: 'Source: not configured' }, errors: { invalid_query: 'Enter a valid search query.', incognito_active: 'Web search is unavailable in incognito mode.', not_configured: 'The selected search source is not configured.', invalid_credentials: 'The search provider rejected the current credential. Update it and try again.', rate_limited: 'The search provider is receiving too many requests. Try again later.', network_error: 'The network request failed. Check your connection and try again.', timeout: 'The search request timed out. Try again.', unsupported_provider: 'The current model does not support hosted search, or Maka has not implemented its protocol yet. Select Tavily to continue.', experimental_disabled: 'The experimental web search feature is currently disabled.' }, }, diff --git a/apps/desktop/src/renderer/locales/shell-copy.ts b/apps/desktop/src/renderer/locales/shell-copy.ts index 3c521c6b39..bd604b6d4e 100644 --- a/apps/desktop/src/renderer/locales/shell-copy.ts +++ b/apps/desktop/src/renderer/locales/shell-copy.ts @@ -47,7 +47,7 @@ type CommandCopy = { }; const STATIC_COMMAND_KEYWORDS: Record = { - 'action:new-chat': ['new', 'chat', 'start', '新', '建', '对话'], + 'action:new-chat': ['new', 'chat', 'start', '新', '建', '任务'], 'action:side-chat': [ 'side', 'chat', @@ -56,7 +56,7 @@ const STATIC_COMMAND_KEYWORDS: Record = { 'explore', '侧边', '侧聊', - '对话', + '任务', '追问', ], 'action:new-deep-research': ['deep', 'research', 'explore', 'readonly', '研究', '深度', '探索', '只读'], @@ -66,7 +66,7 @@ const STATIC_COMMAND_KEYWORDS: Record = { 'theme:light': ['light', 'theme', '浅色', '主题'], 'theme:dark': ['dark', 'theme', '深色', 'night', '主题'], 'theme:auto': ['auto', 'system', 'theme', '跟随', '系统', '主题'], - 'nav:sessions': ['sessions', 'chats', '会话', '对话', 'left'], + 'nav:sessions': ['sessions', 'chats', '任务', '会话', '对话', 'left'], 'nav:automations': ['automations', 'plan', 'task', 'schedule', 'cron', '定时任务', '计划', '提醒'], 'nav:skills': ['skills', '技能'], 'nav:mcp': ['mcp', 'server', 'tools', '扩展', '工具'], @@ -74,7 +74,7 @@ const STATIC_COMMAND_KEYWORDS: Record = { 'diag:open-workspace': ['workspace', 'folder', 'open', 'finder', '工作区', '文件夹', '目录'], 'diag:open-project-folder': ['project', 'folder', 'open', 'finder', '项目', '目录', '文件夹'], 'diag:open-skills': ['skills', 'folder', 'open', 'finder', '技能', '文件夹'], - 'diag:export-conversation': ['export', 'markdown', 'copy', 'conversation', '导出', '对话', '剪贴板', 'md'], + 'diag:export-conversation': ['export', 'markdown', 'copy', 'conversation', '导出', '任务', '剪贴板', 'md'], 'diag:save-conversation-file': [ 'save', 'file', @@ -83,7 +83,7 @@ const STATIC_COMMAND_KEYWORDS: Record = { 'export', '保存', '文件', - '对话', + '任务', '导出', 'md', ], @@ -451,7 +451,7 @@ type ShellCopy = { }; const ZH_STATIC_COMMANDS: Record = { - 'action:new-chat': { label: '新建对话', hint: '开始新的会话', group: '操作' }, + 'action:new-chat': { label: '新建任务', hint: '开始新的任务', group: '操作' }, 'action:side-chat': { label: '打开侧边对话', hint: '⌥⌘S', @@ -472,7 +472,7 @@ const ZH_STATIC_COMMANDS: Record = { 'theme:light': { label: '主题 · 浅色', group: '主题' }, 'theme:dark': { label: '主题 · 深色', group: '主题' }, 'theme:auto': { label: '主题 · 跟随系统', group: '主题' }, - 'nav:sessions': { label: '侧栏 · 会话', group: '导航' }, + 'nav:sessions': { label: '侧栏 · 任务', group: '导航' }, 'nav:automations': { label: '侧栏 · 定时任务', group: '导航' }, 'nav:skills': { label: '打开 · 技能', group: '导航' }, 'nav:mcp': { label: '打开 · MCP', group: '导航' }, @@ -493,12 +493,12 @@ const ZH_STATIC_COMMANDS: Record = { group: '诊断', }, 'diag:export-conversation': { - label: '导出当前对话为 Markdown', + label: '导出当前任务为 Markdown', hint: '复制到剪贴板', group: '诊断', }, 'diag:save-conversation-file': { - label: '保存当前对话为 .md 文件', + label: '保存当前任务为 .md 文件', hint: '用系统保存对话框', group: '诊断', }, @@ -536,8 +536,8 @@ const ZH_STATIC_COMMANDS: Record = { const EN_STATIC_COMMANDS: Record = { 'action:new-chat': { - label: 'New conversation', - hint: 'Start a new conversation', + label: 'New task', + hint: 'Start a new task', group: 'Actions', }, 'action:side-chat': { @@ -568,7 +568,7 @@ const EN_STATIC_COMMANDS: Record = { 'theme:light': { label: 'Theme · Light', group: 'Theme' }, 'theme:dark': { label: 'Theme · Dark', group: 'Theme' }, 'theme:auto': { label: 'Theme · Follow system', group: 'Theme' }, - 'nav:sessions': { label: 'Sidebar · Conversations', group: 'Navigation' }, + 'nav:sessions': { label: 'Sidebar · Tasks', group: 'Navigation' }, 'nav:automations': { label: 'Sidebar · Automations', group: 'Navigation' }, 'nav:skills': { label: 'Open · Skills', group: 'Navigation' }, 'nav:mcp': { label: 'Open · MCP', group: 'Navigation' }, @@ -589,12 +589,12 @@ const EN_STATIC_COMMANDS: Record = { group: 'Diagnostics', }, 'diag:export-conversation': { - label: 'Copy conversation as Markdown', + label: 'Copy task as Markdown', hint: 'Copy to clipboard', group: 'Diagnostics', }, 'diag:save-conversation-file': { - label: 'Save conversation as an .md file', + label: 'Save task as an .md file', hint: 'Use the system save dialog', group: 'Diagnostics', }, @@ -638,6 +638,7 @@ const ZH_SETTINGS_SECTIONS: Record = { subagents: '子 Agent', usage: '使用统计', 'archived-tasks': '已归档任务', + 'import-tasks': '导入任务', memory: '记忆', 'daily-review': '每日回顾', 'bot-chat': '远程接入', @@ -656,6 +657,7 @@ const EN_SETTINGS_SECTIONS: Record = { subagents: 'Subagents', usage: 'Usage', 'archived-tasks': 'Archived tasks', + 'import-tasks': 'Import tasks', memory: 'Memory', 'daily-review': 'Daily Review', 'bot-chat': 'Remote Access', @@ -676,14 +678,14 @@ const SHELL_COPY_BY_LOCALE = { skills: 'Skills 文件夹', }, errors: { - messageRead: '对话内容暂时无法读取,请稍后重试。', - messageRefresh: '对话内容暂时无法刷新,请稍后重试。', + messageRead: '任务内容暂时无法读取,请稍后重试。', + messageRefresh: '任务内容暂时无法刷新,请稍后重试。', openPath: (path: string) => `无法打开${path},请稍后重试。`, workspaceUnavailableTitle: '工作目录不可用', workspaceUnavailableDescription: '工作目录不存在或无法访问。请选择有效目录创建新任务。', }, chatActions: { - newConversation: '新建对话', + newConversation: '新建任务', sendFailedTitle: '发送失败', sendFailedFallback: '消息暂时无法发送,请稍后重试。', skillInvocationBlockedTitle: 'Skill 调用失败,消息未发送', @@ -699,10 +701,10 @@ const SHELL_COPY_BY_LOCALE = { too_many_requests: 'Skill 调用请求超过 50 个上限', }, responseFailedTitle: '响应失败', - responseFailedFallback: '会话操作失败,请稍后重试。', - refreshFailedTitle: '刷新对话失败', - sessionStartFailedTitle: '开始对话失败', - sessionStartFailedFallback: '对话暂时无法开始,请稍后重试。', + responseFailedFallback: '任务操作失败,请稍后重试。', + refreshFailedTitle: '刷新任务失败', + sessionStartFailedTitle: '开始任务失败', + sessionStartFailedFallback: '任务暂时无法开始,请稍后重试。', }, projectActions: { currentProject: '当前项目', @@ -747,23 +749,23 @@ const SHELL_COPY_BY_LOCALE = { setDefaultSuccess: (name: string) => `已设为默认 · ${name}`, setDefaultFailedTitle: '切换默认失败', setDefaultFallback: '默认模型暂时无法切换,请稍后重试。', - newConversation: '新建对话', - conversationCopiedTitle: '已复制对话为 Markdown', + newConversation: '新建任务', + conversationCopiedTitle: '已复制任务为 Markdown', lineCount: (lines: number) => `${lines} 行 · 可粘贴到 Notion / Obsidian / GitHub`, copyFailedTitle: '复制失败', clipboardUnavailable: '剪贴板不可用', - conversationSavedTitle: '已保存当前对话', + conversationSavedTitle: '已保存当前任务', saveSummary: (lines: number, fileName: string) => `${lines} 行 · 保存为 ${fileName}`, saveFailedTitle: '保存失败', invalidExport: '导出内容无效', writeFailed: '无法写入选择的位置', - exportFallback: '导出当前对话失败,请稍后重试。', + exportFallback: '导出当前任务失败,请稍后重试。', memoryOpenFailedTitle: '无法打开 MEMORY.md', openFailedTitle: '打开失败', memoryOpenFallback: '无法打开 MEMORY.md,请稍后重试。', today: '今天', reviewCopiedTitle: '已复制今日回顾为 Markdown', - reviewSummary: (sessions: number, requests: number) => `${sessions} 个对话 · ${requests} 个请求`, + reviewSummary: (sessions: number, requests: number) => `${sessions} 个任务 · ${requests} 个请求`, reviewCopyFallback: '今日回顾暂时不可用,或剪贴板被系统拒绝。', reviewPastedTitle: '已追加今日回顾到输入框', reviewCopied: (label: string) => `已复制${label}回顾`, @@ -780,16 +782,16 @@ const SHELL_COPY_BY_LOCALE = { networkTestFallback: '网络代理测试暂时不可用,请稍后重试。', }, sessionRowActions: { - actionFallback: '会话操作失败,请稍后重试。', - flagFailedTitle: '标记会话失败', + actionFallback: '任务操作失败,请稍后重试。', + flagFailedTitle: '标记任务失败', unflagFailedTitle: '取消标记失败', - archiveFailedTitle: '归档会话失败', - unarchiveFailedTitle: '恢复会话失败', - renameFailedTitle: '重命名会话失败', - deleteFailedTitle: '删除会话失败', - currentConversation: '当前会话', + archiveFailedTitle: '归档任务失败', + unarchiveFailedTitle: '恢复任务失败', + renameFailedTitle: '重命名任务失败', + deleteFailedTitle: '删除任务失败', + currentConversation: '当前任务', deleteTitle: (name: string) => `删除 "${name}"`, - deleteDescription: '会话和全部消息会从磁盘上永久移除。该操作不可撤销。', + deleteDescription: '任务和全部消息会从磁盘上永久移除。该操作不可撤销。', deleteLabel: '删除', cancelLabel: '取消', deletedTitle: (name: string) => `已删除 ${name}`, @@ -899,7 +901,7 @@ const SHELL_COPY_BY_LOCALE = { permissionSwitched: (label: string) => `已切到 ${label}`, permissionFailedTitle: '切换权限模式失败', permissionFallback: '权限模式暂时无法切换,请稍后重试。', - modelSwitchedTitle: '已切换当前会话模型', + modelSwitchedTitle: '已切换当前任务模型', modelSwitchedDescription: (from, to) => `${from} → ${to}`, modelFailedTitle: '切换模型失败', modelFallback: '模型暂时无法切换,请稍后重试。', @@ -935,7 +937,7 @@ const SHELL_COPY_BY_LOCALE = { commandPalette: { label: '命令面板', searchLabel: '命令面板搜索', - placeholder: '搜索命令、设置项或会话…', + placeholder: '搜索命令、设置项或任务…', closeLabel: '关闭命令面板', resultsLabel: '命令面板结果', emptyTitle: '没有匹配的命令', @@ -948,7 +950,7 @@ const SHELL_COPY_BY_LOCALE = { settings: '设置', permissions: '权限', connections: '连接', - conversations: '会话', + conversations: '任务', }, staticKeywords: STATIC_COMMAND_KEYWORDS, commands: ZH_STATIC_COMMANDS, @@ -989,7 +991,7 @@ const SHELL_COPY_BY_LOCALE = { rows: [ { keys: ['⌘', 'K'], - description: '打开命令面板(跳会话 / 设置 / 主题等)', + description: '打开命令面板(跳任务 / 设置 / 主题等)', }, { keys: ['?'], description: '打开 / 关闭此快捷键面板' }, { keys: ['⌘', 'N'], description: '新建任务' }, @@ -1006,18 +1008,14 @@ const SHELL_COPY_BY_LOCALE = { ], }, { - heading: '会话列表', + heading: '任务列表', rows: [ - { keys: ['Tab'], description: '在会话与导航之间移动焦点' }, - { keys: ['↑', '↓'], description: '上下移动聚焦的会话' }, + { keys: ['Tab'], description: '在任务与导航之间移动焦点' }, + { keys: ['↑', '↓'], description: '上下移动聚焦的任务' }, { keys: ['Home', 'End'], description: '跳到列表顶部 / 底部' }, - { - keys: ['←', '→'], - description: '在会话 / 已标记 / 已归档之间循环切换', - }, - { keys: ['Enter'], description: '打开聚焦的会话' }, + { keys: ['Enter'], description: '打开聚焦的任务' }, { keys: ['Delete'], description: '弹出删除确认(永远不静默删除)' }, - { keys: ['F'], description: '聚焦会话列表搜索框(按 Esc 清空)' }, + { keys: ['F'], description: '聚焦任务列表搜索框(按 Esc 清空)' }, ], }, { @@ -1031,7 +1029,7 @@ const SHELL_COPY_BY_LOCALE = { heading: '面板调整', rows: [ { keys: ['Tab'], description: '聚焦左右分割条' }, - { keys: ['←', '→'], description: '微调会话列表宽度(±10 px)' }, + { keys: ['←', '→'], description: '微调任务列表宽度(±10 px)' }, { keys: ['Shift', '←', '→'], description: '快速调整(±50 px)' }, { keys: ['Home', 'End'], description: '直接拉到最小 / 最大宽度' }, ], @@ -1040,21 +1038,21 @@ const SHELL_COPY_BY_LOCALE = { }, chrome: { windowActions: '窗口快捷操作', - searchConversations: '搜索对话', + searchConversations: '搜索任务', expandSidebar: '展开侧边栏', collapseSidebar: '收起侧边栏', newTask: '新任务', - expandWorkbar: '展开会话工作栏', - collapseWorkbar: '收起会话工作栏', + expandWorkbar: '展开任务工作栏', + collapseWorkbar: '收起任务工作栏', workspaceActions: '工作区辅助操作', }, app: { - loadingWorkbarLabel: '正在加载会话工作栏', - loadingWorkbar: '正在加载会话工作栏…', + loadingWorkbarLabel: '正在加载任务工作栏', + loadingWorkbar: '正在加载任务工作栏…', useSkillPrompt: (skillName: string) => `使用 ${skillName} 技能:`, - newConversation: '新建对话', + newConversation: '新建任务', compactErrorTitle: '压缩失败', - compactErrorFallback: '对话暂时无法压缩,请稍后重试。', + compactErrorFallback: '任务暂时无法压缩,请稍后重试。', slashCommands: { compact: { name: '压缩上下文', description: '压缩旧历史并保留当前任务' }, graph: { name: '使用 Graph', description: '查看、切换或单次运行 Graph' }, @@ -1062,14 +1060,14 @@ const SHELL_COPY_BY_LOCALE = { swarm: { name: '使用 Swarm', description: '查看、切换或单次运行 Swarm' }, }, sideChatUnavailableTitle: '暂时无法打开侧边对话', - sideChatUnavailableDescription: '请先在主会话中发送一条消息,再使用 /side。', + sideChatUnavailableDescription: '请先在主任务中发送一条消息,再使用 /side。', sideChatContextPendingTitle: '先处理待发送的上下文', sideChatContextPendingDescription: '当前 Composer 还有附件、引用或文件 mention。请先发送或移除它们,再使用 /side。', resumeStartedTitle: '已开始安全恢复', resumeStartedDescription: '正在从最后一个完整执行边界继续', resumeFailedTitle: '恢复失败', - resumeFailedFallback: '无法启动安全恢复,请检查会话状态后重试。', + resumeFailedFallback: '无法启动安全恢复,请检查任务状态后重试。', goalClearFailedTitle: '停止目标失败', goalClearFailedFallback: '目标仍可能继续运行,请立即重试。', appearanceLoadErrorTitle: '载入外观设置失败', @@ -1079,7 +1077,7 @@ const SHELL_COPY_BY_LOCALE = { memoryErrorFallback: '本地记忆状态暂时无法刷新,请稍后重试。', openModelSettings: '打开设置 · 模型', sidebarCollapsed: '侧边栏已收起', - resizeConversationList: '调整对话列表宽度', + resizeConversationList: '调整任务列表宽度', skipErrorTitle: '跳过失败', tryAgainLater: '请稍后重试。', updateInstallFailedTitle: '无法安装更新', @@ -1093,17 +1091,17 @@ const SHELL_COPY_BY_LOCALE = { updateRetryFailedFallback: '请稍后重试,或手动下载最新版本。', loading: '加载中', goToModels: '去模型', - boundaryUnreadableTitle: '暂时读不到这个对话的权限', - boundaryUnreadableDetail: '在读到之前,这里暂时不能输入。可以重试,或先切换到别的对话。', + boundaryUnreadableTitle: '暂时读不到这个任务的权限', + boundaryUnreadableDetail: '在读到之前,这里暂时不能输入。可以重试,或先切换到别的任务。', boundaryUnreadableRetry: '重试', boundaryUnreadableRetrying: '重试中…', permissionModeChanging: '权限模式正在切换,完成后再继续操作。', - permissionModeStreaming: '当前对话正在流式输出,等结束后再切换权限模式。', - permissionModeRunning: '当前对话正在运行,等结束后再切换权限模式。', + permissionModeStreaming: '当前任务正在流式输出,等结束后再切换权限模式。', + permissionModeRunning: '当前任务正在运行,等结束后再切换权限模式。', permissionModeWaiting: '当前有工具调用正在等待确认,处理后再切换权限模式。', planModeChanging: 'Plan Mode 正在切换,完成后再继续操作。', - planModeStreaming: '当前对话正在流式输出,等结束后再切换 Plan Mode。', - planModeRunning: '当前对话正在运行,等结束后再切换 Plan Mode。', + planModeStreaming: '当前任务正在流式输出,等结束后再切换 Plan Mode。', + planModeRunning: '当前任务正在运行,等结束后再切换 Plan Mode。', planModeWaiting: '当前有工具调用正在等待确认,处理后再切换 Plan Mode。', planModeFailedTitle: '切换 Plan Mode 失败', planModeFallback: 'Plan Mode 暂时无法切换,请稍后重试。', @@ -1115,8 +1113,8 @@ const SHELL_COPY_BY_LOCALE = { planModeExecutionActiveTitle: '计划仍在执行', planModeExecutionActiveDescription: '请先中断当前执行,再进入 Plan Mode 调整方案。', swarmModeChanging: 'Swarm Mode 正在切换,完成后再继续操作。', - swarmModeStreaming: '当前对话正在流式输出,等结束后再切换 Swarm Mode。', - swarmModeRunning: '当前对话正在运行,等结束后再切换 Swarm Mode。', + swarmModeStreaming: '当前任务正在流式输出,等结束后再切换 Swarm Mode。', + swarmModeRunning: '当前任务正在运行,等结束后再切换 Swarm Mode。', swarmModeWaiting: '当前有工具调用正在等待确认,处理后再切换 Swarm Mode。', swarmModeFailedTitle: '切换 Swarm Mode 失败', swarmModeFallback: 'Swarm Mode 暂时无法切换,请稍后重试。', @@ -1124,15 +1122,15 @@ const SHELL_COPY_BY_LOCALE = { swarmModeDisabledTitle: 'Swarm Mode 未开启', swarmModeStatusDescription: '使用 /swarm on、/swarm off,或 /swarm <任务> 单次运行。', graphModeChanging: 'Graph Mode 正在切换,完成后再继续操作。', - graphModeStreaming: '当前对话正在流式输出,等结束后再切换 Graph Mode。', - graphModeRunning: '当前对话正在运行,等结束后再切换 Graph Mode。', + graphModeStreaming: '当前任务正在流式输出,等结束后再切换 Graph Mode。', + graphModeRunning: '当前任务正在运行,等结束后再切换 Graph Mode。', graphModeWaiting: '当前有工具调用正在等待确认,处理后再切换 Graph Mode。', graphModeFailedTitle: '切换 Graph Mode 失败', graphModeFallback: 'Graph Mode 暂时无法切换,请稍后重试。', graphModeEnabledTitle: 'Graph Mode 已开启', graphModeDisabledTitle: 'Graph Mode 未开启', graphModeStatusDescription: '使用 /graph on、/graph off,或 /graph <任务> 单次运行。', - resizeWorkbar: '调整会话工作栏宽度', + resizeWorkbar: '调整任务工作栏宽度', }, }, en: { @@ -1144,15 +1142,15 @@ const SHELL_COPY_BY_LOCALE = { skills: 'Skills folder', }, errors: { - messageRead: 'Conversation content is temporarily unavailable. Try again later.', - messageRefresh: 'Conversation content could not be refreshed. Try again later.', + messageRead: 'Task content is temporarily unavailable. Try again later.', + messageRefresh: 'Task content could not be refreshed. Try again later.', openPath: (path: string) => `Could not open the ${path}. Try again later.`, workspaceUnavailableTitle: 'Working directory unavailable', workspaceUnavailableDescription: 'The working directory does not exist or cannot be accessed. Select a valid folder for a new task.', }, chatActions: { - newConversation: 'New conversation', + newConversation: 'New task', sendFailedTitle: 'Message not sent', sendFailedFallback: 'The message could not be sent. Try again later.', skillInvocationBlockedTitle: 'Skill invocation failed; message not sent', @@ -1169,10 +1167,10 @@ const SHELL_COPY_BY_LOCALE = { too_many_requests: 'more than 50 distinct Skill invocation requests', }, responseFailedTitle: 'Response failed', - responseFailedFallback: 'The conversation action failed. Try again later.', - refreshFailedTitle: 'Could not refresh conversation', - sessionStartFailedTitle: 'Could not start conversation', - sessionStartFailedFallback: 'The conversation could not be started. Try again later.', + responseFailedFallback: 'The task action failed. Try again later.', + refreshFailedTitle: 'Could not refresh task', + sessionStartFailedTitle: 'Could not start task', + sessionStartFailedFallback: 'The task could not be started. Try again later.', }, projectActions: { currentProject: 'Current project', @@ -1217,23 +1215,23 @@ const SHELL_COPY_BY_LOCALE = { setDefaultSuccess: (name: string) => `Set as default · ${name}`, setDefaultFailedTitle: 'Could not change default', setDefaultFallback: 'The default model could not be changed. Try again later.', - newConversation: 'New conversation', - conversationCopiedTitle: 'Conversation copied as Markdown', + newConversation: 'New task', + conversationCopiedTitle: 'Task copied as Markdown', lineCount: (lines: number) => `${lines} lines · Ready for Notion / Obsidian / GitHub`, copyFailedTitle: 'Copy failed', clipboardUnavailable: 'Clipboard unavailable', - conversationSavedTitle: 'Conversation saved', + conversationSavedTitle: 'Task saved', saveSummary: (lines: number, fileName: string) => `${lines} lines · Saved as ${fileName}`, saveFailedTitle: 'Save failed', invalidExport: 'The export content is invalid', writeFailed: 'The selected location could not be written', - exportFallback: 'The conversation could not be exported. Try again later.', + exportFallback: 'The task could not be exported. Try again later.', memoryOpenFailedTitle: 'Could not open MEMORY.md', openFailedTitle: 'Open failed', memoryOpenFallback: 'MEMORY.md could not be opened. Try again later.', today: 'Today', reviewCopiedTitle: "Today's review copied as Markdown", - reviewSummary: (sessions: number, requests: number) => `${sessions} conversations · ${requests} requests`, + reviewSummary: (sessions: number, requests: number) => `${sessions} tasks · ${requests} requests`, reviewCopyFallback: "Today's review is unavailable, or the clipboard was denied.", reviewPastedTitle: "Today's review added to the composer", reviewCopied: (label: string) => `${label} review copied`, @@ -1250,17 +1248,17 @@ const SHELL_COPY_BY_LOCALE = { networkTestFallback: 'Network proxy testing is temporarily unavailable. Try again later.', }, sessionRowActions: { - actionFallback: 'The conversation action failed. Try again later.', - flagFailedTitle: 'Could not flag conversation', + actionFallback: 'The task action failed. Try again later.', + flagFailedTitle: 'Could not flag task', unflagFailedTitle: 'Could not remove flag', - archiveFailedTitle: 'Could not archive conversation', - unarchiveFailedTitle: 'Could not restore conversation', - renameFailedTitle: 'Could not rename conversation', - deleteFailedTitle: 'Could not delete conversation', - currentConversation: 'Current conversation', + archiveFailedTitle: 'Could not archive task', + unarchiveFailedTitle: 'Could not restore task', + renameFailedTitle: 'Could not rename task', + deleteFailedTitle: 'Could not delete task', + currentConversation: 'Current task', deleteTitle: (name: string) => `Delete "${name}"`, deleteDescription: - 'The conversation and all of its messages will be permanently removed from disk. This cannot be undone.', + 'The task and all of its messages will be permanently removed from disk. This cannot be undone.', deleteLabel: 'Delete', cancelLabel: 'Cancel', deletedTitle: (name: string) => `Deleted ${name}`, @@ -1371,7 +1369,7 @@ const SHELL_COPY_BY_LOCALE = { permissionSwitched: (label: string) => `Switched to ${label}`, permissionFailedTitle: 'Could not change permission mode', permissionFallback: 'The permission mode could not be changed. Try again later.', - modelSwitchedTitle: 'Conversation model changed', + modelSwitchedTitle: 'Task model changed', modelSwitchedDescription: (from, to) => `${from} → ${to}`, modelFailedTitle: 'Could not change model', modelFallback: 'The model could not be changed. Try again later.', @@ -1407,7 +1405,7 @@ const SHELL_COPY_BY_LOCALE = { commandPalette: { label: 'Command palette', searchLabel: 'Search the command palette', - placeholder: 'Search commands, settings, or conversations…', + placeholder: 'Search commands, settings, or tasks…', closeLabel: 'Close command palette', resultsLabel: 'Command palette results', emptyTitle: 'No matching commands', @@ -1420,7 +1418,7 @@ const SHELL_COPY_BY_LOCALE = { settings: 'Settings', permissions: 'Permissions', connections: 'Connections', - conversations: 'Conversations', + conversations: 'Tasks', }, staticKeywords: STATIC_COMMAND_KEYWORDS, commands: EN_STATIC_COMMANDS, @@ -1467,7 +1465,7 @@ const SHELL_COPY_BY_LOCALE = { rows: [ { keys: ['⌘', 'K'], - description: 'Open the command palette (conversations, Settings, themes, and more)', + description: 'Open the command palette (tasks, Settings, themes, and more)', }, { keys: ['?'], description: 'Open or close this shortcuts panel' }, { keys: ['⌘', 'N'], description: 'Create a new task' }, @@ -1487,32 +1485,28 @@ const SHELL_COPY_BY_LOCALE = { ], }, { - heading: 'Conversation list', + heading: 'Task list', rows: [ { keys: ['Tab'], - description: 'Move focus between conversations and navigation', + description: 'Move focus between tasks and navigation', }, { keys: ['↑', '↓'], - description: 'Move through focused conversations', + description: 'Move through focused tasks', }, { keys: ['Home', 'End'], description: 'Jump to the top or bottom of the list', }, - { - keys: ['←', '→'], - description: 'Cycle through Conversations, Flagged, and Archived', - }, - { keys: ['Enter'], description: 'Open the focused conversation' }, + { keys: ['Enter'], description: 'Open the focused task' }, { keys: ['Delete'], description: 'Open the delete confirmation (never delete silently)', }, { keys: ['F'], - description: 'Focus conversation search (press Esc to clear)', + description: 'Focus task search (press Esc to clear)', }, ], }, @@ -1535,7 +1529,7 @@ const SHELL_COPY_BY_LOCALE = { { keys: ['Tab'], description: 'Focus the left or right splitter' }, { keys: ['←', '→'], - description: 'Adjust conversation-list width (±10 px)', + description: 'Adjust task-list width (±10 px)', }, { keys: ['Shift', '←', '→'], @@ -1551,21 +1545,21 @@ const SHELL_COPY_BY_LOCALE = { }, chrome: { windowActions: 'Window shortcuts', - searchConversations: 'Search conversations', + searchConversations: 'Search tasks', expandSidebar: 'Expand sidebar', collapseSidebar: 'Collapse sidebar', newTask: 'New task', - expandWorkbar: 'Expand conversation workbar', - collapseWorkbar: 'Collapse conversation workbar', + expandWorkbar: 'Expand task workbar', + collapseWorkbar: 'Collapse task workbar', workspaceActions: 'Workspace actions', }, app: { - loadingWorkbarLabel: 'Loading conversation workbar', - loadingWorkbar: 'Loading conversation workbar…', + loadingWorkbarLabel: 'Loading task workbar', + loadingWorkbar: 'Loading task workbar…', useSkillPrompt: (skillName: string) => `Use the ${skillName} skill: `, - newConversation: 'New conversation', + newConversation: 'New task', compactErrorTitle: 'Compaction failed', - compactErrorFallback: 'The conversation could not be compacted. Try again later.', + compactErrorFallback: 'The task could not be compacted. Try again later.', slashCommands: { compact: { name: 'Compact context', description: 'Compact older history while preserving the current task' }, graph: { name: 'Use Graph', description: 'Inspect, switch, or run Graph once' }, @@ -1574,14 +1568,14 @@ const SHELL_COPY_BY_LOCALE = { }, sideChatUnavailableTitle: 'Side chat is not available yet', sideChatUnavailableDescription: - 'Send a message in the main conversation before using /side.', + 'Send a message in the main task before using /side.', sideChatContextPendingTitle: 'Resolve pending context first', sideChatContextPendingDescription: 'The Composer still has attachments, quotes, or file mentions. Send or remove them before using /side.', resumeStartedTitle: 'Safe recovery started', resumeStartedDescription: 'Continuing from the last complete execution boundary', resumeFailedTitle: 'Recovery failed', - resumeFailedFallback: 'Safe recovery could not start. Check the conversation state and try again.', + resumeFailedFallback: 'Safe recovery could not start. Check the task state and try again.', goalClearFailedTitle: 'Could not stop the goal', goalClearFailedFallback: 'The goal may still be running. Try again now.', appearanceLoadErrorTitle: 'Could not load appearance settings', @@ -1591,7 +1585,7 @@ const SHELL_COPY_BY_LOCALE = { memoryErrorFallback: 'Local memory status could not be refreshed. Try again later.', openModelSettings: 'Open Settings · Models', sidebarCollapsed: 'Sidebar is collapsed', - resizeConversationList: 'Resize conversation list', + resizeConversationList: 'Resize task list', skipErrorTitle: 'Could not skip onboarding', tryAgainLater: 'Try again later.', updateInstallFailedTitle: 'Could not install update', @@ -1605,19 +1599,19 @@ const SHELL_COPY_BY_LOCALE = { updateRetryFailedFallback: 'Try again later, or download the latest version manually.', loading: 'Loading', goToModels: 'Go to Models', - boundaryUnreadableTitle: 'Could not read this conversation’s permissions', + boundaryUnreadableTitle: 'Could not read this task’s permissions', boundaryUnreadableDetail: - 'Until they can be read, you cannot type here. Try again, or switch to another conversation.', + 'Until they can be read, you cannot type here. Try again, or switch to another task.', boundaryUnreadableRetry: 'Try again', boundaryUnreadableRetrying: 'Trying again…', permissionModeChanging: 'The permission mode is changing. Wait for it to finish before continuing.', permissionModeStreaming: - 'This conversation is streaming. Wait for it to finish before changing the permission mode.', - permissionModeRunning: 'This conversation is running. Wait for it to finish before changing the permission mode.', + 'This task is streaming. Wait for it to finish before changing the permission mode.', + permissionModeRunning: 'This task is running. Wait for it to finish before changing the permission mode.', permissionModeWaiting: 'A tool call is waiting for confirmation. Respond before changing the permission mode.', planModeChanging: 'Plan Mode is changing. Wait for it to finish before continuing.', - planModeStreaming: 'This conversation is streaming. Wait for it to finish before changing Plan Mode.', - planModeRunning: 'This conversation is running. Wait for it to finish before changing Plan Mode.', + planModeStreaming: 'This task is streaming. Wait for it to finish before changing Plan Mode.', + planModeRunning: 'This task is running. Wait for it to finish before changing Plan Mode.', planModeWaiting: 'A tool call is waiting for confirmation. Respond before changing Plan Mode.', planModeFailedTitle: 'Could not change Plan Mode', planModeFallback: 'Plan Mode could not be changed. Try again later.', @@ -1629,8 +1623,8 @@ const SHELL_COPY_BY_LOCALE = { planModeExecutionActiveTitle: 'The plan is still running', planModeExecutionActiveDescription: 'Interrupt the active execution before entering Plan Mode to revise it.', swarmModeChanging: 'Swarm Mode is changing. Wait for it to finish before continuing.', - swarmModeStreaming: 'This conversation is streaming. Wait for it to finish before changing Swarm Mode.', - swarmModeRunning: 'This conversation is running. Wait for it to finish before changing Swarm Mode.', + swarmModeStreaming: 'This task is streaming. Wait for it to finish before changing Swarm Mode.', + swarmModeRunning: 'This task is running. Wait for it to finish before changing Swarm Mode.', swarmModeWaiting: 'A tool call is waiting for confirmation. Respond before changing Swarm Mode.', swarmModeFailedTitle: 'Could not change Swarm Mode', swarmModeFallback: 'Swarm Mode could not be changed. Try again later.', @@ -1638,15 +1632,15 @@ const SHELL_COPY_BY_LOCALE = { swarmModeDisabledTitle: 'Swarm Mode is off', swarmModeStatusDescription: 'Use /swarm on, /swarm off, or /swarm for one turn.', graphModeChanging: 'Graph Mode is changing. Wait for it to finish before continuing.', - graphModeStreaming: 'This conversation is streaming. Wait for it to finish before changing Graph Mode.', - graphModeRunning: 'This conversation is running. Wait for it to finish before changing Graph Mode.', + graphModeStreaming: 'This task is streaming. Wait for it to finish before changing Graph Mode.', + graphModeRunning: 'This task is running. Wait for it to finish before changing Graph Mode.', graphModeWaiting: 'A tool call is waiting for confirmation. Respond before changing Graph Mode.', graphModeFailedTitle: 'Could not change Graph Mode', graphModeFallback: 'Graph Mode could not be changed. Try again later.', graphModeEnabledTitle: 'Graph Mode is on', graphModeDisabledTitle: 'Graph Mode is off', graphModeStatusDescription: 'Use /graph on, /graph off, or /graph for one turn.', - resizeWorkbar: 'Resize conversation workbar', + resizeWorkbar: 'Resize task workbar', }, }, } satisfies UiCatalog; diff --git a/apps/desktop/src/renderer/locales/shell-remaining-copy.ts b/apps/desktop/src/renderer/locales/shell-remaining-copy.ts index a151b6bf3f..83bd9fd179 100644 --- a/apps/desktop/src/renderer/locales/shell-remaining-copy.ts +++ b/apps/desktop/src/renderer/locales/shell-remaining-copy.ts @@ -55,7 +55,7 @@ const zhCopy = { refreshFailed: "刷新模型连接失败", refreshFallback: "模型连接暂时无法刷新,请稍后重试。", }, - tasks: { loadFailed: "任务载入失败,请重试。" }, + tasks: { loadFailed: "待办载入失败,请重试。" }, projects: { ungrouped: "未归属项目" }, models: { unavailable: "当前不可用" }, overlays: { @@ -131,7 +131,7 @@ const enCopy: ShellRemainingCopy = { refreshFallback: "Model connections are temporarily unavailable. Try again later.", }, - tasks: { loadFailed: "Failed to load tasks. Try again." }, + tasks: { loadFailed: "Failed to load the to-do list. Try again." }, projects: { ungrouped: "No project" }, models: { unavailable: "Currently unavailable" }, overlays: { diff --git a/apps/desktop/src/renderer/nav-selection.ts b/apps/desktop/src/renderer/nav-selection.ts index b0503870c4..942ba3329b 100644 --- a/apps/desktop/src/renderer/nav-selection.ts +++ b/apps/desktop/src/renderer/nav-selection.ts @@ -3,7 +3,6 @@ import type { ExtensionModule, NavModuleMemory, NavSelection, - SessionFilter, } from '@maka/ui'; import { safeLocalStorageGet } from './browser-storage.js'; @@ -19,18 +18,11 @@ const DEFAULT_MODULE_MEMORY: NavModuleMemory = { function defaultNavigationState(): NavigationState { return { - selection: { section: 'sessions', filter: 'chats' }, + selection: { section: 'sessions' }, moduleMemory: { ...DEFAULT_MODULE_MEMORY }, }; } -function isSessionFilter(value: unknown): value is SessionFilter { - // A stored `archived` — written while the rail still had that filter row — - // fails here, and `parseSelection` falls back to the default `chats`. That is - // the migration: the destination it named no longer exists. - return value === 'chats' || value === 'flagged'; -} - function isExtensionModule(value: unknown): value is ExtensionModule { return value === 'skills' || value === 'mcp'; } @@ -41,10 +33,11 @@ function isAutomationModule(value: unknown): value is AutomationModule { function parseSelection(value: unknown): NavSelection | null { if (!value || typeof value !== 'object') return null; - const candidate = value as { section?: unknown; filter?: unknown; module?: unknown }; - if (candidate.section === 'sessions' && isSessionFilter(candidate.filter)) { - return { section: 'sessions', filter: candidate.filter }; - } + const candidate = value as { section?: unknown; module?: unknown }; + // Any stored `filter` is dropped rather than validated: `archived` named a + // destination that moved to Settings (#2985) and `flagged` was never written, + // so every stored value maps to the one session section that exists (#2984). + if (candidate.section === 'sessions') return { section: 'sessions' }; if (candidate.section === 'extensions' && isExtensionModule(candidate.module)) { return { section: 'extensions', module: candidate.module }; } diff --git a/apps/desktop/src/renderer/session-nav-filter.ts b/apps/desktop/src/renderer/session-nav-filter.ts index 2f7b2f73d1..9a6ec31110 100644 --- a/apps/desktop/src/renderer/session-nav-filter.ts +++ b/apps/desktop/src/renderer/session-nav-filter.ts @@ -1,15 +1,13 @@ import type { SessionSummary } from '@maka/core/session'; -import type { NavSelection } from '@maka/ui'; -export function sessionMatchesNavSelection( - session: SessionSummary, - selection: NavSelection, -): boolean { - const filter = selection.section === 'sessions' ? selection.filter : 'chats'; - switch (filter) { - case 'flagged': - return session.isFlagged && !session.isArchived; - case 'chats': - return !session.isArchived; - } +/** + * Which sessions the rail lists. Archived tasks are managed in Settings › 活动 › + * 已归档任务 (#2985), so the rail shows everything else. + * + * This used to switch on `NavSelection.filter`. That filter is gone (#2984): its + * last two values were a destination that moved to Settings and a value nothing + * ever selected, which left one branch reachable — this one. + */ +export function sessionMatchesRail(session: SessionSummary): boolean { + return !session.isArchived; } diff --git a/apps/desktop/src/renderer/session-status-presentation.ts b/apps/desktop/src/renderer/session-status-presentation.ts index f8e8ad4149..9387048a0c 100644 --- a/apps/desktop/src/renderer/session-status-presentation.ts +++ b/apps/desktop/src/renderer/session-status-presentation.ts @@ -1,39 +1,24 @@ /** - * Pure presentation helpers for SessionStatus + SessionBlockedReason - * used by the sidebar and chat header. + * Renderer-side presentation rules that only Desktop has: which blocked reasons + * are worth acting on, and what to offer after a turn fails. * - * Separated from the React component layer so the copy + tone mapping - * can be unit-tested without a DOM, mirroring `session-health-notice.ts` - * pattern. + * Separated from the React component layer so the rules can be unit-tested + * without a DOM, mirroring the `session-health-notice.ts` pattern. * - * Two contracts enforced here: - * - * 1. **Generalized blocked-reason copy** (@kenji review): UI labels - * never expose the raw `SessionBlockedReason` enum string. The - * mapping below is the canonical translation. New blocked reasons - * must extend the core enum AND this matrix together, or the - * `unknown` fallback applies. - * - * 2. **Status tone matrix**: each SessionStatus has a single visual - * tone (`accent / warning / destructive / info / success / muted`) - * consumed by both the SessionStatusIcon and the chat-header - * status badge. Aligns with the existing session-health-notice tone - * vocabulary. + * Turning a `SessionStatus` into a label and a dot, and a `SessionBlockedReason` + * into copy, is NOT here — both live in `@maka/ui`'s file of the same name, + * which is also where the contract that a UI label never shows a raw enum + * identifier is stated and enforced. This file used to re-export those and + * document a tone matrix "consumed by both the SessionStatusIcon and the + * chat-header status badge", naming two consumers that do not exist; the tone + * layer and the re-exports are gone (#2984). */ import { SANDBOX_BOUNDARY_RESTART_CLOSURE_CLASS } from '@maka/core/sandbox-boundary'; -import type { SessionBlockedReason, SessionStatus, SessionSummary } from '@maka/core/session'; +import type { SessionBlockedReason, SessionSummary } from '@maka/core/session'; import type { UiLocale } from '@maka/core/ui-locale'; -import { - describeBlockedReason, - presentSessionStatus, - type SessionStatusPresentation, - type SessionStatusTone, -} from '@maka/ui'; import { getDesktopConversationCopy } from './locales/conversation-copy.js'; import { describeSessionErrorReason } from './session-error-presentation.js'; -export { presentSessionStatus } from '@maka/ui'; -export { describeBlockedReason } from '@maka/ui'; /** * Session-level "blocked" is only worth interrupting the user when @@ -71,7 +56,8 @@ export function normalizeSessionSummaryForDisplay(session: SessionSummary): Sess /** * Generalized Chinese phrasing for a failed turn's `errorClass` - * Mirrors `describeBlockedReason()`; UI must never display the raw enum identifier. + * Mirrors `describeBlockedReason()` in `@maka/ui`, under the same rule: a UI + * label must never display the raw enum identifier. * * Recognized classes are written by the runtime via `classifyError()`, * `classifyHttpStatus()`, and `event.reason` / `event.code`. The set is diff --git a/apps/desktop/src/renderer/settings/import-tasks-settings-page.tsx b/apps/desktop/src/renderer/settings/import-tasks-settings-page.tsx new file mode 100644 index 0000000000..bedcf00594 --- /dev/null +++ b/apps/desktop/src/renderer/settings/import-tasks-settings-page.tsx @@ -0,0 +1,377 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Banner } from '@astryxdesign/core/Banner'; +import { Button } from '@astryxdesign/core/Button'; +import { CheckboxInput } from '@astryxdesign/core/CheckboxInput'; +import { EmptyState } from '@astryxdesign/core/EmptyState'; +import { List, ListItem } from '@astryxdesign/core/List'; +import { SegmentedControl, SegmentedControlItem } from '@astryxdesign/core/SegmentedControl'; +import { HStack, VStack } from '@astryxdesign/core/Stack'; +import { uiLocaleToIntlLocale } from '@maka/core/ui-locale'; +import type { ExternalSessionSummary } from '@maka/core/external-session'; +import type { SessionSummary } from '@maka/core/session'; +import { Spinner, useMountedRef, useUiLocale } from '@maka/ui'; +import { ICON_SIZE, MessageSquare } from '@maka/ui/icons'; +import { getExternalSessionImportCopy } from '../locales/external-session-import-copy.js'; +import { localizedShellErrorMessage } from '../locales/shell-copy.js'; +import { SettingsPage, SettingsSection } from './settings-section'; + +type CatalogState = { + sessions: ExternalSessionSummary[]; + nextCursor: string | null; +}; + +const EMPTY_CATALOG: CatalogState = { sessions: [], nextCursor: null }; + +/** + * Settings · 活动 · 导入任务 — bring another local agent's conversations in as + * Maka tasks. + * + * This used to be a rail row that opened a modal over the conversation. Import + * is not navigation: it is a rare setup errand you do once per conversation you + * care about, it needs a source, a filter and a paged directory to work + * through, and none of that belongs in a 260px column of the tasks you are + * actually working on. It sits beside 已归档任务 for the same reason that page + * exists — both are about the task catalog rather than the task in front of + * you. + * + * Reading the source directory is Desktop Main's job, so this page holds no + * session state of its own: it lists through `window.maka.externalSessions` and + * hands the imported `SessionSummary` back to the shell, which is what owns + * navigating to it. + * + * Deliberately NOT here: keeping an imported task in sync with its source. + * The importer is a one-shot conversion (`packages/storage/src/external-sessions.ts`) + * and nothing behind it watches the source for changes; a "keep in sync" + * control would be a promise no coordinator can keep. + */ +export function ImportTasksSettingsPage(props: { + /** Hands the freshly imported task to the shell, which opens it. */ + onImported(session: SessionSummary): void; +}) { + const locale = useUiLocale(); + const copy = getExternalSessionImportCopy(locale); + const mountedRef = useMountedRef(); + const [adapterIds, setAdapterIds] = useState([]); + const [adapterId, setAdapterId] = useState(null); + const [includeArchived, setIncludeArchived] = useState(false); + const [catalog, setCatalog] = useState(EMPTY_CATALOG); + const [sourceLoading, setSourceLoading] = useState(false); + const [sourceResolved, setSourceResolved] = useState(false); + const [catalogLoading, setCatalogLoading] = useState(false); + const [loadingMore, setLoadingMore] = useState(false); + const [importingId, setImportingId] = useState(null); + const [sourceError, setSourceError] = useState(null); + const [catalogError, setCatalogError] = useState(null); + const [importError, setImportError] = useState(null); + /** + * Conversations whose import neither succeeded nor failed — Desktop Main + * could not confirm the outcome. Re-importing one is how you end up with two + * copies of the same conversation, so those rows stay disabled for the rest + * of this page's lifetime and the banner says where to look instead. + */ + const [uncertainIds, setUncertainIds] = useState>(new Set()); + // Only the newest list request may write. Switching source or toggling the + // archived filter while a page is in flight would otherwise land the old + // source's rows under the new source's label. + const requestGeneration = useRef(0); + + const loadSources = useCallback(async () => { + const generation = ++requestGeneration.current; + setSourceLoading(true); + setSourceResolved(false); + setSourceError(null); + setCatalogError(null); + setImportError(null); + setAdapterIds([]); + setAdapterId(null); + setCatalog(EMPTY_CATALOG); + try { + const result = await window.maka.externalSessions.listSources(); + if (generation !== requestGeneration.current) return; + setAdapterIds(result.adapterIds); + setAdapterId(result.adapterIds[0] ?? null); + } catch (error) { + if (generation !== requestGeneration.current) return; + setSourceError(localizedShellErrorMessage(error, copy.loadFailedFallback, locale)); + } finally { + if (generation === requestGeneration.current) { + setSourceLoading(false); + setSourceResolved(true); + } + } + }, [copy.loadFailedFallback, locale]); + + const loadCatalog = useCallback( + async (sourceId: string, cursor?: string) => { + const generation = ++requestGeneration.current; + const append = cursor !== undefined; + if (append) setLoadingMore(true); + else { + setCatalogLoading(true); + setCatalog(EMPTY_CATALOG); + } + setCatalogError(null); + setImportError(null); + try { + const result = await window.maka.externalSessions.list({ + adapterId: sourceId, + includeArchived, + ...(cursor === undefined ? {} : { cursor }), + }); + if (generation !== requestGeneration.current) return; + setCatalog((current) => ({ + sessions: append ? [...current.sessions, ...result.sessions] : result.sessions, + nextCursor: result.nextCursor, + })); + } catch (error) { + if (generation !== requestGeneration.current) return; + setCatalogError(localizedShellErrorMessage(error, copy.loadFailedFallback, locale)); + } finally { + if (generation === requestGeneration.current) { + setCatalogLoading(false); + setLoadingMore(false); + } + } + }, + [copy.loadFailedFallback, includeArchived, locale], + ); + + useEffect(() => { + void loadSources(); + }, [loadSources]); + + useEffect(() => { + if (adapterId === null) return; + void loadCatalog(adapterId); + }, [adapterId, includeArchived, loadCatalog]); + + const dateFormatter = useMemo( + () => + new Intl.DateTimeFormat(uiLocaleToIntlLocale(locale), { + dateStyle: 'medium', + timeStyle: 'short', + }), + [locale], + ); + + const importConversation = useCallback( + async (sourceSessionId: string) => { + if (adapterId === null || importingId !== null) return; + setImportingId(sourceSessionId); + setImportError(null); + try { + const outcome = await window.maka.externalSessions.import({ + adapterId, + sourceSessionId, + }); + // Navigating away from Settings unmounts this page while the import is + // still in Desktop Main's hands. The conversion itself completes and is + // stored either way; what must not happen is a completion from a page + // the user has left steering the shell somewhere they did not ask for. + if (!mountedRef.current) return; + if (!outcome.ok) { + setUncertainIds((current) => new Set(current).add(sourceSessionId)); + return; + } + props.onImported(outcome.session); + } catch (error) { + if (!mountedRef.current) return; + setImportError(localizedShellErrorMessage(error, copy.importFailedFallback, locale)); + } finally { + if (mountedRef.current) setImportingId(null); + } + }, + [adapterId, copy.importFailedFallback, importingId, locale, mountedRef, props], + ); + + const noSource = sourceResolved && !sourceLoading && !sourceError && adapterIds.length === 0; + const catalogEmpty = + adapterId !== null && !catalogLoading && !catalogError && catalog.sessions.length === 0; + + if (sourceLoading) { + return ( + +
+ + + {copy.loading} + +
+
+ ); + } + + if (sourceError) { + return ( + + void loadSources()} /> + } + /> + + ); + } + + // No adapter on this machine is the whole page: there is no source to pick, + // no filter that would change anything, and nothing to list. + if (noSource) { + return ( + + + + ); + } + + return ( + + {/* One source is the common case — Codex is the only adapter that ships + — and a segmented control with a single segment is a control nobody + can operate. The description names the source instead, and the switch + appears when there is actually something to switch between. */} + + + {adapterIds.length > 1 && adapterId !== null && ( + + {adapterIds.map((id) => ( + + ))} + + )} + + + + + + + {catalogError && ( + void loadCatalog(adapterId)} + /> + ) + } + /> + )} + + {importError && ( + + )} + + {uncertainIds.size > 0 && ( + + )} + + {catalogLoading && ( +
+ + + {copy.loading} + +
+ )} + + {catalogEmpty && ( + + )} + + {catalog.sessions.length > 0 && ( + + {catalog.sessions.map((session) => { + const timestamp = session.updatedAt ?? session.createdAt; + const description = [ + session.cwd, + timestamp !== undefined ? dateFormatter.format(timestamp) : null, + session.archived ? copy.archived : null, + ] + .filter(Boolean) + .join(' · '); + return ( + 0 ? description : undefined} + startContent={