feat(phase9): frontend types + persistence wiring for llmTags

TranscriptEntry gains llmTags: string[]; TranscriptRow gains the
storage-shape llmTags: string. ContentTags type added. mapTranscriptRow
hydrates llmTags from the comma-joined column. saveTranscriptMeta
forwards llmTags through to update_transcript_meta_cmd, mirroring the
existing manualTags handling.

buildFrontmatter unions auto + manual + llm tags so exported markdown
surfaces every source in one tags: list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-25 00:12:20 +01:00
parent 489c066a70
commit 7eb52d97b1
3 changed files with 27 additions and 1 deletions

View File

@@ -144,6 +144,8 @@ function mapTranscriptRow(row: TranscriptDto): TranscriptEntry {
const text = row.text ?? ""; const text = row.text ?? "";
const rawTags = row.manualTags ?? ""; const rawTags = row.manualTags ?? "";
const manualTags = rawTags ? rawTags.split(",").filter(Boolean) : []; const manualTags = rawTags ? rawTags.split(",").filter(Boolean) : [];
const rawLlmTags = row.llmTags ?? "";
const llmTags = rawLlmTags ? rawLlmTags.split(",").filter(Boolean) : [];
const rawSegments = row.segmentsJson ?? ""; const rawSegments = row.segmentsJson ?? "";
let segments: Segment[] = []; let segments: Segment[] = [];
if (rawSegments) { if (rawSegments) {
@@ -173,6 +175,7 @@ function mapTranscriptRow(row: TranscriptDto): TranscriptEntry {
template: row.template ?? "", template: row.template ?? "",
language: row.language ?? "", language: row.language ?? "",
segments, segments,
llmTags,
}; };
} }
@@ -196,6 +199,7 @@ function normaliseTranscriptEntry(entry: TranscriptWriteEntry): TranscriptEntry
template: entry.template ?? "", template: entry.template ?? "",
language: entry.language ?? "", language: entry.language ?? "",
segments: Array.isArray(entry.segments) ? entry.segments : [], segments: Array.isArray(entry.segments) ? entry.segments : [],
llmTags: Array.isArray(entry.llmTags) ? entry.llmTags : [],
}; };
} }
@@ -292,6 +296,11 @@ export async function saveTranscriptMeta(id: string, patch: TranscriptMetaPatch)
template: patch.template, template: patch.template,
language: patch.language, language: patch.language,
segmentsJson: patch.segments === undefined ? undefined : JSON.stringify(patch.segments), segmentsJson: patch.segments === undefined ? undefined : JSON.stringify(patch.segments),
llmTags: patch.llmTags == null
? undefined
: (Array.isArray(patch.llmTags)
? patch.llmTags.join(",")
: String(patch.llmTags)),
}; };
const row = await invoke<TranscriptDto>("update_transcript_meta_cmd", { const row = await invoke<TranscriptDto>("update_transcript_meta_cmd", {
id: String(id), id: String(id),

View File

@@ -180,6 +180,8 @@ export interface TranscriptDto {
template: string; template: string;
language: string; language: string;
segmentsJson: string; segmentsJson: string;
/** Phase 9. Comma-joined LLM-generated tags ("topic:foo", "intent:..."). */
llmTags: string;
} }
export interface TranscriptEntry { export interface TranscriptEntry {
@@ -200,6 +202,13 @@ export interface TranscriptEntry {
template: string; template: string;
language: string; language: string;
segments: Segment[]; segments: Segment[];
/** Phase 9. AI-generated content tags ("topic:foo", "intent:..."). */
llmTags: string[];
}
export interface ContentTags {
topic: string;
intent: string;
} }
export interface TranscriptWriteEntry extends Partial<TranscriptEntry> { export interface TranscriptWriteEntry extends Partial<TranscriptEntry> {
@@ -220,6 +229,9 @@ export interface TranscriptMetaPatch {
template?: string; template?: string;
language?: string; language?: string;
segments?: Segment[]; segments?: Segment[];
/** Phase 9. Pass an array to set, an empty array to clear, or omit
* the field to leave llm_tags unchanged. */
llmTags?: string[] | string;
} }
/** /**

View File

@@ -90,7 +90,12 @@ export function buildFrontmatter(item: TranscriptEntry | null) {
if (!item) return {}; if (!item) return {};
const auto = deriveAutoTags(item); const auto = deriveAutoTags(item);
const manual = Array.isArray(item.manualTags) ? item.manualTags : []; const manual = Array.isArray(item.manualTags) ? item.manualTags : [];
const tags = Array.from(new Set([...auto, ...manual.map(normaliseTag)])).filter(Boolean); const llm = Array.isArray(item.llmTags) ? item.llmTags : [];
const tags = Array.from(new Set([
...auto,
...manual.map(normaliseTag),
...llm.map(normaliseTag),
])).filter(Boolean);
const wordCount = item.text ? item.text.trim().split(/\s+/).filter(Boolean).length : 0; const wordCount = item.text ? item.text.trim().split(/\s+/).filter(Boolean).length : 0;
return { return {
id: item.id, id: item.id,