diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c448363..5f6cda7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -12,13 +12,13 @@ Purpose: give AI assistants immediate, accurate context for this repository and - Name: TalkEdit - Product: local-first, AI-powered, text-based audio/video editor. -- Primary runtime today: Tauri + React frontend + Python FastAPI backend. -- Legacy/transition artifacts may still exist (for example Electron paths/APIs), but default implementation direction is TalkEdit + Tauri. +- Primary runtime: Tauri + React frontend + Python FastAPI backend. +- Desktop only (Electron has been removed; Tauri is the exclusive desktop runtime). ## Tech Stack - Frontend: React 19, TypeScript, Vite, Tailwind, Zustand. -- Desktop bridge: Tauri API with compatibility shim exposing `window.electronAPI` in `frontend/src/lib/tauri-bridge.ts`. +- Desktop bridge: Tauri API (IPC commands via `window.electronAPI` polyfill in `frontend/src/lib/tauri-bridge.ts` for unified call-site interface). - Backend: FastAPI + Uvicorn (`backend/main.py`) with routers in `backend/routers` and core services in `backend/services`. - Media tooling: FFmpeg for edit/export and codec operations. - AI tooling: WhisperX/faster-whisper for transcription; provider layer supports OpenAI/Anthropic/Ollama. @@ -46,7 +46,7 @@ Use project virtualenvs where available (`.venv312`, `.venv`, or `venv`) for bac - Keep router files thin; put heavy logic in `backend/services`. - Preserve response compatibility for existing frontend callers unless task explicitly allows API breakage. -- Keep frontend bridge compatibility stable: if desktop APIs change, update both Tauri-side implementation and the `window.electronAPI` shim contract. +- Frontend uses unified `window.electronAPI` interface (Tauri-backed via tauri-bridge.ts); desktop APIs are implemented exclusively in Tauri. - Prefer small, focused edits over broad refactors. ## Known Risk Areas @@ -65,6 +65,11 @@ Always update these sections if affected: - `Tech Stack` - `Code Map` - `Run And Build (Preferred)` +- `Working Conventions` +- `Known Risk Areas` +- Recent changes section (if applicable) +- `Code Map` +- `Run And Build (Preferred)` - `Known Risk Areas` If behavior changed significantly, add a short note under a new `Recent Changes` section with: diff --git a/README.md b/README.md index 5873147..772631d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ An open-source, local-first, Descript-like text-based audio and video editor pow ## Architecture -- **Electron + React** desktop app with Tailwind CSS +- **Tauri + React** desktop app with Tailwind CSS - **FastAPI** Python backend (spawned as child process) - **WhisperX** for word-level transcription with alignment - **FFmpeg** for video processing (stream-copy and re-encode) @@ -25,10 +25,8 @@ An open-source, local-first, Descript-like text-based audio and video editor pow ### Install ```bash -# Root dependencies (Electron, concurrently) +# Root and frontend dependencies npm install - -# Frontend dependencies (React, Tailwind, Zustand) cd frontend && npm install && cd .. # Backend dependencies @@ -38,8 +36,8 @@ cd backend && pip install -r requirements.txt && cd .. ### Run (Development) ```bash -# Start all three (backend + frontend + electron) -npm run dev +# Start Tauri dev environment (includes backend + frontend) +npm run dev:tauri ``` Or run them separately: @@ -48,26 +46,24 @@ Or run them separately: # Terminal 1: Backend cd backend && python -m uvicorn main:app --reload --port 8642 -# Terminal 2: Frontend -cd frontend && npm run dev - -# Terminal 3: Electron -npx electron . +# Terminal 2: Frontend + Tauri +cd frontend && cargo tauri dev ``` ## Project Structure ``` -cutscript/ -├── electron/ # Electron main process -│ ├── main.js # App entry, spawns Python backend -│ ├── preload.js # Secure IPC bridge -│ └── python-bridge.js -├── frontend/ # React + Vite + Tailwind +talkedit/ +├── src-tauri/ # Tauri Rust runtime +│ ├── Cargo.toml +│ ├── src/ +│ │ ├── main.rs # App entry & backend spawner +│ │ └── commands/ # Tauri IPC handlers +├── frontend/ # React + Vite + Tailwind │ └── src/ -│ ├── components/ # VideoPlayer, TranscriptEditor, etc. -│ ├── store/ # Zustand state (editorStore, aiStore) -│ ├── hooks/ # useVideoSync, useKeyboardShortcuts +│ ├── components/ # VideoPlayer, TranscriptEditor, etc. +│ ├── store/ # Zustand state (editorStore, aiStore) +│ ├── lib/tauri-bridge.ts # Tauri API polyfill │ └── types/ # TypeScript interfaces ├── backend/ # FastAPI Python backend │ ├── main.py diff --git a/close b/close index 9f79ea8..4e18c07 100755 --- a/close +++ b/close @@ -1,5 +1,5 @@ #!/bin/bash -# Close TalkEdit and/or CutScript processes (Tauri dev, Electron, and Python backends) +# Close TalkEdit processes (Tauri dev and Python backend) KILLED_ANY=0 @@ -75,17 +75,12 @@ kill_pattern "tauri.*TalkEdit\|TalkEdit.*tauri\|cargo.*tauri dev\|/TalkEdit/targ # Vite dev server for TalkEdit (fallback when not bound to 5173 yet) kill_pattern "[/ ]vite([[:space:]]|$)\|[/ ]rsbuild([[:space:]]|$)" "TalkEdit frontend dev server" -# --- CutScript (Electron, port 8642) --- -kill_port 8642 "CutScript" -kill_pattern "electron.*CutScript\|CutScript.*electron" "CutScript (Electron)" -kill_pattern "vite.*CutScript\|CutScript.*vite" "CutScript frontend dev server" - -# --- Orphaned uvicorn workers for either app --- -kill_pattern "uvicorn.*main:app.*--port 800[012]" "leftover uvicorn workers (TalkEdit)" -kill_pattern "uvicorn.*main:app.*--port 864" "leftover uvicorn workers (CutScript)" +# --- Orphaned uvicorn workers --- +kill_pattern "uvicorn.*main:app.*--port 8000" "leftover uvicorn workers (TalkEdit)" +kill_pattern "uvicorn.*main:app.*--port 8642" "leftover uvicorn workers" if [[ $KILLED_ANY -eq 0 ]]; then - echo "Nothing to close — no TalkEdit or CutScript processes found." + echo "Nothing to close — no TalkEdit processes found." else echo "Done." fi diff --git a/electron/main.js b/electron/main.js deleted file mode 100644 index 052a9cc..0000000 --- a/electron/main.js +++ /dev/null @@ -1,198 +0,0 @@ -const { app, BrowserWindow, ipcMain, dialog, safeStorage } = require('electron'); -const path = require('path'); -const { PythonBackend } = require('./python-bridge'); - -let mainWindow = null; -let pythonBackend = null; - -const isDev = !app.isPackaged; -const BACKEND_PORT = 8642; - -function getProjectDirectory() { - const fs = require('fs'); - // Keep project files alongside the TalkEdit workspace in development. - // electron/main.js lives under /electron, so repo root is ../ - const projectsDir = path.join(__dirname, '..', 'Projects'); - if (!fs.existsSync(projectsDir)) { - fs.mkdirSync(projectsDir, { recursive: true }); - } - return projectsDir; -} - -function createWindow() { - mainWindow = new BrowserWindow({ - width: 1400, - height: 900, - minWidth: 1024, - minHeight: 700, - title: 'CutScript', - webPreferences: { - preload: path.join(__dirname, 'preload.js'), - contextIsolation: true, - nodeIntegration: false, - webSecurity: isDev ? false : true, - }, - show: false, - }); - - if (isDev) { - mainWindow.loadURL('http://localhost:5173'); - mainWindow.webContents.openDevTools(); - } else { - mainWindow.loadFile(path.join(__dirname, '..', 'frontend', 'dist', 'index.html')); - } - - mainWindow.once('ready-to-show', () => { - mainWindow.show(); - }); - - mainWindow.on('closed', () => { - mainWindow = null; - }); -} - -app.whenReady().then(async () => { - pythonBackend = new PythonBackend(BACKEND_PORT, isDev); - await pythonBackend.start(); - - createWindow(); - - app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); - } - }); -}); - -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit(); - } -}); - -app.on('before-quit', () => { - if (pythonBackend) { - pythonBackend.stop(); - } -}); - -// IPC Handlers - -ipcMain.handle('dialog:openFile', async (_event, options) => { - const result = await dialog.showOpenDialog(mainWindow, { - properties: ['openFile'], - filters: [ - { name: 'Video Files', extensions: ['mp4', 'avi', 'mov', 'mkv', 'webm'] }, - { name: 'Audio Files', extensions: ['m4a', 'wav', 'mp3', 'flac'] }, - { name: 'All Files', extensions: ['*'] }, - ], - ...options, - }); - return result.canceled ? null : result.filePaths[0]; -}); - -ipcMain.handle('dialog:saveFile', async (_event, options) => { - const result = await dialog.showSaveDialog(mainWindow, { - filters: [ - { name: 'Video Files', extensions: ['mp4', 'mov', 'webm'] }, - { name: 'Project Files', extensions: ['aive'] }, - ], - ...options, - }); - return result.canceled ? null : result.filePath; -}); - -ipcMain.handle('dialog:openProject', async () => { - const projectDir = getProjectDirectory(); - const result = await dialog.showOpenDialog(mainWindow, { - defaultPath: projectDir, - properties: ['openFile'], - filters: [ - { name: 'AI Video Editor Project', extensions: ['aive'] }, - ], - }); - return result.canceled ? null : result.filePaths[0]; -}); - -ipcMain.handle('dialog:saveProject', async (_event, options) => { - const projectDir = getProjectDirectory(); - const result = await dialog.showSaveDialog(mainWindow, { - defaultPath: path.join(projectDir, 'project.aive'), - filters: [ - { name: 'AI Video Editor Project', extensions: ['aive'] }, - ], - ...options, - }); - return result.canceled ? null : result.filePath; -}); - -ipcMain.handle('safe-storage:encrypt', (_event, data) => { - if (safeStorage.isEncryptionAvailable()) { - return safeStorage.encryptString(data).toString('base64'); - } - return data; -}); - -ipcMain.handle('safe-storage:decrypt', (_event, encrypted) => { - if (safeStorage.isEncryptionAvailable()) { - return safeStorage.decryptString(Buffer.from(encrypted, 'base64')); - } - return encrypted; -}); - -ipcMain.handle('get-backend-url', () => { - return `http://localhost:${BACKEND_PORT}`; -}); - -ipcMain.handle('backend:ensureModel', async (_event, modelName) => { - // Python backend downloads models lazily during transcription. - // Keep this IPC for compatibility with existing renderer flow. - return modelName; -}); - -ipcMain.handle('backend:transcribe', async (_event, payload) => { - const filePath = payload?.filePath; - const modelName = payload?.modelName || 'base'; - const language = payload?.language; - - if (!filePath) { - throw new Error('Missing file path for transcription'); - } - - const res = await fetch(`http://127.0.0.1:${BACKEND_PORT}/transcribe`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - file_path: filePath, - model: modelName, - language, - use_gpu: true, - use_cache: true, - diarize: false, - }), - }); - - if (!res.ok) { - let detail = `${res.status} ${res.statusText}`; - try { - const body = await res.json(); - if (body?.detail) detail = `${detail}: ${body.detail}`; - } catch (_err) { - // ignore JSON parse errors for non-JSON responses - } - throw new Error(`Transcription failed: ${detail}`); - } - - return await res.json(); -}); - -ipcMain.handle('fs:readFile', async (_event, filePath) => { - const fs = require('fs'); - return fs.readFileSync(filePath, 'utf-8'); -}); - -ipcMain.handle('fs:writeFile', async (_event, filePath, content) => { - const fs = require('fs'); - fs.writeFileSync(filePath, content, 'utf-8'); - return true; -}); diff --git a/electron/preload.js b/electron/preload.js deleted file mode 100644 index 8ecc763..0000000 --- a/electron/preload.js +++ /dev/null @@ -1,15 +0,0 @@ -const { contextBridge, ipcRenderer } = require('electron'); - -contextBridge.exposeInMainWorld('electronAPI', { - openFile: (options) => ipcRenderer.invoke('dialog:openFile', options), - saveFile: (options) => ipcRenderer.invoke('dialog:saveFile', options), - openProject: () => ipcRenderer.invoke('dialog:openProject'), - saveProject: (options) => ipcRenderer.invoke('dialog:saveProject', options), - getBackendUrl: () => ipcRenderer.invoke('get-backend-url'), - ensureModel: (modelName) => ipcRenderer.invoke('backend:ensureModel', modelName), - transcribe: (filePath, modelName, language) => ipcRenderer.invoke('backend:transcribe', { filePath, modelName, language }), - encryptString: (data) => ipcRenderer.invoke('safe-storage:encrypt', data), - decryptString: (encrypted) => ipcRenderer.invoke('safe-storage:decrypt', encrypted), - readFile: (path) => ipcRenderer.invoke('fs:readFile', path), - writeFile: (path, content) => ipcRenderer.invoke('fs:writeFile', path, content), -}); diff --git a/electron/python-bridge.js b/electron/python-bridge.js deleted file mode 100644 index 7db1b41..0000000 --- a/electron/python-bridge.js +++ /dev/null @@ -1,105 +0,0 @@ -const { spawn } = require('child_process'); -const path = require('path'); -const http = require('http'); - -class PythonBackend { - constructor(port, isDev) { - this.port = port; - this.isDev = isDev; - this.process = null; - } - - async start() { - // In dev mode, check if a backend is already running (e.g. from `npm run dev:backend`) - // If so, reuse it instead of spawning a duplicate. - if (this.isDev) { - const alreadyRunning = await this._isPortOpen(2000); - if (alreadyRunning) { - console.log(`[backend] Dev backend already running on port ${this.port} — reusing it.`); - return; - } - } - - const backendDir = this.isDev - ? path.join(__dirname, '..', 'backend') - : path.join(process.resourcesPath, 'backend'); - - const pythonCmd = process.platform === 'win32' ? 'python' : '/home/dillon/.pyenv/versions/3.11.15/bin/python'; - - this.process = spawn(pythonCmd, [ - '-m', 'uvicorn', 'main:app', - '--host', '127.0.0.1', - '--port', String(this.port), - ], { - cwd: backendDir, - stdio: ['pipe', 'pipe', 'pipe'], - env: { ...process.env, PYTHONUNBUFFERED: '1' }, - }); - - this.process.stdout.on('data', (data) => { - console.log(`[backend] ${data.toString().trim()}`); - }); - - this.process.stderr.on('data', (data) => { - console.error(`[backend] ${data.toString().trim()}`); - }); - - this.process.on('error', (err) => { - console.error('[backend] Failed to start Python backend:', err.message); - }); - - this.process.on('exit', (code) => { - console.log(`[backend] Process exited with code ${code}`); - this.process = null; - }); - - await this._waitForReady(30000); - console.log(`[backend] Ready on port ${this.port}`); - } - - _isPortOpen(timeoutMs) { - return new Promise((resolve) => { - const req = http.get(`http://127.0.0.1:${this.port}/health`, (res) => { - resolve(res.statusCode === 200); - }); - req.on('error', () => resolve(false)); - req.setTimeout(timeoutMs, () => { req.destroy(); resolve(false); }); - req.end(); - }); - } - - stop() { - if (this.process) { - if (process.platform === 'win32') { - spawn('taskkill', ['/pid', String(this.process.pid), '/f', '/t']); - } else { - this.process.kill('SIGTERM'); - } - this.process = null; - } - } - - _waitForReady(timeoutMs) { - const startTime = Date.now(); - return new Promise((resolve, reject) => { - const check = () => { - if (Date.now() - startTime > timeoutMs) { - reject(new Error('Backend startup timed out')); - return; - } - const req = http.get(`http://127.0.0.1:${this.port}/health`, (res) => { - if (res.statusCode === 200) { - resolve(); - } else { - setTimeout(check, 500); - } - }); - req.on('error', () => setTimeout(check, 500)); - req.end(); - }; - setTimeout(check, 1000); - }); - } -} - -module.exports = { PythonBackend }; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 823ba9f..0fa0da1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useRef, useMemo } from 'react'; +import { useEffect, useState, useMemo } from 'react'; import { useEditorStore } from './store/editorStore'; import VideoPlayer from './components/VideoPlayer'; import TranscriptEditor from './components/TranscriptEditor'; @@ -16,7 +16,6 @@ import { Settings, Sparkles, Download, - FolderSearch, FileInput, Save, Scissors, @@ -27,7 +26,6 @@ import { RefreshCw, } from 'lucide-react'; -const IS_ELECTRON = !!window.electronAPI; const LAST_MEDIA_PATH_KEY = 'talkedit:lastMediaPath'; type Panel = 'ai' | 'settings' | 'export' | 'silence' | 'volume' | null; @@ -60,7 +58,6 @@ export default function App() { } = useEditorStore(); const [activePanel, setActivePanel] = useState(null); - const [manualPath, setManualPath] = useState(''); const [whisperModel, setWhisperModel] = useState('base'); const [cutMode, setCutMode] = useState(false); const [muteMode, setMuteMode] = useState(false); @@ -70,7 +67,6 @@ export default function App() { const [showUnsavedPrompt, setShowUnsavedPrompt] = useState(false); const [pendingProceedAction, setPendingProceedAction] = useState<(() => Promise) | null>(null); const [lastSavedSignature, setLastSavedSignature] = useState(null); - const fileInputRef = useRef(null); const projectSignature = useMemo(() => { if (!videoPath) return null; @@ -125,7 +121,7 @@ export default function App() { }; const runGuarded = async (action: () => Promise) => { - if (!IS_ELECTRON || !hasUnsavedChanges) { + if (!hasUnsavedChanges) { await action(); return; } @@ -150,16 +146,11 @@ export default function App() { }, []); useEffect(() => { - if (IS_ELECTRON) { - window.electronAPI!.getBackendUrl().then(setBackendUrl); - } - // In Tauri on Linux/WebKit2GTK the ipc:// custom protocol is blocked by - // WebKit internals; postMessage fallback works but logs noisy warnings. - // The backend URL is fixed at 127.0.0.1:8000 so we rely on the store default. + window.electronAPI!.getBackendUrl().then(setBackendUrl); }, [setBackendUrl]); useEffect(() => { - if (!IS_ELECTRON || videoPath) return; + if (videoPath) return; const savedPath = sessionStorage.getItem(LAST_MEDIA_PATH_KEY); if (savedPath) { loadVideo(savedPath); @@ -167,7 +158,6 @@ export default function App() { }, [videoPath, loadVideo]); useEffect(() => { - if (!IS_ELECTRON) return; if (videoPath) { sessionStorage.setItem(LAST_MEDIA_PATH_KEY, videoPath); return; @@ -176,7 +166,6 @@ export default function App() { }, [videoPath]); const handleLoadProject = async () => { - if (!IS_ELECTRON) return; await runGuarded(async () => { try { const projectPath = await window.electronAPI!.openProject(); @@ -192,7 +181,6 @@ export default function App() { }; const handleSaveProject = async (): Promise => { - if (!IS_ELECTRON) return false; try { const savePath = await window.electronAPI!.saveProject(); if (!savePath) return false; @@ -208,25 +196,15 @@ export default function App() { alert(`Failed to save project: ${err}`); return false; } - return false; }; const handleOpenFile = async () => { await runGuarded(async () => { - if (IS_ELECTRON) { - const path = await window.electronAPI!.openFile(); - if (path) { - setLastSavedSignature(null); - loadVideo(path); - await transcribeVideo(path); - } - } else { - // Browser: use the manual path input - const path = manualPath.trim(); - if (path) { - loadVideo(path); - await transcribeVideo(path); - } + const path = await window.electronAPI!.openFile(); + if (path) { + setLastSavedSignature(null); + loadVideo(path); + await transcribeVideo(path); } }); }; @@ -236,27 +214,15 @@ export default function App() { useEditorStore.getState().reset(); setLastSavedSignature(null); setActivePanel(null); - setManualPath(''); setCutMode(false); setMuteMode(false); setGainMode(false); }); }; - const handleManualSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - const path = manualPath.trim(); - if (!path) return; - loadVideo(path); - await transcribeVideo(path); - }; - const transcribeVideo = async (path: string) => { setTranscribing(true, 0, 'Checking model...'); try { - if (!window.electronAPI?.transcribe) { - throw new Error('Transcription not available'); - } // Step 1: ensure model is downloaded (may take a while on first run) const MODEL_SIZES: Record = { 'tiny': '~75 MB', @@ -294,10 +260,6 @@ export default function App() { const handleReprocessProject = async () => { if (!videoPath) return; - if (!window.electronAPI?.transcribe) { - alert('Reprocessing is only available in desktop mode.'); - return; - } await runGuarded(async () => { setShowReprocessConfirm(true); @@ -428,58 +390,22 @@ export default function App() { English-only models are ~10% faster and more accurate for English content.

- {IS_ELECTRON ? ( -
- - -
- ) : ( - /* Browser: manual path input */ -
-
- - Running in browser — paste the full path to your video file below. - -
-
-
- - setManualPath(e.target.value)} - placeholder="C:\Videos\my-video.mp4" - className="w-full pl-9 pr-3 py-2.5 bg-editor-surface border border-editor-border rounded-lg text-sm text-editor-text placeholder:text-editor-text-muted/40 focus:outline-none focus:border-editor-accent" - autoFocus - /> -
- -
-

- Supported: MP4, AVI, MOV, MKV, WebM, M4A -

-
- )} +
+ + +
); } @@ -508,23 +434,19 @@ export default function App() { } label="Open" - onClick={IS_ELECTRON ? handleOpenFile : () => useEditorStore.getState().reset()} + onClick={handleOpenFile} + /> + } + label="Save" + onClick={handleSaveProject} + disabled={words.length === 0} + /> + } + label="Load" + onClick={handleLoadProject} /> - {IS_ELECTRON && ( - } - label="Save" - onClick={handleSaveProject} - disabled={words.length === 0} - /> - )} - {IS_ELECTRON && ( - } - label="Load" - onClick={handleLoadProject} - /> - )} } label="Cut" diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index f3aa778..0ea679a 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; // Forward console.error/warn/log to backend in dev mode so we can tail webview.log import './lib/dev-logger'; -// Must be imported before App so window.electronAPI is patched before any component runs. +// Tauri bridge polyfill: must be imported before App so window.electronAPI is available to all components import './lib/tauri-bridge'; import App from './App'; import './index.css'; diff --git a/frontend/src/store/aiStore.ts b/frontend/src/store/aiStore.ts index 78bc277..4aba549 100644 --- a/frontend/src/store/aiStore.ts +++ b/frontend/src/store/aiStore.ts @@ -30,26 +30,15 @@ async function encryptAndStore(key: string, value: string): Promise { localStorage.removeItem(ENCRYPTED_KEY_PREFIX + key); return; } - if (window.electronAPI) { - const encrypted = await window.electronAPI.encryptString(value); - localStorage.setItem(ENCRYPTED_KEY_PREFIX + key, encrypted); - } else { - localStorage.setItem(ENCRYPTED_KEY_PREFIX + key, btoa(value)); - } + const encrypted = await window.electronAPI.encryptString(value); + localStorage.setItem(ENCRYPTED_KEY_PREFIX + key, encrypted); } async function loadAndDecrypt(key: string): Promise { const stored = localStorage.getItem(ENCRYPTED_KEY_PREFIX + key); if (!stored) return ''; - if (window.electronAPI) { - try { - return await window.electronAPI.decryptString(stored); - } catch { - return ''; - } - } try { - return atob(stored); + return await window.electronAPI.decryptString(stored); } catch { return ''; } diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts index 4625e75..97a8386 100644 --- a/frontend/src/vite-env.d.ts +++ b/frontend/src/vite-env.d.ts @@ -1,6 +1,6 @@ /// -interface ElectronAPI { +interface DesktopAPI { openFile: (options?: Record) => Promise; saveFile: (options?: Record) => Promise; openProject: () => Promise; @@ -15,5 +15,5 @@ interface ElectronAPI { } interface Window { - electronAPI?: ElectronAPI; + electronAPI: DesktopAPI; } diff --git a/package.json b/package.json index d376a1e..b0c2e45 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "version": "0.1.0", "private": true, "description": "TalkEdit — Open-source AI-powered text-based video editor", - "main": "electron/main.js", "scripts": { "tauri": "tauri", "dev": "cd frontend && npm run dev -- --host", @@ -15,36 +14,9 @@ }, "devDependencies": { "concurrently": "^9.1.0", - "electron": "^33.2.0", - "electron-builder": "^25.1.0", "wait-on": "^8.0.0" }, "dependencies": { "python-shell": "^5.0.0" - }, - "build": { - "appId": "com.talkedit.app", - "productName": "TalkEdit", - "files": [ - "electron/**/*", - "frontend/dist/**/*", - "backend/**/*", - "shared/**/*" - ], - "extraResources": [ - { - "from": "backend", - "to": "backend" - } - ], - "win": { - "target": "nsis" - }, - "mac": { - "target": "dmg" - }, - "linux": { - "target": "AppImage" - } } }