diff --git a/docs/sdlc-architecture.md b/docs/sdlc-architecture.md
new file mode 100644
index 0000000..f17c1bc
--- /dev/null
+++ b/docs/sdlc-architecture.md
@@ -0,0 +1,159 @@
+# 开发产线 (SDLC Pipeline) 架构概览
+
+## 客户可以输入什么
+
+开发产线的入口是「项目管理」,客户分多阶段输入:
+
+### 项目创建阶段
+- 项目名称、描述、项目类型 (web_app / mobile / api_service)
+- 技术栈配置 (JSON: 框架、语言、数据库等)
+- 代码仓库 URL
+- 关联的 Pipeline 定义 ID(选哪个产线模板)
+
+### 迭代创建阶段
+- 迭代名称、迭代类型 (new_feature / bugfix / refactor / upgrade)
+- 迭代范围 (JSON: 功能点、用户故事)
+- 优先级 (1-10)
+- 创建迭代时**自动触发 Pipeline 任务**(project.py:auto-submit)
+
+### 各阶段人工交互输入
+| 阶段 | 步骤 | 输入内容 |
+|------|------|----------|
+| 需求 | requirement_gathering | 需求文档表单 |
+| 需求 | requirement_review | 审批 (approve/reject) |
+| 设计 | table_design | 确认 LLM 生成的表结构 |
+| 设计 | crud_design | 确认 LLM 生成的 CRUD 定义 |
+| 设计 | api_design | 确认 LLM 生成的 API 规范 |
+| 设计 | design_review | 架构负责人审批 |
+| 开发 | code_review | 高级开发审批代码 |
+| 开发 | code_fix | 提交修复说明 |
+| 测试 | test_plan_create | 编写测试方案 |
+| 测试 | bug_fix | 修复说明 + commit SHA |
+| 测试 | acceptance_test | 产品经理验收 |
+| 部署 | deploy_env_collect | SSH 信息 + 数据库连接 |
+| 部署 | deploy_test_approve | 发布经理确认 |
+| 运维 | incident_response | 故障处理方案 |
+| 升级 | upgrade_plan | 新版本规划 |
+
+### 统一输入形式(驾驶舱模式)
+- **正文输入**:多行文本框,支持自然语言
+- **文件附件**:上传需求文档、设计稿、代码文件等
+- 类似与大模型对话的交互体验
+
+## 客户应该看到什么
+
+### 仪表盘驾驶舱
+- 项目总数 / 活跃数
+- 活跃迭代数
+- 待处理 Bug(按严重程度分布:critical/major/minor/trivial)
+- 待审批数
+- 最近 5 个迭代及进度条
+
+### 时间线面板
+- 产线步骤的线性时间线
+- 实时状态更新(completed/running/pending/waiting/failed)
+- 点击步骤展开详情(产物、日志、审批表单)
+- 按阶段分组(需求/设计/开发/测试/部署/运维)
+
+### 对话面板
+- Agent ↔ 用户对话历史
+- Agent 自动推送:分析结果、设计产出预览、测试结果
+- 用户在输入框中回复、审批、补充信息
+- 系统事件通知(步骤开始/完成)
+
+### 步骤详情弹窗
+- 步骤描述和配置
+- 输入/输出 artifact(JSON 树形查看)
+- 执行日志
+- human_task:表单 + 提交按钮
+- approval_gate:approve / reject 按钮
+- failed:错误详情 + 重试
+
+### 项目管理页面
+- 项目列表:状态、类型、关联产线
+- 迭代列表:状态流转 (planning → in_progress → testing → completed)
+- Bug 管理:严重程度、状态、关联迭代、提交人类型
+
+### 测试/部署页面
+- 测试用例列表、结果统计
+- 测试计划、部署环境配置
+
+## 产线信息交互
+
+当前开发产线定义了 **7 个阶段、28 个步骤类型**:
+
+| 阶段 | 步骤数 | 自动 | 人工 | 审批 |
+|------|--------|------|------|------|
+| Phase 1: 需求 | 2 | 0 | 1 | 1 |
+| Phase 2: 设计 | 4 | 3 | 1 | 1 |
+| Phase 3: 开发 | 5 | 3 | 1 | 1 |
+| Phase 4: 测试 | 8 | 4 | 3 | 0 |
+| Phase 5: 部署 | 6 | 3 | 1 | 1 |
+| Phase 6: 运维 | 2 | 1 | 1 | 0 |
+| Phase 7: 升级 | 1 | 0 | 1 | 0 |
+
+### DAG 依赖图
+```
+requirement_gathering → requirement_review
+ → table_design ──┬──→ design_review
+ → crud_design ──┤
+ → api_design ──┘
+design_review → code_generate → code_compliance_check
+ → code_auto_fix → code_review
+code_review → test_plan_create → test_case_generate
+ → functional_test ──┬──→ bug_report
+ → performance_test──┘
+bug_report → bug_fix → bug_verify → acceptance_test
+acceptance_test → deploy_env_collect → deploy_test
+ → deploy_test_verify → deploy_test_approve
+deploy_test_approve → deploy_production
+ → deploy_production_verify → monitor
+monitor → incident_response (if alerts)
+```
+
+### 步骤间信息传递
+信息通过 artifact 传递 — handler 的 output_data 存入 `pipeline_artifacts` 表,
+下游通过 `input_data[dep_step_name]` 获取上游产物。
+
+### 对话层信息流
+- 用户在驾驶舱输入框发送消息 → 存储到 sd_conversations → DSPY 处理
+- Agent 分析后可能在对话中回复,同时触发产线步骤流转
+- 关键步骤完成时 Agent 推送结果到对话面板
+
+## 开发规范遵循
+
+### 模块架构
+- 产线专属 UI(pipeline-sdlc)与引擎层(pipeline_task)严格分离
+- 跨模块调用:`entire_url('/pipeline_task/api/task_list.dspy')` 绝对路径
+- pipeline_task 零数据表,纯薄交互层
+
+### UI 规范
+- index.ui 统一三层布局:Title (cheight:6) → 卡片区 (cheight:18) → VScrollPanel (css:filler)
+- 白底卡片 + SVG 图标(严禁暗色主题、emoji、backgroundColor)
+- Tabular popup → Tree 步骤树弹窗
+- toolbar 按钮 script 中用 `this` 取选中行
+
+### DSPY 规范
+- 零 import(预加载:json、DBPools、get_user、params_kw、getID)
+- `return` 代替 `print`
+- `getID()` 代替 `uuid()`
+- `await get_user()` 不是 `get_user()`
+- 禁 `ServerEnv()`,用 `request._run_ns`
+- `sor.U()` 只要 2 个参数 (table, data_dict)
+- `await sor.sqlExe(sql, {})` 第二参数必须是 dict
+
+### models/*.json 规范
+- summary[0].primary 必须是数组
+- float/double 字段必须有 len + dec
+- indexes idxfields 必须是数组
+- codes 引用 appcodes_kv 必须用 parentid= 不是 id=
+
+### CRUD json/*.json 规范
+- 根键必须是 `tblname` + `params`
+- editable 必须有 new/update/delete_data_url
+- 使用 `{{entire_url('../api/xxx.dspy')}}` 格式
+
+### 部署规范(铁律)
+- 本地改 → git commit → git push → 服务器 git pull → pip install(如需) → 重启
+- 禁直接在服务器改源文件
+- curl 200 ≠ 功能正常,必须浏览器验证
diff --git a/docs/sdlc-cockpit-design.md b/docs/sdlc-cockpit-design.md
new file mode 100644
index 0000000..c3d71da
--- /dev/null
+++ b/docs/sdlc-cockpit-design.md
@@ -0,0 +1,179 @@
+# 开发产线 (SDLC Pipeline) 驾驶舱设计文档
+
+## 设计理念
+
+开发产线的用户界面采用**驾驶舱模式**设计。核心理念是:
+
+- **像和大模型对话一样交互**:用户以自然语言描述需求,上传文件,Agent 自动分析和执行
+- **状态信息直线展示**:产线进度以时间线/步骤条形式一目了然
+- **详情按需展开**:点击步骤节点才展开具体内容(产物、日志、审批表单)
+
+## 用户输入
+
+统一为**类大模型交互界面**:
+
+### 输入形式
+```
+┌─────────────────────────────────────────┐
+│ 📎 已附加: design-doc.pdf [×] │
+│ │
+│ ┌─────────────────────────────────────┐ │
+│ │ 请输入你的需求或回复... │ │
+│ │ │ │
+│ │ │ │
+│ └─────────────────────────────────────┘ │
+│ [📎] [发送 ⏎]│
+└─────────────────────────────────────────┘
+```
+
+### 输入组件
+| 组件 | 说明 |
+|------|------|
+| 正文输入框 | 多行文本,支持自然语言描述需求、回复Agent |
+| 文件附件 | 支持上传多个文件(需求文档、设计稿、代码等) |
+| 发送按钮 | 提交后触发 Agent 分析并推入产线 |
+
+### 输入可触发的操作
+- 新建迭代:描述需求 → Agent 自动创建迭代并启动产线
+- 回复人工任务:填写表单 → Agent 继续执行
+- 审批决策:approve / reject → 产线流转
+- 补充信息:代码修复说明、环境配置等
+
+## 驾驶舱布局
+
+```
+┌──────────────────────────────────────────────────────────┐
+│ 驾驶舱标题栏:[项目选择器▾] [迭代选择器▾] [刷新] │
+├──────────────────────────────────────────────────────────┤
+│ 统计卡片行:活跃项目 | 进行中迭代 | 待处理Bug | 待审批 │
+├───────────────────────────┬──────────────────────────────┤
+│ 时间线面板 (40%) │ 对话面板 (60%) │
+│ ┌────────────────────────┐│ ┌──────────────────────────┐ │
+│ │ 🟢 需求采集 ✓ ││ │ 🤖 Agent: 收到需求... │ │
+│ │ 🟢 需求评审 ✓ ││ │ 👤 你: 请增加XX功能 │ │
+│ │ 🟡 表设计 进行中 ││ │ 🤖 Agent: 设计完成, │ │
+│ │ ⚪ CRUD设计 待执行 ││ │ 请确认以下方案... │ │
+│ │ ⚪ API设计 待执行 ││ │ │ │
+│ │ ⚪ 设计评审 阻塞 ││ │ ┌──────────────────────┐ │ │
+│ │ ⚪ 代码生成 待执行 ││ │ │ 输入需求或回复... │ │ │
+│ └────────────────────────┘│ │ │ [📎][发送] │ │ │
+│ │ │ └──────────────────────┘ │ │
+│ [点击步骤展开详情→] │ └──────────────────────────┘ │
+└───────────────────────────┴──────────────────────────────┘
+```
+
+## 时间线设计
+
+### 步骤节点状态
+| 图标 | 状态 | 含义 |
+|------|------|------|
+| 🟢 ✓ | completed | 步骤已完成 |
+| 🟡 ⟳ | running | 正在执行中 |
+| ⚪ ○ | pending | 等待前置步骤完成 |
+| 🔵 ⚠ | waiting | 等待人工输入/审批 |
+| 🔴 ✗ | failed | 执行失败 |
+| ⏭ ⊘ | skipped | 已跳过 |
+| ⏸ ∥ | paused | 已暂停 |
+
+### 时间线交互
+- **直线排列**:步骤按 DAG 拓扑排序后垂直排列,连接线显示依赖关系
+- **当前步骤高亮**:running/waiting 状态的步骤有脉冲动画
+- **点击展开**:点击任意步骤节点,在右侧面板或弹窗中展示详情
+- **详情内容**:
+ - 步骤描述和配置
+ - 输入/输出 artifact(支持 JSON 树形查看)
+ - 执行日志
+ - 如果是 human_task:显示表单或审批按钮
+ - 如果是 failed:显示错误信息和重试按钮
+
+### 步骤分组(时间段)
+```
+▼ 需求阶段
+ 🟢 需求采集
+ 🟢 需求评审
+▼ 设计阶段
+ 🟡 表设计
+ ⚪ CRUD设计
+ ⚪ API设计
+ ⚪ 设计评审
+▼ 开发阶段
+ ⚪ 代码生成
+ ...
+```
+
+## 对话面板设计
+
+### 消息类型
+| 角色 | 样式 | 说明 |
+|------|------|------|
+| 🤖 Agent | 左对齐,深色背景 | 系统自动消息:分析结果、步骤状态变更 |
+| 👤 用户 | 右对齐,蓝色背景 | 用户输入:需求、回复、审批 |
+| ⚡ 系统 | 居中,灰色小字 | 系统事件:步骤开始/完成、Bug上报 |
+
+### Agent 消息内容
+- 需求分析结果
+- 设计产出预览(表结构、CRUD清单)
+- 代码审查报告
+- 测试结果摘要
+- 部署状态
+- **人工任务请求**:表单字段、审批选项
+
+### 用户消息触发
+- 输入框发送 → 创建用户消息 → 调用 DSPY 处理 → Agent 回复
+
+### 对话持久化
+消息存储在数据库表 `sd_conversations` 中,与迭代关联。
+
+## 数据模型
+
+### sd_conversations(对话消息表)
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | str(32) | 主键 |
+| iteration_id | str(32) | 关联迭代 |
+| task_id | str(32) | 关联 Pipeline 任务 |
+| step_name | str(100) | 关联步骤(可为空) |
+| role | str(20) | agent / user / system |
+| content | text | 消息正文 |
+| attachments | text | 附件列表 JSON |
+| created_at | timestamp | 创建时间 |
+
+## 技术实现
+
+### 页面路由
+- `/sd_cockpit/` → 驾驶舱主页面
+- `/sd_cockpit/timeline.dspy` → 时间线数据 API
+- `/sd_cockpit/chat_messages.dspy` → 对话消息 API(GET 读取 / POST 发送)
+- `/sd_cockpit/step_detail.dspy` → 步骤详情 API
+- `/sd_cockpit/dashboard_data.dspy` → 统计卡片数据
+
+### Bricks Widget 映射
+| 设计元素 | Bricks 实现 |
+|----------|------------|
+| 时间线列表 | VBox + 自定义 styled VBox items |
+| 对话面板 | VScrollPanel + VBox message bubbles |
+| 输入框 | Form widget (textarea + submit) |
+| 统计卡片 | ResponsableBox + VBox.card |
+| 步骤详情 | Popup + urlwidget (加载 step_detail.ui) |
+| 文件上传 | Form file field |
+| 项目/迭代选择 | code dropdown widget |
+
+### 跨模块调用
+驾驶舱在 pipeline-sdlc 模块内,调用 pipeline_task API 时使用绝对路径:
+```
+entire_url('/pipeline_task/api/task_submit.dspy')
+entire_url('/pipeline_task/api/task_detail.dspy')
+entire_url('/pipeline_task/api/human_complete.dspy')
+```
+
+## 开发规范遵循
+
+| 规范项 | 应用 |
+|--------|------|
+| 布局标准 | 四卡片 + filler 内容区,白底 SVG 图标 |
+| DSPY 规范 | 零 import、return 代替 print、getID()、await get_user() |
+| 跨模块调用 | 绝对路径 entire_url('/pipeline_task/api/...') |
+| 部署铁律 | 本地改 → commit → push → 服务器 pull → 重启 → 浏览器验证 |
+| DSPY async | 用 request._run_ns,不用 ServerEnv() |
+| sor.U() | 只传2个参数 |
+| 禁止 | 不直接改服务器原文,不动其他模块 |
diff --git a/models/sd_conversations.json b/models/sd_conversations.json
new file mode 100644
index 0000000..39f72be
--- /dev/null
+++ b/models/sd_conversations.json
@@ -0,0 +1,31 @@
+{
+ "summary": [
+ {
+ "name": "sd_conversations",
+ "title": "开发产线对话记录表",
+ "primary": ["id"],
+ "catelog": "entity"
+ }
+ ],
+ "fields": [
+ {"name": "id", "title": "主键ID", "type": "str", "length": 32, "nullable": "no"},
+ {"name": "iteration_id", "title": "迭代ID", "type": "str", "length": 32, "nullable": "no"},
+ {"name": "task_id", "title": "Pipeline任务ID", "type": "str", "length": 32},
+ {"name": "step_name", "title": "关联步骤名", "type": "str", "length": 100},
+ {"name": "role", "title": "角色", "type": "str", "length": 20, "nullable": "no", "default": "'user'"},
+ {"name": "content", "title": "消息正文", "type": "text"},
+ {"name": "attachments", "title": "附件列表JSON", "type": "text"},
+ {"name": "msg_type", "title": "消息类型", "type": "str", "length": 20, "nullable": "no", "default": "'text'"},
+ {"name": "org_id", "title": "组织ID", "type": "str", "length": 32, "nullable": "no", "default": "'0'"},
+ {"name": "created_by", "title": "创建人", "type": "str", "length": 32},
+ {"name": "created_at", "title": "创建时间", "type": "timestamp", "nullable": "no"}
+ ],
+ "indexes": [
+ {"name": "idx_sd_conv_iteration", "idxtype": "index", "idxfields": ["iteration_id"]},
+ {"name": "idx_sd_conv_task", "idxtype": "index", "idxfields": ["task_id"]},
+ {"name": "idx_sd_conv_created", "idxtype": "index", "idxfields": ["created_at"]}
+ ],
+ "codes": [
+ {"field": "role", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='sd_conversation_role'"}
+ ]
+}
diff --git a/wwwroot/api/cockpit_chat.dspy b/wwwroot/api/cockpit_chat.dspy
new file mode 100644
index 0000000..36f9f3d
--- /dev/null
+++ b/wwwroot/api/cockpit_chat.dspy
@@ -0,0 +1,146 @@
+# cockpit_chat.dspy - Send message or list conversation
+# POST: action=send_message, iteration_id, message_text
+# GET: action=list_messages, iteration_id (optional), task_id (optional)
+
+action = (params_kw or {}).get('action', 'list_messages')
+dbname = get_module_dbname('pipeline-sdlc')
+
+if action == 'send_message':
+ iteration_id = (params_kw or {}).get('iteration_id', '')
+ task_id = (params_kw or {}).get('task_id', '')
+ message_text = (params_kw or {}).get('message_text', '').strip()
+
+ if not iteration_id or not message_text:
+ return json.dumps({"error": "Missing iteration_id or message_text"}, ensure_ascii=False)
+
+ uid = await get_user()
+
+ async with DBPools().sqlorContext(dbname) as sor:
+ # Save user message
+ msg_id = getID()
+ await sor.I('sd_conversations', {
+ 'id': msg_id,
+ 'iteration_id': iteration_id,
+ 'task_id': task_id or '',
+ 'step_name': '',
+ 'role': 'user',
+ 'content': message_text,
+ 'attachments': '[]',
+ 'msg_type': 'text',
+ 'org_id': '0',
+ 'created_by': uid
+ })
+
+ # Generate agent response
+ # 1. Check if there's an active pipeline task for this iteration
+ if task_id:
+ agent_reply = f"收到你的消息。当前任务 {task_id} 正在执行中,你的输入已记录。"
+ else:
+ # Check for existing iterations
+ iters = await sor.sqlExe(
+ "SELECT id, task_id, status FROM sd_iterations WHERE id=${iid}$", {"iid": iteration_id})
+ if iters:
+ it = iters[0]
+ if hasattr(it, 'task_id') and it.task_id:
+ agent_reply = f"收到。迭代任务 {it.task_id} 状态: {it.status}。"
+ else:
+ agent_reply = f"收到你的需求:「{message_text[:50]}...」。请确认是否要为此迭代启动开发产线?"
+ else:
+ agent_reply = f"收到你的需求:「{message_text[:50]}...」。建议先在项目管理中创建项目和迭代。"
+
+ # Save agent message
+ agent_msg_id = getID()
+ await sor.I('sd_conversations', {
+ 'id': agent_msg_id,
+ 'iteration_id': iteration_id,
+ 'task_id': task_id or '',
+ 'step_name': '',
+ 'role': 'agent',
+ 'content': agent_reply,
+ 'attachments': '[]',
+ 'msg_type': 'text',
+ 'org_id': '0',
+ 'created_by': 'system'
+ })
+
+ return json.dumps({"success": True, "message_id": msg_id}, ensure_ascii=False)
+
+else:
+ # list_messages - return messages as Bricks widget JSON
+ iteration_id = (params_kw or {}).get('iteration_id', '')
+ task_id = (params_kw or {}).get('task_id', '')
+
+ msgs = []
+ if iteration_id or task_id:
+ async with DBPools().sqlorContext(dbname) as sor:
+ where = []
+ params = {}
+ if task_id:
+ where.append("task_id=${tid}$")
+ params["tid"] = task_id
+ if iteration_id:
+ where.append("iteration_id=${iid}$")
+ params["iid"] = iteration_id
+
+ sql = f"SELECT role, content, msg_type, created_at FROM sd_conversations WHERE {' OR '.join(where)} ORDER BY created_at ASC LIMIT 50"
+ msgs = await sor.sqlExe(sql, params)
+
+ # Build chat messages widget
+ msg_widgets = []
+ for m in msgs:
+ role = m.role if hasattr(m, 'role') else ''
+ content = m.content if hasattr(m, 'content') else ''
+
+ if role == 'agent':
+ bg = '#e8f0fe'
+ align = 'flex-start'
+ label = 'Agent'
+ label_color = '#3b82f6'
+ elif role == 'user':
+ bg = '#dbeafe'
+ align = 'flex-end'
+ label = '你'
+ label_color = '#2563eb'
+ else:
+ bg = '#f1f5f9'
+ align = 'center'
+ label = '系统'
+ label_color = '#94a3b8'
+
+ msg_widgets.append({
+ "widgettype": "VBox",
+ "options": {
+ "width": "85%",
+ "alignSelf": align,
+ "bgcolor": bg,
+ "borderRadius": "12px",
+ "padding": "12px 16px",
+ "marginBottom": "10px",
+ "gap": "4px"
+ },
+ "subwidgets": [
+ {"widgettype": "Text", "options": {
+ "text": label, "cfontsize": 0.75,
+ "color": label_color, "fontWeight": "bold"
+ }},
+ {"widgettype": "Text", "options": {
+ "text": content, "cfontsize": 0.95,
+ "color": "#1e293b", "whiteSpace": "pre-wrap"
+ }}
+ ]
+ })
+
+ if not msg_widgets:
+ msg_widgets.append({
+ "widgettype": "Text",
+ "options": {
+ "text": "暂无对话记录。选择一个迭代后,在下方输入框中开始对话。",
+ "cfontsize": 0.9, "color": "#94a3b8", "padding": "20px"
+ }
+ })
+
+ return {
+ "widgettype": "VBox",
+ "options": {"width": "100%", "padding": "4px"},
+ "subwidgets": msg_widgets
+ }
diff --git a/wwwroot/api/cockpit_stats.dspy b/wwwroot/api/cockpit_stats.dspy
new file mode 100644
index 0000000..ea02998
--- /dev/null
+++ b/wwwroot/api/cockpit_stats.dspy
@@ -0,0 +1,123 @@
+# cockpit_stats.dspy - Returns dashboard stat cards as Bricks widget JSON
+dbname = get_module_dbname('pipeline-sdlc')
+
+async with DBPools().sqlorContext(dbname) as sor:
+ # Active projects
+ proj = await sor.sqlExe(
+ "SELECT COUNT(*) as active FROM sd_projects WHERE status='active'", {})
+ active_projects = proj[0].active if proj else 0
+
+ # Active iterations
+ iters = await sor.sqlExe(
+ "SELECT COUNT(*) as cnt FROM sd_iterations WHERE status='in_progress'", {})
+ active_iters = iters[0].cnt if iters else 0
+
+ # Open bugs
+ bugs = await sor.sqlExe(
+ "SELECT COUNT(*) as cnt FROM sd_bugs WHERE status NOT IN ('closed','resolved')", {})
+ open_bugs = bugs[0].cnt if bugs else 0
+
+ # Pending reviews (human tasks)
+ reviews = await sor.sqlExe(
+ "SELECT COUNT(*) as cnt FROM pipeline_human_tasks WHERE status='pending'", {})
+ pending_reviews = reviews[0].cnt if reviews else 0
+
+return {
+ "widgettype": "ResponsableBox",
+ "options": {"gap": "16px", "minWidth": "180px", "width": "100%"},
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {
+ "css": "card", "cwidth": 23, "cheight": 12,
+ "padding": "20px", "bgcolor": "#fff",
+ "borderRadius": "8px", "border": "1px solid #e2e8f0",
+ "alignItems": "center", "gap": "6px", "cursor": "pointer"
+ },
+ "binds": [{
+ "wid": "self", "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.main_content",
+ "options": {"url": entire_url("/pipeline-sdlc/sd_projects/")},
+ "mode": "replace"
+ }],
+ "subwidgets": [
+ {"widgettype": "Text", "options": {
+ "text": f"{active_projects}", "cfontsize": 2.2,
+ "color": "#3b82f6", "fontWeight": "bold"
+ }},
+ {"widgettype": "Text", "options": {
+ "text": "活跃项目", "cfontsize": 0.9, "color": "#64748b"
+ }}
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "css": "card", "cwidth": 23, "cheight": 12,
+ "padding": "20px", "bgcolor": "#fff",
+ "borderRadius": "8px", "border": "1px solid #e2e8f0",
+ "alignItems": "center", "gap": "6px", "cursor": "pointer"
+ },
+ "binds": [{
+ "wid": "self", "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.main_content",
+ "options": {"url": entire_url("/pipeline-sdlc/sd_iterations/")},
+ "mode": "replace"
+ }],
+ "subwidgets": [
+ {"widgettype": "Text", "options": {
+ "text": f"{active_iters}", "cfontsize": 2.2,
+ "color": "#22c55e", "fontWeight": "bold"
+ }},
+ {"widgettype": "Text", "options": {
+ "text": "进行中迭代", "cfontsize": 0.9, "color": "#64748b"
+ }}
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "css": "card", "cwidth": 23, "cheight": 12,
+ "padding": "20px", "bgcolor": "#fff",
+ "borderRadius": "8px", "border": "1px solid #e2e8f0",
+ "alignItems": "center", "gap": "6px", "cursor": "pointer"
+ },
+ "binds": [{
+ "wid": "self", "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.main_content",
+ "options": {"url": entire_url("/pipeline-sdlc/sd_bugs/")},
+ "mode": "replace"
+ }],
+ "subwidgets": [
+ {"widgettype": "Text", "options": {
+ "text": f"{open_bugs}", "cfontsize": 2.2,
+ "color": "#f59e0b", "fontWeight": "bold"
+ }},
+ {"widgettype": "Text", "options": {
+ "text": "待处理Bug", "cfontsize": 0.9, "color": "#64748b"
+ }}
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "css": "card", "cwidth": 23, "cheight": 12,
+ "padding": "20px", "bgcolor": "#fff",
+ "borderRadius": "8px", "border": "1px solid #e2e8f0",
+ "alignItems": "center", "gap": "6px", "cursor": "pointer"
+ },
+ "subwidgets": [
+ {"widgettype": "Text", "options": {
+ "text": f"{pending_reviews}", "cfontsize": 2.2,
+ "color": "#8b5cf6", "fontWeight": "bold"
+ }},
+ {"widgettype": "Text", "options": {
+ "text": "待审批", "cfontsize": 0.9, "color": "#64748b"
+ }}
+ ]
+ }
+ ]
+}
diff --git a/wwwroot/api/cockpit_timeline.dspy b/wwwroot/api/cockpit_timeline.dspy
new file mode 100644
index 0000000..d271247
--- /dev/null
+++ b/wwwroot/api/cockpit_timeline.dspy
@@ -0,0 +1,159 @@
+# cockpit_timeline.dspy - Returns pipeline step timeline as Bricks widget JSON
+# params: iteration_id (optional), task_id (optional)
+
+iteration_id = (params_kw or {}).get('iteration_id', '')
+task_id = (params_kw or {}).get('task_id', '')
+dbname = get_module_dbname('pipeline-sdlc')
+
+if not task_id and iteration_id:
+ # Look up task_id from iteration
+ async with DBPools().sqlorContext(dbname) as sor:
+ iters = await sor.sqlExe(
+ "SELECT task_id FROM sd_iterations WHERE id=${iid}$", {"iid": iteration_id})
+ if iters and hasattr(iters[0], 'task_id') and iters[0].task_id:
+ task_id = iters[0].task_id
+
+if not task_id:
+ return {
+ "widgettype": "VBox",
+ "options": {"padding": "20px", "alignItems": "center"},
+ "subwidgets": [
+ {"widgettype": "Text", "options": {
+ "text": "暂无产线任务。\n请先创建迭代以启动开发产线。",
+ "cfontsize": 0.9, "color": "#94a3b8", "textAlign": "center"
+ }}
+ ]
+ }
+
+# Query pipeline steps via pipeline_service
+try:
+ from pipeline_service.executor import get_task_steps
+ steps = await get_task_steps(task_id)
+except Exception:
+ # Fallback: query pipeline_tasks and pipeline_task_steps directly
+ async with DBPools().sqlorContext(dbname) as sor:
+ steps = await sor.sqlExe(
+ "SELECT step_name, state, step_order, step_config, started_at, completed_at "
+ "FROM pipeline_task_steps WHERE task_id=${tid}$ ORDER BY step_order",
+ {"tid": task_id}
+ )
+
+# Status icon mapping
+status_config = {
+ 'completed': {'icon': '✓', 'color': '#22c55e', 'bg': '#f0fdf4', 'label': '已完成'},
+ 'running': {'icon': '⟳', 'color': '#f59e0b', 'bg': '#fffbeb', 'label': '执行中'},
+ 'pending': {'icon': '○', 'color': '#94a3b8', 'bg': '#f8fafc', 'label': '待执行'},
+ 'waiting': {'icon': '⏳', 'color': '#3b82f6', 'bg': '#eff6ff', 'label': '等待中'},
+ 'failed': {'icon': '✗', 'color': '#ef4444', 'bg': '#fef2f2', 'label': '失败'},
+ 'paused': {'icon': '⏸', 'color': '#8b5cf6', 'bg': '#f5f3ff', 'label': '已暂停'},
+ 'skipped': {'icon': '⊘', 'color': '#94a3b8', 'bg': '#f1f5f9', 'label': '已跳过'},
+}
+
+step_widgets = []
+
+# Group by phase
+current_phase = None
+for step in (steps or []):
+ sn = step.step_name if hasattr(step, 'step_name') else step.get('step_name', '')
+ state = step.state if hasattr(step, 'state') else step.get('state', 'pending')
+ step_config_raw = step.step_config if hasattr(step, 'step_config') else step.get('step_config', '{}')
+ step_order = step.step_order if hasattr(step, 'step_order') else step.get('step_order', 0)
+
+ # Parse step_config for phase grouping
+ try:
+ cfg = json.loads(step_config_raw) if isinstance(step_config_raw, str) else (step_config_raw or {})
+ except Exception:
+ cfg = {}
+ phase = cfg.get('phase', '')
+ phase_label = cfg.get('phase_label', '')
+
+ # Phase header
+ if phase and phase != current_phase:
+ current_phase = phase
+ step_widgets.append({
+ "widgettype": "HBox",
+ "options": {
+ "width": "100%", "alignItems": "center",
+ "padding": "12px 0 6px 0", "gap": "8px"
+ },
+ "subwidgets": [
+ {"widgettype": "Svg", "options": {
+ "svg": '',
+ "width": "12px", "height": "12px"
+ }},
+ {"widgettype": "Text", "options": {
+ "text": phase_label or phase, "cfontsize": 0.85,
+ "color": "#64748b", "fontWeight": "bold"
+ }}
+ ]
+ })
+
+ # Status
+ sc = status_config.get(state, status_config['pending'])
+ # Connection line
+ connector = {
+ "widgettype": "VBox",
+ "options": {"width": "2px", "height": "100%", "minHeight": "8px",
+ "bgcolor": "#e2e8f0", "marginLeft": "9px"}
+ }
+
+ step_widgets.append({
+ "widgettype": "HBox",
+ "options": {
+ "width": "100%", "alignItems": "flex-start",
+ "padding": "4px 0", "gap": "10px", "cursor": "pointer"
+ },
+ "binds": [{
+ "wid": "self", "event": "click",
+ "actiontype": "popup",
+ "popup_desc": {
+ "widgettype": "urlwidget",
+ "options": {
+ "url": entire_url(f"/pipeline_task/step_panel.ui?task_id=" + task_id + "&step_name=" + sn)
+ }
+ },
+ "popupwindow": True,
+ "options": {"title": f"步骤详情: {sn}", "width": "700px", "height": "500px"}
+ }],
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {"alignItems": "center", "minWidth": "24px"},
+ "subwidgets": [
+ {"widgettype": "Text", "options": {
+ "text": sc['icon'], "cfontsize": 1.2,
+ "color": sc['color'], "fontWeight": "bold"
+ }}
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {"flex": 1, "gap": "2px"},
+ "subwidgets": [
+ {"widgettype": "Text", "options": {
+ "text": sn, "cfontsize": 0.9,
+ "color": "#1e293b", "fontWeight": "500"
+ }},
+ {"widgettype": "Text", "options": {
+ "text": sc['label'], "cfontsize": 0.75,
+ "color": sc['color']
+ }}
+ ]
+ }
+ ]
+ })
+
+if not step_widgets:
+ step_widgets.append({
+ "widgettype": "Text",
+ "options": {
+ "text": "产线步骤尚未生成。\n任务初始化中...",
+ "cfontsize": 0.9, "color": "#94a3b8", "padding": "20px"
+ }
+ })
+
+return {
+ "widgettype": "VBox",
+ "options": {"width": "100%", "padding": "4px"},
+ "subwidgets": step_widgets
+}
diff --git a/wwwroot/index.ui b/wwwroot/index.ui
index d4b14d4..8170c3b 100644
--- a/wwwroot/index.ui
+++ b/wwwroot/index.ui
@@ -1,103 +1,495 @@
{
- "widgettype": "VBox",
- "options": {"width": "100%", "height": "100%", "padding": "0"},
- "subwidgets": [
+ "widgettype": "VBox",
+ "options": {
+ "width": "100%",
+ "height": "100%",
+ "padding": "0"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "HBox",
+ "options": {
+ "width": "100%",
+ "alignItems": "center",
+ "padding": "24px 24px 12px 24px",
+ "cheight": 6
+ },
+ "subwidgets": [
{
- "widgettype": "HBox",
- "options": {"width": "100%", "alignItems": "center", "padding": "24px 24px 12px 24px", "cheight": 6},
- "subwidgets": [
- {"widgettype": "Title2", "options": {"text": "开发产线"}},
- {"widgettype": "Filler"},
- {"widgettype": "Text", "options": {"text": "项目管理、迭代跟踪、测试与部署", "cfontsize": 1.2}}
- ]
+ "widgettype": "Title2",
+ "options": {
+ "text": "开发产线"
+ }
},
{
- "widgettype": "HBox",
- "options": {"width": "100%", "padding": "12px 24px 12px 24px", "cheight": 18},
- "subwidgets": [
- {
- "widgettype": "ResponsableBox",
- "options": {"gap": "24px", "minWidth": "260px"},
- "subwidgets": [
- {
- "widgettype": "VBox",
- "options": {"css": "card", "cwidth": 23, "cheight": 12, "padding": "24px", "cursor": "pointer", "bgcolor": "#fff"},
- "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.pipeline_sdlc_content", "options": {"url": "{{entire_url('sd_projects/')}}"}, "mode": "replace"}],
- "subwidgets": [
- {"widgettype": "VBox", "options": {"width": "44px", "height": "44px", "bgcolor": "#EFF6FF", "borderRadius": "12px", "alignItems": "center", "justifyContent": "center", "marginBottom": "12px"}, "subwidgets": [
- {"widgettype": "Svg", "options": {"svg": "", "width": "24px", "height": "24px"}}
- ]},
- {"widgettype": "Title3", "options": {"text": "项目管理", "marginBottom": "4px"}},
- {"widgettype": "Text", "options": {"text": "创建与管理软件项目", "cfontsize": 1.2}}
- ]
- },
- {
- "widgettype": "VBox",
- "options": {"css": "card", "cwidth": 23, "cheight": 12, "padding": "24px", "cursor": "pointer", "bgcolor": "#fff"},
- "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.pipeline_sdlc_content", "options": {"url": "{{entire_url('sd_iterations/')}}"}, "mode": "replace"}],
- "subwidgets": [
- {"widgettype": "VBox", "options": {"width": "44px", "height": "44px", "bgcolor": "#F0FDF4", "borderRadius": "12px", "alignItems": "center", "justifyContent": "center", "marginBottom": "12px"}, "subwidgets": [
- {"widgettype": "Svg", "options": {"svg": "", "width": "24px", "height": "24px"}}
- ]},
- {"widgettype": "Title3", "options": {"text": "迭代管理", "marginBottom": "4px"}},
- {"widgettype": "Text", "options": {"text": "规划Sprint与迭代周期", "cfontsize": 1.2}}
- ]
- },
- {
- "widgettype": "VBox",
- "options": {"css": "card", "cwidth": 23, "cheight": 12, "padding": "24px", "cursor": "pointer", "bgcolor": "#fff"},
- "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.pipeline_sdlc_content", "options": {"url": "{{entire_url('sd_test_plans/')}}"}, "mode": "replace"}],
- "subwidgets": [
- {"widgettype": "VBox", "options": {"width": "44px", "height": "44px", "bgcolor": "#F5F3FF", "borderRadius": "12px", "alignItems": "center", "justifyContent": "center", "marginBottom": "12px"}, "subwidgets": [
- {"widgettype": "Svg", "options": {"svg": "", "width": "24px", "height": "24px"}}
- ]},
- {"widgettype": "Title3", "options": {"text": "测试计划", "marginBottom": "4px"}},
- {"widgettype": "Text", "options": {"text": "制定与跟踪测试计划", "cfontsize": 1.2}}
- ]
- },
- {
- "widgettype": "VBox",
- "options": {"css": "card", "cwidth": 23, "cheight": 12, "padding": "24px", "cursor": "pointer", "bgcolor": "#fff"},
- "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.pipeline_sdlc_content", "options": {"url": "{{entire_url('sd_test_cases/')}}"}, "mode": "replace"}],
- "subwidgets": [
- {"widgettype": "VBox", "options": {"width": "44px", "height": "44px", "bgcolor": "#FFF7ED", "borderRadius": "12px", "alignItems": "center", "justifyContent": "center", "marginBottom": "12px"}, "subwidgets": [
- {"widgettype": "Svg", "options": {"svg": "", "width": "24px", "height": "24px"}}
- ]},
- {"widgettype": "Title3", "options": {"text": "测试用例", "marginBottom": "4px"}},
- {"widgettype": "Text", "options": {"text": "编写与管理测试用例", "cfontsize": 1.2}}
- ]
- },
- {
- "widgettype": "VBox",
- "options": {"css": "card", "cwidth": 23, "cheight": 12, "padding": "24px", "cursor": "pointer", "bgcolor": "#fff"},
- "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.pipeline_sdlc_content", "options": {"url": "{{entire_url('sd_bugs/')}}"}, "mode": "replace"}],
- "subwidgets": [
- {"widgettype": "VBox", "options": {"width": "44px", "height": "44px", "bgcolor": "#FEF2F2", "borderRadius": "12px", "alignItems": "center", "justifyContent": "center", "marginBottom": "12px"}, "subwidgets": [
- {"widgettype": "Svg", "options": {"svg": "", "width": "24px", "height": "24px"}}
- ]},
- {"widgettype": "Title3", "options": {"text": "Bug管理", "marginBottom": "4px"}},
- {"widgettype": "Text", "options": {"text": "跟踪与修复项目缺陷", "cfontsize": 1.2}}
- ]
- },
- {
- "widgettype": "VBox",
- "options": {"css": "card", "cwidth": 23, "cheight": 12, "padding": "24px", "cursor": "pointer", "bgcolor": "#fff"},
- "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.pipeline_sdlc_content", "options": {"url": "{{entire_url('sd_deploy_envs/')}}"}, "mode": "replace"}],
- "subwidgets": [
- {"widgettype": "VBox", "options": {"width": "44px", "height": "44px", "bgcolor": "#F0FDFA", "borderRadius": "12px", "alignItems": "center", "justifyContent": "center", "marginBottom": "12px"}, "subwidgets": [
- {"widgettype": "Svg", "options": {"svg": "", "width": "24px", "height": "24px"}}
- ]},
- {"widgettype": "Title3", "options": {"text": "部署环境", "marginBottom": "4px"}},
- {"widgettype": "Text", "options": {"text": "配置开发测试生产环境", "cfontsize": 1.2}}
- ]
- }
- ]
- }
- ]},
+ "widgettype": "Filler"
+ },
{
- "widgettype": "VScrollPanel",
- "id": "pipeline_sdlc_content",
- "options": {"css": "filler", "width": "100%", "height": "100%"}
+ "widgettype": "Text",
+ "options": {
+ "text": "项目管理、迭代跟踪、测试与部署",
+ "cfontsize": 1.2
+ }
}
- ]
-}
+ ]
+ },
+ {
+ "widgettype": "HBox",
+ "options": {
+ "width": "100%",
+ "padding": "12px 24px 12px 24px",
+ "cheight": 18
+ },
+ "subwidgets": [
+ {
+ "widgettype": "ResponsableBox",
+ "options": {
+ "gap": "24px",
+ "minWidth": "260px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {
+ "css": "card",
+ "cwidth": 23,
+ "cheight": 12,
+ "padding": "24px",
+ "cursor": "pointer",
+ "bgcolor": "#1e293b",
+ "border": "1px solid #6366f1",
+ "borderRadius": "12px"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.main_content",
+ "options": {
+ "url": "{{entire_url('/pipeline-sdlc/sd_cockpit/')}}"
+ },
+ "mode": "replace"
+ }
+ ],
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {
+ "width": "44px",
+ "height": "44px",
+ "bgcolor": "#312e81",
+ "borderRadius": "12px",
+ "alignItems": "center",
+ "justifyContent": "center",
+ "marginBottom": "12px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": "",
+ "width": "24px",
+ "height": "24px"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "Title3",
+ "options": {
+ "text": "驾驶舱",
+ "marginBottom": "4px"
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "AI驱动的对话式开发",
+ "cfontsize": 0.9,
+ "color": "#94a3b8"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "css": "card",
+ "cwidth": 23,
+ "cheight": 12,
+ "padding": "24px",
+ "cursor": "pointer",
+ "bgcolor": "#fff"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.main_content",
+ "options": {
+ "url": "{{entire_url('/pipeline-sdlc/sd_projects/')}}"
+ },
+ "mode": "replace"
+ }
+ ],
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {
+ "width": "44px",
+ "height": "44px",
+ "bgcolor": "#EFF6FF",
+ "borderRadius": "12px",
+ "alignItems": "center",
+ "justifyContent": "center",
+ "marginBottom": "12px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": "",
+ "width": "24px",
+ "height": "24px"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "Title3",
+ "options": {
+ "text": "项目管理",
+ "marginBottom": "4px"
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "创建与管理软件项目",
+ "cfontsize": 1.2
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "css": "card",
+ "cwidth": 23,
+ "cheight": 12,
+ "padding": "24px",
+ "cursor": "pointer",
+ "bgcolor": "#fff"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.main_content",
+ "options": {
+ "url": "{{entire_url('/pipeline-sdlc/sd_iterations/')}}"
+ },
+ "mode": "replace"
+ }
+ ],
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {
+ "width": "44px",
+ "height": "44px",
+ "bgcolor": "#F0FDF4",
+ "borderRadius": "12px",
+ "alignItems": "center",
+ "justifyContent": "center",
+ "marginBottom": "12px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": "",
+ "width": "24px",
+ "height": "24px"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "Title3",
+ "options": {
+ "text": "迭代管理",
+ "marginBottom": "4px"
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "规划Sprint与迭代周期",
+ "cfontsize": 1.2
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "css": "card",
+ "cwidth": 23,
+ "cheight": 12,
+ "padding": "24px",
+ "cursor": "pointer",
+ "bgcolor": "#fff"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.main_content",
+ "options": {
+ "url": "{{entire_url('/pipeline-sdlc/sd_test_plans/')}}"
+ },
+ "mode": "replace"
+ }
+ ],
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {
+ "width": "44px",
+ "height": "44px",
+ "bgcolor": "#F5F3FF",
+ "borderRadius": "12px",
+ "alignItems": "center",
+ "justifyContent": "center",
+ "marginBottom": "12px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": "",
+ "width": "24px",
+ "height": "24px"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "Title3",
+ "options": {
+ "text": "测试计划",
+ "marginBottom": "4px"
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "制定与跟踪测试计划",
+ "cfontsize": 1.2
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "css": "card",
+ "cwidth": 23,
+ "cheight": 12,
+ "padding": "24px",
+ "cursor": "pointer",
+ "bgcolor": "#fff"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.main_content",
+ "options": {
+ "url": "{{entire_url('/pipeline-sdlc/sd_test_cases/')}}"
+ },
+ "mode": "replace"
+ }
+ ],
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {
+ "width": "44px",
+ "height": "44px",
+ "bgcolor": "#FFF7ED",
+ "borderRadius": "12px",
+ "alignItems": "center",
+ "justifyContent": "center",
+ "marginBottom": "12px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": "",
+ "width": "24px",
+ "height": "24px"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "Title3",
+ "options": {
+ "text": "测试用例",
+ "marginBottom": "4px"
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "编写与管理测试用例",
+ "cfontsize": 1.2
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "css": "card",
+ "cwidth": 23,
+ "cheight": 12,
+ "padding": "24px",
+ "cursor": "pointer",
+ "bgcolor": "#fff"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.main_content",
+ "options": {
+ "url": "{{entire_url('/pipeline-sdlc/sd_bugs/')}}"
+ },
+ "mode": "replace"
+ }
+ ],
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {
+ "width": "44px",
+ "height": "44px",
+ "bgcolor": "#FEF2F2",
+ "borderRadius": "12px",
+ "alignItems": "center",
+ "justifyContent": "center",
+ "marginBottom": "12px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": "",
+ "width": "24px",
+ "height": "24px"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "Title3",
+ "options": {
+ "text": "Bug管理",
+ "marginBottom": "4px"
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "跟踪与修复项目缺陷",
+ "cfontsize": 1.2
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "css": "card",
+ "cwidth": 23,
+ "cheight": 12,
+ "padding": "24px",
+ "cursor": "pointer",
+ "bgcolor": "#fff"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.main_content",
+ "options": {
+ "url": "{{entire_url('/pipeline-sdlc/sd_deploy_envs/')}}"
+ },
+ "mode": "replace"
+ }
+ ],
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {
+ "width": "44px",
+ "height": "44px",
+ "bgcolor": "#F0FDFA",
+ "borderRadius": "12px",
+ "alignItems": "center",
+ "justifyContent": "center",
+ "marginBottom": "12px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": "",
+ "width": "24px",
+ "height": "24px"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "Title3",
+ "options": {
+ "text": "部署环境",
+ "marginBottom": "4px"
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "配置开发测试生产环境",
+ "cfontsize": 1.2
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "widgettype": "VScrollPanel",
+ "id": "pipeline_sdlc_content",
+ "options": {
+ "css": "filler",
+ "width": "100%",
+ "height": "100%"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/wwwroot/sd_cockpit/index.ui b/wwwroot/sd_cockpit/index.ui
new file mode 100644
index 0000000..5faf4bf
--- /dev/null
+++ b/wwwroot/sd_cockpit/index.ui
@@ -0,0 +1,257 @@
+{
+ "widgettype": "VBox",
+ "options": {
+ "width": "100%",
+ "height": "100%",
+ "padding": "0"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "HBox",
+ "options": {
+ "width": "100%",
+ "alignItems": "center",
+ "padding": "20px 24px 12px 24px",
+ "cheight": 6,
+ "gap": "12px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Title2",
+ "options": {
+ "text": "开发产线驾驶舱"
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "Cockpit",
+ "cfontsize": 0.9,
+ "color": "#94a3b8"
+ }
+ },
+ {
+ "widgettype": "Filler"
+ },
+ {
+ "widgettype": "Button",
+ "options": {
+ "name": "refresh_btn",
+ "label": "刷新",
+ "css": "small"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "script",
+ "target": "self",
+ "script": "var p=bricks.getWidgetById('cockpit_timeline');if(p)p.render({});var c=bricks.getWidgetById('cockpit_chat');if(c)c.render({});var d=bricks.getWidgetById('cockpit_dashboard');if(d)d.render({});"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "widgettype": "HBox",
+ "options": {
+ "width": "100%",
+ "padding": "12px 24px 12px 24px",
+ "cheight": 18,
+ "gap": "16px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "urlwidget",
+ "id": "cockpit_dashboard",
+ "options": {
+ "url": "{{entire_url('/pipeline-sdlc/api/cockpit_stats.dspy')}}",
+ "method": "GET"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "HBox",
+ "options": {
+ "css": "filler",
+ "width": "100%",
+ "height": "100%",
+ "padding": "12px 24px 24px 24px",
+ "gap": "16px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {
+ "cwidth": 35,
+ "height": "100%",
+ "gap": "8px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "HBox",
+ "options": {
+ "alignItems": "center",
+ "padding": "0 0 8px 0",
+ "gap": "8px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Title3",
+ "options": {
+ "text": "产线时间线"
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "选择一个迭代查看",
+ "cfontsize": 0.8,
+ "color": "#94a3b8"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "VScrollPanel",
+ "id": "cockpit_timeline",
+ "options": {
+ "css": "filler",
+ "width": "100%",
+ "height": "100%",
+ "bgcolor": "#f8fafc",
+ "border": "1px solid #e2e8f0",
+ "borderRadius": "8px",
+ "padding": "12px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "请先在项目管理中创建迭代,或从工具栏选择一个迭代来查看产线进度。",
+ "cfontsize": 0.9,
+ "color": "#64748b",
+ "padding": "20px"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "cwidth": 65,
+ "height": "100%",
+ "gap": "8px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "HBox",
+ "options": {
+ "alignItems": "center",
+ "padding": "0 0 8px 0",
+ "gap": "8px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Title3",
+ "options": {
+ "text": "对话"
+ }
+ },
+ {
+ "widgettype": "Filler"
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "像和大模型对话一样驱动开发",
+ "cfontsize": 0.8,
+ "color": "#94a3b8"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "VScrollPanel",
+ "id": "cockpit_chat",
+ "options": {
+ "css": "filler",
+ "width": "100%",
+ "height": "100%",
+ "bgcolor": "#f8fafc",
+ "border": "1px solid #e2e8f0",
+ "borderRadius": "8px",
+ "padding": "12px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "选择一个迭代后,这里将显示对话记录。\n在下方输入框中描述你的需求,Agent 将自动分析并驱动产线执行。",
+ "cfontsize": 0.9,
+ "color": "#64748b",
+ "padding": "20px"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "Form",
+ "id": "chat_form",
+ "options": {
+ "name": "chat_form",
+ "submit_url": "{{entire_url('/pipeline-sdlc/api/cockpit_chat.dspy')}}",
+ "fields": [
+ {"name": "iteration_id", "uitype": "hide", "value": ""},
+ {"name": "task_id", "uitype": "hide", "value": ""},
+ {"name": "message_text", "uitype": "textarea", "placeholder": "描述你的需求或回复Agent...", "rows": 3, "cwidth": 60},
+ {"name": "action", "uitype": "hide", "value": "send_message"}
+ ]
+ },
+ "subwidgets": [
+ {
+ "widgettype": "HBox",
+ "options": {
+ "padding": "8px 0 0 0",
+ "gap": "8px",
+ "justifyContent": "flex-end"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Button",
+ "options": {
+ "name": "send_btn",
+ "label": "发送",
+ "type": "submit",
+ "css": "primary"
+ }
+ }
+ ]
+ }
+ ],
+ "binds": [
+ {
+ "wid": "self",
+ "event": "beforesubmit",
+ "actiontype": "script",
+ "target": "self",
+ "script": "var iter_id=bricks.getWidgetById('current_iteration_id');if(!iter_id||!iter_id.options.value){bricks.show_error({title:'提示',message:'请先在迭代管理中选择一个迭代'});return false;}var msg=this.getValue('message_text');if(!msg||!msg.trim()){bricks.show_error({title:'提示',message:'请输入内容'});return false;}return true;"
+ },
+ {
+ "wid": "self",
+ "event": "submited",
+ "actiontype": "script",
+ "target": "self",
+ "script": "this.setValue('message_text','');var c=bricks.getWidgetById('cockpit_chat');if(c)c.render({});var t=bricks.getWidgetById('cockpit_timeline');if(t)t.render({});"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}