feat: add more options for deleting recordings automatically and added button to open the folder that holds the recordings (#330)
* feat: add more options for deleting recordings automatically and added button to open the folder that olds the recordings - deleting automatically options: after certain time, never or preserving certain number of recordings - new button "Open Recordings Folder" located on History page * refactor: moved "Delete Recordings" selector to Debug settings * refactor: simplify opening recordings folder logic by using tauri's opener plugin * clean up ui and code a bit --------- Co-authored-by: CJ Pais <cj@cjpais.com>
This commit is contained in:
parent
45eec57226
commit
3b3ed5abc5
13 changed files with 319 additions and 59 deletions
|
|
@ -60,7 +60,35 @@ pub async fn update_history_limit(
|
|||
crate::settings::write_settings(&app, settings);
|
||||
|
||||
history_manager
|
||||
.update_history_limit()
|
||||
.cleanup_old_entries()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_recording_retention_period(
|
||||
app: AppHandle,
|
||||
history_manager: State<'_, Arc<HistoryManager>>,
|
||||
period: String,
|
||||
) -> Result<(), String> {
|
||||
use crate::settings::RecordingRetentionPeriod;
|
||||
|
||||
let retention_period = match period.as_str() {
|
||||
"never" => RecordingRetentionPeriod::Never,
|
||||
"preserve_limit" => RecordingRetentionPeriod::PreserveLimit,
|
||||
"days3" => RecordingRetentionPeriod::Days3,
|
||||
"weeks2" => RecordingRetentionPeriod::Weeks2,
|
||||
"months3" => RecordingRetentionPeriod::Months3,
|
||||
_ => return Err(format!("Invalid retention period: {}", period)),
|
||||
};
|
||||
|
||||
let mut settings = crate::settings::get_settings(&app);
|
||||
settings.recording_retention_period = retention_period;
|
||||
crate::settings::write_settings(&app, settings);
|
||||
|
||||
history_manager
|
||||
.cleanup_old_entries()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ pub mod transcription;
|
|||
|
||||
use crate::utils::cancel_current_operation;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn cancel_operation(app: AppHandle) {
|
||||
|
|
@ -20,3 +21,20 @@ pub fn get_app_dir_path(app: AppHandle) -> Result<String, String> {
|
|||
|
||||
Ok(app_data_dir.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn open_recordings_folder(app: AppHandle) -> Result<(), String> {
|
||||
let app_data_dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| format!("Failed to get app data directory: {}", e))?;
|
||||
|
||||
let recordings_dir = app_data_dir.join("recordings");
|
||||
|
||||
let path = recordings_dir.to_string_lossy().as_ref().to_string();
|
||||
app.opener()
|
||||
.open_path(path, None::<String>)
|
||||
.map_err(|e| format!("Failed to open recordings folder: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,6 +253,7 @@ pub fn run() {
|
|||
trigger_update_check,
|
||||
commands::cancel_operation,
|
||||
commands::get_app_dir_path,
|
||||
commands::open_recordings_folder,
|
||||
commands::models::get_available_models,
|
||||
commands::models::get_model_info,
|
||||
commands::models::download_model,
|
||||
|
|
@ -286,7 +287,8 @@ pub fn run() {
|
|||
commands::history::toggle_history_entry_saved,
|
||||
commands::history::get_audio_file_path,
|
||||
commands::history::delete_history_entry,
|
||||
commands::history::update_history_limit
|
||||
commands::history::update_history_limit,
|
||||
commands::history::update_recording_retention_period
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
|
|
|||
|
|
@ -102,11 +102,6 @@ impl HistoryManager {
|
|||
post_processed_text: Option<String>,
|
||||
post_process_prompt: Option<String>,
|
||||
) -> Result<()> {
|
||||
// If history limit is 0, do not save at all.
|
||||
if crate::settings::get_history_limit(&self.app_handle) == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let timestamp = Utc::now().timestamp();
|
||||
let file_name = format!("handy-{}.wav", timestamp);
|
||||
let title = self.format_timestamp_title(timestamp);
|
||||
|
|
@ -155,7 +150,57 @@ impl HistoryManager {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn cleanup_old_entries(&self) -> Result<()> {
|
||||
pub fn cleanup_old_entries(&self) -> Result<()> {
|
||||
let retention_period = crate::settings::get_recording_retention_period(&self.app_handle);
|
||||
|
||||
match retention_period {
|
||||
crate::settings::RecordingRetentionPeriod::Never => {
|
||||
// Don't delete anything
|
||||
return Ok(());
|
||||
}
|
||||
crate::settings::RecordingRetentionPeriod::PreserveLimit => {
|
||||
// Use the old count-based logic with history_limit
|
||||
let limit = crate::settings::get_history_limit(&self.app_handle);
|
||||
return self.cleanup_by_count(limit);
|
||||
}
|
||||
_ => {
|
||||
// Use time-based logic
|
||||
return self.cleanup_by_time(retention_period);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_entries_and_files(&self, entries: &[(i64, String)]) -> Result<usize> {
|
||||
if entries.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let conn = self.get_connection()?;
|
||||
let mut deleted_count = 0;
|
||||
|
||||
for (id, file_name) in entries {
|
||||
// Delete database entry
|
||||
conn.execute(
|
||||
"DELETE FROM transcription_history WHERE id = ?1",
|
||||
params![id],
|
||||
)?;
|
||||
|
||||
// Delete WAV file
|
||||
let file_path = self.recordings_dir.join(file_name);
|
||||
if file_path.exists() {
|
||||
if let Err(e) = fs::remove_file(&file_path) {
|
||||
error!("Failed to delete WAV file {}: {}", file_name, e);
|
||||
} else {
|
||||
debug!("Deleted old WAV file: {}", file_name);
|
||||
deleted_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(deleted_count)
|
||||
}
|
||||
|
||||
fn cleanup_by_count(&self, limit: usize) -> Result<()> {
|
||||
let conn = self.get_connection()?;
|
||||
|
||||
// Get all entries that are not saved, ordered by timestamp desc
|
||||
|
|
@ -172,29 +217,48 @@ impl HistoryManager {
|
|||
entries.push(row?);
|
||||
}
|
||||
|
||||
let limit = crate::settings::get_history_limit(&self.app_handle);
|
||||
if entries.len() > limit {
|
||||
let entries_to_delete = &entries[limit..];
|
||||
let deleted_count = self.delete_entries_and_files(entries_to_delete)?;
|
||||
|
||||
for (id, file_name) in entries_to_delete {
|
||||
// Delete database entry
|
||||
conn.execute(
|
||||
"DELETE FROM transcription_history WHERE id = ?1",
|
||||
params![id],
|
||||
)?;
|
||||
|
||||
// Delete WAV file
|
||||
let file_path = self.recordings_dir.join(file_name);
|
||||
if file_path.exists() {
|
||||
if let Err(e) = fs::remove_file(&file_path) {
|
||||
error!("Failed to delete WAV file {}: {}", file_name, e);
|
||||
} else {
|
||||
debug!("Deleted old WAV file: {}", file_name);
|
||||
}
|
||||
}
|
||||
if deleted_count > 0 {
|
||||
debug!("Cleaned up {} old history entries by count", deleted_count);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Cleaned up {} old history entries", entries_to_delete.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cleanup_by_time(&self, retention_period: crate::settings::RecordingRetentionPeriod) -> Result<()> {
|
||||
let conn = self.get_connection()?;
|
||||
|
||||
// Calculate cutoff timestamp (current time minus retention period)
|
||||
let now = Utc::now().timestamp();
|
||||
let cutoff_timestamp = match retention_period {
|
||||
crate::settings::RecordingRetentionPeriod::Days3 => now - (3 * 24 * 60 * 60), // 3 days in seconds
|
||||
crate::settings::RecordingRetentionPeriod::Weeks2 => now - (2 * 7 * 24 * 60 * 60), // 2 weeks in seconds
|
||||
crate::settings::RecordingRetentionPeriod::Months3 => now - (3 * 30 * 24 * 60 * 60), // 3 months in seconds (approximate)
|
||||
_ => unreachable!("Should not reach here"),
|
||||
};
|
||||
|
||||
// Get all unsaved entries older than the cutoff timestamp
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, file_name FROM transcription_history WHERE saved = 0 AND timestamp < ?1"
|
||||
)?;
|
||||
|
||||
let rows = stmt.query_map(params![cutoff_timestamp], |row| {
|
||||
Ok((row.get::<_, i64>("id")?, row.get::<_, String>("file_name")?))
|
||||
})?;
|
||||
|
||||
let mut entries_to_delete: Vec<(i64, String)> = Vec::new();
|
||||
for row in rows {
|
||||
entries_to_delete.push(row?);
|
||||
}
|
||||
|
||||
let deleted_count = self.delete_entries_and_files(&entries_to_delete)?;
|
||||
|
||||
if deleted_count > 0 {
|
||||
debug!("Cleaned up {} old history entries based on retention period", deleted_count);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -323,9 +387,4 @@ impl HistoryManager {
|
|||
format!("Recording {}", timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_history_limit(&self) -> Result<()> {
|
||||
self.cleanup_old_entries()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,16 @@ pub enum ClipboardHandling {
|
|||
CopyToClipboard,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RecordingRetentionPeriod {
|
||||
Never,
|
||||
PreserveLimit,
|
||||
Days3,
|
||||
Weeks2,
|
||||
Months3,
|
||||
}
|
||||
|
||||
impl Default for ModelUnloadTimeout {
|
||||
fn default() -> Self {
|
||||
ModelUnloadTimeout::Never
|
||||
|
|
@ -179,6 +189,8 @@ pub struct AppSettings {
|
|||
pub word_correction_threshold: f64,
|
||||
#[serde(default = "default_history_limit")]
|
||||
pub history_limit: usize,
|
||||
#[serde(default = "default_recording_retention_period")]
|
||||
pub recording_retention_period: RecordingRetentionPeriod,
|
||||
#[serde(default)]
|
||||
pub paste_method: PasteMethod,
|
||||
#[serde(default)]
|
||||
|
|
@ -244,6 +256,10 @@ fn default_history_limit() -> usize {
|
|||
5
|
||||
}
|
||||
|
||||
fn default_recording_retention_period() -> RecordingRetentionPeriod {
|
||||
RecordingRetentionPeriod::PreserveLimit
|
||||
}
|
||||
|
||||
fn default_audio_feedback_volume() -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
|
@ -362,6 +378,7 @@ pub fn get_default_settings() -> AppSettings {
|
|||
model_unload_timeout: ModelUnloadTimeout::Never,
|
||||
word_correction_threshold: default_word_correction_threshold(),
|
||||
history_limit: default_history_limit(),
|
||||
recording_retention_period: default_recording_retention_period(),
|
||||
paste_method: PasteMethod::default(),
|
||||
clipboard_handling: ClipboardHandling::default(),
|
||||
post_process_enabled: default_post_process_enabled(),
|
||||
|
|
@ -472,3 +489,8 @@ pub fn get_history_limit(app: &AppHandle) -> usize {
|
|||
let settings = get_settings(app);
|
||||
settings.history_limit
|
||||
}
|
||||
|
||||
pub fn get_recording_retention_period(app: &AppHandle) -> RecordingRetentionPeriod {
|
||||
let settings = get_settings(app);
|
||||
settings.recording_retention_period
|
||||
}
|
||||
|
|
|
|||
54
src/components/settings/RecordingRetentionPeriod.tsx
Normal file
54
src/components/settings/RecordingRetentionPeriod.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import React from "react";
|
||||
import { Dropdown } from "../ui/Dropdown";
|
||||
import { SettingContainer } from "../ui/SettingContainer";
|
||||
import { useSettings } from "../../hooks/useSettings";
|
||||
import { RecordingRetentionPeriod } from "../../lib/types";
|
||||
|
||||
interface RecordingRetentionPeriodProps {
|
||||
descriptionMode?: "inline" | "tooltip";
|
||||
grouped?: boolean;
|
||||
}
|
||||
|
||||
export const RecordingRetentionPeriodSelector: React.FC<RecordingRetentionPeriodProps> = React.memo(
|
||||
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||
const {
|
||||
getSetting,
|
||||
updateSetting,
|
||||
isUpdating,
|
||||
} = useSettings();
|
||||
|
||||
const selectedRetentionPeriod = getSetting("recording_retention_period") || "never";
|
||||
const historyLimit = getSetting("history_limit") || 5;
|
||||
|
||||
const handleRetentionPeriodSelect = async (period: string) => {
|
||||
await updateSetting("recording_retention_period", period as RecordingRetentionPeriod);
|
||||
};
|
||||
|
||||
const retentionOptions = [
|
||||
{ value: "never", label: "Never" },
|
||||
{ value: "preserve_limit", label: `Preserve ${historyLimit} Recordings` },
|
||||
{ value: "days3", label: "After 3 Days" },
|
||||
{ value: "weeks2", label: "After 2 Weeks" },
|
||||
{ value: "months3", label: "After 3 Months" },
|
||||
];
|
||||
|
||||
return (
|
||||
<SettingContainer
|
||||
title="Delete Recordings"
|
||||
description="Automatically delete recordings from the device"
|
||||
descriptionMode={descriptionMode}
|
||||
grouped={grouped}
|
||||
>
|
||||
<Dropdown
|
||||
options={retentionOptions}
|
||||
selectedValue={selectedRetentionPeriod}
|
||||
onSelect={handleRetentionPeriodSelect}
|
||||
placeholder="Select retention period..."
|
||||
disabled={isUpdating("recording_retention_period")}
|
||||
/>
|
||||
</SettingContainer>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
RecordingRetentionPeriodSelector.displayName = "RecordingRetentionPeriodSelector";
|
||||
|
|
@ -24,4 +24,4 @@ export const AdvancedSettings: React.FC = () => {
|
|||
</SettingsGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
@ -6,6 +6,7 @@ import { AlwaysOnMicrophone } from "../AlwaysOnMicrophone";
|
|||
import { SoundPicker } from "../SoundPicker";
|
||||
import { PostProcessingToggle } from "../PostProcessingToggle";
|
||||
import { MuteWhileRecording } from "../MuteWhileRecording";
|
||||
import { RecordingRetentionPeriodSelector } from "../RecordingRetentionPeriod";
|
||||
import { ClamshellMicrophoneSelector } from "../ClamshellMicrophoneSelector";
|
||||
|
||||
export const DebugSettings: React.FC = () => {
|
||||
|
|
@ -18,6 +19,7 @@ export const DebugSettings: React.FC = () => {
|
|||
/>
|
||||
<WordCorrectionThreshold descriptionMode="tooltip" grouped={true} />
|
||||
<HistoryLimit descriptionMode="tooltip" grouped={true} />
|
||||
<RecordingRetentionPeriodSelector descriptionMode="tooltip" grouped={true} />
|
||||
<AlwaysOnMicrophone descriptionMode="tooltip" grouped={true} />
|
||||
<ClamshellMicrophoneSelector descriptionMode="tooltip" grouped={true} />
|
||||
<PostProcessingToggle descriptionMode="tooltip" grouped={true} />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { SettingsGroup } from "../../ui/SettingsGroup";
|
||||
import { AudioPlayer } from "../../ui/AudioPlayer";
|
||||
import { Copy, Star, Check, Trash2 } from "lucide-react";
|
||||
import { Button } from "../../ui/Button";
|
||||
import { Copy, Star, Check, Trash2, FolderOpen } from "lucide-react";
|
||||
import { convertFileSrc, invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
|
|
@ -14,6 +14,25 @@ interface HistoryEntry {
|
|||
transcription_text: string;
|
||||
}
|
||||
|
||||
interface OpenRecordingsButtonProps {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const OpenRecordingsButton: React.FC<OpenRecordingsButtonProps> = ({
|
||||
onClick,
|
||||
}) => (
|
||||
<Button
|
||||
onClick={onClick}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="flex items-center gap-2"
|
||||
title="Open recordings folder"
|
||||
>
|
||||
<FolderOpen className="w-4 h-4" />
|
||||
<span>Open Recordings Folder</span>
|
||||
</Button>
|
||||
);
|
||||
|
||||
export const HistorySettings: React.FC = () => {
|
||||
const [historyEntries, setHistoryEntries] = useState<HistoryEntry[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
|
@ -93,14 +112,32 @@ export const HistorySettings: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const openRecordingsFolder = async () => {
|
||||
try {
|
||||
await invoke("open_recordings_folder");
|
||||
} catch (error) {
|
||||
console.error("Failed to open recordings folder:", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="max-w-3xl w-full mx-auto space-y-6">
|
||||
<SettingsGroup title="History">
|
||||
<div className="px-4 py-3 text-center text-text/60">
|
||||
Loading history...
|
||||
<div className="space-y-2">
|
||||
<div className="px-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xs font-medium text-mid-gray uppercase tracking-wide">
|
||||
History
|
||||
</h2>
|
||||
</div>
|
||||
<OpenRecordingsButton onClick={openRecordingsFolder} />
|
||||
</div>
|
||||
</SettingsGroup>
|
||||
<div className="bg-background border border-mid-gray/20 rounded-lg overflow-visible">
|
||||
<div className="px-4 py-3 text-center text-text/60">
|
||||
Loading history...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -108,29 +145,51 @@ export const HistorySettings: React.FC = () => {
|
|||
if (historyEntries.length === 0) {
|
||||
return (
|
||||
<div className="max-w-3xl w-full mx-auto space-y-6">
|
||||
<SettingsGroup title="History">
|
||||
<div className="px-4 py-3 text-center text-text/60">
|
||||
No transcriptions yet. Start recording to build your history!
|
||||
<div className="space-y-2">
|
||||
<div className="px-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xs font-medium text-mid-gray uppercase tracking-wide">
|
||||
History
|
||||
</h2>
|
||||
</div>
|
||||
<OpenRecordingsButton onClick={openRecordingsFolder} />
|
||||
</div>
|
||||
</SettingsGroup>
|
||||
<div className="bg-background border border-mid-gray/20 rounded-lg overflow-visible">
|
||||
<div className="px-4 py-3 text-center text-text/60">
|
||||
No transcriptions yet. Start recording to build your history!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl w-full mx-auto space-y-6">
|
||||
<SettingsGroup title="History">
|
||||
{historyEntries.map((entry) => (
|
||||
<HistoryEntryComponent
|
||||
key={entry.id}
|
||||
entry={entry}
|
||||
onToggleSaved={() => toggleSaved(entry.id)}
|
||||
onCopyText={() => copyToClipboard(entry.transcription_text)}
|
||||
getAudioUrl={getAudioUrl}
|
||||
deleteAudio={deleteAudioEntry}
|
||||
/>
|
||||
))}
|
||||
</SettingsGroup>
|
||||
<div className="space-y-2">
|
||||
<div className="px-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xs font-medium text-mid-gray uppercase tracking-wide">
|
||||
History
|
||||
</h2>
|
||||
</div>
|
||||
<OpenRecordingsButton onClick={openRecordingsFolder} />
|
||||
</div>
|
||||
<div className="bg-background border border-mid-gray/20 rounded-lg overflow-visible">
|
||||
<div className="divide-y divide-mid-gray/20">
|
||||
{historyEntries.map((entry) => (
|
||||
<HistoryEntryComponent
|
||||
key={entry.id}
|
||||
entry={entry}
|
||||
onToggleSaved={() => toggleSaved(entry.id)}
|
||||
onCopyText={() => copyToClipboard(entry.transcription_text)}
|
||||
getAudioUrl={getAudioUrl}
|
||||
deleteAudio={deleteAudioEntry}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,4 +24,5 @@ export { AppDataDirectory } from "./AppDataDirectory";
|
|||
export { ModelUnloadTimeoutSetting } from "./ModelUnloadTimeout";
|
||||
export { StartHidden } from "./StartHidden";
|
||||
export { HistoryLimit } from "./HistoryLimit";
|
||||
export { RecordingRetentionPeriodSelector } from "./RecordingRetentionPeriod";
|
||||
export { AutostartToggle } from "./AutostartToggle";
|
||||
|
|
|
|||
|
|
@ -13,15 +13,17 @@ export const Button: React.FC<ButtonProps> = ({
|
|||
...props
|
||||
}) => {
|
||||
const baseClasses =
|
||||
"font-medium rounded focus:outline-none transition-colors disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer";
|
||||
"font-medium rounded border focus:outline-none transition-colors disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer";
|
||||
|
||||
const variantClasses = {
|
||||
primary:
|
||||
"text-white bg-background-ui hover:bg-background-ui/80 focus:ring-1 focus:ring-background-ui",
|
||||
secondary: "bg-mid-gray/10 hover:bg-background-ui/30 focus:outline-none",
|
||||
"text-white bg-background-ui border-background-ui hover:bg-background-ui/80 hover:border-background-ui/80 focus:ring-1 focus:ring-background-ui",
|
||||
secondary:
|
||||
"bg-mid-gray/10 border-mid-gray/20 hover:bg-background-ui/30 hover:border-logo-primary focus:outline-none",
|
||||
danger:
|
||||
"text-white bg-red-600 hover:bg-red-700 focus:ring-1 focus:ring-red-500",
|
||||
ghost: "text-current hover:bg-mid-gray/10 focus:bg-mid-gray/20",
|
||||
"text-white bg-red-600 border-mid-gray/20 hover:bg-red-700 hover:border-red-700 focus:ring-1 focus:ring-red-500",
|
||||
ghost:
|
||||
"text-current border-transparent hover:bg-mid-gray/10 hover:border-logo-primary focus:bg-mid-gray/20",
|
||||
};
|
||||
|
||||
const sizeClasses = {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,15 @@ export const ClipboardHandlingSchema = z.enum([
|
|||
]);
|
||||
export type ClipboardHandling = z.infer<typeof ClipboardHandlingSchema>;
|
||||
|
||||
export const RecordingRetentionPeriodSchema = z.enum([
|
||||
"never",
|
||||
"preserve_limit",
|
||||
"days3",
|
||||
"weeks2",
|
||||
"months3",
|
||||
]);
|
||||
export type RecordingRetentionPeriod = z.infer<typeof RecordingRetentionPeriodSchema>;
|
||||
|
||||
export const LLMPromptSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
|
|
@ -89,6 +98,7 @@ export const SettingsSchema = z.object({
|
|||
model_unload_timeout: ModelUnloadTimeoutSchema.optional().default("never"),
|
||||
word_correction_threshold: z.number().optional().default(0.18),
|
||||
history_limit: z.number().optional().default(5),
|
||||
recording_retention_period: RecordingRetentionPeriodSchema.optional().default("preserve_limit"),
|
||||
paste_method: PasteMethodSchema.optional().default("ctrl_v"),
|
||||
clipboard_handling: ClipboardHandlingSchema.optional().default("dont_modify"),
|
||||
post_process_enabled: z.boolean().optional().default(false),
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ const DEFAULT_SETTINGS: Partial<Settings> = {
|
|||
debug_mode: false,
|
||||
custom_words: [],
|
||||
history_limit: 5,
|
||||
recording_retention_period: "preserve_limit",
|
||||
mute_while_recording: false,
|
||||
};
|
||||
|
||||
|
|
@ -111,6 +112,8 @@ const settingUpdaters: {
|
|||
invoke("set_selected_output_device", {
|
||||
deviceName: value === "Default" ? "default" : value,
|
||||
}),
|
||||
recording_retention_period: (value) =>
|
||||
invoke("update_recording_retention_period", { period: value }),
|
||||
translate_to_english: (value) =>
|
||||
invoke("change_translate_to_english_setting", { enabled: value }),
|
||||
selected_language: (value) =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue