From 08eb20eca467c0d2b98210cb2a51ed7ba3f75c57 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 19 Jul 2026 16:18:33 -0400 Subject: [PATCH 1/8] refac --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61f1bcae..53dab53b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- 🤖 **Gemini is now its own coding agent.** Choose Gemini as an agent and Computer will start the Gemini CLI with its Google sign-in flow, keep the chat resumable, and handle image attachments like the other coding agents. +- 🤖 **Gemini is now its own coding agent.** Choose Gemini as an agent and Computer will start it with its Google sign-in flow, keep the chat resumable, and handle image attachments like the other coding agents. ### Fixed From 845cf81110e2c7821181cff8a370c59b5a984a6f Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 20 Jul 2026 11:32:15 -0400 Subject: [PATCH 2/8] refac --- LICENSE | 5 ----- 1 file changed, 5 deletions(-) diff --git a/LICENSE b/LICENSE index bdc1bfd9..2cfb6da4 100644 --- a/LICENSE +++ b/LICENSE @@ -18,11 +18,6 @@ Attribution elements are any elements identifying the origin of the software, such as logos, icons, visual marks, the product name, copyright notices, attribution statements, and about screens or version information. -Multi-Tenant Use - -You may not host, deploy, manage, or otherwise facilitate multiple instances -of the software or its derivative works as a service. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE From 556ad116b53754fd99f97a42c9cd2196738e3704 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 20 Jul 2026 21:27:59 -0400 Subject: [PATCH 3/8] refac --- .../src/lib/components/Admin/Web.svelte | 40 ++++++++++++++++++- cptr/frontend/src/lib/i18n/locales/en.json | 6 +++ cptr/frontend/static/browser-encoder.js | 11 +++-- cptr/utils/browser/viewer.py | 28 ++++++++++++- 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/cptr/frontend/src/lib/components/Admin/Web.svelte b/cptr/frontend/src/lib/components/Admin/Web.svelte index a399ea3c..2543a872 100644 --- a/cptr/frontend/src/lib/components/Admin/Web.svelte +++ b/cptr/frontend/src/lib/components/Admin/Web.svelte @@ -51,7 +51,10 @@ let firecrawlBaseUrl = $state('https://api.firecrawl.dev'); let browserUseApiKey = $state(''); let browserUseBaseUrl = $state('https://api.browser-use.com'); + type BrowserEncoderHardwareAcceleration = 'no-preference' | 'prefer-hardware' | 'prefer-software'; let browserQualityDefault = $state<'low' | 'balanced' | 'crisp'>('balanced'); + let browserEncoderHardwareAcceleration = + $state('no-preference'); let browserQualityProfiles = $state({ low: { bitrate: 3000000, frame_rate: 15 }, balanced: { bitrate: 6000000, frame_rate: 24 }, @@ -118,6 +121,14 @@ } browserQualityMaxResolution = Number(config['browser.quality.max_resolution']) || 1080; browserQualityMaxBitrate = Number(config['browser.quality.max_bitrate']) || 12000000; + if ( + config['browser.encoder.hardware_acceleration'] === 'prefer-hardware' || + config['browser.encoder.hardware_acceleration'] === 'prefer-software' + ) { + browserEncoderHardwareAcceleration = config[ + 'browser.encoder.hardware_acceleration' + ] as BrowserEncoderHardwareAcceleration; + } } catch { toast.error($t('admin.failedToLoadConfig')); } @@ -159,6 +170,7 @@ 'browser.browser_use_api_key': browserUseApiKey, 'browser.browser_use_base_url': browserUseBaseUrl, 'browser.quality.default': browserQualityDefault, + 'browser.encoder.hardware_acceleration': browserEncoderHardwareAcceleration, 'browser.quality.profiles': browserQualityProfiles, 'browser.quality.max_resolution': browserQualityMaxResolution, 'browser.quality.max_bitrate': browserQualityMaxBitrate @@ -571,7 +583,9 @@ class="h-7 w-24 rounded-lg border border-gray-200 bg-gray-100 px-2 text-xs text-gray-700 outline-none dark:border-white/8 dark:bg-white/6 dark:text-gray-300" /> - +
@@ -587,6 +601,30 @@ class="h-7 w-24 rounded-lg border border-gray-200 bg-gray-100 px-2 text-xs text-gray-700 outline-none dark:border-white/8 dark:bg-white/6 dark:text-gray-300" />
+
+
+ {$t('admin.browserEncoderHardwareAcceleration')} +

+ {$t('admin.browserEncoderHardwareAccelerationHint')} +

+
+ +
{:else} diff --git a/cptr/frontend/src/lib/i18n/locales/en.json b/cptr/frontend/src/lib/i18n/locales/en.json index 2debee86..573fabda 100644 --- a/cptr/frontend/src/lib/i18n/locales/en.json +++ b/cptr/frontend/src/lib/i18n/locales/en.json @@ -425,6 +425,11 @@ "admin.browserQualityMaxBitrate": "Max bitrate", "admin.browserQualityFrameRate": "Frame rate", "admin.browserQualityAllowDsf2": "Allow DSF 2", + "admin.browserEncoderHardwareAcceleration": "Encoder acceleration", + "admin.browserEncoderHardwareAccelerationHint": "Auto uses hardware when available and falls back to software.", + "admin.browserEncoderAccelerationAuto": "Auto", + "admin.browserEncoderAccelerationHardware": "Hardware only", + "admin.browserEncoderAccelerationSoftware": "Software only", "admin.browserQualityLow": "Low", "admin.browserQualityBalanced": "Balanced", "admin.browserQualityCrisp": "Crisp", @@ -1043,6 +1048,7 @@ "models.paramValue": "value", "editor.binaryFile": "Binary file", "editor.noChanges": "No changes", + "editor.closeUnsavedConfirm": "Close \"{{name}}\" without saving changes?", "editor.noUncommittedModifications": "This file has no uncommitted modifications.", "git.loadChangesFailed": "Failed to load git changes", "git.unableToLoadChanges": "Unable to load changes", diff --git a/cptr/frontend/static/browser-encoder.js b/cptr/frontend/static/browser-encoder.js index accb4421..7a81b370 100644 --- a/cptr/frontend/static/browser-encoder.js +++ b/cptr/frontend/static/browser-encoder.js @@ -2,6 +2,11 @@ const params = new URLSearchParams(location.hash.slice(1)); const windowCapture = params.get('window') === '1'; const audioEnabled = params.get('audio') === '1'; + const accelerationOptions = new Set(['no-preference', 'prefer-hardware', 'prefer-software']); + const requestedAcceleration = params.get('hardwareAcceleration') || 'no-preference'; + const hardwareAcceleration = accelerationOptions.has(requestedAcceleration) + ? requestedAcceleration + : 'no-preference'; let socket; let encoder; let audioEncoder; @@ -65,7 +70,7 @@ } async function configure(width, height) { - const key = `${width}x${height}:${quality.bitrate}:${quality.frame_rate}`; + const key = `${width}x${height}:${quality.bitrate}:${quality.frame_rate}:${hardwareAcceleration}`; if (configured === key) return; const config = { codec: 'avc1.42E028', @@ -73,12 +78,12 @@ height, bitrate: quality.bitrate, framerate: quality.frame_rate, - hardwareAcceleration: 'prefer-hardware', + hardwareAcceleration, latencyMode: 'realtime', avc: { format: 'annexb' } }; const support = await VideoEncoder.isConfigSupported(config); - if (!support.supported) throw new Error(`H.264 WebCodecs encoding is unavailable at ${width}×${height}; lower Admin > Web > Streaming quality > Advanced > Max height`); + if (!support.supported) throw new Error(`H.264 WebCodecs encoding is unavailable at ${width}×${height} with ${hardwareAcceleration}; lower Admin > Web > Streaming quality > Advanced > Max height or change encoder acceleration`); if (encoder) { try { await encoder.flush(); } catch {} encoder.close(); diff --git a/cptr/utils/browser/viewer.py b/cptr/utils/browser/viewer.py index 83a7de33..46a9b0ff 100644 --- a/cptr/utils/browser/viewer.py +++ b/cptr/utils/browser/viewer.py @@ -32,6 +32,7 @@ EventHandler = Callable[[dict[str, Any]], Awaitable[None]] QUALITY_PRESETS = ("low", "balanced", "crisp") +ENCODER_HARDWARE_ACCELERATION = ("no-preference", "prefer-hardware", "prefer-software") DEFAULT_QUALITY_PROFILES = { "low": {"bitrate": 3_000_000, "frame_rate": 15}, "balanced": {"bitrate": 6_000_000, "frame_rate": 24}, @@ -616,8 +617,17 @@ async def start(self, session: Any, origin: str, *, cdp_url: str = "") -> Chrome base = local_origin(origin) ws_scheme = "wss" if base.startswith("https:") else "ws" ws_url = f"{ws_scheme}://{urlsplit(base).netloc}/api/browser/sessions/{session.session_id}/encoder" + hardware_acceleration = await Config.get("browser.encoder.hardware_acceleration") + if hardware_acceleration not in ENCODER_HARDWARE_ACCELERATION: + hardware_acceleration = "no-preference" fragment = urlencode( - {"session": session.session_id, "token": token, "ws": ws_url, "audio": "1"} + { + "session": session.session_id, + "token": token, + "ws": ws_url, + "audio": "1", + "hardwareAcceleration": hardware_acceleration, + } ) controller_url = f"{base}/browser-encoder.html#{fragment}" controller_id, controller_cdp = await host.create_target(controller_url) @@ -684,7 +694,17 @@ async def connect_personal(self, owner: str, origin: str, cdp_url: str) -> Perso base = local_origin(origin) ws_scheme = "wss" if base.startswith("https:") else "ws" ws_url = f"{ws_scheme}://{urlsplit(base).netloc}/api/browser/sessions/personal/encoder" - fragment = urlencode({"token": token, "ws": ws_url, "window": "1"}) + hardware_acceleration = await Config.get("browser.encoder.hardware_acceleration") + if hardware_acceleration not in ENCODER_HARDWARE_ACCELERATION: + hardware_acceleration = "no-preference" + fragment = urlencode( + { + "token": token, + "ws": ws_url, + "window": "1", + "hardwareAcceleration": hardware_acceleration, + } + ) controller_id, controller_cdp = await host.create_target( f"{base}/browser-encoder.html#{fragment}", new_window=True ) @@ -1041,12 +1061,16 @@ async def _restart_encoder(self, viewer: ChromeViewer) -> None: f"{ws_scheme}://{urlsplit(base).netloc}/api/browser/sessions/" f"{viewer.session.session_id}/encoder" ) + hardware_acceleration = await Config.get("browser.encoder.hardware_acceleration") + if hardware_acceleration not in ENCODER_HARDWARE_ACCELERATION: + hardware_acceleration = "no-preference" fragment = urlencode( { "session": viewer.session.session_id, "token": viewer.encoder_token, "ws": ws_url, "audio": "1", + "hardwareAcceleration": hardware_acceleration, } ) viewer.controller_id, viewer.controller_cdp = await viewer.host.create_target( From 86581fa78f7866e948e0d866c98462c875948bf9 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 20 Jul 2026 21:29:27 -0400 Subject: [PATCH 4/8] refac --- cptr/utils/agents/detection.py | 46 ++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/cptr/utils/agents/detection.py b/cptr/utils/agents/detection.py index 3ef05614..98dbb567 100644 --- a/cptr/utils/agents/detection.py +++ b/cptr/utils/agents/detection.py @@ -9,8 +9,8 @@ import re import shutil import socket -from dataclasses import dataclass, asdict from contextlib import suppress +from dataclasses import dataclass, asdict from typing import Any import httpx @@ -59,6 +59,39 @@ def _resolve_command(command: str) -> str | None: return shutil.which(command) +def _find_claude_desktop_command() -> str | None: + if os.name == "nt": + appdata = os.environ.get("APPDATA") + root = os.path.join(appdata, "Claude", "claude-code") if appdata else None + relative_paths = (("claude.exe",),) + else: + root = os.path.join( + os.path.expanduser("~"), + "Library", + "Application Support", + "Claude", + "claude-code", + ) + relative_paths = ( + ("claude.app", "Contents", "MacOS", "claude"), + ("claude",), + ) + + candidates: list[tuple[tuple[int, int, int], str]] = [] + if root is not None and os.path.isdir(root): + for name in os.listdir(root): + version_dir = os.path.join(root, name) + version = _parse_version_tuple(name) + if version is None or not os.path.isdir(version_dir): + continue + for relative_path in relative_paths: + candidate = os.path.join(version_dir, *relative_path) + if os.path.isfile(candidate) and os.access(candidate, os.X_OK): + candidates.append((version, candidate)) + break + return max(candidates)[1] if candidates else None + + async def _run_probe( argv: list[str], timeout: float = 3.0, @@ -82,7 +115,10 @@ async def _run_probe( async def detect_profile(profile: dict[str, Any]) -> AgentDetection: - command = _resolve_command(str(profile.get("command") or "")) + raw_command = str(profile.get("command") or "").strip() + command = _resolve_command(raw_command) + if command is None and profile.get("agent") == "claude_code" and raw_command == "claude": + command = _find_claude_desktop_command() if command is None: return AgentDetection("not_found", None, None, "Command not found") @@ -549,6 +585,12 @@ async def get_agent_status(app_state=None, refresh: bool = False) -> dict[str, A and (mode != "auto" or detected.status == "ready") ) effective_profile = dict(profile) + if ( + detected.command + and profile.get("agent") == "claude_code" + and _resolve_command(str(profile.get("command") or "")) is None + ): + effective_profile["command"] = detected.command effective_profile["models"] = models if models and effective_profile.get("default_model") not in models: effective_profile["default_model"] = models[0] From b130c27138429e3e9abf1dc4ed53a00903ec906a Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 20 Jul 2026 21:34:59 -0400 Subject: [PATCH 5/8] refac --- .../src/lib/components/ConfirmDialog.svelte | 94 +++++++++++++++++++ cptr/frontend/src/lib/i18n/locales/en.json | 2 + cptr/frontend/src/lib/stores.ts | 33 ++++++- cptr/frontend/src/lib/stores/confirm.ts | 42 +++++++++ cptr/frontend/src/routes/+layout.svelte | 2 + 5 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 cptr/frontend/src/lib/components/ConfirmDialog.svelte create mode 100644 cptr/frontend/src/lib/stores/confirm.ts diff --git a/cptr/frontend/src/lib/components/ConfirmDialog.svelte b/cptr/frontend/src/lib/components/ConfirmDialog.svelte new file mode 100644 index 00000000..4e5f94b7 --- /dev/null +++ b/cptr/frontend/src/lib/components/ConfirmDialog.svelte @@ -0,0 +1,94 @@ + + + + +{#if $confirmDialog} + +
settleConfirm(false)} + onkeydown={() => {}} + > + + +
+{/if} + + diff --git a/cptr/frontend/src/lib/i18n/locales/en.json b/cptr/frontend/src/lib/i18n/locales/en.json index 573fabda..950916d4 100644 --- a/cptr/frontend/src/lib/i18n/locales/en.json +++ b/cptr/frontend/src/lib/i18n/locales/en.json @@ -1048,7 +1048,9 @@ "models.paramValue": "value", "editor.binaryFile": "Binary file", "editor.noChanges": "No changes", + "editor.closeUnsavedTitle": "Unsaved changes", "editor.closeUnsavedConfirm": "Close \"{{name}}\" without saving changes?", + "editor.closeWithoutSaving": "Close without saving", "editor.noUncommittedModifications": "This file has no uncommitted modifications.", "git.loadChangesFailed": "Failed to load git changes", "git.unableToLoadChanges": "Unable to load changes", diff --git a/cptr/frontend/src/lib/stores.ts b/cptr/frontend/src/lib/stores.ts index 15f831c0..2b4dc2dd 100644 --- a/cptr/frontend/src/lib/stores.ts +++ b/cptr/frontend/src/lib/stores.ts @@ -28,6 +28,7 @@ import { import { listSessions, createSession, deleteSession } from '$lib/apis/terminal'; import { createBrowserSession, deleteBrowserSession, listBrowserSessions } from '$lib/apis/browser'; import { changeLocale, i18next } from '$lib/i18n'; +import { requestConfirm } from '$lib/stores/confirm'; import { streamingChatTabs } from '$lib/stores/chat'; import { keybindings, loadKeybindings } from '$lib/stores/keybindings'; import { defaultPwaPreferences, type PwaPreferences } from '$lib/intents/types'; @@ -1176,7 +1177,7 @@ export async function openBrowserTab( } catch (error) { console.error('Failed to create browser session:', error); toast.error(error instanceof Error ? error.message : 'Failed to open Browser'); - closeTab(tabId, gid); + closeTab(tabId, gid, { skipUnsavedPrompt: true }); } } @@ -1219,18 +1220,39 @@ export function openChatTab(chatId?: string, targetGroupId?: string): void { })); } -export function closeTab(tabId: string, groupId?: string): void { +export async function closeTab( + tabId: string, + groupId?: string, + options: { skipUnsavedPrompt?: boolean } = {} +): Promise { const ws = get(currentWorkspace); - if (!ws) return; + if (!ws) return false; // Find the group containing this tab const gid = groupId ?? ws.groups.find((g) => g.tabs.some((t) => t.id === tabId))?.id ?? ws.activeGroupId; const group = ws.groups.find((g) => g.id === gid); - if (!group) return; + if (!group) return false; const tab = group.tabs.find((t) => t.id === tabId); - if (!tab || tab.permanent) return; + if (!tab || tab.permanent) return false; + + if (tab.unsaved && !options.skipUnsavedPrompt) { + const confirmed = await requestConfirm({ + title: i18next.t('editor.closeUnsavedTitle', { + defaultValue: 'Unsaved changes' + }), + message: i18next.t('editor.closeUnsavedConfirm', { + name: tab.label, + defaultValue: 'Close "{{name}}" without saving changes?' + }), + cancelLabel: i18next.t('common.cancel', { defaultValue: 'Cancel' }), + confirmLabel: i18next.t('editor.closeWithoutSaving', { + defaultValue: 'Close without saving' + }) + }); + if (!confirmed) return false; + } if (tab.type === 'terminal' && tab.sessionId) { deleteSession(tab.sessionId); @@ -1296,6 +1318,7 @@ export function closeTab(tabId: string, groupId?: string): void { activeGroupId: activeGroupStillExists ? ws.activeGroupId : newGroups[0].id }; }); + return true; } export function setActiveTab(tabId: string, groupId?: string): void { diff --git a/cptr/frontend/src/lib/stores/confirm.ts b/cptr/frontend/src/lib/stores/confirm.ts new file mode 100644 index 00000000..fed77509 --- /dev/null +++ b/cptr/frontend/src/lib/stores/confirm.ts @@ -0,0 +1,42 @@ +import { get, writable } from 'svelte/store'; + +export interface ConfirmOptions { + title: string; + message: string; + confirmLabel?: string; + cancelLabel?: string; +} + +export interface ConfirmRequest extends Required { + id: number; + resolve: (confirmed: boolean) => void; +} + +export const confirmDialog = writable(null); + +let nextId = 0; + +export function requestConfirm(options: ConfirmOptions): Promise { + if (typeof window === 'undefined') return Promise.resolve(false); + + const existing = get(confirmDialog); + if (existing) existing.resolve(false); + + return new Promise((resolve) => { + confirmDialog.set({ + id: ++nextId, + title: options.title, + message: options.message, + confirmLabel: options.confirmLabel ?? 'Confirm', + cancelLabel: options.cancelLabel ?? 'Cancel', + resolve + }); + }); +} + +export function settleConfirm(confirmed: boolean): void { + const request = get(confirmDialog); + if (!request) return; + confirmDialog.set(null); + request.resolve(confirmed); +} diff --git a/cptr/frontend/src/routes/+layout.svelte b/cptr/frontend/src/routes/+layout.svelte index 4700a977..de498be9 100644 --- a/cptr/frontend/src/routes/+layout.svelte +++ b/cptr/frontend/src/routes/+layout.svelte @@ -12,6 +12,7 @@ import SettingsModal from '$lib/components/SettingsModal.svelte'; import AuthScreen from '$lib/components/AuthScreen.svelte'; import ChangelogModal from '$lib/components/ChangelogModal.svelte'; + import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'; import UpdateToast from '$lib/components/UpdateToast.svelte'; import { Toaster, toast } from 'svelte-sonner'; import { @@ -507,3 +508,4 @@ style: 'font-size: 0.75rem; font-family: var(--font-sans); border-radius: 0.5rem;' }} /> + From 036b28b06428ed4e14c22b57f19b71c5b065d3c6 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 20 Jul 2026 21:39:28 -0400 Subject: [PATCH 6/8] refac --- .../src/lib/components/Admin/Skills.svelte | 31 +++++++++++------ .../src/lib/components/Admin/Subagents.svelte | 4 ++- .../src/lib/components/ChromeBrowser.svelte | 3 +- .../lib/components/DiffSettingsMenu.svelte | 9 ++--- .../src/lib/components/FileEditor.svelte | 20 +++++------ .../frontend/src/lib/components/GitBar.svelte | 4 +-- .../src/lib/components/GitView.svelte | 4 +-- .../src/lib/components/SettingsModal.svelte | 2 +- .../lib/components/chat/AskUserCard.svelte | 25 ++++++++------ .../components/chat/AssistantMessage.svelte | 2 +- .../components/chat/ChatFilePreview.svelte | 5 +-- .../src/lib/components/chat/ChatInput.svelte | 34 ++++++++++--------- .../lib/components/chat/SkillsModal.svelte | 11 +++--- .../chat/ToolCallCollapsible.svelte | 4 +-- .../lib/components/chat/UserMessage.svelte | 2 +- 15 files changed, 92 insertions(+), 68 deletions(-) diff --git a/cptr/frontend/src/lib/components/Admin/Skills.svelte b/cptr/frontend/src/lib/components/Admin/Skills.svelte index 3e301a72..f6d9600a 100644 --- a/cptr/frontend/src/lib/components/Admin/Skills.svelte +++ b/cptr/frontend/src/lib/components/Admin/Skills.svelte @@ -2,6 +2,7 @@ import { onMount } from 'svelte'; import { toast } from 'svelte-sonner'; import { getAdminConfig, updateConfig } from '$lib/apis/admin'; + import { t } from '$lib/i18n'; import ToggleSwitch from '$lib/components/common/ToggleSwitch.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; @@ -23,7 +24,7 @@ config['skills.background_review_enabled'] !== 'false'; reviewInterval = Number(config['skills.review_interval_turns']) || 10; } catch { - toast.error('Failed to load config'); + toast.error($t('admin.failedToLoadConfig')); } loading = false; }); @@ -37,9 +38,9 @@ 'skills.background_review_enabled': backgroundReview, 'skills.review_interval_turns': Math.max(1, Number(reviewInterval) || 10) }); - toast.success('Saved'); + toast.success($t('settings.saved')); } catch { - toast.error('Failed to save'); + toast.error($t('admin.failedToSave')); } finally { saving = false; } @@ -51,12 +52,16 @@
{:else}
-

Skills

+

+ {$t('chat.skills')} +

-

Behavior

+

+ {$t('admin.skillsBehavior')} +

{#if enabled} -

Limits

+

+ {$t('admin.skillsLimits')} +

- {saving ? 'Saving...' : 'Save'} + {saving ? $t('settings.saving') : $t('settings.save')}
{/if} diff --git a/cptr/frontend/src/lib/components/Admin/Subagents.svelte b/cptr/frontend/src/lib/components/Admin/Subagents.svelte index 718e0f4f..71e66dd3 100644 --- a/cptr/frontend/src/lib/components/Admin/Subagents.svelte +++ b/cptr/frontend/src/lib/components/Admin/Subagents.svelte @@ -165,7 +165,9 @@ step="1000" class="w-20 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" /> - chars + {$t('admin.chars')}
diff --git a/cptr/frontend/src/lib/components/ChromeBrowser.svelte b/cptr/frontend/src/lib/components/ChromeBrowser.svelte index ea599393..4c4c3d0a 100644 --- a/cptr/frontend/src/lib/components/ChromeBrowser.svelte +++ b/cptr/frontend/src/lib/components/ChromeBrowser.svelte @@ -1,5 +1,6 @@ {#if loading} -
Loading...
+
{$t('common.loading')}
{:else if error}
{error}
{:else if kind === 'image'} diff --git a/cptr/frontend/src/lib/components/chat/ChatInput.svelte b/cptr/frontend/src/lib/components/chat/ChatInput.svelte index 884cb828..e7458b6d 100644 --- a/cptr/frontend/src/lib/components/chat/ChatInput.svelte +++ b/cptr/frontend/src/lib/components/chat/ChatInput.svelte @@ -1177,9 +1177,9 @@ {#if slashCommandIds.includes('compact')} @@ -1359,9 +1359,9 @@ {#if slashCommandIds.includes('skills:create')} {/if} {#if slashSkillSuggestions.length > 0} -
Skills
+
+ {$t('chat.skills')} +
{#each slashSkillSuggestions as skill (skill.id)}
- Skills + {$t('chat.skills')}
@@ -39,7 +40,7 @@ {item.name}
- {item.managed ? 'managed' : 'read-only'} + {item.managed ? $t('chat.skillManaged') : $t('chat.skillReadOnly')}
@@ -49,7 +50,9 @@ {/each}
{:else} -
No skills found
+
+ {$t('chat.noSkillsFound')} +
{/if} diff --git a/cptr/frontend/src/lib/components/chat/ToolCallCollapsible.svelte b/cptr/frontend/src/lib/components/chat/ToolCallCollapsible.svelte index 155b215d..14a96f2f 100644 --- a/cptr/frontend/src/lib/components/chat/ToolCallCollapsible.svelte +++ b/cptr/frontend/src/lib/components/chat/ToolCallCollapsible.svelte @@ -235,8 +235,8 @@
{question.question}
- {answer || 'No answer recorded'}{answer === question.options?.[0]?.label - ? ' (Recommended)' + {answer || $t('chat.noAnswerRecorded')}{answer === question.options?.[0]?.label + ? ` (${$t('chat.recommended')})` : ''}
diff --git a/cptr/frontend/src/lib/components/chat/UserMessage.svelte b/cptr/frontend/src/lib/components/chat/UserMessage.svelte index 82757f21..fc01c05a 100644 --- a/cptr/frontend/src/lib/components/chat/UserMessage.svelte +++ b/cptr/frontend/src/lib/components/chat/UserMessage.svelte @@ -134,7 +134,7 @@ aria-expanded={timerExpanded} onclick={() => (timerExpanded = !timerExpanded)} > - Timer + {$t('chat.timer')} {content} Date: Mon, 20 Jul 2026 21:46:58 -0400 Subject: [PATCH 7/8] refac --- .../components/Admin/CreateBotModal.svelte | 9 +- .../Admin/CreateConnectionModal.svelte | 4 +- .../Admin/EditConnectionModal.svelte | 9 +- .../src/lib/components/SidebarHeader.svelte | 2 +- cptr/frontend/src/lib/i18n/locales/de.json | 155 +++++++++++++---- cptr/frontend/src/lib/i18n/locales/en.json | 45 ++++- cptr/frontend/src/lib/i18n/locales/es.json | 157 ++++++++++++++---- cptr/frontend/src/lib/i18n/locales/fr.json | 157 ++++++++++++++---- cptr/frontend/src/lib/i18n/locales/ja.json | 157 ++++++++++++++---- cptr/frontend/src/lib/i18n/locales/ko.json | 155 +++++++++++++---- cptr/frontend/src/lib/i18n/locales/pt-BR.json | 157 ++++++++++++++---- cptr/frontend/src/lib/i18n/locales/ru.json | 157 ++++++++++++++---- cptr/frontend/src/lib/i18n/locales/zh-CN.json | 157 ++++++++++++++---- cptr/frontend/src/lib/i18n/locales/zh-TW.json | 155 +++++++++++++---- 14 files changed, 1141 insertions(+), 335 deletions(-) diff --git a/cptr/frontend/src/lib/components/Admin/CreateBotModal.svelte b/cptr/frontend/src/lib/components/Admin/CreateBotModal.svelte index 52fe31a2..1796c4df 100644 --- a/cptr/frontend/src/lib/components/Admin/CreateBotModal.svelte +++ b/cptr/frontend/src/lib/components/Admin/CreateBotModal.svelte @@ -78,7 +78,7 @@ try { verifyResult = await verifyBotToken(platform, token.trim()); } catch (e: any) { - verifyResult = { ok: false, error: e.message || 'Verification failed' }; + verifyResult = { ok: false, error: e.message || $t('messaging.verificationFailed') }; } finally { verifying = false; } @@ -149,7 +149,7 @@
- Provider Type + {$t('connections.providerType')} {/if} diff --git a/cptr/frontend/src/lib/components/Admin/EditConnectionModal.svelte b/cptr/frontend/src/lib/components/Admin/EditConnectionModal.svelte index e208414b..d124e598 100644 --- a/cptr/frontend/src/lib/components/Admin/EditConnectionModal.svelte +++ b/cptr/frontend/src/lib/components/Admin/EditConnectionModal.svelte @@ -156,18 +156,15 @@ -