chore: update macOS deployment target to 14.0 and commit pending changes
This commit is contained in:
parent
7765a7211d
commit
09352b35a0
@ -79,6 +79,12 @@ mobile:
|
|||||||
build_modes: [debug, profile, release]
|
build_modes: [debug, profile, release]
|
||||||
description: Mobile runtime logs view
|
description: Mobile runtime logs view
|
||||||
ui_surface: settings_page
|
ui_surface: settings_page
|
||||||
|
help:
|
||||||
|
enabled: true
|
||||||
|
release_tier: stable
|
||||||
|
build_modes: [debug, profile, release]
|
||||||
|
description: Mobile setup help and installation guide
|
||||||
|
ui_surface: settings_page
|
||||||
account_access:
|
account_access:
|
||||||
enabled: true
|
enabled: true
|
||||||
release_tier: stable
|
release_tier: stable
|
||||||
@ -186,6 +192,12 @@ desktop:
|
|||||||
build_modes: [debug, profile, release]
|
build_modes: [debug, profile, release]
|
||||||
description: Desktop runtime logs view
|
description: Desktop runtime logs view
|
||||||
ui_surface: settings_page
|
ui_surface: settings_page
|
||||||
|
help:
|
||||||
|
enabled: true
|
||||||
|
release_tier: stable
|
||||||
|
build_modes: [debug, profile, release]
|
||||||
|
description: Desktop setup help and installation guide
|
||||||
|
ui_surface: settings_page
|
||||||
account_access:
|
account_access:
|
||||||
enabled: true
|
enabled: true
|
||||||
release_tier: stable
|
release_tier: stable
|
||||||
|
|||||||
65
docs/cases/00-review-env-and-matrix.md
Normal file
65
docs/cases/00-review-env-and-matrix.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# XWorkmate App Review Test Cases|环境与覆盖矩阵
|
||||||
|
|
||||||
|
> 适用目录:`xworkmate-app/docs/cases/`
|
||||||
|
>
|
||||||
|
> 目标:为 Apple 审核、公网 Bridge、自建 Bridge、本地 Bridge、典型 AI 工作流提供稳定、可复现、可回归的测试用例。
|
||||||
|
|
||||||
|
## 1. 测试账号与环境变量
|
||||||
|
|
||||||
|
### 1.1 云端账号
|
||||||
|
|
||||||
|
| 项目 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| 账号类型 | 只读评审账号(Apple 审核专用) |
|
||||||
|
| 服务地址 | `https://accounts.svc.plus` |
|
||||||
|
| 邮箱 / 账号 | `review@svc.plus` |
|
||||||
|
| 密码 | 从安全变量读取:`XWORKMATE_REVIEW_PASSWORD` |
|
||||||
|
|
||||||
|
> 不建议将真实密码提交到仓库。Apple 审核备注、CI Secret、本地 `.env.local` 可保存真实值。
|
||||||
|
|
||||||
|
### 1.2 公网 xworkmate-bridge|正式 Token 组合
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus
|
||||||
|
BRIDGE_AUTH_TOKEN=${BRIDGE_AUTH_TOKEN}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 公网 xworkmate-bridge|Review Token 组合
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus
|
||||||
|
BRIDGE_REVIEW_AUTH_TOKEN=${BRIDGE_REVIEW_AUTH_TOKEN}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 本地 xworkmate-bridge|开发组合
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BRIDGE_SERVER_URL=http://127.0.0.1:8787
|
||||||
|
BRIDGE_AUTH_TOKEN=${BRIDGE_AUTH_TOKEN}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 通用验收标准
|
||||||
|
|
||||||
|
- App 能完成账号登录、Bridge 连接、Token 校验、会话初始化。
|
||||||
|
- 网络异常、Token 失效、Bridge 不可达时,App 不崩溃,并给出明确错误提示。
|
||||||
|
- 长任务执行期间,状态流、日志流、任务结果、取消重试行为保持稳定。
|
||||||
|
- 同一任务重复执行 3 次,产物结构和关键字段稳定,不出现空白页、卡死、重复提交、状态错乱。
|
||||||
|
- App 从前台切后台再恢复,任务状态可以继续读取或明确提示已中断。
|
||||||
|
|
||||||
|
## 3. 五类典型任务覆盖矩阵
|
||||||
|
|
||||||
|
| 用例 | 核心链路 | 主要覆盖点 | 稳定性关注 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| AI 资讯 Markdown | 搜索 / 摘要 / Markdown 生成 | 长文本、引用、结构化输出 | 流式输出中断、重复标题、空结果兜底 |
|
||||||
|
| 图片制作视频 | 图片输入 / 脚本 / 分镜 / 视频指令 | 多模态任务、素材引用、步骤编排 | 附件丢失、任务超时、进度状态错乱 |
|
||||||
|
| 7 张连续图片 | 连续图文 / 风格一致 / 批量生成 | 多步骤连续产物 | 序号错乱、风格漂移、单张失败重试 |
|
||||||
|
| 多平台软文矩阵 | 公众号 / 小红书 / 视频号 / 推文 | 同主题多平台改写 | 语气失控、长度失控、平台字段缺失 |
|
||||||
|
| 章节拆解生成图文 PDF | 长文拆解 / 图文页 / PDF 导出 | 长链路 Artifact | 内存占用、导出失败、页面丢失 |
|
||||||
|
|
||||||
|
## 4. 回归优先级
|
||||||
|
|
||||||
|
P0:登录、Bridge 连接、Token 校验、会话创建、任务状态流。
|
||||||
|
|
||||||
|
P1:长任务恢复、取消、重试、错误提示、Artifact 列表刷新。
|
||||||
|
|
||||||
|
P2:多平台文本质量、图片风格一致性、PDF 排版一致性。
|
||||||
43
docs/cases/01-ai-news-markdown.md
Normal file
43
docs/cases/01-ai-news-markdown.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Case 01|AI 资讯 Markdown 生成
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
验证 XWorkmate App 在连接公网或本地 xworkmate-bridge 后,能稳定执行“AI 资讯收集 → 摘要 → Markdown 文档生成”的典型文本任务。
|
||||||
|
|
||||||
|
## 前置条件
|
||||||
|
|
||||||
|
- 已登录只读评审账号。
|
||||||
|
- Bridge 已连接成功。
|
||||||
|
- 当前会话可创建任务并接收状态流。
|
||||||
|
|
||||||
|
## 输入提示词
|
||||||
|
|
||||||
|
```text
|
||||||
|
请整理最近 AI 领域值得技术人关注的 5 条资讯,输出 Markdown。
|
||||||
|
要求:
|
||||||
|
1. 每条包含标题、发生时间、核心变化、对开发者的影响。
|
||||||
|
2. 不要堆砌新闻,要提炼趋势。
|
||||||
|
3. 最后给出 3 条行动建议。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 执行步骤
|
||||||
|
|
||||||
|
1. 打开 XWorkmate App。
|
||||||
|
2. 使用 Review 账号登录。
|
||||||
|
3. 选择公网 Bridge 或本地 Bridge。
|
||||||
|
4. 新建任务并输入提示词。
|
||||||
|
5. 等待任务完成,查看 Markdown 结果。
|
||||||
|
6. 复制或导出结果,确认格式完整。
|
||||||
|
|
||||||
|
## 预期结果
|
||||||
|
|
||||||
|
- 任务能进入 Running 状态,并持续返回日志或进度。
|
||||||
|
- Markdown 结构完整,包含标题、列表、结论。
|
||||||
|
- 不出现空白结果、重复输出、乱码、半截 Markdown。
|
||||||
|
- 网络短暂波动后,App 能恢复状态或明确提示失败原因。
|
||||||
|
|
||||||
|
## 稳定性检查
|
||||||
|
|
||||||
|
- 连续执行 3 次,任务状态不串线。
|
||||||
|
- 切后台 30 秒后返回,结果仍可查看。
|
||||||
|
- Bridge Token 错误时,能显示授权失败,而不是无限 Loading。
|
||||||
44
docs/cases/02-image-to-video.md
Normal file
44
docs/cases/02-image-to-video.md
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Case 02|图片制作视频任务
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
验证“图片素材 → 视频脚本 → 分镜规划 → 生成指令”的多模态长链路任务是否稳定。
|
||||||
|
|
||||||
|
## 前置条件
|
||||||
|
|
||||||
|
- App 已登录。
|
||||||
|
- Bridge 连接成功。
|
||||||
|
- 至少准备 1 张测试图片。
|
||||||
|
|
||||||
|
## 输入提示词
|
||||||
|
|
||||||
|
```text
|
||||||
|
基于这张图片,帮我生成一个 30 秒短视频方案。
|
||||||
|
要求:
|
||||||
|
1. 输出视频标题。
|
||||||
|
2. 拆成 5 个分镜。
|
||||||
|
3. 每个分镜包含画面描述、旁白、字幕、转场建议。
|
||||||
|
4. 风格适合技术公众号和视频号。
|
||||||
|
5. 最后输出可交给视频生成工具的结构化指令。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 执行步骤
|
||||||
|
|
||||||
|
1. 上传或选择一张测试图片。
|
||||||
|
2. 新建图片制作视频任务。
|
||||||
|
3. 输入提示词并开始执行。
|
||||||
|
4. 观察上传、任务创建、执行、结果展示全过程。
|
||||||
|
5. 复制分镜结果,确认结构完整。
|
||||||
|
|
||||||
|
## 预期结果
|
||||||
|
|
||||||
|
- 图片能被正确绑定到任务。
|
||||||
|
- 任务执行过程中不丢失附件引用。
|
||||||
|
- 输出包含标题、5 个分镜、旁白、字幕、转场建议。
|
||||||
|
- 若图片上传失败,应明确提示,不应继续生成无图任务。
|
||||||
|
|
||||||
|
## 稳定性检查
|
||||||
|
|
||||||
|
- 大图、普通图各执行 1 次。
|
||||||
|
- 弱网环境下,上传失败可重试。
|
||||||
|
- 用户取消任务后,不应继续追加输出。
|
||||||
37
docs/cases/03-seven-continuous-images.md
Normal file
37
docs/cases/03-seven-continuous-images.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Case 03|7 张连续图片生成
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
验证连续 7 张图文图片任务在风格一致性、顺序稳定性、失败重试方面的表现。
|
||||||
|
|
||||||
|
## 输入提示词
|
||||||
|
|
||||||
|
```text
|
||||||
|
围绕“AI-Native 技术人成长路线图”,规划 7 张连续图片。
|
||||||
|
要求:
|
||||||
|
1. 每张图有页码、标题、核心观点、3 个关键词。
|
||||||
|
2. 蓝白科技风,适合公众号竖版图。
|
||||||
|
3. 7 张图之间逻辑连续,不要重复。
|
||||||
|
4. 输出每张图的文案与设计提示词。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 执行步骤
|
||||||
|
|
||||||
|
1. 新建连续图片任务。
|
||||||
|
2. 输入提示词。
|
||||||
|
3. 等待系统生成 7 页图文规划或图片提示词。
|
||||||
|
4. 检查每一页是否完整。
|
||||||
|
5. 记录任务耗时、失败页、重试结果。
|
||||||
|
|
||||||
|
## 预期结果
|
||||||
|
|
||||||
|
- 输出严格包含 7 张,不多不少。
|
||||||
|
- 每张图都有页码、标题、核心观点、关键词。
|
||||||
|
- 主题连续,表达不重复。
|
||||||
|
- 单页生成失败时,可单页重试,不影响已完成页面。
|
||||||
|
|
||||||
|
## 稳定性检查
|
||||||
|
|
||||||
|
- 执行期间切换页面,任务状态不丢失。
|
||||||
|
- 第 4 张之后仍保持同一风格约束。
|
||||||
|
- 重试后页码顺序不乱。
|
||||||
39
docs/cases/04-multi-platform-copy-matrix.md
Normal file
39
docs/cases/04-multi-platform-copy-matrix.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Case 04|多平台软文矩阵生成
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
验证同一主题在公众号、小红书、视频号、朋友圈、推文等多平台之间的改写稳定性。
|
||||||
|
|
||||||
|
## 输入提示词
|
||||||
|
|
||||||
|
```text
|
||||||
|
主题:企业为什么最终会走向平台工程。
|
||||||
|
请生成多平台软文矩阵:
|
||||||
|
1. 公众号 500 字观点文。
|
||||||
|
2. 小红书 300 字种草式表达。
|
||||||
|
3. 视频号 60 秒口播稿。
|
||||||
|
4. 朋友圈 100 字转发文案。
|
||||||
|
5. 推文 280 字以内。
|
||||||
|
要求:同一观点,不同平台语气;不要标题党;适合技术人阅读。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 执行步骤
|
||||||
|
|
||||||
|
1. 新建软文矩阵任务。
|
||||||
|
2. 输入主题和平台要求。
|
||||||
|
3. 等待任务完成。
|
||||||
|
4. 分平台检查文案结构、长度和语气。
|
||||||
|
5. 导出或复制结果。
|
||||||
|
|
||||||
|
## 预期结果
|
||||||
|
|
||||||
|
- 输出包含 5 个平台版本。
|
||||||
|
- 每个平台有清晰标题或字段标识。
|
||||||
|
- 公众号版本偏观点深度,小红书更轻,视频号适合口播。
|
||||||
|
- 不出现平台漏项、字段混乱、同文复制。
|
||||||
|
|
||||||
|
## 稳定性检查
|
||||||
|
|
||||||
|
- 长度限制能被大体遵守。
|
||||||
|
- 多次生成不丢平台字段。
|
||||||
|
- 结果页刷新后仍能看到完整产物。
|
||||||
41
docs/cases/05-chapter-to-graphic-pdf.md
Normal file
41
docs/cases/05-chapter-to-graphic-pdf.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Case 05|章节拆解生成图文 PDF
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
验证“长章节输入 → 图文拆解 → 页面规划 → PDF 产物”的长链路 Artifact 任务稳定性。
|
||||||
|
|
||||||
|
## 输入提示词
|
||||||
|
|
||||||
|
```text
|
||||||
|
请把下面这章内容拆解成一份 7 页图文 PDF。
|
||||||
|
要求:
|
||||||
|
1. 第 1 页是封面观点。
|
||||||
|
2. 第 2-6 页每页一个核心小节。
|
||||||
|
3. 第 7 页是总结和行动建议。
|
||||||
|
4. 每页包含标题、主文案、视觉建议、关键词。
|
||||||
|
5. 最后输出 PDF 结构目录。
|
||||||
|
|
||||||
|
章节内容:
|
||||||
|
企业上云不是一次资源搬迁,而是业务边界、成本结构、治理能力和交付模式的重建……
|
||||||
|
```
|
||||||
|
|
||||||
|
## 执行步骤
|
||||||
|
|
||||||
|
1. 新建章节拆解任务。
|
||||||
|
2. 粘贴长文本章节。
|
||||||
|
3. 执行图文 PDF 规划任务。
|
||||||
|
4. 检查页面数量、章节顺序、封面和总结页。
|
||||||
|
5. 若支持导出 PDF,执行导出并打开检查。
|
||||||
|
|
||||||
|
## 预期结果
|
||||||
|
|
||||||
|
- 页面规划完整,严格 7 页。
|
||||||
|
- 每页包含标题、主文案、视觉建议、关键词。
|
||||||
|
- PDF 导出成功时,页面不丢失、不空白、不乱序。
|
||||||
|
- PDF 导出失败时,错误信息清晰,并保留中间图文结构。
|
||||||
|
|
||||||
|
## 稳定性检查
|
||||||
|
|
||||||
|
- 长文本输入不会导致 App 卡死。
|
||||||
|
- 导出期间切后台,回到前台后状态可见。
|
||||||
|
- Artifact 列表刷新后,PDF 或中间结果仍可访问。
|
||||||
@ -5,6 +5,7 @@
|
|||||||
## 当前入口
|
## 当前入口
|
||||||
|
|
||||||
- [核心功能集成测试手动 Case](./core-integration-manual-cases.md)
|
- [核心功能集成测试手动 Case](./core-integration-manual-cases.md)
|
||||||
|
- [云端账号与 XWorkmate Bridge 连接手动 Case](./cloud-account-and-bridge-manual-cases.md)
|
||||||
- [云原生 Service Mesh 网络科普视频调研场景测试用例](./service-mesh-evolution-video-scenario/README.md)
|
- [云原生 Service Mesh 网络科普视频调研场景测试用例](./service-mesh-evolution-video-scenario/README.md)
|
||||||
- [OpenClaw Gateway 5 并发 E2E 回归场景](./openclaw-gateway-e2e-regression/README.md)
|
- [OpenClaw Gateway 5 并发 E2E 回归场景](./openclaw-gateway-e2e-regression/README.md)
|
||||||
|
|
||||||
|
|||||||
489
docs/cases/cloud-account-and-bridge-manual-cases.md
Normal file
489
docs/cases/cloud-account-and-bridge-manual-cases.md
Normal file
@ -0,0 +1,489 @@
|
|||||||
|
# 云端账号与 XWorkmate Bridge 连接手动 Case
|
||||||
|
|
||||||
|
本文档整理 Apple 审核专用只读账号、svc.plus 云端同步、以及公网 / 本地 `xworkmate-bridge` 接入的手动验证用例。
|
||||||
|
|
||||||
|
## 1. 测试账号与连接参数
|
||||||
|
|
||||||
|
### 1.1 云端账号
|
||||||
|
|
||||||
|
| 项目 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| 账号类型 | 只读评审账号(Apple 审核专用) |
|
||||||
|
| 服务地址 | `https://accounts.svc.plus` |
|
||||||
|
| 邮箱 / 账号 | `review@svc.plus` |
|
||||||
|
| 密码 | `***REMOVED-CREDENTIAL***` |
|
||||||
|
|
||||||
|
### 1.2 公网 xworkmate-bridge 组合 1
|
||||||
|
|
||||||
|
| 环境变量 | 值 |
|
||||||
|
|----------|----|
|
||||||
|
| `BRIDGE_SERVER_URL` | `https://xworkmate-bridge.svc.plus` |
|
||||||
|
| `BRIDGE_AUTH_TOKEN` | `***REMOVED-CREDENTIAL***` |
|
||||||
|
|
||||||
|
### 1.3 公网 xworkmate-bridge 组合 2
|
||||||
|
|
||||||
|
| 环境变量 | 值 |
|
||||||
|
|----------|----|
|
||||||
|
| `BRIDGE_SERVER_URL` | `https://xworkmate-bridge.svc.plus` |
|
||||||
|
| `BRIDGE_REVIEW_AUTH_TOKEN` | `***REMOVED-CREDENTIAL***` |
|
||||||
|
|
||||||
|
### 1.4 本地 xworkmate-bridge
|
||||||
|
|
||||||
|
| 环境变量 | 值 |
|
||||||
|
|----------|----|
|
||||||
|
| `BRIDGE_SERVER_URL` | `http://127.0.0.1:8787` |
|
||||||
|
| `BRIDGE_AUTH_TOKEN` | `***REMOVED-CREDENTIAL***` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 通用证据记录要求
|
||||||
|
|
||||||
|
每个 case 执行后建议记录:
|
||||||
|
|
||||||
|
- 当前 App 版本 / 构建号
|
||||||
|
- 当前平台与网络环境
|
||||||
|
- 当前入口:`Settings -> Integrations`
|
||||||
|
- 当前页签:`svc.plus 云端同步` 或 `AI 智能体工作空间`
|
||||||
|
- 服务地址 / Bridge 地址
|
||||||
|
- token 类型:`BRIDGE_AUTH_TOKEN` 或 `BRIDGE_REVIEW_AUTH_TOKEN`
|
||||||
|
- 连接测试结果摘要
|
||||||
|
- 是否出现 secret 明文
|
||||||
|
- 截图点:保存前、保存后、重新进入设置页
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 五类典型任务
|
||||||
|
|
||||||
|
以下五类任务用于验证 App 登录后能否通过当前连接方式稳定创建任务、执行技能、生成文件产物并回写到当前线程。
|
||||||
|
|
||||||
|
| 任务编号 | 类型 | 验收产物 |
|
||||||
|
|----------|------|----------|
|
||||||
|
| CASE-001 | 采集最新 AI 资讯 | `.md` 文件 |
|
||||||
|
| CASE-002 | 附件图片制作视频 | 视频文件,附件图片被使用 |
|
||||||
|
| CASE-003 | 安全身份演进连续图片 | 7 张连续风格图片 |
|
||||||
|
| CASE-004 | 多平台软文矩阵 | 多个 `.md` 文件 |
|
||||||
|
| CASE-005 | 章节拆解 + Codex + GPT images2 + PDF | 汇总排版后的 PDF |
|
||||||
|
|
||||||
|
### `TASK-CASE-001` 采集最新 AI 资讯并保存 Markdown
|
||||||
|
|
||||||
|
- 输入提示词
|
||||||
|
|
||||||
|
```text
|
||||||
|
采集最新AI资讯,保存在md文件
|
||||||
|
```
|
||||||
|
|
||||||
|
- 期望结果
|
||||||
|
- 任务能联网采集最新 AI 资讯
|
||||||
|
- 结果保存为 Markdown 文件
|
||||||
|
- 线程结果区展示文件产物
|
||||||
|
- Markdown 内包含标题、来源、摘要和时间信息
|
||||||
|
- 建议记录项
|
||||||
|
- 任务线程 ID
|
||||||
|
- 输出 `.md` 文件路径
|
||||||
|
- 资讯来源数量
|
||||||
|
- 截图点:任务完成后的 artifact 区域
|
||||||
|
|
||||||
|
### `TASK-CASE-002` 附件图片制作视频
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- 当前线程上传至少 1 张图片附件
|
||||||
|
- 输入提示词
|
||||||
|
|
||||||
|
```text
|
||||||
|
制作视频,附件带有图片
|
||||||
|
```
|
||||||
|
|
||||||
|
- 期望结果
|
||||||
|
- 任务识别并使用用户上传的图片附件
|
||||||
|
- 输出视频文件
|
||||||
|
- 失败时错误信息明确说明缺少图片、视频生成失败或依赖不可用
|
||||||
|
- 产物归属当前线程 workspace
|
||||||
|
- 建议记录项
|
||||||
|
- 附件图片文件名
|
||||||
|
- 输出视频路径
|
||||||
|
- 视频时长和分辨率
|
||||||
|
- 截图点:附件与视频产物
|
||||||
|
|
||||||
|
### `TASK-CASE-003` 安全身份演进连续 7 张图片
|
||||||
|
|
||||||
|
- 输入提示词
|
||||||
|
|
||||||
|
```text
|
||||||
|
从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进,连续制作 7 张一系列图片
|
||||||
|
```
|
||||||
|
|
||||||
|
- 期望结果
|
||||||
|
- 输出 7 张图片
|
||||||
|
- 7 张图片主题分别覆盖:单机权限、网络边界、Web 安全、云身份、Zero Trust、AI Agent 身份、AI 模型与知识保护
|
||||||
|
- 图片风格、尺寸、命名方式保持连续一致
|
||||||
|
- 线程结果区能看到完整图片系列
|
||||||
|
- 建议记录项
|
||||||
|
- 7 张图片路径
|
||||||
|
- 图片尺寸
|
||||||
|
- 是否存在缺图或主题错位
|
||||||
|
- 截图点:图片系列列表
|
||||||
|
|
||||||
|
### `TASK-CASE-004` 安全身份演进多平台软文矩阵
|
||||||
|
|
||||||
|
- 输入提示词
|
||||||
|
|
||||||
|
```text
|
||||||
|
围绕 从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进
|
||||||
|
|
||||||
|
1. 输出Markdown格式文件, 微信公众号短图文 400-600字 插入关键词的软文
|
||||||
|
2. 输出Markdown格式文件, 小红书风格 600-800字 插入钩子话题的软文
|
||||||
|
3. 输出Markdown格式文件, X文案串 小于144字的英语 鲜明的观点
|
||||||
|
4. 输出Markdown格式文件, 微信公众号文章 800-1200字左右
|
||||||
|
5. 输出Markdown格式文件, 头条号长文 800-1200字左右
|
||||||
|
```
|
||||||
|
|
||||||
|
- 期望结果
|
||||||
|
- 输出 Markdown 格式文件
|
||||||
|
- 至少包含微信公众号短图文、小红书风格、X 文案串、微信公众号文章、头条号长文五类内容
|
||||||
|
- 字数、语言和平台风格符合输入要求
|
||||||
|
- X 文案串为英语且单条小于 144 字符
|
||||||
|
- 内容围绕同一条安全身份演进主线,不跑题
|
||||||
|
- 建议记录项
|
||||||
|
- 输出文件路径
|
||||||
|
- 每个平台内容字数
|
||||||
|
- X 文案字符数
|
||||||
|
- 截图点:Markdown 产物列表
|
||||||
|
|
||||||
|
### `TASK-CASE-005` 安全身份演进章节拆解生成图文 PDF
|
||||||
|
|
||||||
|
- 输入提示词
|
||||||
|
|
||||||
|
```text
|
||||||
|
从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进
|
||||||
|
拆章节 -> 每章调用 Codex -> 每章 GPT images2 生成图 -> 汇总排版 -> 输出 PDF
|
||||||
|
```
|
||||||
|
|
||||||
|
- 期望结果
|
||||||
|
- 任务先拆分章节,再逐章生成内容
|
||||||
|
- 每章调用 Codex 生成或整理章节文本
|
||||||
|
- 每章调用 GPT images2 生成配图
|
||||||
|
- 最终汇总排版为 PDF
|
||||||
|
- PDF 中章节顺序与演进主线一致,图片与章节内容匹配
|
||||||
|
- 建议记录项
|
||||||
|
- 章节清单
|
||||||
|
- 每章图片路径
|
||||||
|
- 输出 PDF 路径
|
||||||
|
- PDF 页数
|
||||||
|
- 截图点:PDF artifact 与预览页
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 连接云端账号
|
||||||
|
|
||||||
|
### `MANUAL-CLOUD-001` 只读评审账号登录
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- App 可访问 `https://accounts.svc.plus`
|
||||||
|
- 当前未登录,或已退出其他账号
|
||||||
|
- 操作步骤
|
||||||
|
1. 打开 `Settings -> Integrations`
|
||||||
|
2. 切换到 `svc.plus 云端同步`
|
||||||
|
3. 在 `服务地址` 输入 `https://accounts.svc.plus`
|
||||||
|
4. 在 `邮箱或账号` 输入 `review@svc.plus`
|
||||||
|
5. 在 `密码` 输入 `***REMOVED-CREDENTIAL***`
|
||||||
|
6. 点击 `登录`
|
||||||
|
7. 等待账号同步完成
|
||||||
|
- 期望结果
|
||||||
|
- 登录成功,账号状态稳定显示为已登录或同步完成
|
||||||
|
- App 不展示密码明文
|
||||||
|
- 若账号侧托管 bridge 配置可用,设置页可同步到对应连接配置
|
||||||
|
- 只读评审账号不能触发破坏性写入或管理动作
|
||||||
|
- 建议记录项
|
||||||
|
- 登录账号
|
||||||
|
- 服务地址
|
||||||
|
- 登录结果摘要
|
||||||
|
- 同步后的 bridge endpoint
|
||||||
|
- 截图点:登录成功后的 `svc.plus 云端同步` 页
|
||||||
|
|
||||||
|
### `MANUAL-CLOUD-002` 退出后重新登录保持稳定
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- 已完成 `MANUAL-CLOUD-001`
|
||||||
|
- 操作步骤
|
||||||
|
1. 在设置页退出当前账号
|
||||||
|
2. 关闭或返回设置页
|
||||||
|
3. 再次进入 `Settings -> Integrations -> svc.plus 云端同步`
|
||||||
|
4. 使用 `review@svc.plus` / `***REMOVED-CREDENTIAL***` 重新登录
|
||||||
|
5. 观察同步状态与本地配置状态
|
||||||
|
- 期望结果
|
||||||
|
- 退出后不会继续显示已登录状态
|
||||||
|
- 重新登录成功
|
||||||
|
- 重新登录后同步状态可恢复
|
||||||
|
- 本地已保存的手动 bridge override 不应被异常覆盖
|
||||||
|
- 建议记录项
|
||||||
|
- 退出前后账号状态
|
||||||
|
- 重新登录结果
|
||||||
|
- 同步前后 endpoint 对比
|
||||||
|
|
||||||
|
### `MANUAL-CLOUD-TASKS-001` 云端账号登录后执行五类典型任务
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- 已完成 `MANUAL-CLOUD-001`
|
||||||
|
- 云端同步状态稳定
|
||||||
|
- 当前线程可创建新任务
|
||||||
|
- 操作步骤
|
||||||
|
1. 使用 `review@svc.plus` 登录 `svc.plus 云端同步`
|
||||||
|
2. 确认账号同步完成
|
||||||
|
3. 新建或进入一个测试线程
|
||||||
|
4. 依次执行 `TASK-CASE-001` 到 `TASK-CASE-005`
|
||||||
|
5. 每个任务完成后记录产物路径和结果摘要
|
||||||
|
- 期望结果
|
||||||
|
- 五类任务均能在云端账号连接上下文下启动
|
||||||
|
- 任务产物均归属当前 App 线程
|
||||||
|
- 云端账号只读属性不影响正常评审用任务执行
|
||||||
|
- 执行过程中不暴露密码、session 或 bridge token 明文
|
||||||
|
- 建议记录项
|
||||||
|
- 登录账号
|
||||||
|
- 五类任务的线程 ID
|
||||||
|
- 五类任务的产物路径
|
||||||
|
- 失败任务的错误摘要
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 连接公网 xworkmate-bridge
|
||||||
|
|
||||||
|
### `MANUAL-BRIDGE-REMOTE-001` 公网 bridge 使用 `BRIDGE_AUTH_TOKEN`
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- 当前网络可访问 `https://xworkmate-bridge.svc.plus`
|
||||||
|
- 准备公网组合 1 的 `BRIDGE_AUTH_TOKEN`
|
||||||
|
- 操作步骤
|
||||||
|
1. 打开 `Settings -> Integrations`
|
||||||
|
2. 切换到 `AI 智能体工作空间`
|
||||||
|
3. 在 `Bridge 地址` 输入 `https://xworkmate-bridge.svc.plus`
|
||||||
|
4. 在 `鉴权令牌 (TOKEN)` 输入 `***REMOVED-CREDENTIAL***`
|
||||||
|
5. 点击 `保存配置`
|
||||||
|
6. 重新进入设置页确认配置仍然存在
|
||||||
|
7. 发起一次需要 AI 智能体工作空间的任务,确认可建立连接
|
||||||
|
- 期望结果
|
||||||
|
- 配置保存成功
|
||||||
|
- 重新进入设置页时 endpoint 保持为公网 bridge 地址
|
||||||
|
- token 不以明文展示
|
||||||
|
- 任务请求走公网 bridge,而不是本地 `127.0.0.1`
|
||||||
|
- 建议记录项
|
||||||
|
- Bridge 地址
|
||||||
|
- token 类型:`BRIDGE_AUTH_TOKEN`
|
||||||
|
- 保存结果
|
||||||
|
- 任务执行结果摘要
|
||||||
|
- 截图点:`AI 智能体工作空间` 保存后的页面
|
||||||
|
|
||||||
|
### `MANUAL-BRIDGE-REMOTE-002` 公网 bridge 使用 `BRIDGE_REVIEW_AUTH_TOKEN`
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- 当前网络可访问 `https://xworkmate-bridge.svc.plus`
|
||||||
|
- 准备公网组合 2 的 `BRIDGE_REVIEW_AUTH_TOKEN`
|
||||||
|
- 操作步骤
|
||||||
|
1. 打开 `Settings -> Integrations -> AI 智能体工作空间`
|
||||||
|
2. 在 `Bridge 地址` 输入 `https://xworkmate-bridge.svc.plus`
|
||||||
|
3. 在 `鉴权令牌 (TOKEN)` 输入 `***REMOVED-CREDENTIAL***`
|
||||||
|
4. 点击 `保存配置`
|
||||||
|
5. 重新进入设置页确认配置稳定
|
||||||
|
6. 发起一次 AI 智能体工作空间任务
|
||||||
|
- 期望结果
|
||||||
|
- 使用 review token 也能保存并建立连接
|
||||||
|
- token 不以明文展示
|
||||||
|
- 任务侧不会把 review token 写入日志明文
|
||||||
|
- 失败时错误信息能区分网络不可达、鉴权失败和服务异常
|
||||||
|
- 建议记录项
|
||||||
|
- Bridge 地址
|
||||||
|
- token 类型:`BRIDGE_REVIEW_AUTH_TOKEN`
|
||||||
|
- 保存结果
|
||||||
|
- 连接或任务结果摘要
|
||||||
|
- 是否在页面或日志看到 secret 明文
|
||||||
|
|
||||||
|
### `MANUAL-BRIDGE-REMOTE-TASKS-001` 公网 bridge 组合 1 执行五类典型任务
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- 已完成 `MANUAL-BRIDGE-REMOTE-001`
|
||||||
|
- 当前配置使用 `BRIDGE_AUTH_TOKEN`
|
||||||
|
- 操作步骤
|
||||||
|
1. 确认 `Bridge 地址` 为 `https://xworkmate-bridge.svc.plus`
|
||||||
|
2. 确认 token 类型为 `BRIDGE_AUTH_TOKEN`
|
||||||
|
3. 新建或进入一个测试线程
|
||||||
|
4. 依次执行 `TASK-CASE-001` 到 `TASK-CASE-005`
|
||||||
|
5. 每个任务完成后重新进入设置页,确认公网 bridge 配置未丢失
|
||||||
|
- 期望结果
|
||||||
|
- 五类任务均通过公网 bridge 组合 1 执行
|
||||||
|
- 生成 Markdown、视频、图片系列和 PDF 产物
|
||||||
|
- 任务不会回退到本地 `127.0.0.1`
|
||||||
|
- bridge token 不出现在页面、任务摘要或普通日志明文中
|
||||||
|
- 建议记录项
|
||||||
|
- token 类型
|
||||||
|
- 每类任务产物路径
|
||||||
|
- bridge 连接结果
|
||||||
|
- 是否出现 endpoint 回退
|
||||||
|
|
||||||
|
### `MANUAL-BRIDGE-REMOTE-TASKS-002` 公网 bridge 组合 2 执行五类典型任务
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- 已完成 `MANUAL-BRIDGE-REMOTE-002`
|
||||||
|
- 当前配置使用 `BRIDGE_REVIEW_AUTH_TOKEN`
|
||||||
|
- 操作步骤
|
||||||
|
1. 确认 `Bridge 地址` 为 `https://xworkmate-bridge.svc.plus`
|
||||||
|
2. 确认 token 类型为 `BRIDGE_REVIEW_AUTH_TOKEN`
|
||||||
|
3. 新建或进入一个测试线程
|
||||||
|
4. 依次执行 `TASK-CASE-001` 到 `TASK-CASE-005`
|
||||||
|
5. 对比五类任务的启动、执行、产物回写是否与组合 1 一致
|
||||||
|
- 期望结果
|
||||||
|
- review token 能支持五类典型评审任务
|
||||||
|
- Markdown、视频、图片系列和 PDF 均能作为产物回写
|
||||||
|
- 若某类任务受权限限制失败,错误信息应明确说明鉴权或能力限制
|
||||||
|
- 不泄漏 `BRIDGE_REVIEW_AUTH_TOKEN` 明文
|
||||||
|
- 建议记录项
|
||||||
|
- token 类型
|
||||||
|
- 五类任务结果摘要
|
||||||
|
- 失败任务错误码或错误文案
|
||||||
|
- 截图点:最终产物列表
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 连接本地 xworkmate-bridge
|
||||||
|
|
||||||
|
### `MANUAL-BRIDGE-LOCAL-001` 本地 bridge 使用 `BRIDGE_AUTH_TOKEN`
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- 本机已启动 `xworkmate-bridge`
|
||||||
|
- `http://127.0.0.1:8787` 可访问
|
||||||
|
- 准备本地组合的 `BRIDGE_AUTH_TOKEN`
|
||||||
|
- 操作步骤
|
||||||
|
1. 打开 `Settings -> Integrations -> AI 智能体工作空间`
|
||||||
|
2. 在 `Bridge 地址` 输入 `http://127.0.0.1:8787`
|
||||||
|
3. 在 `鉴权令牌 (TOKEN)` 输入 `***REMOVED-CREDENTIAL***`
|
||||||
|
4. 点击 `保存配置`
|
||||||
|
5. 发起一次 AI 智能体工作空间任务
|
||||||
|
6. 对照本地 bridge 日志确认请求到达
|
||||||
|
- 期望结果
|
||||||
|
- 配置保存成功
|
||||||
|
- 任务请求命中本地 bridge
|
||||||
|
- 页面不会把公网 bridge 与本地 bridge endpoint 混用
|
||||||
|
- 重新进入设置页后仍显示本地 bridge 地址
|
||||||
|
- 建议记录项
|
||||||
|
- 本地 bridge 监听地址
|
||||||
|
- App 保存结果
|
||||||
|
- 本地 bridge 日志摘要
|
||||||
|
- 任务结果摘要
|
||||||
|
|
||||||
|
### `MANUAL-BRIDGE-LOCAL-002` 本地 bridge 未启动时的错误提示
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- 本地 `xworkmate-bridge` 未启动
|
||||||
|
- 设置页使用 `http://127.0.0.1:8787`
|
||||||
|
- 操作步骤
|
||||||
|
1. 打开 `Settings -> Integrations -> AI 智能体工作空间`
|
||||||
|
2. 保存本地 bridge 地址与 token
|
||||||
|
3. 发起一次 AI 智能体工作空间任务
|
||||||
|
4. 观察页面错误提示
|
||||||
|
- 期望结果
|
||||||
|
- 保存配置可完成,或给出明确的连接失败提示
|
||||||
|
- 任务失败信息明确指向本地 bridge 不可达
|
||||||
|
- 不会自动回退到公网 bridge
|
||||||
|
- 不会清空用户刚输入的本地配置
|
||||||
|
- 建议记录项
|
||||||
|
- 错误提示文案
|
||||||
|
- 任务失败摘要
|
||||||
|
- 设置页配置是否保留
|
||||||
|
|
||||||
|
### `MANUAL-BRIDGE-LOCAL-TASKS-001` 本地 bridge 执行五类典型任务
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- 已完成 `MANUAL-BRIDGE-LOCAL-001`
|
||||||
|
- 本地 `xworkmate-bridge` 保持运行
|
||||||
|
- 操作步骤
|
||||||
|
1. 确认 `Bridge 地址` 为 `http://127.0.0.1:8787`
|
||||||
|
2. 新建或进入一个测试线程
|
||||||
|
3. 依次执行 `TASK-CASE-001` 到 `TASK-CASE-005`
|
||||||
|
4. 每个任务完成后对照本地 bridge 日志
|
||||||
|
5. 重新进入设置页确认本地 bridge 地址仍然保留
|
||||||
|
- 期望结果
|
||||||
|
- 五类任务请求均命中本地 bridge
|
||||||
|
- 任务产物正常回写到当前线程
|
||||||
|
- 断网或公网不可用时,本地 bridge 任务仍按本地能力给出明确结果
|
||||||
|
- 不会自动切换到公网 bridge
|
||||||
|
- 建议记录项
|
||||||
|
- 本地 bridge 日志摘要
|
||||||
|
- 五类任务产物路径
|
||||||
|
- 是否发生公网 endpoint 混用
|
||||||
|
- 截图点:设置页与任务产物
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 云端同步与手动配置共存
|
||||||
|
|
||||||
|
### `MANUAL-BRIDGE-MIXED-001` 云端账号登录后切换公网 bridge 手动配置
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- 已使用 `review@svc.plus` 登录
|
||||||
|
- 云端同步状态稳定
|
||||||
|
- 操作步骤
|
||||||
|
1. 登录 `svc.plus 云端同步`
|
||||||
|
2. 切换到 `AI 智能体工作空间`
|
||||||
|
3. 手动输入公网 bridge 组合 1
|
||||||
|
4. 点击 `保存配置`
|
||||||
|
5. 返回主页面后重新进入设置页
|
||||||
|
- 期望结果
|
||||||
|
- 手动 bridge 配置保存成功
|
||||||
|
- 云端账号登录状态不丢失
|
||||||
|
- 本地手动配置不会被同一轮页面刷新异常覆盖
|
||||||
|
- 页面能区分云端同步状态与 AI 智能体工作空间连接状态
|
||||||
|
- 建议记录项
|
||||||
|
- 登录账号
|
||||||
|
- 同步状态
|
||||||
|
- 手动配置 endpoint
|
||||||
|
- 重新进入设置页后的 endpoint
|
||||||
|
|
||||||
|
### `MANUAL-BRIDGE-MIXED-002` 公网 bridge 与本地 bridge 来回切换
|
||||||
|
|
||||||
|
- 前置条件
|
||||||
|
- 公网 bridge 可访问
|
||||||
|
- 本地 bridge 可按需启动
|
||||||
|
- 操作步骤
|
||||||
|
1. 保存公网 bridge 组合 1
|
||||||
|
2. 发起一次任务并记录结果
|
||||||
|
3. 切换为本地 bridge 组合
|
||||||
|
4. 发起一次任务并记录结果
|
||||||
|
5. 再次切换回公网 bridge 组合 2
|
||||||
|
6. 重新进入设置页确认最终配置
|
||||||
|
- 期望结果
|
||||||
|
- 每次切换后 endpoint 与 token 类型都按用户最后一次保存生效
|
||||||
|
- 任务请求不会继续使用旧 endpoint
|
||||||
|
- 页面不会出现公网 / 本地配置交叉污染
|
||||||
|
- 最终配置以最后一次保存为准
|
||||||
|
- 建议记录项
|
||||||
|
- 三次保存的 endpoint
|
||||||
|
- 三次任务结果摘要
|
||||||
|
- 最终设置页截图
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 回归覆盖矩阵
|
||||||
|
|
||||||
|
| 测试编号 | 云端账号 | 公网 Bridge | 本地 Bridge | Secret 隐藏 | 配置持久化 |
|
||||||
|
|----------|:--------:|:-----------:|:-----------:|:-----------:|:----------:|
|
||||||
|
| MANUAL-CLOUD-001 | ✅ | ✅ | - | ✅ | ✅ |
|
||||||
|
| MANUAL-CLOUD-002 | ✅ | ✅ | - | ✅ | ✅ |
|
||||||
|
| MANUAL-CLOUD-TASKS-001 | ✅ | ✅ | - | ✅ | ✅ |
|
||||||
|
| MANUAL-BRIDGE-REMOTE-001 | - | ✅ | - | ✅ | ✅ |
|
||||||
|
| MANUAL-BRIDGE-REMOTE-002 | - | ✅ | - | ✅ | ✅ |
|
||||||
|
| MANUAL-BRIDGE-REMOTE-TASKS-001 | - | ✅ | - | ✅ | ✅ |
|
||||||
|
| MANUAL-BRIDGE-REMOTE-TASKS-002 | - | ✅ | - | ✅ | ✅ |
|
||||||
|
| MANUAL-BRIDGE-LOCAL-001 | - | - | ✅ | ✅ | ✅ |
|
||||||
|
| MANUAL-BRIDGE-LOCAL-002 | - | - | ✅ | ✅ | ✅ |
|
||||||
|
| MANUAL-BRIDGE-LOCAL-TASKS-001 | - | - | ✅ | ✅ | ✅ |
|
||||||
|
| MANUAL-BRIDGE-MIXED-001 | ✅ | ✅ | - | ✅ | ✅ |
|
||||||
|
| MANUAL-BRIDGE-MIXED-002 | - | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
|
## 9. 典型任务覆盖矩阵
|
||||||
|
|
||||||
|
| 连接方式 | CASE-001 AI 资讯 MD | CASE-002 图片视频 | CASE-003 7 图系列 | CASE-004 软文矩阵 | CASE-005 图文 PDF |
|
||||||
|
|----------|:-------------------:|:-----------------:|:-----------------:|:-----------------:|:-----------------:|
|
||||||
|
| 云端账号 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| 公网 bridge 组合 1 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| 公网 bridge 组合 2 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| 本地 bridge | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
|
> 注意:以上 token 为评审 / 测试用途。执行测试时不得将 token 明文贴入公开 issue、公开日志或截图备注。
|
||||||
41
docs/cases/codex-goal-stability.md
Normal file
41
docs/cases/codex-goal-stability.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Codex /goal|XWorkmate App 稳定性修复
|
||||||
|
|
||||||
|
```text
|
||||||
|
/goal
|
||||||
|
你在 /Users/shenlan/workspaces/ai-workspace-lab/xworkmate-app 仓库中持续执行稳定性修复。
|
||||||
|
|
||||||
|
请参考:
|
||||||
|
- /Users/shenlan/workspaces/ai-workspace-lab/xworkspace-console/docs/case/
|
||||||
|
- /Users/shenlan/workspaces/ai-workspace-lab/xworkmate-app/docs/cases/
|
||||||
|
|
||||||
|
目标不是重构 UI,而是围绕 Review 账号、公网 xworkmate-bridge、本地 xworkmate-bridge 和 5 类典型任务用例,修复 App 在登录、Bridge 连接、Token 校验、任务创建、状态流、长任务恢复、取消重试、Artifact 展示中的稳定性问题。
|
||||||
|
|
||||||
|
环境覆盖:
|
||||||
|
1. 云端只读评审账号:accounts.svc.plus / review@svc.plus,密码从 XWORKMATE_REVIEW_PASSWORD 读取。
|
||||||
|
2. 公网 Bridge:BRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus,支持 BRIDGE_AUTH_TOKEN。
|
||||||
|
3. 公网 Review Bridge:BRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus,支持 BRIDGE_REVIEW_AUTH_TOKEN。
|
||||||
|
4. 本地 Bridge:BRIDGE_SERVER_URL=http://127.0.0.1:8787,支持 BRIDGE_AUTH_TOKEN。
|
||||||
|
|
||||||
|
典型任务用例:
|
||||||
|
1. AI 资讯 Markdown。
|
||||||
|
2. 图片制作视频。
|
||||||
|
3. 7 张连续图片。
|
||||||
|
4. 多平台软文矩阵。
|
||||||
|
5. 章节拆解生成图文 PDF。
|
||||||
|
|
||||||
|
执行要求:
|
||||||
|
1. 先阅读 docs/cases 下的用例和覆盖矩阵,梳理当前 App 中对应链路。
|
||||||
|
2. 不改变现有主视觉和核心交互,优先修复崩溃、卡死、无限 Loading、状态丢失、错误提示不清晰、Token 分支混乱、长任务恢复失败等问题。
|
||||||
|
3. 优先补齐连接层、任务状态层、错误处理层、日志可观测性和最小回归测试。
|
||||||
|
4. 对每一处修复,说明触发场景、根因、修改文件、验证方式。
|
||||||
|
5. 每轮只做小步提交,避免大面积重构。
|
||||||
|
6. 禁止把真实密码或 Token 写入仓库;统一通过环境变量、Secret 或本地 .env.local 读取。
|
||||||
|
7. 如果发现用例与当前实现不一致,优先补充兼容层或清晰错误提示,不要直接删除功能入口。
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
- 三种 Bridge 连接模式均能给出明确成功或失败状态。
|
||||||
|
- Review 账号登录失败、Token 错误、Bridge 不可达时 App 不崩溃。
|
||||||
|
- 五类典型任务至少能完成任务创建、状态展示、失败提示和结果/Artifact 展示的主链路验证。
|
||||||
|
- 长任务切后台再恢复后,状态不会错乱。
|
||||||
|
- 连续执行 3 次同类任务,不出现任务串线、重复提交、空白结果页。
|
||||||
|
```
|
||||||
@ -49,6 +49,7 @@ abstract final class UiFeatureKeys {
|
|||||||
static const settingsArchivedTasks = 'settings.archived_tasks';
|
static const settingsArchivedTasks = 'settings.archived_tasks';
|
||||||
static const settingsRemoteDesktop = 'settings.remote_desktop';
|
static const settingsRemoteDesktop = 'settings.remote_desktop';
|
||||||
static const settingsLogs = 'settings.logs';
|
static const settingsLogs = 'settings.logs';
|
||||||
|
static const settingsHelp = 'settings.help';
|
||||||
static const settingsAccountAccess = 'settings.account_access';
|
static const settingsAccountAccess = 'settings.account_access';
|
||||||
static const settingsVaultServer = 'settings.vault_server';
|
static const settingsVaultServer = 'settings.vault_server';
|
||||||
static const settingsExperimentalCanvas = 'settings.experimental_canvas';
|
static const settingsExperimentalCanvas = 'settings.experimental_canvas';
|
||||||
@ -370,6 +371,7 @@ class UiFeatureAccess {
|
|||||||
UiFeatureKeys.settingsArchivedTasks: SettingsTab.archivedTasks,
|
UiFeatureKeys.settingsArchivedTasks: SettingsTab.archivedTasks,
|
||||||
UiFeatureKeys.settingsRemoteDesktop: SettingsTab.remoteDesktop,
|
UiFeatureKeys.settingsRemoteDesktop: SettingsTab.remoteDesktop,
|
||||||
UiFeatureKeys.settingsLogs: SettingsTab.logs,
|
UiFeatureKeys.settingsLogs: SettingsTab.logs,
|
||||||
|
UiFeatureKeys.settingsHelp: SettingsTab.help,
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isEnabledPath(String path) {
|
bool isEnabledPath(String path) {
|
||||||
|
|||||||
@ -6,11 +6,15 @@ import 'desktop_client.dart';
|
|||||||
import 'desktop_input_handler.dart';
|
import 'desktop_input_handler.dart';
|
||||||
import '../../app/app_controller.dart';
|
import '../../app/app_controller.dart';
|
||||||
import '../../runtime/gateway_acp_client.dart';
|
import '../../runtime/gateway_acp_client.dart';
|
||||||
|
import '../../runtime/runtime_models.dart';
|
||||||
import '../../widgets/surface_card.dart';
|
import '../../widgets/surface_card.dart';
|
||||||
import '../../i18n/app_language.dart';
|
import '../../i18n/app_language.dart';
|
||||||
import '../workspace_management/workspace_management_panel.dart';
|
import '../workspace_management/workspace_management_panel.dart';
|
||||||
import '../workspace_management/workspace_management_i18n.dart';
|
import '../workspace_management/workspace_management_i18n.dart';
|
||||||
|
|
||||||
|
bool desktopRemoteWorkspaceConnectionEnabled(RuntimeConnectionMode mode) =>
|
||||||
|
mode == RuntimeConnectionMode.remote;
|
||||||
|
|
||||||
class DesktopView extends StatefulWidget {
|
class DesktopView extends StatefulWidget {
|
||||||
const DesktopView({
|
const DesktopView({
|
||||||
super.key,
|
super.key,
|
||||||
@ -239,8 +243,8 @@ class _DesktopViewState extends State<DesktopView> {
|
|||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
appText(
|
appText(
|
||||||
'连接AI工作空间失败: $message',
|
'连接远程 AI 工作空间失败: $message',
|
||||||
'Failed to connect AI Workspace: $message',
|
'Failed to connect Remote AI Workspace: $message',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.redAccent,
|
backgroundColor: Colors.redAccent,
|
||||||
@ -252,7 +256,10 @@ class _DesktopViewState extends State<DesktopView> {
|
|||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
appText('连接AI工作空间失败: $e', 'Failed to connect AI Workspace: $e'),
|
appText(
|
||||||
|
'连接远程 AI 工作空间失败: $e',
|
||||||
|
'Failed to connect Remote AI Workspace: $e',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.redAccent,
|
backgroundColor: Colors.redAccent,
|
||||||
),
|
),
|
||||||
@ -284,6 +291,9 @@ class _DesktopViewState extends State<DesktopView> {
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final isDark = theme.brightness == Brightness.dark;
|
final isDark = theme.brightness == Brightness.dark;
|
||||||
final hasVideoFrame = _hasVideoFrame;
|
final hasVideoFrame = _hasVideoFrame;
|
||||||
|
final canConnectRemoteWorkspace = desktopRemoteWorkspaceConnectionEnabled(
|
||||||
|
widget.controller.connection.mode,
|
||||||
|
);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
@ -305,7 +315,9 @@ class _DesktopViewState extends State<DesktopView> {
|
|||||||
children: [
|
children: [
|
||||||
// Connection Button
|
// Connection Button
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: _toggleConnection,
|
onPressed: canConnectRemoteWorkspace
|
||||||
|
? _toggleConnection
|
||||||
|
: null,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: _connectionState == 'connected'
|
backgroundColor: _connectionState == 'connected'
|
||||||
? Colors.redAccent
|
? Colors.redAccent
|
||||||
@ -332,8 +344,8 @@ class _DesktopViewState extends State<DesktopView> {
|
|||||||
: (_connectionState == 'connecting'
|
: (_connectionState == 'connecting'
|
||||||
? appText('正在连接...', 'Connecting...')
|
? appText('正在连接...', 'Connecting...')
|
||||||
: appText(
|
: appText(
|
||||||
'连接AI工作空间',
|
'连接远程 AI 工作空间',
|
||||||
'Connect AI Workspace',
|
'Connect Remote AI Workspace',
|
||||||
)),
|
)),
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
@ -563,8 +575,8 @@ class _DesktopViewState extends State<DesktopView> {
|
|||||||
'Establishing WebRTC connection, please wait...',
|
'Establishing WebRTC connection, please wait...',
|
||||||
)
|
)
|
||||||
: appText(
|
: appText(
|
||||||
'未开启 AI 工作空间流。点击“连接AI工作空间”启动视频流。',
|
'未开启远程 AI 工作空间流。点击“连接远程 AI 工作空间”启动视频流。',
|
||||||
'AI Workspace stream not enabled. Click "Connect AI Workspace" to start the video stream.',
|
'Remote AI Workspace stream not enabled. Click "Connect Remote AI Workspace" to start the video stream.',
|
||||||
),
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: theme.colorScheme.onSurface
|
color: theme.colorScheme.onSurface
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import '../../runtime/runtime_controllers.dart';
|
|||||||
import '../../runtime/runtime_models.dart';
|
import '../../runtime/runtime_models.dart';
|
||||||
import '../../theme/app_palette.dart';
|
import '../../theme/app_palette.dart';
|
||||||
import 'mobile_settings_page_widgets.dart';
|
import 'mobile_settings_page_widgets.dart';
|
||||||
|
import '../settings/settings_help_panel.dart';
|
||||||
|
|
||||||
class MobileSettingsPage extends StatefulWidget {
|
class MobileSettingsPage extends StatefulWidget {
|
||||||
const MobileSettingsPage({super.key, required this.controller});
|
const MobileSettingsPage({super.key, required this.controller});
|
||||||
@ -185,12 +186,14 @@ class _MobileSettingsPageState extends State<MobileSettingsPage> {
|
|||||||
await controller.refreshSingleAgentCapabilitiesInternal(
|
await controller.refreshSingleAgentCapabilitiesInternal(
|
||||||
forceRefresh: true,
|
forceRefresh: true,
|
||||||
);
|
);
|
||||||
} catch (e, stackTrace) { debugPrint('Error: $e\n$stackTrace');
|
} catch (e, stackTrace) {
|
||||||
|
debugPrint('Error: $e\n$stackTrace');
|
||||||
// Account login should not fail only because runtime refresh is transient.
|
// Account login should not fail only because runtime refresh is transient.
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await controller.refreshAcpCapabilitiesInternal(forceRefresh: true);
|
await controller.refreshAcpCapabilitiesInternal(forceRefresh: true);
|
||||||
} catch (e, stackTrace) { debugPrint('Error: $e\n$stackTrace');
|
} catch (e, stackTrace) {
|
||||||
|
debugPrint('Error: $e\n$stackTrace');
|
||||||
// Runtime capabilities can be refreshed again from Assistant.
|
// Runtime capabilities can be refreshed again from Assistant.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,6 +277,8 @@ class _MobileSettingsPageState extends State<MobileSettingsPage> {
|
|||||||
],
|
],
|
||||||
if (currentTab == SettingsTab.archivedTasks)
|
if (currentTab == SettingsTab.archivedTasks)
|
||||||
_ArchivedTasksSection(controller: controller)
|
_ArchivedTasksSection(controller: controller)
|
||||||
|
else if (currentTab == SettingsTab.help)
|
||||||
|
const SettingsHelpPanel()
|
||||||
else
|
else
|
||||||
_AccountSection(
|
_AccountSection(
|
||||||
settings: settings,
|
settings: settings,
|
||||||
|
|||||||
@ -26,7 +26,9 @@ class MobileSettingsTabSelectorInternal extends StatelessWidget {
|
|||||||
icon: Icon(
|
icon: Icon(
|
||||||
tab == SettingsTab.archivedTasks
|
tab == SettingsTab.archivedTasks
|
||||||
? Icons.inventory_2_outlined
|
? Icons.inventory_2_outlined
|
||||||
: Icons.hub_outlined,
|
: (tab == SettingsTab.help
|
||||||
|
? Icons.help_outline_rounded
|
||||||
|
: Icons.hub_outlined),
|
||||||
),
|
),
|
||||||
label: Text(tab.label),
|
label: Text(tab.label),
|
||||||
),
|
),
|
||||||
|
|||||||
239
lib/features/settings/settings_help_panel.dart
Normal file
239
lib/features/settings/settings_help_panel.dart
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import '../../i18n/app_language.dart';
|
||||||
|
import '../../theme/app_palette.dart';
|
||||||
|
|
||||||
|
class SettingsHelpPanel extends StatelessWidget {
|
||||||
|
const SettingsHelpPanel({super.key});
|
||||||
|
|
||||||
|
static const _sections = <_HelpSection>[
|
||||||
|
_HelpSection(
|
||||||
|
title: '1. 快速安装(一键部署)',
|
||||||
|
codeLabel: 'bash',
|
||||||
|
code: '''
|
||||||
|
curl -sfL https://install.svc.plus/ai-workspace | bash -
|
||||||
|
''',
|
||||||
|
),
|
||||||
|
_HelpSection(
|
||||||
|
title: '2. 带 API Key 安装',
|
||||||
|
codeLabel: 'bash',
|
||||||
|
code: '''
|
||||||
|
export DEEPSEEK_API_KEY="<your-deepseek-api-key>"
|
||||||
|
export NVIDIA_API_KEY="<your-nvidia-api-key>"
|
||||||
|
export OLLAMA_API_KEY="<your-ollama-api-key>"
|
||||||
|
curl -sfL https://install.svc.plus/ai-workspace | bash -
|
||||||
|
''',
|
||||||
|
),
|
||||||
|
_HelpSection(
|
||||||
|
title: '3. 卸载(保留数据)',
|
||||||
|
codeLabel: 'bash',
|
||||||
|
code: '''
|
||||||
|
curl -sfL https://install.svc.plus/ai-workspace | bash -s -- uninstall
|
||||||
|
''',
|
||||||
|
),
|
||||||
|
_HelpSection(
|
||||||
|
title: '4. 彻底卸载(清除所有数据)',
|
||||||
|
codeLabel: 'bash',
|
||||||
|
code: '''
|
||||||
|
curl -sfL https://install.svc.plus/ai-workspace | bash -s -- uninstall --purge
|
||||||
|
''',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final palette = context.palette;
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.fromLTRB(24, 24, 24, 28),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
appText('帮助', 'Help'),
|
||||||
|
style: theme.textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
appText(
|
||||||
|
'测试提示词模板,本文档提供了用于测试 setup-ai-workspace-all-in-one.sh 的标准化提示词模板,可直接复制粘贴到终端执行。',
|
||||||
|
'Test prompt templates for setup-ai-workspace-all-in-one.sh. Copy and paste directly into your terminal.',
|
||||||
|
),
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: palette.textSecondary,
|
||||||
|
height: 1.45,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
for (final section in _sections) ...[
|
||||||
|
_HelpCodeSectionCard(section: section),
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
],
|
||||||
|
_InfoBlock(
|
||||||
|
title: appText('环境变量参考', 'Environment variables'),
|
||||||
|
child: Table(
|
||||||
|
columnWidths: const <int, TableColumnWidth>{
|
||||||
|
0: FlexColumnWidth(1.1),
|
||||||
|
1: FlexColumnWidth(1.4),
|
||||||
|
2: FlexColumnWidth(0.7),
|
||||||
|
},
|
||||||
|
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||||
|
children: [
|
||||||
|
_tableRow('DEEPSEEK_API_KEY', 'DeepSeek 模型 API 密钥', '可选'),
|
||||||
|
_tableRow('NVIDIA_API_KEY', 'NVIDIA NIM API 密钥', '可选'),
|
||||||
|
_tableRow('OLLAMA_API_KEY', 'Ollama 服务 API 密钥', '可选'),
|
||||||
|
_tableRow('PLAYBOOK_DIR', '本地 Playbook 目录路径(开发调试用)', '可选'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
_InfoBlock(
|
||||||
|
title: appText('支持平台', 'Supported platforms'),
|
||||||
|
child: Table(
|
||||||
|
columnWidths: const <int, TableColumnWidth>{
|
||||||
|
0: FlexColumnWidth(1.1),
|
||||||
|
1: FlexColumnWidth(0.7),
|
||||||
|
},
|
||||||
|
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||||
|
children: [
|
||||||
|
_tableRow('macOS (Apple Silicon / Intel)', '已测试'),
|
||||||
|
_tableRow('Debian 11/12', '已测试'),
|
||||||
|
_tableRow('Ubuntu 22.04/24.04', '已测试'),
|
||||||
|
_tableRow('其他 Linux 发行版', '未测试'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TableRow _tableRow(String a, String b, [String? c]) {
|
||||||
|
return TableRow(
|
||||||
|
children: [_tableCell(a), _tableCell(b), if (c != null) _tableCell(c)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _tableCell(String text) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 6),
|
||||||
|
child: Text(text),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InfoBlock extends StatelessWidget {
|
||||||
|
const _InfoBlock({required this.title, required this.child});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final palette = context.palette;
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(18),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: palette.surfaceSecondary,
|
||||||
|
borderRadius: BorderRadius.circular(18),
|
||||||
|
border: Border.all(color: palette.strokeSoft),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(title, style: theme.textTheme.titleMedium),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HelpSection {
|
||||||
|
const _HelpSection({
|
||||||
|
required this.title,
|
||||||
|
required this.codeLabel,
|
||||||
|
required this.code,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final String codeLabel;
|
||||||
|
final String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HelpCodeSectionCard extends StatelessWidget {
|
||||||
|
const _HelpCodeSectionCard({required this.section});
|
||||||
|
|
||||||
|
final _HelpSection section;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final palette = context.palette;
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
section.title,
|
||||||
|
style: theme.textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: palette.surfacePrimary,
|
||||||
|
borderRadius: BorderRadius.circular(18),
|
||||||
|
border: Border.all(color: palette.strokeSoft),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(24, 18, 18, 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
section.codeLabel,
|
||||||
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
|
color: palette.textSecondary,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
tooltip: appText('复制', 'Copy'),
|
||||||
|
onPressed: () => Clipboard.setData(
|
||||||
|
ClipboardData(text: section.code.trim()),
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.content_copy_rounded),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: const EdgeInsets.all(18),
|
||||||
|
child: SelectableText(
|
||||||
|
section.code.trimRight(),
|
||||||
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
height: 1.7,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@ import '../../widgets/surface_card.dart';
|
|||||||
import 'settings_account_panel.dart';
|
import 'settings_account_panel.dart';
|
||||||
import 'settings_about_panel.dart';
|
import 'settings_about_panel.dart';
|
||||||
import 'settings_archived_tasks_panel.dart';
|
import 'settings_archived_tasks_panel.dart';
|
||||||
|
import 'settings_help_panel.dart';
|
||||||
import 'settings_logs_panel.dart';
|
import 'settings_logs_panel.dart';
|
||||||
import 'settings_remote_desktop_panel.dart';
|
import 'settings_remote_desktop_panel.dart';
|
||||||
|
|
||||||
@ -556,6 +557,11 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
onStop: (sessionKey) => _stopArchivedTask(sessionKey),
|
onStop: (sessionKey) => _stopArchivedTask(sessionKey),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
] else if (currentTab == SettingsTab.help) ...[
|
||||||
|
SurfaceCard(
|
||||||
|
key: const ValueKey('settings-help-panel-card'),
|
||||||
|
child: const SettingsHelpPanel(),
|
||||||
|
),
|
||||||
] else if (currentTab == SettingsTab.remoteDesktop) ...[
|
] else if (currentTab == SettingsTab.remoteDesktop) ...[
|
||||||
SurfaceCard(
|
SurfaceCard(
|
||||||
key: const ValueKey('settings-remote-desktop-panel-card'),
|
key: const ValueKey('settings-remote-desktop-panel-card'),
|
||||||
@ -603,9 +609,11 @@ class _SettingsTabSelector extends StatelessWidget {
|
|||||||
? Icons.desktop_windows_outlined
|
? Icons.desktop_windows_outlined
|
||||||
: (tab == SettingsTab.logs
|
: (tab == SettingsTab.logs
|
||||||
? Icons.terminal_outlined
|
? Icons.terminal_outlined
|
||||||
|
: (tab == SettingsTab.help
|
||||||
|
? Icons.help_outline_rounded
|
||||||
: (tab == SettingsTab.archivedTasks
|
: (tab == SettingsTab.archivedTasks
|
||||||
? Icons.inventory_2_outlined
|
? Icons.inventory_2_outlined
|
||||||
: Icons.hub_outlined)),
|
: Icons.hub_outlined))),
|
||||||
),
|
),
|
||||||
label: Text(tab.label),
|
label: Text(tab.label),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -149,7 +149,7 @@ extension AssistantModeCopy on AssistantMode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SettingsTab { gateway, archivedTasks, remoteDesktop, logs }
|
enum SettingsTab { gateway, archivedTasks, remoteDesktop, logs, help }
|
||||||
|
|
||||||
extension SettingsTabCopy on SettingsTab {
|
extension SettingsTabCopy on SettingsTab {
|
||||||
String get label => switch (this) {
|
String get label => switch (this) {
|
||||||
@ -157,6 +157,7 @@ extension SettingsTabCopy on SettingsTab {
|
|||||||
SettingsTab.archivedTasks => appText('归档任务', 'Archived tasks'),
|
SettingsTab.archivedTasks => appText('归档任务', 'Archived tasks'),
|
||||||
SettingsTab.remoteDesktop => appText('AI工作空间', 'AI Workspace'),
|
SettingsTab.remoteDesktop => appText('AI工作空间', 'AI Workspace'),
|
||||||
SettingsTab.logs => appText('运行日志', 'Runtime Logs'),
|
SettingsTab.logs => appText('运行日志', 'Runtime Logs'),
|
||||||
|
SettingsTab.help => appText('帮助', 'Help'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
platform :osx, '10.15'
|
platform :osx, '14.0'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
@ -97,7 +97,7 @@ post_install do |installer|
|
|||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_macos_build_settings(target)
|
flutter_additional_macos_build_settings(target)
|
||||||
target.build_configurations.each do |config|
|
target.build_configurations.each do |config|
|
||||||
config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '11.5'
|
config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '14.0'
|
||||||
|
|
||||||
next unless ['Pods-Runner', 'Pods-RunnerTests', 'WebRTC-SDK', 'flutter_webrtc'].include?(target.name)
|
next unless ['Pods-Runner', 'Pods-RunnerTests', 'WebRTC-SDK', 'flutter_webrtc'].include?(target.name)
|
||||||
|
|
||||||
|
|||||||
@ -61,6 +61,6 @@ SPEC CHECKSUMS:
|
|||||||
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
|
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
|
||||||
WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db
|
WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db
|
||||||
|
|
||||||
PODFILE CHECKSUM: 0ff086e365498790bfa0f77c18fab96518c27b1e
|
PODFILE CHECKSUM: 1eb7d5d1472c632b8f775dd34562291c20ae818a
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@ -511,7 +511,7 @@
|
|||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
OTHER_CFLAGS = (
|
OTHER_CFLAGS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@ -539,7 +539,7 @@
|
|||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
OTHER_CFLAGS = (
|
OTHER_CFLAGS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@ -567,7 +567,7 @@
|
|||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
OTHER_CFLAGS = (
|
OTHER_CFLAGS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@ -629,7 +629,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
@ -665,7 +665,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.5;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
OTHER_CFLAGS = (
|
OTHER_CFLAGS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"-Wno-ignored-attributes",
|
"-Wno-ignored-attributes",
|
||||||
@ -688,7 +688,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
};
|
};
|
||||||
name = Profile;
|
name = Profile;
|
||||||
@ -740,7 +740,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
@ -791,7 +791,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
@ -827,7 +827,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.5;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
OTHER_CFLAGS = (
|
OTHER_CFLAGS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"-Wno-ignored-attributes",
|
"-Wno-ignored-attributes",
|
||||||
@ -867,7 +867,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.5;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
OTHER_CFLAGS = (
|
OTHER_CFLAGS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"-Wno-ignored-attributes",
|
"-Wno-ignored-attributes",
|
||||||
@ -890,7 +890,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@ -899,7 +899,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|||||||
25
test/features/settings/settings_help_panel_test.dart
Normal file
25
test/features/settings/settings_help_panel_test.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:xworkmate/features/settings/settings_help_panel.dart';
|
||||||
|
import 'package:xworkmate/theme/app_theme.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('renders setup help sections and metadata tables', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: AppTheme.light(),
|
||||||
|
home: const Scaffold(body: SettingsHelpPanel()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text('帮助'), findsWidgets);
|
||||||
|
expect(find.text('1. 快速安装(一键部署)'), findsOneWidget);
|
||||||
|
expect(find.text('2. 带 API Key 安装'), findsOneWidget);
|
||||||
|
expect(find.text('环境变量参考'), findsOneWidget);
|
||||||
|
expect(find.text('支持平台'), findsOneWidget);
|
||||||
|
expect(find.text('DEEPSEEK_API_KEY'), findsOneWidget);
|
||||||
|
expect(find.text('macOS (Apple Silicon / Intel)'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:xworkmate/app/app_controller.dart';
|
import 'package:xworkmate/app/app_controller.dart';
|
||||||
|
import 'package:xworkmate/features/desktop/desktop_view.dart';
|
||||||
import 'package:xworkmate/features/settings/settings_remote_desktop_panel.dart';
|
import 'package:xworkmate/features/settings/settings_remote_desktop_panel.dart';
|
||||||
import 'package:xworkmate/runtime/secure_config_store.dart';
|
import 'package:xworkmate/runtime/secure_config_store.dart';
|
||||||
import 'package:xworkmate/runtime/runtime_models.dart';
|
import 'package:xworkmate/runtime/runtime_models.dart';
|
||||||
@ -8,8 +9,23 @@ import 'package:xworkmate/theme/app_theme.dart';
|
|||||||
import 'package:xworkmate/widgets/surface_card.dart';
|
import 'package:xworkmate/widgets/surface_card.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
test('remote workspace connection is enabled only in remote mode', () {
|
||||||
|
expect(
|
||||||
|
desktopRemoteWorkspaceConnectionEnabled(RuntimeConnectionMode.remote),
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
desktopRemoteWorkspaceConnectionEnabled(
|
||||||
|
RuntimeConnectionMode.unconfigured,
|
||||||
|
),
|
||||||
|
isFalse,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
group('SettingsRemoteDesktopPanel', () {
|
group('SettingsRemoteDesktopPanel', () {
|
||||||
testWidgets('renders the panel title and connection dashboard', (tester) async {
|
testWidgets('renders the panel title and connection dashboard', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
// Set desktop window size
|
// Set desktop window size
|
||||||
tester.view.physicalSize = const Size(1280, 900);
|
tester.view.physicalSize = const Size(1280, 900);
|
||||||
tester.view.devicePixelRatio = 1.0;
|
tester.view.devicePixelRatio = 1.0;
|
||||||
@ -30,20 +46,13 @@ void main() {
|
|||||||
|
|
||||||
// Verify the panel headers and titles
|
// Verify the panel headers and titles
|
||||||
expect(find.text('AI工作空间'), findsOneWidget);
|
expect(find.text('AI工作空间'), findsOneWidget);
|
||||||
expect(find.text('连接AI工作空间'), findsOneWidget);
|
expect(find.text('连接远程 AI 工作空间'), findsOneWidget);
|
||||||
|
final connectButton = tester.widget<ElevatedButton>(
|
||||||
|
find.widgetWithText(ElevatedButton, '连接远程 AI 工作空间'),
|
||||||
|
);
|
||||||
|
expect(connectButton.onPressed, isNull);
|
||||||
expect(find.text('工作空间管理'), findsOneWidget);
|
expect(find.text('工作空间管理'), findsOneWidget);
|
||||||
|
|
||||||
// Verify advanced options are hidden initially
|
|
||||||
expect(find.text('GPU 加速'), findsNothing);
|
|
||||||
|
|
||||||
// Tap to expand advanced options
|
|
||||||
await tester.tap(find.text('高级选项'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// Verify advanced options appear
|
|
||||||
expect(find.text('GPU 加速'), findsOneWidget);
|
|
||||||
expect(find.widgetWithText(TextField, 'Display'), findsOneWidget);
|
|
||||||
expect(find.text('Display'), findsOneWidget);
|
|
||||||
expect(find.text('工作空间管理'), findsOneWidget);
|
expect(find.text('工作空间管理'), findsOneWidget);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -54,10 +63,7 @@ Widget _buildTestApp({required Widget child}) {
|
|||||||
theme: AppTheme.light(),
|
theme: AppTheme.light(),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SizedBox(
|
child: SizedBox(width: 1100, child: SurfaceCard(child: child)),
|
||||||
width: 1100,
|
|
||||||
child: SurfaceCard(child: child),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -31,6 +31,7 @@ void main() {
|
|||||||
desktop.availableSettingsTabs,
|
desktop.availableSettingsTabs,
|
||||||
isNot(contains(SettingsTab.remoteDesktop)),
|
isNot(contains(SettingsTab.remoteDesktop)),
|
||||||
);
|
);
|
||||||
|
expect(desktop.availableSettingsTabs, contains(SettingsTab.help));
|
||||||
expect(
|
expect(
|
||||||
desktop.sanitizeSettingsTab(SettingsTab.remoteDesktop),
|
desktop.sanitizeSettingsTab(SettingsTab.remoteDesktop),
|
||||||
SettingsTab.gateway,
|
SettingsTab.gateway,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user