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:
Bohdan Triapitsyn
2026-06-03 02:42:00 +03:00
parent e80bd15dd9
commit c7bc026b4b
81 changed files with 269 additions and 16914 deletions
+1 -118
View File
@@ -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[]) => {