diff --git a/src/lib/pages/HistoryPage.svelte b/src/lib/pages/HistoryPage.svelte index 61bbafb..fedadb9 100644 --- a/src/lib/pages/HistoryPage.svelte +++ b/src/lib/pages/HistoryPage.svelte @@ -26,7 +26,7 @@ import EmptyState from "$lib/components/EmptyState.svelte"; import { formatTime, formatDuration } from "$lib/utils/time.js"; import { PLAYBACK_SPEEDS } from "$lib/utils/constants.js"; - import { Search, Clock, Play, Pause, FileText, Mic, ChevronDown, ExternalLink, Star, Tag } from 'lucide-svelte'; + import { Search, Clock, Play, Pause, FileText, Mic, ChevronDown, ExternalLink, Sparkles, Star, Tag } from 'lucide-svelte'; const prefs = getPreferences(); const COLLAPSED_ROW_MIN_HEIGHT = 54; @@ -439,6 +439,66 @@ } } + // Auto-title flow. Mirrors the tag flow above piece-for-piece — same + // per-row + bulk shape, same in-flight Set tracking, same progress + // string. The actual generation lives in `generate_title_cmd` in + // src-tauri/src/commands/llm.rs; the on-demand button here is the + // retry path when auto-title-on-save (in addToHistory) was skipped + // because the LLM wasn't loaded yet. + let titling = $state(new Set()); + let bulkTitling = $state(false); + let bulkTitlingProgress = $state(""); + + async function titleRow(item) { + if (titling.has(item.id)) return; + const next = new Set(titling); + next.add(item.id); + titling = next; + try { + const title = await invoke("generate_title_cmd", { transcript: item.text || "" }); + const trimmed = (title || "").trim(); + if (trimmed) { + await renameHistoryEntry(item.id, { title: trimmed }); + } else { + toasts.warn("No title produced", "The model returned an empty response."); + } + } catch (err) { + toasts.error("Title generation failed", String(err)); + } finally { + const rest = new Set(titling); + rest.delete(item.id); + titling = rest; + } + } + + async function titleAllUntitled() { + if (bulkTitling) return; + bulkTitling = true; + try { + const untitled = history.filter((i) => !i.title || !i.title.trim()); + let done = 0; + let written = 0; + for (const item of untitled) { + done += 1; + bulkTitlingProgress = `${done} / ${untitled.length}`; + try { + const title = await invoke("generate_title_cmd", { transcript: item.text || "" }); + const trimmed = (title || "").trim(); + if (trimmed) { + await renameHistoryEntry(item.id, { title: trimmed }); + written += 1; + } + } catch (err) { + console.error("bulk title failed for", item.id, err); + } + } + toasts.success(`Titled ${written} transcript${written === 1 ? "" : "s"}`); + } finally { + bulkTitling = false; + bulkTitlingProgress = ""; + } + } + async function exportMarkdown(item) { await saveTranscriptAsMarkdown(item); } @@ -565,6 +625,17 @@ {bulkTagging ? `Tagging ${bulkTaggingProgress}` : "Tag all untagged"} {/if} + {#if history.some((i) => !i.title || !i.title.trim())} + + {/if} {#if history.length > 0} + {#if item.audioPath && item.segments && item.segments.length > 0}