diff --git a/packages/ui/src/components/onboarding/ChooserScreen.tsx b/packages/ui/src/components/onboarding/ChooserScreen.tsx index 30f5668f..955e027c 100644 --- a/packages/ui/src/components/onboarding/ChooserScreen.tsx +++ b/packages/ui/src/components/onboarding/ChooserScreen.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { RiFileCopyLine, RiCheckLine, RiExternalLinkLine } from '@remixicon/react'; -import { isDesktopShell, isTauriShell } from '@/lib/desktop'; +import { RiFileCopyLine, RiCheckLine, RiExternalLinkLine, RiArrowDownSLine } from '@remixicon/react'; +import { isDesktopShell, isTauriShell, startDesktopWindowDrag } from '@/lib/desktop'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { updateDesktopSettings } from '@/lib/persistence'; @@ -14,6 +14,7 @@ import { useI18n } from '@/lib/i18n'; const INSTALL_COMMAND = 'curl -fsSL https://opencode.ai/install | bash'; const DOCS_URL = 'https://opencode.ai/docs'; const WINDOWS_WSL_DOCS_URL = 'https://opencode.ai/docs/windows-wsl'; +const POLL_INTERVAL_MS = 2500; type OnboardingPlatform = 'macos' | 'linux' | 'windows' | 'unknown'; @@ -24,8 +25,8 @@ type ChooserScreenProps = { function BashCommand({ onCopy, copyTitle }: { onCopy: () => void; copyTitle: string }) { return ( -
- +
+ curl -fsSL https://opencode.ai/install @@ -34,8 +35,9 @@ function BashCommand({ onCopy, copyTitle }: { onCopy: () => void; copyTitle: str @@ -43,24 +45,17 @@ function BashCommand({ onCopy, copyTitle }: { onCopy: () => void; copyTitle: str ); } -const HINT_DELAY_MS = 30000; - export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) { const { t } = useI18n(); const [copied, setCopied] = React.useState(false); - const [showHint, setShowHint] = React.useState(false); const [isDesktopApp, setIsDesktopApp] = React.useState(false); - const [isRetrying, setIsRetrying] = React.useState(false); - const [isChecking, setIsChecking] = React.useState(false); - const [checkError, setCheckError] = React.useState(null); + const [isApplyingPath, setIsApplyingPath] = React.useState(false); + const [isManualChecking, setIsManualChecking] = React.useState(false); const [opencodeBinary, setOpencodeBinary] = React.useState(''); const [platform, setPlatform] = React.useState('unknown'); const [activeTab, setActiveTab] = React.useState<'local' | 'remote'>('local'); - - React.useEffect(() => { - const timer = setTimeout(() => setShowHint(true), HINT_DELAY_MS); - return () => clearTimeout(timer); - }, []); + const [advancedOpen, setAdvancedOpen] = React.useState(false); + const [troubleOpen, setTroubleOpen] = React.useState(false); React.useEffect(() => { setIsDesktopApp(isDesktopShell()); @@ -73,19 +68,10 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) { } const ua = navigator.userAgent || ''; - if (/Windows/i.test(ua)) { - setPlatform('windows'); - return; - } - if (/Macintosh|Mac OS X/i.test(ua)) { - setPlatform('macos'); - return; - } - if (/Linux/i.test(ua)) { - setPlatform('linux'); - return; - } - setPlatform('unknown'); + if (/Windows/i.test(ua)) setPlatform('windows'); + else if (/Macintosh|Mac OS X/i.test(ua)) setPlatform('macos'); + else if (/Linux/i.test(ua)) setPlatform('linux'); + else setPlatform('unknown'); }, []); React.useEffect(() => { @@ -97,9 +83,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) { const data = (await response.json().catch(() => null)) as null | { opencodeBinary?: unknown }; if (!data || cancelled) return; const value = typeof data.opencodeBinary === 'string' ? data.opencodeBinary.trim() : ''; - if (value) { - setOpencodeBinary(value); - } + if (value) setOpencodeBinary(value); } catch { // ignore } @@ -110,18 +94,12 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) { }, []); const handleDragStart = React.useCallback(async (e: React.MouseEvent) => { - if ((e.target as HTMLElement).closest('button, a, input, select, textarea, code')) { - return; - } + const target = e.target as HTMLElement; + if (target.closest('.app-region-no-drag')) return; + if (target.closest('button, a, input, select, textarea, code, summary, details')) return; if (e.button !== 0) return; - if (isDesktopApp && isTauriShell()) { - try { - const { getCurrentWindow } = await import('@tauri-apps/api/window'); - const window = getCurrentWindow(); - await window.startDragging(); - } catch (error) { - console.error('Failed to start window dragging:', error); - } + if (isDesktopApp) { + await startDesktopWindowDrag(); } }, [isDesktopApp]); @@ -136,18 +114,74 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) { } }, []); + const persistFirstChoice = React.useCallback(async (choice: 'local' | 'remote') => { + if (!isTauriShell()) return; + + const config = await desktopHostsGet(); + await desktopHostsSet({ + ...config, + ...(choice === 'local' ? { defaultHostId: 'local' } : {}), + initialHostChoiceCompleted: true, + }); + }, []); + + const announceAvailable = React.useCallback(async () => { + if (isTauriShell()) { + await persistFirstChoice('local'); + } + onCliAvailable?.(); + }, [onCliAvailable, persistFirstChoice]); + + // Background polling: while the local tab is visible, periodically check + // whether the OpenCode CLI is reachable. As soon as it is, transition + // automatically — the user doesn't have to click anything. + React.useEffect(() => { + if (activeTab !== 'local') return; + + let cancelled = false; + let timer: ReturnType | null = null; + + const tick = async () => { + if (cancelled) return; + try { + const available = await checkCliAvailability(); + if (cancelled) return; + if (available) { + await announceAvailable(); + return; + } + } catch { + // ignore + } + if (!cancelled) { + timer = setTimeout(tick, POLL_INTERVAL_MS); + } + }; + + timer = setTimeout(tick, POLL_INTERVAL_MS); + + return () => { + cancelled = true; + if (timer) clearTimeout(timer); + }; + }, [activeTab, checkCliAvailability, announceAvailable]); + + const handleManualCheck = React.useCallback(async () => { + setIsManualChecking(true); + try { + const available = await checkCliAvailability(); + if (available) await announceAvailable(); + } finally { + setIsManualChecking(false); + } + }, [checkCliAvailability, announceAvailable]); + const handleBrowse = React.useCallback(async () => { - if (typeof window === 'undefined') { - return; - } - if (!isDesktopApp || !isTauriShell()) { - return; - } + if (typeof window === 'undefined') return; + if (!isDesktopApp || !isTauriShell()) return; const tauri = (window as unknown as { __TAURI__?: { dialog?: { open?: (opts: Record) => Promise } } }).__TAURI__; - if (!tauri?.dialog?.open) { - return; - } + if (!tauri?.dialog?.open) return; try { const selected = await tauri.dialog.open({ @@ -163,41 +197,18 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) { } }, [isDesktopApp, t]); - // Persist the user's first choice (local or remote) - const persistFirstChoice = React.useCallback(async (choice: 'local' | 'remote') => { - if (!isTauriShell()) return; - - const config = await desktopHostsGet(); - await desktopHostsSet({ - ...config, - // Only change defaultHostId when switching to local; remote keeps - // whatever was there (or null) until a successful connect. - ...(choice === 'local' ? { defaultHostId: 'local' } : {}), - initialHostChoiceCompleted: true, - }); - }, []); - const handleApplyPath = React.useCallback(async () => { - setIsRetrying(true); + setIsApplyingPath(true); try { await updateDesktopSettings({ opencodeBinary: opencodeBinary.trim() }); - - // In first-launch mode, persist the local choice when user manually - // sets the binary path, so the choice is remembered after restart. if (isTauriShell()) { await persistFirstChoice('local'); - } - - // In desktop boot flow, always restart the entire Tauri app so Rust - // can re-evaluate the boot outcome with the updated binary path. - if (isTauriShell()) { await restartDesktopApp(); return; } - await fetch('/api/config/reload', { method: 'POST' }); } finally { - setTimeout(() => setIsRetrying(false), 1000); + setTimeout(() => setIsApplyingPath(false), 1000); } }, [opencodeBinary, persistFirstChoice]); @@ -211,32 +222,6 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) { } }, []); - const handleChooseRemote = React.useCallback(() => { - setActiveTab('remote'); - }, []); - - const handleCheckAndContinue = React.useCallback(async () => { - setIsChecking(true); - setCheckError(null); - try { - const available = await checkCliAvailability(); - if (available) { - // In first-launch mode, persist the local choice when CLI becomes - // available, so the choice is remembered after restart. - if (isTauriShell()) { - await persistFirstChoice('local'); - } - onCliAvailable?.(); - } else { - setCheckError(t('onboarding.localSetup.errors.cliNotReady')); - } - } catch (err) { - setCheckError(err instanceof Error ? err.message : t('onboarding.localSetup.errors.detectionFailed')); - } finally { - setIsChecking(false); - } - }, [checkCliAvailability, onCliAvailable, persistFirstChoice, t]); - const docsUrl = platform === 'windows' ? WINDOWS_WSL_DOCS_URL : DOCS_URL; const binaryPlaceholder = platform === 'windows' @@ -245,27 +230,29 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) { ? '/home/you/.bun/bin/opencode' : '/Users/you/.bun/bin/opencode'; + const showLocal = !isDesktopApp || !isTauriShell() || activeTab === 'local'; + return (
-
-
-

+
+
+

{t('onboarding.chooser.title')}

-

+

{t('onboarding.chooser.description')}

-
+ {isDesktopApp && isTauriShell() && ( -
+
@@ -290,133 +277,168 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) { )} {isDesktopApp && isTauriShell() && activeTab === 'remote' ? ( - setActiveTab('local')} - showBackButton={false} - onSwitchToLocal={() => setActiveTab('local')} - /> - ) : ( - <> - {(!isDesktopApp || !isTauriShell() || activeTab === 'local') && ( - <> - {platform === 'windows' && ( -
-
{t('onboarding.localSetup.windows.title')}
-
    -
  1. {t('onboarding.localSetup.windows.stepInstallWsl')} wsl --install {t('onboarding.localSetup.windows.stepInstallWslSuffix')}
  2. -
  3. {t('onboarding.localSetup.windows.stepRunInstallInWsl')}
  4. -
  5. {t('onboarding.localSetup.windows.stepSetBinaryPath')}
  6. -
-
- )} +
+ setActiveTab('local')} + showBackButton={false} + onSwitchToLocal={() => setActiveTab('local')} + /> +
+ ) : null} -
-
- {copied ? ( -
- - {t('onboarding.common.status.copiedToClipboard')} -
- ) : ( - - )} -
+ {showLocal && ( +
+ {platform === 'windows' && ( +
+
{t('onboarding.localSetup.windows.title')}
+
    +
  1. {t('onboarding.localSetup.windows.stepInstallWsl')} wsl --install {t('onboarding.localSetup.windows.stepInstallWslSuffix')}
  2. +
  3. {t('onboarding.localSetup.windows.stepRunInstallInWsl')}
  4. +
  5. {t('onboarding.localSetup.windows.stepSetBinaryPath')}
  6. +
+
+ )} + +

+ {t('onboarding.localSetup.intro')} +

+ +
+ {copied ? ( +
+ + {t('onboarding.common.status.copiedToClipboard')}
+ ) : ( + + )} +
- - {platform === 'windows' ? t('onboarding.localSetup.docs.windows') : t('onboarding.localSetup.docs.default')} - - +
+ + {platform === 'windows' ? t('onboarding.localSetup.docs.windows') : t('onboarding.localSetup.docs.default')} + + + +
- {checkError && ( -
- {checkError} -
- )} +
+ + + + +
+
+ {t('onboarding.localSetup.status.watching')} +
+
+ {t('onboarding.localSetup.status.autoContinue')} +
+
+
-
+
setAdvancedOpen((e.currentTarget as HTMLDetailsElement).open)} + > + + {t('onboarding.localSetup.advanced.title')} + + +
+
+ setOpencodeBinary(e.target.value)} + placeholder={binaryPlaceholder} + disabled={isApplyingPath} + className="flex-1 font-mono text-xs" + /> + - -

- {t('onboarding.localSetup.helper.checkAndContinue')} -

+

+ {t('onboarding.localSetup.helper.saveAndReload')} +

+
+
-
-
-
{t('onboarding.localSetup.field.alreadyInstalled')}
-
- setOpencodeBinary(e.target.value)} - placeholder={binaryPlaceholder} - disabled={isRetrying} - className="flex-1 font-mono text-xs" - /> - - -
-
{t('onboarding.localSetup.helper.saveAndReload')}
-
-
- - )} - +
setTroubleOpen((e.currentTarget as HTMLDetailsElement).open)} + > + + {t('onboarding.localSetup.troubleshoot.title')} + + +
    + {platform === 'windows' ? ( + <> +
  • {t('onboarding.localSetup.windows.hintInstallInWsl')}
  • +
  • {t('onboarding.localSetup.windows.hintDetectionFailed')}
  • + + ) : ( + <> +
  • {t('onboarding.localSetup.hint.ensurePath')}
  • +
  • {t('onboarding.localSetup.hint.setEnv')}
  • +
  • {t('onboarding.localSetup.hint.missingRuntime')}
  • + + )} +
+
+
)}
- - {showHint && activeTab === 'local' && ( -
- {platform === 'windows' ? ( - <> -

- {t('onboarding.localSetup.windows.hintInstallInWsl')} -

-

- {t('onboarding.localSetup.windows.hintDetectionFailed')} -

- - ) : ( - <> -

- {t('onboarding.localSetup.hint.ensurePath')} -

-

- {t('onboarding.localSetup.hint.setEnv')} -

-

- {t('onboarding.localSetup.hint.missingRuntime')} -

- - )} -
- )}
); } diff --git a/packages/ui/src/lib/i18n/messages/en.ts b/packages/ui/src/lib/i18n/messages/en.ts index a7fe8533..779662aa 100644 --- a/packages/ui/src/lib/i18n/messages/en.ts +++ b/packages/ui/src/lib/i18n/messages/en.ts @@ -1951,12 +1951,18 @@ export const dict = { 'onboarding.localSetup.windows.stepInstallWslSuffix': 'in PowerShell.', 'onboarding.localSetup.windows.stepRunInstallInWsl': 'Run the install command below inside your WSL terminal.', 'onboarding.localSetup.windows.stepSetBinaryPath': 'If OpenChamber does not detect OpenCode automatically, set the binary path below.', - 'onboarding.localSetup.docs.windows': 'View Windows + WSL documentation', - 'onboarding.localSetup.docs.default': 'View documentation', - 'onboarding.localSetup.actions.checking': 'Checking...', + 'onboarding.localSetup.intro': 'OpenCode is the heart of OpenChamber — install it to get started.', + 'onboarding.localSetup.docs.windows': 'OpenCode Windows + WSL docs', + 'onboarding.localSetup.docs.default': 'OpenCode docs', + 'onboarding.localSetup.actions.checking': 'Checking…', 'onboarding.localSetup.actions.checkAndContinue': "I've completed installation, check and continue", + 'onboarding.localSetup.actions.checkNow': 'Check now', 'onboarding.localSetup.helper.checkAndContinue': 'Click to check if OpenCode CLI is available. If successful, you\'ll automatically enter the main screen.', + 'onboarding.localSetup.status.watching': 'Waiting for OpenCode', + 'onboarding.localSetup.status.autoContinue': "We'll continue automatically once it's detected.", 'onboarding.localSetup.field.alreadyInstalled': 'Already installed? Set the OpenCode CLI path:', + 'onboarding.localSetup.advanced.title': 'Set a custom binary path', + 'onboarding.localSetup.troubleshoot.title': 'Having trouble?', 'onboarding.localSetup.actions.browse': 'Browse', 'onboarding.localSetup.actions.apply': 'Apply', 'onboarding.localSetup.helper.saveAndReload': 'Saves to OpenChamber settings and reloads OpenCode configuration.', diff --git a/packages/ui/src/lib/i18n/messages/es.ts b/packages/ui/src/lib/i18n/messages/es.ts index a01e3e63..4931df1e 100644 --- a/packages/ui/src/lib/i18n/messages/es.ts +++ b/packages/ui/src/lib/i18n/messages/es.ts @@ -1917,12 +1917,18 @@ export const dict: Record = { "onboarding.localSetup.windows.stepInstallWslSuffix": "en PowerShell.", "onboarding.localSetup.windows.stepRunInstallInWsl": "Ejecuta el comando de instalación a continuación dentro de tu terminal de WSL.", "onboarding.localSetup.windows.stepSetBinaryPath": "Si OpenChamber no detecta OpenCode automáticamente, establece la ruta del ejecutable a continuación.", - "onboarding.localSetup.docs.windows": "Ver documentación de Windows + WSL", - "onboarding.localSetup.docs.default": "Ver documentación", + "onboarding.localSetup.intro": "OpenCode es el corazón de OpenChamber. Instálalo para empezar.", + "onboarding.localSetup.docs.windows": "Documentación de OpenCode para Windows + WSL", + "onboarding.localSetup.docs.default": "Documentación de OpenCode", "onboarding.localSetup.actions.checking": "Verificando...", "onboarding.localSetup.actions.checkAndContinue": "He completado la instalación, verificar y continuar", + "onboarding.localSetup.actions.checkNow": "Verificar ahora", "onboarding.localSetup.helper.checkAndContinue": "Haz clic para verificar si la CLI de OpenCode está disponible. Si la verificación funciona, entrarás automáticamente en la pantalla principal.", + "onboarding.localSetup.status.watching": "Esperando OpenCode", + "onboarding.localSetup.status.autoContinue": "Continuaremos automáticamente al detectarlo.", "onboarding.localSetup.field.alreadyInstalled": "¿Ya está instalado? Establece la ruta de la CLI de OpenCode:", + "onboarding.localSetup.advanced.title": "Establecer una ruta personalizada del binario", + "onboarding.localSetup.troubleshoot.title": "¿Tienes problemas?", "onboarding.localSetup.actions.browse": "Explorar", "onboarding.localSetup.actions.apply": "Aplicar", "onboarding.localSetup.helper.saveAndReload": "Guarda en la configuración de OpenChamber y recarga la configuración de OpenCode.", diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts index 98f4df6c..66c15393 100644 --- a/packages/ui/src/lib/i18n/messages/ko.ts +++ b/packages/ui/src/lib/i18n/messages/ko.ts @@ -1951,12 +1951,18 @@ export const dict: Record = { 'onboarding.localSetup.windows.stepInstallWslSuffix': 'PowerShell에서 실행하세요.', 'onboarding.localSetup.windows.stepRunInstallInWsl': '아래 설치 명령을 WSL 터미널에서 실행하세요.', 'onboarding.localSetup.windows.stepSetBinaryPath': 'OpenChamber가 OpenCode를 자동으로 찾지 못하면 아래에 바이너리 경로를 설정하세요.', - 'onboarding.localSetup.docs.windows': 'Windows + WSL 문서 보기', - 'onboarding.localSetup.docs.default': '문서 보기', + 'onboarding.localSetup.intro': 'OpenCode는 OpenChamber의 심장입니다. 먼저 설치하고 시작하세요.', + 'onboarding.localSetup.docs.windows': 'OpenCode Windows + WSL 문서', + 'onboarding.localSetup.docs.default': 'OpenCode 문서', 'onboarding.localSetup.actions.checking': '확인 중…', 'onboarding.localSetup.actions.checkAndContinue': '설치를 마쳤습니다. 확인 후 계속', + 'onboarding.localSetup.actions.checkNow': '지금 확인', 'onboarding.localSetup.helper.checkAndContinue': 'OpenCode CLI를 사용할 수 있으면 확인을 누르세요. 성공하면 자동으로 메인 화면으로 이동합니다.', + 'onboarding.localSetup.status.watching': 'OpenCode 대기 중', + 'onboarding.localSetup.status.autoContinue': '감지되면 자동으로 계속됩니다.', 'onboarding.localSetup.field.alreadyInstalled': '이미 설치되어 있나요? OpenCode CLI 경로 설정:', + 'onboarding.localSetup.advanced.title': '사용자 지정 바이너리 경로 설정', + 'onboarding.localSetup.troubleshoot.title': '문제가 있나요?', 'onboarding.localSetup.actions.browse': '찾아보기', 'onboarding.localSetup.actions.apply': '적용', 'onboarding.localSetup.helper.saveAndReload': 'OpenChamber 설정에 저장하고 OpenCode 구성을 다시 로드합니다.', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts index e55f5144..b0faf229 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts @@ -1917,12 +1917,18 @@ export const dict: Record = { "onboarding.localSetup.windows.stepInstallWslSuffix": "no PowerShell.", "onboarding.localSetup.windows.stepRunInstallInWsl": "Execute o comando de instalação a seguir dentro de seu terminal de WSL.", "onboarding.localSetup.windows.stepSetBinaryPath": "Se o OpenChamber não detectar o OpenCode automaticamente, defina o caminho do executável a seguir.", - "onboarding.localSetup.docs.windows": "Ver documentação de Windows + WSL", - "onboarding.localSetup.docs.default": "Ver documentação", + "onboarding.localSetup.intro": "OpenCode é o coração do OpenChamber. Instale-o para começar.", + "onboarding.localSetup.docs.windows": "Documentação do OpenCode para Windows + WSL", + "onboarding.localSetup.docs.default": "Documentação do OpenCode", "onboarding.localSetup.actions.checking": "Verificando...", "onboarding.localSetup.actions.checkAndContinue": "Concluí a instalação, verificar e continuar", + "onboarding.localSetup.actions.checkNow": "Verificar agora", "onboarding.localSetup.helper.checkAndContinue": "Clique para verificar se a CLI do OpenCode está disponível. Se a verificação funcionar, você entrará automaticamente na tela principal.", + "onboarding.localSetup.status.watching": "Aguardando OpenCode", + "onboarding.localSetup.status.autoContinue": "Continuaremos automaticamente assim que detectado.", "onboarding.localSetup.field.alreadyInstalled": "Já está instalado? Defina o caminho da CLI do OpenCode:", + "onboarding.localSetup.advanced.title": "Definir um caminho personalizado do binário", + "onboarding.localSetup.troubleshoot.title": "Está com problemas?", "onboarding.localSetup.actions.browse": "Explorar", "onboarding.localSetup.actions.apply": "Aplicar", "onboarding.localSetup.helper.saveAndReload": "Salve nas configurações do OpenChamber e recarregue configurações do OpenCode.", diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts index 1ea5e01e..d9f144e4 100644 --- a/packages/ui/src/lib/i18n/messages/uk.ts +++ b/packages/ui/src/lib/i18n/messages/uk.ts @@ -1917,12 +1917,18 @@ export const dict: Record = { "onboarding.localSetup.windows.stepInstallWslSuffix": "в PowerShell.", "onboarding.localSetup.windows.stepRunInstallInWsl": "Виконайте наведену нижче команду встановлення всередині терміналу WSL.", "onboarding.localSetup.windows.stepSetBinaryPath": "Якщо OpenChamber не визначає OpenCode автоматично, укажіть шлях до виконуваного файла нижче.", - "onboarding.localSetup.docs.windows": "Перегляньте документацію Windows + WSL", - "onboarding.localSetup.docs.default": "Переглянути документацію", + "onboarding.localSetup.intro": "OpenCode — серце OpenChamber. Встановіть його, щоб почати.", + "onboarding.localSetup.docs.windows": "Документація OpenCode для Windows + WSL", + "onboarding.localSetup.docs.default": "Документація OpenCode", "onboarding.localSetup.actions.checking": "Перевірка...", "onboarding.localSetup.actions.checkAndContinue": "Я завершив встановлення, перевірте та продовжуйте", + "onboarding.localSetup.actions.checkNow": "Перевірити зараз", "onboarding.localSetup.helper.checkAndContinue": "Натисніть, щоб перевірити, чи доступний OpenCode CLI. У разі успіху ви автоматично перейдете на головний екран.", + "onboarding.localSetup.status.watching": "Очікування OpenCode", + "onboarding.localSetup.status.autoContinue": "Ми автоматично продовжимо після виявлення.", "onboarding.localSetup.field.alreadyInstalled": "Вже встановлено? Встановіть шлях OpenCode CLI:", + "onboarding.localSetup.advanced.title": "Вказати власний шлях до бінарного файлу", + "onboarding.localSetup.troubleshoot.title": "Виникли проблеми?", "onboarding.localSetup.actions.browse": "Огляд", "onboarding.localSetup.actions.apply": "Застосувати", "onboarding.localSetup.helper.saveAndReload": "Зберігає налаштування в OpenChamber і перезавантажує конфігурацію OpenCode.", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts index ffce19df..a154c846 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts @@ -1917,12 +1917,18 @@ export const dict: Record = { 'onboarding.localSetup.windows.stepInstallWslSuffix': '安装 WSL。', 'onboarding.localSetup.windows.stepRunInstallInWsl': '在 WSL 终端中运行下方安装命令。', 'onboarding.localSetup.windows.stepSetBinaryPath': '如果 OpenChamber 未自动检测到 OpenCode,请在下方设置可执行文件路径。', - 'onboarding.localSetup.docs.windows': '查看 Windows + WSL 文档', - 'onboarding.localSetup.docs.default': '查看文档', + 'onboarding.localSetup.intro': 'OpenCode 是 OpenChamber 的核心,先安装它即可开始。', + 'onboarding.localSetup.docs.windows': 'OpenCode Windows + WSL 文档', + 'onboarding.localSetup.docs.default': 'OpenCode 文档', 'onboarding.localSetup.actions.checking': '检测中...', 'onboarding.localSetup.actions.checkAndContinue': '我已完成安装,检测并继续', + 'onboarding.localSetup.actions.checkNow': '立即检测', 'onboarding.localSetup.helper.checkAndContinue': '点击检测 OpenCode CLI 是否可用。成功后将自动进入主界面。', + 'onboarding.localSetup.status.watching': '正在等待 OpenCode', + 'onboarding.localSetup.status.autoContinue': '检测到后将自动继续。', 'onboarding.localSetup.field.alreadyInstalled': '已安装?设置 OpenCode CLI 路径:', + 'onboarding.localSetup.advanced.title': '设置自定义二进制路径', + 'onboarding.localSetup.troubleshoot.title': '遇到问题?', 'onboarding.localSetup.actions.browse': '浏览', 'onboarding.localSetup.actions.apply': '应用', 'onboarding.localSetup.helper.saveAndReload': '将保存到 OpenChamber 设置并重新加载 OpenCode 配置。',