commit a75a9aaaaeee8b8e98af744725fc54a58cc3d883 Author: st2411020120 Date: Thu Jan 8 20:32:03 2026 +0800 Initial commit: 多 Agent 决策工作坊项目 diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/Project_Design.md b/Project_Design.md new file mode 100644 index 0000000..60403d8 --- /dev/null +++ b/Project_Design.md @@ -0,0 +1,144 @@ +# 多 Agent 决策工作坊 - 项目设计文档 + +## 1. 一句话描述 + +多 Agent 决策工作坊是一个为团队提供方案评审服务的 Web 应用,通过模拟多角色视角的辩论,帮助团队生成全面、客观的决策要点。 + +## 2. 核心功能 (MVP) + +### 2.1 工作坊创建与管理 +- 创建新的决策工作坊,设置工作目标和评审范围 +- 管理已有的工作坊,支持查看和编辑工作坊信息 + +### 2.2 多角色配置 +- 为每个工作坊配置不同的角色(如产品经理、技术专家、用户代表等) +- 为每个角色设置独特的视角和关注点,确保决策考虑的全面性 + +### 2.3 AI 驱动的决策分析 +- 收集多角色的辩论内容和观点 +- 使用 DeepSeek API 分析辩论内容,生成全面的决策要点和建议 +- 展示决策分析结果,包括关键因素、风险评估和具体建议 + +## 3. 交互流程 + +### 3.1 用户操作流程 + +1. **用户打开应用** + - 访问应用首页,查看已有工作坊列表 + +2. **创建新工作坊** + - 点击"创建新工作坊"按钮 + - 填写工作坊名称和目标 + - 点击"下一步"按钮进入角色配置 + +3. **配置角色** + - 输入角色名称(如产品经理、技术专家等) + - 输入角色的视角和关注点 + - 点击"添加更多角色"继续添加,或点击"完成配置"进入辩论环节 + +4. **开始辩论** + - 选择一个角色 + - 输入该角色的观点和建议 + - 点击"提交观点"按钮 + - 重复以上步骤,为不同角色添加观点 + +5. **查看决策分析** + - 点击"查看结果"按钮 + - 系统使用 DeepSeek API 分析辩论内容 + - 展示 AI 生成的决策要点和建议 + +### 3.2 页面流程 + +``` +首页 (index.html) + ↓ +创建工作坊 (create_workshop.html) + ↓ +配置角色 (configure_roles.html) + ↓ +开始辩论 (start_debate.html) + ↓ +查看结果 (results.html) +``` + +## 4. 技术实现 + +### 4.1 技术栈 + +- **后端**:Python 3.8+, Flask 框架 +- **前端**:HTML5, CSS3, Jinja2 模板引擎 +- **AI 集成**:DeepSeek API +- **环境管理**:uv 虚拟环境 +- **依赖管理**:pip +- **配置管理**:python-dotenv + +### 4.2 核心技术点 + +#### 4.2.1 DeepSeek API 集成 + +- 使用 DeepSeek API 分析辩论内容,生成决策要点 +- 构建有效的系统提示,引导 AI 生成高质量的决策分析 +- 实现错误处理,确保 API 调用失败时系统仍能正常运行 + +#### 4.2.2 数据管理 + +- 使用内存字典存储工作坊数据(开发阶段) +- 支持工作坊的创建、编辑和查询 +- 支持角色配置和辩论内容的管理 + +#### 4.2.3 用户界面 + +- 响应式设计,适配不同屏幕尺寸 +- 直观的用户操作流程,减少学习成本 +- 清晰的结果展示,突出决策要点和建议 + +## 5. 项目结构 + +``` +. +├── app.py # 主应用文件 +├── .env # 环境变量配置 +├── README.md # 项目说明文档 +├── Project_Design.md # 项目设计文档 +├── requirements.txt # 依赖项列表 +└── templates/ # HTML 模板文件 + ├── index.html # 首页 + ├── create_workshop.html # 创建工作坊页面 + ├── configure_roles.html # 配置角色页面 + ├── start_debate.html # 开始辩论页面 + └── results.html # 查看结果页面 +``` + +## 6. 依赖项 + +| 依赖项 | 版本 | 用途 | +|-------|------|------| +| Flask | ^2.0.0 | Web 框架 | +| requests | ^2.26.0 | HTTP 客户端,用于调用 DeepSeek API | +| python-dotenv | ^0.19.0 | 环境变量管理 | + +## 7. 环境要求 + +- Python 3.8+ +- uv 虚拟环境管理工具 +- DeepSeek API 密钥 + +## 8. 后续扩展 + +- **持久化存储**:使用数据库存储工作坊数据,支持长期保存和历史查询 +- **用户认证**:添加用户登录功能,支持多用户协作 +- **高级分析**:增强 AI 分析能力,提供更详细的决策建议和风险评估 +- **导出功能**:支持将决策结果导出为 PDF 或其他格式 +- **集成其他 LLM**:支持集成多个 AI 模型,提供更全面的分析视角 + +## 9. 风险评估 + +- **API 调用限制**:DeepSeek API 可能有调用次数或速率限制,需要实现合理的错误处理和重试机制 +- **数据安全**:工作坊内容可能包含敏感信息,需要考虑数据加密和访问控制 +- **性能优化**:AI 分析可能需要较长时间,需要实现异步处理或进度提示 + +## 10. 结论 + +多 Agent 决策工作坊通过模拟多角色视角的辩论,结合 AI 分析能力,为团队提供了一种全新的方案评审方式。它不仅能够帮助团队生成全面、客观的决策要点,还能够促进团队成员之间的有效沟通和协作。 + +该项目的 MVP 版本已经包含了核心功能,能够满足基本的方案评审需求。后续可以通过持续优化和扩展,进一步提升系统的功能和用户体验。 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f78c96 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# 多 Agent 决策工作坊 + +## 项目概述 + +多 Agent 决策工作坊是一个基于 Flask 和 DeepSeek API 的方案评审工具,通过模拟多角色视角的辩论,帮助团队生成全面、客观的决策要点。 + +## 核心功能 + +1. **工作坊创建与管理**:创建新的决策工作坊,设置工作目标和评审范围。 +2. **多角色配置**:为每个工作坊配置不同的角色(如产品经理、技术专家、用户代表等),每个角色拥有独特的视角和关注点。 +3. **AI 驱动的决策分析**:基于多角色的辩论内容,使用 DeepSeek API 生成全面的决策要点和建议。 + +## 使用场景 + +- **产品方案评审**:评估新功能或产品方案的可行性和风险。 +- **技术选型决策**:在多种技术方案中进行客观比较和选择。 +- **项目规划评审**:对项目计划、资源分配等进行多维度分析。 + +## 快速开始 + +### 环境要求 + +- Python 3.8+ +- uv 虚拟环境管理工具 +- DeepSeek API 密钥 + +### 安装步骤 + +1. **克隆项目** + +2. **初始化虚拟环境** + ```bash + uv init + ``` + +3. **安装依赖** + ```bash + uv add flask requests python-dotenv + ``` + +4. **配置 API 密钥** + 创建 `.env` 文件,添加以下内容: + ``` + DEEPSEEK_API_KEY=your_deepseek_api_key + ``` + +5. **启动应用** + ```bash + python app.py + ``` + +6. **访问应用** + 打开浏览器,访问 `http://localhost:5000` + +## 项目结构 + +``` +. +├── app.py # 主应用文件 +├── .env # 环境变量配置 +├── README.md # 项目说明文档 +├── Project_Design.md # 项目设计文档 +├── requirements.txt # 依赖项列表 +└── templates/ # HTML 模板文件 + ├── index.html # 首页 + ├── create_workshop.html # 创建工作坊页面 + ├── configure_roles.html # 配置角色页面 + ├── start_debate.html # 开始辩论页面 + └── results.html # 查看结果页面 +``` + +## 使用指南 + +1. **创建工作坊**:在首页点击"创建新工作坊",填写工作坊名称和目标。 +2. **配置角色**:为工作坊添加不同的角色,每个角色需要设置名称和视角。 +3. **开始辩论**:选择角色,输入该角色的观点和建议。 +4. **查看结果**:系统会基于所有角色的辩论内容,生成决策要点和建议。 + +## 技术栈 + +- **后端**:Python, Flask +- **前端**:HTML, CSS, Jinja2 模板 +- **AI 集成**:DeepSeek API +- **环境管理**:uv + +## 许可证 + +MIT \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..eed3d8d --- /dev/null +++ b/app.py @@ -0,0 +1,133 @@ +from flask import Flask, render_template, request, redirect, url_for +import requests +import json +from dotenv import load_dotenv +import os + +# 加载环境变量 +load_dotenv() + +app = Flask(__name__) + +# DeepSeek API 配置 +DEEPSEEK_API_KEY = os.getenv('DEEPSEEK_API_KEY', 'sk-137bb3c986b640ae838d2cd2bfbca1dc') +DEEPSEEK_API_URL = 'https://api.deepseek.com/v1/chat/completions' + +def generate_decision_points(workshop): + """使用DeepSeek API生成决策要点""" + # 构建辩论内容的摘要 + debate_summary = "辩论内容:\n" + for item in workshop['debate_content']: + debate_summary += f"{item['role']}: {item['opinion']}\n" + + # 构建系统提示 + system_prompt = f"你是一个决策分析专家,需要基于以下辩论内容,为'{workshop['name']}'工作坊生成全面的决策要点。工作坊目标是:{workshop['goal']}。请从多个角度分析,提取关键决策点,并提供具体的建议。" + + # 构建请求数据 + data = { + "model": "deepseek-chat", + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": debate_summary} + ], + "temperature": 0.7, + "max_tokens": 1000 + } + + # 发送请求 + headers = { + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {DEEPSEEK_API_KEY}' + } + + try: + response = requests.post(DEEPSEEK_API_URL, headers=headers, data=json.dumps(data)) + response.raise_for_status() + result = response.json() + decision_points = result['choices'][0]['message']['content'] + return decision_points + except Exception as e: + # 错误处理,返回简单的决策要点 + decision_points = [] + for content in workshop['debate_content']: + decision_points.append(f"{content['role']}: {content['opinion'][:100]}...") + return "\n".join(decision_points) + +# 存储工作坊数据的临时字典 +workshops = {} + +@app.route('/') +def index(): + return render_template('index.html', workshops=workshops) + +@app.route('/create', methods=['GET', 'POST']) +def create_workshop(): + if request.method == 'POST': + workshop_id = len(workshops) + 1 + workshop_name = request.form['workshop_name'] + workshop_goal = request.form['workshop_goal'] + + workshops[workshop_id] = { + 'name': workshop_name, + 'goal': workshop_goal, + 'roles': [], + 'debate_content': [], + 'decision_points': [] + } + + return redirect(url_for('configure_roles', workshop_id=workshop_id)) + return render_template('create_workshop.html') + +@app.route('/workshop//roles', methods=['GET', 'POST']) +def configure_roles(workshop_id): + if workshop_id not in workshops: + return redirect(url_for('index')) + + if request.method == 'POST': + role_name = request.form['role_name'] + role_perspective = request.form['role_perspective'] + + workshops[workshop_id]['roles'].append({ + 'name': role_name, + 'perspective': role_perspective + }) + + if 'add_more' in request.form: + return redirect(url_for('configure_roles', workshop_id=workshop_id)) + else: + return redirect(url_for('start_debate', workshop_id=workshop_id)) + + return render_template('configure_roles.html', workshop=workshops[workshop_id], workshop_id=workshop_id) + +@app.route('/workshop//debate', methods=['GET', 'POST']) +def start_debate(workshop_id): + if workshop_id not in workshops: + return redirect(url_for('index')) + + if request.method == 'POST': + role_index = int(request.form['role_index']) + role_opinion = request.form['role_opinion'] + + workshops[workshop_id]['debate_content'].append({ + 'role': workshops[workshop_id]['roles'][role_index]['name'], + 'opinion': role_opinion + }) + + return redirect(url_for('start_debate', workshop_id=workshop_id)) + + return render_template('start_debate.html', workshop=workshops[workshop_id], workshop_id=workshop_id) + +@app.route('/workshop//results') +def show_results(workshop_id): + if workshop_id not in workshops: + return redirect(url_for('index')) + + # 使用DeepSeek API生成决策要点 + workshop = workshops[workshop_id] + decision_points = generate_decision_points(workshop) + workshop['decision_points'] = decision_points + + return render_template('results.html', workshop=workshop, workshop_id=workshop_id) + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..5083e22 --- /dev/null +++ b/main.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from 1!") + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9d460da --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "1" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.14" +dependencies = [ + "flask>=3.1.2", + "python-dotenv>=1.2.1", + "requests>=2.32.5", +] diff --git a/templates/configure_roles.html b/templates/configure_roles.html new file mode 100644 index 0000000..d1a33ba --- /dev/null +++ b/templates/configure_roles.html @@ -0,0 +1,108 @@ + + + + + + 配置角色 - {{ workshop.name }} + + + +
+

配置角色 - {{ workshop.name }}

+ 返回首页 + +
+

已配置角色

+ {% if workshop.roles %} + {% for role in workshop.roles %} +
+ {{ role.name }} +

{{ role.perspective }}

+
+ {% endfor %} + {% else %} +

暂无角色,请添加第一个角色

+ {% endif %} +
+ +
+ + + + + + + + +
+
+ + \ No newline at end of file diff --git a/templates/create_workshop.html b/templates/create_workshop.html new file mode 100644 index 0000000..69ce2b2 --- /dev/null +++ b/templates/create_workshop.html @@ -0,0 +1,79 @@ + + + + + + 创建工作坊 + + + +
+

创建工作坊

+ 返回首页 + +
+ + + + + + + +
+
+ + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..cfc06f6 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,89 @@ + + + + + + 多 Agent 决策工作坊 + + + +
+

多 Agent 决策工作坊

+ 创建新工作坊 + +
+

工作坊列表

+ {% if workshops %} + {% for workshop_id, workshop in workshops.items() %} +
+

{{ workshop.name }}

+

目标: {{ workshop.goal }}

+

角色数量: {{ workshop.roles|length }}

+ +
+ {% endfor %} + {% else %} +

暂无工作坊,请创建新工作坊

+ {% endif %} +
+
+ + \ No newline at end of file diff --git a/templates/results.html b/templates/results.html new file mode 100644 index 0000000..573dda1 --- /dev/null +++ b/templates/results.html @@ -0,0 +1,99 @@ + + + + + + 决策要点 - {{ workshop.name }} + + + +
+

决策要点 - {{ workshop.name }}

+ 返回首页 + 继续辩论 + +

工作坊目标

+

{{ workshop.goal }}

+ +

AI 生成的决策要点

+
+ {{ workshop.decision_points }} +
+ +

辩论内容摘要

+
+ {% if workshop.debate_content %} + {% for item in workshop.debate_content %} +
+ {{ item.role }} +

{{ item.opinion }}

+
+ {% endfor %} + {% else %} +

暂无辩论内容,请先添加辩论内容

+ {% endif %} +
+
+ + \ No newline at end of file diff --git a/templates/start_debate.html b/templates/start_debate.html new file mode 100644 index 0000000..cc323db --- /dev/null +++ b/templates/start_debate.html @@ -0,0 +1,120 @@ + + + + + + 开始辩论 - {{ workshop.name }} + + + +
+

开始辩论 - {{ workshop.name }}

+ 返回首页 + 查看结果 + +
+

辩论内容

+ {% if workshop.debate_content %} + {% for item in workshop.debate_content %} +
+ {{ item.role }} +

{{ item.opinion }}

+
+ {% endfor %} + {% else %} +

暂无辩论内容,请选择角色发表观点

+ {% endif %} +
+ +
+ + + + + + + +
+
+ + \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..938bfe2 --- /dev/null +++ b/uv.lock @@ -0,0 +1,206 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "1" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "flask" }, + { name = "python-dotenv" }, + { name = "requests" }, +] + +[package.metadata] +requires-dist = [ + { name = "flask", specifier = ">=3.1.2" }, + { name = "python-dotenv", specifier = ">=1.2.1" }, + { name = "requests", specifier = ">=2.32.5" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "flask" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/ea/b0f8eeb287f8df9066e56e831c7824ac6bab645dd6c7a8f4b2d767944f9b/werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e", size = 864687, upload-time = "2025-11-29T02:15:22.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/f9/9e082990c2585c744734f85bec79b5dae5df9c974ffee58fe421652c8e91/werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905", size = 224960, upload-time = "2025-11-29T02:15:21.13Z" }, +]