Release 1.1: Pre-meeting models, Calendar improvements, Logs, Compact Recorder
This commit is contained in:
@@ -40,6 +40,8 @@ interface RecorderProps {
|
||||
recordingSubject?: string;
|
||||
onAutoStartHandled?: () => void;
|
||||
addToast: (msg: string, type: 'success' | 'error' | 'info', duration?: number) => void;
|
||||
selectedModel: string;
|
||||
onModelChange: (model: string) => void;
|
||||
}
|
||||
|
||||
interface AudioDevice {
|
||||
@@ -51,14 +53,15 @@ const Recorder: React.FC<RecorderProps> = ({
|
||||
apiKey, productId, prompts,
|
||||
setTranscription, setSummary,
|
||||
onSaveToHistory, savePath, onRecordingComplete,
|
||||
onOpenSettings, addToast, ...props
|
||||
onOpenSettings, addToast, selectedModel, onModelChange, ...props
|
||||
}) => {
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [isStopping, setIsStopping] = useState(false); // New lock state
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const [status, setStatus] = useState<string>('Ready to record');
|
||||
const [selectedDevice, setSelectedDevice] = useState<string>('');
|
||||
const [selectedPromptId, setSelectedPromptId] = useState<string>('');
|
||||
const [selectedModel, setSelectedModel] = useState<string>('mixtral');
|
||||
// selectedModel is now a prop
|
||||
const [recordingMode, setRecordingMode] = useState<'voice' | 'meeting'>('voice');
|
||||
const [devices, setDevices] = useState<AudioDevice[]>([]);
|
||||
const [availableModels, setAvailableModels] = useState<Array<{ id: string, name: string }>>([]);
|
||||
@@ -191,9 +194,9 @@ const Recorder: React.FC<RecorderProps> = ({
|
||||
setSilenceDuration(diff);
|
||||
|
||||
// Auto-stop after 30 seconds of silence
|
||||
if (diff > 30) { // 30 seconds
|
||||
if (diff > 30 && !isStopping) { // Check lock
|
||||
console.log("Auto-stopping due to silence");
|
||||
setStatus("Auto-stopping (Silence detected)...");
|
||||
addToast("Auto-stopping (Silence detected)", "info", 3000);
|
||||
stopRecording();
|
||||
}
|
||||
}
|
||||
@@ -264,12 +267,18 @@ const Recorder: React.FC<RecorderProps> = ({
|
||||
};
|
||||
|
||||
const stopRecording = async () => {
|
||||
if (isStopping) return;
|
||||
setIsStopping(true);
|
||||
|
||||
try {
|
||||
setIsRecording(false);
|
||||
setIsPaused(false);
|
||||
setStatus('Processing...');
|
||||
const filePath = await invoke<string>('stop_recording');
|
||||
|
||||
// Wait a moment for file flush (safety)
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
|
||||
setStatus('Transcribing (Infomaniak Whisper)...');
|
||||
const transText = await invoke<string>('transcribe_audio', {
|
||||
filePath,
|
||||
@@ -343,51 +352,53 @@ const Recorder: React.FC<RecorderProps> = ({
|
||||
console.error(e);
|
||||
setStatus(`Error: ${e}`);
|
||||
addToast(`Error processing: ${e}`, 'error');
|
||||
} finally {
|
||||
setIsStopping(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full bg-background relative">
|
||||
{/* Fixed Header */}
|
||||
<div className="w-full flex justify-center items-center p-6 shrink-0">
|
||||
<img src={logo} alt="Logo" className="h-12 object-contain" />
|
||||
{/* Fixed Header - Reduced padding */}
|
||||
<div className="w-full flex justify-center items-center p-4 shrink-0">
|
||||
<img src={logo} alt="Logo" className="h-10 object-contain" />
|
||||
</div>
|
||||
|
||||
{/* Scrollable Content */}
|
||||
<div className="flex-1 overflow-y-auto p-6 flex flex-col items-center pb-20">
|
||||
<div className="mb-6 relative shrink-0">
|
||||
<div className={`w-32 h-32 rounded-full flex items-center justify-center transition-all duration-300 ${isRecording ? (isPaused ? 'bg-yellow-500/10' : 'bg-red-500/10 animate-pulse') : 'bg-secondary'}`}>
|
||||
{/* Scrollable Content - Reduced spacing */}
|
||||
<div className="flex-1 overflow-y-auto px-6 pb-6 flex flex-col items-center">
|
||||
<div className="mb-4 relative shrink-0">
|
||||
<div className={`w-24 h-24 rounded-full flex items-center justify-center transition-all duration-300 ${isRecording ? (isPaused ? 'bg-yellow-500/10' : 'bg-red-500/10 animate-pulse') : 'bg-secondary'}`}>
|
||||
{isRecording ? (
|
||||
<div className={`w-24 h-24 rounded-full flex items-center justify-center shadow-[0_0_20px_rgba(239,68,68,0.5)] ${isPaused ? 'bg-yellow-500' : 'bg-red-500'}`}>
|
||||
<Mic size={40} className="text-white animate-bounce" />
|
||||
<div className={`w-16 h-16 rounded-full flex items-center justify-center shadow-[0_0_20px_rgba(239,68,68,0.5)] ${isPaused ? 'bg-yellow-500' : 'bg-red-500'}`}>
|
||||
<Mic size={32} className="text-white animate-bounce" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-24 h-24 rounded-full bg-primary flex items-center justify-center">
|
||||
<Mic size={40} className="text-primary-foreground" />
|
||||
<div className="w-16 h-16 rounded-full bg-primary flex items-center justify-center">
|
||||
<Mic size={32} className="text-primary-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 className="text-2xl font-bold mb-2 text-foreground">
|
||||
<h1 className="text-xl font-bold mb-1 text-foreground">
|
||||
{isRecording ? (isPaused ? 'Paused' : 'Listening...') : 'Ready to Record'}
|
||||
</h1>
|
||||
|
||||
<p className="text-muted-foreground mb-6 text-center text-sm h-6">
|
||||
<p className="text-muted-foreground mb-4 text-center text-xs h-5">
|
||||
{status}
|
||||
{isRecording && !isPaused && silenceDuration > 10 && (
|
||||
<span className="block text-xs text-yellow-500 mt-1 opacity-80">
|
||||
Silence detected: {Math.floor(silenceDuration)}s (Auto-stop in {90 - Math.floor(silenceDuration)}s)
|
||||
<span className="block text-xs text-yellow-500 mt-0.5 opacity-80">
|
||||
Silence detected: {Math.floor(silenceDuration)}s
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
|
||||
<div className="w-full max-w-sm space-y-4 mb-6 shrink-0">
|
||||
<div className="w-full max-w-sm space-y-3 mb-4 shrink-0">
|
||||
{!isRecording ? (
|
||||
<button
|
||||
onClick={() => startRecording()}
|
||||
disabled={!apiKey || !productId}
|
||||
className="w-full py-4 text-lg font-semibold bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-all shadow-md hover:shadow-lg"
|
||||
className="w-full py-3 text-base font-semibold bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-all shadow-md hover:shadow-lg"
|
||||
>
|
||||
{!apiKey ? 'Configure API Key First' : 'Start Recording'}
|
||||
</button>
|
||||
@@ -456,9 +467,13 @@ const Recorder: React.FC<RecorderProps> = ({
|
||||
</label>
|
||||
<select
|
||||
value={selectedModel}
|
||||
onChange={(e) => setSelectedModel(e.target.value)}
|
||||
onChange={(e) => {
|
||||
onModelChange(e.target.value);
|
||||
// localStorage handled in App.tsx
|
||||
}}
|
||||
className="w-full p-2 text-sm bg-secondary rounded border border-border outline-none focus:ring-2 focus:ring-primary"
|
||||
disabled={isRecording}
|
||||
// Allow changing model while recording (since it's used for summary after)
|
||||
disabled={false}
|
||||
>
|
||||
{availableModels.map(m => (
|
||||
<option key={m.id} value={m.id}>{m.name}</option>
|
||||
@@ -475,7 +490,8 @@ const Recorder: React.FC<RecorderProps> = ({
|
||||
value={selectedPromptId}
|
||||
onChange={(e) => setSelectedPromptId(e.target.value)}
|
||||
className="w-full p-2 text-sm bg-secondary rounded border border-border outline-none focus:ring-2 focus:ring-primary"
|
||||
disabled={isRecording || prompts.length === 0}
|
||||
// Allow changing template while recording
|
||||
disabled={prompts.length === 0}
|
||||
>
|
||||
{prompts.map(p => (
|
||||
<option key={p.id} value={p.id}>{p.name}</option>
|
||||
|
||||
Reference in New Issue
Block a user