From bd0890a3940b2c7afdd946976d3ed638dd619a9f Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 30 Jul 2026 15:53:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(sdlc):=20cockpit=20driving=20mode=20?= =?UTF-8?q?=E2=80=94=20conversation=20timeline=20+=20agent=20chat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/sdlc-architecture.md | 159 ++++++++ docs/sdlc-cockpit-design.md | 179 +++++++++ models/sd_conversations.json | 31 ++ wwwroot/api/cockpit_chat.dspy | 146 ++++++++ wwwroot/api/cockpit_stats.dspy | 123 +++++++ wwwroot/api/cockpit_timeline.dspy | 159 ++++++++ wwwroot/index.ui | 586 +++++++++++++++++++++++++----- wwwroot/sd_cockpit/index.ui | 257 +++++++++++++ 8 files changed, 1543 insertions(+), 97 deletions(-) create mode 100644 docs/sdlc-architecture.md create mode 100644 docs/sdlc-cockpit-design.md create mode 100644 models/sd_conversations.json create mode 100644 wwwroot/api/cockpit_chat.dspy create mode 100644 wwwroot/api/cockpit_stats.dspy create mode 100644 wwwroot/api/cockpit_timeline.dspy create mode 100644 wwwroot/sd_cockpit/index.ui 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({});" + } + ] + } + ] + } + ] + } + ] +}