refactor: remove legacy Tauri desktop support
Electron updater now uses Electron release metadata only Removed legacy Tauri package and migration workflow Replaced Tauri shim usage with the desktop bridge
This commit is contained in:
@@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { toast } from '@/components/ui';
|
||||
import { isDesktopShell, isVSCodeRuntime } from '@/lib/desktop';
|
||||
import { invokeDesktop, isDesktopShell, isVSCodeRuntime } from '@/lib/desktop';
|
||||
import { syncDesktopSettings, initializeAppearancePreferences } from '@/lib/persistence';
|
||||
import { applyPersistedDirectoryPreferences } from '@/lib/directoryPersistence';
|
||||
import { DesktopHostSwitcherInline } from '@/components/desktop/DesktopHostSwitcher';
|
||||
@@ -129,11 +129,7 @@ const issueDesktopClientTokenViaShell = async (password: string, trustDevice: bo
|
||||
if (!isDesktopShell() || typeof window === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
const invoke = (window as unknown as { __TAURI__?: { core?: { invoke?: (cmd: string, args?: Record<string, unknown>) => Promise<unknown> } } }).__TAURI__?.core?.invoke;
|
||||
if (typeof invoke !== 'function') {
|
||||
return '';
|
||||
}
|
||||
const response = await invoke('desktop_remote_password_login', {
|
||||
const response = await invokeDesktop('desktop_remote_password_login', {
|
||||
url: getRuntimeApiBaseUrl(),
|
||||
password,
|
||||
trustDevice,
|
||||
|
||||
@@ -36,8 +36,7 @@ import { useCurrentSessionActivity } from '@/hooks/useSessionActivity';
|
||||
import { toast } from '@/components/ui';
|
||||
import { Button } from '@/components/ui/button';
|
||||
// useMessageStore removed — messages now come from sync system
|
||||
import { isTauriShell, isVSCodeRuntime } from '@/lib/desktop';
|
||||
import { runtimeFetch } from '@/lib/runtime-fetch';
|
||||
import { isVSCodeRuntime } from '@/lib/desktop';
|
||||
import { isIMECompositionEvent } from '@/lib/ime';
|
||||
import { StopIcon } from '@/components/icons/StopIcon';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
@@ -968,7 +967,6 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
|
||||
const suppressNextFileDropTextInsertTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const pendingDroppedAbsolutePathsRef = React.useRef<string[]>([]);
|
||||
const canAcceptDropRef = React.useRef(false);
|
||||
const nativeDragInsideDropZoneRef = React.useRef(false);
|
||||
const mentionRef = React.useRef<FileMentionHandle>(null);
|
||||
const commandRef = React.useRef<CommandAutocompleteHandle>(null);
|
||||
const skillRef = React.useRef<SkillAutocompleteHandle>(null);
|
||||
@@ -3407,121 +3405,6 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
|
||||
}
|
||||
};
|
||||
|
||||
// Tauri desktop: handle native file drops via onDragDropEvent
|
||||
React.useEffect(() => {
|
||||
if (!isTauriShell()) return;
|
||||
let cancelled = false;
|
||||
let unlisten: (() => void) | null = null;
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
const { getCurrentWebviewWindow } = await import('@tauri-apps/api/webviewWindow');
|
||||
const webviewWindow = getCurrentWebviewWindow();
|
||||
const removeListener = await webviewWindow.onDragDropEvent(async (event) => {
|
||||
if (!canAcceptDropRef.current) return;
|
||||
|
||||
const payload = (event as { payload?: unknown }).payload;
|
||||
if (!payload || typeof payload !== 'object') return;
|
||||
|
||||
const typed = payload as { type?: string; paths?: string[]; position?: { x?: number; y?: number } };
|
||||
const type = typed.type;
|
||||
const x = typed.position?.x;
|
||||
const y = typed.position?.y;
|
||||
|
||||
// Check if drop is inside the chat input area
|
||||
const zone = dropZoneRef.current;
|
||||
let inZone: boolean | null = null;
|
||||
if (zone && typeof x === 'number' && typeof y === 'number') {
|
||||
const rect = zone.getBoundingClientRect();
|
||||
inZone = x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
|
||||
// Handle retina displays where Tauri might report physical pixels
|
||||
if (!inZone && window.devicePixelRatio > 1) {
|
||||
const sx = x / window.devicePixelRatio;
|
||||
const sy = y / window.devicePixelRatio;
|
||||
inZone = sx >= rect.left && sx <= rect.right && sy >= rect.top && sy <= rect.bottom;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'enter' || type === 'over') {
|
||||
if (inZone !== null) {
|
||||
nativeDragInsideDropZoneRef.current = inZone;
|
||||
}
|
||||
setIsDragging(nativeDragInsideDropZoneRef.current);
|
||||
return;
|
||||
}
|
||||
if (type === 'leave') {
|
||||
nativeDragInsideDropZoneRef.current = false;
|
||||
setIsDragging(false);
|
||||
return;
|
||||
}
|
||||
if (type === 'drop') {
|
||||
const shouldHandleDrop = inZone ?? nativeDragInsideDropZoneRef.current;
|
||||
nativeDragInsideDropZoneRef.current = false;
|
||||
setIsDragging(false);
|
||||
if (!shouldHandleDrop) return;
|
||||
|
||||
const paths = Array.isArray(typed.paths)
|
||||
? typed.paths.filter((p): p is string => typeof p === 'string')
|
||||
: [];
|
||||
if (paths.length === 0) return;
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const normalizedPath = normalizeDroppedPath(path);
|
||||
const fileName = normalizedPath.split(/[\\/]/).pop() || normalizedPath;
|
||||
let file: File;
|
||||
|
||||
// In Tauri shell, dropped paths are local machine paths.
|
||||
// Read bytes via native command to avoid workspace-bound /api/fs/raw restrictions.
|
||||
if (isTauriShell()) {
|
||||
const { invoke } = await import('@tauri-apps/api/core');
|
||||
const result = await invoke<{ mime: string; base64: string }>('desktop_read_file', { path: normalizedPath });
|
||||
const byteCharacters = atob(result.base64);
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
const blob = new Blob([byteArray], { type: result.mime || 'application/octet-stream' });
|
||||
file = new File([blob], fileName, { type: result.mime || 'application/octet-stream' });
|
||||
} else {
|
||||
const response = await runtimeFetch('/api/fs/raw', { query: { path: normalizedPath } });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to read dropped file (${response.status})`);
|
||||
}
|
||||
const blob = await response.blob();
|
||||
file = new File([blob], fileName, { type: blob.type || 'application/octet-stream' });
|
||||
}
|
||||
|
||||
await addAttachedFile(file);
|
||||
} catch (error) {
|
||||
console.error('Failed to attach dropped file:', path, error);
|
||||
toast.error(t('chat.chatInput.toast.attachNamedFailed', {
|
||||
name: path.split(/[\\/]/).pop() || t('chat.chatInput.fileFallback'),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (cancelled) {
|
||||
removeListener();
|
||||
return;
|
||||
}
|
||||
unlisten = removeListener;
|
||||
} catch (error) {
|
||||
if (!cancelled) {
|
||||
console.warn('Failed to register Tauri drag-drop listener:', error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
if (unlisten) unlisten();
|
||||
};
|
||||
}, [addAttachedFile, normalizeDroppedPath, t]);
|
||||
|
||||
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
const attachFiles = React.useCallback(async (files: FileList | File[]) => {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { toast } from '@/components/ui';
|
||||
import { isElectronShell, isTauriShell, isDesktopShell } from '@/lib/desktop';
|
||||
import { isElectronShell, isDesktopShell } from '@/lib/desktop';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
@@ -354,7 +354,7 @@ export function DesktopHostSwitcherDialog({
|
||||
}, [allHosts, defaultHostId, t]);
|
||||
|
||||
const persist = React.useCallback(async (nextHosts: DesktopHost[], nextDefaultHostId: string | null) => {
|
||||
if (!isTauriShell()) return;
|
||||
if (!isDesktopShell()) return;
|
||||
setIsSaving(true);
|
||||
setError('');
|
||||
try {
|
||||
@@ -376,7 +376,7 @@ export function DesktopHostSwitcherDialog({
|
||||
}, [onOpenChange, setSettingsDialogOpen, setSettingsPage]);
|
||||
|
||||
const refresh = React.useCallback(async () => {
|
||||
if (!isTauriShell()) return;
|
||||
if (!isDesktopShell()) return;
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
@@ -408,7 +408,7 @@ export function DesktopHostSwitcherDialog({
|
||||
}, [t]);
|
||||
|
||||
const probeAll = React.useCallback(async (hosts: DesktopHost[]) => {
|
||||
if (!isTauriShell()) return;
|
||||
if (!isDesktopShell()) return;
|
||||
setIsProbing(true);
|
||||
const nextProbingHostIds: Record<string, true> = {};
|
||||
for (const host of hosts) {
|
||||
@@ -458,7 +458,7 @@ export function DesktopHostSwitcherDialog({
|
||||
}, [open, allHosts, probeAll]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!open || !isTauriShell()) {
|
||||
if (!open || !isDesktopShell()) {
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
@@ -512,7 +512,7 @@ export function DesktopHostSwitcherDialog({
|
||||
|
||||
const isSshHost = Boolean(sshHostIds[host.id]);
|
||||
|
||||
if (host.id !== LOCAL_HOST_ID && isSshHost && isTauriShell()) {
|
||||
if (host.id !== LOCAL_HOST_ID && isSshHost && isDesktopShell()) {
|
||||
let existingStatus = sshStatusesById[host.id];
|
||||
const latestStatus = await desktopSshStatus(host.id)
|
||||
.then((items) => items.find((item) => item.id === host.id) || null)
|
||||
@@ -596,7 +596,7 @@ export function DesktopHostSwitcherDialog({
|
||||
}
|
||||
}
|
||||
|
||||
if (host.id !== LOCAL_HOST_ID && isTauriShell()) {
|
||||
if (host.id !== LOCAL_HOST_ID && isDesktopShell()) {
|
||||
setSwitchingHostId(host.id);
|
||||
const probe = await desktopHostProbe(origin, { clientToken: host.clientToken || null }).catch((): HostProbeResult => ({ status: 'unreachable', latencyMs: 0 }));
|
||||
setStatusById((prev) => ({
|
||||
@@ -705,7 +705,7 @@ export function DesktopHostSwitcherDialog({
|
||||
error: null,
|
||||
});
|
||||
|
||||
if (!hostId || hostId === LOCAL_HOST_ID || !isTauriShell()) {
|
||||
if (!hostId || hostId === LOCAL_HOST_ID || !isDesktopShell()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -721,7 +721,7 @@ export function DesktopHostSwitcherDialog({
|
||||
}, [allHosts, handleSwitch, sshSwitchModal.hostId]);
|
||||
|
||||
const connectSshHostInPlace = React.useCallback(async (host: DesktopHost) => {
|
||||
if (!isTauriShell()) return;
|
||||
if (!isDesktopShell()) return;
|
||||
setSwitchingHostId(host.id);
|
||||
try {
|
||||
await desktopSshConnect(host.id);
|
||||
@@ -750,7 +750,7 @@ export function DesktopHostSwitcherDialog({
|
||||
return null;
|
||||
}
|
||||
|
||||
const tauriAvailable = isTauriShell();
|
||||
const desktopAvailable = isDesktopShell();
|
||||
|
||||
const content = (
|
||||
<>
|
||||
@@ -772,7 +772,7 @@ export function DesktopHostSwitcherDialog({
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary'
|
||||
)}
|
||||
onClick={() => void probeAll(allHosts)}
|
||||
disabled={!tauriAvailable || isLoading || isProbing}
|
||||
disabled={!desktopAvailable || isLoading || isProbing}
|
||||
aria-label={t('desktopHostSwitcher.actions.refreshInstancesAria')}
|
||||
>
|
||||
<Icon name="refresh" className={cn('h-4 w-4', isProbing && 'animate-spin')} />
|
||||
@@ -805,7 +805,7 @@ export function DesktopHostSwitcherDialog({
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => void probeAll(allHosts)}
|
||||
disabled={!tauriAvailable || isLoading || isProbing}
|
||||
disabled={!desktopAvailable || isLoading || isProbing}
|
||||
>
|
||||
<Icon name="refresh" className={cn('h-4 w-4', isProbing && 'animate-spin')} />
|
||||
{t('desktopHostSwitcher.actions.refresh')}
|
||||
@@ -814,7 +814,7 @@ export function DesktopHostSwitcherDialog({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!tauriAvailable && (
|
||||
{!desktopAvailable && (
|
||||
<div className="flex-shrink-0 rounded-lg border border-border/50 bg-muted/20 p-3">
|
||||
<div className="typography-meta text-muted-foreground">
|
||||
{t('desktopHostSwitcher.state.limitedOnPage')}
|
||||
@@ -974,7 +974,7 @@ export function DesktopHostSwitcherDialog({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{tauriAvailable && editingId && editingId !== LOCAL_HOST_ID && (
|
||||
{desktopAvailable && editingId && editingId !== LOCAL_HOST_ID && (
|
||||
<div className="flex-shrink-0 rounded-lg border border-border/50 bg-muted/20 p-3">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="typography-ui-label font-medium text-foreground">{t('desktopHostSwitcher.edit.title')}</div>
|
||||
@@ -1202,7 +1202,7 @@ export function DesktopHostSwitcherButton({ headerIconButtonClass }: DesktopHost
|
||||
}, [connectDefaultSshInstance, startupSshModal.hostId, startupSshModal.hostLabel]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isTauriShell()) return;
|
||||
if (!isDesktopShell()) return;
|
||||
|
||||
let cancelled = false;
|
||||
const run = async () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { toast } from '@/components/ui';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { isDesktopLocalOriginActive, isTauriShell, openDesktopPath, openDesktopProjectInApp } from '@/lib/desktop';
|
||||
import { isDesktopLocalOriginActive, isDesktopShell, openDesktopPath, openDesktopProjectInApp } from '@/lib/desktop';
|
||||
import { DEFAULT_OPEN_IN_APP_ID, OPEN_IN_APPS } from '@/lib/openInApps';
|
||||
import { useOpenInAppsStore, type OpenInAppOption } from '@/stores/useOpenInAppsStore';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
@@ -87,7 +87,7 @@ export const OpenInAppButton = ({ directory, className }: OpenInAppButtonProps)
|
||||
initialize();
|
||||
}, [initialize]);
|
||||
|
||||
const isDesktopLocal = isTauriShell() && isDesktopLocalOriginActive();
|
||||
const isDesktopLocal = isDesktopShell() && isDesktopLocalOriginActive();
|
||||
|
||||
const selectedApp = React.useMemo(() => {
|
||||
const known = availableApps.find((app) => app.id === selectedAppId)
|
||||
|
||||
@@ -1660,15 +1660,12 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
}
|
||||
|
||||
let disposed = false;
|
||||
let unlistenResize: (() => void) | null = null;
|
||||
|
||||
const syncFullscreenState = async () => {
|
||||
try {
|
||||
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
||||
const currentWindow = getCurrentWindow();
|
||||
const fullscreen = await currentWindow.isFullscreen();
|
||||
const fullscreen = await invokeDesktop<boolean>('desktop_is_window_fullscreen');
|
||||
if (!disposed) {
|
||||
setIsDesktopWindowFullscreen(fullscreen);
|
||||
setIsDesktopWindowFullscreen(fullscreen === true);
|
||||
}
|
||||
} catch {
|
||||
if (!disposed) {
|
||||
@@ -1677,26 +1674,16 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const attach = async () => {
|
||||
try {
|
||||
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
||||
const currentWindow = getCurrentWindow();
|
||||
unlistenResize = await currentWindow.onResized(() => {
|
||||
void syncFullscreenState();
|
||||
});
|
||||
} catch {
|
||||
// Ignore listener setup failures; fallback state remains false.
|
||||
}
|
||||
const onResize = () => {
|
||||
void syncFullscreenState();
|
||||
};
|
||||
|
||||
void syncFullscreenState();
|
||||
void attach();
|
||||
window.addEventListener('openchamber:window-resized', onResize);
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
if (unlistenResize) {
|
||||
unlistenResize();
|
||||
}
|
||||
window.removeEventListener('openchamber:window-resized', onResize);
|
||||
};
|
||||
}, [isDesktopApp, isMacPlatform]);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { isDesktopShell, isTauriShell, startDesktopWindowDrag } from '@/lib/desktop';
|
||||
import { isDesktopShell, requestFileAccess, startDesktopWindowDrag } from '@/lib/desktop';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
@@ -116,7 +116,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
|
||||
}, []);
|
||||
|
||||
const persistFirstChoice = React.useCallback(async (choice: 'local' | 'remote') => {
|
||||
if (!isTauriShell()) return;
|
||||
if (!isDesktopApp) return;
|
||||
|
||||
const config = await desktopHostsGet();
|
||||
await desktopHostsSet({
|
||||
@@ -124,14 +124,14 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
|
||||
...(choice === 'local' ? { defaultHostId: 'local' } : {}),
|
||||
initialHostChoiceCompleted: true,
|
||||
});
|
||||
}, []);
|
||||
}, [isDesktopApp]);
|
||||
|
||||
const announceAvailable = React.useCallback(async () => {
|
||||
if (isTauriShell()) {
|
||||
if (isDesktopApp) {
|
||||
await persistFirstChoice('local');
|
||||
}
|
||||
onCliAvailable?.();
|
||||
}, [onCliAvailable, persistFirstChoice]);
|
||||
}, [isDesktopApp, onCliAvailable, persistFirstChoice]);
|
||||
|
||||
// Background polling: while the local tab is visible, periodically check
|
||||
// whether the OpenCode CLI is reachable. As soon as it is, transition
|
||||
@@ -179,30 +179,23 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
|
||||
|
||||
const handleBrowse = React.useCallback(async () => {
|
||||
if (typeof window === 'undefined') return;
|
||||
if (!isDesktopApp || !isTauriShell()) return;
|
||||
|
||||
const tauri = (window as unknown as { __TAURI__?: { dialog?: { open?: (opts: Record<string, unknown>) => Promise<unknown> } } }).__TAURI__;
|
||||
if (!tauri?.dialog?.open) return;
|
||||
if (!isDesktopApp) return;
|
||||
|
||||
try {
|
||||
const selected = await tauri.dialog.open({
|
||||
title: t('onboarding.localSetup.dialog.selectOpencodeBinary'),
|
||||
multiple: false,
|
||||
directory: false,
|
||||
});
|
||||
if (typeof selected === 'string' && selected.trim().length > 0) {
|
||||
setOpencodeBinary(selected.trim());
|
||||
const selected = await requestFileAccess();
|
||||
if (selected.success && selected.path && selected.path.trim().length > 0) {
|
||||
setOpencodeBinary(selected.path.trim());
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, [isDesktopApp, t]);
|
||||
}, [isDesktopApp]);
|
||||
|
||||
const handleApplyPath = React.useCallback(async () => {
|
||||
setIsApplyingPath(true);
|
||||
try {
|
||||
await updateDesktopSettings({ opencodeBinary: opencodeBinary.trim() });
|
||||
if (isTauriShell()) {
|
||||
if (isDesktopApp) {
|
||||
await persistFirstChoice('local');
|
||||
await restartDesktopApp();
|
||||
return;
|
||||
@@ -211,7 +204,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
|
||||
} finally {
|
||||
setTimeout(() => setIsApplyingPath(false), 1000);
|
||||
}
|
||||
}, [opencodeBinary, persistFirstChoice]);
|
||||
}, [isDesktopApp, opencodeBinary, persistFirstChoice]);
|
||||
|
||||
const handleCopy = React.useCallback(async () => {
|
||||
const result = await copyTextToClipboard(INSTALL_COMMAND);
|
||||
@@ -231,7 +224,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
|
||||
? '/home/you/.bun/bin/opencode'
|
||||
: '/Users/you/.bun/bin/opencode';
|
||||
|
||||
const showLocal = !isDesktopApp || !isTauriShell() || activeTab === 'local';
|
||||
const showLocal = !isDesktopApp || activeTab === 'local';
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -248,7 +241,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{isDesktopApp && isTauriShell() && (
|
||||
{isDesktopApp && (
|
||||
<div className="app-region-no-drag flex gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
@@ -277,7 +270,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDesktopApp && isTauriShell() && activeTab === 'remote' ? (
|
||||
{isDesktopApp && activeTab === 'remote' ? (
|
||||
<div className="app-region-no-drag">
|
||||
<RemoteConnectionForm
|
||||
onBack={() => setActiveTab('local')}
|
||||
@@ -394,7 +387,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={handleBrowse}
|
||||
disabled={isApplyingPath || !isDesktopApp || !isTauriShell()}
|
||||
disabled={isApplyingPath || !isDesktopApp}
|
||||
>
|
||||
{t('onboarding.localSetup.actions.browse')}
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { isDesktopShell, isTauriShell } from '@/lib/desktop';
|
||||
import { isDesktopShell, requestFileAccess, startDesktopWindowDrag } from '@/lib/desktop';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
@@ -122,14 +122,8 @@ export function LocalSetupScreen({
|
||||
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]);
|
||||
|
||||
@@ -148,37 +142,28 @@ export function LocalSetupScreen({
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
if (!isDesktopApp || !isTauriShell()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tauri = (window as unknown as { __TAURI__?: { dialog?: { open?: (opts: Record<string, unknown>) => Promise<unknown> } } }).__TAURI__;
|
||||
if (!tauri?.dialog?.open) {
|
||||
if (!isDesktopApp) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const selected = await tauri.dialog.open({
|
||||
title: t('onboarding.localSetup.dialog.selectOpencodeBinary'),
|
||||
multiple: false,
|
||||
directory: false,
|
||||
});
|
||||
if (typeof selected === 'string' && selected.trim().length > 0) {
|
||||
setOpencodeBinary(selected.trim());
|
||||
const selected = await requestFileAccess();
|
||||
if (selected.success && selected.path && selected.path.trim().length > 0) {
|
||||
setOpencodeBinary(selected.path.trim());
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, [isDesktopApp, t]);
|
||||
}, [isDesktopApp]);
|
||||
|
||||
const handleApplyPath = React.useCallback(async () => {
|
||||
setIsRetrying(true);
|
||||
try {
|
||||
await updateDesktopSettings({ opencodeBinary: opencodeBinary.trim() });
|
||||
|
||||
// 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()) {
|
||||
// In desktop boot flow, restart the app so the native host can
|
||||
// re-evaluate the boot outcome with the updated binary path.
|
||||
if (isDesktopApp) {
|
||||
await restartDesktopApp();
|
||||
return;
|
||||
}
|
||||
@@ -187,7 +172,7 @@ export function LocalSetupScreen({
|
||||
} finally {
|
||||
setTimeout(() => setIsRetrying(false), 1000);
|
||||
}
|
||||
}, [opencodeBinary]);
|
||||
}, [isDesktopApp, opencodeBinary]);
|
||||
|
||||
const handleCopy = React.useCallback(async () => {
|
||||
const result = await copyTextToClipboard(INSTALL_COMMAND);
|
||||
@@ -321,7 +306,7 @@ export function LocalSetupScreen({
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={handleBrowse}
|
||||
disabled={isRetrying || !isDesktopApp || !isTauriShell()}
|
||||
disabled={isRetrying || !isDesktopApp}
|
||||
>
|
||||
{t('onboarding.localSetup.actions.browse')}
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { isTauriShell, restartDesktopApp } from '@/lib/desktop';
|
||||
import { isDesktopShell, restartDesktopApp } from '@/lib/desktop';
|
||||
import { DesktopConnectionRecovery, type RecoveryVariant } from './DesktopConnectionRecovery';
|
||||
import { RemoteConnectionForm } from './RemoteConnectionForm';
|
||||
import { resolveRecoveryNextStep } from './desktopRecoveryRouting';
|
||||
@@ -43,7 +43,7 @@ export function RecoveryScreen({
|
||||
}: RecoveryScreenProps) {
|
||||
// Persist the user's first choice (local or remote)
|
||||
const persistFirstChoice = React.useCallback(async (choice: 'local' | 'remote') => {
|
||||
if (!isTauriShell()) return;
|
||||
if (!isDesktopShell()) return;
|
||||
|
||||
const config = await desktopHostsGet();
|
||||
await desktopHostsSet({
|
||||
@@ -56,9 +56,9 @@ export function RecoveryScreen({
|
||||
}, []);
|
||||
|
||||
const handleRecoveryRetry = React.useCallback(async () => {
|
||||
// In desktop boot flow, always restart the entire Tauri app so Rust
|
||||
// can re-evaluate the boot outcome.
|
||||
if (isTauriShell()) {
|
||||
// In desktop boot flow, restart the app so the native host can
|
||||
// re-evaluate the boot outcome.
|
||||
if (isDesktopShell()) {
|
||||
await restartDesktopApp();
|
||||
return;
|
||||
}
|
||||
@@ -77,7 +77,7 @@ export function RecoveryScreen({
|
||||
// switch-default-to-local → persist local choice and restart
|
||||
await persistFirstChoice('local');
|
||||
|
||||
if (isTauriShell()) {
|
||||
if (isDesktopShell()) {
|
||||
await restartDesktopApp();
|
||||
return;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ export function RecoveryScreen({
|
||||
isRecoveryMode={true}
|
||||
onSwitchToLocal={onSwitchToLocalFromRemote || (() => {
|
||||
persistFirstChoice('local').then(() => {
|
||||
if (isTauriShell()) {
|
||||
if (isDesktopShell()) {
|
||||
restartDesktopApp();
|
||||
} else {
|
||||
onEnterLocalSetup?.();
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '@/lib/desktopHosts';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { isTauriShell } from '@/lib/desktop';
|
||||
import { isDesktopShell, restartDesktopApp } from '@/lib/desktop';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
type ConnectionState = 'idle' | 'testing' | 'success' | 'error';
|
||||
@@ -153,9 +153,8 @@ export function RemoteConnectionForm({
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTauriShell()) {
|
||||
const tauri = (window as unknown as { __TAURI__?: { core?: { invoke?: (cmd: string, args?: Record<string, unknown>) => Promise<unknown> } } }).__TAURI__;
|
||||
await tauri?.core?.invoke?.('desktop_restart');
|
||||
if (isDesktopShell()) {
|
||||
await restartDesktopApp();
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : t('onboarding.remoteConnection.errors.failedToSaveConnection'));
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { isDesktopShell, isTauriShell } from '@/lib/desktop';
|
||||
import { isDesktopShell, requestFileAccess } from '@/lib/desktop';
|
||||
import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
@@ -54,28 +54,19 @@ export const OpenCodeCliSettings: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDesktopShell() || !isTauriShell()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tauri = (window as unknown as { __TAURI__?: { dialog?: { open?: (opts: Record<string, unknown>) => Promise<unknown> } } }).__TAURI__;
|
||||
if (!tauri?.dialog?.open) {
|
||||
if (!isDesktopShell()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const selected = await tauri.dialog.open({
|
||||
title: t('settings.openchamber.opencodeCli.dialog.selectBinaryTitle'),
|
||||
multiple: false,
|
||||
directory: false,
|
||||
});
|
||||
if (typeof selected === 'string' && selected.trim().length > 0) {
|
||||
setValue(selected.trim());
|
||||
const selected = await requestFileAccess();
|
||||
if (selected.success && selected.path && selected.path.trim().length > 0) {
|
||||
setValue(selected.path.trim());
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, [t]);
|
||||
}, []);
|
||||
|
||||
const handleSaveAndReload = React.useCallback(async () => {
|
||||
setIsSaving(true);
|
||||
@@ -130,7 +121,7 @@ export const OpenCodeCliSettings: React.FC = () => {
|
||||
variant="outline"
|
||||
size="xs"
|
||||
onClick={handleBrowse}
|
||||
disabled={isLoading || isSaving || !isDesktopShell() || !isTauriShell()}
|
||||
disabled={isLoading || isSaving || !isDesktopShell()}
|
||||
className="h-7 w-7 p-0"
|
||||
aria-label={t('settings.openchamber.opencodeCli.actions.browseAria')}
|
||||
title={t('settings.openchamber.opencodeCli.actions.browse')}
|
||||
|
||||
@@ -42,7 +42,6 @@ import { getRuntimeApiBaseUrl } from '@/lib/runtime-switch';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import type { Extension } from '@codemirror/state';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useFilesViewTabsStore } from '@/stores/useFilesViewTabsStore';
|
||||
@@ -2629,7 +2628,10 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
|
||||
const srcPromise = files.readFileBinary
|
||||
? files.readFileBinary(selectedFile.path, selectedFileReadOptions).then((result) => result.dataUrl)
|
||||
: Promise.resolve(convertFileSrc(selectedFile.path, 'asset'));
|
||||
: Promise.resolve(getRuntimeUrlResolver().authenticatedAsset('/api/fs/raw', {
|
||||
path: selectedFile.path,
|
||||
allowOutsideWorkspace: selectedFileReadOptions.allowOutsideWorkspace ? 'true' : undefined,
|
||||
}));
|
||||
|
||||
await srcPromise
|
||||
.then((src) => {
|
||||
|
||||
Reference in New Issue
Block a user