removed electron
This commit is contained in:
@ -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 & 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"
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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 '';
|
||||
}
|
||||
|
||||
4
frontend/src/vite-env.d.ts
vendored
4
frontend/src/vite-env.d.ts
vendored
@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user