Strip markdown decoration from model-emitted insight titles
Models wrap the title line despite the prompt — "**Title: A Day in the Woods**", "## Title: ...", bold around just the label — which made parse_title_body's bare "Title:" prefix match fall through to the fallbacks and leak asterisks into the stored title. strip_title_markdown trims bold/italic markers, heading hashes, backticks, and quotes from both ends; applied to the label line, the extracted title, both fallback paths, and generate_photo_title (which previously stripped only quotes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+44
-12
@@ -33,30 +33,40 @@ use crate::utils::{earliest_fs_time, normalize_path};
|
||||
/// and labels the truncation via `found_header`.
|
||||
const LOCATION_HISTORY_DISPLAY_LIMIT: usize = 20;
|
||||
|
||||
/// Strip common markdown decoration (bold/italic markers, heading hashes,
|
||||
/// backticks, quotes) from both ends of a model-emitted title. Models wrap
|
||||
/// the line despite the prompt: `**Title: A Day in the Woods**`,
|
||||
/// `## Title: ...`, `"..."`.
|
||||
pub(crate) fn strip_title_markdown(s: &str) -> &str {
|
||||
s.trim_matches(|c: char| matches!(c, '*' | '_' | '`' | '#' | '"') || c.is_whitespace())
|
||||
}
|
||||
|
||||
/// Parse a "Title: ...\n\n<body>" response into (title, body).
|
||||
/// Falls back to the first sentence as the title if the model didn't
|
||||
/// follow the format.
|
||||
pub(crate) fn parse_title_body(raw: &str) -> (String, String) {
|
||||
let trimmed = raw.trim();
|
||||
|
||||
// Try "Title: <title>\n\n<body>" or "Title: <title>\n<body>"
|
||||
if let Some(rest) = trimmed
|
||||
// Try "Title: <title>\n<body>", tolerating markdown decoration around
|
||||
// the title line.
|
||||
let (first_line, rest) = match trimmed.find('\n') {
|
||||
Some(pos) => (&trimmed[..pos], trimmed[pos..].trim()),
|
||||
None => (trimmed, ""),
|
||||
};
|
||||
let first_line = strip_title_markdown(first_line);
|
||||
if let Some(t) = first_line
|
||||
.strip_prefix("Title:")
|
||||
.or_else(|| trimmed.strip_prefix("title:"))
|
||||
.or_else(|| first_line.strip_prefix("title:"))
|
||||
{
|
||||
let rest = rest.trim_start();
|
||||
if let Some(split_pos) = rest.find("\n\n").or_else(|| rest.find('\n')) {
|
||||
let title = rest[..split_pos].trim();
|
||||
let body = rest[split_pos..].trim();
|
||||
if !title.is_empty() && !body.is_empty() {
|
||||
return (title.to_string(), body.to_string());
|
||||
}
|
||||
let title = strip_title_markdown(t);
|
||||
if !title.is_empty() && !rest.is_empty() {
|
||||
return (title.to_string(), rest.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: first sentence (up to first `. ` or `.\n`) becomes the title
|
||||
if let Some(pos) = trimmed.find(". ").or_else(|| trimmed.find(".\n")) {
|
||||
let title = &trimmed[..pos];
|
||||
let title = strip_title_markdown(&trimmed[..pos]);
|
||||
let body = trimmed[pos + 1..].trim();
|
||||
if title.len() <= 100 && !body.is_empty() {
|
||||
return (title.to_string(), body.to_string());
|
||||
@@ -65,7 +75,7 @@ pub(crate) fn parse_title_body(raw: &str) -> (String, String) {
|
||||
|
||||
// Last resort: truncate to 60 chars for title, full text as body
|
||||
let title: String = trimmed.chars().take(60).collect();
|
||||
let title = title.trim_end().to_string();
|
||||
let title = strip_title_markdown(title.trim_end()).to_string();
|
||||
(title, trimmed.to_string())
|
||||
}
|
||||
|
||||
@@ -5126,6 +5136,28 @@ mod tests {
|
||||
assert_eq!(b, "Everyone gathered...");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_title_body_strips_bold_wrapper() {
|
||||
let (t, b) = parse_title_body("**Title: A Day in the Woods**\n\nWe hiked the ridge trail.");
|
||||
assert_eq!(t, "A Day in the Woods");
|
||||
assert_eq!(b, "We hiked the ridge trail.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_title_body_strips_bold_label_only() {
|
||||
// Bold around just the label: "**Title:** X"
|
||||
let (t, b) = parse_title_body("**Title:** Garden Party\n\nEveryone gathered...");
|
||||
assert_eq!(t, "Garden Party");
|
||||
assert_eq!(b, "Everyone gathered...");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_title_body_strips_heading_hashes() {
|
||||
let (t, b) = parse_title_body("## Title: Morning Walk\nThe sun was rising...");
|
||||
assert_eq!(t, "Morning Walk");
|
||||
assert_eq!(b, "The sun was rising...");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_title_body_fallback_first_sentence() {
|
||||
let (t, b) = parse_title_body("A warm summer day. We gathered at the park for a picnic.");
|
||||
|
||||
Reference in New Issue
Block a user