✨ feat: 完成aide decide设计文档
This commit is contained in:
@@ -1,5 +1,16 @@
|
||||
# aide decide 子命令设计文档
|
||||
|
||||
## 零、详细设计文档包
|
||||
|
||||
本文档为概览设计;更细的实现交接规格见:
|
||||
|
||||
- [aide-program/docs/commands/decide/README.md](decide/README.md)
|
||||
- [aide-program/docs/commands/decide/cli.md](decide/cli.md)
|
||||
- [aide-program/docs/commands/decide/server.md](decide/server.md)
|
||||
- [aide-program/docs/commands/decide/web.md](decide/web.md)
|
||||
- [aide-program/docs/commands/decide/storage.md](decide/storage.md)
|
||||
- [aide-program/docs/commands/decide/verification.md](decide/verification.md)
|
||||
|
||||
## 一、背景
|
||||
|
||||
### 1.1 解决的问题
|
||||
|
||||
225
aide-program/docs/commands/decide/README.md
Normal file
225
aide-program/docs/commands/decide/README.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# aide decide 详细设计(实现交接包)
|
||||
|
||||
本目录为 `aide decide` 子命令的**详细设计**。目标是让接手开发者在不阅读额外上下文的情况下,能够依据本文档集完成实现、联调与验证。
|
||||
|
||||
实现位置:
|
||||
- 核心实现:`aide-program/aide/decide/`
|
||||
- CLI 入口:`aide-program/aide/main.py` 的 `aide decide ...` 子命令树
|
||||
|
||||
上游/关联文档:
|
||||
- 概览设计:[`aide-program/docs/commands/decide.md`](../decide.md)
|
||||
- 数据格式规范(输入/输出格式):[`aide-program/docs/formats/data.md`](../../formats/data.md)
|
||||
- 配置格式规范:[`aide-program/docs/formats/config.md`](../../formats/config.md)
|
||||
- 插件侧调用契约:[`/aide:prep`](../../../../aide-marketplace/aide-plugin/docs/commands/prep.md)
|
||||
|
||||
## 一、范围与目标
|
||||
|
||||
### 1.1 目标
|
||||
|
||||
- 提供**程序化的待定项确认机制**,替代终端中的逐项文本确认
|
||||
- 以 Web 界面呈现待定项,支持选项选择与备注添加
|
||||
- 存储决策记录,支持历史追溯
|
||||
- 遵循"静默即成功"的输出原则
|
||||
|
||||
### 1.2 非目标
|
||||
|
||||
- 不分析待定项内容(这是 LLM 的职责)
|
||||
- 不做决策建议或推荐排序
|
||||
- 不修改业务代码
|
||||
- 不实现复杂的用户认证或多用户支持
|
||||
|
||||
## 二、关键约定(必须先统一)
|
||||
|
||||
1. **技术选型**:
|
||||
- HTTP 服务:使用 Python 标准库 `http.server`,无需额外依赖
|
||||
- Web 前端:使用纯 HTML/CSS/JavaScript,无需构建工具,直接嵌入 Python 代码或作为静态资源
|
||||
|
||||
2. **服务生命周期**:
|
||||
- `aide decide '<json>'` 启动服务并阻塞等待
|
||||
- 用户在 Web 界面提交决策后,服务自动关闭
|
||||
- 服务关闭后,LLM 调用 `aide decide result` 获取结果
|
||||
|
||||
3. **端口配置**:
|
||||
- 默认端口:3721
|
||||
- 端口被占用时:尝试下一个端口(3722、3723...),最多尝试 10 次
|
||||
- 可通过配置文件 `decide.port` 指定固定端口
|
||||
|
||||
4. **数据存储**:
|
||||
- 待处理数据:`.aide/decisions/pending.json`
|
||||
- 历史记录:`.aide/decisions/{timestamp}.json`
|
||||
- `.aide/` 默认被 gitignore
|
||||
|
||||
5. **超时策略**:
|
||||
- 默认无超时(等待用户操作)
|
||||
- 可通过配置 `decide.timeout` 设置超时时间(秒)
|
||||
- 超时后服务关闭,`aide decide result` 返回错误
|
||||
|
||||
## 三、文档索引(按实现模块拆分)
|
||||
|
||||
| 文档 | 内容 |
|
||||
|------|------|
|
||||
| [cli.md](cli.md) | CLI 命令规格、参数校验、输出规范 |
|
||||
| [server.md](server.md) | HTTP 服务设计、API 端点、生命周期管理 |
|
||||
| [web.md](web.md) | Web 前端设计、组件结构、交互流程 |
|
||||
| [storage.md](storage.md) | 数据存储设计、文件格式、生命周期 |
|
||||
| [verification.md](verification.md) | 验证清单(实现完成后的自检) |
|
||||
|
||||
## 四、推荐实现模块划分(仅文件/职责约定)
|
||||
|
||||
实现位于 `aide-program/aide/decide/`,按职责拆分为:
|
||||
|
||||
```
|
||||
aide/decide/
|
||||
├── __init__.py # 模块导出
|
||||
├── types.py # 数据类型定义(DecideInput, DecideOutput 等)
|
||||
├── storage.py # 数据存储(pending.json 读写、历史记录)
|
||||
├── server.py # HTTP 服务器(启动、路由、关闭)
|
||||
├── handlers.py # API 请求处理器
|
||||
└── web/ # 静态资源目录
|
||||
├── index.html # 主页面
|
||||
├── style.css # 样式
|
||||
└── app.js # 交互逻辑
|
||||
```
|
||||
|
||||
各模块职责:
|
||||
|
||||
- `types`:定义数据结构,与 `aide-program/docs/formats/data.md` 保持一致
|
||||
- `storage`:负责 pending.json 和历史记录的读写、清理
|
||||
- `server`:HTTP 服务器生命周期管理、端口探测、静态资源服务
|
||||
- `handlers`:处理 API 请求(获取待定项、提交决策)
|
||||
- `web/`:纯静态前端资源,由 server 提供服务
|
||||
|
||||
> 注:本文档只约定职责与接口,不提供实现代码。
|
||||
|
||||
## 五、实现任务拆分(建议顺序)
|
||||
|
||||
### 阶段1:基础设施
|
||||
|
||||
1. 创建 `aide/decide/` 目录结构
|
||||
2. 实现 `types.py`:定义 DecideInput、DecideItem、Option、DecideOutput、Decision 等数据类型
|
||||
3. 实现 `storage.py`:pending.json 读写、历史记录保存
|
||||
|
||||
### 阶段2:CLI 入口
|
||||
|
||||
4. 在 `main.py` 添加 `aide decide` 子命令路由
|
||||
5. 实现 JSON 解析与验证逻辑
|
||||
6. 实现 `aide decide result` 命令
|
||||
|
||||
### 阶段3:HTTP 服务
|
||||
|
||||
7. 实现 `server.py`:HTTP 服务器基础框架
|
||||
8. 实现端口探测逻辑
|
||||
9. 实现 `handlers.py`:API 端点处理
|
||||
10. 实现服务生命周期管理(启动、等待、关闭)
|
||||
|
||||
### 阶段4:Web 前端
|
||||
|
||||
11. 创建 `web/index.html`:页面结构
|
||||
12. 创建 `web/style.css`:样式设计
|
||||
13. 创建 `web/app.js`:交互逻辑(加载数据、选择选项、提交决策)
|
||||
|
||||
### 阶段5:集成与验证
|
||||
|
||||
14. 端到端测试:完整流程验证
|
||||
15. 按 `verification.md` 逐项检查
|
||||
|
||||
### 依赖关系
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
[阶段1: 基础设施] as P1
|
||||
[阶段2: CLI入口] as P2
|
||||
[阶段3: HTTP服务] as P3
|
||||
[阶段4: Web前端] as P4
|
||||
[阶段5: 集成验证] as P5
|
||||
|
||||
P1 --> P2
|
||||
P1 --> P3
|
||||
P2 --> P3
|
||||
P3 --> P4
|
||||
P1 --> P5
|
||||
P2 --> P5
|
||||
P3 --> P5
|
||||
P4 --> P5
|
||||
@enduml
|
||||
```
|
||||
|
||||
## 六、整体业务流程
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
participant LLM
|
||||
participant "aide decide" as CLI
|
||||
participant "HTTP Server" as Server
|
||||
participant "Web Browser" as Browser
|
||||
participant User
|
||||
|
||||
== 提交待定项 ==
|
||||
LLM -> CLI : aide decide '<json>'
|
||||
CLI -> CLI : 解析并验证 JSON
|
||||
CLI -> CLI : 保存到 pending.json
|
||||
CLI -> Server : 启动 HTTP 服务
|
||||
CLI --> LLM : 输出访问链接
|
||||
note right: → Web 服务已启动\n→ 请访问: http://localhost:3721\n→ 等待用户完成决策...
|
||||
|
||||
== 用户操作 ==
|
||||
LLM -> User : 告知访问链接
|
||||
User -> Browser : 打开链接
|
||||
Browser -> Server : GET /
|
||||
Server --> Browser : 返回 index.html
|
||||
Browser -> Server : GET /api/items
|
||||
Server --> Browser : 返回待定项数据
|
||||
Browser -> User : 渲染界面
|
||||
User -> Browser : 选择选项、添加备注
|
||||
User -> Browser : 点击提交
|
||||
Browser -> Server : POST /api/submit
|
||||
Server -> Server : 保存决策结果
|
||||
Server --> Browser : 返回成功
|
||||
Server -> Server : 关闭服务
|
||||
|
||||
== 获取结果 ==
|
||||
CLI --> LLM : 服务已关闭
|
||||
LLM -> CLI : aide decide result
|
||||
CLI -> CLI : 读取最新决策
|
||||
CLI --> LLM : 返回 JSON 结果
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
## 七、风险与待定项(需要开发前确认)
|
||||
|
||||
### 7.1 需要确认的设计决策
|
||||
|
||||
| 问题 | 建议方案 | 备选方案 |
|
||||
|------|----------|----------|
|
||||
| 前端是否需要支持移动端 | 否,仅支持桌面浏览器 | 响应式设计 |
|
||||
| 是否支持多个待定项会话并行 | 否,同一时间只能有一个 pending | 支持多会话 |
|
||||
| 服务启动后是否自动打开浏览器 | 否,仅输出链接 | 使用 webbrowser 模块自动打开 |
|
||||
|
||||
### 7.2 潜在风险
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| 端口被占用 | 服务无法启动 | 自动尝试下一个端口 |
|
||||
| 用户长时间不操作 | 服务一直阻塞 | 可配置超时时间 |
|
||||
| 浏览器兼容性 | 界面显示异常 | 使用标准 HTML/CSS/JS,避免新特性 |
|
||||
| JSON 数据过大 | 解析/传输慢 | 限制 items 数量(建议 ≤50) |
|
||||
|
||||
### 7.3 后续优化方向(不在本次实现范围)
|
||||
|
||||
- 支持键盘快捷键操作
|
||||
- 支持决策历史查看
|
||||
- 支持决策导出(Markdown/PDF)
|
||||
- 支持自定义主题
|
||||
|
||||
## 八、相关文档
|
||||
|
||||
- [program 导览](../../README.md)
|
||||
- [decide 概览设计](../decide.md)
|
||||
- [数据格式文档](../../formats/data.md)
|
||||
- [配置格式文档](../../formats/config.md)
|
||||
- [aide skill 设计文档](../../../../aide-marketplace/aide-plugin/docs/skill/aide.md)
|
||||
365
aide-program/docs/commands/decide/cli.md
Normal file
365
aide-program/docs/commands/decide/cli.md
Normal file
@@ -0,0 +1,365 @@
|
||||
# aide decide CLI 规格
|
||||
|
||||
## 一、命令一览
|
||||
|
||||
`aide decide` 提供两个子命令:
|
||||
|
||||
| 子命令 | 语法(API 约定) | 成功输出 | 主要用途 |
|
||||
|--------|------------------|----------|----------|
|
||||
| (默认) | `aide decide '<json>'` | 输出访问链接,阻塞等待 | 提交待定项数据并启动 Web 服务 |
|
||||
| result | `aide decide result` | 输出 JSON 结果 | 获取用户决策结果 |
|
||||
|
||||
## 二、命令详细规格
|
||||
|
||||
### 2.1 aide decide(提交数据并启动服务)
|
||||
|
||||
**语法**:
|
||||
|
||||
```
|
||||
aide decide '<json_data>'
|
||||
```
|
||||
|
||||
**参数**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `<json_data>` | string | 是 | 待定项 JSON 数据,需用引号包裹 |
|
||||
|
||||
**输入数据格式**:
|
||||
|
||||
见 `aide-program/docs/formats/data.md` 的"待定项数据格式"章节。
|
||||
|
||||
**成功输出**:
|
||||
|
||||
```
|
||||
→ Web 服务已启动
|
||||
→ 请访问: http://localhost:3721
|
||||
→ 等待用户完成决策...
|
||||
```
|
||||
|
||||
服务关闭后:
|
||||
|
||||
```
|
||||
✓ 决策已完成
|
||||
```
|
||||
|
||||
**错误输出**:
|
||||
|
||||
```
|
||||
✗ JSON 解析失败: <具体错误>
|
||||
建议: 检查 JSON 格式是否正确
|
||||
```
|
||||
|
||||
```
|
||||
✗ 数据验证失败: <具体错误>
|
||||
建议: 检查必填字段是否完整
|
||||
```
|
||||
|
||||
```
|
||||
✗ 无法启动服务: 端口 3721-3730 均被占用
|
||||
建议: 关闭占用端口的程序,或在配置中指定其他端口
|
||||
```
|
||||
|
||||
**行为流程**:
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
:接收 JSON 参数;
|
||||
|
||||
:解析 JSON;
|
||||
if (解析成功?) then (是)
|
||||
else (否)
|
||||
:输出错误: JSON 解析失败;
|
||||
stop
|
||||
endif
|
||||
|
||||
:验证数据格式;
|
||||
if (验证通过?) then (是)
|
||||
else (否)
|
||||
:输出错误: 数据验证失败;
|
||||
stop
|
||||
endif
|
||||
|
||||
:保存到 pending.json;
|
||||
|
||||
:探测可用端口;
|
||||
if (找到可用端口?) then (是)
|
||||
else (否)
|
||||
:输出错误: 端口均被占用;
|
||||
stop
|
||||
endif
|
||||
|
||||
:启动 HTTP 服务;
|
||||
|
||||
:输出访问链接;
|
||||
|
||||
:阻塞等待用户操作;
|
||||
|
||||
:用户提交决策;
|
||||
|
||||
:保存决策结果;
|
||||
|
||||
:关闭服务;
|
||||
|
||||
:输出: 决策已完成;
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 2.2 aide decide result(获取决策结果)
|
||||
|
||||
**语法**:
|
||||
|
||||
```
|
||||
aide decide result
|
||||
```
|
||||
|
||||
**参数**:无
|
||||
|
||||
**成功输出**:
|
||||
|
||||
直接输出 JSON 格式的决策结果(便于 LLM 解析):
|
||||
|
||||
```json
|
||||
{
|
||||
"decisions": [
|
||||
{"id": 1, "chosen": "option_a"},
|
||||
{"id": 2, "chosen": "option_b", "note": "用户的补充说明"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**错误输出**:
|
||||
|
||||
```
|
||||
✗ 尚无决策结果
|
||||
建议: 请等待用户在 Web 界面完成操作
|
||||
```
|
||||
|
||||
```
|
||||
✗ 未找到待定项数据
|
||||
建议: 请先执行 aide decide '<json>'
|
||||
```
|
||||
|
||||
```
|
||||
✗ 决策结果已过期
|
||||
建议: 请重新执行 aide decide '<json>'
|
||||
```
|
||||
|
||||
**行为流程**:
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
:检查 pending.json 是否存在;
|
||||
if (存在?) then (是)
|
||||
else (否)
|
||||
:输出错误: 未找到待定项数据;
|
||||
stop
|
||||
endif
|
||||
|
||||
:查找最新决策记录;
|
||||
if (存在决策记录?) then (是)
|
||||
else (否)
|
||||
:输出错误: 尚无决策结果;
|
||||
stop
|
||||
endif
|
||||
|
||||
:验证决策记录与 pending 匹配;
|
||||
if (匹配?) then (是)
|
||||
else (否)
|
||||
:输出错误: 决策结果已过期;
|
||||
stop
|
||||
endif
|
||||
|
||||
:输出 JSON 结果;
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
## 三、参数校验规则
|
||||
|
||||
### 3.1 JSON 数据校验
|
||||
|
||||
**必填字段**:
|
||||
|
||||
| 字段 | 类型 | 校验规则 |
|
||||
|------|------|----------|
|
||||
| `task` | string | 非空字符串 |
|
||||
| `source` | string | 非空字符串 |
|
||||
| `items` | array | 非空数组,至少包含 1 个待定项 |
|
||||
|
||||
**待定项(DecideItem)校验**:
|
||||
|
||||
| 字段 | 类型 | 校验规则 |
|
||||
|------|------|----------|
|
||||
| `id` | number | 正整数,在 items 中唯一 |
|
||||
| `title` | string | 非空字符串 |
|
||||
| `options` | array | 非空数组,至少包含 2 个选项 |
|
||||
|
||||
**选项(Option)校验**:
|
||||
|
||||
| 字段 | 类型 | 校验规则 |
|
||||
|------|------|----------|
|
||||
| `value` | string | 非空字符串,在同一 item 的 options 中唯一 |
|
||||
| `label` | string | 非空字符串 |
|
||||
|
||||
**可选字段**:
|
||||
|
||||
| 字段 | 类型 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `location` | object | null |
|
||||
| `context` | string | null |
|
||||
| `recommend` | string | null(若提供,必须是 options 中某个 value) |
|
||||
| `score` | number | null(若提供,范围 0-100) |
|
||||
| `pros` | array | null |
|
||||
| `cons` | array | null |
|
||||
|
||||
### 3.2 校验错误信息
|
||||
|
||||
校验失败时,错误信息应明确指出:
|
||||
|
||||
1. 哪个字段出错
|
||||
2. 期望的格式/值
|
||||
3. 实际收到的值(如适用)
|
||||
|
||||
**示例**:
|
||||
|
||||
```
|
||||
✗ 数据验证失败: items[0].options 至少需要 2 个选项,当前只有 1 个
|
||||
建议: 为每个待定项提供至少 2 个可选方案
|
||||
```
|
||||
|
||||
```
|
||||
✗ 数据验证失败: items[1].recommend 值 "invalid" 不在 options 中
|
||||
建议: recommend 必须是 options 中某个选项的 value
|
||||
```
|
||||
|
||||
## 四、输出规范
|
||||
|
||||
### 4.1 静默原则
|
||||
|
||||
- 成功时输出必要的状态信息(访问链接、完成提示)
|
||||
- 错误时输出详细的错误信息和建议
|
||||
- `aide decide result` 成功时仅输出 JSON,便于程序解析
|
||||
|
||||
### 4.2 固定前缀
|
||||
|
||||
沿用 `aide-program/docs/README.md` 的输出规范:
|
||||
|
||||
| 前缀 | 函数 | 用途 |
|
||||
|------|------|------|
|
||||
| `✓` | `output.ok()` | 成功 |
|
||||
| `✗` | `output.err()` | 失败 |
|
||||
| `→` | `output.info()` | 进行中/信息 |
|
||||
|
||||
### 4.3 JSON 输出格式
|
||||
|
||||
`aide decide result` 的 JSON 输出:
|
||||
|
||||
- 使用 UTF-8 编码
|
||||
- 紧凑格式(无缩进),便于程序解析
|
||||
- 输出到 stdout,错误信息输出到 stderr
|
||||
|
||||
## 五、退出码
|
||||
|
||||
| 退出码 | 含义 |
|
||||
|-------:|------|
|
||||
| 0 | 成功 |
|
||||
| 1 | 失败(参数错误、校验失败、服务启动失败等) |
|
||||
|
||||
## 六、典型调用序列
|
||||
|
||||
### 6.1 正常流程
|
||||
|
||||
```bash
|
||||
# 1. LLM 提交待定项数据
|
||||
$ aide decide '{"task":"实现用户认证","source":"task.md","items":[...]}'
|
||||
→ Web 服务已启动
|
||||
→ 请访问: http://localhost:3721
|
||||
→ 等待用户完成决策...
|
||||
✓ 决策已完成
|
||||
|
||||
# 2. LLM 获取决策结果
|
||||
$ aide decide result
|
||||
{"decisions":[{"id":1,"chosen":"jwt"},{"id":2,"chosen":"bcrypt"}]}
|
||||
```
|
||||
|
||||
### 6.2 服务超时
|
||||
|
||||
```bash
|
||||
# 配置了超时时间的情况
|
||||
$ aide decide '{"task":"...","source":"...","items":[...]}'
|
||||
→ Web 服务已启动
|
||||
→ 请访问: http://localhost:3721
|
||||
→ 等待用户完成决策...
|
||||
⚠ 服务超时,已自动关闭
|
||||
|
||||
$ aide decide result
|
||||
✗ 尚无决策结果
|
||||
建议: 请等待用户在 Web 界面完成操作
|
||||
```
|
||||
|
||||
### 6.3 与 /aide:prep 的集成
|
||||
|
||||
```
|
||||
/aide:prep 流程中:
|
||||
1. LLM 分析任务,识别待定项
|
||||
2. LLM 构造 JSON 数据
|
||||
3. LLM 调用 aide decide '<json>'
|
||||
4. LLM 告知用户访问链接
|
||||
5. 用户在 Web 界面完成决策
|
||||
6. LLM 调用 aide decide result 获取结果
|
||||
7. LLM 根据决策结果继续任务
|
||||
```
|
||||
|
||||
## 七、方法签名原型
|
||||
|
||||
```
|
||||
# CLI 入口(在 main.py 中)
|
||||
def cmd_decide(args: list[str]) -> int:
|
||||
"""
|
||||
处理 aide decide 命令
|
||||
|
||||
args[0] 为 JSON 数据或 "result"
|
||||
返回退出码
|
||||
"""
|
||||
|
||||
def cmd_decide_submit(json_data: str) -> int:
|
||||
"""
|
||||
提交待定项数据并启动服务
|
||||
|
||||
1. 解析并验证 JSON
|
||||
2. 保存到 pending.json
|
||||
3. 启动 HTTP 服务
|
||||
4. 等待用户操作
|
||||
5. 返回退出码
|
||||
"""
|
||||
|
||||
def cmd_decide_result() -> int:
|
||||
"""
|
||||
获取决策结果
|
||||
|
||||
1. 检查 pending.json
|
||||
2. 查找最新决策记录
|
||||
3. 验证匹配性
|
||||
4. 输出 JSON 结果
|
||||
5. 返回退出码
|
||||
"""
|
||||
```
|
||||
|
||||
## 八、相关文档
|
||||
|
||||
- [decide 详细设计入口](README.md)
|
||||
- [HTTP 服务设计](server.md)
|
||||
- [数据存储设计](storage.md)
|
||||
- [数据格式文档](../../formats/data.md)
|
||||
472
aide-program/docs/commands/decide/server.md
Normal file
472
aide-program/docs/commands/decide/server.md
Normal file
@@ -0,0 +1,472 @@
|
||||
# aide decide HTTP 服务设计
|
||||
|
||||
## 一、概述
|
||||
|
||||
aide decide 使用内置 HTTP 服务器提供 Web 界面,供用户确认待定项。本文档定义服务器的设计规格。
|
||||
|
||||
### 1.1 技术选型
|
||||
|
||||
- 使用 Python 标准库 `http.server`
|
||||
- 无需额外依赖
|
||||
- 单线程阻塞模式(简化实现,满足单用户场景)
|
||||
|
||||
### 1.2 服务职责
|
||||
|
||||
| 职责 | 说明 |
|
||||
|------|------|
|
||||
| 静态资源服务 | 提供 HTML/CSS/JS 文件 |
|
||||
| API 服务 | 提供数据获取和提交接口 |
|
||||
| 生命周期管理 | 启动、等待、关闭 |
|
||||
|
||||
## 二、服务生命周期
|
||||
|
||||
### 2.1 状态机
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
[*] --> 初始化 : 调用 start()
|
||||
初始化 --> 端口探测 : 配置加载完成
|
||||
端口探测 --> 启动失败 : 无可用端口
|
||||
端口探测 --> 运行中 : 找到可用端口
|
||||
启动失败 --> [*]
|
||||
运行中 --> 关闭中 : 收到提交/超时/中断
|
||||
关闭中 --> 已关闭
|
||||
已关闭 --> [*]
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 2.2 启动流程
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
:加载配置;
|
||||
note right: decide.port, decide.timeout
|
||||
|
||||
:确定起始端口;
|
||||
if (配置了固定端口?) then (是)
|
||||
:使用配置端口;
|
||||
else (否)
|
||||
:使用默认端口 3721;
|
||||
endif
|
||||
|
||||
:端口探测循环;
|
||||
repeat
|
||||
:尝试绑定端口;
|
||||
if (绑定成功?) then (是)
|
||||
:记录实际端口;
|
||||
break
|
||||
else (否)
|
||||
:端口 += 1;
|
||||
endif
|
||||
repeat while (尝试次数 < 10?)
|
||||
|
||||
if (找到可用端口?) then (是)
|
||||
else (否)
|
||||
:返回错误;
|
||||
stop
|
||||
endif
|
||||
|
||||
:创建 HTTP 服务器;
|
||||
|
||||
:注册请求处理器;
|
||||
|
||||
:输出访问链接;
|
||||
|
||||
:进入服务循环;
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 2.3 关闭流程
|
||||
|
||||
服务在以下情况下关闭:
|
||||
|
||||
| 触发条件 | 处理方式 |
|
||||
|----------|----------|
|
||||
| 用户提交决策 | 正常关闭,返回成功 |
|
||||
| 超时(若配置) | 正常关闭,返回警告 |
|
||||
| 键盘中断(Ctrl+C) | 正常关闭,返回中断 |
|
||||
| 异常错误 | 异常关闭,返回错误 |
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
:收到关闭信号;
|
||||
|
||||
:停止接受新请求;
|
||||
|
||||
:等待当前请求完成;
|
||||
note right: 最多等待 5 秒
|
||||
|
||||
:关闭 socket;
|
||||
|
||||
:清理资源;
|
||||
|
||||
:返回关闭原因;
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
## 三、端口配置
|
||||
|
||||
### 3.1 端口探测策略
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `decide.port` | 3721 | 起始端口 |
|
||||
| 最大尝试次数 | 10 | 从起始端口开始尝试 |
|
||||
|
||||
**探测逻辑**:
|
||||
|
||||
1. 从 `decide.port` 开始
|
||||
2. 尝试绑定端口
|
||||
3. 若失败,尝试下一个端口
|
||||
4. 最多尝试 10 次
|
||||
5. 全部失败则返回错误
|
||||
|
||||
### 3.2 端口占用检测
|
||||
|
||||
```
|
||||
check_port_available(port: int) -> bool:
|
||||
"""
|
||||
检查端口是否可用
|
||||
|
||||
1. 创建 socket
|
||||
2. 尝试绑定到 127.0.0.1:port
|
||||
3. 成功则端口可用,关闭 socket 返回 True
|
||||
4. 失败则端口被占用,返回 False
|
||||
"""
|
||||
```
|
||||
|
||||
## 四、API 端点设计
|
||||
|
||||
### 4.1 端点一览
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/` | 返回主页面(index.html) |
|
||||
| GET | `/style.css` | 返回样式文件 |
|
||||
| GET | `/app.js` | 返回脚本文件 |
|
||||
| GET | `/api/items` | 获取待定项数据 |
|
||||
| POST | `/api/submit` | 提交决策结果 |
|
||||
|
||||
### 4.2 GET /api/items
|
||||
|
||||
**请求**:无参数
|
||||
|
||||
**响应**:
|
||||
|
||||
成功(200):
|
||||
```json
|
||||
{
|
||||
"task": "实现用户认证模块",
|
||||
"source": "task-now.md",
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "认证方式选择",
|
||||
"context": "任务描述中未明确指定认证方式",
|
||||
"options": [
|
||||
{"value": "jwt", "label": "JWT Token 认证", "score": 85, "pros": [...], "cons": [...]},
|
||||
{"value": "session", "label": "Session 认证", "score": 70, "pros": [...], "cons": [...]}
|
||||
],
|
||||
"recommend": "jwt"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
失败(500):
|
||||
```json
|
||||
{
|
||||
"error": "无法读取待定项数据",
|
||||
"detail": "文件不存在或格式错误"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 POST /api/submit
|
||||
|
||||
**请求**:
|
||||
|
||||
Content-Type: `application/json`
|
||||
|
||||
```json
|
||||
{
|
||||
"decisions": [
|
||||
{"id": 1, "chosen": "jwt"},
|
||||
{"id": 2, "chosen": "bcrypt", "note": "团队更熟悉 bcrypt"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
成功(200):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "决策已保存"
|
||||
}
|
||||
```
|
||||
|
||||
校验失败(400):
|
||||
```json
|
||||
{
|
||||
"error": "决策数据无效",
|
||||
"detail": "缺少待定项 2 的决策"
|
||||
}
|
||||
```
|
||||
|
||||
服务器错误(500):
|
||||
```json
|
||||
{
|
||||
"error": "保存失败",
|
||||
"detail": "无法写入文件"
|
||||
}
|
||||
```
|
||||
|
||||
**提交后行为**:
|
||||
|
||||
1. 验证决策数据完整性(所有待定项都有决策)
|
||||
2. 保存决策结果到历史记录
|
||||
3. 设置关闭标志
|
||||
4. 返回成功响应
|
||||
5. 服务器在响应发送后关闭
|
||||
|
||||
### 4.4 静态资源服务
|
||||
|
||||
| 路径 | 文件 | Content-Type |
|
||||
|------|------|--------------|
|
||||
| `/` | `web/index.html` | `text/html; charset=utf-8` |
|
||||
| `/style.css` | `web/style.css` | `text/css; charset=utf-8` |
|
||||
| `/app.js` | `web/app.js` | `application/javascript; charset=utf-8` |
|
||||
|
||||
**资源加载方式**:
|
||||
|
||||
- 方案A:从文件系统读取(开发时便于修改)
|
||||
- 方案B:嵌入 Python 代码(部署时无需额外文件)
|
||||
|
||||
建议:使用方案A,资源文件放在 `aide/decide/web/` 目录下。
|
||||
|
||||
## 五、请求处理
|
||||
|
||||
### 5.1 请求处理流程
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
:接收 HTTP 请求;
|
||||
|
||||
:解析请求路径和方法;
|
||||
|
||||
switch (路径)
|
||||
case (/)
|
||||
:返回 index.html;
|
||||
case (/style.css)
|
||||
:返回 style.css;
|
||||
case (/app.js)
|
||||
:返回 app.js;
|
||||
case (/api/items)
|
||||
if (方法 == GET?) then (是)
|
||||
:读取 pending.json;
|
||||
:返回 JSON 数据;
|
||||
else (否)
|
||||
:返回 405 Method Not Allowed;
|
||||
endif
|
||||
case (/api/submit)
|
||||
if (方法 == POST?) then (是)
|
||||
:解析请求体;
|
||||
:验证决策数据;
|
||||
:保存决策结果;
|
||||
:设置关闭标志;
|
||||
:返回成功响应;
|
||||
else (否)
|
||||
:返回 405 Method Not Allowed;
|
||||
endif
|
||||
case (其他)
|
||||
:返回 404 Not Found;
|
||||
endswitch
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 5.2 CORS 处理
|
||||
|
||||
由于前端和后端在同一服务器,通常不需要 CORS。但为了开发调试方便,建议添加以下响应头:
|
||||
|
||||
```
|
||||
Access-Control-Allow-Origin: *
|
||||
Access-Control-Allow-Methods: GET, POST, OPTIONS
|
||||
Access-Control-Allow-Headers: Content-Type
|
||||
```
|
||||
|
||||
### 5.3 错误处理
|
||||
|
||||
| HTTP 状态码 | 场景 |
|
||||
|-------------|------|
|
||||
| 200 | 请求成功 |
|
||||
| 400 | 请求数据无效 |
|
||||
| 404 | 路径不存在 |
|
||||
| 405 | 方法不允许 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 六、超时处理
|
||||
|
||||
### 6.1 配置项
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `decide.timeout` | 0 | 超时时间(秒),0 表示无超时 |
|
||||
|
||||
### 6.2 超时实现
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
fork
|
||||
:服务循环;
|
||||
:处理请求;
|
||||
fork again
|
||||
:超时计时器;
|
||||
:等待 timeout 秒;
|
||||
:设置超时标志;
|
||||
end fork
|
||||
|
||||
if (超时?) then (是)
|
||||
:关闭服务;
|
||||
:输出警告;
|
||||
else (否)
|
||||
:正常关闭;
|
||||
endif
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
**注意**:由于使用单线程模型,超时检测需要在请求处理间隙进行,或使用 `select` 实现非阻塞等待。
|
||||
|
||||
## 七、方法签名原型
|
||||
|
||||
```
|
||||
class DecideServer:
|
||||
"""HTTP 服务器"""
|
||||
|
||||
root: Path # 项目根目录
|
||||
port: int # 实际使用的端口
|
||||
timeout: int # 超时时间(秒)
|
||||
pending_path: Path # pending.json 路径
|
||||
web_dir: Path # 静态资源目录
|
||||
should_close: bool # 关闭标志
|
||||
close_reason: str # 关闭原因
|
||||
|
||||
def __init__(self, root: Path) -> None:
|
||||
"""初始化服务器"""
|
||||
|
||||
def start(self) -> bool:
|
||||
"""
|
||||
启动服务器
|
||||
|
||||
1. 加载配置
|
||||
2. 探测可用端口
|
||||
3. 创建 HTTP 服务器
|
||||
4. 输出访问链接
|
||||
5. 进入服务循环
|
||||
6. 返回是否成功完成
|
||||
"""
|
||||
|
||||
def stop(self, reason: str) -> None:
|
||||
"""
|
||||
停止服务器
|
||||
|
||||
1. 设置关闭标志和原因
|
||||
2. 关闭 socket
|
||||
"""
|
||||
|
||||
def _find_available_port(self) -> int | None:
|
||||
"""
|
||||
探测可用端口
|
||||
|
||||
从配置端口开始,最多尝试 10 次
|
||||
返回可用端口或 None
|
||||
"""
|
||||
|
||||
def _serve_forever(self) -> None:
|
||||
"""
|
||||
服务循环
|
||||
|
||||
处理请求直到 should_close 为 True
|
||||
"""
|
||||
|
||||
|
||||
class DecideHandler:
|
||||
"""请求处理器"""
|
||||
|
||||
server: DecideServer # 服务器引用
|
||||
|
||||
def handle_request(self, method: str, path: str, body: bytes) -> Response:
|
||||
"""
|
||||
处理请求
|
||||
|
||||
根据 method 和 path 分发到具体处理函数
|
||||
"""
|
||||
|
||||
def handle_index(self) -> Response:
|
||||
"""返回主页面"""
|
||||
|
||||
def handle_static(self, filename: str) -> Response:
|
||||
"""返回静态资源"""
|
||||
|
||||
def handle_get_items(self) -> Response:
|
||||
"""处理 GET /api/items"""
|
||||
|
||||
def handle_submit(self, body: bytes) -> Response:
|
||||
"""处理 POST /api/submit"""
|
||||
|
||||
|
||||
Response = tuple[int, dict[str, str], bytes]
|
||||
# (状态码, 响应头, 响应体)
|
||||
```
|
||||
|
||||
## 八、安全考虑
|
||||
|
||||
### 8.1 绑定地址
|
||||
|
||||
- 默认绑定到 `127.0.0.1`(仅本地访问)
|
||||
- 不绑定到 `0.0.0.0`(避免外部访问)
|
||||
|
||||
### 8.2 输入验证
|
||||
|
||||
- 验证 JSON 格式
|
||||
- 验证决策数据完整性
|
||||
- 限制请求体大小(建议 1MB)
|
||||
|
||||
### 8.3 路径遍历防护
|
||||
|
||||
- 静态资源只从 `web/` 目录提供
|
||||
- 不允许 `..` 路径
|
||||
|
||||
## 九、相关文档
|
||||
|
||||
- [decide 详细设计入口](README.md)
|
||||
- [CLI 规格](cli.md)
|
||||
- [Web 前端设计](web.md)
|
||||
- [数据存储设计](storage.md)
|
||||
383
aide-program/docs/commands/decide/storage.md
Normal file
383
aide-program/docs/commands/decide/storage.md
Normal file
@@ -0,0 +1,383 @@
|
||||
# aide decide 数据存储设计
|
||||
|
||||
## 一、概述
|
||||
|
||||
aide decide 的数据存储负责管理待定项数据和决策记录的持久化。
|
||||
|
||||
### 1.1 存储位置
|
||||
|
||||
所有数据存储在项目根目录的 `.aide/decisions/` 下:
|
||||
|
||||
```
|
||||
.aide/
|
||||
└── decisions/
|
||||
├── pending.json # 当前待处理的待定项
|
||||
└── 2025-01-15T10-30-00.json # 历史决策记录
|
||||
```
|
||||
|
||||
### 1.2 数据格式规范
|
||||
|
||||
数据格式以 `aide-program/docs/formats/data.md` 为准,本文档补充存储相关的实现细节。
|
||||
|
||||
## 二、文件说明
|
||||
|
||||
### 2.1 pending.json
|
||||
|
||||
**用途**:存储当前待处理的待定项数据
|
||||
|
||||
**生命周期**:
|
||||
- 创建:`aide decide '<json>'` 执行时
|
||||
- 读取:Web 前端通过 API 获取
|
||||
- 保留:决策完成后保留,用于 `aide decide result` 验证匹配性
|
||||
- 覆盖:下次 `aide decide '<json>'` 执行时覆盖
|
||||
|
||||
**内容格式**:与输入数据格式相同(DecideInput)
|
||||
|
||||
```json
|
||||
{
|
||||
"task": "实现用户认证模块",
|
||||
"source": "task-now.md",
|
||||
"items": [...],
|
||||
"_meta": {
|
||||
"created_at": "2025-01-15T10:30:00+08:00",
|
||||
"session_id": "2025-01-15T10-30-00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**元数据字段**(`_meta`):
|
||||
- `created_at`:创建时间(ISO 8601 格式)
|
||||
- `session_id`:会话标识(用于匹配决策记录)
|
||||
|
||||
### 2.2 历史决策记录
|
||||
|
||||
**文件名格式**:`{session_id}.json`
|
||||
|
||||
**示例**:`2025-01-15T10-30-00.json`
|
||||
|
||||
**内容格式**:
|
||||
|
||||
```json
|
||||
{
|
||||
"input": {
|
||||
"task": "实现用户认证模块",
|
||||
"source": "task-now.md",
|
||||
"items": [...]
|
||||
},
|
||||
"output": {
|
||||
"decisions": [
|
||||
{"id": 1, "chosen": "jwt"},
|
||||
{"id": 2, "chosen": "bcrypt", "note": "团队更熟悉 bcrypt"}
|
||||
]
|
||||
},
|
||||
"completed_at": "2025-01-15T10:35:00+08:00"
|
||||
}
|
||||
```
|
||||
|
||||
## 三、存储操作
|
||||
|
||||
### 3.1 保存待定项数据
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
:接收 DecideInput 数据;
|
||||
|
||||
:生成 session_id;
|
||||
note right: 格式: YYYY-MM-DDTHH-mm-ss
|
||||
|
||||
:添加 _meta 字段;
|
||||
|
||||
:确保 .aide/decisions/ 目录存在;
|
||||
|
||||
:写入 pending.json;
|
||||
note right: 原子写入
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 3.2 保存决策结果
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
:接收 DecideOutput 数据;
|
||||
|
||||
:读取 pending.json;
|
||||
|
||||
:提取 session_id;
|
||||
|
||||
:构造 DecisionRecord;
|
||||
note right: input + output + completed_at
|
||||
|
||||
:写入 {session_id}.json;
|
||||
note right: 原子写入
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 3.3 读取决策结果
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
:检查 pending.json 是否存在;
|
||||
if (存在?) then (是)
|
||||
else (否)
|
||||
:返回错误: 未找到待定项数据;
|
||||
stop
|
||||
endif
|
||||
|
||||
:读取 pending.json;
|
||||
|
||||
:提取 session_id;
|
||||
|
||||
:构造历史记录文件名;
|
||||
note right: {session_id}.json
|
||||
|
||||
:检查历史记录是否存在;
|
||||
if (存在?) then (是)
|
||||
else (否)
|
||||
:返回错误: 尚无决策结果;
|
||||
stop
|
||||
endif
|
||||
|
||||
:读取历史记录;
|
||||
|
||||
:返回 output.decisions;
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
## 四、并发与原子性
|
||||
|
||||
### 4.1 原子写入
|
||||
|
||||
为避免写入过程中断导致文件损坏,必须使用原子写入:
|
||||
|
||||
1. 写入临时文件:`{filename}.tmp`
|
||||
2. 确保写入完成(fsync,如实现选择支持)
|
||||
3. 原子重命名为目标文件
|
||||
|
||||
```
|
||||
save_atomic(path: Path, data: dict) -> None:
|
||||
"""
|
||||
原子写入 JSON 文件
|
||||
|
||||
1. 序列化为 JSON 字符串
|
||||
2. 写入 {path}.tmp
|
||||
3. 重命名为 {path}
|
||||
"""
|
||||
```
|
||||
|
||||
### 4.2 并发控制
|
||||
|
||||
由于 aide decide 是单用户场景,且同一时间只有一个会话,通常不需要复杂的并发控制。
|
||||
|
||||
但为了安全,建议:
|
||||
- 写入前检查文件是否被其他进程占用
|
||||
- 使用文件锁(可选)
|
||||
|
||||
### 4.3 编码规范
|
||||
|
||||
- 文件编码:UTF-8
|
||||
- 换行符:`\n`(Unix 风格)
|
||||
- JSON 格式:缩进 2 空格,便于人工阅读
|
||||
|
||||
## 五、生命周期管理
|
||||
|
||||
### 5.1 文件生命周期
|
||||
|
||||
| 文件 | 创建时机 | 删除时机 |
|
||||
|------|----------|----------|
|
||||
| pending.json | `aide decide '<json>'` | 不自动删除,下次覆盖 |
|
||||
| {session_id}.json | 用户提交决策 | 不自动删除 |
|
||||
|
||||
### 5.2 历史记录清理
|
||||
|
||||
历史记录不自动清理,由用户手动管理。
|
||||
|
||||
建议的清理策略(可作为后续功能):
|
||||
- 保留最近 N 条记录
|
||||
- 保留最近 N 天的记录
|
||||
- 提供 `aide decide clean` 命令
|
||||
|
||||
### 5.3 目录初始化
|
||||
|
||||
首次使用时,需要确保目录存在:
|
||||
|
||||
```
|
||||
ensure_decisions_dir(root: Path) -> Path:
|
||||
"""
|
||||
确保 .aide/decisions/ 目录存在
|
||||
|
||||
1. 检查 .aide/ 是否存在
|
||||
2. 若不存在,返回错误(需要先执行 aide init)
|
||||
3. 创建 decisions/ 子目录(如不存在)
|
||||
4. 返回目录路径
|
||||
"""
|
||||
```
|
||||
|
||||
## 六、容错与恢复
|
||||
|
||||
### 6.1 JSON 解析失败
|
||||
|
||||
当文件存在但无法解析时:
|
||||
|
||||
```
|
||||
✗ 无法解析 pending.json: <具体错误>
|
||||
建议: 文件可能已损坏,请重新执行 aide decide '<json>'
|
||||
```
|
||||
|
||||
### 6.2 文件缺失
|
||||
|
||||
| 场景 | 错误信息 |
|
||||
|------|----------|
|
||||
| .aide/ 不存在 | `✗ .aide 目录不存在,请先执行 aide init` |
|
||||
| pending.json 不存在 | `✗ 未找到待定项数据,请先执行 aide decide '<json>'` |
|
||||
| 历史记录不存在 | `✗ 尚无决策结果,请等待用户完成操作` |
|
||||
|
||||
### 6.3 数据不一致
|
||||
|
||||
当 pending.json 和历史记录的 session_id 不匹配时:
|
||||
|
||||
```
|
||||
✗ 决策结果已过期
|
||||
建议: pending.json 已被更新,请重新执行 aide decide '<json>'
|
||||
```
|
||||
|
||||
## 七、方法签名原型
|
||||
|
||||
```
|
||||
class DecideStorage:
|
||||
"""决策数据存储管理"""
|
||||
|
||||
root: Path # 项目根目录
|
||||
decisions_dir: Path # .aide/decisions/ 目录
|
||||
pending_path: Path # pending.json 路径
|
||||
|
||||
def __init__(self, root: Path) -> None:
|
||||
"""
|
||||
初始化存储管理器
|
||||
|
||||
1. 设置路径
|
||||
2. 确保目录存在
|
||||
"""
|
||||
|
||||
def save_pending(self, data: DecideInput) -> str:
|
||||
"""
|
||||
保存待定项数据
|
||||
|
||||
1. 生成 session_id
|
||||
2. 添加 _meta 字段
|
||||
3. 原子写入 pending.json
|
||||
4. 返回 session_id
|
||||
"""
|
||||
|
||||
def load_pending(self) -> DecideInput | None:
|
||||
"""
|
||||
加载待定项数据
|
||||
|
||||
返回 DecideInput 或 None(文件不存在)
|
||||
"""
|
||||
|
||||
def get_session_id(self) -> str | None:
|
||||
"""
|
||||
获取当前会话 ID
|
||||
|
||||
从 pending.json 的 _meta.session_id 读取
|
||||
"""
|
||||
|
||||
def save_result(self, output: DecideOutput) -> None:
|
||||
"""
|
||||
保存决策结果
|
||||
|
||||
1. 读取 pending.json 获取 input 和 session_id
|
||||
2. 构造 DecisionRecord
|
||||
3. 原子写入 {session_id}.json
|
||||
"""
|
||||
|
||||
def load_result(self) -> DecideOutput | None:
|
||||
"""
|
||||
加载决策结果
|
||||
|
||||
1. 获取 session_id
|
||||
2. 读取 {session_id}.json
|
||||
3. 返回 output 部分
|
||||
"""
|
||||
|
||||
def has_pending(self) -> bool:
|
||||
"""检查是否有待处理的待定项"""
|
||||
|
||||
def has_result(self) -> bool:
|
||||
"""检查是否有决策结果"""
|
||||
|
||||
def _ensure_dir(self) -> None:
|
||||
"""确保目录存在"""
|
||||
|
||||
def _save_atomic(self, path: Path, data: dict) -> None:
|
||||
"""原子写入 JSON 文件"""
|
||||
|
||||
def _load_json(self, path: Path) -> dict | None:
|
||||
"""加载 JSON 文件"""
|
||||
|
||||
|
||||
# 数据类型定义(与 types.py 一致)
|
||||
|
||||
DecideInput:
|
||||
task: str
|
||||
source: str
|
||||
items: list[DecideItem]
|
||||
_meta: MetaInfo | None
|
||||
|
||||
MetaInfo:
|
||||
created_at: str
|
||||
session_id: str
|
||||
|
||||
DecideOutput:
|
||||
decisions: list[Decision]
|
||||
|
||||
DecisionRecord:
|
||||
input: DecideInput
|
||||
output: DecideOutput
|
||||
completed_at: str
|
||||
```
|
||||
|
||||
## 八、与其他模块的关系
|
||||
|
||||
### 8.1 被调用方
|
||||
|
||||
| 调用方 | 调用的方法 |
|
||||
|--------|------------|
|
||||
| CLI (cmd_decide_submit) | save_pending() |
|
||||
| CLI (cmd_decide_result) | load_result(), has_pending(), has_result() |
|
||||
| Server (handle_get_items) | load_pending() |
|
||||
| Server (handle_submit) | save_result() |
|
||||
|
||||
### 8.2 依赖
|
||||
|
||||
| 依赖项 | 用途 |
|
||||
|--------|------|
|
||||
| core/config.py | 读取项目根目录 |
|
||||
| json | JSON 序列化/反序列化 |
|
||||
| pathlib | 路径操作 |
|
||||
| datetime | 时间戳生成 |
|
||||
|
||||
## 九、相关文档
|
||||
|
||||
- [decide 详细设计入口](README.md)
|
||||
- [CLI 规格](cli.md)
|
||||
- [HTTP 服务设计](server.md)
|
||||
- [数据格式文档](../../formats/data.md)
|
||||
528
aide-program/docs/commands/decide/verification.md
Normal file
528
aide-program/docs/commands/decide/verification.md
Normal file
@@ -0,0 +1,528 @@
|
||||
# 验证清单(实现完成后的自检)
|
||||
|
||||
本清单用于在实现 `aide decide` 后进行验证,确保行为符合设计文档与 plugin 契约。
|
||||
|
||||
## 一、准备条件
|
||||
|
||||
- 已执行 `aide init`,确保 `.aide/` 目录存在
|
||||
- 准备测试用的 JSON 数据(见附录)
|
||||
- 确保端口 3721 未被占用(或准备测试端口探测)
|
||||
|
||||
## 二、CLI 用例
|
||||
|
||||
### 2.1 正常提交待定项
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 执行 `aide decide '<valid_json>'`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 输出包含 `→ Web 服务已启动`
|
||||
- 输出包含 `→ 请访问: http://localhost:3721`(或其他端口)
|
||||
- 输出包含 `→ 等待用户完成决策...`
|
||||
- `.aide/decisions/pending.json` 被创建
|
||||
- 服务阻塞等待
|
||||
|
||||
### 2.2 JSON 解析失败
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 执行 `aide decide 'invalid json'`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 输出 `✗ JSON 解析失败: ...`
|
||||
- 退出码为 1
|
||||
- 不创建 pending.json
|
||||
- 不启动服务
|
||||
|
||||
### 2.3 数据验证失败
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 执行 `aide decide '{"task":"test"}'`(缺少必填字段)
|
||||
|
||||
**期望**:
|
||||
|
||||
- 输出 `✗ 数据验证失败: ...`
|
||||
- 错误信息明确指出缺少的字段
|
||||
- 退出码为 1
|
||||
|
||||
### 2.4 获取决策结果(无数据)
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 确保 `.aide/decisions/` 为空
|
||||
2. 执行 `aide decide result`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 输出 `✗ 未找到待定项数据`
|
||||
- 退出码为 1
|
||||
|
||||
### 2.5 获取决策结果(有 pending 无 result)
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 执行 `aide decide '<json>'` 并立即中断(Ctrl+C)
|
||||
2. 执行 `aide decide result`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 输出 `✗ 尚无决策结果`
|
||||
- 退出码为 1
|
||||
|
||||
### 2.6 获取决策结果(正常)
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 完成一次完整的决策流程
|
||||
2. 执行 `aide decide result`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 输出 JSON 格式的决策结果
|
||||
- JSON 可被正确解析
|
||||
- 包含所有待定项的决策
|
||||
- 退出码为 0
|
||||
|
||||
## 三、HTTP 服务用例
|
||||
|
||||
### 3.1 端口探测
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 占用端口 3721(如 `nc -l 3721`)
|
||||
2. 执行 `aide decide '<json>'`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 服务在 3722 端口启动
|
||||
- 输出显示正确的端口号
|
||||
|
||||
### 3.2 端口全部被占用
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 占用端口 3721-3730
|
||||
2. 执行 `aide decide '<json>'`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 输出 `✗ 无法启动服务: 端口 3721-3730 均被占用`
|
||||
- 退出码为 1
|
||||
|
||||
### 3.3 GET /api/items
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 启动服务
|
||||
2. 使用 curl 访问 `http://localhost:3721/api/items`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 返回 200 状态码
|
||||
- Content-Type 为 `application/json`
|
||||
- 返回的 JSON 与 pending.json 内容一致
|
||||
|
||||
### 3.4 POST /api/submit(正常)
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 启动服务
|
||||
2. 使用 curl 提交决策:
|
||||
```bash
|
||||
curl -X POST http://localhost:3721/api/submit \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"decisions":[{"id":1,"chosen":"jwt"}]}'
|
||||
```
|
||||
|
||||
**期望**:
|
||||
|
||||
- 返回 200 状态码
|
||||
- 返回 `{"success":true,"message":"决策已保存"}`
|
||||
- 服务关闭
|
||||
- 历史记录文件被创建
|
||||
|
||||
### 3.5 POST /api/submit(数据不完整)
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 启动服务(待定项有 2 个)
|
||||
2. 提交只包含 1 个决策的数据
|
||||
|
||||
**期望**:
|
||||
|
||||
- 返回 400 状态码
|
||||
- 返回错误信息指出缺少的决策
|
||||
- 服务不关闭
|
||||
|
||||
### 3.6 静态资源服务
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 启动服务
|
||||
2. 访问 `http://localhost:3721/`
|
||||
3. 访问 `http://localhost:3721/style.css`
|
||||
4. 访问 `http://localhost:3721/app.js`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 所有资源返回 200 状态码
|
||||
- Content-Type 正确
|
||||
- 内容非空
|
||||
|
||||
### 3.7 404 处理
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 启动服务
|
||||
2. 访问 `http://localhost:3721/nonexistent`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 返回 404 状态码
|
||||
|
||||
### 3.8 服务中断(Ctrl+C)
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 启动服务
|
||||
2. 按 Ctrl+C
|
||||
|
||||
**期望**:
|
||||
|
||||
- 服务正常关闭
|
||||
- 无异常输出
|
||||
- 退出码为 0 或 130(SIGINT)
|
||||
|
||||
## 四、Web 前端用例
|
||||
|
||||
### 4.1 页面加载
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 启动服务
|
||||
2. 在浏览器中打开页面
|
||||
|
||||
**期望**:
|
||||
|
||||
- 页面正常渲染
|
||||
- 显示任务名称和来源
|
||||
- 显示所有待定项
|
||||
- 提交按钮为禁用状态
|
||||
|
||||
### 4.2 选项选择
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 点击某个选项
|
||||
|
||||
**期望**:
|
||||
|
||||
- 选项被选中(视觉反馈)
|
||||
- 进度更新(如 "已完成 1/2 项")
|
||||
- 若所有项已选择,提交按钮启用
|
||||
|
||||
### 4.3 推荐选项显示
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 提交包含 recommend 字段的数据
|
||||
2. 查看页面
|
||||
|
||||
**期望**:
|
||||
|
||||
- 推荐选项有特殊标记
|
||||
- 卡片头部显示推荐信息
|
||||
|
||||
### 4.4 备注输入
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 在备注框中输入文字
|
||||
2. 提交决策
|
||||
3. 查看决策结果
|
||||
|
||||
**期望**:
|
||||
|
||||
- 备注内容被保存
|
||||
- 决策结果中包含 note 字段
|
||||
|
||||
### 4.5 提交决策
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 选择所有待定项的选项
|
||||
2. 点击提交按钮
|
||||
|
||||
**期望**:
|
||||
|
||||
- 显示提交中状态
|
||||
- 提交成功后显示成功提示
|
||||
- 页面提示可以关闭
|
||||
|
||||
### 4.6 提交失败处理
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 模拟服务端错误(如断开网络)
|
||||
2. 点击提交按钮
|
||||
|
||||
**期望**:
|
||||
|
||||
- 显示错误提示
|
||||
- 提交按钮恢复可用
|
||||
- 可以重试
|
||||
|
||||
### 4.7 浏览器兼容性
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 在 Chrome、Firefox、Safari、Edge 中分别测试
|
||||
|
||||
**期望**:
|
||||
|
||||
- 页面正常渲染
|
||||
- 交互功能正常
|
||||
- 无 JavaScript 错误
|
||||
|
||||
## 五、数据存储用例
|
||||
|
||||
### 5.1 pending.json 创建
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 执行 `aide decide '<json>'`
|
||||
2. 检查 `.aide/decisions/pending.json`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 文件存在
|
||||
- JSON 格式正确
|
||||
- 包含 `_meta` 字段
|
||||
- `_meta.session_id` 格式正确
|
||||
|
||||
### 5.2 历史记录创建
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 完成一次决策流程
|
||||
2. 检查 `.aide/decisions/` 目录
|
||||
|
||||
**期望**:
|
||||
|
||||
- 存在 `{session_id}.json` 文件
|
||||
- 文件包含 input、output、completed_at 字段
|
||||
- session_id 与 pending.json 中的一致
|
||||
|
||||
### 5.3 pending.json 覆盖
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 执行 `aide decide '<json1>'`
|
||||
2. 中断服务
|
||||
3. 执行 `aide decide '<json2>'`
|
||||
4. 检查 pending.json
|
||||
|
||||
**期望**:
|
||||
|
||||
- pending.json 内容为 json2
|
||||
- session_id 已更新
|
||||
|
||||
### 5.4 原子写入验证
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 在写入过程中模拟中断
|
||||
2. 检查文件状态
|
||||
|
||||
**期望**:
|
||||
|
||||
- 文件要么是完整的旧内容,要么是完整的新内容
|
||||
- 不会出现部分写入的损坏状态
|
||||
|
||||
## 六、端到端用例
|
||||
|
||||
### 6.1 完整流程
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 执行 `aide decide '<json>'`
|
||||
2. 在浏览器中打开链接
|
||||
3. 选择所有选项
|
||||
4. 添加备注
|
||||
5. 点击提交
|
||||
6. 执行 `aide decide result`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 整个流程无错误
|
||||
- 决策结果正确反映用户选择
|
||||
- 备注内容正确保存
|
||||
|
||||
### 6.2 与 /aide:prep 集成
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 模拟 LLM 调用流程
|
||||
2. 构造待定项 JSON
|
||||
3. 调用 aide decide
|
||||
4. 完成决策
|
||||
5. 获取结果
|
||||
|
||||
**期望**:
|
||||
|
||||
- 流程符合 plugin 契约
|
||||
- 输出格式便于 LLM 解析
|
||||
|
||||
## 七、异常用例
|
||||
|
||||
### 7.1 .aide 目录不存在
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 删除 .aide 目录
|
||||
2. 执行 `aide decide '<json>'`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 输出 `✗ .aide 目录不存在,请先执行 aide init`
|
||||
- 退出码为 1
|
||||
|
||||
### 7.2 pending.json 损坏
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 手动写入无效 JSON 到 pending.json
|
||||
2. 执行 `aide decide result`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 输出解析错误信息
|
||||
- 建议重新执行 aide decide
|
||||
|
||||
### 7.3 超大 JSON 数据
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 构造包含 100 个待定项的 JSON
|
||||
2. 执行 `aide decide '<json>'`
|
||||
|
||||
**期望**:
|
||||
|
||||
- 正常处理或给出合理的限制提示
|
||||
- 不会导致内存溢出或崩溃
|
||||
|
||||
### 7.4 特殊字符处理
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. 在 task、title、note 中包含特殊字符(中文、emoji、引号、换行)
|
||||
2. 完成决策流程
|
||||
|
||||
**期望**:
|
||||
|
||||
- 特殊字符正确保存和显示
|
||||
- 不会导致 JSON 解析错误
|
||||
|
||||
## 八、附录:测试数据
|
||||
|
||||
### 8.1 最小有效数据
|
||||
|
||||
```json
|
||||
{
|
||||
"task": "测试任务",
|
||||
"source": "test.md",
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "测试问题",
|
||||
"options": [
|
||||
{"value": "a", "label": "选项A"},
|
||||
{"value": "b", "label": "选项B"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 完整数据
|
||||
|
||||
```json
|
||||
{
|
||||
"task": "实现用户认证模块",
|
||||
"source": "task-now.md",
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "认证方式选择",
|
||||
"location": {
|
||||
"file": "task-now.md",
|
||||
"start": 5,
|
||||
"end": 7
|
||||
},
|
||||
"context": "任务描述中未明确指定认证方式,需要确认",
|
||||
"options": [
|
||||
{
|
||||
"value": "jwt",
|
||||
"label": "JWT Token 认证",
|
||||
"score": 85,
|
||||
"pros": ["无状态", "易于扩展", "跨域友好"],
|
||||
"cons": ["Token 无法主动失效", "需要处理刷新"]
|
||||
},
|
||||
{
|
||||
"value": "session",
|
||||
"label": "Session 认证",
|
||||
"score": 70,
|
||||
"pros": ["实现简单", "可主动失效"],
|
||||
"cons": ["需要存储", "扩展性差"]
|
||||
}
|
||||
],
|
||||
"recommend": "jwt"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "密码加密算法",
|
||||
"context": "选择密码存储的加密算法",
|
||||
"options": [
|
||||
{
|
||||
"value": "bcrypt",
|
||||
"label": "bcrypt",
|
||||
"score": 90,
|
||||
"pros": ["安全性高", "自带盐值"],
|
||||
"cons": ["计算较慢"]
|
||||
},
|
||||
{
|
||||
"value": "argon2",
|
||||
"label": "Argon2",
|
||||
"score": 95,
|
||||
"pros": ["最新标准", "抗GPU攻击"],
|
||||
"cons": ["库支持较少"]
|
||||
}
|
||||
],
|
||||
"recommend": "bcrypt"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 期望的决策结果
|
||||
|
||||
```json
|
||||
{
|
||||
"decisions": [
|
||||
{"id": 1, "chosen": "jwt"},
|
||||
{"id": 2, "chosen": "bcrypt", "note": "团队更熟悉 bcrypt"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 九、相关文档
|
||||
|
||||
- [decide 详细设计入口](README.md)
|
||||
- [CLI 规格](cli.md)
|
||||
- [HTTP 服务设计](server.md)
|
||||
- [Web 前端设计](web.md)
|
||||
- [数据存储设计](storage.md)
|
||||
560
aide-program/docs/commands/decide/web.md
Normal file
560
aide-program/docs/commands/decide/web.md
Normal file
@@ -0,0 +1,560 @@
|
||||
# aide decide Web 前端设计
|
||||
|
||||
## 一、概述
|
||||
|
||||
aide decide 的 Web 前端提供待定项确认界面,供用户选择选项并提交决策。
|
||||
|
||||
### 1.1 技术选型
|
||||
|
||||
| 技术 | 选择 | 理由 |
|
||||
|------|------|------|
|
||||
| 框架 | 纯 HTML/CSS/JS | 无需构建工具,无 Node.js 依赖 |
|
||||
| 样式 | 原生 CSS | 简单场景无需预处理器 |
|
||||
| 交互 | 原生 JavaScript | 避免引入框架依赖 |
|
||||
|
||||
### 1.2 设计原则
|
||||
|
||||
- **简洁**:聚焦核心功能,无多余装饰
|
||||
- **清晰**:信息层次分明,操作明确
|
||||
- **可用**:支持主流浏览器,无障碍友好
|
||||
|
||||
## 二、页面结构
|
||||
|
||||
### 2.1 整体布局
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 页面头部 │ │
|
||||
│ │ 标题: Aide 待定项确认 │ │
|
||||
│ │ 任务: <task> │ │
|
||||
│ │ 来源: <source> │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 待定项列表 │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────────────────────────────────────┐ │ │
|
||||
│ │ │ 待定项卡片 1 │ │ │
|
||||
│ │ │ ... │ │ │
|
||||
│ │ └─────────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────────────────────────────────────┐ │ │
|
||||
│ │ │ 待定项卡片 2 │ │ │
|
||||
│ │ │ ... │ │ │
|
||||
│ │ └─────────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 页面底部 │ │
|
||||
│ │ [提交决策] │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 待定项卡片结构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 1. <title> [推荐: xxx] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ <context> │
|
||||
│ │
|
||||
│ 位置: <file>:<start>-<end> │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 选项: │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ ○ <option_a.label> 评分: 85 │ │
|
||||
│ │ 优点: xxx, xxx │ │
|
||||
│ │ 缺点: xxx │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ ● <option_b.label> ← 已选中 评分: 70│ │
|
||||
│ │ 优点: xxx │ │
|
||||
│ │ 缺点: xxx, xxx │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 备注(可选): │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 三、HTML 结构原型
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Aide 待定项确认</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- 页面头部 -->
|
||||
<header class="header">
|
||||
<h1>Aide 待定项确认</h1>
|
||||
<div class="task-info">
|
||||
<p><strong>任务:</strong> <span id="task-name"></span></p>
|
||||
<p><strong>来源:</strong> <span id="task-source"></span></p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 待定项列表 -->
|
||||
<main class="items-container" id="items-container">
|
||||
<!-- 动态生成待定项卡片 -->
|
||||
</main>
|
||||
|
||||
<!-- 页面底部 -->
|
||||
<footer class="footer">
|
||||
<div class="progress">
|
||||
<span id="progress-text">已完成 0/0 项</span>
|
||||
</div>
|
||||
<button class="submit-btn" id="submit-btn" disabled>
|
||||
提交决策
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- 提交成功提示 -->
|
||||
<div class="success-overlay" id="success-overlay" hidden>
|
||||
<div class="success-message">
|
||||
<h2>决策已提交</h2>
|
||||
<p>您可以关闭此页面</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 3.1 待定项卡片模板
|
||||
|
||||
```html
|
||||
<article class="item-card" data-item-id="{id}">
|
||||
<header class="item-header">
|
||||
<h2 class="item-title">
|
||||
<span class="item-number">{id}.</span>
|
||||
{title}
|
||||
</h2>
|
||||
<span class="recommend-badge" data-show="{hasRecommend}">
|
||||
推荐: {recommend}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<div class="item-context" data-show="{hasContext}">
|
||||
{context}
|
||||
</div>
|
||||
|
||||
<div class="item-location" data-show="{hasLocation}">
|
||||
位置: {file}:{start}-{end}
|
||||
</div>
|
||||
|
||||
<div class="options-list">
|
||||
<!-- 选项列表 -->
|
||||
</div>
|
||||
|
||||
<div class="item-note">
|
||||
<label for="note-{id}">备注(可选):</label>
|
||||
<textarea id="note-{id}" placeholder="添加补充说明..."></textarea>
|
||||
</div>
|
||||
</article>
|
||||
```
|
||||
|
||||
### 3.2 选项模板
|
||||
|
||||
```html
|
||||
<label class="option-item" data-value="{value}" data-recommended="{isRecommended}">
|
||||
<input type="radio" name="item-{itemId}" value="{value}">
|
||||
<div class="option-content">
|
||||
<div class="option-header">
|
||||
<span class="option-label">{label}</span>
|
||||
<span class="option-score" data-show="{hasScore}">
|
||||
评分: {score}
|
||||
</span>
|
||||
</div>
|
||||
<div class="option-details" data-show="{hasDetails}">
|
||||
<div class="option-pros" data-show="{hasPros}">
|
||||
<strong>优点:</strong> {pros}
|
||||
</div>
|
||||
<div class="option-cons" data-show="{hasCons}">
|
||||
<strong>缺点:</strong> {cons}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
```
|
||||
|
||||
## 四、CSS 样式规范
|
||||
|
||||
### 4.1 设计变量
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* 颜色 */
|
||||
--color-primary: #2563eb;
|
||||
--color-primary-hover: #1d4ed8;
|
||||
--color-success: #16a34a;
|
||||
--color-warning: #ca8a04;
|
||||
--color-error: #dc2626;
|
||||
--color-text: #1f2937;
|
||||
--color-text-secondary: #6b7280;
|
||||
--color-border: #e5e7eb;
|
||||
--color-background: #f9fafb;
|
||||
--color-card: #ffffff;
|
||||
|
||||
/* 间距 */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
|
||||
/* 圆角 */
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
|
||||
/* 阴影 */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
|
||||
/* 字体 */
|
||||
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
--font-size-sm: 14px;
|
||||
--font-size-md: 16px;
|
||||
--font-size-lg: 18px;
|
||||
--font-size-xl: 24px;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 关键样式类
|
||||
|
||||
| 类名 | 用途 |
|
||||
|------|------|
|
||||
| `.container` | 页面容器,最大宽度 800px,居中 |
|
||||
| `.header` | 页面头部 |
|
||||
| `.item-card` | 待定项卡片 |
|
||||
| `.item-card.completed` | 已选择的卡片(边框高亮) |
|
||||
| `.option-item` | 选项容器 |
|
||||
| `.option-item.selected` | 已选中的选项(背景高亮) |
|
||||
| `.option-item[data-recommended="true"]` | 推荐选项(特殊标记) |
|
||||
| `.submit-btn` | 提交按钮 |
|
||||
| `.submit-btn:disabled` | 禁用状态(未完成所有选择) |
|
||||
| `.success-overlay` | 提交成功遮罩 |
|
||||
|
||||
### 4.3 响应式设计
|
||||
|
||||
虽然主要面向桌面浏览器,但应保证基本的响应式支持:
|
||||
|
||||
```css
|
||||
/* 小屏幕适配 */
|
||||
@media (max-width: 640px) {
|
||||
.container {
|
||||
padding: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.item-card {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.option-details {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 五、JavaScript 交互逻辑
|
||||
|
||||
### 5.1 应用状态
|
||||
|
||||
```javascript
|
||||
const AppState = {
|
||||
task: "", // 任务名称
|
||||
source: "", // 来源文档
|
||||
items: [], // 待定项列表
|
||||
decisions: {}, // 当前决策 { itemId: chosenValue }
|
||||
notes: {}, // 用户备注 { itemId: noteText }
|
||||
isSubmitting: false // 提交中标志
|
||||
};
|
||||
```
|
||||
|
||||
### 5.2 核心函数
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 初始化应用
|
||||
* 1. 从 API 加载数据
|
||||
* 2. 渲染界面
|
||||
* 3. 绑定事件
|
||||
*/
|
||||
async function init(): void
|
||||
|
||||
/**
|
||||
* 加载待定项数据
|
||||
* GET /api/items
|
||||
*/
|
||||
async function loadItems(): DecideInput
|
||||
|
||||
/**
|
||||
* 渲染待定项列表
|
||||
*/
|
||||
function renderItems(data: DecideInput): void
|
||||
|
||||
/**
|
||||
* 渲染单个待定项卡片
|
||||
*/
|
||||
function renderItemCard(item: DecideItem): HTMLElement
|
||||
|
||||
/**
|
||||
* 渲染选项列表
|
||||
*/
|
||||
function renderOptions(item: DecideItem): HTMLElement
|
||||
|
||||
/**
|
||||
* 处理选项选择
|
||||
*/
|
||||
function handleOptionSelect(itemId: number, value: string): void
|
||||
|
||||
/**
|
||||
* 处理备注输入
|
||||
*/
|
||||
function handleNoteInput(itemId: number, note: string): void
|
||||
|
||||
/**
|
||||
* 更新进度显示
|
||||
*/
|
||||
function updateProgress(): void
|
||||
|
||||
/**
|
||||
* 检查是否可以提交
|
||||
*/
|
||||
function canSubmit(): boolean
|
||||
|
||||
/**
|
||||
* 提交决策
|
||||
* POST /api/submit
|
||||
*/
|
||||
async function submitDecisions(): void
|
||||
|
||||
/**
|
||||
* 显示成功提示
|
||||
*/
|
||||
function showSuccess(): void
|
||||
|
||||
/**
|
||||
* 显示错误提示
|
||||
*/
|
||||
function showError(message: string): void
|
||||
```
|
||||
|
||||
### 5.3 事件绑定
|
||||
|
||||
```javascript
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
// 选项选择事件(事件委托)
|
||||
document.getElementById('items-container').addEventListener('change', (e) => {
|
||||
if (e.target.type === 'radio') {
|
||||
const itemId = parseInt(e.target.name.replace('item-', ''));
|
||||
const value = e.target.value;
|
||||
handleOptionSelect(itemId, value);
|
||||
}
|
||||
});
|
||||
|
||||
// 备注输入事件(事件委托)
|
||||
document.getElementById('items-container').addEventListener('input', (e) => {
|
||||
if (e.target.tagName === 'TEXTAREA') {
|
||||
const itemId = parseInt(e.target.id.replace('note-', ''));
|
||||
handleNoteInput(itemId, e.target.value);
|
||||
}
|
||||
});
|
||||
|
||||
// 提交按钮点击
|
||||
document.getElementById('submit-btn').addEventListener('click', submitDecisions);
|
||||
```
|
||||
|
||||
### 5.4 交互流程
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
:页面加载;
|
||||
|
||||
:调用 init();
|
||||
|
||||
:GET /api/items;
|
||||
|
||||
if (加载成功?) then (是)
|
||||
:渲染待定项列表;
|
||||
:绑定事件监听;
|
||||
else (否)
|
||||
:显示错误信息;
|
||||
stop
|
||||
endif
|
||||
|
||||
repeat
|
||||
:用户操作;
|
||||
switch (操作类型)
|
||||
case (选择选项)
|
||||
:更新 decisions;
|
||||
:高亮选中选项;
|
||||
:更新进度;
|
||||
case (输入备注)
|
||||
:更新 notes;
|
||||
case (点击提交)
|
||||
if (所有项已选择?) then (是)
|
||||
:禁用提交按钮;
|
||||
:POST /api/submit;
|
||||
if (提交成功?) then (是)
|
||||
:显示成功提示;
|
||||
stop
|
||||
else (否)
|
||||
:显示错误信息;
|
||||
:恢复提交按钮;
|
||||
endif
|
||||
else (否)
|
||||
:提示完成所有选择;
|
||||
endif
|
||||
endswitch
|
||||
repeat while (继续操作?)
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
## 六、错误处理
|
||||
|
||||
### 6.1 加载错误
|
||||
|
||||
```javascript
|
||||
async function loadItems() {
|
||||
try {
|
||||
const response = await fetch('/api/items');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
showError('无法加载待定项数据,请刷新页面重试');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 提交错误
|
||||
|
||||
```javascript
|
||||
async function submitDecisions() {
|
||||
try {
|
||||
const response = await fetch('/api/submit', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(buildDecisionData())
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || '提交失败');
|
||||
}
|
||||
|
||||
showSuccess();
|
||||
} catch (error) {
|
||||
showError(`提交失败: ${error.message}`);
|
||||
AppState.isSubmitting = false;
|
||||
updateSubmitButton();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 错误提示样式
|
||||
|
||||
```html
|
||||
<div class="error-toast" id="error-toast" hidden>
|
||||
<span class="error-icon">✗</span>
|
||||
<span class="error-message" id="error-message"></span>
|
||||
</div>
|
||||
```
|
||||
|
||||
## 七、无障碍支持
|
||||
|
||||
### 7.1 语义化 HTML
|
||||
|
||||
- 使用 `<header>`, `<main>`, `<footer>`, `<article>` 等语义标签
|
||||
- 使用 `<label>` 关联表单控件
|
||||
- 使用 `<fieldset>` 和 `<legend>` 组织选项组
|
||||
|
||||
### 7.2 键盘导航
|
||||
|
||||
- 所有交互元素可通过 Tab 键访问
|
||||
- 选项可通过方向键切换
|
||||
- Enter 键可触发提交
|
||||
|
||||
### 7.3 ARIA 属性
|
||||
|
||||
```html
|
||||
<main role="main" aria-label="待定项列表">
|
||||
<article role="group" aria-labelledby="item-title-1">
|
||||
<h2 id="item-title-1">...</h2>
|
||||
<div role="radiogroup" aria-label="选项">
|
||||
...
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<button aria-disabled="true" aria-describedby="submit-hint">
|
||||
提交决策
|
||||
</button>
|
||||
<span id="submit-hint" class="sr-only">
|
||||
请先完成所有待定项的选择
|
||||
</span>
|
||||
```
|
||||
|
||||
## 八、浏览器兼容性
|
||||
|
||||
### 8.1 目标浏览器
|
||||
|
||||
| 浏览器 | 最低版本 |
|
||||
|--------|----------|
|
||||
| Chrome | 80+ |
|
||||
| Firefox | 75+ |
|
||||
| Safari | 13+ |
|
||||
| Edge | 80+ |
|
||||
|
||||
### 8.2 避免使用的特性
|
||||
|
||||
- CSS Grid 子网格(subgrid)
|
||||
- CSS 容器查询(container queries)
|
||||
- JavaScript 可选链操作符(?.)在旧版本中不支持
|
||||
- Top-level await
|
||||
|
||||
### 8.3 Polyfill 策略
|
||||
|
||||
不引入 polyfill,通过避免新特性保证兼容性。
|
||||
|
||||
## 九、文件清单
|
||||
|
||||
```
|
||||
aide/decide/web/
|
||||
├── index.html # 主页面
|
||||
├── style.css # 样式文件
|
||||
└── app.js # 交互逻辑
|
||||
```
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- [decide 详细设计入口](README.md)
|
||||
- [HTTP 服务设计](server.md)
|
||||
- [数据格式文档](../../formats/data.md)
|
||||
@@ -1,7 +1,8 @@
|
||||
1. 首先阅读 @statements/optimize.md,还有 @README.md 并根据其指引了解跟aide flow有关的所有信息
|
||||
2. 我想对aide flow开始详细设计,我将在完成设计后将所有设计文档打包交给其他开发人员进行开发,
|
||||
1. 首先阅读 @statements/optimize.md,还有 @README.md 并根据其指引了解跟aide decide有关的所有信息
|
||||
2. 对aide decide开始详细设计,我将在完成设计后将所有设计文档打包交给其他开发人员进行开发,
|
||||
- 设计文档中要有一个用于指导接手人员的入口文档,其中要说明:需要了解哪些信息、需要做什么事
|
||||
|
||||
对开发文档完善程度的预期是:可以仅靠整套文档和可能含有的其他附属文件的内容,以此为确定目标和完整指导,开始目标程序的实际开发工作,直到构建产出程序和通过测试运行
|
||||
对开发文档完善程度的预期是:可以仅靠整套文档和可能含有的其他附属文件的内容(我将会打包aide-program目录),以此为确定目标和完整指导(aide-program/docs/ 目录下的相关文档),开始目标程序的实际开发工作,直到构建产出程序和通过测试运行(运行aide decide -h 及其一系列命令与参数)
|
||||
|
||||
切记,非常重要:开发文档中不允许给出代码片段、实现代码,最多只能出现类型与数据结构伪代码原型、方法签名、业务逻辑流程的plantuml流程图、API约定
|
||||
|
||||
|
||||
Reference in New Issue
Block a user