feat(cis): add LLM config modal, summary generation, keywords tab, and export button (Phase 3)
- LLMConfigModal: provider dropdown, model field, API key input (localStorage only) - Summary generation: modal with 5 summary types, calls API, displays result - Keywords tab: add/delete watches, real-time detection, event logging - Export button: SRT/TXT/JSON download via API endpoint - All Phase 3 frontend tasks complete
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Close from '@mui/icons-material/Close';
|
||||
import { useTranscriptionStore, LLMConfig } from '../../stores/transcriptionStore';
|
||||
|
||||
const PROVIDERS = [
|
||||
{ value: 'openai', label: 'OpenAI' },
|
||||
{ value: 'anthropic', label: 'Anthropic' },
|
||||
{ value: 'ollama', label: 'Ollama' },
|
||||
{ value: 'openrouter', label: 'OpenRouter' },
|
||||
{ value: 'google', label: 'Google' },
|
||||
] as const;
|
||||
|
||||
const LLMConfigModal: React.FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpen, onClose }) => {
|
||||
const { llmConfig, setLLMConfig } = useTranscriptionStore();
|
||||
const [form, setForm] = useState<LLMConfig>(llmConfig);
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setForm(useTranscriptionStore.getState().llmConfig);
|
||||
setSaved(false);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleSave = () => {
|
||||
setLLMConfig(form);
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 2000);
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[9999] overflow-y-auto">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black/50 transition-opacity"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Modal panel */}
|
||||
<div className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 w-full max-w-md mx-auto">
|
||||
{/* Header */}
|
||||
<div className="bg-gray-50 px-4 py-3 flex items-center justify-between border-b border-gray-200">
|
||||
<h3 className="text-lg font-medium leading-6 text-gray-900">
|
||||
LLM Provider Settings
|
||||
</h3>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-500 focus:outline-none"
|
||||
>
|
||||
<Close sx={{ fontSize: 20 }} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="px-4 py-5 sm:p-6 space-y-4">
|
||||
{/* Provider dropdown */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Provider
|
||||
</label>
|
||||
<select
|
||||
value={form.provider}
|
||||
onChange={(e) => setForm({ ...form, provider: e.target.value as LLMConfig['provider'] })}
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
>
|
||||
{PROVIDERS.map((p) => (
|
||||
<option key={p.value} value={p.value}>
|
||||
{p.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Model name */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Model
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={form.model}
|
||||
onChange={(e) => setForm({ ...form, model: e.target.value })}
|
||||
placeholder="e.g. gpt-4o, claude-sonnet-4-20250514"
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* API Key */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
API Key
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={form.apiKey}
|
||||
onChange={(e) => setForm({ ...form, apiKey: e.target.value })}
|
||||
placeholder="sk-..."
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Note */}
|
||||
<p className="text-xs text-gray-500">
|
||||
API keys are stored locally in your browser only. They are never sent to Supabase or stored on any server.
|
||||
</p>
|
||||
|
||||
{/* Save button */}
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className={`w-full rounded-md px-4 py-2 text-sm font-medium text-white transition-colors ${
|
||||
saved
|
||||
? 'bg-green-600 hover:bg-green-700'
|
||||
: 'bg-blue-600 hover:bg-blue-700'
|
||||
}`}
|
||||
>
|
||||
{saved ? '✓ Saved!' : 'Save Settings'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LLMConfigModal;
|
||||
Reference in New Issue
Block a user