feat: 实现Word文档标题格式自动调整功能
- 新增WordFormatter工具,支持多种编号格式(中文/阿拉伯/罗马/字母) - 支持用户自定义格式输入(如"一. 一.1 一.1.1") - 支持从参考Word文档提取格式 - 在project new命令中集成格式调整交互 - 新增UserFeedbackNode支持目录优化循环 - 优化TocAgent工作流,支持用户反馈优化 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
922c43e65b
commit
5d16d8badc
@ -5,8 +5,8 @@
|
|||||||
"technical_count": 6,
|
"technical_count": 6,
|
||||||
"commercial_count": 3,
|
"commercial_count": 3,
|
||||||
"deviation_count": 0,
|
"deviation_count": 0,
|
||||||
"chapter_count": 8,
|
"chapter_count": 7,
|
||||||
"execution_time": 259.9994738101959,
|
"execution_time": 415.01412439346313,
|
||||||
"warnings": [
|
"warnings": [
|
||||||
"AI审查: 5条优化建议"
|
"AI审查: 5条优化建议"
|
||||||
],
|
],
|
||||||
@ -58,79 +58,29 @@
|
|||||||
"deviation_items": [],
|
"deviation_items": [],
|
||||||
"chapters": [
|
"chapters": [
|
||||||
{
|
{
|
||||||
"id": "evaluation_index",
|
"id": "chapter_1",
|
||||||
"title": "评标索引表(技术评分完全对应)",
|
"title": "评标索引表(技术评分完全对应)",
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"score": null,
|
"score": 0.0,
|
||||||
"template_placeholder": "{{evaluation_index_content}}",
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_1_after_sales_service",
|
"id": "chapter_2",
|
||||||
"title": "售后服务",
|
"title": "设备规格 (8.0分)",
|
||||||
"level": 1,
|
|
||||||
"score": 3.0,
|
|
||||||
"template_placeholder": null,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"id": "chapter_2_1_warranty_service",
|
|
||||||
"title": "质保期服务承诺",
|
|
||||||
"level": 2,
|
|
||||||
"score": 0.0,
|
|
||||||
"template_placeholder": null,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"id": "chapter_3_1_warranty_scope",
|
|
||||||
"title": "质保期限及范围",
|
|
||||||
"level": 3,
|
|
||||||
"score": 0.0,
|
|
||||||
"template_placeholder": null,
|
|
||||||
"children": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "chapter_3_2_service_response",
|
|
||||||
"title": "质保期内服务响应机制",
|
|
||||||
"level": 3,
|
|
||||||
"score": 0.0,
|
|
||||||
"template_placeholder": null,
|
|
||||||
"children": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "chapter_3_3_fault_handling",
|
|
||||||
"title": "质保期故障处理流程",
|
|
||||||
"level": 3,
|
|
||||||
"score": 0.0,
|
|
||||||
"template_placeholder": null,
|
|
||||||
"children": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "chapter_3_4_service_guarantee",
|
|
||||||
"title": "质保期服务保障措施",
|
|
||||||
"level": 3,
|
|
||||||
"score": 0.0,
|
|
||||||
"template_placeholder": null,
|
|
||||||
"children": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "chapter_1_equipment_specification",
|
|
||||||
"title": "设备规格",
|
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"score": 8.0,
|
"score": 8.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "chapter_2_1_hardware_config",
|
"id": "chapter_2_1",
|
||||||
"title": "硬件配置方案",
|
"title": "硬件配置方案",
|
||||||
"level": 2,
|
"level": 2,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "chapter_3_1_core_hardware",
|
"id": "chapter_2_1_1",
|
||||||
"title": "核心硬件技术参数",
|
"title": "核心硬件技术参数",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
@ -138,7 +88,7 @@
|
|||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_2_performance_indicators",
|
"id": "chapter_2_1_2",
|
||||||
"title": "设备性能指标说明",
|
"title": "设备性能指标说明",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
@ -146,16 +96,16 @@
|
|||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_3_hardware_compatibility",
|
"id": "chapter_2_1_3",
|
||||||
"title": "硬件兼容性与扩展性",
|
"title": "硬件兼容性分析",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_4_config_optimization",
|
"id": "chapter_2_1_4",
|
||||||
"title": "硬件配置优化方案",
|
"title": "配置方案优势阐述",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
@ -166,71 +116,62 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_1_software_design",
|
"id": "chapter_3",
|
||||||
"title": "软件功能设计",
|
"title": "技术方案 (13.0分)",
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"score": 0.0,
|
"score": 13.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "chapter_2_1_software_functions",
|
"id": "chapter_3_1",
|
||||||
"title": "软件功能设计",
|
"title": "软件功能设计",
|
||||||
"level": 2,
|
"level": 2,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "chapter_3_1_core_modules",
|
"id": "chapter_3_1_1",
|
||||||
"title": "核心功能模块",
|
"title": "核心功能模块设计",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_2_user_interaction",
|
"id": "chapter_3_1_2",
|
||||||
"title": "用户交互功能",
|
"title": "用户交互界面设计",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_3_data_management",
|
"id": "chapter_3_1_3",
|
||||||
"title": "数据管理功能",
|
"title": "数据处理与分析功能",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_4_system_management",
|
"id": "chapter_3_1_4",
|
||||||
"title": "系统管理功能",
|
"title": "系统集成与接口设计",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "chapter_1_system_architecture",
|
|
||||||
"title": "系统架构设计",
|
|
||||||
"level": 1,
|
|
||||||
"score": 0.0,
|
|
||||||
"template_placeholder": null,
|
|
||||||
"children": [
|
|
||||||
{
|
{
|
||||||
"id": "chapter_2_1_architecture_design",
|
"id": "chapter_3_2",
|
||||||
"title": "系统架构设计",
|
"title": "系统架构设计",
|
||||||
"level": 2,
|
"level": 2,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "chapter_3_1_overall_architecture",
|
"id": "chapter_3_2_1",
|
||||||
"title": "总体架构设计",
|
"title": "总体架构设计",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
@ -238,16 +179,16 @@
|
|||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_2_technology_selection",
|
"id": "chapter_3_2_2",
|
||||||
"title": "技术架构选型",
|
"title": "技术选型与框架设计",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_3_deployment_architecture",
|
"id": "chapter_3_2_3",
|
||||||
"title": "系统部署架构",
|
"title": "性能与扩展性设计",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
@ -258,87 +199,54 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_1_project_implementation",
|
"id": "chapter_4",
|
||||||
"title": "项目实施方案",
|
"title": "质量安全 (5.0分)",
|
||||||
"level": 1,
|
|
||||||
"score": 0.0,
|
|
||||||
"template_placeholder": null,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"id": "chapter_2_1_project_plan",
|
|
||||||
"title": "项目计划",
|
|
||||||
"level": 2,
|
|
||||||
"score": 0.0,
|
|
||||||
"template_placeholder": null,
|
|
||||||
"children": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "chapter_2_2_implementation_team",
|
|
||||||
"title": "实施团队",
|
|
||||||
"level": 2,
|
|
||||||
"score": 0.0,
|
|
||||||
"template_placeholder": null,
|
|
||||||
"children": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "chapter_2_3_schedule_arrangement",
|
|
||||||
"title": "进度安排",
|
|
||||||
"level": 2,
|
|
||||||
"score": 0.0,
|
|
||||||
"template_placeholder": null,
|
|
||||||
"children": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "chapter_1_quality_security",
|
|
||||||
"title": "质量安全",
|
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"score": 5.0,
|
"score": 5.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "chapter_2_1_network_security",
|
"id": "chapter_4_1",
|
||||||
"title": "网络安全防护体系",
|
"title": "网络安全防护体系",
|
||||||
"level": 2,
|
"level": 2,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "chapter_3_1_border_security",
|
"id": "chapter_4_1_1",
|
||||||
"title": "网络边界安全防护",
|
"title": "网络安全架构设计",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_2_intrusion_detection",
|
"id": "chapter_4_1_2",
|
||||||
"title": "入侵检测与防御系统",
|
"title": "网络安全防护措施",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_3_security_audit",
|
"id": "chapter_4_1_3",
|
||||||
"title": "网络安全审计与监控",
|
"title": "网络安全监控与预警",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_4_access_control",
|
"id": "chapter_4_1_4",
|
||||||
"title": "网络访问控制策略",
|
"title": "网络安全应急响应",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_5_security_emergency",
|
"id": "chapter_4_1_5",
|
||||||
"title": "网络安全应急预案",
|
"title": "网络安全管理制度",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
@ -349,21 +257,21 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_1_compliance_response",
|
"id": "chapter_5",
|
||||||
"title": "合规响应",
|
"title": "合规响应 (3.0分)",
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"score": 3.0,
|
"score": 3.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "chapter_2_1_technical_strength",
|
"id": "chapter_5_1",
|
||||||
"title": "技术实力",
|
"title": "技术实力",
|
||||||
"level": 2,
|
"level": 2,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "chapter_3_1_core_team",
|
"id": "chapter_5_1_1",
|
||||||
"title": "核心技术团队介绍",
|
"title": "核心技术团队介绍",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
@ -371,24 +279,214 @@
|
|||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_2_technical_certification",
|
"id": "chapter_5_1_2",
|
||||||
"title": "技术资质与认证",
|
"title": "技术研发能力展示",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_3_rd_capability",
|
"id": "chapter_5_1_3",
|
||||||
"title": "技术研发能力",
|
"title": "技术设备与工具配置",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapter_3_4_implementation_experience",
|
"id": "chapter_5_1_4",
|
||||||
"title": "技术实施经验",
|
"title": "技术成果与专利情况",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_5_1_5",
|
||||||
|
"title": "技术质量管理体系",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_6",
|
||||||
|
"title": "培训服务",
|
||||||
|
"level": 1,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "chapter_6_1",
|
||||||
|
"title": "培训计划",
|
||||||
|
"level": 2,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_6_2",
|
||||||
|
"title": "培训内容",
|
||||||
|
"level": 2,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_6_3",
|
||||||
|
"title": "培训方式",
|
||||||
|
"level": 2,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7",
|
||||||
|
"title": "售后服务 (3.0分)",
|
||||||
|
"level": 1,
|
||||||
|
"score": 3.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "chapter_7_1",
|
||||||
|
"title": "质保期服务承诺",
|
||||||
|
"level": 2,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "chapter_7_1_1",
|
||||||
|
"title": "质保期限与范围",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7_1_2",
|
||||||
|
"title": "质保期内服务内容",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7_1_3",
|
||||||
|
"title": "质保期响应机制",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7_1_4",
|
||||||
|
"title": "质保期满后服务延续方案",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7_2",
|
||||||
|
"title": "技术支持服务",
|
||||||
|
"level": 2,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "chapter_7_2_1",
|
||||||
|
"title": "技术支持团队配置",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7_2_2",
|
||||||
|
"title": "技术支持响应时间",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7_2_3",
|
||||||
|
"title": "技术支持服务方式",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7_3",
|
||||||
|
"title": "维护服务",
|
||||||
|
"level": 2,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "chapter_7_3_1",
|
||||||
|
"title": "定期维护计划",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7_3_2",
|
||||||
|
"title": "故障处理流程",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7_3_3",
|
||||||
|
"title": "备品备件保障",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7_4",
|
||||||
|
"title": "客户服务保障",
|
||||||
|
"level": 2,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "chapter_7_4_1",
|
||||||
|
"title": "客户服务热线",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7_4_2",
|
||||||
|
"title": "客户满意度调查",
|
||||||
|
"level": 3,
|
||||||
|
"score": 0.0,
|
||||||
|
"template_placeholder": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chapter_7_4_3",
|
||||||
|
"title": "客户投诉处理机制",
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"score": 0.0,
|
"score": 0.0,
|
||||||
"template_placeholder": null,
|
"template_placeholder": null,
|
||||||
|
|||||||
@ -18,6 +18,8 @@ from ...nodes.toc import (
|
|||||||
AdjustChaptersNode,
|
AdjustChaptersNode,
|
||||||
FinalizeChaptersNode
|
FinalizeChaptersNode
|
||||||
)
|
)
|
||||||
|
from ...nodes.toc.user_feedback import UserFeedbackNode
|
||||||
|
from ...nodes.toc.optimize_with_feedback import OptimizeWithFeedbackNode
|
||||||
from ...nodes.toc.base_mixins import WorkflowUtilsMixin
|
from ...nodes.toc.base_mixins import WorkflowUtilsMixin
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -49,7 +51,9 @@ class TocAgentBuilder(AgentBuilder):
|
|||||||
.add_node(ReviewStructureNode()) \
|
.add_node(ReviewStructureNode()) \
|
||||||
.add_node(ApplyReviewSuggestionsNode()) \
|
.add_node(ApplyReviewSuggestionsNode()) \
|
||||||
.add_node(AdjustChaptersNode()) \
|
.add_node(AdjustChaptersNode()) \
|
||||||
.add_node(FinalizeChaptersNode())
|
.add_node(FinalizeChaptersNode()) \
|
||||||
|
.add_node(UserFeedbackNode()) \
|
||||||
|
.add_node(OptimizeWithFeedbackNode())
|
||||||
|
|
||||||
# 设置入口点
|
# 设置入口点
|
||||||
builder.set_entry("group_criteria")
|
builder.set_entry("group_criteria")
|
||||||
@ -99,8 +103,28 @@ class TocAgentBuilder(AgentBuilder):
|
|||||||
{"continue": "finalize_chapters", "end": "END"}
|
{"continue": "finalize_chapters", "end": "END"}
|
||||||
)
|
)
|
||||||
|
|
||||||
# 最后一个节点直接结束
|
# finalize_chapters连接到user_feedback
|
||||||
self.add_edge("finalize_chapters", "END")
|
self.add_conditional_edge(
|
||||||
|
"finalize_chapters",
|
||||||
|
should_continue,
|
||||||
|
{"continue": "user_feedback", "end": "END"}
|
||||||
|
)
|
||||||
|
|
||||||
|
# user_feedback的条件边
|
||||||
|
def check_needs_optimization(state: Dict[str, Any]) -> str:
|
||||||
|
"""检查是否需要根据用户反馈进行优化"""
|
||||||
|
if state.get("needs_optimization", False):
|
||||||
|
return "optimize"
|
||||||
|
return "end"
|
||||||
|
|
||||||
|
self.add_conditional_edge(
|
||||||
|
"user_feedback",
|
||||||
|
check_needs_optimization,
|
||||||
|
{"optimize": "optimize_with_feedback", "end": "END"}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 优化后循环回finalize_chapters
|
||||||
|
self.add_edge("optimize_with_feedback", "finalize_chapters")
|
||||||
|
|
||||||
|
|
||||||
class TocAgent(BaseAgent, BaseAgentFactory):
|
class TocAgent(BaseAgent, BaseAgentFactory):
|
||||||
|
|||||||
@ -282,9 +282,54 @@ def new(mode: str, presets: str):
|
|||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
console.print("⚠️ Word模板生成失败,但项目已创建", style="yellow")
|
console.print("⚠️ Word模板生成失败,但项目已创建", style="yellow")
|
||||||
|
else:
|
||||||
|
# Word生成成功后,询问是否调整标题格式
|
||||||
|
if click.confirm("\n📝 是否需要调整Word文档的标题编号格式?", default=False):
|
||||||
|
from ..tools.word_formatter import WordFormatter
|
||||||
|
|
||||||
|
console.print("\n请选择格式来源:", style="blue")
|
||||||
|
console.print(" 1. 默认格式 (1. / 1.1 / 1.1.1)")
|
||||||
|
console.print(" 2. 自定义输入格式")
|
||||||
|
console.print(" 3. 从参考Word文件提取")
|
||||||
|
|
||||||
|
format_choice = click.prompt("请选择", type=click.IntRange(1, 3), default=1)
|
||||||
|
|
||||||
|
format_dict = None
|
||||||
|
if format_choice == 1:
|
||||||
|
format_dict = WordFormatter.get_default_format()
|
||||||
|
console.print("✓ 使用默认格式", style="green")
|
||||||
|
|
||||||
|
elif format_choice == 2:
|
||||||
|
format_input = click.prompt(
|
||||||
|
"请输入格式规则(例如:1. 章 1.1 节 1.1.1 小节)",
|
||||||
|
default="1. 1.1 1.1.1"
|
||||||
|
)
|
||||||
|
format_dict = WordFormatter.parse_user_format(format_input)
|
||||||
|
console.print(f"✓ 解析格式: {format_dict}", style="green")
|
||||||
|
|
||||||
|
elif format_choice == 3:
|
||||||
|
reference_file = click.prompt("请输入参考Word文档路径", type=str)
|
||||||
|
if Path(reference_file).exists():
|
||||||
|
format_dict = WordFormatter.extract_format_from_word(reference_file)
|
||||||
|
console.print(f"✓ 提取格式: {format_dict}", style="green")
|
||||||
|
else:
|
||||||
|
console.print("⚠️ 文件不存在,使用默认格式", style="yellow")
|
||||||
|
format_dict = WordFormatter.get_default_format()
|
||||||
|
|
||||||
|
# 应用格式
|
||||||
|
with console.status("正在调整标题格式..."):
|
||||||
|
format_success = WordFormatter.apply_format_to_document(
|
||||||
|
str(template_file),
|
||||||
|
format_dict
|
||||||
|
)
|
||||||
|
|
||||||
|
if format_success:
|
||||||
|
console.print("✅ 标题格式调整成功!", style="green")
|
||||||
|
else:
|
||||||
|
console.print("⚠️ 格式调整失败", style="yellow")
|
||||||
|
|
||||||
# 显示创建结果
|
# 显示创建结果
|
||||||
console.print("✅ 项目创建成功!", style="green")
|
console.print("\n✅ 项目创建成功!", style="green")
|
||||||
|
|
||||||
# 显示统计信息
|
# 显示统计信息
|
||||||
console.print(f"\n💰 识别到商务评分项: {result.commercial_count}项(已排除)")
|
console.print(f"\n💰 识别到商务评分项: {result.commercial_count}项(已排除)")
|
||||||
|
|||||||
@ -11,6 +11,8 @@ from .review_structure import ReviewStructureNode
|
|||||||
from .apply_suggestions import ApplyReviewSuggestionsNode
|
from .apply_suggestions import ApplyReviewSuggestionsNode
|
||||||
from .adjust_chapters import AdjustChaptersNode
|
from .adjust_chapters import AdjustChaptersNode
|
||||||
from .finalize_chapters import FinalizeChaptersNode
|
from .finalize_chapters import FinalizeChaptersNode
|
||||||
|
from .user_feedback import UserFeedbackNode
|
||||||
|
from .optimize_with_feedback import OptimizeWithFeedbackNode
|
||||||
|
|
||||||
# 辅助组件
|
# 辅助组件
|
||||||
from .factories import ChapterFactory
|
from .factories import ChapterFactory
|
||||||
@ -32,6 +34,8 @@ __all__ = [
|
|||||||
"ApplyReviewSuggestionsNode",
|
"ApplyReviewSuggestionsNode",
|
||||||
"AdjustChaptersNode",
|
"AdjustChaptersNode",
|
||||||
"FinalizeChaptersNode",
|
"FinalizeChaptersNode",
|
||||||
|
"UserFeedbackNode",
|
||||||
|
"OptimizeWithFeedbackNode",
|
||||||
|
|
||||||
# 辅助组件
|
# 辅助组件
|
||||||
"ChapterFactory",
|
"ChapterFactory",
|
||||||
|
|||||||
138
src/bidmaster/nodes/toc/user_feedback.py
Normal file
138
src/bidmaster/nodes/toc/user_feedback.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
"""用户反馈收集节点
|
||||||
|
|
||||||
|
在目录生成完成后,收集用户对目录结构的满意度和修改意见。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Dict, List, Any
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
from ..base import BaseNode, NodeContext
|
||||||
|
from ...tools.parser import DocumentChapter
|
||||||
|
from .base_mixins import TocNodeBase
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class UserFeedbackNode(BaseNode, TocNodeBase):
|
||||||
|
"""收集用户对目录的反馈"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return "user_feedback"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return "收集用户对目录的反馈"
|
||||||
|
|
||||||
|
def execute(self, state: Dict[str, Any], context: NodeContext) -> Dict[str, Any]:
|
||||||
|
"""执行用户反馈收集"""
|
||||||
|
return self.safe_execute(self._do_user_feedback, state, "收集用户对目录的反馈")
|
||||||
|
|
||||||
|
def _do_user_feedback(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""执行实际的用户反馈收集逻辑"""
|
||||||
|
# 验证必需字段
|
||||||
|
if not self.validate_required_fields(state, ["final_chapters"]):
|
||||||
|
raise ValueError("缺少最终章节数据")
|
||||||
|
|
||||||
|
final_chapters = state.get("final_chapters", [])
|
||||||
|
|
||||||
|
# 获取交互处理器
|
||||||
|
interaction_handler = state.get("interaction_handler")
|
||||||
|
|
||||||
|
if not interaction_handler:
|
||||||
|
# 无交互处理器时直接结束(静默模式或程序化模式)
|
||||||
|
logger.info("无交互处理器,跳过用户反馈收集")
|
||||||
|
return self._update_state(state,
|
||||||
|
should_continue=False,
|
||||||
|
user_feedback="",
|
||||||
|
needs_optimization=False)
|
||||||
|
|
||||||
|
# 创建Console实例展示目录结构
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
# 展示目录结构
|
||||||
|
console.print("\n" + "="*60, style="dim")
|
||||||
|
console.print("📚 生成的标书目录结构:", style="bold blue")
|
||||||
|
console.print("="*60, style="dim")
|
||||||
|
|
||||||
|
# 格式化并显示目录
|
||||||
|
toc_display = self._format_chapters_for_display(final_chapters)
|
||||||
|
console.print(toc_display)
|
||||||
|
console.print("="*60 + "\n", style="dim")
|
||||||
|
|
||||||
|
# 询问用户是否满意
|
||||||
|
satisfied = interaction_handler(
|
||||||
|
interaction_type="confirm",
|
||||||
|
prompt="您对上述生成的目录结构满意吗?",
|
||||||
|
default=True,
|
||||||
|
key="user_satisfied_with_toc"
|
||||||
|
)
|
||||||
|
|
||||||
|
if satisfied:
|
||||||
|
logger.info("用户对目录结构满意,结束工作流")
|
||||||
|
return self._update_state(state,
|
||||||
|
should_continue=False,
|
||||||
|
user_feedback="",
|
||||||
|
needs_optimization=False)
|
||||||
|
|
||||||
|
# 收集用户意见
|
||||||
|
feedback = interaction_handler(
|
||||||
|
interaction_type="text",
|
||||||
|
prompt="请输入您的修改意见(例如:调整章节顺序、增加缺失内容、优化章节标题等)",
|
||||||
|
key="user_toc_feedback"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not feedback or feedback.strip() == "":
|
||||||
|
# 用户没有输入意见,默认满意
|
||||||
|
logger.info("用户未提供具体意见,默认满意")
|
||||||
|
return self._update_state(state,
|
||||||
|
should_continue=False,
|
||||||
|
user_feedback="",
|
||||||
|
needs_optimization=False)
|
||||||
|
|
||||||
|
logger.info(f"收集到用户反馈: {feedback[:50]}...")
|
||||||
|
|
||||||
|
return self._update_state(state,
|
||||||
|
should_continue=True,
|
||||||
|
user_feedback=feedback,
|
||||||
|
needs_optimization=True)
|
||||||
|
|
||||||
|
def _format_chapters_for_display(self, chapters: List[DocumentChapter], indent_level: int = 0) -> str:
|
||||||
|
"""格式化章节用于显示
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chapters: 章节列表
|
||||||
|
indent_level: 缩进级别
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
格式化的章节字符串
|
||||||
|
"""
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
for i, chapter in enumerate(chapters, 1):
|
||||||
|
indent = " " * indent_level
|
||||||
|
|
||||||
|
# 根据层级使用不同的标记
|
||||||
|
if indent_level == 0:
|
||||||
|
marker = f"{i}."
|
||||||
|
elif indent_level == 1:
|
||||||
|
marker = f"{i})"
|
||||||
|
else:
|
||||||
|
marker = "-"
|
||||||
|
|
||||||
|
# 添加分值信息
|
||||||
|
score_text = f" ({chapter.score:.1f}分)" if chapter.score and chapter.score > 0 else ""
|
||||||
|
|
||||||
|
lines.append(f"{indent}{marker} {chapter.title}{score_text}")
|
||||||
|
|
||||||
|
# 递归处理子章节
|
||||||
|
if chapter.children:
|
||||||
|
child_text = self._format_chapters_for_display(
|
||||||
|
chapter.children,
|
||||||
|
indent_level + 1
|
||||||
|
)
|
||||||
|
lines.append(child_text)
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
@ -55,12 +55,17 @@ class WordProcessor:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def _add_chapters_to_doc(self, doc: Document, chapters: List[DocumentChapter]):
|
def _add_chapters_to_doc(self, doc: Document, chapters: List[DocumentChapter]):
|
||||||
"""递归添加章节到文档(不含编号)"""
|
"""递归添加章节到文档"""
|
||||||
for chapter in chapters:
|
for chapter in chapters:
|
||||||
# 提取标题文本(去除现有编号)
|
# 检查标题是否已包含编号(如 "1." "1.1" 开头)
|
||||||
title_text = self._extract_title_text(chapter.title)
|
title_text = chapter.title
|
||||||
|
has_numbering = self._check_has_numbering(title_text)
|
||||||
|
|
||||||
# 使用Word的标题样式,让Word自动编号
|
# 如果没有编号,尝试提取纯标题文本
|
||||||
|
if not has_numbering:
|
||||||
|
title_text = self._extract_title_text(title_text)
|
||||||
|
|
||||||
|
# 使用Word的标题样式
|
||||||
if chapter.level <= 9: # Word支持Heading 1-9
|
if chapter.level <= 9: # Word支持Heading 1-9
|
||||||
heading = doc.add_heading(title_text, level=chapter.level)
|
heading = doc.add_heading(title_text, level=chapter.level)
|
||||||
else:
|
else:
|
||||||
@ -94,6 +99,28 @@ class WordProcessor:
|
|||||||
if chapter.level == 1:
|
if chapter.level == 1:
|
||||||
doc.add_paragraph() # 添加空行
|
doc.add_paragraph() # 添加空行
|
||||||
|
|
||||||
|
def _check_has_numbering(self, title: str) -> bool:
|
||||||
|
"""检查标题是否包含编号
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: 标题文本
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True 如果标题以编号开头
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
# 匹配各种编号格式
|
||||||
|
patterns = [
|
||||||
|
r'^(\d+(?:\.\d+)*\.?\s+)', # 1. / 1.1 / 1.1.1
|
||||||
|
r'^([一二三四五六七八九十]+、\s+)', # 中文数字
|
||||||
|
r'^(\([0-9]+\)\s+)', # (1)
|
||||||
|
r'^([A-Z]\.\s+)', # A.
|
||||||
|
]
|
||||||
|
for pattern in patterns:
|
||||||
|
if re.match(pattern, title):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _extract_title_text(self, title: str) -> str:
|
def _extract_title_text(self, title: str) -> str:
|
||||||
"""提取标题文本,去除编号"""
|
"""提取标题文本,去除编号"""
|
||||||
import re
|
import re
|
||||||
|
|||||||
320
src/bidmaster/tools/word_formatter.py
Normal file
320
src/bidmaster/tools/word_formatter.py
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
"""Word文档标题格式调整工具
|
||||||
|
|
||||||
|
读取已生成的Word文档,根据用户输入或参考文件调整标题编号格式。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Optional
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from docx import Document
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NumberingStyle(str, Enum):
|
||||||
|
"""编号样式枚举"""
|
||||||
|
ARABIC = "arabic" # 1, 2, 3
|
||||||
|
CHINESE = "chinese" # 一, 二, 三
|
||||||
|
ROMAN_UPPER = "roman_upper" # I, II, III
|
||||||
|
ROMAN_LOWER = "roman_lower" # i, ii, iii
|
||||||
|
LETTER_UPPER = "letter_upper" # A, B, C
|
||||||
|
LETTER_LOWER = "letter_lower" # a, b, c
|
||||||
|
|
||||||
|
|
||||||
|
# 中文数字映射
|
||||||
|
CHINESE_NUMBERS = {
|
||||||
|
'一': 1, '二': 2, '三': 3, '四': 4, '五': 5,
|
||||||
|
'六': 6, '七': 7, '八': 8, '九': 9, '十': 10
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class WordFormatter:
|
||||||
|
"""Word文档格式调整器"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_user_format(format_str: str) -> Dict[int, Dict[str, any]]:
|
||||||
|
"""解析用户输入的格式字符串
|
||||||
|
|
||||||
|
Args:
|
||||||
|
format_str: 如 "一. 一.1 一.1.1" 或 "1. 1.1 1.1.1"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
格式字典 {
|
||||||
|
1: {"style": "chinese", "suffix": ".", "pattern": "一."},
|
||||||
|
2: {"style": "arabic", "suffix": "", "pattern": "一.1"},
|
||||||
|
3: {"style": "arabic", "suffix": "", "pattern": "一.1.1"}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
format_dict = {}
|
||||||
|
|
||||||
|
# 匹配各种格式的编号
|
||||||
|
patterns = [
|
||||||
|
(r'([一二三四五六七八九十]+)([\.、]?)', NumberingStyle.CHINESE), # 一. 或 一、
|
||||||
|
(r'([IVXLCDM]+)([\.、]?)', NumberingStyle.ROMAN_UPPER), # I. 或 I、
|
||||||
|
(r'([ivxlcdm]+)([\.、]?)', NumberingStyle.ROMAN_LOWER), # i. 或 i、
|
||||||
|
(r'([A-Z])([\.、]?)', NumberingStyle.LETTER_UPPER), # A. 或 A、
|
||||||
|
(r'([a-z])([\.、]?)', NumberingStyle.LETTER_LOWER), # a. 或 a、
|
||||||
|
(r'(\d+(?:\.\d+)*)([\.、]?)', NumberingStyle.ARABIC), # 1. 或 1.1.1
|
||||||
|
]
|
||||||
|
|
||||||
|
# 按空格分割输入
|
||||||
|
parts = format_str.split()
|
||||||
|
|
||||||
|
for level, part in enumerate(parts, 1):
|
||||||
|
matched = False
|
||||||
|
for pattern, style in patterns:
|
||||||
|
match = re.match(f'^{pattern}$', part.strip())
|
||||||
|
if match:
|
||||||
|
number_part = match.group(1)
|
||||||
|
suffix = match.group(2) if match.lastindex >= 2 else ""
|
||||||
|
|
||||||
|
format_dict[level] = {
|
||||||
|
"style": style,
|
||||||
|
"suffix": suffix,
|
||||||
|
"pattern": part.strip()
|
||||||
|
}
|
||||||
|
matched = True
|
||||||
|
logger.info(f"识别第{level}级: {part} -> 样式:{style}, 后缀:{suffix}")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not matched:
|
||||||
|
logger.warning(f"无法识别格式: {part}")
|
||||||
|
|
||||||
|
if not format_dict:
|
||||||
|
logger.warning(f"未识别到任何格式,使用默认")
|
||||||
|
return WordFormatter.get_default_format()
|
||||||
|
|
||||||
|
return format_dict
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_default_format() -> Dict[int, Dict[str, any]]:
|
||||||
|
"""默认的多级编号格式"""
|
||||||
|
return {
|
||||||
|
1: {"style": NumberingStyle.ARABIC, "suffix": ".", "pattern": "1."},
|
||||||
|
2: {"style": NumberingStyle.ARABIC, "suffix": "", "pattern": "1.1"},
|
||||||
|
3: {"style": NumberingStyle.ARABIC, "suffix": "", "pattern": "1.1.1"}
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def apply_format_to_document(
|
||||||
|
doc_path: str,
|
||||||
|
format_dict: Dict[int, Dict[str, any]],
|
||||||
|
output_path: Optional[str] = None
|
||||||
|
) -> bool:
|
||||||
|
"""应用格式到Word文档
|
||||||
|
|
||||||
|
Args:
|
||||||
|
doc_path: Word文档路径
|
||||||
|
format_dict: 格式字典
|
||||||
|
output_path: 输出路径(None则覆盖原文件)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
doc = Document(doc_path)
|
||||||
|
|
||||||
|
# 为每个层级的标题添加编号
|
||||||
|
counters = {} # {level: current_number}
|
||||||
|
|
||||||
|
for para in doc.paragraphs:
|
||||||
|
if para.style.name.startswith('Heading'):
|
||||||
|
level = int(para.style.name.split()[-1])
|
||||||
|
|
||||||
|
# 更新计数器
|
||||||
|
counters[level] = counters.get(level, 0) + 1
|
||||||
|
|
||||||
|
# 重置更低级别的计数器
|
||||||
|
for lower_level in range(level + 1, 10):
|
||||||
|
if lower_level in counters:
|
||||||
|
counters[lower_level] = 0
|
||||||
|
|
||||||
|
# 获取格式配置
|
||||||
|
format_config = format_dict.get(level, format_dict.get(1))
|
||||||
|
|
||||||
|
# 构建编号
|
||||||
|
numbering = WordFormatter._build_numbering(
|
||||||
|
level,
|
||||||
|
counters,
|
||||||
|
format_config
|
||||||
|
)
|
||||||
|
|
||||||
|
# 移除旧编号并添加新编号
|
||||||
|
clean_text = WordFormatter._remove_numbering(para.text)
|
||||||
|
|
||||||
|
# 重要:直接设置文本(python-docx的限制)
|
||||||
|
para.text = f"{numbering} {clean_text}"
|
||||||
|
|
||||||
|
logger.debug(f"设置标题: {para.text}")
|
||||||
|
|
||||||
|
# 保存文档
|
||||||
|
save_path = output_path or doc_path
|
||||||
|
doc.save(save_path)
|
||||||
|
logger.info(f"格式应用成功: {save_path}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"应用格式失败: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _build_numbering(
|
||||||
|
level: int,
|
||||||
|
counters: Dict[int, int],
|
||||||
|
format_config: Dict[str, any]
|
||||||
|
) -> str:
|
||||||
|
"""构建编号字符串
|
||||||
|
|
||||||
|
Args:
|
||||||
|
level: 当前层级
|
||||||
|
counters: 各层级计数器
|
||||||
|
format_config: 格式配置
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
编号字符串,如 "一." 或 "1.1.1"
|
||||||
|
"""
|
||||||
|
style = format_config.get("style", NumberingStyle.ARABIC)
|
||||||
|
suffix = format_config.get("suffix", ".")
|
||||||
|
|
||||||
|
# 收集当前层级及以上的编号
|
||||||
|
numbers = []
|
||||||
|
for lv in range(1, level + 1):
|
||||||
|
num = counters.get(lv, 1)
|
||||||
|
|
||||||
|
# 根据第一级的样式来决定各级的显示方式
|
||||||
|
if lv == 1:
|
||||||
|
# 第一级使用指定的样式
|
||||||
|
numbers.append(WordFormatter._format_number(num, style))
|
||||||
|
else:
|
||||||
|
# 其他层级使用阿拉伯数字
|
||||||
|
numbers.append(str(num))
|
||||||
|
|
||||||
|
# 组合编号
|
||||||
|
if level == 1:
|
||||||
|
# 一级标题:样式 + 后缀
|
||||||
|
return f"{numbers[0]}{suffix}"
|
||||||
|
else:
|
||||||
|
# 多级标题:一级样式.数字.数字
|
||||||
|
return '.'.join(numbers)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _format_number(num: int, style: NumberingStyle) -> str:
|
||||||
|
"""将数字转换为指定样式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
num: 数字
|
||||||
|
style: 样式
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
格式化后的字符串
|
||||||
|
"""
|
||||||
|
if style == NumberingStyle.CHINESE:
|
||||||
|
# 中文数字
|
||||||
|
chinese_map = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||||
|
if num <= 10:
|
||||||
|
return chinese_map[num]
|
||||||
|
else:
|
||||||
|
# 超过10简化处理
|
||||||
|
return f"第{num}"
|
||||||
|
|
||||||
|
elif style == NumberingStyle.ROMAN_UPPER:
|
||||||
|
return WordFormatter._int_to_roman(num).upper()
|
||||||
|
|
||||||
|
elif style == NumberingStyle.ROMAN_LOWER:
|
||||||
|
return WordFormatter._int_to_roman(num).lower()
|
||||||
|
|
||||||
|
elif style == NumberingStyle.LETTER_UPPER:
|
||||||
|
if num <= 26:
|
||||||
|
return chr(64 + num) # A=65
|
||||||
|
else:
|
||||||
|
return f"({num})"
|
||||||
|
|
||||||
|
elif style == NumberingStyle.LETTER_LOWER:
|
||||||
|
if num <= 26:
|
||||||
|
return chr(96 + num) # a=97
|
||||||
|
else:
|
||||||
|
return f"({num})"
|
||||||
|
|
||||||
|
else: # ARABIC
|
||||||
|
return str(num)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _int_to_roman(num: int) -> str:
|
||||||
|
"""整数转罗马数字"""
|
||||||
|
val = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
|
||||||
|
syms = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I']
|
||||||
|
roman_num = ''
|
||||||
|
for i in range(len(val)):
|
||||||
|
count = int(num / val[i])
|
||||||
|
roman_num += syms[i] * count
|
||||||
|
num -= val[i] * count
|
||||||
|
return roman_num
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _remove_numbering(text: str) -> str:
|
||||||
|
"""移除标题中的编号"""
|
||||||
|
patterns = [
|
||||||
|
r'^(\d+(?:\.\d+)*\.?\s+)', # 1. / 1.1 / 1.1.1
|
||||||
|
r'^([一二三四五六七八九十]+[\.、]\s+)', # 一. / 一、
|
||||||
|
r'^([IVXLCDM]+[\.、]\s+)', # I. / I、
|
||||||
|
r'^([ivxlcdm]+[\.、]\s+)', # i. / i、
|
||||||
|
r'^([A-Z][\.、]\s+)', # A. / A、
|
||||||
|
r'^([a-z][\.、]\s+)', # a. / a、
|
||||||
|
r'^(\([0-9]+\)\s+)', # (1)
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
text = re.sub(pattern, '', text, flags=re.IGNORECASE)
|
||||||
|
|
||||||
|
return text.strip()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extract_format_from_word(reference_file: str) -> Dict[int, Dict[str, any]]:
|
||||||
|
"""从参考Word文档提取标题编号格式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reference_file: 参考Word文档路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
格式字典
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
doc = Document(reference_file)
|
||||||
|
format_patterns = {}
|
||||||
|
|
||||||
|
for para in doc.paragraphs:
|
||||||
|
if para.style.name.startswith('Heading'):
|
||||||
|
level = int(para.style.name.split()[-1])
|
||||||
|
text = para.text.strip()
|
||||||
|
|
||||||
|
# 提取编号部分
|
||||||
|
for pattern_re, style in [
|
||||||
|
(r'^([一二三四五六七八九十]+)([\.、])', NumberingStyle.CHINESE),
|
||||||
|
(r'^([IVXLCDM]+)([\.、])', NumberingStyle.ROMAN_UPPER),
|
||||||
|
(r'^(\d+(?:\.\d+)*)([\.、]?)', NumberingStyle.ARABIC),
|
||||||
|
]:
|
||||||
|
match = re.match(pattern_re, text)
|
||||||
|
if match and level not in format_patterns:
|
||||||
|
number_part = match.group(1)
|
||||||
|
suffix = match.group(2) if match.lastindex >= 2 else ""
|
||||||
|
|
||||||
|
format_patterns[level] = {
|
||||||
|
"style": style,
|
||||||
|
"suffix": suffix,
|
||||||
|
"pattern": f"{number_part}{suffix}"
|
||||||
|
}
|
||||||
|
logger.info(f"从参考文档提取: 级别{level} = {number_part}{suffix}")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not format_patterns:
|
||||||
|
logger.warning(f"未从 {reference_file} 提取到格式")
|
||||||
|
return WordFormatter.get_default_format()
|
||||||
|
|
||||||
|
return format_patterns
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"提取Word格式失败: {e}")
|
||||||
|
return WordFormatter.get_default_format()
|
||||||
Loading…
Reference in New Issue
Block a user