From 6cacec533cf977e16233563d36faa66b38aa3da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=93=E9=B1=BC=E4=BA=91=E7=B2=BE=E7=81=B5?= <16496725+fishing-yun@user.noreply.gitee.com> Date: Wed, 17 Dec 2025 06:53:07 +0000 Subject: [PATCH 1/5] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20=E6=99=BA=E6=9E=84?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\346\231\272\346\236\204\346\226\207\346\241\243/.keep" | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 "\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/.keep" diff --git "a/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/.keep" "b/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/.keep" new file mode 100644 index 0000000..e69de29 -- Gitee From b3bd09f7e6db2d07321eea4b8ddf24031fbc0dc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=93=E9=B1=BC=E4=BA=91=E7=B2=BE=E7=81=B5?= <16496725+fishing-yun@user.noreply.gitee.com> Date: Wed, 17 Dec 2025 06:53:15 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20?= =?UTF-8?q?=E4=BD=9C=E5=93=81=E6=8F=90=E4=BA=A4/=E6=99=BA=E6=9E=84?= =?UTF-8?q?=E6=96=87=E6=A1=A3/.keep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\346\231\272\346\236\204\346\226\207\346\241\243/.keep" | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 "\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/.keep" diff --git "a/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/.keep" "b/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/.keep" deleted file mode 100644 index e69de29..0000000 -- Gitee From c0b8dd0866b80b1b874f664a2f19e81e0539c371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=93=E9=B1=BC=E4=BA=91=E7=B2=BE=E7=81=B5?= <16496725+fishing-yun@user.noreply.gitee.com> Date: Wed, 17 Dec 2025 06:53:33 +0000 Subject: [PATCH 3/5] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20=E6=99=BA=E6=9E=84?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\346\231\272\346\236\204\346\226\207\346\241\243/.keep" | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 "\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/.keep" diff --git "a/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/.keep" "b/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/.keep" new file mode 100644 index 0000000..e69de29 -- Gitee From 0efb3ba29c4116367abc1c1431be13c3a8928889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=93=E9=B1=BC=E4=BA=91=E7=B2=BE=E7=81=B5?= <16496725+fishing-yun@user.noreply.gitee.com> Date: Wed, 17 Dec 2025 06:55:53 +0000 Subject: [PATCH 4/5] =?UTF-8?q?=E6=99=BA=E6=9E=84=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 钓鱼云精灵 <16496725+fishing-yun@user.noreply.gitee.com> --- .../README.md" | 140 ++++ .../doc_generator_assistant.py" | 318 +++++++++ .../doc_generator_web.py" | 602 ++++++++++++++++++ .../index.html" | 501 +++++++++++++++ .../main.py" | 86 +++ .../requirements.txt" | 36 ++ .../show.png" | Bin 0 -> 125738 bytes ...00\346\234\257\346\226\207\346\241\243.md" | 228 +++++++ 8 files changed, 1911 insertions(+) create mode 100644 "\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/README.md" create mode 100644 "\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/doc_generator_assistant.py" create mode 100644 "\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/doc_generator_web.py" create mode 100644 "\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/index.html" create mode 100644 "\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/main.py" create mode 100644 "\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/requirements.txt" create mode 100644 "\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/show.png" create mode 100644 "\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/\346\212\200\346\234\257\346\226\207\346\241\243.md" diff --git "a/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/README.md" "b/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/README.md" new file mode 100644 index 0000000..0b44a54 --- /dev/null +++ "b/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/README.md" @@ -0,0 +1,140 @@ +# 智构文档 - 技术文档自动生成智能体 + +智构文档是一款专为应对复杂技术场景而生的长篇技术文档智能生成Agent。它凭借智能架构能力,自动生成结构清晰、内容准确、术语规范的系统设计文档、技术架构文档、部署指南等长篇核心资料。将工程师从繁重的文档编写中解放,让知识沉淀变得自动化、系统化,助力团队打造坚实的技术知识底座。 + +## 功能特点 + +- 📝 **多类型文档支持**:系统设计文档、技术架构文档、部署指南等 +- 🧠 **智能生成**:基于LLM技术,根据用户需求自动生成高质量文档 +- 📋 **结构化模板**:内置规范的文档结构模板,确保文档专业性 +- 🎨 **用户友好界面**:直观的Web界面,操作简单易用 +- ⬇️ **文档下载**:支持将生成的文档下载为Markdown格式 + +## 技术栈 + +- Python 3.7+ +- lazyllm:语言模型接口封装 +- markdown:Markdown转HTML +- http.server:Web服务器 + +## 安装与运行 + +### 1. 安装依赖 + +```bash +# 安装项目依赖 +pip install -r requirements.txt +``` + +### 2. 启动智能体 + +```bash +# 运行智能体 +python main.py + +# 或指定参数运行 +python main.py --host 0.0.0.0 --port 7861 --model_path /path/to/your/model +``` + +### 3. 访问Web界面 + +启动后,在浏览器中访问: +``` +http://localhost:7861 +``` + +## 项目结构 + +``` +智构文档/ +├── main.py # 主程序入口 +├── doc_generator_assistant.py # 文档生成智能体核心模块 +├── doc_generator_web.py # Web界面模块 +├── index.html # 前端页面 +├── README.md # 项目说明文档 +├── requirements.txt # 依赖文件 +├── 技术文档.md # 技术文档 +├── show.png # 项目截图 +└── .gitignore # Git忽略文件 +``` + +## 使用方法 + +1. **选择文档类型**:在下拉菜单中选择需要生成的文档类型(系统设计文档、技术架构文档、部署指南) +2. **输入文档需求**:详细描述您的文档需求,包括系统功能、技术栈、业务场景等 +3. **生成文档**:点击"生成文档"按钮,等待文档生成完成 +4. **查看与下载**:查看生成的文档内容,或点击"下载Markdown"按钮将文档保存到本地 + +## 示例需求 + +### 系统设计文档示例需求 +``` +我需要一个电商平台的系统设计文档,主要包含以下功能模块: +1. 用户管理:注册、登录、个人信息管理 +2. 商品管理:商品发布、分类、搜索、详情 +3. 订单管理:下单、支付、物流跟踪 +4. 购物车:添加商品、修改数量、结算 +5. 支付系统:支持微信支付、支付宝支付 +6. 后台管理:商品管理、订单管理、用户管理、数据统计 + +技术栈要求: +- 前端:React + Ant Design +- 后端:Spring Boot + MySQL +- 中间件:Redis(缓存)、RabbitMQ(消息队列) +- 部署:Docker + Kubernetes +``` + +### 技术架构文档示例需求 +``` +我需要一个微服务架构的技术架构文档,系统包含以下服务: +1. 用户服务:负责用户认证与授权 +2. 产品服务:管理产品信息 +3. 订单服务:处理订单流程 +4. 支付服务:处理支付请求 +5. 通知服务:发送系统通知 + +技术栈: +- 服务框架:Spring Cloud +- 注册中心:Eureka +- 配置中心:Spring Cloud Config +- 网关:Spring Cloud Gateway +- 断路器:Hystrix +- 追踪:Sleuth + Zipkin +- 数据库:MySQL(关系型)、MongoDB(非关系型) +- 缓存:Redis +``` + +### 部署指南示例需求 +``` +我需要一个基于Docker和Kubernetes的部署指南,系统包含以下组件: +1. 前端应用:使用Nginx部署React应用 +2. 后端服务:Spring Boot微服务 +3. 数据库:MySQL 8.0 +4. 缓存:Redis 6.0 +5. 消息队列:RabbitMQ 3.9 + +部署环境: +- Kubernetes集群:3个节点(1个master,2个worker) +- 操作系统:Ubuntu 20.04 +- Docker版本:20.10+ +``` + +## 项目结构 + +``` +doc_generator_agent/ +├── assistant.py # 核心文档生成逻辑 +├── web_ui.py # Web界面实现 +├── main.py # 主程序入口 +├── index.html # 自动生成的HTML页面 +└── README.md # 项目说明文档 +``` + +## 注意事项 + +1. 首次运行时,系统会自动生成`index.html`文件 +2. 如果使用本地模型,请确保模型路径正确且模型文件完整 +3. 如果使用在线模型,请确保网络连接正常且已配置好API密钥 +4. 生成文档的质量取决于用户提供的需求描述详细程度,建议尽可能详细地描述需求 + + diff --git "a/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/doc_generator_assistant.py" "b/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/doc_generator_assistant.py" new file mode 100644 index 0000000..202864a --- /dev/null +++ "b/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/doc_generator_assistant.py" @@ -0,0 +1,318 @@ +""" +智构文档智能体核心模块 +提供技术文档自动生成功能,支持系统设计文档、技术架构文档、部署指南等 +""" + +import os +from typing import Dict, List +from lazyllm import TrainableModule, OnlineChatModule + + +class DocGeneratorAssistant: + """智构文档智能体类""" + + def __init__(self, model_path: str = None): + """ + 初始化智构文档智能体 + + Args: + model_path: 本地模型路径,如果为None则使用在线模型 + """ + self.model_path = model_path + self._init_model() + self._init_templates() + + def _init_model(self): + """初始化语言模型""" + self.model_available = True + try: + if self.model_path and os.path.exists(self.model_path): + # 使用本地模型 + self.llm = TrainableModule(self.model_path) + else: + # 使用在线模型 + self.llm = OnlineChatModule() + except Exception as e: + print(f"模型初始化失败: {e}") + print("将使用模拟模式运行,请后续配置正确的模型或API密钥") + self.model_available = False + self.llm = None + + def _init_templates(self): + """初始化文档模板""" + # 加载文档结构模板 + self.document_templates = self._load_document_templates() + + # 加载文档生成提示词模板 + self.generation_prompts = self._load_generation_prompts() + + def _load_document_templates(self) -> Dict[str, List[str]]: + """加载文档结构模板""" + return { + "通用文档": [ + "1. 文档概述", + "2. 主要内容", + "3. 详细说明", + "4. 总结与结论" + ], + "系统设计文档": [ + "1. 文档概述", + "2. 系统架构", + "3. 核心功能模块", + "4. 数据模型设计", + "5. API接口设计", + "6. 系统安全性设计", + "7. 性能优化策略", + "8. 部署与运维方案", + "9. 测试策略", + "10. 总结与展望" + ], + "技术架构文档": [ + "1. 架构概述", + "2. 技术栈选型", + "3. 系统分层设计", + "4. 核心组件设计", + "5. 集成与接口设计", + "6. 技术风险与应对", + "7. 扩展与演进计划" + ], + "部署指南": [ + "1. 部署环境准备", + "2. 安装依赖", + "3. 配置文件说明", + "4. 部署步骤", + "5. 验证部署", + "6. 常见问题与解决方案" + ] + } + + def _load_generation_prompts(self) -> Dict[str, str]: + """加载文档生成提示词模板""" + return { + "通用文档": """ + 你是一位专业的文档撰写专家,请根据用户提供的需求,生成一份结构清晰、内容详细的通用文档。 + + 用户需求:{requirements} + + 文档结构请遵循以下大纲,确保每个部分都有详细的描述: + 1. 文档概述 + - 文档目的:详细说明文档的用途和目标读者 + - 背景介绍:提供相关的背景信息和上下文 + - 文档范围:明确文档涵盖的内容和不包含的内容 + 2. 主要内容 + - 核心主题:文档的主要内容和讨论点 + - 关键信息:重要的事实、数据或结论 + - 结构组织:内容的逻辑组织和分类 + 3. 详细说明 + - 深入分析:对主要内容的详细分析和解释 + - 具体细节:必要的细节信息和示例 + - 支持材料:图表、表格或其他支持材料 + 4. 总结与结论 + - 主要结论:文档的核心结论和发现 + - 建议:相关的建议或行动方案 + - 后续步骤:可能的后续步骤或进一步研究 + + 请确保文档内容详细、准确,语言流畅,符合专业文档的规范。根据用户需求调整内容的深度和广度,确保文档满足用户的实际需求。 + """, + "系统设计文档": """ + 你是一位专业的系统架构师,请根据用户提供的系统需求,生成一份结构清晰、内容详细的系统设计文档。 + + 用户需求:{requirements} + + 文档结构请遵循以下大纲,确保每个部分都有详细的描述: + 1. 文档概述 + - 文档目的:详细说明文档的用途和目标读者 + - 术语定义:列出所有相关技术术语及其解释 + - 文档范围:明确文档涵盖的内容和不包含的内容 + 2. 系统架构 + - 整体架构图:描述系统的整体结构和主要组件 + - 架构风格说明:解释选择的架构风格及其优势 + - 各层职责:详细描述系统各层的功能和职责 + 3. 核心功能模块 + 对每个核心模块进行详细描述,包括: + - 模块概述:模块的功能定位和核心价值 + - 功能列表:列出模块包含的所有具体功能点 + - 处理流程:描述模块的主要业务流程和数据流转 + - 关键接口:列出模块对外提供的主要接口 + - 依赖关系:说明模块与其他模块的依赖关系 + 4. 数据模型设计 + - 实体关系图:描述系统的主要实体及其关系 + - 核心数据表结构:详细列出每个表的字段名、数据类型、约束和描述 + - 数据流转:说明数据在系统各模块之间的流动路径 + 5. API接口设计 + 对每个主要API接口进行详细描述: + - 接口名称和URL + - 请求方法(GET/POST等) + - 请求参数:名称、类型、必填项、描述 + - 响应格式:成功和失败的响应结构和示例 + - 错误码:常见错误码及其说明 + 6. 系统安全性设计 + - 认证与授权:详细说明用户认证和权限控制机制 + - 数据安全:说明数据加密、存储和传输的安全措施 + - 访问控制:描述系统的访问控制策略和实现方式 + - 安全审计:说明安全日志和审计机制 + 7. 性能优化策略 + - 缓存策略:详细说明使用的缓存技术和缓存策略 + - 数据库优化:索引设计、查询优化等 + - 并发处理:系统的并发控制和负载均衡策略 + - 资源管理:内存、CPU等资源的管理策略 + 8. 部署与运维方案 + - 部署架构:详细描述系统的部署结构 + - 环境配置:列出所有环境变量和配置项 + - 监控与告警:监控指标、告警规则和处理流程 + - 日志管理:日志的收集、存储和分析方案 + 9. 测试策略 + - 测试类型:单元测试、集成测试、系统测试等 + - 测试重点:关键功能和流程的测试策略 + - 测试工具:使用的测试工具和框架 + - 测试环境:测试环境的配置和管理 + 10. 总结与展望 + - 系统亮点:总结系统的主要优势和特点 + - 后续优化方向:列出系统可以进一步改进的地方 + + 请确保文档内容详细、准确,符合技术文档的专业规范,便于开发团队理解和执行。每个模块的描述应足够具体,包含可操作的信息。 + """, + "技术架构文档": """ + 你是一位资深的技术架构师,请根据用户提供的系统信息,生成一份全面、详细的技术架构文档。 + + 系统信息:{requirements} + + 文档结构请遵循以下大纲,确保每个部分都有详细的描述: + 1. 架构概述 + - 架构目标:详细说明架构设计的目标和原则 + - 设计原则:列出架构设计遵循的核心原则 + - 架构决策记录:记录所有重要的架构决策及其原因 + 2. 技术栈选型 + 详细说明每个技术组件的选型理由和版本: + - 前端技术:框架、库、构建工具等 + - 后端技术:语言、框架、中间件等 + - 数据库:主数据库、缓存、搜索引擎等 + - 云服务:使用的云平台和服务 + 3. 系统分层设计 + 详细描述系统的分层结构: + - 表示层:负责用户界面和交互 + - 业务逻辑层:处理核心业务逻辑 + - 数据访问层:负责数据持久化 + - 基础设施层:提供底层服务支持 + 4. 核心组件设计 + 对每个核心组件进行详细描述: + - 组件概述:组件的功能和定位 + - 设计细节:组件的内部结构和实现方式 + - 关键算法:组件使用的核心算法和技术 + - 接口定义:组件提供的接口和协议 + - 性能特性:组件的性能要求和优化措施 + 5. 集成与接口设计 + - 内部接口:系统内部组件之间的接口设计 + - 外部系统集成:与第三方系统的集成方式和接口 + - 消息队列设计:消息的格式、路由和处理机制 + - API网关:API的统一管理和保护 + 6. 技术风险与应对 + - 潜在风险:详细列出所有潜在的技术风险 + - 应对措施:针对每个风险的具体应对策略 + - 容错机制:系统的容错设计和故障恢复机制 + - 灾难恢复:系统的备份和恢复方案 + 7. 扩展与演进计划 + - 横向扩展:系统的水平扩展能力设计 + - 功能演进:系统功能的迭代和扩展计划 + - 技术债务管理:技术债务的识别和管理策略 + - 架构演进:架构的长期演进路线图 + + 请确保文档内容专业、深入,能够指导技术团队的开发和维护工作。每个组件的描述应包含足够的技术细节。 + """, + "部署指南": """ + 你是一位专业的DevOps工程师,请根据用户提供的系统信息,生成一份详细、可操作的部署指南。 + + 系统信息:{requirements} + + 文档结构请遵循以下大纲,确保每个步骤都有详细的说明: + 1. 部署环境准备 + - 硬件要求:详细列出服务器的硬件配置要求 + - 操作系统要求:支持的操作系统版本和配置 + - 依赖软件:列出所有需要预先安装的软件及其版本 + - 网络要求:网络配置和端口开放要求 + 2. 安装依赖 + - 依赖列表:详细列出所有依赖项及其版本 + - 安装步骤:每个依赖项的具体安装命令和配置 + - 验证安装:如何验证依赖是否正确安装 + 3. 配置文件说明 + - 配置文件路径:所有配置文件的位置 + - 关键配置项:每个重要配置项的含义和取值范围 + - 环境变量:所有环境变量的说明和默认值 + - 安全配置:与安全相关的配置项 + 4. 部署步骤 + 提供详细的、可复制的部署步骤: + - 步骤1:[步骤名称] - 详细说明每个步骤的操作和命令 + - 步骤2:[步骤名称] - 包含所有必要的命令和参数 + - ... + - 确保步骤顺序正确,每个步骤都有明确的目标 + 5. 验证部署 + - 服务状态检查:如何检查服务是否正常运行 + - 功能验证:测试系统的主要功能是否正常 + - 性能测试:基本的性能测试方法和指标 + - 日志检查:如何查看和分析系统日志 + 6. 常见问题与解决方案 + 列出所有可能遇到的问题和详细解决方案: + - 问题1:[问题描述] - 包含错误信息和可能的原因 + 解决方案:详细的解决步骤和命令 + - 问题2:[问题描述] + 解决方案:详细的解决步骤和命令 + - ... + 7. 运维与监控 + - 日常运维:系统日常维护的任务和操作 + - 监控配置:监控工具的配置和使用 + - 告警规则:告警的配置和处理流程 + - 性能优化:日常性能优化的方法和建议 + + 请确保文档步骤清晰、可操作性强,便于运维人员快速部署和维护系统。所有命令和配置都应详细准确。 + """ + } + + def generate_document(self, doc_type: str, requirements: str) -> str: + """ + 生成技术文档 + + Args: + doc_type: 文档类型(系统设计文档、技术架构文档、部署指南等) + requirements: 用户提供的文档需求 + + Returns: + 生成的文档内容 + """ + if not self.model_available: + return "模型未配置,无法生成文档。请配置有效的模型路径或API密钥后重试。" + + # 获取对应的生成提示词 + prompt_template = self.generation_prompts.get(doc_type, self.generation_prompts["通用文档"]) + prompt = prompt_template.format(requirements=requirements) + + try: + # 使用LLM生成文档 + response = self.llm(prompt) + return response + except Exception as e: + return f"文档生成失败:{str(e)}" + + def get_document_templates(self) -> Dict[str, List[str]]: + """ + 获取支持的文档模板 + + Returns: + 文档模板字典,键为文档类型,值为文档结构大纲 + """ + return self.document_templates + + def validate_doc_type(self, doc_type: str) -> bool: + """ + 验证文档类型是否支持 + + Args: + doc_type: 文档类型 + + Returns: + 布尔值,表示文档类型是否支持 + """ + return doc_type in self.document_templates + + + + diff --git "a/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/doc_generator_web.py" "b/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/doc_generator_web.py" new file mode 100644 index 0000000..9f1f049 --- /dev/null +++ "b/\344\275\234\345\223\201\346\217\220\344\272\244/\346\231\272\346\236\204\346\226\207\346\241\243/doc_generator_web.py" @@ -0,0 +1,602 @@ +""" +智构文档智能体Web界面模块 +提供用户友好的界面,用于输入文档需求并生成技术文档 +""" + +import http.server +import socketserver +import json +from http import HTTPStatus +import markdown + + +class DocGeneratorWebUI: + def __init__(self, model_path: str = None): + from doc_generator_assistant import DocGeneratorAssistant + self.agent = DocGeneratorAssistant(model_path) + self.port = 7861 + + def create_ui(self): + # 创建HTTP服务器 + class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): + def __init__(self, *args, agent=None, **kwargs): + self.agent = agent + super().__init__(*args, **kwargs) + + def do_GET(self): + if self.path == '/': + self.path = '/index.html' + elif self.path == '/api/get_templates': + # 处理获取文档模板请求 + try: + templates = self.agent.get_document_templates() + self.send_response(HTTPStatus.OK) + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps({'templates': templates}).encode('utf-8')) + return + except Exception as e: + self.send_response(HTTPStatus.INTERNAL_SERVER_ERROR) + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps({'error': str(e)}).encode('utf-8')) + return + return super().do_GET() + + def do_POST(self): + if self.path == '/api/generate_doc': + # 处理文档生成请求 + content_length = int(self.headers['Content-Length']) + post_data = self.rfile.read(content_length) + data = json.loads(post_data.decode('utf-8')) + doc_type = data.get('doc_type', '') + requirements = data.get('requirements', '') + + try: + # 使用智能体生成文档 + response = self.agent.generate_document(doc_type, requirements) + # 将Markdown转换为HTML + html_response = markdown.markdown(response, extensions=['extra', 'codehilite']) + self.send_response(HTTPStatus.OK) + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps({'response': html_response, 'markdown': response}).encode('utf-8')) + except Exception as e: + self.send_response(HTTPStatus.INTERNAL_SERVER_ERROR) + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps({'error': str(e)}).encode('utf-8')) + else: + self.send_response(HTTPStatus.NOT_FOUND) + self.end_headers() + + return MyHTTPRequestHandler + + def generate_html(self): + # 生成HTML页面内容 + html_content = ''' + + +
+ + +技术文档自动生成助手 - 让文档编写更高效
+技术文档自动生成助手 - 让文档编写更高效
+X28v4Z6K3 zj<4H??)lAL4z-YkILo17C;G;vn(HJ@7obxZ;fU^}QlT-NTcD=EQ^AQcS7u)0B^6gC zEupG!jx-cGPBwcDTcSp+TAwH?x3XP!o9_Dhj0ja5t!O&vJU;bxOSufO$bpQHoOhEW zFtfXxEg1YO*Q=TlBJ>Q8;o{%;?S692?(`*S&J?XW^1PsQd3J@dZQjhP8sD%yv{9%E}j})l*D3&dR zd@cVZrK)E0v+`KD)4m!v8cn{PyLpi7nqyD)i=U-+(hbug6o?*DsVh53+p|Gb(X5jU zH yQwvfAX_`VUaytO~EFB*G!SW~h~ zzBL?z6U6j{KGYV7uO&$rF87d$qq`GW2-%%0ITlJ{brUEkbl>r$4B~CL7IaLmqsIm4 zGTn|kB)OKRxE!Q|j8|Y!* 4EL3pw#<=~LSVyE8NaV*S z>HfuAzdZnank>_jjN{f5AeX5rNi%)$JPa{$KAsbk z3U4wh-Jlr5o;GIe??3t$4H(+_ov4_D_4L@nKm*^iq7xZ5Jc{OOmWbNLD90Vjlryy< z!3yWb)w@sdI;O8|N84i3*7G s*jC9W3o??y#>e7B zfynhw{?(F=Ulu+ap0z~ j2xp?$;kgt2=x^I?!t-?NB>b6re;6K zi@n0A;$zwZrZ8uw7OK#{o0Zgfs6ze9#bGd=)n&Xl2D|%&)+Qhnb~czJlaQBb7J85y zTQi8elICMT$XjkO)CS&X=aV)PB+3euC~p0B)+2g>3k}cDf9`sAzUMIoCQCuQL$uWs z_g1q|yJfkZ$KH*2NKD4F@!}}E6;br{d@?F!|Gw)jknR&d=dC9qI^b)sYW3$z90?&T zx1C(TYCQ}raS|=gLin)S=ya;{9 h3MC8!Fh`VekxlKc%*xVVWpC3 5nw*TruYg_R T2vMwB=r&d_ z?TI_Ikf|@&`r&x%k_weSyWg#RYSps1mR=kt?ADTINfhO{(a(9rJ{h9;ZfU?f @Y1rhyfH7vA@4R zii?0PLMBRMoIg^=mFY};a<&2YquBYQ8#pY@_TC=#@8ACy-1EF-s&Lb{^I!3>m#lI( z{dm8v?<||3URSaO9Vh|#C=)&8kpUr)fdI+@ozNz2Jpg{SVoGzwzp0|>ajey2w!em@ z1oiRp@yTN!-PDUH%r^=>*1-Un?A_rv05}xg=Y_s$0nN+qHljFq$2@MM_*9O;J$Di5 z+HMtRZj;bF{6-!r(d+$w5J<4=^~1bU!A1X2LeQSqU()+81{7@40G#Dbt5xG`KgJh- z)rBZ#=>Vkcqz}es^862a!vYFD%f5Gm$pxWaSy)&scNVusJ_KEZa;@l3d!aT9R5sKJ zWo2B^CqR-E$m-<)DB_aRQq5Cr%RpnqF6j+?mpz<%l9iPe)md>f@b7w((5Zoefwiv& z)gSL%gT|H<);c7hQ kKS(bc;?TLSxl8=1ihbg!hOBtN}-^F E(5*vfem=hOiR;pR%km(TTiS^o0JADx&s^U= zob-#YEn1-UNPh}FGG(Q_wYBB> zDW$}!07l`>0I>AG<=|;c z15~(wq>M*hHto%WH11-|&Hlv4Ey38}-D~U{Sdj JQJA&g!=Yc34>^;W5mtI4(L0(_GT0;B3W-hkB%6? zvxaM&?%J=-oB!``e>LBn$*Z1>S7 k_E|C3gTynud!loKDnk20ANd%Ne_+)Ci%m0{ETOe0;t#0jM8 (M_()smXBUuVoU_T>I?7WTde?;7dVzl(qK#9v|Z4|+5Y zZbK5G$Kn5I$q}o;v%UX6Ya7^Jy8r*jS&sjkJ)IjUc}P}DN+WktDVM?>SWHX|e&g$h zDL~D}Vf;}2ZIC=cL}yrN=%Rh~l%<2SbCr^`SXX&J!w +|Pb?*asX&i|&>Nf& zQ}Xii($bob2L41vMa9ONpL{2hS%#ub+d6Xq)R53m(kpX5M9Sb`)j|ICsSx10p9fHP zSy_cbp(-nR`T46A5`^Hf_dt #EmGJ(L-g)twC48c|5SHp-i0XjQyaCpEQfYZPuCRJ}S=q?Q z$nx?sc5QIQAP^7KhxhK?yJI~bC1vFl0~B_oc9|DAd2!7-D_X5p5t<+#aB7X<<4Zmi z3vlWJyf7B{( fq~H#O?qE+#D<3cjmDEw&dwSCYq? z)?M?8=8}i5s!TH#Wz}^M-e#4Lz}{~`1?+Tu7i&qt1t;AV{)`?TURqwpR6Ymw(F4q` ztgN)NvkPw;DRky$RBHW4jwtS>cyt U3DE<8MX?W}F;`{YbFjOv@vRK7>~#ceUF>2GaJ zo*ugJySjLV4R{!}>|h880z(0PqC-jp>3un<#WM*R85v+?@>mTJs8$uY12q1_MsKBo z4UJ)_W~4ejNQB~B=6Wd!%yXT;)cdK$m2u9)B?# TC-baFZAz zgpMz;H6$*sd${)&XlmKIvZ%ONkV-*bUcMj%nHG$0z qqi z&f_P*8Kvki&uV}O-dPObsf~@zu3@vzBDrBe D6-M8)$k5e)|V+*n=xRz1bwLJa!uIBQ=G7-O)Y8A?by)zA@G#RqCc1A6;EN0Q9d zCmzpvcf;?0ce%U7W9siL{(B(#wqYIWzD;6|ZDy@UtVj^-z`^k5;LwMC=h_nKJD`LA zB0852ojvGl!<8WXHo~|w?~u}<@6*k(?a_mJHe@i3HYf_8&1idnw&eZ6ZFgii$Hhvo zlj{Y=ZJVwuvEL)}54fCOAMW8F+P~IqWX1lYTu35O{l!fv@g+Q`L>$2=-3>TpS0%aX zMtHIs1A46!%z@`=(n3Nnai7~-#5lTEetV5pPVZ(OY{3aj9^rMrzhXyg&dNhM`O!pv zb3&0odt^>#d}S1kA18YDj6!nwNh|pVPh nK<4ty}};e zYnb^oME+v`?j!zdIKX%I2>M0(xx1hP`|&T#TH_m>; exm|+#|v5)cgu32 -9 z&Ny{B9uI-Yy!D#Z{Q;qo__uY{6+11lwk0xz2U?vfIgHm&(L+8(ws}6#RL*Z4C%U8N ztL9GL25}u+KKSINo@%eOVTP1HY#yB3h;|^#{#zPBr7dAd=HV2u9knn#w=o`wM&*~b zp{;b4S9CTqZGW=H+|w7vAqeZ2CuybB+Rj2t7B80BBKUD59wP~m*u$McpDt4GyQKGS z$^R*I80=Ug>_#18SFjS*4@|~7$So`8JnxbR-Z45R&7VI0i_bmztr1@#;)L7yCsMr> zy5KVG&VTufw1y~huwUhL9xn~Q-r~6&z8ulzFN>qT2^MvsV(X;|FZs>UTcCd1=*7o} zw?WRdt*J`5=|4=7>bL`XtP@u+*f;O1Sf82+t0vk&XA=+sKf4QcR#-}W8tMPW8fiT( zfc2H-yFTow$@g~s+`)CvFv@hyo1^=;MOSw5f+}{9UYEl7r{Wklxnb8AWA=tf4{~^} zl%D0GutHbei~pMU#-F~yxdtBny>BgZ7(hwQ`TP9KnIw0-tKLKCyU+{lZxUoE{%JpQ zhdhM*vMbPuBo+sHB|$5rzQ&Zy z7F(@fg)%kFTojHSw{Yx*o(^Vi62{F|t{VhAC8{ick0?eyUw;gFpG*Lwp6eUEC1Rer zd$+3L8)H1BZBL{u5s`l92<0$#K&!DVNa!TWf7ny;G{)G0^OxYK>V-8`m*&x{`WKC? zU~uKacFk3Gl4Z)N0?YQv9-d2gj2WY@Rqu}#q1R1<2iarX;RZh5-knqBG{W3+Vog&F z!gVylb%EMqbgbNUG;h
F2cMttL zjblhpWBqKV%;y1;&Mx!602NO2><^_#lC)@5ao2abMrvS!u0o#?w;F;BMx`Zlvs zlKevz9)IPr9@QqRdjW0EKCwNj-^h@`kkaD>RuqG5!MM8ZYSz+m6V9W%Bq$M5mAaGF zO=GZ1pU*S?h~8<_I45igzvS?CR`@yxQIO~aI{#c!>(ZMA*`ZUGZ@SVkBd-%mtx>e5 zkmIIo=Dn!LxiS7;3d|}e=h^lLx>GH@Pg-}}n8oc!J~W_W 3 I3l*L;WZ zO4O^veupGzG6w-)&;)}GduPsz<&wHLb-7$*K09AUpKv4hkfj)uwz`jZ+Y+8$G?|Pj zaMlb2;`y?S(R+i|U+)t}h9BjKhQ&)a6xDtHf^ E4N zv{LEdl57Yzx#}06LRM (VatdTVr`Ti^&Z*JML%VA0)~O;#ljXi7*KF>~=JMr4#jDFE zWi%DXJ^Q+>?P<>9h%2daGjTC2@?vb0TP!-j*Sn?`j>Tote+gNt(Xtq`xvKoC-Z`dx zb &qPno)gP^#P*V?wKj<@Ybzzn9+yUqE4WncaP_;zM zqYSYT_jhV1)_CG_tn{f=j{?f^R^D2>=Gz$BZl?Im0U2O~8TzU 1Hd`wb~%aVKHIcyg+ 3D-U4I$bZTb(0k9OiQ(?tmpR6R6n~p zd0~7Ql?SIk$00@4aX~MGgyaDJOj(aqKyi^S+lds;7P@OkMkmN9wd#d1IXN)+0$DMC z+reZmpjMkp#n-#-(ykZAgnC={skAXus3OB0U1b;3LBRP8JTC;B%v@*X&!*cyD7MML zZeJ{PNM2P8Os#Axs_R(|kG>JLNjvuH71*bQ{4T6Js5tFT=!O(S*iidXG15u{O5-hl zMC#J%Z7-B<1 tT$9bB`zii6*}euF2&Eg8X=hl=OuNxpNG$iF#Mae64Tdzm zZ=nS&db90kP)iY_*pxW5e;TQUi98@NZ>W(=I=t})omC^?nb9+B^pW9Ak?HHggRQO9 z`O$OLg18(r O^(RkGz_;wG`2cXzYW(|!bU&vrV+1K*I$ F>6jvD|5%-W4+Gx_0(TCjH}a$n$!LIaKA!v^jR57 vG*(WRoTTfT`%J+5=x69i2dZ#WKVcUs=$_B#r6WBun(ibGf#5|ua`+A>ka0~4T zqq%<9Ig)|;lwXmW;h}VWZEtY{;Q}7?4q2Ckt704$Qrn%=49!U{wU4*UW!{pWnbx4% zl9 -C><#KZqe^b3FIShP;|F1u!MYeo>@e E4PA^DQ8BsA9dsT=DClO|cw3FD0k0wM&7;pKX#|9bpQ3{*%OpQj#1s>r2$u z>U%wm+nenwqjd3lDad`+bAe^$qQsY{8sF=p#?tveSAHeuQeIPp<}R+<^dArfdt|3M zmz$O*=}T*sA>sTs%gqbhXLgt2Mdwz=cRhK&^vigtO&6Hx+YMVavM9Yf{2hk&@%|`n zME$_|7EO< 0ZJp`_ZKudpSI_G#Iwh(P*3#Yrs3n923y4>NGHe z4aOMdoo{Ex4=L!zvT90=NJ9Aywr^e7hs1`wyyDd$NsI6M3j#s>SEGK?KRo s z-T6}5VynH9Kw6JLCtZ2-1H{fWds7YX6Ub!cRQA_~eEIC>t$}TuldxUMGax5UMXF=% z{MO@sSH6Mo@?gVI;vP1L*O~Ohar=Eg2ka)hzQH{-^b;X;JOAgTgKb55adh+63S?or zk*CuvWC7vv%LMfoNq)a=(R_($G)=i3gR(ocnV3(f*I3?pjjds1N27y&E2;|)`rRp0 zSoC9IOu(-d+`|2QH>+91J6h|a )lWS=nY+1J^F?X_q&4z|M?uEy{!dq)e$D;P~m(lE9Z=;DtI=Pn h*3F@iy2{Fu87CAE$#L4gD 0~IFb8Wx=qq$NN1ly>ExITdXU0{+Dn04plAh_jw`W6ZO-@S>LeR$zI-24?5wDA zlSA&)^)4VKs+<)SkA?vJdU~)f0NCw1?piZAr?%2LFKbm6MA@zicW>TswMt=O7#VS> zp6c_ jrn}+g>|&<}5-gjOmzrL&{%-DYk3Chn#}RZh9MAt_ z;JownEa)e1+-kM&GOjln7&Xvr);z|p+h2D(`NEz!_WtV4y?g>D*wyuE$!()7>^wz4 zYs*(qF_EGe7N8}F#BSS~akfkJ!;aEToZLFsHMgoB8q^4aF(vF;jYUv N5obCN1w vR9-0cD&T6G;#rmRF_}vtfhZfdB z99qtK=#ib@sDLMs9@?Dkb{}1~b9Z=c0HKat6gz(cUvR&lSWf)2k$7i1FCpDs$a%YW z{0DiLKl)=i0)Q9CPE1%!OZL(;&2=s5R3enuCAKw>66`K-@wi%{4gExy_y1}!twALZ zM|oRnW mOKWFYL_x|n7zJll?OYg~LR z1Z)MeyiaHgCy|DHVCR|0G?adusZW u zZpu^9#4C)1xPqh`Cm-sGVzWV-h_FyrY;T%wz3UYw^~o+7e4#=#DR({~gBdgDC`IIU zFPD+%u)Ah9rn&OeM~7bN=FmdfB2Ll<*Vd+wVj;eNQ*$w<>^S??9G~3kiwjF4uG-hQ z>4IcgTyhhQZS|Qb3++5G{s$2Hnqhh@eU|6bU xvYEJ1FE6>R+gudhKd&){Yl#kFuoTq+pGC_O)bf5NF8+`Kv zzb!&60(a_ViPe0G)5nPok!uvLd|k!0ka@FOle%C8Or4-5lC9@S3VTu%7o{~{5z!Hn z@vyfce%vd{$#YyF8|_Dq{V*wY)LqkdXnzf?&Uiom6^J#}yC)pmqO~5iZ&VJ(LGzy) z(?0KHFBnR @jXoI}zV@Ey zHj=R|A}f+F=kEhr4=}**@pd=6`P1^6(8?v8uDEYDG1byNE|a30sjmZz>b~&pAuqhS z!;$+*89b4wrEp@#AB!D6$7_!Y(LdtGCT))Xc-M;jilK%{mcA&T`TfSkp_U@wzWqpA zY&>jO{DYBv+;sM9bkS!QF`8Sdwk?tavxbb>InRf8%9L)*)URjRMb3&!uTa~BZv8qf zFP9dX3c|r5= `s}=NjTRW@?*%}nD>%MzrE2NuT@&49I zTbrxDph7-*mPiibd5L83dnmQI?d;?t0kYlsW+~WLf#B^6+rr%zUDdhnVJ9y{|6w9y zwj7z~aVDgWcJ^9l9QOn+U7asVyoQ=>fa@^Y>9iLnInTsKdyPAcv?sArn^|(*H8eby z(62~T&2+u|BFvIhv>uI{DRr5mHOFJ;wgTRv^RzH9Fl%3 GUk3y}h){_Znrf&{! zsZSY=>TK4TK4uwJ!3f?DvW#A>C)DI8U@P_ErVE`%hgeLA*KEg1PVxu)GmLNedU~IX zR98pus#p~wu|aq3Kj)h=LPM{H`wTpq`VD#U^oC2zddloNpMnC^d?SZd<1dpXz$9n$ zRxwW(Wi_5=E>KC<6P%&95AJCrAK>Wo(PXi=Uo(i!gv$~16<%yrnJT=#5yH-HgsH)3 zy0z(jU+G4kFp%H=7G<0!uA87$3{d$YVEY7vhFn!f=JV-0J@n-IDgF`|lv9z`<)ucR z-dc(tniL(EKoolO`x7}a2jAu0i{O~H2)xnGyZK|MzJ{JSW^@D^s4sNIZYIo$GpENg zl79TA5v#Gsbx3nXp(pDp*YutnE@KL6YCl0zX?$0Lkf1Mu9zJOF#6_W}#hlXAU}xx6 zRSTw>y8_P5t1sptG9{%*M>$6M%uNa+5_HzbSsI`pI=r;HJ+r1m5}OjqpUL~y6%6H( zh9?kmy_~SqHuc2@FRF__xm7;DJg+MsVi;xb#%H*%h<`*k&-p;n<%PaxIbz_wu3yFx zO7cvemUkt;r$CB>3;o7?@}qtli`ClQ&yUhlTa+Lm%jC{#4d2z)JOyzOh}Qm5;PQJ< zTw77y-a>PqWRb{^ 85%cehZy#LA$X`*RIbluS)Ri>|D=3^Yo;9lKnv0Dyn;I3srg2fYjB) z+X&)#O9c&|WK!JMJNLXN%M)5-S9p*4G?4I?5<6Z0;f(($$0EF?Za=2+RD=qV%MB$3 z(I2mlQ!{aIQ^vhpZNnK;gB m$n``Z= zQqZAE7i;>-!%u{d(53y&6rUnMp|QOU5W;e_eT>|2Q&4;P*TkGGjhE%(tOr3EkUh6q z%W?BJy3OZ6s=-=q7s(mjl?vqriFyCW59=>yU2_tXODq)G3Y7L;BdSSR(!MWBU3L%P z_vR$3t0#TpV`OBcvRx4MC!p{1lWMl2;VdEx-(s2J0D>r+1KWAfZr~4>r@!z)6v%G! zi=J1XEn$h|G=gJF4 =foGJr~bShcGOoT|T=uvP~m>^#h{}*C9eM45}=L `?=~(%eOZ4yIXA~5a`A& z&HHH;!iZlXl6df#r|P}_{7b(XP9z7pCEJB9$O?Oan^+2e4>z1VUGK~FtW%lcZ0^zn z#6#9bTcLeBCu&s-pYZuC%~<^)WREqGQ1_3iCY3CN1ot5ma32QV%@f~TKAV{!z@ahv zQX{YP$1nrxZm8>}JdENLn_?9Y^VxzVzrtKO;ZYAFvl{zNj^WQ?aB+f$X?9$(*I4o< z@^mTw&C#0{Om6)i7|wIYNJ6~e2opo^(xrpfih2(~89cW80{owdE(opZ2{AJtF4xf& z5i;Yq7Q99$Abu(G^n?_GV2NvC0?xM~!(yA%T{EO%&cb*z#~iG}$Mpe-#yxTRGTgh< z8^>o9jSMZ+cs%pd`DHJ+%Gq`oX_Obh0% zg{homNP}cI#|k%s@WP4Rv8HZnQghkM&G&`~06@RO-yPMu%a^n|w{7XBR1|FK-WFrv z;c<6=bqQV$zzDs6z!!PX$1msK%_Zt&AQ8-(tQL8VI8?*5yyEVD_I#D!5M*@_>vtUP z$u^+=c2S=mR4bp?^rv1t*c3|x?MRa2xS4F!!OaJfw (!ti0hNnfa$KAuO74VWDi&>(u~hMoIueVve 9Iig=PT8t*(6d$jixQ$6SW(11O=W)O%J6gf-yoD z&H6y)6W78rV^94vE*yn=(O9%VEu|}`D@Fy=*tfbRbwomh6im#0grt&p+MEKYr9!7@ zBd8`{O!v3$hMp=zkPu}o3OpICD)(Hgr3yRg3dB{8HZqLizm8)cYMdF)*5)sjf(| qo)8iOf~W3(i0&fCMG7Z zKu>;!Zg&178;9R~({YZFk5X0%9bN4y9w#EZL`vK}{MJWfkRWJ~@6DWbwsETlF}1 zV*zcouhHX
u5PKk z$oposgN@(B@oi|Jrh4f)?LeoX&O${uiq0eYw{XJ-Q4yYOB5D7W1ns%S?*Sq$Nal)! z&qjT|XPs8q;p9p$!;tBFXSd&^e?KBCxtJk}!~}VN{*997t!8G`MQ0r$ZR1kd=g@Fj zFwAndRJCN0Ymb+{`Zkmw<~3f>ex)>q5H)-N1BwF4G!?gk#s>~B$Fi%aBkmaN2uO4N z={EOqmXXNsav_+veG}GQjyuoQY+-ms{$v+#UciJu6yk!!;EeEH-pAR$*`Gf5zxsA# zN%Z>Ri^Hft7X)*kE3l8onP*-cHK7mDbK=qn`VG!`VhL5W-x=2P2Orc9w4MEU(=#vK zBLhoeVx*${@PInM4eD5i3aWWE0l>v^P5T!iM90-h%kI|Kcb@~a)@(p;hS<>jC1DH) z!qVy6J=6rpkSL<|74gPLtOMBo(Qcl5hS$i@Q2WmySvJ^D^!96P|DTDxB+piEX~Kg; z-Bo!BdScel2Zz4mEd>(h(l7`lnc;xQPq64-Wf6mn`_McW=w4E?+!=wS(U-?Qc*>SQ zn{|>QG=$Q}54h1=a{RHSTi@WMM2=y}yc6q+$}*--RaC=I7r^WZGlVYx@OCduE?;a! zC&LIeNju@P&|PD{Tz-jVNuIrMH8g7j*&9_0pEXRHwacE1x0_948&&4ri*$#SovcAV z{god2d2Efgt*nR3PyxlC%C9c#7Pt^rB^#|+Zdzm D*(0Y(I)mb0$L}s zMKXF+5=z9Y_+7GeZ?hvq**WIHi$yg@y*S&6#gC3~p5-cx<0Ci4vT|XcDf-NBA=q-^ zM{IY0q6j KrPX%u1Uo)hn8B@8p^;KJ%1-YM7Uf%B0BIKp=`tTFE~}38pSgBC~6enx8>lW!u3V zVbGM8ao?9nE{X!kPo#WHRX^DUI>|)?30cQ{f8Xo $KLNoCy}?!(B7LIl!Ai2KF0J-yqFd`Z85ow z6Ht6?d-oD6SL!4F{W#Xs8Sr%L-7s4EXfXqDOpY-5at{de?jzFrKP+h%Yeg>HHQ!Vj zYOwix371LS`7AA@dITNRMftb?`MB(4CtcHOm+g^*^KpB3-a>B=()4J$d6FuSO5a zkVl?!&?4gQFw=Va6@_k%mQ{!J6(mR2!%&BHG*1r?%<{PdFW7O>4{aLIaV3n#=x(~U zdyddO*`t6G^a%9}8}sR5iFmSJP+0pM8$(0?JsDBE`#{g|< qcM 5joX$!x7hP`pt9Bj_pH*b~{I+7TRb(qlD> zNG(bOA$&_RlX*0yhl`Z--slAs;mBGAmpoOe4o5CBJ@>_KOStgcxgfE%4-K4MqdqgutWy~UU~ yw?y@vUn#`w@%sVOs`!VI@%dv!J6t z^QyJ+a2GXF75cC5@>8tKn+7q#5bMf8vAsH3sN6e;s{y1T;V0uoML-_&dF0u z7&`2HtSrNOrrt{14W{*P??#XJKx=>YgY_Fa(S<%5CpG6Wc`I?epkHJ}@Y3unwTzgO z;G91!%$I6g(m%0%(Lf9-FV_u3HPaIvgiixl%ja2)c2%NXG^LbI5cd=3!`%B12iB9J zA{PVRLnyqgGn&CiKi#>BiD~8fIick~kc_joCtAVzC1ejkH;3v@KhKgf3N*vv>?`B6 z#AKd4VuYJ${?V1SS_JMr2#Hpt#l6avgY*hkXVh>Qo0dyMi)1~yN%lfJoUanjua<|-b5qylq!BgJ^DX8q6md)wF_(#Dsnbv?UhwCx z?sdHMD?*cunQ}WAQK$Z&G>gs~?pq%OpxXS!5h{O9%`~p4{2lS>gO=T0MVVDgXIQcp zl1i)CP>hSOA(;0KTClMv$jJ#t_0^xxg2kys%nAui?J-x5D{a+}%PilTI$*YR9!0mm zz;lR_BM5tmf10E(kfs}T9&{d$LFj)zd79gnAUK=Mz<7)c%y z; y7=hL}g+l zjFFlHUla`Iz^lx?#7ayKW{M5=c4EuTWM6&6kz^3mge>=ZL!g79c -$hov`~E+e ZF>M!#CUIa0&{VXlN*%$(J zE*q=f&&|HLr|QKa+<)=dZ!^~2ZD|rmfG&R0aA=s+=*T2H NKHTO7uv}9gfLCsJ Ykj&jd5t9>0EZl2b6)D>(94nvhJJK(1qqUcRYo50J|M$=Nf1ifsy)Nh zAa`U>cJR5sXP%PzP?nw!?@(TrG5gN4WYu-6JPqdH5H3ujeOs@;R|=`Ci&j=o_I0nR z JQgltzwtt3OGvaOtd%c7Z|NM9=DPGL?m&mqa&P zr=mnQ3Szu6uoYbN0g?z&7zffl%ipu+TI=#r4IASDHUm~Mx|rf(-?p|NKcqA!@#`tI zzfZPIJxocipM+EHq;0s%RKb{h!N(7=ST&qpzu;tzx?io!!-EU{A|xSj+aTx@?pa=! zo1yG&MtSJt1#F}9bbsZs5pxF;s+W5#kGTiHW&8q3H=(OmXCZWpQurk;zhjdn$7ARp zhsCMm{Lo-_XGG(2bfU8;1nRjQCy5KOq6IuQ{<6U;js;Z6EG6d`e+&B)u^I>kplFB9 zE`x@7;oJ4E`%Kx^CBYoGZZ*Zm!`0zaP%1%LpK?3(M4sG6uOrt_&1yil4OPo9%x2nL z{l& x>2(KXH65k-6F1 j||{aqjz<4tFNuy91jW#y1cxU$Wpy`cn2si zj3i-!T}%7>`y(SG#@v+n4}kb{JUqN>h<*y&XdhR8sP %y)^ zEbkZW4eU#ad{}fcO}XhE&XN{rabSsZiS&Fgi9XQr1DZW6=^7iRqq6m^rZ@VBMftpC zzBcoBV0`#)61CU&w8t+ReM7a?1K+Wc^=0OMFML@~Rbu=O*7$-RkyH37h>Lh^jImTF z( wt>NVc zeYEcS^a8d(h6Wfsp0lvLY`WeGYz-$#SZzatyPF$2D-fm6$+_R9; ?X~<_B8hwd;U @*F34;+nlhUNgiAhM z>|oa3L22rbPR0hEc-HOz^_9fLn~r5H0MPb*=q|pm3X1fLW|GLh+5kGl0zY^*=0o~t z*5$_JFV>R>)ymXzd?cfV|Fq^KGBXgfJE7x|1GN+8>YY3+XO{{)spT7Kw%woNxh)}O ze~iS(voL7~E@$P|ydS3{W|_s%&%zIJJjgxBJTS%3!?&byaJnV?2u=5Kmhwj>cS-YN z-<=!6-3YeE4Q185C9)QvOO8uvpR!8<%GtI)s5I#c52wj{4>0*xAOpbKx@dF*3@{iN zeBvP{tiAzh%vFG#y)kA9@aPLRd}#c3o1gs0^Of@k-1nW!^o-fQ3HiBN$x}q=-&p&l z!OIRpbL`rXGJJBB@cwu?d1 i{H+X{WE2T8fy5Oa1PlfPRBmW!IC6FZ*#EY<7*ixVy&)rJ7*<<6lZ68p z=v$jmb~{?L48Sxb?q{b_G9`{E(d0cqAa*h{x@t7)XRq)O^$GdIz(?#WJT}cv%$jjU za^`e~v)~}xYZTq=rEl-Eph!y?Bk=(5bY4I{TxrVln%_r6W0pNnT|>iR#W9o?lJUs0 zj(e=&H3ZVK&>vt8ko^yJb@g00YMbi<0J5tCbD3Jtw?(2Pn+2~%ee<=SYfa(_1lgJG z1Xe|FaigQi16^5A!s{Wu9$up5*N3I-$%4euvd~E0v849uxYL!-v@XHGO9Fx+aEG5F zTA2o}kSkukM$%;1+dV|Z5XiseN_MR)U`nZk@%rip{&;tGeUlJsco18{WwNLkkEJ~R zrQ@kT?9A=2|KLa4FYK?cmB+6jFM+vXWQcp$V74ZUO{hOCIlrl!O3%-F#;Y&iY`fOn z !Zy=+9tUxoY5P3GHm)XIuCWfEYx-5KVahgXoiDtcq!hH& zIfe+>;#|YD)=lW~U1!*`))4 R+M4VW)epPc*`LtIr>~K^}kKSiX9hoO*A#O5E4GjMdXw z%c}EdhvqB7lZaLgsI-TV5TzWZiA?F0hQ>(;yB&0h9`vGozf5U9G@j<>@p`UuRr< WR!=;*;g0H9&TCS+wHXJ($@2K~d*Ti?SnKkIz^ F`leUXh?|N_ z@L6J^fvhG#cDE)OWzOTMY~_*uN{TX9^_0`SI@`7SvP&ZCt7+ywYw850D$8->S6hgk zxf(llo%20q$>O0AEwu84BQDCGxh_El<=nyAqz6=%ghpnt*D?MY8pDF$RE=@k)X(M~hufWgrG^*v&t)R#~i5QA_1iYyhe0V|6V zWrlaqTMIX@zyh53%cG2kogZqqY*DEu$b|*3vDvGx@FA};^!*&?=$V-Yit?4(@^AA( zA0xMku2CWlt^#;&hU`m?nj;%MzSY*{xm(_6!tP$(P9zG9d$P;y^2u3FeOgpwQgOw1 z@Tmqk>&4lncM2x#-os$wOmfT)k;g^2L^qi2Na*c3T`!IrzYn`xaZPeI(*!&&3{+9u ze%lBn24EU^rs!7$H$nkW@>g%+hO+NtZc_sB$Lh~>=}r`ssDr-}A6;--SK!xaTr;1F z^y!H0e6R**IB*f{>p^X Laot9%WuBaYvWUghF<96(=8(;X1n=@SG_m zY3Qfjhzl4k1DCYX0Kc4Pg~Z;;<>?3Hsow1h!R199&x@6c^7>{IpJ<}rj2<`rl!4Ay zMTxZ4ok;+?{oAfO3-{_0T-m}7Rk)5dFkyV?S}_#ijJr(G7X!xQS~cOdiX8kk`FSbo zW3#B;QY~_8HcLp!UJ#-lrMM)Z0_@?7z7(Nn*G$eWnol$c3d-&4W5)Nq?JNr f zfXm}tr@qZL@1U9-94a6x^9}I457SL$e0G_h$OW1H&&^I2a}5n}x;}tF#c4LKl?ltE z!@_aMu)?`zp`Ax2G|5g}YmY2xY&j3e-+cNrwA7&5*Zk$sgP5~J-ZH{zRH2)D?Gumn z7jp!M-kRvayC>3>q^jbyE&JDWX2k!@PJsQ7al1vv0>_1*knWgGZh-}4oJ*(`O}-J+ zElId+lrcTYL}(StoZbHEM|x-sn&M~HK(pu S_rAQsBh1` zyK>S!PoMvv{xT(8E*fy=4X7`Mj3 Sd5REl6?khBXyGCv51~9#%`xdh(!;<-H?)c zlGwp*eC>I~d;G|*;gzG@X_2inLDMR$<)DwRXLhP83CCEs8wLfD(HY$DJa4u(g-;UN zU}T8vut1rYkMEHez0_3$;TLyLE=XuK>KcZvll{n6;5;Yx`o1$y%(OHInD~pPJnix> zbk-IM)}yM2>~wvOHiM~Gzgx?jCI2I(c+x-auE6Pk^pGhQhvk4aSL)Ylh*OE+i >mZqK$v+TPm6$`bmovw-2hu{})s_T#HXumFT5wNbmg*T~l9ZXV~lU%m~+NF zf2{)%kwbeB6&x0I8OS-5z2u=-p?zyXWYid#ke&p^sVJ1U35V9dEW;Q;(@>oaBAv zq;9Ex46@M*k6#xXi=Sqkw*elkV6WiXZJwrSycG<8ML7rTf1rdw%PgXMKnW%FZ{k^0 z_1B3{8unh@i5a~<&bM1{R*CV~3Xl)bYEQc;l{{rVvi9k`IK=VA{~fdHlKVVMVx@>L z(==ww^K-v9sS$TSuF(qW^9$Hv=#}ghbz|OuH$e*D;XINt%BW~qNU!OxWud}oQB5QL zUe>&yuvLQG4@pL=!~7VzC~n!zsC0(9KIc7T+U6QhwAF>3Mj~tiQknjkoUhcN>7QOe zlXM5{v?fxgiVOlG=#b#lD)%#6sfvDAg0G|6xG&54pFX4Gbx`IQA#%|?s2VDVDyJXq zukiNfh67G8ply!#m4(>8LU+5qzFiG5&2Y?x8phsK=7rx-TG7*u$E~@3g7p_nqbAbg zjFNRlghVby+U7zZG^(dKT)mwBJ^m~&vLVpJ!eDiVg7M9-PIRS0^(k=*=n4f@pmzPM zn?LMhaVqMk>+M%pKQmYFX8r_hT9u^SU+h*<+ayg+JY5hr85Ts9H+@zgCv}k<)oaPt zEZ;Jt2vR%A{C)xw&xV3K7xgagyy{1j3i^*f`5I5H> zuIY xdGHjMJ#C@tZDry(zz3mto z7HZYrxR$6_-n?BL9*%1^GLj)` hbA zFHVn@opp{m!HVGS4?Fe!{H_$kJd#wdQ|U7&`?_{5qH~!9;LJ>M;IV3C9$g1h=vV8? zC0ukqGvn(FOffCliGq&LvO5+N_3WF@ru682FP12bywyg%-S_8&{qAm(480zLcmcm~ zw(S#H*dO(2@F$p;6|wX&ojpk?(?9^ZOAuf@OXy`8AXpdC2i~kn*uMB~WO9qIms084 zAcOHfIJP{#8ZH21$t-L=Q4#BodW(Z4D{E>iOm*H1!sj6Nx4aIi(#ALB5(~%ggZJdJ zoru2*+q5czax6%|8_{NWCn0e@9!U<>s8!{G(vXV&WKTg(Bgl+&B6a1Y#OImWH9=j* zMGxFk%VBmfkIllz5m@Jk0cecT=ZlI5#A ~EV~AKMU(@7gPR78kMDJhD2HVARi#+iR7kj~&w}ziB*VHw7-i0jP4&sN zO1okw-7`4bz4gdrQDf=MWLaiy;$uQZ=0=8V=6QOJ7>F}@>+hdmQWn0Gu;QKyzzQ-F zcemPc0h%nZNQoaz)ie@oX5g{hYaKJqtnx17#`R4?dKC7h{^iAdMkHJ_)9EB!xw^Mr zcbpxpq60JCYe|4OSW$9l+J9Hv1rreyaFUagbHOgMGf|l?Bf`KNuUW+J-`_f>04Yh9 zWV`W=b(df8gdctxE|XDlDQz^I45A^2l^zVsPKnHUSb?wj{r7-}8j|fOCEGwaAPiJw zjIp78b2jkdP~znXqvO^aXFjUesyJtQYkq!2 gp+lhF zbMT6!FveyQb^_QH`YE`M&hhdI9Mx0k@FaKDOZ{& zi+-^o%Jbx_+LP9XcZPR+bhnS~#&AF~d@_9chvbv3*nm~ #ASy|Be@U|@QrtUibNtPycgkPNre#Ek*S%cIs8qs}8ao(I zNEUEEa}~H;lOOS6jowhpBFazcoPY|Zpm6h{e1$lqw?8F}6pE~Mna3PugQ{<)TFs(~ zL5##69A_O2$nTo z;-3sJf94;Yu1X4(m}FyPyMo{V+w(PGZ$!5r(zq3r9;U0JH_Kis{9`9%Q9jx`*N-lt z`-Jmbqgomu (ml-D{VY^gq%}s#lq@b2@o91@7M{- zewvLYWnH9QuTCp52a~S<{8XeFWvhL{cNoR2M7^dxRe80{%L$ rRrZmVqbN 3B`X232iUV_oH`;}_Da!UBh{8+q z;%WWPvzaO8dz9((i6}=xkoW$lH3;%7A->V}S395sc-iF@>Hv}Dq&9M+9Si(}ky4Ly zI1Qem883*9Z!1@n>a=)|r;@p=fxS#eHa)>-TjX_M|NKdc7}%~gR5?&jM$BS?N?27j zFvu~&$4KvmO=3QkDCD0{h**uP^E0Wy+h^B7;=kgehv(ePJ2GJyI #m-{XBc=;@v )%GtyE<5(5I0!mpL}ikENy=E6F?hWsQv&MDp^V zbDO46+b)r^R~vx6Jv%#Ff@WFtzC4<~LI@%>4IF?XUN(vSRO7XfZLiZO51kcq*2Q}o zbpPSQ2~m?l>oZ*y@GcmSEf+CFR>OMUC=pf81l y3Egbo%Pr{+i<;=&uY5UNN=X%j+^EY#-{Xo z+Qa|83=H~N2>532yKDUQwy%1@bqHX49p4&lfO9NiSXfx~S4io;_Wx;{k9g&Kcj}*3 zC7U3P6W0vpLm#A`3fj2rZ5YcydzH*OlPgpK1Mu7DKtceStZ-xs*Z jnqvW2&hQbH$MF9rX zc}_BBgt~@2{mYHGqQc%bJWF}1gwIf}k1z9^;`H4Awg%+E=-?pf8yeHy!QbJmc*VtX zIb)7OL%`&%9LEvXMa8p!b$UjsAxd CzK+mfQoBTkd>DAoIx#=LcjN&W zD~pT0vO**R+wjXms~69&&+Z|>M%;=&W@Hrk(%jN=mUMeZhXc4TiHXl@PVKxumN^Xi z4R_cV4{1?E$+ZBjxjAw;1L{AVhCmTYZSACx_)u*LJ6Kv4+!X*(K76dQjIkplDl6t$ ze^GNND}lIeosR&Cw%Kgc7nM=(jL2K+JXmkw>vxqyyKRcE<00xA1eoX`pt{cWUwhf- z+)b|I04aYC+JD`Dz3@rHI{9FEx08BFbu!M m8SoDQaBE8g@Gua9Q86o!_CFZ$_=U(v3~m?6m(4UM;Jv z BQ5rXVjbujXF^t}ZEw+&JO6WMUlPh<*E<<`F;# zJ8Bh%2*6nNZ*#m7V87t?U%(j^o?S|hl1%o89K#D^fFVzgkAeOC+VO+#8uf2&-!Rbw zfC;)eKm8{_wNe7hMuy;tsC)(A@l(gQe)?w8X0!m)_!JaxA1-x8L_`3$`OVL7`PO4r zwQbk{iq<*w5GyXey1o1I1zqm%xe5SlayaW0|A{MSon1mc#37i#JBr}1+RLV)0bIb7 z*ZtpE05 _@Up6b9+{`M?VfCR?sNX qv&e1J}^_-1#gUjx(3Mtg&^bmY-$SW#RYo_%`m z80wW&93@?m0szPStx*K{7y(`f?Bl?tt+;{SKi|I{ul t}qU$Yo<69Ps4Ab#pB2t;SJ~iEy`I>RaKRh?3Fxpy}@4z0<6=j{ndsk zP?9M;n>Cyyq6cv1+1c&YT8|$eA74QtWuR{pE=BzEmYOH(Ev{+>yIPw6FHXi}$x&Ms zaz;XVdz*E{`3 y=7=JK24()cl|~3=Eh%-qwpdb*>J@W%}&i!-@ncpPjk? zFA7+rN?Yk*)i@Wr-k$WY^BU&Ll*YQKPEj%LkCqn1OnJB!XdoBpOK|qkG8eqSR&tK| zzeOY%B1(#jV-NEQy`P|w5)+R}ePgEJy&r1^1XG5?>(?D>^v~3${xs|>$6ZqGY?2m* zdyixHS^Qt`4=*2&^C{og$UocTe`WMf%*X4UHt=|5=J`z_SFKVXNAzf~Dhe|wi#t^0 z!Rq;4>(2mP`rmyUA2+ ~wiT|q$@Zah7HT-gzF~<;~BC21u zI@%R$-(-Z@N|1&^$S%w4`!4~1Z>riO7lA{5uI)A_;0>|)K*~wAEIEOu4|qOp?;eY3 zQ0?!>W9>bNI^ee&=Q4!yO5KSAE)=Q#k81zpzt6R_3scYs|2HauqS@a)*{BTQ@)@A@ z+A+lVf5#xeM*&PePvldDlK&fhL^PYD9r$)=v;?eyL4j-do_L`)>hA~tn$@@e;*jDI z+V^jOvA&WZSBcID{{Po|{ogwNP7|2u(~kci<6C_@M4f{|oijA=JD-ihr|hgVy;kP6 z)IC3x)tgWD48n74I-u+p(IAiv8lnICnd0N}*$3X2>jf|a0;%yI#aGOB1P%hV=0f3r z2KvdF$`w @BteU@>cewH|hW@ zV8h7sAiOy4)pWMm;4Ea{s3z~G<(xCq9?$x`tnCaEV@2`us)Om{;0;7z&guFJU;bz< zE$gZtvIrh)yo0gS*9O(UY_nL2+|kr`@p_Q Lo=$gVUNBQ0y)-}Ofw^4mX`GUWE0L6chLURLF< z a!$$ zR+MN{u>d{PxE?B2q)@g)%to$rfPP;L@Rp!1230P%OWQ}`SVjhQD0cP%5-<=f)KsB@ zp p>!=WQP}AB+5+%SW>@?*8p>Sy&uD1f()2 z1Plyhub(+=>Lsc|_cO6A1a11_)7-Rtr{dp177$dDS6eOxa&S0*_SH?(M|X*)CfO`; zYiH%IV8U%Qb)&oA(n(TySWBw)u~v6G$7z{XpnNXfI%8+&(T-l`_!!&^T>pBGn7ie> z;h)7#aE?@o?NCF>|1CnK`Y+s&juKoglW?_Jbl>?>h&)t5l;Xt^+uc%A&w`{fd{H{) zvTPu-;fs!WUMl#@*1LvbskJ#h%^{kxR^R>}3dAgbYvqk;4s} ^J}&K>`IH1S5YENf zZbSJ`9`0KYkQATx`R_jnqn9nqBjh#Vm22(|6%bX7)B|6)-ctuH`lbk=X2gF@o(jNh z5-CDvsA<=jeYq9-smTn8@{Nj}9W}g_2-E-1$w-=M^GCf}->$4QG!VfK9pXKdHSRtC zd!MB*+ld5{(6a)9Fr5UXHV?1Fley#0;<|pU8ZG@csWvP<1cX|k;YXSIq_SxJ%d0U$ zCGwVz4ZjAbspT!EvEH+Wdw8)s-w(U_k7=XLzT3IuqwLo^%@)dK SN7&!L67cdb5f04^SIvSa)#da0to-paGiB#}G8?4+0 z UCu-fJz^ws z#Q&fC33bN%yO+2VL(t=<;S!18vxbe={V&`z;h*92K%w%9l?r`Fw|CA^tJ`Ll?b-_r zjWcOHep_p`jmKE&ElP6b*Hc`#3&mc2R9i~=j-vx9=2FHtrD=YKZuc>Q>USFnW^Uz1 zXQgTnOYOWbd_k-k&5ONYi)cXtDFPvRz{~kx6Al3)WwH{6TTugAzLmv>U_HNU_o ZUr?kB23>phNn)-|tO$Ur5&Tdi?rt);V zdI{(;)g(OE-`PVc6b9pd+HFlV!7;OLdNfMho@ZOAt7f*=mH(A4^M_wkRtUF-{)h0a zj7=96KjX*^>wBAZbs_{YS7H<`7HU1#S$9O9^{Qv*dadg$wK-t=GbGg9l~rFhX{Dy6 zj_)-UdQIKD8$PhJ#%J2#=W_tP_dM1qP)F=IO&)J=tFeJ(>ed_JrCzoM200HvHa}W1 zqprV5U6#t$4BG3%V0m`)-w*gkjl74G*fHq-C$YzjJKK{A559;_A@3L2J60p>2~ eW o=@@Bs4 AiOW=^aD}Js={2NRcjG zdI_P2Py>p9^cn)8Nryl{N`eFeXQS_b-1q%(?-+NC^X;7dCCMOUueJ7EbN%LTwm3k9 zM9{P%h{l~`M^Tg?v%Uf7bh}G7%bdPE=+My*I+J(V%zF;qvNbtxY+wn-2docf@dqRe z_h6@LchUzvNa^d}KTT^2%YXLe7E8z_9b(us-41amr+CoLHcBMEDHwlP<5U5GoZPyF zo&K^=KS=$pbW^sm#AP`gzHkIrT1cZBpe51BvE1*$x$8jUK(3@3{}J$}@Qcw&U?!z% zYWVk5==lRC;SUG0^=@2JOsX>3>xe;3&J+7Vf%?~>1vNVn##-s(K3+T#ZKOQD^6PqEpOSUyf}=*f-17VN2B%9p0?u!tVxl@A zJr)e@;Z00b*+RDLTu1@@Udn2Bz3fXXwf`)ExQYt )KEZHVC&)kKBNre#Z!e_Hkz{%#2ZAQR8GaPQEnq_Uo&=h_`Q8j6~jLz*|wJ za@;gl7N$#&N*-?Ck>91t+K;-RXRv6P>r}OMbpEZ!m!htL(I%RqM=TExH%+)E(dZ1M zUlAVfdkF}`BitLjDZm&92sImP>+xD cAdXr(vFw#LNIPxQ^w($at*GK~@c;l{m- zIEVk2%u-I!oF~@dSDK-=>Kq7Fa3gvu|H3x7xHiTWH+L`pXFCIUXz_o7&A(9f@7f5d z{56FB$}%nUU)2Bi$&udw? xNpXC34V%z_DJO7iQOA6xJ|2@VP zySw<$J>7@@BgXZNf$Yy9M)Ls0IN_I3d{JFE{Ocb>6{jmi8>>{1r&9`ra{qG+;7 _g{aP!-@Z>Oihxx?$Y{c{SyCitN6WbsuaV7k`T2?9Zn;a`DT z>%e!;7^waVDkFc^6U;|E9uJg-m&S1O@Hh;qD AJaG2kW>XH2K!H{BvS$XJsTc93;3G1!}v`5uIAj-B#?q{kalQK?uvgo9#W e%`@nINsD48-VLgp551^~W{(a9(WA(NTafWRnC+xdSfJ3kO5Ep02L0*rT28Z6GX? zQ6|_wlsu9w(;d5?5curM6;`qb&Q}&1_!wqs<7IkCzdGA5{d0Bts}<45uJ4RtJnsjWAtgb1;fV=+?)JTNu1ir0=o7?t z^vf-|(`qe`shJ)U^EKrUya7?7W;G=(@!{{52?{Kho?ZFy)xINp*QwgdE!?p(XJxQO z<$SE~8S2j%WGjBL^gTB=PP3fvZ>a|;1{>hvR##U8{1ycAv3=3?&eG6e?cx_p!iQe_ z#yuh3m91TCT*?5~eoAU6S&tX}O`bvdGIBZ6D`M2$!}Dp3e#=WhJq&y4?tdstQlke? ziYP8sb4G>23IssJ*OjeadJT5kUH=KDg%-XQ;8k)G9DAQ+GP2@hI1 <$9g);>j+`BSxnCH2q+HrZ{BoH$+mijS(NMKmDy zJ?}?5oxf$?4P9&!AF*n+1U3@F0-g{oZVohYQb-@Q!c33JvD()z3B%Fi3M|b`QL2}2 z^}JEAZ~;ctsdLRN=-0Buv32B0fa;gUe;;~8r>$q8jxi>|M}kj=lQvZf`zgxb|LlIy z723|a;Cb~Lg73Z+JyUD36k*xp)FpF!VjKCjVejy}hIZ_C&cUe}?(ul&K)fq!^pl?W zD-F$a58nS&XWCowQ2CH;;a{n0DXsOLB-K8Dhzh6)@Xzp0tY{IN&Z}N({-sa53V+Z) z_>WaN>SS=Pzwk#7(VG&~+s fh(&xJXa^(Hs4nEv(2?B-@{cl1@6AF>9>rSwQ8oCP_$5q*3%&=M zpT{lD;gi*I*Govllbuj6DX=1ui73Y)*2Cw^&v_A2dJ_#lEG#blO4H^8i*a1-<@^%Q zLj5% {H@yG&ae7mn9j$oBsKZ|K95BJbK%* ztU0R0&?(ALlZ;5-lmZpXhG=wcu>ZA7k8Ul04cv@*Z_)GRgy_-lzNnGX-V2pAe{$G? zGBS6cPogMxx8Hvh;XeF ;CLo?TKE#BM=DGUj 4hX|!LW*C1G8wsDIcM^+wWuU}jpcupb z9I2DMVk*$Ba@QhZMIK0|j$P=vTV{N4i=wob_^x1z{=4#dZkuF$RWss1`s{3N#cts- z9rLp(D#foZx1mrQq}fc|b4}r?^oEA)p(&)-PBwdjBvohII86^;tEXX{-L@oks_%^% z%*fpts#a>R>wD{;=eMSRAUxND&c)j6*0TQjM`3yE_-7dF`VsLgWMP;0a%U!au2ovb z2~yVP!HcJ(MaStJYKGez-^RW~8=w2GKspAaQGNNpXim@r wF|*7GSOa8`nzA;#ke5~{Bm^Wl*YdCUeC z%vTw*3lw`A5F7{7efX%GSk=3`(Pwo|+{;L)Aa{fWnfN`-I8^|V^|Jel3^}fMKeEVn z=5TReh^ov!+&NJPdOp0l4K!o{%DOr(SMiaO9yH&;Z9Tw?!pgVqKp0>~YSBp2@%q3V zRUE9?eG>)uLwCAsm5Z^Mx>kxPIxiXr)fg?zJov%a-uPoyM&AgH%4daOcLXsc(c#%V zH BpoO}Cx? z>&^n2hlmRMqC4q)_7f>t;TQ*LLWSVelJ4=Un%>UHER%O1dNVF9F J#TjK| zNM~~uo%?(u$vIWVFwTjGuj{5wY>A@>q)iT9&CDRuEvvYx?yyg$@(lFY+q1d5^mV uXv 7_pc1H;HKXUrt;QaC0qn=0+%^g^Jn~cq&W$% I(Nb$%mzGgh}Vf006Uc{!v$l`3S@$eL+R%5?(cVqaj(+thl?tmn_ zRzGWG+6I9%;83ZB6d`*S#sDdnBbISvh&4thEl;uzQfIyAX@RH81GHfQUShfcCqq!F z@dkfUBhInT2OQR`dFNUNs|2o1G;5MniC`Pv<{RCs_Nj21ra3qfUoXZ2k3>&Me98I} zj5mPC`Sn?~wXls}yIG)K2Qp7+3fB9Vo1S_!)#W6qQ|no4m1fKEMnOaYJZ}`#BH|Q& zkD*nhxdhu(UxK9_Tce#Bq095!oxEX@UPgJmKD#3#DrfAt`7F}==xn5v&Kra){H5tG zTVUZH7GKprx}99QE`=;XnP oe+|Z<@_W(aOUYXdZu%B}|Nf%KJ86GR zsXEwh)v#FHy8Dl DtKX =OJ78rg|OFBBJK z;|c7KDWPEv%?V3$&roQuYZ**N!#QL9v4|Gd0M}1#ADnihL~`rv#;<7W(v?sbEV&(Q zJI@HP*&^3R$3ebY<+nX9>!Kj*80enjRMN7KMVy!NCUmPN5Eld8#LY1>sm_?ySB=l> z+sQ~xvoDcBbjUvXbxKerU39H^VRj91Y2H@Lu!3W@wcOX+`D>Bo?MtodQ!20;OL5hN z`IL$jxO0`Li2J57>XSEH_r^Zgfn5cb(f4_$ygR h8QDR?=Gni(>;vP!}f8W85EDQ>Y>*kP9xpfrmGQhNvuEFq& z`oL88!gyZ+MZP_wVS| $V!cmN$*d-0hP*>1Lr!o^SWS?SI25+RmP1?|HtKS5ytb zfqe&S_qK0edK&jE)+Kpt@E(M~K<{o0j+dUyE0&?(x|PmZCcW|1N^l(Jl;|rWzAiam zHvgEVg{7><*f4vjqR@nLU5QN-%P_X5MUS1>WC*G??~z0b19f>^3-OOh1CrU=#=b=2 zrx4cXT3I;_mL6#i#hVMVPOajb4No=WQ27J5=-f8O38L7qzO1L*8n>Cd?ja91lnooo z7MiZs=^G*`4Jfw`%<7&p!mV;_hsx5cB$Y(hGXcfW9TG5(WYwO&?!4BgVY<2MAgxc; zh#gM^ofte|Y->Fdjm1ApJI%FvK>MnFqR;?N#Be>JTD|) sy)bo_eRBp7 9)^yr;_HprJp|sezFhx22o8P) zCyrXrj8Iv}4 `5^}uwn)Zx%H+x# z&S ws}XD|_FDjs8RY5PiX&ED|+Q<4REnWcSGP<-VJP`)zb^aHU+}$yVOw zpICc)_jecr&-;WGUFpCM=Lx#mQIJvdEWgCWQ@XOLag~oxo%Qm~2rOlwXNK5}q{?|~ zdN >7XMRmNXamn>47j^XL*luj|cmsOih+W(0!cQx38pceC*ueWgUq-ymV^ z8^8H_FkhpnyBuM>Fzp!c^Me}eIsO2NS3h{J=HUn5C`$^fL~s^689CjhuK?@FO4-1L zC0$kGkmZ|CrFmA)UZmSEy&&kO;Cx$&(_VB;-aS4|-4@hzwuhs^SnLP{K|;Sh(R|hS zrAh47Fl;EBIb6fbOj^OGT3xtJKxsTF`Cf^rWR^6bJL8TvB73kUB$~@_8sP6k_LV@Q z4P#L~47;SX@(Dl_n2sW#@mDK-Fs| )Dv3>Pun_p~XJL)6Ba-ACo; zG0};ua;7D_D`6W=z#~CG@ f8ts`(~f z4F> l~homa!#{t-}8i;egDC{2b z*BkJToj |R>Sn@x6zZG$}n5(pQy)^`FzeQCfx(RkPv%s~}HKS~a9 zYghIgj6w%l*AIxz2@gKP){-K+$&@9+$>;{PCes`;?jD)2_Qv|HS>`gSde+}G@}Hrg ze3}8=WLM&Z5&5`INw%JQb|c>d*hsChPMeor1eb8^a3j0T=yXhZQatOd- c?nU 1=-7Z)6^L7;@m2d)9>`^_lE}WBn*Ndz*sa*A6NT^W0e a!qTr3Wutu}9w z>Hey<#VwWvd;Qn^%c6q$e@4V)$Cg%OB_pNLqfRHvSp{AChe*!Bd{s5Cn`VtSI6{@} z-}ZWN_u`LkOn%^jkz*9nw2mYO?H1AsHsvDfVRPtdF59dED@!L2-m#TyT_ _? z+i7c)xx@UjE%Q>o*-d5to|cA!7>A5Ext3*{i Qf!5pjc(y2v3UT?Dh+dX&wIB@>IR7=8ljJo*uX0 zvh13%y`cCq-r)BIijla6=;$(PLh!X(?*y=OG~24 lQ68tLVsYbd0&^=78}&L)7~SIB=6g zDicwI jlqLlVHqjK$s;yn53<85 zVfQkddHVFMqsN2%LLudnhDZ-06hvkJ{?Jftt{^p}q<4E@;uF`@2{jz;u@LdLGUZ{( zYEz%4&lC0Y(dyO47>~?KZ3;r1nY&cwP2cH#QEV8QV=9b;TL87Hs3o9{- czZ& z0e-nZEsyen3(Ph1nAatO`#<^sJ=vy1IZsJL!-F0~ZYSLNbMi-~l67<;fBlxX-)flV zmmF5odpP#XdQRlzneMMe;23%BUPn$rCHoS4b)Eq+s_k1I_|d2Rl$(qe09v98XDXYu zt$OR)4X#f#Ik{tfzuCKr+~jz>JMFJLjY;D`adA%8Qm*QyAHkRnJuDz8l$`tBpYKV_VeZXH0)Ln 00^pl}Tg-4o7GYf;m*k9uYLN@%3>9 olMT8`T@ z+>N94b__EWx35U?Ck;F-cz~bo9(j9V+`YJSv8Ph}P-_R}UVHFaoJcNr;(jy_Y7KLP zyMG*y>qk}2ngsD^ziBD>!ALDC6|(E%ULm`FSK`UJ;AA $}|p z)m{I@AZ`;n^W}u ki z^^uQDPk-c-sN1jM!|pY(G40w>U?Mm_e9=&phr>+bl4tnOhD+Vlw$c5CwX1o%$1`Z9 z<*n!DO^8Of)uzKJr_y=K-!D%Hr+V(eQ&zVnUEC3?@BCzM$yk3$*qNkk2wA2NlkXBu zRc~p9vT|5)j5kwu*=m&qkIX`pF`PyOWwkEON%!efHTjWM296fnW$6O+iHH;elQ%7Q zWIdg65 C?2s;oMcp{Z)DXnpPT*}N&wXET~E`@y8~Hd<}r#-&~UZmT$2 zbUetpQpVEml%;ZyXK+~HtboXbeyw433bi2SbDt+kkL6uAeN41uGW%Q Qen2| zeHZBY!X#gn>d#~%Wd&UNKH(c~dZZ=j%@Mk BkR?#21OLWV37? zirEI AFFNG=GHzBKsQsp;} zvyRPgx2GflkK-pd(3fKV1_0AyJS6GPOd6g!W6ChxVQ**OW7-gP6x>L3q)`eNFR+MA z%VooJi20+t@Mqu5kM+{)?Lm3t? ;=NfvPer0XeX zJqDlgqD@w`umBRe(^CTRsoaA{#7@x|jTK%S?7!s$#^qRtG#AH~y8%4N;Pt1ECq)Nt zq^2c(X*YYx(|^aTbJ~|m gDQk_rnrBS6o{v{cqgp+dxRQ30mdn7{Gx3TdB7oDws)^7G zB~?V)`?*ydJuP=k7l?*0?0_jbbJ);2jb@ZM>(v27Tg=Xsd26vRvizIjoy}YKB$m2g zL%H=;sutg13e pHoaCeP)Gf8kz*-^oXJ@Z~w zm8G))`E=UWgyeQ>I&F#M`e$>84S(e?PC{l^HUNsz?cne> z xfX>#5Vjh|f` zVpH}$=3%u<14j_IJv*?&`*E3kTa|Xt3H4ZC?Vu6om~q@xkZ}piW}V_%vs;sGGG)nm z+mF-z`ZGwk@7)NiBClt%&{1tyw+!=sR%e`$KE;InR7>^Gkh|dw;PXasxeOn3>tm%2 ztsemZpFaQiR&bbK#(Su?2Yb6`T~!@)<)bDYLue^iDV?W0j%Ti>oNbK#i7*+Nn%i(E zo)jVqo&Ei)CTn&&H&w(SnTwZ%KGP3do>G*#i%d;{qa@QLXOc&w8A}&T5_3J3WFgSJ zeCANtlj63-!nb3CFf&6Zk@%1wGiA|+5fJ!nP0c)7%CJ(jf6y!?a`O10>_2 Nz}r$C`3%-@OGf{yfY*(aS>`l>86GxlBu_l~28$@O}E+ z>%8^_k+8gsx%@Il=2MA%Q!&Dro0yoKR+x2-wS!2oU)xHH&~|1VIR}p;MrrTs2lmGf zA8zI1r!1h0kBg5sH1!XU#fxoK;u&gE9G?$OOIAQ}5~J?tD9U0!)5M347|6jO0IHwH z+0yAs(DqPUq(QE4J{_g}F$qQHnqhMqp4Gb$zm^*&s>7);L(h}$QHg24RcDE47MXDq z^9Y^Wu&cKY+NK_hu@mHWJ{%L0fWzeC*GObBbLu8qk2aYB73eQHI(FGVavEWcE8eH< zPxeDOl(c_)P!!-&%Eo`eNwh$N$1$fjIav%D=V}4lCQQNG6ZcvA4Fn>3Ov|N`LM{<| zdos634qDaexmOt*mNC5~oU&@R oGF051_O!+khlt4rt=(qkEndbo{N2BmuXF@u z@%O2X pxbgasHn%; zH{+DsrLE1wv4~w-E9rrcdY)=Dscf^D`gt(ym@zGV``VxuQkIL~^ 4JRPY4Aea4WNTZj$TovvA$TC)^@rdiis|FU|1B=W7gN~ z?rREAmS-4Ej;cdAyHn|>zG!7$`&A|Jxkz>8T#kCvci(yxGC-;9a?dfZ+8AjIQs`Vo z(;h0AYG0dAAf;q+z&(XlywZAD`NgM>8^`huU^?(&BSG!pHugBPxEDavz|38Vzi0 zg;?jVxwt@?xw8vf6;wHfMt2@cuWy}(1iatLa<0r^_R~>(2XK%p5V4ula;;2PH{0Bb z!>~tlpF(%ii&IiGlJ6`y6|-y&Z59W&<0kRNO(5?a8AGT22g9{*s0+F_2|fLqhH#PW zT3%6zpF*wK=i-)<^c9uRTh)0i2>!9eyGglxmC8+|=H}Do(%I{E%E0QqF!?W->kxzz zA7mr?|Ca2^Gj;wx;@zt1^xb-OF`gIl!jnNFlBJyzp#W=rUH8Bp5}XOkBwlSz-@Z@+ z4!DhHMz3>DA0BTnw{h#z;^O+st^zjSrCqY|#1zV^2Dzf|F1p1spIj@4{hrKSOOvs5 z0!+(~0wC8g(@*zcS>o(Qxfzx5n!+iAf%xP$ce9mdbe&jLvoRBx%{=#WIgy^QV&Dyq z6Bu3{<5xP+Gi>OXl~gy+Jba!+91s$;H4u#K=z5zW*l6oxJLJzR^pjuns;_q16T{np zTk5B~y<2_B)2q9eg@uL3OSEN6_7aDj?*Q~EMk-{us;sQHuP^$;2mWpq0hxK!nf3fG zq9XnX*0b#F?k-)@5AyL*QPa>kwB-N&++kbm=C7KuPs(1qzn?mWGc*k}qFuA#h kU^c1&3CvykbA24>i7gX;( jef9ertm& zL#J*&rRE3*boeg@`$Ha;AY22Nz-ZBT_( D36cyKP>e*X9We)<2`YD7(`w_Pk;t1^ys>luU$hQ11I zuI!i (C25UiWC_^?IQ2Njmzzjak!tkDr06PFRzeS d|(_>f!7O!)Zbb zr>5EL@os*e_9TZa`~E1{oj}+!8tWm!p$;@!)v@7fpngj0Mc_^y*MXjZ03ItPyvOyi zgGgNhGv?F$!nAj40&BVvfo6$`bG`uxRo{5md{Sa{jd5oD%rVUx;Yzx$G7E@&Q