Release 1.1: Pre-meeting models, Calendar improvements, Logs, Compact Recorder

This commit is contained in:
michael.borak
2026-01-20 17:15:14 +01:00
parent f61bcf1cc3
commit 79f509951c
16 changed files with 2011 additions and 321 deletions

View File

@@ -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>