Files
TalkEdit/frontend/src/components/DevPanel.tsx

129 lines
5.2 KiB
TypeScript
Raw Normal View History

2026-03-30 18:36:41 -06:00
import { useState, useCallback } from 'react';
import { useEditorStore } from '../store/editorStore';
import { Terminal, ChevronDown, ChevronUp, Play, Wifi } from 'lucide-react';
export default function DevPanel() {
const [open, setOpen] = useState(false);
const [pathInput, setPathInput] = useState('');
const [testResult, setTestResult] = useState<string | null>(null);
const [testing, setTesting] = useState(false);
const { backendUrl, videoPath, loadVideo } = useEditorStore();
const handleLoad = useCallback(() => {
const p = pathInput.trim();
if (p) loadVideo(p);
}, [pathInput, loadVideo]);
const testEndpoint = useCallback(async (endpoint: string) => {
setTesting(true);
setTestResult(null);
try {
const url = `${backendUrl}${endpoint}`;
const res = await fetch(url);
const text = res.headers.get('content-type')?.includes('json')
? JSON.stringify(await res.json(), null, 2)
: `${res.status} ${res.statusText} (${res.headers.get('content-type') ?? 'no type'})`;
setTestResult(`${url}\n${text}`);
} catch (e) {
setTestResult(`${e}`);
} finally {
setTesting(false);
}
}, [backendUrl]);
const testWaveform = useCallback(async () => {
const p = pathInput.trim() || videoPath;
if (!p) { setTestResult('No path to test'); return; }
setTesting(true);
setTestResult(null);
try {
const url = `${backendUrl}/audio/waveform?path=${encodeURIComponent(p)}`;
const res = await fetch(url);
if (res.ok) {
const buf = await res.arrayBuffer();
setTestResult(`✓ Waveform OK — ${buf.byteLength} bytes\n${url}`);
} else {
const body = await res.text().catch(() => '');
setTestResult(`✗ HTTP ${res.status}\n${body}`);
}
} catch (e) {
setTestResult(`${e}`);
} finally {
setTesting(false);
}
}, [backendUrl, pathInput, videoPath]);
return (
<div className="fixed bottom-0 right-0 z-50 w-96 font-mono text-[11px]">
{/* Header */}
<button
onClick={() => setOpen(o => !o)}
className="w-full flex items-center justify-between px-3 py-1.5 bg-[#0d0f1a] border-t border-l border-[#2a2d3e] text-[#6b7280] hover:text-white"
>
<span className="flex items-center gap-1.5">
<Terminal className="w-3 h-3" />
DevPanel
<span className="ml-2 text-[#4a4f6a]">{backendUrl}</span>
</span>
{open ? <ChevronDown className="w-3 h-3" /> : <ChevronUp className="w-3 h-3" />}
</button>
{open && (
<div className="bg-[#0d0f1a] border-t border-l border-[#2a2d3e] p-3 space-y-3">
{/* State */}
<div className="space-y-0.5 text-[#4a4f6a]">
<div>backendUrl: <span className="text-[#6366f1]">{backendUrl}</span></div>
<div className="truncate">videoPath: <span className="text-[#6366f1]">{videoPath ?? 'null'}</span></div>
</div>
{/* Load file by path */}
<div className="space-y-1">
<div className="text-[#6b7280] uppercase tracking-wider text-[9px]">Load file</div>
<div className="flex gap-1">
<input
type="text"
value={pathInput}
onChange={e => setPathInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleLoad()}
placeholder={videoPath ?? '/path/to/file.wav'}
className="flex-1 bg-[#13141f] border border-[#2a2d3e] rounded px-2 py-1 text-white placeholder-[#2a2d3e] focus:outline-none focus:border-[#6366f1]"
/>
<button
onClick={handleLoad}
disabled={!pathInput.trim()}
className="px-2 py-1 bg-[#6366f1] hover:bg-[#4f52d4] disabled:opacity-30 rounded text-white"
>
<Play className="w-3 h-3" />
</button>
</div>
</div>
{/* Quick tests */}
<div className="space-y-1">
<div className="text-[#6b7280] uppercase tracking-wider text-[9px]">Test endpoints</div>
<div className="flex flex-wrap gap-1">
<button onClick={() => testEndpoint('/health')} className="px-2 py-0.5 bg-[#1e2030] hover:bg-[#2a2d3e] rounded text-[#6b7280] hover:text-white flex items-center gap-1">
<Wifi className="w-2.5 h-2.5" />/health
</button>
<button onClick={() => testEndpoint('/audio/capabilities')} className="px-2 py-0.5 bg-[#1e2030] hover:bg-[#2a2d3e] rounded text-[#6b7280] hover:text-white">
/audio/capabilities
</button>
<button onClick={testWaveform} disabled={testing} className="px-2 py-0.5 bg-[#1e2030] hover:bg-[#2a2d3e] disabled:opacity-40 rounded text-[#6b7280] hover:text-white">
/audio/waveform
</button>
</div>
</div>
{/* Result */}
{testResult && (
<pre className="bg-[#13141f] border border-[#2a2d3e] rounded p-2 text-[10px] text-[#9ca3af] whitespace-pre-wrap break-all max-h-32 overflow-y-auto">
{testResult}
</pre>
)}
</div>
)}
</div>
);
}