From 84edddded84129d8cbc746f32ae722974f1a2ec8 Mon Sep 17 00:00:00 2001
From: dillonj
Date: Wed, 15 Apr 2026 17:40:27 -0600
Subject: [PATCH] removed electron
---
.github/copilot-instructions.md | 13 ++-
README.md | 36 +++---
close | 15 +--
electron/main.js | 198 --------------------------------
electron/preload.js | 15 ---
electron/python-bridge.js | 105 -----------------
frontend/src/App.tsx | 152 ++++++------------------
frontend/src/main.tsx | 2 +-
frontend/src/store/aiStore.ts | 17 +--
frontend/src/vite-env.d.ts | 4 +-
package.json | 28 -----
11 files changed, 73 insertions(+), 512 deletions(-)
delete mode 100644 electron/main.js
delete mode 100644 electron/preload.js
delete mode 100644 electron/python-bridge.js
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.
-
-
-
-
- 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"
- }
}
}