removed electron

This commit is contained in:
2026-04-15 17:40:27 -06:00
parent 48d761c713
commit 84edddded8
11 changed files with 73 additions and 512 deletions

View File

@ -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:

View File

@ -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

15
close
View File

@ -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

View File

@ -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 <repo>/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;
});

View File

@ -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),
});

View File

@ -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 };

View File

@ -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<Panel>(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<void>) | null>(null);
const [lastSavedSignature, setLastSavedSignature] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const projectSignature = useMemo(() => {
if (!videoPath) return null;
@ -125,7 +121,7 @@ export default function App() {
};
const runGuarded = async (action: () => Promise<void>) => {
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<boolean> => {
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<string, string> = {
'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.
</p>
{IS_ELECTRON ? (
<div className="flex flex-col items-center gap-3">
<button
onClick={handleOpenFile}
className="flex items-center gap-2 px-6 py-3 bg-editor-accent hover:bg-editor-accent-hover rounded-lg text-white font-medium transition-colors"
>
<FolderOpen className="w-5 h-5" />
Open Video File
</button>
<button
onClick={handleLoadProject}
className="flex items-center gap-2 px-4 py-2 text-sm text-editor-text-muted hover:text-editor-text hover:bg-editor-surface rounded-lg transition-colors"
>
<FileInput className="w-4 h-4" />
Load Project (.aive)
</button>
</div>
) : (
/* Browser: manual path input */
<div className="w-full max-w-lg space-y-3">
<div className="flex items-center gap-2 px-3 py-1.5 bg-editor-warning/10 border border-editor-warning/30 rounded-lg">
<span className="text-editor-warning text-xs">
Running in browser paste the full path to your video file below.
</span>
</div>
<form onSubmit={handleManualSubmit} className="flex gap-2">
<div className="flex-1 relative">
<FolderSearch className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-editor-text-muted pointer-events-none" />
<input
ref={fileInputRef}
type="text"
value={manualPath}
onChange={(e) => 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
/>
</div>
<button
type="submit"
disabled={!manualPath.trim()}
className="flex items-center gap-2 px-5 py-2.5 bg-editor-accent hover:bg-editor-accent-hover disabled:opacity-40 rounded-lg text-sm text-white font-medium transition-colors whitespace-nowrap"
>
<Film className="w-4 h-4" />
Load &amp; Transcribe
</button>
</form>
<p className="text-[11px] text-editor-text-muted text-center">
Supported: MP4, AVI, MOV, MKV, WebM, M4A
</p>
</div>
)}
<div className="flex flex-col items-center gap-3">
<button
onClick={handleOpenFile}
className="flex items-center gap-2 px-6 py-3 bg-editor-accent hover:bg-editor-accent-hover rounded-lg text-white font-medium transition-colors"
>
<FolderOpen className="w-5 h-5" />
Open Video File
</button>
<button
onClick={handleLoadProject}
className="flex items-center gap-2 px-4 py-2 text-sm text-editor-text-muted hover:text-editor-text hover:bg-editor-surface rounded-lg transition-colors"
>
<FileInput className="w-4 h-4" />
Load Project (.aive)
</button>
</div>
</div>
);
}
@ -508,23 +434,19 @@ export default function App() {
<ToolbarButton
icon={<FolderOpen className="w-4 h-4" />}
label="Open"
onClick={IS_ELECTRON ? handleOpenFile : () => useEditorStore.getState().reset()}
onClick={handleOpenFile}
/>
<ToolbarButton
icon={<Save className="w-4 h-4" />}
label="Save"
onClick={handleSaveProject}
disabled={words.length === 0}
/>
<ToolbarButton
icon={<FileInput className="w-4 h-4" />}
label="Load"
onClick={handleLoadProject}
/>
{IS_ELECTRON && (
<ToolbarButton
icon={<Save className="w-4 h-4" />}
label="Save"
onClick={handleSaveProject}
disabled={words.length === 0}
/>
)}
{IS_ELECTRON && (
<ToolbarButton
icon={<FileInput className="w-4 h-4" />}
label="Load"
onClick={handleLoadProject}
/>
)}
<ToolbarButton
icon={<Scissors className="w-4 h-4" />}
label="Cut"

View File

@ -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';

View File

@ -30,26 +30,15 @@ async function encryptAndStore(key: string, value: string): Promise<void> {
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<string> {
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 '';
}

View File

@ -1,6 +1,6 @@
/// <reference types="vite/client" />
interface ElectronAPI {
interface DesktopAPI {
openFile: (options?: Record<string, unknown>) => Promise<string | null>;
saveFile: (options?: Record<string, unknown>) => Promise<string | null>;
openProject: () => Promise<string | null>;
@ -15,5 +15,5 @@ interface ElectronAPI {
}
interface Window {
electronAPI?: ElectronAPI;
electronAPI: DesktopAPI;
}

View File

@ -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"
}
}
}