修复: loop_state 懒创建继承+入口早退清理(#10), update_full 剥离列封 lost-update(#11), MidStream 保文不回填半截 reasoning, 标题剥标签 sanitize(#9), ANSI 源头抑制+strip 兜底(#8)
This commit is contained in:
@@ -236,34 +236,67 @@ fn clean_title(raw: &str) -> String {
|
||||
///
|
||||
/// 开头常出现测试/问候("1"、"你好"等),取最长消息自然跳过这些噪音。
|
||||
/// 极端情况全部过短时(仅测试消息),用第一条非空 user 兜底。
|
||||
/// #9 标题泄漏:提取前剥离 HTML/Markdown 标签(<details>/<summary>/代码围栏标记等),
|
||||
/// 治「content 首行是标签结构体,截断后标题裸露 `<details> <summa…`」。
|
||||
fn sanitize_title_source(raw: &str) -> String {
|
||||
let mut out = String::with_capacity(raw.len());
|
||||
let mut chars = raw.chars().peekable();
|
||||
while let Some(c) = chars.next() {
|
||||
match c {
|
||||
'<' => {
|
||||
// HTML 标签:吞到 '>'(含);残缺(无 '>')吞到行尾
|
||||
let mut closed = false;
|
||||
for n in chars.by_ref() {
|
||||
if n == '>' {
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
if n == '\n' && !closed {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let _ = closed;
|
||||
}
|
||||
'`' => {
|
||||
// 代码围栏/行内标记:跳过反引号本身(保留代码文本语义)
|
||||
}
|
||||
'#' | '*' | '_' | '~' if out.is_empty() || out.ends_with(|ch: char| ch.is_whitespace()) => {
|
||||
// 行首 Markdown 修饰符(# 标题/* 列表/_ 强调)不进标题;行中出现的保留(可能是正常标点)
|
||||
}
|
||||
_ => out.push(c),
|
||||
}
|
||||
}
|
||||
// 压缩空白(标签剥离后可能留下连续空格/空行)
|
||||
out.split_whitespace().collect::<Vec<_>>().join(" ")
|
||||
}
|
||||
|
||||
pub(crate) fn extract_title(messages: &[ChatMessage]) -> Option<String> {
|
||||
const MAX_TITLE_CHARS: usize = 60;
|
||||
const SCAN_LIMIT: usize = 5;
|
||||
|
||||
let user_msgs: Vec<&str> = messages.iter()
|
||||
let user_msgs: Vec<String> = messages.iter()
|
||||
.filter(|m| matches!(m.role, MessageRole::User))
|
||||
.take(SCAN_LIMIT)
|
||||
.map(|m| m.content.trim())
|
||||
.map(|m| sanitize_title_source(m.content.trim()))
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
|
||||
// 取字符数最多的
|
||||
let best = user_msgs.iter()
|
||||
.max_by_key(|s| s.chars().count())
|
||||
.copied();
|
||||
.max_by_key(|s| s.chars().count());
|
||||
|
||||
match best {
|
||||
Some(text) => {
|
||||
let mut chars = text.chars();
|
||||
let t: String = chars.by_ref().take(MAX_TITLE_CHARS).collect();
|
||||
if chars.next().is_some() { Some(format!("{}...", t)) } else { Some(t) }
|
||||
}
|
||||
None => {
|
||||
} None => {
|
||||
// 极端情况全空 → 第一条非空 user(兜底)
|
||||
messages.iter()
|
||||
.find(|m| matches!(m.role, MessageRole::User) && !m.content.trim().is_empty())
|
||||
.map(|m| {
|
||||
let mut chars = m.content.trim().chars();
|
||||
let sanitized = sanitize_title_source(m.content.trim());
|
||||
let mut chars = sanitized.chars();
|
||||
let t: String = chars.by_ref().take(MAX_TITLE_CHARS).collect();
|
||||
if chars.next().is_some() { format!("{}...", t) } else { t }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user