diff --git a/convert_pdf.py b/convert_pdf.py deleted file mode 100644 index 0f40bae..0000000 --- a/convert_pdf.py +++ /dev/null @@ -1,108 +0,0 @@ -import fitz -from docx import Document -from docx.shared import Pt, Cm, RGBColor -from docx.enum.text import WD_PARAGRAPH_ALIGNMENT - -pdf_path = r'C:\Users\qwer\.picobot\media\ws\aa56c052-ea10-4bc1-aed4-7d06770b6fd9_夜读 _ 明白了这4点,就不难养出有主体性的孩子.pdf' -output_path = r'C:\Users\qwer\.picobot\media\夜读_明白了这4点_就不难养出有主体性的孩子.docx' - -pdf_doc = fitz.open(pdf_path) -doc = Document() - -# 页面边距 -for section in doc.sections: - section.top_margin = Cm(2.54) - section.bottom_margin = Cm(2.54) - section.left_margin = Cm(3.18) - section.right_margin = Cm(3.18) - -# 正文样式 -style = doc.styles['Normal'] -style.font.name = '宋体' -style.font.size = Pt(12) -style.paragraph_format.line_spacing = 1.5 -style.paragraph_format.first_line_indent = Pt(24) - -def add_run(paragraph, text, bold=False, size=None, color=None, italic=False, font_name=None): - run = paragraph.add_run(text) - run.bold = bold - if size: run.font.size = Pt(size) - if color: run.font.color.rgb = RGBColor(*color) - run.italic = italic - if font_name: run.font.name = font_name - return run - -# 收集所有文本 -full_text = [] -for i, page in enumerate(pdf_doc): - text = page.get_text().strip() - if text: - full_text.append(text) - -all_text = '\n'.join(full_text) -lines = [l.strip() for l in all_text.split('\n') if l.strip()] - -# 定义段落标记 -sections_headers = ['塑教育,提倡积极养育', '懂互动,给予丰盈幸福', - '有边界,养出人生底气', '稳情绪,才能赢得孩子'] -skip_lines = ['南方都市报电商官方账号。', '南都甄选', '公众号'] - -first = True -for line in lines: - # 跳过广告行 - if line in skip_lines: - continue - - # 主标题 - if first: - p = doc.add_paragraph() - p.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER - p.paragraph_format.first_line_indent = Pt(0) - p.paragraph_format.space_after = Pt(12) - add_run(p, line, bold=True, size=22, font_name='黑体') - first = False - - # 引用句(引号开头结尾、较短) - elif (line.startswith('"') and line.endswith('"')) or \ - (line.startswith('"') and line.endswith('"') and len(line) < 60): - p = doc.add_paragraph() - p.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER - p.paragraph_format.first_line_indent = Pt(0) - p.paragraph_format.space_before = Pt(6) - p.paragraph_format.space_after = Pt(6) - add_run(p, line, italic=True, size=12, color=(102, 102, 102)) - - # 日期来源 - elif line in ['2026年6月14日 22:28 广东', '南方都市报']: - p = doc.add_paragraph() - p.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT - p.paragraph_format.first_line_indent = Pt(0) - add_run(p, line, size=10.5, color=(128, 128, 128)) - - # 4个小标题 - elif line in sections_headers: - p = doc.add_paragraph() - p.paragraph_format.first_line_indent = Pt(0) - p.paragraph_format.space_before = Pt(18) - p.paragraph_format.space_after = Pt(6) - add_run(p, line, bold=True, size=15, font_name='黑体') - - # 作者信息 - elif any(line.startswith(x) for x in ['作者:', '统筹:', '图片:', '投稿邮箱:']): - p = doc.add_paragraph() - p.paragraph_format.first_line_indent = Pt(0) - add_run(p, line, size=10.5, color=(128, 128, 128)) - - # 末尾信息 - elif '转载自' in line or '把世界当成' in line: - p = doc.add_paragraph() - p.paragraph_format.first_line_indent = Pt(0) - p.paragraph_format.space_before = Pt(6) - add_run(p, line, size=10.5, color=(102, 102, 102)) - - else: - doc.add_paragraph(line) - -pdf_doc.close() -doc.save(output_path) -print('转换完成!') diff --git a/src/gateway/session.rs b/src/gateway/session.rs index 8598913..77beaab 100644 --- a/src/gateway/session.rs +++ b/src/gateway/session.rs @@ -186,21 +186,44 @@ impl BusToolCallEmitter { .unwrap_or_default() .as_secs() as i64; + // 读取现有 DB 记录,独立对比决定 created_by_message_id 是否更新 + let existing = self.store.list_todos(&scope_key).unwrap_or_default(); + let existing_map: std::collections::HashMap<&str, &crate::storage::TodoRecord> = + existing.iter().map(|r| (r.id.as_str(), r)).collect(); + let records: Vec = todos_array .iter() .enumerate() .filter_map(|(idx, item)| { + let id = item.get("id")?.as_str()?; + let content = item.get("content")?.as_str()?; + let status = item.get("status")?.as_str()?; + + // 仅 content 或 status 实际变化时更新 created_by_message_id + let changed = match existing_map.get(id) { + Some(old) => old.content != content || old.status != status, + None => true, // 新项 + }; + + let msg_id = if changed { + message.tool_call_id.clone() + } else { + existing_map + .get(id) + .and_then(|r| r.created_by_message_id.clone()) + }; + Some(crate::storage::TodoRecord { - id: item.get("id")?.as_str()?.to_string(), + id: id.to_string(), scope_key: scope_key.clone(), session_id: session_id.clone(), topic_id: topic_id.clone(), - content: item.get("content")?.as_str()?.to_string(), - status: item.get("status")?.as_str()?.to_string(), + content: content.to_string(), + status: status.to_string(), priority: "medium".to_string(), created_at: now + idx as i64, updated_at: now, - created_by_message_id: message.tool_call_id.clone(), + created_by_message_id: msg_id, }) }) .collect(); diff --git a/src/tools/task/runtime.rs b/src/tools/task/runtime.rs index dcc77f7..8344b28 100644 --- a/src/tools/task/runtime.rs +++ b/src/tools/task/runtime.rs @@ -221,21 +221,44 @@ impl SubAgentEmitter { .unwrap_or_default() .as_secs() as i64; + // 读取现有 DB 记录,独立对比决定 created_by_message_id 是否更新 + let existing = self.store.list_todos(scope_key).unwrap_or_default(); + let existing_map: std::collections::HashMap<&str, &crate::storage::TodoRecord> = + existing.iter().map(|r| (r.id.as_str(), r)).collect(); + let records: Vec = todos_array .iter() .enumerate() .filter_map(|(idx, item)| { + let id = item.get("id")?.as_str()?; + let content = item.get("content")?.as_str()?; + let status = item.get("status")?.as_str()?; + + // 仅 content 或 status 实际变化时更新 created_by_message_id + let changed = match existing_map.get(id) { + Some(old) => old.content != content || old.status != status, + None => true, // 新项 + }; + + let msg_id = if changed { + message.tool_call_id.clone() + } else { + existing_map + .get(id) + .and_then(|r| r.created_by_message_id.clone()) + }; + Some(crate::storage::TodoRecord { - id: item.get("id")?.as_str()?.to_string(), + id: id.to_string(), scope_key: scope_key.clone(), session_id: scope_key.clone(), topic_id: None, - content: item.get("content")?.as_str()?.to_string(), - status: item.get("status")?.as_str()?.to_string(), + content: content.to_string(), + status: status.to_string(), priority: "medium".to_string(), created_at: now + idx as i64, updated_at: now, - created_by_message_id: message.tool_call_id.clone(), + created_by_message_id: msg_id, }) }) .collect(); diff --git a/src/tools/todo_write.rs b/src/tools/todo_write.rs index 5599e06..d70a912 100644 --- a/src/tools/todo_write.rs +++ b/src/tools/todo_write.rs @@ -144,8 +144,8 @@ impl Tool for TodoWriteTool { None => return Ok(error_result("todo_write requires session_id or topic_id in tool context")), }; - // 2. 提取当前消息 ID(用于记录待办的创建来源) - let message_id = context.message_id.clone(); + // 2. 提取当前 tool call ID(用于定位修改该项的 tool 消息,前端用 tool_call_id 作为 data-message-id) + let message_id = context.tool_call_id.clone(); // 3. 解析入参 let todos_array = match args.get("todos").and_then(|v| v.as_array()) { @@ -219,11 +219,18 @@ impl Tool for TodoWriteTool { continue; } + // 仅在 content 或 status 实际变化时更新 created_by_message_id + let changed = old_item.content != content + || old_item.status.as_str() != new_status.as_str(); processed_items.push(TodoItem { id, content, status: new_status.as_str().to_string(), - created_by_message_id: message_id.clone(), + created_by_message_id: if changed { + message_id.clone() + } else { + old_item.created_by_message_id.clone() + }, }); } else if merge_mode { // merge 模式:id 不匹配,尝试 content fallback @@ -239,11 +246,17 @@ impl Tool for TodoWriteTool { validation_errors.push(format!("Item '{}': {}", content, err)); continue; } + // content 匹配上说明 content 不变,仅需比较 status + let changed = old_item.status.as_str() != new_status.as_str(); processed_items.push(TodoItem { id: old_item.id.clone(), content, status: new_status.as_str().to_string(), - created_by_message_id: message_id.clone(), + created_by_message_id: if changed { + message_id.clone() + } else { + old_item.created_by_message_id.clone() + }, }); } else { // 全新项