prompt 加要求:第一行 `TITLE: <主题>` + `---` + 正文;backend parse 头两行,覆盖 recordings.title;summary 字段不含 title 行。 失败 fallback 不动 title。前端 sidebar/主视图自带 5s 轮询自动刷新。
This commit is contained in:
+47
-8
@@ -427,8 +427,8 @@ async fn process_recording(s: AppState, id: i64) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// LLM:生成会议纪要
|
// LLM:生成会议纪要 + 标题
|
||||||
let summary = match call_llm_summary(&s, &transcript).await {
|
let raw = match call_llm_summary(&s, &transcript).await {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(%id, error = %e, "LLM failed");
|
tracing::error!(%id, error = %e, "LLM failed");
|
||||||
@@ -436,14 +436,50 @@ async fn process_recording(s: AppState, id: i64) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let (new_title, summary_body) = parse_title_from_summary(&raw);
|
||||||
{
|
{
|
||||||
let conn = s.db.lock().unwrap();
|
let conn = s.db.lock().unwrap();
|
||||||
let _ = conn.execute(
|
if let Some(t) = new_title.as_deref() {
|
||||||
"UPDATE recordings SET summary = ?1, status = 'done', error = NULL WHERE id = ?2",
|
let _ = conn.execute(
|
||||||
params![&summary, id],
|
"UPDATE recordings SET title = ?1, summary = ?2, status = 'done', error = NULL WHERE id = ?3",
|
||||||
);
|
params![t, &summary_body, id],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let _ = conn.execute(
|
||||||
|
"UPDATE recordings SET summary = ?1, status = 'done', error = NULL WHERE id = ?2",
|
||||||
|
params![&summary_body, id],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tracing::info!(%id, "done");
|
tracing::info!(%id, title = ?new_title, "done");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从 LLM 输出剥离 `TITLE: ...\n---\n` 头部。
|
||||||
|
/// 返回 (Option<title>, summary_body),title 失败时返回 None + 原文。
|
||||||
|
fn parse_title_from_summary(raw: &str) -> (Option<String>, String) {
|
||||||
|
let mut lines = raw.lines();
|
||||||
|
let first = lines.next().unwrap_or("").trim();
|
||||||
|
let Some(rest) = first
|
||||||
|
.strip_prefix("TITLE:")
|
||||||
|
.or_else(|| first.strip_prefix("Title:"))
|
||||||
|
.or_else(|| first.strip_prefix("标题:"))
|
||||||
|
.or_else(|| first.strip_prefix("标题:"))
|
||||||
|
else {
|
||||||
|
return (None, raw.to_string());
|
||||||
|
};
|
||||||
|
let title: String = rest.trim().chars().take(80).collect();
|
||||||
|
if title.is_empty() {
|
||||||
|
return (None, raw.to_string());
|
||||||
|
}
|
||||||
|
// 吃掉接下来的 `---` separator + 空行
|
||||||
|
let body: String = lines
|
||||||
|
.skip_while(|l| {
|
||||||
|
let t = l.trim();
|
||||||
|
t.is_empty() || t == "---" || t.starts_with("---")
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
(Some(title), body)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_status(s: &AppState, id: i64, status: &str, transcript: Option<&str>, error: Option<&str>) {
|
fn set_status(s: &AppState, id: i64, status: &str, transcript: Option<&str>, error: Option<&str>) {
|
||||||
@@ -501,8 +537,11 @@ async fn call_llm_summary(s: &AppState, transcript: &str) -> Result<String, Stri
|
|||||||
"model": s.llm_model,
|
"model": s.llm_model,
|
||||||
"messages": [
|
"messages": [
|
||||||
{ "role": "system", "content":
|
{ "role": "system", "content":
|
||||||
"你是一个会议纪要助手。根据语音转写整理一份结构化纪要(markdown 格式):\n\
|
"你是一个会议纪要助手。根据语音转写输出:\n\
|
||||||
\n\
|
\n\
|
||||||
|
第一行:`TITLE: <8-20 字符的会议主题>`(不含日期/时间,提取核心议题)\n\
|
||||||
|
第二行:`---`\n\
|
||||||
|
之后是 markdown 纪要:\n\
|
||||||
1. **概要**:1-2 句话总结\n\
|
1. **概要**:1-2 句话总结\n\
|
||||||
2. **关键讨论点**:bullet 列出\n\
|
2. **关键讨论点**:bullet 列出\n\
|
||||||
3. **决定 / 结论**\n\
|
3. **决定 / 结论**\n\
|
||||||
|
|||||||
Reference in New Issue
Block a user