
-
-
-
-
-
-
- {showHistory ? (
-
-
Saved Recordings
- {history.length === 0 &&
No saved history.
}
- {history.map(item => (
-
-
-
{item.date}
-
-
-
-
-
-
{item.summary ? item.summary.slice(0, 50) + "..." : "No Summary"}
-
{item.transcription.slice(0, 50)}...
+
+
+ {isRecording ? (
+
+
- ))}
-
- ) : (
- <>
-
-
- {isRecording ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
-
-
- {isRecording ? 'Listening...' : 'Ready to Record'}
-
-
-
- {status}
-
-
-
- {!isRecording ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {(transcription || summary) && (
-
- {/* Transcription Block (Source) */}
- {transcription && (
-
-
-
-
-
Original Transcription
-
-
-
-
- {transcription}
-
-
- )}
-
- {/* Summary Block (Result) */}
- {summary && (
-
-
-
-
-
AI Summary
-
-
-
-
-
-
-
-
- {summary}
-
-
-
- )}
+ ) : (
+
+
)}
- >
- )}
+
+
+
+
+ {isRecording ? (isPaused ? 'Paused' : 'Listening...') : 'Ready to Record'}
+
+
+
+ {status}
+
+
+
+ {!isRecording ? (
+
+ ) : (
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx
index 5f8dc66..3b91d26 100644
--- a/src/components/Settings.tsx
+++ b/src/components/Settings.tsx
@@ -1,157 +1,319 @@
-import React, { useState, useEffect } from 'react';
-import { Plus, Trash2 } from 'lucide-react';
-
-interface PromptTemplate {
- id: string;
- name: string;
- content: string;
-}
+import React, { useState } from 'react';
+import { Save, FolderOpen, Lock, Upload, Download, Eye, EyeOff } from 'lucide-react';
+import { open } from '@tauri-apps/plugin-dialog';
+import { encryptData, decryptData } from '../utils/backup';
+import { PromptTemplate } from '../App';
interface SettingsProps {
- onSave: (apiKey: string, productId: string, prompts: PromptTemplate[]) => void;
- onBack: () => void; // New onBack prop
- initialApiKey: string;
- initialProductId: string;
- initialPrompts: PromptTemplate[];
+ apiKey: string;
+ productId: string;
+ savePath: string;
+ prompts: PromptTemplate[];
+ onSave: (apiKey: string, productId: string, prompts: PromptTemplate[], savePath: string) => void;
+ onClose: () => void;
}
-const Settings: React.FC
= ({ onSave, onBack, initialApiKey, initialProductId, initialPrompts }) => {
- const [apiKey, setApiKey] = useState(initialApiKey);
- const [productId, setProductId] = useState(initialProductId);
- const [prompts, setPrompts] = useState(initialPrompts);
+const Settings: React.FC = ({ apiKey, productId, prompts, savePath, onSave, onClose }) => {
+ const [localApiKey, setLocalApiKey] = useState(apiKey);
+ const [localProductId, setLocalProductId] = useState(productId);
+ const [localSavePath, setLocalSavePath] = useState(savePath);
+ const [localPrompts, setLocalPrompts] = useState(prompts);
+ const [statusIdx, setStatusIdx] = useState(null);
- useEffect(() => {
- setApiKey(initialApiKey);
- setProductId(initialProductId);
- // Only reset prompts if passed different ones (mounting), usually state is preserved in App
- }, [initialApiKey, initialProductId]);
+ // Backup & Restore State
+ const [backupPassword, setBackupPassword] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
+ const [isImportModalOpen, setIsImportModalOpen] = useState(false);
+ const [importFileContent, setImportFileContent] = useState(null);
- const handleSave = (e: React.FormEvent) => {
- e.preventDefault();
- onSave(apiKey, productId, prompts);
+ const handlePromptChange = (id: string, field: 'name' | 'content', value: string) => {
+ setLocalPrompts(localPrompts.map(p => p.id === id ? { ...p, [field]: value } : p));
};
const addPrompt = () => {
- setPrompts([...prompts, { id: Date.now().toString(), name: 'New Prompt', content: '' }]);
- };
-
- const updatePrompt = (id: string, field: 'name' | 'content', value: string) => {
- setPrompts(prompts.map(p => p.id === id ? { ...p, [field]: value } : p));
+ setLocalPrompts([...localPrompts, { id: Date.now().toString(), name: 'New Prompt', content: '' }]);
};
const removePrompt = (id: string) => {
- setPrompts(prompts.filter(p => p.id !== id));
+ setLocalPrompts(localPrompts.filter(p => p.id !== id));
+ };
+
+ const handleSave = () => {
+ onSave(localApiKey, localProductId, localPrompts, localSavePath);
+ onClose();
+ };
+
+ const handleSelectFolder = async () => {
+ try {
+ const selected = await open({
+ directory: true,
+ multiple: false,
+ defaultPath: localSavePath || undefined,
+ });
+ if (selected && typeof selected === 'string') {
+ setLocalSavePath(selected);
+ }
+ } catch (e) {
+ console.error("Failed to open directory picker", e);
+ setStatusIdx('Error: Failed to open directory picker.');
+ }
+ };
+
+ const handleExport = async () => {
+ if (!backupPassword) {
+ setStatusIdx('Error: Password required to encrypt backup.');
+ return;
+ }
+ try {
+ const data = {
+ apiKey: localApiKey,
+ productId: localProductId,
+ prompts: localPrompts,
+ savePath: localSavePath
+ };
+ const encrypted = await encryptData(data, backupPassword);
+ const blob = new Blob([encrypted], { type: 'text/plain' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `hearbit_backup_${new Date().toISOString().slice(0, 10)}.conf`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ setStatusIdx('Configuration exported successfully!');
+ } catch (e) {
+ console.error(e);
+ setStatusIdx('Export failed.');
+ }
+ };
+
+ const triggerImport = () => {
+ document.getElementById('import-file-input')?.click();
+ };
+
+ const handleFileSelect = (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ if (event.target?.result) {
+ setImportFileContent(event.target.result as string);
+ setIsImportModalOpen(true);
+ setBackupPassword('');
+ }
+ };
+ reader.readAsText(file);
+ e.target.value = '';
+ };
+
+ const confirmImport = async () => {
+ if (!backupPassword) {
+ setStatusIdx('Error: Password required to decrypt.');
+ return;
+ }
+ if (!importFileContent) return;
+
+ try {
+ const data = await decryptData(importFileContent, backupPassword);
+ if (data.apiKey) setLocalApiKey(data.apiKey);
+ if (data.productId) setLocalProductId(data.productId);
+ if (data.prompts) setLocalPrompts(data.prompts);
+ if (data.savePath) setLocalSavePath(data.savePath);
+
+ setStatusIdx('Configuration imported! Click Save to apply.');
+ setIsImportModalOpen(false);
+ setImportFileContent(null);
+ } catch (e) {
+ console.error(e);
+ setStatusIdx('Import failed: Wrong password or corrupted file.');
+ }
};
return (
-
-
-
Infomaniak Settings
-