✨ feat: 完成aide decide设计文档
This commit is contained in:
@@ -1,5 +1,16 @@
|
|||||||
# aide decide 子命令设计文档
|
# 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 解决的问题
|
### 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有关的所有信息
|
1. 首先阅读 @statements/optimize.md,还有 @README.md 并根据其指引了解跟aide decide有关的所有信息
|
||||||
2. 我想对aide flow开始详细设计,我将在完成设计后将所有设计文档打包交给其他开发人员进行开发,
|
2. 对aide decide开始详细设计,我将在完成设计后将所有设计文档打包交给其他开发人员进行开发,
|
||||||
|
- 设计文档中要有一个用于指导接手人员的入口文档,其中要说明:需要了解哪些信息、需要做什么事
|
||||||
|
|
||||||
对开发文档完善程度的预期是:可以仅靠整套文档和可能含有的其他附属文件的内容,以此为确定目标和完整指导,开始目标程序的实际开发工作,直到构建产出程序和通过测试运行
|
对开发文档完善程度的预期是:可以仅靠整套文档和可能含有的其他附属文件的内容(我将会打包aide-program目录),以此为确定目标和完整指导(aide-program/docs/ 目录下的相关文档),开始目标程序的实际开发工作,直到构建产出程序和通过测试运行(运行aide decide -h 及其一系列命令与参数)
|
||||||
|
|
||||||
切记,非常重要:开发文档中不允许给出代码片段、实现代码,最多只能出现类型与数据结构伪代码原型、方法签名、业务逻辑流程的plantuml流程图、API约定
|
切记,非常重要:开发文档中不允许给出代码片段、实现代码,最多只能出现类型与数据结构伪代码原型、方法签名、业务逻辑流程的plantuml流程图、API约定
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user