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

@@ -1,9 +1,8 @@
import { useState, useEffect } from 'react';
import { listen } from "@tauri-apps/api/event";
import { useState } from 'react';
import { Settings as SettingsIcon } from "lucide-react";
import Settings from "./components/Settings";
import Settings, { SmtpConfig, AzureConfig } from "./components/Settings";
import Recorder from "./components/Recorder";
import LogViewer, { LogEntry } from "./components/LogViewer";
import TranscriptionView from "./components/TranscriptionView";
import Tabs from "./components/Tabs";
import MeetingsView from "./components/MeetingsView";
@@ -17,9 +16,16 @@ export interface PromptTemplate {
keywords?: string[];
}
export interface EmailTemplate {
id: string;
name: string;
subject: string;
body: string;
}
function App() {
const [view, setView] = useState<'recorder' | 'logs' | 'settings' | 'transcription' | 'meetings' | 'history'>('recorder');
const [lastTab, setLastTab] = useState<'recorder' | 'logs' | 'transcription' | 'meetings' | 'history'>('recorder');
const [view, setView] = useState<'recorder' | 'settings' | 'transcription' | 'meetings' | 'history'>('recorder');
const [lastTab, setLastTab] = useState<'recorder' | 'transcription' | 'meetings' | 'history'>('recorder');
// Auto-start recording state to handle "Join & Record" transition
@@ -40,6 +46,23 @@ function App() {
const [apiKey, setApiKey] = useState(localStorage.getItem('infomaniak_api_key') || '');
const [productId, setProductId] = useState(localStorage.getItem('infomaniak_product_id') || '');
const [savePath, setSavePath] = useState(localStorage.getItem('infomaniak_save_path') || '');
const [smtpConfig, setSmtpConfig] = useState<SmtpConfig>(() => {
const saved = localStorage.getItem('hearbit_smtp_config');
return saved ? JSON.parse(saved) : { host: '', port: '587', user: '', pass: '', sender: '', senderName: '' };
});
const [azureConfig, setAzureConfig] = useState<AzureConfig>(() => {
const saved = localStorage.getItem('hearbit_azure_config');
return saved ? JSON.parse(saved) : { clientId: '', tenantId: '' };
});
const [selectedModel, setSelectedModel] = useState<string>(() => {
return localStorage.getItem('hearbit_selected_model') || 'mixtral';
});
const handleModelChange = (model: string) => {
setSelectedModel(model);
localStorage.setItem('hearbit_selected_model', model);
};
// Default prompts if none exist
/* eslint-disable no-useless-escape */ // Escape quotes in prompts
@@ -164,20 +187,70 @@ Wann findet das nächste Meeting statt oder was ist der nächste Meilenstein?`,
}
];
// Default Email Templates
const defaultEmailTemplates: EmailTemplate[] = [
{
id: '1',
name: 'Meeting Summary (Standard)',
subject: 'Meeting Summary: {{subject}}',
body: `Hi everyone,
Here is the summary of our meeting "{{subject}}" from {{date}}.
{{summary}}
Best regards,
Hearbit Assistant`
},
{
id: '2',
name: 'Action Items Only',
subject: 'Action Items: {{subject}}',
body: `Hi Team,
Please find below the action items from our call on {{date}}:
{{summary}}
Thanks!`
}
];
const [prompts, setPrompts] = useState<PromptTemplate[]>(() => {
const saved = localStorage.getItem('infomaniak_prompts');
return saved ? JSON.parse(saved) : defaultPrompts;
});
const handleSaveSettings = (newApiKey: string, newProductId: string, newPrompts: PromptTemplate[], newSavePath: string) => {
const [emailTemplates, setEmailTemplates] = useState<EmailTemplate[]>(() => {
const saved = localStorage.getItem('hearbit_email_templates');
return saved ? JSON.parse(saved) : defaultEmailTemplates;
});
const handleSaveSettings = (
newApiKey: string,
newProductId: string,
newPrompts: PromptTemplate[],
newSavePath: string,
newSmtp: SmtpConfig,
newAzure: AzureConfig,
newEmailTemplates: EmailTemplate[]
) => {
setApiKey(newApiKey);
setProductId(newProductId);
setPrompts(newPrompts);
setSavePath(newSavePath);
setSmtpConfig(newSmtp);
setAzureConfig(newAzure);
setEmailTemplates(newEmailTemplates);
localStorage.setItem('infomaniak_api_key', newApiKey);
localStorage.setItem('infomaniak_product_id', newProductId);
localStorage.setItem('infomaniak_prompts', JSON.stringify(newPrompts));
localStorage.setItem('infomaniak_save_path', newSavePath);
localStorage.setItem('hearbit_smtp_config', JSON.stringify(newSmtp));
localStorage.setItem('hearbit_azure_config', JSON.stringify(newAzure));
localStorage.setItem('hearbit_email_templates', JSON.stringify(newEmailTemplates));
setView(lastTab);
};
@@ -250,18 +323,7 @@ Wann findet das nächste Meeting statt oder was ist der nächste Meilenstein?`,
setView('transcription'); // Switch to Transcription view to see content
};
// Logs State
const [logs, setLogs] = useState<LogEntry[]>([]);
useEffect(() => {
const unlisten = listen<LogEntry>('log-event', (event) => {
setLogs((prevLogs) => [...prevLogs, event.payload]);
});
return () => {
unlisten.then(f => f());
};
}, []);
return (
<div className="min-h-screen bg-background text-foreground flex flex-col select-none overflow-hidden">
@@ -271,7 +333,7 @@ Wann findet das nächste Meeting statt oder was ist der nächste Meilenstein?`,
<div className="absolute right-4 top-4">
<button
onClick={() => {
setLastTab(view === 'logs' || view === 'history' ? view : 'recorder');
setLastTab(view === 'history' ? view : 'recorder');
setView('settings');
}}
className="p-2 text-muted-foreground hover:text-foreground hover:bg-secondary rounded-full transition-colors"
@@ -281,7 +343,7 @@ Wann findet das nächste Meeting statt oder was ist der nächste Meilenstein?`,
</div>
<Tabs
currentTab={view as 'recorder' | 'logs' | 'transcription' | 'meetings' | 'history'}
currentTab={view as 'recorder' | 'transcription' | 'meetings' | 'history'}
onTabChange={(t) => setView(t)}
/>
</div>
@@ -313,11 +375,34 @@ Wann findet das nächste Meeting statt oder was ist der nächste Meilenstein?`,
recordingSubject={recordingSubject}
onAutoStartHandled={() => setAutoStartRecording(false)}
addToast={addToast}
selectedModel={selectedModel}
onModelChange={handleModelChange}
/>
)}
{view === 'transcription' && (
<TranscriptionView transcription={transcription} summary={summary} />
<TranscriptionView
transcription={transcription}
summary={summary}
smtpConfig={smtpConfig}
apiKey={apiKey}
productId={productId}
prompts={prompts}
emailTemplates={emailTemplates}
onUpdateSummary={(newSummary) => {
setSummary(newSummary); // Update view
// Also update history item if it exists
// We identify by transcription content match (simple heuristic) or we should track currentId
const histIdx = history.findIndex(h => h.transcription === transcription);
if (histIdx >= 0) {
const newHist = [...history];
newHist[histIdx] = { ...newHist[histIdx], summary: newSummary };
setHistory(newHist);
localStorage.setItem('infomaniak_history', JSON.stringify(newHist));
}
}}
addToast={addToast}
/>
)}
{view === 'history' && (
@@ -329,18 +414,22 @@ Wann findet das nächste Meeting statt oder was ist der nächste Meilenstein?`,
)}
{view === 'meetings' && (
<MeetingsView
azureClientId={azureConfig.clientId}
onStartRecording={(subject) => {
setView('recorder');
setRecordingSubject(subject || '');
setAutoStartRecording(true);
}}
apiKey={apiKey}
productId={productId}
selectedModel={selectedModel}
onModelChange={handleModelChange}
/>
)}
{view === 'logs' && (
<LogViewer logs={logs} />
)}
{view === 'settings' && (
<Settings
@@ -350,6 +439,9 @@ Wann findet das nächste Meeting statt oder was ist der nächste Meilenstein?`,
productId={productId}
prompts={prompts}
savePath={savePath}
smtpConfig={smtpConfig}
azureConfig={azureConfig}
emailTemplates={emailTemplates}
/>
)}
</div>