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:
@@ -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[]) => {
|
||||
|
||||
Reference in New Issue
Block a user