still working on crashes
This commit is contained in:
@ -17,8 +17,10 @@ from routers import transcribe, export, ai, captions, audio
|
|||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Dev log file — frontend forwards console.error/warn here so the agent can read it
|
# Dev log file — keep outside workspace to avoid dev watcher reload loops.
|
||||||
DEV_LOG_PATH = Path(__file__).parent.parent / "webview.log"
|
DEV_LOG_PATH = Path(
|
||||||
|
os.environ.get("TALKEDIT_DEV_LOG_PATH", str(Path(tempfile.gettempdir()) / "talkedit-webview.log"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
@ -205,6 +207,7 @@ async def dev_log(request: Request):
|
|||||||
if args:
|
if args:
|
||||||
line += " " + " ".join(args)
|
line += " " + " ".join(args)
|
||||||
line += "\n"
|
line += "\n"
|
||||||
|
DEV_LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
with open(DEV_LOG_PATH, "a") as f:
|
with open(DEV_LOG_PATH, "a") as f:
|
||||||
f.write(line)
|
f.write(line)
|
||||||
return {"ok": True}
|
return {"ok": True, "path": str(DEV_LOG_PATH)}
|
||||||
|
|||||||
8
close
8
close
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
KILLED_ANY=0
|
KILLED_ANY=0
|
||||||
BACKEND_PORT="${BACKEND_PORT:-8000}"
|
BACKEND_PORT="${BACKEND_PORT:-8000}"
|
||||||
|
FRONTEND_PORT="${FRONTEND_PORT:-5173}"
|
||||||
|
|
||||||
kill_port() {
|
kill_port() {
|
||||||
local port=$1
|
local port=$1
|
||||||
@ -10,7 +11,7 @@ kill_port() {
|
|||||||
local pids
|
local pids
|
||||||
pids=$(lsof -ti tcp:"$port" 2>/dev/null)
|
pids=$(lsof -ti tcp:"$port" 2>/dev/null)
|
||||||
if [[ -n "$pids" ]]; then
|
if [[ -n "$pids" ]]; then
|
||||||
echo "Stopping $name backend (port $port, PID $pids)..."
|
echo "Stopping $name (port $port, PID $pids)..."
|
||||||
kill "$pids" 2>/dev/null
|
kill "$pids" 2>/dev/null
|
||||||
KILLED_ANY=1
|
KILLED_ANY=1
|
||||||
fi
|
fi
|
||||||
@ -31,8 +32,9 @@ kill_pattern() {
|
|||||||
# --- TalkEdit (Tauri) ---
|
# --- TalkEdit (Tauri) ---
|
||||||
kill_port "$BACKEND_PORT" "TalkEdit"
|
kill_port "$BACKEND_PORT" "TalkEdit"
|
||||||
kill_pattern "tauri.*TalkEdit\|TalkEdit.*tauri\|cargo.*tauri dev\|/TalkEdit/target/debug" "TalkEdit (Tauri dev)"
|
kill_pattern "tauri.*TalkEdit\|TalkEdit.*tauri\|cargo.*tauri dev\|/TalkEdit/target/debug" "TalkEdit (Tauri dev)"
|
||||||
# Vite dev server for TalkEdit (port 5173)
|
# Frontend dev server: first kill by listening port, then by known process patterns.
|
||||||
kill_pattern "vite.*5173\|rsbuild.*5173" "TalkEdit frontend dev server"
|
kill_port "$FRONTEND_PORT" "TalkEdit frontend"
|
||||||
|
kill_pattern "vite\|rsbuild\|npm.*run dev\|pnpm.*dev\|yarn.*dev" "TalkEdit frontend dev server"
|
||||||
|
|
||||||
# --- Orphaned uvicorn workers for TalkEdit ---
|
# --- Orphaned uvicorn workers for TalkEdit ---
|
||||||
kill_pattern "uvicorn.*main:app.*--port ${BACKEND_PORT}" "leftover uvicorn workers (TalkEdit)"
|
kill_pattern "uvicorn.*main:app.*--port ${BACKEND_PORT}" "leftover uvicorn workers (TalkEdit)"
|
||||||
|
|||||||
@ -49,6 +49,7 @@ export default function App() {
|
|||||||
const [cutMode, setCutMode] = useState(false);
|
const [cutMode, setCutMode] = useState(false);
|
||||||
const [muteMode, setMuteMode] = useState(false);
|
const [muteMode, setMuteMode] = useState(false);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const lastVideoPathRef = useRef<string | null>(null);
|
||||||
|
|
||||||
useKeyboardShortcuts();
|
useKeyboardShortcuts();
|
||||||
|
|
||||||
@ -74,13 +75,44 @@ export default function App() {
|
|||||||
// The backend URL is fixed at 127.0.0.1:8000 so we rely on the store default.
|
// The backend URL is fixed at 127.0.0.1:8000 so we rely on the store default.
|
||||||
}, [setBackendUrl]);
|
}, [setBackendUrl]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!import.meta.env.DEV) return;
|
||||||
|
const previousVideoPath = lastVideoPathRef.current;
|
||||||
|
if (previousVideoPath !== videoPath) {
|
||||||
|
console.log('[app-state] videoPath transition', {
|
||||||
|
from: previousVideoPath,
|
||||||
|
to: videoPath,
|
||||||
|
wordCount: words.length,
|
||||||
|
isTranscribing,
|
||||||
|
});
|
||||||
|
if (previousVideoPath && !videoPath) {
|
||||||
|
console.warn('[app-state] videoPath cleared and UI will show welcome screen', {
|
||||||
|
previousVideoPath,
|
||||||
|
wordCount: words.length,
|
||||||
|
isTranscribing,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
lastVideoPathRef.current = videoPath;
|
||||||
|
}
|
||||||
|
}, [videoPath, words.length, isTranscribing]);
|
||||||
|
|
||||||
const handleLoadProject = async () => {
|
const handleLoadProject = async () => {
|
||||||
if (!IS_DESKTOP) return;
|
if (!IS_DESKTOP) return;
|
||||||
try {
|
try {
|
||||||
|
if (import.meta.env.DEV) console.log('[app-action] loadProject:dialogOpen');
|
||||||
const projectPath = await window.desktopAPI!.openProject();
|
const projectPath = await window.desktopAPI!.openProject();
|
||||||
|
if (import.meta.env.DEV) console.log('[app-action] loadProject:dialogResult', { projectPath });
|
||||||
if (!projectPath) return;
|
if (!projectPath) return;
|
||||||
const content = await window.desktopAPI!.readFile(projectPath);
|
const content = await window.desktopAPI!.readFile(projectPath);
|
||||||
const data = JSON.parse(content);
|
const data = JSON.parse(content);
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.log('[app-action] loadProject:parsed', {
|
||||||
|
projectPath,
|
||||||
|
videoPath: data?.videoPath,
|
||||||
|
words: Array.isArray(data?.words) ? data.words.length : null,
|
||||||
|
segments: Array.isArray(data?.segments) ? data.segments.length : null,
|
||||||
|
});
|
||||||
|
}
|
||||||
useEditorStore.getState().loadProject(data);
|
useEditorStore.getState().loadProject(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load project:', err);
|
console.error('Failed to load project:', err);
|
||||||
@ -104,8 +136,11 @@ export default function App() {
|
|||||||
|
|
||||||
const handleOpenFile = async () => {
|
const handleOpenFile = async () => {
|
||||||
if (IS_DESKTOP) {
|
if (IS_DESKTOP) {
|
||||||
|
if (import.meta.env.DEV) console.log('[app-action] openFile:dialogOpen');
|
||||||
const path = await window.desktopAPI!.openFile();
|
const path = await window.desktopAPI!.openFile();
|
||||||
|
if (import.meta.env.DEV) console.log('[app-action] openFile:dialogResult', { path });
|
||||||
if (path) {
|
if (path) {
|
||||||
|
if (import.meta.env.DEV) console.log('[app-action] openFile:loadVideo', { path });
|
||||||
loadVideo(path);
|
loadVideo(path);
|
||||||
await transcribeVideo(path);
|
await transcribeVideo(path);
|
||||||
}
|
}
|
||||||
@ -113,6 +148,7 @@ export default function App() {
|
|||||||
// Browser: use the manual path input
|
// Browser: use the manual path input
|
||||||
const path = manualPath.trim();
|
const path = manualPath.trim();
|
||||||
if (path) {
|
if (path) {
|
||||||
|
if (import.meta.env.DEV) console.log('[app-action] openFile:webManualPath', { path });
|
||||||
loadVideo(path);
|
loadVideo(path);
|
||||||
await transcribeVideo(path);
|
await transcribeVideo(path);
|
||||||
}
|
}
|
||||||
@ -123,11 +159,13 @@ export default function App() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const path = manualPath.trim();
|
const path = manualPath.trim();
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
|
if (import.meta.env.DEV) console.log('[app-action] manualSubmit:loadVideo', { path });
|
||||||
loadVideo(path);
|
loadVideo(path);
|
||||||
await transcribeVideo(path);
|
await transcribeVideo(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
const transcribeVideo = async (path: string) => {
|
const transcribeVideo = async (path: string) => {
|
||||||
|
if (import.meta.env.DEV) console.log('[app-action] transcribe:start', { path, whisperModel });
|
||||||
setTranscribing(true, 0, 'Checking model...');
|
setTranscribing(true, 0, 'Checking model...');
|
||||||
try {
|
try {
|
||||||
if (!window.desktopAPI?.transcribe) {
|
if (!window.desktopAPI?.transcribe) {
|
||||||
@ -154,15 +192,25 @@ export default function App() {
|
|||||||
const modelLabel = MODEL_SIZES[whisperModel] ?? 'unknown size';
|
const modelLabel = MODEL_SIZES[whisperModel] ?? 'unknown size';
|
||||||
setTranscribing(true, 5, `Downloading ${whisperModel} model (${modelLabel})...`);
|
setTranscribing(true, 5, `Downloading ${whisperModel} model (${modelLabel})...`);
|
||||||
await window.desktopAPI.ensureModel(whisperModel);
|
await window.desktopAPI.ensureModel(whisperModel);
|
||||||
|
if (import.meta.env.DEV) console.log('[app-action] transcribe:modelReady', { whisperModel });
|
||||||
|
|
||||||
// Step 2: run transcription
|
// Step 2: run transcription
|
||||||
setTranscribing(true, 20, 'Transcribing audio...');
|
setTranscribing(true, 20, 'Transcribing audio...');
|
||||||
const data = await window.desktopAPI.transcribe(path, whisperModel);
|
const data = await window.desktopAPI.transcribe(path, whisperModel);
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.log('[app-action] transcribe:result', {
|
||||||
|
path,
|
||||||
|
words: Array.isArray(data?.words) ? data.words.length : null,
|
||||||
|
segments: Array.isArray(data?.segments) ? data.segments.length : null,
|
||||||
|
language: data?.language,
|
||||||
|
});
|
||||||
|
}
|
||||||
setTranscription(data);
|
setTranscription(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Transcription error:', err);
|
console.error('Transcription error:', err);
|
||||||
alert(`Transcription failed. Check the console for details.\n\n${err}`);
|
alert(`Transcription failed. Check the console for details.\n\n${err}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
if (import.meta.env.DEV) console.log('[app-action] transcribe:finish', { path });
|
||||||
setTranscribing(false);
|
setTranscribing(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,13 +1,29 @@
|
|||||||
/**
|
/**
|
||||||
* Dev-only console interceptor.
|
* Dev-only console interceptor.
|
||||||
* Forwards console.error / console.warn to the backend /dev/log endpoint,
|
* Forwards console.error / console.warn to the backend /dev/log endpoint,
|
||||||
* which appends them to webview.log so the agent can read it.
|
* which appends them to a backend-managed dev log file.
|
||||||
*/
|
*/
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
const BACKEND = 'http://127.0.0.1:8000';
|
const BACKEND = 'http://127.0.0.1:8000';
|
||||||
|
|
||||||
type ConsoleFn = (...args: unknown[]) => void;
|
type ConsoleFn = (...args: unknown[]) => void;
|
||||||
|
|
||||||
|
const serialize = (value: unknown): string => {
|
||||||
|
if (typeof value === 'string') return value;
|
||||||
|
if (value instanceof Error) {
|
||||||
|
return JSON.stringify({
|
||||||
|
name: value.name,
|
||||||
|
message: value.message,
|
||||||
|
stack: value.stack,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.stringify(value);
|
||||||
|
} catch {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const forward = (level: string, orig: ConsoleFn): ConsoleFn =>
|
const forward = (level: string, orig: ConsoleFn): ConsoleFn =>
|
||||||
(...args: unknown[]) => {
|
(...args: unknown[]) => {
|
||||||
orig(...args);
|
orig(...args);
|
||||||
@ -15,7 +31,7 @@ if (import.meta.env.DEV) {
|
|||||||
fetch(`${BACKEND}/dev/log`, {
|
fetch(`${BACKEND}/dev/log`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ level, message: String(first ?? ''), args: rest.map(String) }),
|
body: JSON.stringify({ level, message: serialize(first ?? ''), args: rest.map(serialize) }),
|
||||||
}).catch(() => {/* backend not running yet */});
|
}).catch(() => {/* backend not running yet */});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -28,31 +28,48 @@ const EXPORT_FILTERS = [
|
|||||||
const BACKEND_PORT = import.meta.env.VITE_BACKEND_PORT || '8000';
|
const BACKEND_PORT = import.meta.env.VITE_BACKEND_PORT || '8000';
|
||||||
const BACKEND_URL = `http://127.0.0.1:${BACKEND_PORT}`;
|
const BACKEND_URL = `http://127.0.0.1:${BACKEND_PORT}`;
|
||||||
|
|
||||||
|
const debugBridge = (event: string, details?: Record<string, unknown>) => {
|
||||||
|
if (!import.meta.env.DEV) return;
|
||||||
|
console.log('[tauri-bridge]', event, details ?? {});
|
||||||
|
};
|
||||||
|
|
||||||
window.desktopAPI = {
|
window.desktopAPI = {
|
||||||
openFile: async (_options?: Record<string, unknown>): Promise<string | null> => {
|
openFile: async (_options?: Record<string, unknown>): Promise<string | null> => {
|
||||||
|
debugBridge('openFile:dialogOpen');
|
||||||
const result = await open({
|
const result = await open({
|
||||||
multiple: false,
|
multiple: false,
|
||||||
filters: VIDEO_FILTERS,
|
filters: VIDEO_FILTERS,
|
||||||
});
|
});
|
||||||
return typeof result === 'string' ? result : null;
|
const path = typeof result === 'string' ? result : null;
|
||||||
|
debugBridge('openFile:dialogResult', { path });
|
||||||
|
return path;
|
||||||
},
|
},
|
||||||
|
|
||||||
saveFile: async (_options?: Record<string, unknown>): Promise<string | null> => {
|
saveFile: async (_options?: Record<string, unknown>): Promise<string | null> => {
|
||||||
|
debugBridge('saveFile:dialogOpen');
|
||||||
const result = await save({ filters: EXPORT_FILTERS });
|
const result = await save({ filters: EXPORT_FILTERS });
|
||||||
return result ?? null;
|
const path = result ?? null;
|
||||||
|
debugBridge('saveFile:dialogResult', { path });
|
||||||
|
return path;
|
||||||
},
|
},
|
||||||
|
|
||||||
openProject: async (): Promise<string | null> => {
|
openProject: async (): Promise<string | null> => {
|
||||||
|
debugBridge('openProject:dialogOpen');
|
||||||
const result = await open({
|
const result = await open({
|
||||||
multiple: false,
|
multiple: false,
|
||||||
filters: PROJECT_FILTERS,
|
filters: PROJECT_FILTERS,
|
||||||
});
|
});
|
||||||
return typeof result === 'string' ? result : null;
|
const path = typeof result === 'string' ? result : null;
|
||||||
|
debugBridge('openProject:dialogResult', { path });
|
||||||
|
return path;
|
||||||
},
|
},
|
||||||
|
|
||||||
saveProject: async (): Promise<string | null> => {
|
saveProject: async (): Promise<string | null> => {
|
||||||
|
debugBridge('saveProject:dialogOpen');
|
||||||
const result = await save({ filters: PROJECT_FILTERS });
|
const result = await save({ filters: PROJECT_FILTERS });
|
||||||
return result ?? null;
|
const path = result ?? null;
|
||||||
|
debugBridge('saveProject:dialogResult', { path });
|
||||||
|
return path;
|
||||||
},
|
},
|
||||||
|
|
||||||
getBackendUrl: (): Promise<string> => {
|
getBackendUrl: (): Promise<string> => {
|
||||||
@ -77,10 +94,12 @@ window.desktopAPI = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
readFile: (path: string): Promise<string> => {
|
readFile: (path: string): Promise<string> => {
|
||||||
|
debugBridge('readFile', { path });
|
||||||
return readTextFile(path);
|
return readTextFile(path);
|
||||||
},
|
},
|
||||||
|
|
||||||
writeFile: async (path: string, content: string): Promise<boolean> => {
|
writeFile: async (path: string, content: string): Promise<boolean> => {
|
||||||
|
debugBridge('writeFile', { path, size: content.length });
|
||||||
await writeTextFile(path, content);
|
await writeTextFile(path, content);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
// Forward console.error/warn/log to backend in dev mode so we can tail webview.log
|
// Forward console.error/warn/log to backend in dev mode so we can tail the backend dev log.
|
||||||
import './lib/dev-logger';
|
import './lib/dev-logger';
|
||||||
// Must be imported before App so window.desktopAPI is patched before any component runs.
|
// Must be imported before App so window.desktopAPI is patched before any component runs.
|
||||||
import './lib/tauri-bridge';
|
import './lib/tauri-bridge';
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
import { temporal } from 'zundo';
|
import { temporal } from 'zundo';
|
||||||
import type { Word, Segment, DeletedRange, CutRange, MuteRange, TranscriptionResult, ProjectFile } from '../types/project';
|
import type { Word, Segment, DeletedRange, CutRange, MuteRange, TranscriptionResult, ProjectFile } from '../types/project';
|
||||||
|
|
||||||
@ -84,12 +85,21 @@ const initialState: EditorState = {
|
|||||||
|
|
||||||
let nextRangeId = 1;
|
let nextRangeId = 1;
|
||||||
|
|
||||||
|
const debugEditorStore = (event: string, details?: Record<string, unknown>) => {
|
||||||
|
if (!import.meta.env.DEV) return;
|
||||||
|
console.log('[editor-store]', event, details ?? {});
|
||||||
|
};
|
||||||
|
|
||||||
export const useEditorStore = create<EditorState & EditorActions>()(
|
export const useEditorStore = create<EditorState & EditorActions>()(
|
||||||
temporal(
|
persist(
|
||||||
(set, get) => ({
|
temporal(
|
||||||
|
(set, get) => ({
|
||||||
...initialState,
|
...initialState,
|
||||||
|
|
||||||
setBackendUrl: (url) => set({ backendUrl: url }),
|
setBackendUrl: (url) => {
|
||||||
|
debugEditorStore('setBackendUrl', { url });
|
||||||
|
set({ backendUrl: url });
|
||||||
|
},
|
||||||
|
|
||||||
setExportedAudioPath: (path) => set({ exportedAudioPath: path }),
|
setExportedAudioPath: (path) => set({ exportedAudioPath: path }),
|
||||||
|
|
||||||
@ -123,12 +133,18 @@ export const useEditorStore = create<EditorState & EditorActions>()(
|
|||||||
: `${backend}/file?path=${encodeURIComponent(filePath)}`;
|
: `${backend}/file?path=${encodeURIComponent(filePath)}`;
|
||||||
};
|
};
|
||||||
const url = buildMediaUrl(path);
|
const url = buildMediaUrl(path);
|
||||||
|
debugEditorStore('loadVideo:start', {
|
||||||
|
path,
|
||||||
|
backend,
|
||||||
|
previousVideoPath: get().videoPath,
|
||||||
|
});
|
||||||
set({
|
set({
|
||||||
...initialState,
|
...initialState,
|
||||||
backendUrl: backend,
|
backendUrl: backend,
|
||||||
videoPath: path,
|
videoPath: path,
|
||||||
videoUrl: url,
|
videoUrl: url,
|
||||||
});
|
});
|
||||||
|
debugEditorStore('loadVideo:done', { path, url });
|
||||||
},
|
},
|
||||||
|
|
||||||
setTranscription: (result) => {
|
setTranscription: (result) => {
|
||||||
@ -138,6 +154,12 @@ export const useEditorStore = create<EditorState & EditorActions>()(
|
|||||||
globalIdx += seg.words.length;
|
globalIdx += seg.words.length;
|
||||||
return annotated;
|
return annotated;
|
||||||
});
|
});
|
||||||
|
debugEditorStore('setTranscription', {
|
||||||
|
wordCount: result.words?.length ?? 0,
|
||||||
|
segmentCount: result.segments?.length ?? 0,
|
||||||
|
language: result.language,
|
||||||
|
currentVideoPath: get().videoPath,
|
||||||
|
});
|
||||||
set({
|
set({
|
||||||
words: result.words,
|
words: result.words,
|
||||||
segments: annotatedSegments,
|
segments: annotatedSegments,
|
||||||
@ -310,10 +332,18 @@ export const useEditorStore = create<EditorState & EditorActions>()(
|
|||||||
|
|
||||||
loadProject: (data) => {
|
loadProject: (data) => {
|
||||||
const backend = get().backendUrl;
|
const backend = get().backendUrl;
|
||||||
const isWav = data.videoPath.toLowerCase().endsWith('.wav');
|
const resolvedVideoPath = typeof data?.videoPath === 'string' ? data.videoPath : null;
|
||||||
|
if (!resolvedVideoPath) {
|
||||||
|
debugEditorStore('loadProject:invalidVideoPath', {
|
||||||
|
videoPathType: typeof data?.videoPath,
|
||||||
|
hasKeys: data && typeof data === 'object' ? Object.keys(data as Record<string, unknown>) : [],
|
||||||
|
});
|
||||||
|
throw new Error('Project file missing required videoPath string');
|
||||||
|
}
|
||||||
|
const isWav = resolvedVideoPath.toLowerCase().endsWith('.wav');
|
||||||
const url = isWav
|
const url = isWav
|
||||||
? `${backend}/file?path=${encodeURIComponent(data.videoPath)}&format=mp3`
|
? `${backend}/file?path=${encodeURIComponent(resolvedVideoPath)}&format=mp3`
|
||||||
: `${backend}/file?path=${encodeURIComponent(data.videoPath)}`;
|
: `${backend}/file?path=${encodeURIComponent(resolvedVideoPath)}`;
|
||||||
|
|
||||||
let globalIdx = 0;
|
let globalIdx = 0;
|
||||||
const annotatedSegments = (data.segments || []).map((seg: Segment) => {
|
const annotatedSegments = (data.segments || []).map((seg: Segment) => {
|
||||||
@ -322,10 +352,20 @@ export const useEditorStore = create<EditorState & EditorActions>()(
|
|||||||
return annotated;
|
return annotated;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
debugEditorStore('loadProject:start', {
|
||||||
|
videoPath: resolvedVideoPath,
|
||||||
|
words: Array.isArray(data?.words) ? data.words.length : null,
|
||||||
|
segments: Array.isArray(data?.segments) ? data.segments.length : null,
|
||||||
|
cutRanges: Array.isArray(data?.cutRanges) ? data.cutRanges.length : null,
|
||||||
|
muteRanges: Array.isArray(data?.muteRanges) ? data.muteRanges.length : null,
|
||||||
|
deletedRanges: Array.isArray(data?.deletedRanges) ? data.deletedRanges.length : null,
|
||||||
|
previousVideoPath: get().videoPath,
|
||||||
|
});
|
||||||
|
|
||||||
set({
|
set({
|
||||||
...initialState,
|
...initialState,
|
||||||
backendUrl: backend,
|
backendUrl: backend,
|
||||||
videoPath: data.videoPath,
|
videoPath: resolvedVideoPath,
|
||||||
videoUrl: url,
|
videoUrl: url,
|
||||||
words: data.words || [],
|
words: data.words || [],
|
||||||
segments: annotatedSegments,
|
segments: annotatedSegments,
|
||||||
@ -335,9 +375,21 @@ export const useEditorStore = create<EditorState & EditorActions>()(
|
|||||||
language: data.language || '',
|
language: data.language || '',
|
||||||
exportedAudioPath: data.exportedAudioPath ?? null,
|
exportedAudioPath: data.exportedAudioPath ?? null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
debugEditorStore('loadProject:done', {
|
||||||
|
videoPath: resolvedVideoPath,
|
||||||
|
url,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
reset: () => set(initialState),
|
reset: () => {
|
||||||
|
const stack = new Error().stack?.split('\n').slice(1, 6).join(' | ');
|
||||||
|
debugEditorStore('reset', {
|
||||||
|
previousVideoPath: get().videoPath,
|
||||||
|
stack,
|
||||||
|
});
|
||||||
|
set(initialState);
|
||||||
|
},
|
||||||
|
|
||||||
pauseUndo: () => {
|
pauseUndo: () => {
|
||||||
// Access the temporal store through the useEditorStore
|
// Access the temporal store through the useEditorStore
|
||||||
@ -354,7 +406,39 @@ export const useEditorStore = create<EditorState & EditorActions>()(
|
|||||||
temporalStore.getState().resume();
|
temporalStore.getState().resume();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{ limit: 100 },
|
{ limit: 100 },
|
||||||
|
),
|
||||||
|
{
|
||||||
|
name: 'talkedit-editor-session',
|
||||||
|
version: 1,
|
||||||
|
partialize: (state) => ({
|
||||||
|
videoPath: state.videoPath,
|
||||||
|
videoUrl: state.videoUrl,
|
||||||
|
exportedAudioPath: state.exportedAudioPath,
|
||||||
|
words: state.words,
|
||||||
|
segments: state.segments,
|
||||||
|
deletedRanges: state.deletedRanges,
|
||||||
|
cutRanges: state.cutRanges,
|
||||||
|
muteRanges: state.muteRanges,
|
||||||
|
language: state.language,
|
||||||
|
backendUrl: state.backendUrl,
|
||||||
|
currentTime: state.currentTime,
|
||||||
|
duration: state.duration,
|
||||||
|
}),
|
||||||
|
onRehydrateStorage: () => (state, error) => {
|
||||||
|
if (error) {
|
||||||
|
debugEditorStore('persist:rehydrate:error', { error: String(error) });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugEditorStore('persist:rehydrate:done', {
|
||||||
|
videoPath: state?.videoPath ?? null,
|
||||||
|
words: state?.words?.length ?? 0,
|
||||||
|
segments: state?.segments?.length ?? 0,
|
||||||
|
cutRanges: state?.cutRanges?.length ?? 0,
|
||||||
|
muteRanges: state?.muteRanges?.length ?? 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
15
open
15
open
@ -5,6 +5,7 @@ PROJECT_DIR="$PWD"
|
|||||||
|
|
||||||
export BACKEND_PORT="${BACKEND_PORT:-8000}"
|
export BACKEND_PORT="${BACKEND_PORT:-8000}"
|
||||||
export VITE_BACKEND_PORT="${VITE_BACKEND_PORT:-$BACKEND_PORT}"
|
export VITE_BACKEND_PORT="${VITE_BACKEND_PORT:-$BACKEND_PORT}"
|
||||||
|
export TALKEDIT_DEV_LOG_PATH="${TALKEDIT_DEV_LOG_PATH:-/tmp/talkedit-webview.log}"
|
||||||
BACKEND_URL="http://127.0.0.1:${BACKEND_PORT}/health"
|
BACKEND_URL="http://127.0.0.1:${BACKEND_PORT}/health"
|
||||||
FRONTEND_URL="http://127.0.0.1:5173"
|
FRONTEND_URL="http://127.0.0.1:5173"
|
||||||
|
|
||||||
@ -18,20 +19,20 @@ else
|
|||||||
|
|
||||||
# Try common terminal emulators in order
|
# Try common terminal emulators in order
|
||||||
if command -v ghostty &>/dev/null; then
|
if command -v ghostty &>/dev/null; then
|
||||||
ghostty -e bash -c "cd '${BACKEND_DIR}' && '${VENV_PYTHON}' -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT}; exec bash" &
|
ghostty -e bash -c "cd '${BACKEND_DIR}' && TALKEDIT_DEV_LOG_PATH='${TALKEDIT_DEV_LOG_PATH}' '${VENV_PYTHON}' -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT}; exec bash" &
|
||||||
elif command -v kitty &>/dev/null; then
|
elif command -v kitty &>/dev/null; then
|
||||||
kitty --title "TalkEdit Backend" -- bash -c "cd '${BACKEND_DIR}' && '${VENV_PYTHON}' -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT}; exec bash" &
|
kitty --title "TalkEdit Backend" -- bash -c "cd '${BACKEND_DIR}' && TALKEDIT_DEV_LOG_PATH='${TALKEDIT_DEV_LOG_PATH}' '${VENV_PYTHON}' -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT}; exec bash" &
|
||||||
elif command -v alacritty &>/dev/null; then
|
elif command -v alacritty &>/dev/null; then
|
||||||
alacritty --title "TalkEdit Backend" -e bash -c "cd '${BACKEND_DIR}' && '${VENV_PYTHON}' -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT}; exec bash" &
|
alacritty --title "TalkEdit Backend" -e bash -c "cd '${BACKEND_DIR}' && TALKEDIT_DEV_LOG_PATH='${TALKEDIT_DEV_LOG_PATH}' '${VENV_PYTHON}' -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT}; exec bash" &
|
||||||
elif command -v konsole &>/dev/null; then
|
elif command -v konsole &>/dev/null; then
|
||||||
konsole --new-tab -e bash -c "cd '${BACKEND_DIR}' && '${VENV_PYTHON}' -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT}; exec bash" &
|
konsole --new-tab -e bash -c "cd '${BACKEND_DIR}' && TALKEDIT_DEV_LOG_PATH='${TALKEDIT_DEV_LOG_PATH}' '${VENV_PYTHON}' -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT}; exec bash" &
|
||||||
elif command -v gnome-terminal &>/dev/null; then
|
elif command -v gnome-terminal &>/dev/null; then
|
||||||
gnome-terminal --title "TalkEdit Backend" -- bash -c "cd '${BACKEND_DIR}' && '${VENV_PYTHON}' -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT}; exec bash" &
|
gnome-terminal --title "TalkEdit Backend" -- bash -c "cd '${BACKEND_DIR}' && TALKEDIT_DEV_LOG_PATH='${TALKEDIT_DEV_LOG_PATH}' '${VENV_PYTHON}' -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT}; exec bash" &
|
||||||
elif command -v xterm &>/dev/null; then
|
elif command -v xterm &>/dev/null; then
|
||||||
xterm -T "TalkEdit Backend" -e bash -c "cd '${BACKEND_DIR}' && '${VENV_PYTHON}' -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT}; exec bash" &
|
xterm -T "TalkEdit Backend" -e bash -c "cd '${BACKEND_DIR}' && TALKEDIT_DEV_LOG_PATH='${TALKEDIT_DEV_LOG_PATH}' '${VENV_PYTHON}' -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT}; exec bash" &
|
||||||
else
|
else
|
||||||
echo "No supported terminal emulator found. Starting backend in background..."
|
echo "No supported terminal emulator found. Starting backend in background..."
|
||||||
cd "${BACKEND_DIR}" && "${VENV_PYTHON}" -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT} &
|
cd "${BACKEND_DIR}" && TALKEDIT_DEV_LOG_PATH="${TALKEDIT_DEV_LOG_PATH}" "${VENV_PYTHON}" -m uvicorn main:app --host 127.0.0.1 --port ${BACKEND_PORT} &
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Wait up to 15s for backend to become ready
|
# Wait up to 15s for backend to become ready
|
||||||
|
|||||||
Reference in New Issue
Block a user