feat(templates): apply templates at all 4 notes-render call sites (T8.1, FR-NOTE-5)
Adds StartRecordingArgs.template_id + list_note_templates command; renames built_in_template to summary_prompt_bias to disambiguate it from the new NoteTemplate concept (same meeting-type identifiers, different purpose). Also fixes refresh_notes_and_notify (the rename/merge-speaker refresh path) to route through Store::update_notes instead of a raw std::fs::write — it was bypassing T8.2's FTS reindexing, a gap missed there because this call site uses meeting_dir(meeting_id) without the & that my earlier grep matched on.
This commit is contained in:
+54
-13
@@ -36,6 +36,8 @@ pub struct StartRecordingArgs {
|
||||
/// Retain audio as .wav? Defaults to false (ADR-0009). Controls retention, not capture.
|
||||
#[serde(default)]
|
||||
pub record: bool,
|
||||
/// Note-template id (Phase 8, T8.1, FR-NOTE-5) — see `notes::built_in_note_templates`.
|
||||
pub template_id: Option<String>,
|
||||
}
|
||||
|
||||
// ---- File-backed settings (consent/default-retention/storage policy) ----
|
||||
@@ -215,6 +217,7 @@ pub async fn start_recording(
|
||||
.meeting_title
|
||||
.unwrap_or_else(|| "Untitled meeting".to_string()),
|
||||
calendar_event_id: args.calendar_event_id,
|
||||
template_id: args.template_id,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| WaError::new("storage", e.to_string()))?;
|
||||
@@ -477,7 +480,7 @@ pub async fn stop_recording(
|
||||
// T2.10: persist transcript.json (via finalize_meeting) and notes.md
|
||||
// *before* touching the working WAV, so a crash here still leaves a
|
||||
// recoverable, regenerable meeting.
|
||||
state
|
||||
let template_id = state
|
||||
.store
|
||||
.finalize_meeting(
|
||||
&meeting_id,
|
||||
@@ -494,7 +497,11 @@ pub async fn stop_recording(
|
||||
.await
|
||||
.map_err(|e| WaError::new("storage", e.to_string()))?;
|
||||
|
||||
let notes_md = crate::notes::MarkdownNotes.to_markdown(&segments, &speakers, None);
|
||||
let template = template_id
|
||||
.as_deref()
|
||||
.and_then(crate::notes::note_template_by_id);
|
||||
let notes_md =
|
||||
crate::notes::MarkdownNotes.to_markdown(&segments, &speakers, None, template.as_ref());
|
||||
let _ = state.store.update_notes(&meeting_id, ¬es_md).await;
|
||||
|
||||
let _ = app.emit(
|
||||
@@ -613,9 +620,17 @@ async fn refresh_notes_and_notify(
|
||||
.get_meeting(meeting_id)
|
||||
.await
|
||||
.map_err(|e| WaError::new("storage", e.to_string()))?;
|
||||
let notes_md =
|
||||
crate::notes::MarkdownNotes.to_markdown(&meeting.segments, &meeting.speakers, None);
|
||||
let _ = std::fs::write(meeting_dir(meeting_id).join("notes.md"), notes_md);
|
||||
let template = meeting
|
||||
.template_id
|
||||
.as_deref()
|
||||
.and_then(crate::notes::note_template_by_id);
|
||||
let notes_md = crate::notes::MarkdownNotes.to_markdown(
|
||||
&meeting.segments,
|
||||
&meeting.speakers,
|
||||
None,
|
||||
template.as_ref(),
|
||||
);
|
||||
let _ = state.store.update_notes(meeting_id, ¬es_md).await;
|
||||
let _ = app.emit(
|
||||
"diarization://updated",
|
||||
serde_json::json!({ "meetingId": meeting_id, "speakers": meeting.speakers }),
|
||||
@@ -881,6 +896,10 @@ pub async fn reprocess_transcript(
|
||||
.map(|s| (s.end_ms / 1000) as i64)
|
||||
.unwrap_or(meeting.duration_secs.unwrap_or(0));
|
||||
|
||||
let template = meeting
|
||||
.template_id
|
||||
.as_deref()
|
||||
.and_then(crate::notes::note_template_by_id);
|
||||
state
|
||||
.store
|
||||
.finalize_meeting(
|
||||
@@ -898,7 +917,12 @@ pub async fn reprocess_transcript(
|
||||
.await
|
||||
.map_err(|e| WaError::new("storage", e.to_string()))?;
|
||||
|
||||
let notes_md = crate::notes::MarkdownNotes.to_markdown(&segments, &meeting.speakers, None);
|
||||
let notes_md = crate::notes::MarkdownNotes.to_markdown(
|
||||
&segments,
|
||||
&meeting.speakers,
|
||||
None,
|
||||
template.as_ref(),
|
||||
);
|
||||
let _ = state.store.update_notes(&meeting_id, ¬es_md).await;
|
||||
|
||||
let _ = app.emit(
|
||||
@@ -961,7 +985,7 @@ pub async fn resume_transcription(
|
||||
.map(|s| (s.end_ms / 1000) as i64)
|
||||
.unwrap_or(0);
|
||||
|
||||
state
|
||||
let template_id = state
|
||||
.store
|
||||
.finalize_meeting(
|
||||
&meeting_id,
|
||||
@@ -980,7 +1004,11 @@ pub async fn resume_transcription(
|
||||
.await
|
||||
.map_err(|e| WaError::new("storage", e.to_string()))?;
|
||||
|
||||
let notes_md = crate::notes::MarkdownNotes.to_markdown(&segments, &speakers, None);
|
||||
let template = template_id
|
||||
.as_deref()
|
||||
.and_then(crate::notes::note_template_by_id);
|
||||
let notes_md =
|
||||
crate::notes::MarkdownNotes.to_markdown(&segments, &speakers, None, template.as_ref());
|
||||
let _ = state.store.update_notes(&meeting_id, ¬es_md).await;
|
||||
|
||||
let _ = app.emit(
|
||||
@@ -1050,6 +1078,13 @@ pub async fn search(state: State<'_, AppState>, query: String) -> WaResult<Vec<S
|
||||
.map_err(|e| WaError::new("storage", e.to_string()))
|
||||
}
|
||||
|
||||
/// Built-in note templates for a picker at recording-start time (Phase 8,
|
||||
/// T8.1, FR-NOTE-5).
|
||||
#[tauri::command]
|
||||
pub async fn list_note_templates() -> WaResult<Vec<crate::notes::NoteTemplate>> {
|
||||
Ok(crate::notes::built_in_note_templates())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_meeting(state: State<'_, AppState>, meeting_id: MeetingId) -> WaResult<Meeting> {
|
||||
state
|
||||
@@ -1261,10 +1296,13 @@ pub async fn pull_ollama_model(app: AppHandle, model: String) -> WaResult<()> {
|
||||
.map_err(|e| WaError::new("llm", e.to_string()))
|
||||
}
|
||||
|
||||
/// Built-in prompt biases for common meeting shapes (T5.4). User-authored
|
||||
/// templates are Phase 8's FR-NOTE-5 — an unrecognized id is ignored rather
|
||||
/// than erroring, so a stale/removed template id never blocks a summary.
|
||||
fn built_in_template(id: &str) -> Option<&'static str> {
|
||||
/// Built-in LLM-prompt biases for common meeting shapes (T5.4) — distinct
|
||||
/// from `notes::NoteTemplate` (Phase 8, T8.1/FR-NOTE-5), which structures
|
||||
/// notes.md itself. Both use the same meeting-type identifiers
|
||||
/// ("standup"/"retro"/"one-on-one") since they describe the same kinds of
|
||||
/// meetings, but are two independent lookups. An unrecognized id is ignored
|
||||
/// rather than erroring, so a stale/removed template id never blocks a summary.
|
||||
fn summary_prompt_bias(id: &str) -> Option<&'static str> {
|
||||
match id {
|
||||
"standup" => Some(
|
||||
"This is a daily standup. Focus the summary on what each person did, what's \
|
||||
@@ -1304,7 +1342,9 @@ fn build_prompt(meeting: &Meeting, template_id: Option<&str>) -> crate::llm::Pro
|
||||
crate::llm::Prompt {
|
||||
transcript: meeting.notes_markdown.clone(),
|
||||
metadata,
|
||||
template: template_id.and_then(built_in_template).map(str::to_string),
|
||||
template: template_id
|
||||
.and_then(summary_prompt_bias)
|
||||
.map(str::to_string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1779,6 +1819,7 @@ mod tests {
|
||||
summary: None,
|
||||
calendar_event_id: None,
|
||||
tags: Vec::new(),
|
||||
template_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user