v0.5: add screenshots, update README, fix wizard i18n

This commit is contained in:
2026-02-24 21:12:02 +10:00
parent 8ef619ebdd
commit 04f0bd03b7
2 changed files with 306 additions and 215 deletions
+208 -161
View File
@@ -1,12 +1,162 @@
# OpenClaw Manager # OpenClaw Manager (OCM)
> 🦀 A zero-dependency local web UI for managing [OpenClaw](https://github.com/OpenClaw-AI/openclaw) AI agents > A zero-dependency, single-file web UI for managing [OpenClaw](https://github.com/anthropics/openclaw) AI agents locally.
>
> 零依赖本地 Web 管理界面,告别手动编辑配置文件 [中文说明](#中文说明)
--- ---
[中文](#中文说明) | [English](#english) ## What is this?
OpenClaw Manager is a lightweight local web dashboard that lets you visually manage your OpenClaw AI agents, models, auth, cron jobs, and more — without editing JSON files by hand.
Everything ships as **a single `.js` file** with zero npm dependencies. Just need Node.js 18+.
## Screenshots
### Landing Page
Choose between Sub-agent mode and Multi-agent mode. Switch language (EN / 中文) anytime.
![Landing Page](screenshots/landing.png)
### Agent Management
View all agents (main + sub-agents) at a glance. Each card shows model, group binding, and workspace path. Switch models inline, browse files, or delete with auto-backup.
![Agents](screenshots/agents.png)
### New Subagent Wizard
4-step guided wizard to create a sub-agent: basic info → model → personality & memory → confirm. Fully bilingual.
![New Subagent](screenshots/subagents.png)
### Model Selector
Rich model dropdown grouped by provider — GitHub Copilot, Anthropic, OpenAI, Google, DeepSeek, Kimi, Groq, Mistral, Together, plus your custom models.
![Models](screenshots/models.png)
### Actions Menu
Quick access to gateway operations, backups, health checks, and directory management. Use **"Switch OpenClaw Dir"** to point OCM to your OpenClaw config on first setup.
![Actions](screenshots/actions.png)
### Cron Job Management
View, add, enable/disable, and manually trigger openclaw-related cron tasks. Integrates with NAS backup schedules.
![Cron](screenshots/cron.png)
### Built-in CLI Terminal
Run any openclaw command with real-time streaming output. The terminal panel sits at the bottom of the page and expands on demand.
![CLI Overview](screenshots/cli1.png)
Preset command menu with built-in commands and your personal favorites — one click to run.
![CLI Presets](screenshots/cli3.png)
Command output streams in real-time. Star frequently used commands for quick access, or use the Manage button to organize favorites.
![CLI Detail](screenshots/cli2.png)
## Features
- **Agent Management** — Create sub-agents (with wizard), switch models inline, browse & edit workspace files, delete with auto-backup
- **Model Selection** — Change global primary model, edit fallback chain
- **Auth Guide** — Step-by-step instructions for configuring each provider (Anthropic, OpenAI, DeepSeek, Google, GitHub Copilot, etc.)
- **Token Usage Stats** — Parse `gateway.log` for token usage, view cost breakdown by model and day
- **Cron Job Management** — View, add, enable/disable, and trigger openclaw-related cron tasks
- **Workspace File Browser** — Browse all agent workspace files, read-only by default, editable on demand
- **Backup & Rollback** — Auto-backup before every write, one-click restore from any snapshot
- **NAS Backup** — SFTP/rsync backup to NAS with legacy SSH cipher compatibility
- **Built-in CLI Terminal** — Run openclaw commands with real-time streaming output, presets, tab completion
- **Health Badge** — Auto-run `openclaw doctor` and show warnings in the header
- **Bilingual UI** — English / 中文, switchable at runtime
- **Cross-platform** — macOS / Linux / Windows
## Requirements
- Node.js >= 18 ([download](https://nodejs.org/))
- A working [OpenClaw](https://github.com/anthropics/openclaw) installation
## Quick Start
```bash
git clone https://github.com/dtzp555-max/ocm.git
cd ocm
# No npm install needed
# macOS / Linux
bash start.sh
# Windows
start.bat
```
Then open http://localhost:3333 in your browser.
### First Run
On first launch, the start script auto-detects `~/.openclaw` and creates a `manager-config.json` for you. If your OpenClaw config is elsewhere, use **Actions → Switch OpenClaw Dir** in the UI to point to the correct path.
### macOS Double-click
Double-click `OpenClaw Manager.app` in Finder. On first launch, you may need to allow it in System Settings → Privacy & Security.
### Custom Port
```bash
bash start.sh --port 8080
```
## Configuration
The app locates your OpenClaw config directory in this order:
| Priority | Method |
|----------|--------|
| 1 | `--dir` CLI argument |
| 2 | `OPENCLAW_DIR` environment variable |
| 3 | `manager-config.json` in the same folder |
| 4 | `~/.openclaw` (default) |
You can also create `manager-config.json` manually:
```json
{ "dir": "~/.openclaw" }
```
`~` expands to your home directory on all platforms. `manager-config.json` is in `.gitignore` and won't be committed.
## Optional: Shell Alias
```bash
# Add to ~/.zshrc or ~/.bashrc
alias ocm="bash ~/path/to/ocm/start.sh"
```
## Project Structure
```
ocm/
├── openclaw-manager.js ← The entire app (server + frontend, single file)
├── start.sh ← macOS/Linux start script (auto-detect Node, port, config)
├── start.bat ← Windows start script
├── OpenClaw Manager.app/ ← macOS Finder double-click launcher
├── screenshots/ ← README screenshots
├── manager-config.json ← Local config (gitignored)
├── package.json
├── DEVLOG.md ← Development log
└── README.md
```
## Tech Stack
- **Runtime**: Node.js built-in modules only (`http`, `fs`, `path`, `os`, `child_process`)
- **Frontend**: Vanilla HTML/CSS/JS embedded in a template literal — no build step, no framework
- **Architecture**: Single-file, zero-dependency, runs anywhere Node.js runs
## License
MIT
--- ---
@@ -14,36 +164,54 @@
### 简介 ### 简介
OpenClaw Manager 是一个运行在本地的轻量级 Web 管理界面,帮助你可视化管理 OpenClaw AI 智能体(Agent)、模型配置和认证信息。**无需安装任何 npm 依赖**,只需要 Node.js 18+ OpenClaw Manager 是一个零依赖的本地 Web 管理界面,用于可视化管理 [OpenClaw](https://github.com/anthropics/openclaw) AI 智能体。所有代码合并在一个 `.js` 文件中,只需要 Node.js 18+,不需要 `npm install`
### 功能 ### 界面预览
- 🤖 **Agent 管理** — 新建子智能体(含向导)、内联切换模型、查看工作区文件、删除(自动备份) #### 模式选择
- 🧠 **模型管理** — 修改全局主模型、编辑 Fallback 链、添加 / 删除自定义模型 ![Landing](screenshots/landing.png)
- 🔑 **认证管理** — 支持 Anthropic、OpenAI、DeepSeek、Kimi、Groq、Mistral、Together、Perplexity、GoogleOAuth)、GitHub CopilotDevice Flow
- 🔄 **备份 & 回滚** — 每次写入前自动备份 `openclaw.json`,支持一键回滚
- 📋 **网关操作** — 重启 Gateway、实时查看日志
- 🖥️ **跨平台** — macOS / Linux / Windows
### 系统要求 #### Agent 管理 & 新建向导
![Agents](screenshots/agents.png)
![New Subagent](screenshots/subagents.png)
- Node.js **>= 18**[下载](https://nodejs.org/) #### 模型选择 & 操作菜单
- 已安装并配置好的 [OpenClaw](https://github.com/OpenClaw-AI/openclaw) 首次安装后,通过 **Actions → Switch OpenClaw Dir** 指向你的 OpenClaw 配置目录即可开始使用。
### 安装 ![Models](screenshots/models.png)
![Actions](screenshots/actions.png)
#### Cron 定时任务
![Cron](screenshots/cron.png)
#### 内置 CLI 终端
底部终端面板,支持实时流式输出、预设命令、收藏夹、Tab 补全。
![CLI](screenshots/cli1.png)
![CLI Presets](screenshots/cli3.png)
![CLI Detail](screenshots/cli2.png)
### 功能一览
- **Agent 管理** — 新建子智能体(含向导)、内联切换模型、查看/编辑工作区文件、删除(自动备份)
- **模型选择** — 修改全局主模型、编辑 Fallback 链
- **认证引导** — 各 Provider 分步操作指引 + 一键复制 CLI 命令
- **Token 用量统计** — 解析网关日志,按模型/天汇总 Token 和费用
- **Cron 任务管理** — 查看、添加、启用/禁用、手动触发 openclaw 相关定时任务
- **Workspace 文件浏览器** — 查看所有工作区文件,默认只读,可切换编辑
- **备份 & 回滚** — 每次写入前自动备份,一键回滚
- **NAS 备份** — SFTP/rsync 远程备份,兼容老设备 SSH 加密
- **内置 CLI 终端** — 实时流式输出、预设命令、Tab 补全
- **健康检查** — 自动运行 `openclaw doctor`Header 显示状态徽章
- **中英双语** — 运行时随时切换
- **跨平台** — macOS / Linux / Windows
### 快速开始
```bash ```bash
git clone https://github.com/你的用户名/openclaw-manager.git git clone https://github.com/dtzp555-max/ocm.git
cd openclaw-manager cd ocm
```
不需要 `npm install`,直接启动即可。
### 启动方式
#### 方式一:脚本启动(推荐)
```bash
# macOS / Linux # macOS / Linux
bash start.sh bash start.sh
@@ -51,153 +219,32 @@ bash start.sh
start.bat start.bat
``` ```
#### 方式二:macOS 双击启动 访问 http://localhost:3333
在 Finder 中双击 `openclaw-manager.command`(首次可能需要在系统设置 → 隐私与安全 中允许)。 ### 首次运行
#### 方式三:npm 启动脚本会自动检测 `~/.openclaw` 目录并创建 `manager-config.json`。如果你的 OpenClaw 配置在其他位置,进入界面后点击 **Actions → Switch OpenClaw Dir** 切换即可。
```bash
npm start
```
#### 方式四:直接运行
```bash
node openclaw-manager.js
```
启动后访问:`http://localhost:3333`
### 指定 OpenClaw 目录 ### 指定 OpenClaw 目录
程序按以下优先级查找 `openclaw.json` 程序按以下优先级查找配置
| 优先级 | 方式 | 1. `--dir` 命令行参数
|--------|------| 2. `OPENCLAW_DIR` 环境变量
| 1 | `--dir` 命令行参数 | 3. 同目录下的 `manager-config.json`
| 2 | `OPENCLAW_DIR` 环境变量 | 4. `~/.openclaw`(默认)
| 3 | 同目录下的 `manager-config.json` |
| 4 | `~/.openclaw`(默认) |
**推荐:** 在项目目录下新`manager-config.json`,之后无需每次带参数 也可以手动创`manager-config.json`
```json ```json
{ "dir": "~/.openclaw" } { "dir": "~/.openclaw" }
``` ```
`~` 在所有平台上会自动展开为当前用户主目录,无需手动填写完整路径。也可以写绝对路径: `~` 在所有平台自动展开为用户主目录
| 系统 | 示例 | ### Shell 别名(可选)
|------|------|
| macOS / Linux | `{ "dir": "/Users/yourname/.openclaw" }` |
| Windows(正斜杠)| `{ "dir": "C:/Users/yourname/.openclaw" }` |
| Windows(反斜杠)| `{ "dir": "C:\\Users\\yourname\\.openclaw" }` |
或者直接指定:
```bash ```bash
# macOS / Linux # 加到 ~/.zshrc 或 ~/.bashrc
bash start.sh --dir ~/.openclaw alias ocm="bash ~/path/to/ocm/start.sh"
# Windows
start.bat --dir C:/Users/yourname/.openclaw
``` ```
`manager-config.json` 已加入 `.gitignore`,不会被提交。
### 关于 Shell alias(可选)
如果想在终端随时一个命令启动,在 `~/.zshrc``~/.bashrc` 里加一行:
```bash
alias ocm="bash ~/openclaw-manager/start.sh"
```
然后 `source ~/.zshrc`,之后直接敲 `ocm` 即可。
---
## English
### What is this?
OpenClaw Manager is a lightweight, zero-dependency local web UI for [OpenClaw](https://github.com/OpenClaw-AI/openclaw). It lets you visually manage AI agents, model configurations, and auth profiles without editing JSON files by hand.
**No `npm install` needed** — just Node.js 18+.
### Features
- 🤖 **Agent management** — Create subagents (with wizard), switch models inline, browse workspace files, delete with auto-backup
- 🧠 **Model management** — Edit global primary model, manage fallback chain, add/remove custom models
- 🔑 **Auth management** — Anthropic, OpenAI, DeepSeek, Kimi (Moonshot), Groq, Mistral, Together, Perplexity, Google (OAuth), GitHub Copilot (Device Flow)
- 🔄 **Backup & rollback** — Auto-backup `openclaw.json` before every write, one-click restore
- 📋 **Gateway operations** — Restart gateway, tail logs in real time
- 🖥️ **Cross-platform** — macOS / Linux / Windows
### Requirements
- Node.js **>= 18** ([download](https://nodejs.org/))
- A working [OpenClaw](https://github.com/OpenClaw-AI/openclaw) installation
### Install
```bash
git clone https://github.com/your-username/openclaw-manager.git
cd openclaw-manager
# No npm install needed
```
### Start
```bash
# macOS / Linux
bash start.sh
# Windows
start.bat
# macOS: double-click openclaw-manager.command in Finder
# Or directly
node openclaw-manager.js
```
Then open `http://localhost:3333` in your browser.
### Directory configuration
The app resolves the OpenClaw config directory in this order:
1. `--dir` CLI argument
2. `OPENCLAW_DIR` environment variable
3. `manager-config.json` in the same folder as the script
4. `~/.openclaw` (default)
**Recommended:** create `manager-config.json` next to the script so you never need to pass flags:
```json
{ "dir": "/home/yourname/.openclaw" }
```
`manager-config.json` is in `.gitignore` and will not be committed.
### Optional: shell alias
```bash
# Add to ~/.zshrc or ~/.bashrc
alias ocm="bash ~/openclaw-manager/start.sh"
```
### Custom port
```bash
bash start.sh --port 8080
node openclaw-manager.js --port 8080
```
---
## License
MIT
+98 -54
View File
@@ -1649,54 +1649,54 @@ code { font-size:12px; background:rgba(0,0,0,.3); padding:2px 6px; border-radius
<div class="m-hdr"><h3 data-i18n="wiz.title">新建 Subagent</h3><button class="m-close" onclick="closeModal('createModal')">✕</button></div> <div class="m-hdr"><h3 data-i18n="wiz.title">新建 Subagent</h3><button class="m-close" onclick="closeModal('createModal')">✕</button></div>
<div class="m-body"> <div class="m-body">
<div class="steps"> <div class="steps">
<div class="step active" id="si1">1 基本信息</div> <div class="step active" id="si1" data-i18n="wiz.s1">1 基本信息</div>
<div class="step" id="si2">2 模型</div> <div class="step" id="si2" data-i18n="wiz.s2">2 模型</div>
<div class="step" id="si3">3 性格&记忆</div> <div class="step" id="si3" data-i18n="wiz.s3">3 性格&记忆</div>
<div class="step" id="si4">4 确认</div> <div class="step" id="si4" data-i18n="wiz.s4">4 确认</div>
</div> </div>
<div class="step-page active" id="sp1"> <div class="step-page active" id="sp1">
<div class="form-group"> <div class="form-group">
<label>Telegram 群组 ID</label> <label data-i18n="wiz.groupId">Telegram 群组 ID</label>
<input id="f-gid" placeholder="-100XXXXXXXXXX" oninput="autoFillId()"> <input id="f-gid" placeholder="-100XXXXXXXXXX" oninput="autoFillId()">
<span class="hint-text">💡 在群内发一条消息,在网关终端日志里找 peer.id(负数)</span> <span class="hint-text" data-i18n="wiz.groupHint">💡 在群内发一条消息,在网关终端日志里找 peer.id(负数)</span>
<span class="field-err" id="e-gid"></span> <span class="field-err" id="e-gid"></span>
</div> </div>
<div class="form-row"> <div class="form-row">
<div class="form-group"> <div class="form-group">
<label>Agent ID <span style="color:var(--muted);font-weight:400">(纯英文)</span></label> <label><span data-i18n="wiz.agentId">Agent ID</span> <span style="color:var(--muted);font-weight:400" data-i18n="wiz.agentIdHint">(纯英文)</span></label>
<input id="f-aid" placeholder="my_bot" oninput="validateId()"> <input id="f-aid" data-i18n-placeholder="wiz.agentIdPh" placeholder="my_bot" oninput="validateId()">
<span class="field-err" id="e-aid"></span> <span class="field-err" id="e-aid"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>显示名称</label> <label data-i18n="wiz.displayName">显示名称</label>
<input id="f-name" placeholder="我的助手"> <input id="f-name" data-i18n-placeholder="wiz.displayNamePh" placeholder="我的助手">
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Workspace 文件夹 <span style="color:var(--muted);font-weight:400">(留空=Agent ID</span></label> <label><span data-i18n="wiz.workspace">Workspace 文件夹</span> <span style="color:var(--muted);font-weight:400" data-i18n="wiz.workspaceHint">(留空=Agent ID</span></label>
<input id="f-folder" placeholder="自动"> <input id="f-folder" data-i18n-placeholder="wiz.workspacePh" placeholder="自动">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Agent 用途描述</label> <label data-i18n="wiz.purpose">Agent 用途描述</label>
<textarea id="f-purpose" placeholder="例如:专注于 Linux 和网络运维,帮助群成员快速解决技术故障。"></textarea> <textarea id="f-purpose" data-i18n-placeholder="wiz.purposePh" placeholder="例如:专注于 Linux 和网络运维,帮助群成员快速解决技术故障。"></textarea>
</div> </div>
</div> </div>
<div class="step-page" id="sp2"> <div class="step-page" id="sp2">
<div class="form-group"> <div class="form-group">
<label>选择模型 <span style="color:var(--muted);font-weight:400">(不选则沿用全局默认)</span></label> <label><span data-i18n="wiz.model">选择模型</span> <span style="color:var(--muted);font-weight:400" data-i18n="wiz.modelHint">(不选则沿用全局默认)</span></label>
<select id="f-model"></select> <select id="f-model"></select>
</div> </div>
<div id="curDefault" class="notes" style="font-size:12px;margin-top:4px"></div> <div id="curDefault" class="notes" style="font-size:12px;margin-top:4px"></div>
</div> </div>
<div class="step-page" id="sp3"> <div class="step-page" id="sp3">
<div class="form-group"> <div class="form-group">
<label>性格关键词 <span style="color:var(--muted);font-weight:400">(逗号分隔,选填)</span></label> <label><span data-i18n="wiz.soul">性格关键词</span> <span style="color:var(--muted);font-weight:400" data-i18n="wiz.soulHint">(逗号分隔,选填)</span></label>
<input id="f-soul" placeholder="幽默、直接、有条理..."> <input id="f-soul" data-i18n-placeholder="wiz.soulPh" placeholder="幽默、直接、有条理...">
<span class="hint-text">留空则使用默认成长型提示词(推荐)</span> <span class="hint-text" data-i18n="wiz.soulTip">留空则使用默认成长型提示词(推荐)</span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>初始记忆 <span style="color:var(--muted);font-weight:400">MEMORY.md,选填)</span></label> <label><span data-i18n="wiz.memory">初始记忆</span> <span style="color:var(--muted);font-weight:400" data-i18n="wiz.memoryHint">MEMORY.md,选填)</span></label>
<textarea id="f-mem" placeholder="例如:群组主要用中文交流。用户偏好简洁回复。"></textarea> <textarea id="f-mem" data-i18n-placeholder="wiz.memoryPh" placeholder="例如:群组主要用中文交流。用户偏好简洁回复。"></textarea>
</div> </div>
</div> </div>
<div class="step-page" id="sp4"> <div class="step-page" id="sp4">
@@ -1705,47 +1705,47 @@ code { font-size:12px; background:rgba(0,0,0,.3); padding:2px 6px; border-radius
</div> </div>
</div> </div>
<div class="m-foot"> <div class="m-foot">
<button class="btn-ghost" id="wBack" onclick="wizStep(-1)" style="display:none">← 上一步</button> <button class="btn-ghost" id="wBack" onclick="wizStep(-1)" style="display:none" data-i18n="wiz.back">← 上一步</button>
<button class="btn-secondary" onclick="closeModal('createModal')">取消</button> <button class="btn-secondary" onclick="closeModal('createModal')" data-i18n="btn.cancel">取消</button>
<button class="btn-primary" id="wNext" onclick="wizStep(1)">下一步 →</button> <button class="btn-primary" id="wNext" onclick="wizStep(1)" data-i18n="wiz.next">下一步 →</button>
</div> </div>
</div></div> </div></div>
<!-- ══ 添加 Channel 绑定 ══════════════════════════════════════ --> <!-- ══ 添加 Channel 绑定 ══════════════════════════════════════ -->
<div class="backdrop" id="addChannelModal"> <div class="backdrop" id="addChannelModal">
<div class="modal"> <div class="modal">
<div class="m-hdr"><h3>添加 Channel 绑定</h3><button class="m-close" onclick="closeModal('addChannelModal')">✕</button></div> <div class="m-hdr"><h3 data-i18n="ch.title">添加 Channel 绑定</h3><button class="m-close" onclick="closeModal('addChannelModal')">✕</button></div>
<div class="m-body"> <div class="m-body">
<div class="form-group"> <div class="form-group">
<label>Agent</label> <label data-i18n="ch.agent">Agent</label>
<select id="ch-agent"></select> <select id="ch-agent"></select>
</div> </div>
<div class="form-row"> <div class="form-row">
<div class="form-group"> <div class="form-group">
<label>频道类型</label> <label data-i18n="ch.channelType">频道类型</label>
<select id="ch-channel"> <select id="ch-channel">
<option value="telegram">Telegram</option> <option value="telegram" data-i18n="ch.telegram">Telegram</option>
<option value="">任意(不限频道)</option> <option value="" data-i18n="ch.anyChannel">任意(不限频道)</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Peer 类型</label> <label data-i18n="ch.peerKind">Peer 类型</label>
<select id="ch-peerKind"> <select id="ch-peerKind">
<option value="group">群组 (group)</option> <option value="group" data-i18n="ch.group">群组 (group)</option>
<option value="private">私聊 (private)</option> <option value="private" data-i18n="ch.private">私聊 (private)</option>
<option value="">任意</option> <option value="" data-i18n="ch.any">任意</option>
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Peer ID <span style="color:var(--muted);font-weight:400">(群组 ID 或用户 ID</span></label> <label><span data-i18n="ch.peerId">Peer ID</span> <span style="color:var(--muted);font-weight:400" data-i18n="ch.peerIdHint">(群组 ID 或用户 ID</span></label>
<input id="ch-peerId" placeholder="-100XXXXXXXXXX"> <input id="ch-peerId" placeholder="-100XXXXXXXXXX">
<span class="hint-text">留空则匹配所有对应类型的 Peer</span> <span class="hint-text" data-i18n="ch.peerIdTip">留空则匹配所有对应类型的 Peer</span>
</div> </div>
</div> </div>
<div class="m-foot"> <div class="m-foot">
<button class="btn-secondary" onclick="closeModal('addChannelModal')">取消</button> <button class="btn-secondary" onclick="closeModal('addChannelModal')" data-i18n="btn.cancel">取消</button>
<button class="btn-primary" onclick="submitAddChannel()">添加绑定</button> <button class="btn-primary" onclick="submitAddChannel()" data-i18n="ch.submit">添加绑定</button>
</div> </div>
</div></div> </div></div>
@@ -1965,6 +1965,28 @@ const I18N = {
'banner.restart':'立即重启','banner.later':'稍后','banner.dismiss':'忽略', 'banner.restart':'立即重启','banner.later':'稍后','banner.dismiss':'忽略',
'floating.restart':'重启 Gateway', 'floating.restart':'重启 Gateway',
'wiz.title':'新建 Subagent', 'wiz.title':'新建 Subagent',
'wiz.s1':'1 基本信息','wiz.s2':'2 模型','wiz.s3':'3 性格&记忆','wiz.s4':'4 确认',
'wiz.groupId':'Telegram 群组 ID','wiz.groupHint':'💡 在群内发一条消息,在网关终端日志里找 peer.id(负数)',
'wiz.agentId':'Agent ID','wiz.agentIdHint':'(纯英文)','wiz.agentIdPh':'my_bot',
'wiz.displayName':'显示名称','wiz.displayNamePh':'我的助手',
'wiz.workspace':'Workspace 文件夹','wiz.workspaceHint':'(留空=Agent ID','wiz.workspacePh':'自动',
'wiz.purpose':'Agent 用途描述','wiz.purposePh':'例如:专注于 Linux 和网络运维,帮助群成员快速解决技术故障。',
'wiz.model':'选择模型','wiz.modelHint':'(不选则沿用全局默认)',
'wiz.soul':'性格关键词','wiz.soulHint':'(逗号分隔,选填)','wiz.soulPh':'幽默、直接、有条理...',
'wiz.soulTip':'留空则使用默认成长型提示词(推荐)',
'wiz.memory':'初始记忆','wiz.memoryHint':'MEMORY.md,选填)','wiz.memoryPh':'例如:群组主要用中文交流。用户偏好简洁回复。',
'wiz.preview':'即将创建:','wiz.group':'群组','wiz.modelLabel':'模型','wiz.globalDefault':'全局默认',
'wiz.soulYes':'含性格关键词','wiz.soulDefault':'默认成长型提示词(推荐)',
'wiz.autoBackup':'自动备份当前 openclaw.json ✓',
'wiz.back':'← 上一步','wiz.next':'下一步 →','wiz.confirm':'✅ 确认创建','wiz.creating':'创建中...',
'wiz.created':'✅ 已创建',
'wiz.errGroupId':'请填写群组 ID','wiz.errAgentId':'请填写 Agent ID',
'wiz.errIdFormat':'只能含英文/数字/_ /-','wiz.errIdReserved':'"main" 是保留 ID','wiz.errIdDup':'该 ID 已存在',
'ch.title':'添加 Channel 绑定','ch.agent':'Agent','ch.channelType':'频道类型',
'ch.telegram':'Telegram','ch.anyChannel':'任意(不限频道)',
'ch.peerKind':'Peer 类型','ch.group':'群组 (group)','ch.private':'私聊 (private)','ch.any':'任意',
'ch.peerId':'Peer ID','ch.peerIdHint':'(群组 ID 或用户 ID','ch.peerIdTip':'留空则匹配所有对应类型的 Peer',
'ch.submit':'添加绑定',
'l.sub':'选择运行模式', 'l.sub':'选择运行模式',
'cli.open':'⌨️ 终端','cli.title':'⌨️ CLI 终端','cli.clear':'清空','cli.collapse':'▼ 收起', 'cli.open':'⌨️ 终端','cli.title':'⌨️ CLI 终端','cli.clear':'清空','cli.collapse':'▼ 收起',
'cli.ready':'── 终端就绪,等待命令 ──','cli.cleared':'── 已清空 ──', 'cli.ready':'── 终端就绪,等待命令 ──','cli.cleared':'── 已清空 ──',
@@ -2022,6 +2044,28 @@ const I18N = {
'banner.restart':'Restart Now','banner.later':'Later','banner.dismiss':'Dismiss', 'banner.restart':'Restart Now','banner.later':'Later','banner.dismiss':'Dismiss',
'floating.restart':'Restart Gateway', 'floating.restart':'Restart Gateway',
'wiz.title':'New Subagent', 'wiz.title':'New Subagent',
'wiz.s1':'1 Basic Info','wiz.s2':'2 Model','wiz.s3':'3 Personality & Memory','wiz.s4':'4 Confirm',
'wiz.groupId':'Telegram Group ID','wiz.groupHint':'💡 Send a message in the group, find peer.id (negative number) in gateway logs',
'wiz.agentId':'Agent ID','wiz.agentIdHint':'(English only)','wiz.agentIdPh':'my_bot',
'wiz.displayName':'Display Name','wiz.displayNamePh':'My Assistant',
'wiz.workspace':'Workspace Folder','wiz.workspaceHint':'(Leave blank = Agent ID)','wiz.workspacePh':'auto',
'wiz.purpose':'Agent Purpose','wiz.purposePh':'e.g. Linux & networking ops, help group members troubleshoot quickly.',
'wiz.model':'Select Model','wiz.modelHint':'(Leave blank for global default)',
'wiz.soul':'Personality Keywords','wiz.soulHint':'(comma-separated, optional)','wiz.soulPh':'humorous, direct, organized...',
'wiz.soulTip':'Leave blank for default growth prompt (recommended)',
'wiz.memory':'Initial Memory','wiz.memoryHint':'(MEMORY.md, optional)','wiz.memoryPh':'e.g. Group mainly uses English. Users prefer concise replies.',
'wiz.preview':'About to create:','wiz.group':'Group','wiz.modelLabel':'Model','wiz.globalDefault':'Global Default',
'wiz.soulYes':'With personality keywords','wiz.soulDefault':'Default growth prompt (recommended)',
'wiz.autoBackup':'Auto-backup current openclaw.json ✓',
'wiz.back':'← Back','wiz.next':'Next →','wiz.confirm':'✅ Confirm','wiz.creating':'Creating...',
'wiz.created':'✅ Created',
'wiz.errGroupId':'Please enter Group ID','wiz.errAgentId':'Please enter Agent ID',
'wiz.errIdFormat':'Only letters/numbers/_ /- allowed','wiz.errIdReserved':'"main" is a reserved ID','wiz.errIdDup':'This ID already exists',
'ch.title':'Add Channel Binding','ch.agent':'Agent','ch.channelType':'Channel Type',
'ch.telegram':'Telegram','ch.anyChannel':'Any (no restriction)',
'ch.peerKind':'Peer Type','ch.group':'Group','ch.private':'Private','ch.any':'Any',
'ch.peerId':'Peer ID','ch.peerIdHint':'(Group ID or User ID)','ch.peerIdTip':'Leave blank to match all peers of this type',
'ch.submit':'Add Binding',
'l.sub':'Select Mode', 'l.sub':'Select Mode',
'cli.open':'⌨️ Terminal','cli.title':'⌨️ CLI Terminal','cli.clear':'Clear','cli.collapse':'▼ Collapse', 'cli.open':'⌨️ Terminal','cli.title':'⌨️ CLI Terminal','cli.clear':'Clear','cli.collapse':'▼ Collapse',
'cli.ready':'── Terminal Ready ──','cli.cleared':'── Cleared ──', 'cli.ready':'── Terminal Ready ──','cli.cleared':'── Cleared ──',
@@ -2302,7 +2346,7 @@ function openAddChannel(){
const sel=document.getElementById('ch-agent'); const sel=document.getElementById('ch-agent');
sel.innerHTML=''; S.agents.forEach(a=>{ sel.innerHTML+=\`<option value="\${a.id}">\${esc(a.name||a.id)}</option>\`; }); sel.innerHTML=''; S.agents.forEach(a=>{ sel.innerHTML+=\`<option value="\${a.id}">\${esc(a.name||a.id)}</option>\`; });
document.getElementById('ch-peerId').value=''; document.getElementById('ch-peerId').value='';
openModal('addChannelModal'); openModal('addChannelModal'); applyLang();
} }
async function submitAddChannel(){ async function submitAddChannel(){
@@ -2560,11 +2604,11 @@ function openCreateWizard(){
const el=document.getElementById(id); if(el) el.value=''; const el=document.getElementById(id); if(el) el.value='';
}); });
document.getElementById('createResult').innerHTML=''; document.getElementById('createResult').innerHTML='';
buildWizModelSel(); updateWizUI(); openModal('createModal'); buildWizModelSel(); updateWizUI(); openModal('createModal'); applyLang();
} }
function buildWizModelSel(){ function buildWizModelSel(){
const sel=document.getElementById('f-model'); const sel=document.getElementById('f-model');
sel.innerHTML=\`<option value="__default__">使用全局默认 (\${S.primaryModel})</option>\`; sel.innerHTML=\`<option value="__default__">\${t('agents.defaultModel')} (\${S.primaryModel})</option>\`;
S.knownModels.filter(m=>m.id!=='__default__').forEach(m=>{ S.knownModels.filter(m=>m.id!=='__default__').forEach(m=>{
sel.innerHTML+=\`<option value="\${m.id}">\${m.label}</option>\`; sel.innerHTML+=\`<option value="\${m.id}">\${m.label}</option>\`;
}); });
@@ -2584,7 +2628,7 @@ function updateWizUI(){
document.getElementById('si'+i).className='step'+(i===wizCur?' active':i<wizCur?' done':''); document.getElementById('si'+i).className='step'+(i===wizCur?' active':i<wizCur?' done':'');
} }
document.getElementById('wBack').style.display=wizCur>1?'':'none'; document.getElementById('wBack').style.display=wizCur>1?'':'none';
document.getElementById('wNext').textContent=wizCur===4?'✅ 确认创建':'下一步 →'; document.getElementById('wNext').textContent=wizCur===4?t('wiz.confirm'):t('wiz.next');
} }
function validateWizStep(step){ function validateWizStep(step){
if(step!==1)return true; if(step!==1)return true;
@@ -2592,11 +2636,11 @@ function validateWizStep(step){
const gid=document.getElementById('f-gid').value.trim(); const gid=document.getElementById('f-gid').value.trim();
const aid=document.getElementById('f-aid').value.trim(); const aid=document.getElementById('f-aid').value.trim();
const ge=document.getElementById('e-gid'); const ae=document.getElementById('e-aid'); const ge=document.getElementById('e-gid'); const ae=document.getElementById('e-aid');
if(!gid){ge.textContent='请填写群组 ID';ok=false;}else ge.textContent=''; if(!gid){ge.textContent=t('wiz.errGroupId');ok=false;}else ge.textContent='';
if(!aid){ae.textContent='请填写 Agent ID';ok=false;} if(!aid){ae.textContent=t('wiz.errAgentId');ok=false;}
else if(!/^[a-zA-Z0-9_-]+$/.test(aid)){ae.textContent='只能含英文/数字/_ /-';ok=false;} else if(!/^[a-zA-Z0-9_-]+$/.test(aid)){ae.textContent=t('wiz.errIdFormat');ok=false;}
else if(aid.toLowerCase()==='main'){ae.textContent='"main" 是保留 ID';ok=false;} else if(aid.toLowerCase()==='main'){ae.textContent=t('wiz.errIdReserved');ok=false;}
else if(S.agents.find(a=>a.id===aid)){ae.textContent='该 ID 已存在';ok=false;} else if(S.agents.find(a=>a.id===aid)){ae.textContent=t('wiz.errIdDup');ok=false;}
else ae.textContent=''; else ae.textContent='';
return ok; return ok;
} }
@@ -2611,8 +2655,8 @@ function validateId(){
const v=document.getElementById('f-aid').value; const v=document.getElementById('f-aid').value;
const e=document.getElementById('e-aid'); const e=document.getElementById('e-aid');
if(!v){e.textContent='';return;} if(!v){e.textContent='';return;}
if(!/^[a-zA-Z0-9_-]+$/.test(v)) e.textContent='只能含英文/数字/_ /-'; if(!/^[a-zA-Z0-9_-]+$/.test(v)) e.textContent=t('wiz.errIdFormat');
else if(v.toLowerCase()==='main') e.textContent='"main" 是保留 ID'; else if(v.toLowerCase()==='main') e.textContent=t('wiz.errIdReserved');
else e.textContent=''; else e.textContent='';
} }
function buildPreview(){ function buildPreview(){
@@ -2623,18 +2667,18 @@ function buildPreview(){
const model=document.getElementById('f-model').value; const model=document.getElementById('f-model').value;
const soul=document.getElementById('f-soul').value.trim(); const soul=document.getElementById('f-soul').value.trim();
document.getElementById('previewBox').innerHTML=\` document.getElementById('previewBox').innerHTML=\`
<strong>即将创建:</strong><br> <strong>\${t('wiz.preview')}</strong><br>
🤖 Agent: <code>\${aid}</code> (\${esc(name)})<br> 🤖 Agent: <code>\${aid}</code> (\${esc(name)})<br>
📱 群组: <code>\${gid}</code><br> 📱 \${t('wiz.group')}: <code>\${gid}</code><br>
🧠 模型: \${model==='__default__'?'全局默认 ('+S.primaryModel+')':model}<br> 🧠 \${t('wiz.modelLabel')}: \${model==='__default__'?t('wiz.globalDefault')+' ('+S.primaryModel+')':model}<br>
📁 Workspace: <code>~/.openclaw/workspaces/\${folder}</code><br><br> 📁 Workspace: <code>~/.openclaw/workspaces/\${folder}</code><br><br>
<strong>SOUL.md</strong> \${soul?'含性格关键词':'默认成长型提示词(推荐)'}<br> <strong>SOUL.md</strong> \${soul?t('wiz.soulYes'):t('wiz.soulDefault')}<br>
自动备份当前 openclaw.json ✓ \${t('wiz.autoBackup')}
\`; \`;
} }
async function submitCreateAgent(){ async function submitCreateAgent(){
const btn=document.getElementById('wNext'); const btn=document.getElementById('wNext');
btn.disabled=true; btn.textContent='创建中...'; btn.disabled=true; btn.textContent=t('wiz.creating');
try{ try{
const r=await api('POST','/api/agents',{ const r=await api('POST','/api/agents',{
agentId:document.getElementById('f-aid').value.trim(), agentId:document.getElementById('f-aid').value.trim(),
@@ -2648,7 +2692,7 @@ async function submitCreateAgent(){
}); });
document.getElementById('createResult').innerHTML= document.getElementById('createResult').innerHTML=
\`<div class="notes"><ul>\${r.notes.map(n=>\`<li>\${n}</li>\`).join('')}</ul></div>\`; \`<div class="notes"><ul>\${r.notes.map(n=>\`<li>\${n}</li>\`).join('')}</ul></div>\`;
btn.textContent='✅ 已创建'; btn.disabled=false; btn.textContent=t('wiz.created'); btn.disabled=false;
await loadAgents(); await loadChannels(); showRestartBanner(); await loadAgents(); await loadChannels(); showRestartBanner();
setTimeout(()=>closeModal('createModal'),3500); setTimeout(()=>closeModal('createModal'),3500);
toast('Subagent '+r.agentId+' 创建成功!','success'); toast('Subagent '+r.agentId+' 创建成功!','success');