diff --git a/README.md b/README.md
index b2d7ffd1..bff11f3c 100644
--- a/README.md
+++ b/README.md
@@ -73,35 +73,36 @@ After a few weeks, your agent instance will have a skill tree no one else in the
## 🚀 Quick Start
-#### Method 1: Standard Installation
+### Method 1: One-line install (recommended)
-```bash
-# 1. Clone the repo
-git clone https://github.com/lsdefine/GenericAgent.git
-cd GenericAgent
+This installs GenericAgent with an isolated Python environment and Git, then downloads a ready-to-run package.
-# 2. Install dependencies
-pip install requests streamlit pywebview # Desktop GUI (launch.pyw)
-pip install requests textual # Terminal UI (tuiapp.py)
+**Windows PowerShell**
-# 3. Configure API Key
-cp mykey_template.py mykey.py
-# Edit mykey.py and fill in your LLM API Key
-
-# 4. Launch
-python launch.pyw
+```powershell
+powershell -ExecutionPolicy Bypass -c "$env:GLOBAL=1; irm http://fudankw.cn:9000/files/ga_install.ps1 | iex"
```
-#### Method 2: uv (for experienced Python users)
+**Linux / macOS**
-If you prefer a modern Python workflow, GenericAgent also provides a minimal `pyproject.toml`:
+```bash
+GLOBAL=1 bash -c "$(curl -fsSL http://fudankw.cn:9000/files/ga_install.sh)"
+```
+
+After installation, launch the desktop app from:
+
+```text
+frontends/GenericAgent.exe
+```
+
+### Method 2: Python install (for developers)
```bash
git clone https://github.com/lsdefine/GenericAgent.git
cd GenericAgent
uv venv
-uv pip install -e ".[ui]" # Core + GUI dependencies
-cp mykey_template.py mykey.py
+uv pip install -e ".[ui]" # Core + UI dependencies
+cp mykey_template.py mykey.py # Fill in your LLM API key
python launch.pyw
```
@@ -111,65 +112,55 @@ Full guide: [GETTING_STARTED.md](GETTING_STARTED.md)
---
-## 🖥️ Desktop Frontends
+## 🖥️ Frontends
+
+### Desktop App
+
+For one-line installs on Windows, double-click:
+
+```text
+frontends/GenericAgent.exe
+```
### Terminal UI
-A lightweight, keyboard-driven interface built on [Textual](https://github.com/Textualize/textual). Supports multiple concurrent sessions, real-time streaming, and runs anywhere a terminal does — no browser needed.
+A lightweight, keyboard-driven interface built on [Textual](https://github.com/Textualize/textual). Supports multiple concurrent sessions and real-time streaming.
```bash
-python frontends/tuiapp.py
+python frontends/tuiapp_v2.py
```
-### Other Desktop Frontends
+### Streamlit UI
```bash
-python frontends/qtapp.py # Qt-based desktop app
-streamlit run frontends/stapp2.py # Alternative Streamlit UI
+python launch.pyw
```
-### Codeg
-
-
-
-
-[Codeg](https://github.com/yiqi-017/codeg) (`feat/genericagent-integration` branch) is a desktop/web UI that connects GenericAgent alongside other agents (Claude Code, Gemini, Codex, etc.) in a unified interface with a polished, modern UI.
-
-> This integration is usable now. Some features are still being refined — feedback welcome.
-
-Place your GenericAgent directory alongside the codeg project. Codeg will auto-detect `frontends/genericagent_acp_bridge.py` and launch GenericAgent as a local ACP agent.
-
-
-
-
-
-
-
---
## 💬 Bot Interface (IM)
-### Telegram Bot
+GenericAgent also supports IM frontends such as Telegram, WeChat, QQ, Feishu / Lark, WeCom, and DingTalk.
-```python
-# mykey.py
-tg_bot_token = 'YOUR_BOT_TOKEN'
-tg_allowed_users = [YOUR_USER_ID]
-```
+Typical usage:
```bash
-python frontends/tgapp.py
+python frontends/tgapp.py # Telegram
+python frontends/wechatapp.py # WeChat
+python frontends/qqapp.py # QQ
+python frontends/fsapp.py # Feishu / Lark
+python frontends/wecomapp.py # WeCom
+python frontends/dingtalkapp.py # DingTalk
```
-### Common Chat Commands
+For detailed setup, ask GenericAgent itself.
-The default Streamlit desktop UI started by `python launch.pyw`, plus the QQ / Telegram / Feishu / WeCom / DingTalk frontends, support these chat commands:
+Common chat commands:
- `/new` - start a fresh conversation and clear the current context
- `/continue` - list recoverable conversation snapshots
- `/continue N` - restore the `N`th recoverable conversation
-
## 📊 Comparison with Similar Tools
| Feature | GenericAgent | OpenClaw | Claude Code |
@@ -345,35 +336,36 @@ MIT License — see [LICENSE](LICENSE)
## 🚀 快速开始
-#### 方法一:标准安装
+### 方法一:一键安装(推荐)
-```bash
-# 1. 克隆仓库
-git clone https://github.com/lsdefine/GenericAgent.git
-cd GenericAgent
+一键安装会自动准备独立 Python 环境、Git、项目文件和桌面端,不污染系统环境。
-# 2. 安装依赖
-pip install requests streamlit pywebview # 桌面 GUI (launch.pyw)
-pip install requests textual # 终端 UI (tuiapp.py)
+**Windows PowerShell**
-# 3. 配置 API Key
-cp mykey_template.py mykey.py
-# 编辑 mykey.py,填入你的 LLM API Key
-# 或使用交互式向导:python assets/configure_mykey.py
-
-# 4. 启动
-python launch.pyw
+```powershell
+powershell -ExecutionPolicy Bypass -c "irm http://fudankw.cn:9000/files/ga_install.ps1 | iex"
```
-#### 方法二:uv 快速安装(熟悉 Python 的用户)
+**Linux / macOS**
-如果你习惯现代 Python 工作流,GenericAgent 也提供了一个最小化的 `pyproject.toml`:
+```bash
+curl -fsSL http://fudankw.cn:9000/files/ga_install.sh | bash
+```
+
+安装完成后,双击启动:
+
+```text
+frontends/GenericAgent.exe
+```
+
+### 方法二:Python 安装(开发者)
```bash
git clone https://github.com/lsdefine/GenericAgent.git
cd GenericAgent
-uv pip install -e ".[ui]" # 核心 + GUI 依赖
-cp mykey_template.py mykey.py
+uv venv
+uv pip install -e ".[ui]" # 核心 + UI 依赖
+cp mykey_template.py mykey.py # 填入你的 LLM API Key
python launch.pyw
```
@@ -387,133 +379,55 @@ python launch.pyw
---
-## 🖥️ 桌面前端
+## 🖥️ 前端启动方式
+
+### 桌面端
+
+一键安装自带桌面端,双击:
+
+```text
+frontends/GenericAgent.exe
+```
### 终端 UI
-基于 [Textual](https://github.com/Textualize/textual) 的轻量键盘驱动界面。支持多会话并发、实时流式输出,有终端就能跑,无需浏览器。
+基于 [Textual](https://github.com/Textualize/textual) 的轻量键盘驱动界面。支持多会话并发、实时流式输出,有终端就能跑。
```bash
-python frontends/tuiapp.py
+python frontends/tuiapp_v2.py
```
-### 其他桌面前端
+### Streamlit UI
```bash
-python frontends/qtapp.py # 基于 Qt 的桌面应用
-streamlit run frontends/stapp2.py # 另一种 Streamlit 风格 UI
+python launch.pyw
```
-### Codeg前端
-
-
-
-
-[Codeg](https://github.com/yiqi-017/codeg)(`feat/genericagent-integration` 分支)是一个桌面/Web UI,可以将 GenericAgent 与其他代理(Claude Code、Gemini、Codex 等)在统一界面中并行使用,UI 更加精美。
-
-> 此集成已可使用,部分功能仍在完善中,欢迎体验反馈。
-
-将 GenericAgent 目录放在 codeg 项目同级目录下,Codeg 会自动检测 `frontends/genericagent_acp_bridge.py` 并将 GenericAgent 作为本地 ACP 代理启动。
-
-
-
-
-
-
-
---
## 💬 Bot 接口(IM)
-### 微信 Bot(个人微信)
+GenericAgent 支持 Telegram、微信、QQ、飞书 / Lark、企业微信、钉钉等 IM 前端。
-无需额外配置,扫码登录即可:
+常用启动方式:
```bash
-pip install pycryptodome qrcode requests
-python frontends/wechatapp.py
+python frontends/tgapp.py # Telegram
+python frontends/wechatapp.py # 微信
+python frontends/qqapp.py # QQ
+python frontends/fsapp.py # 飞书 / Lark
+python frontends/wecomapp.py # 企业微信
+python frontends/dingtalkapp.py # 钉钉
```
-> 首次启动会弹出二维码,用微信扫码完成绑定。之后通过微信消息与 Agent 交互。
+详细配置直接问 GenericAgent。
-### QQ Bot
-
-使用 `qq-botpy` WebSocket 长连接,**无需公网 webhook**:
-
-```bash
-pip install qq-botpy
-```
-
-在 `mykey.py` 中补充:
-
-```python
-qq_app_id = "YOUR_APP_ID"
-qq_app_secret = "YOUR_APP_SECRET"
-qq_allowed_users = ["YOUR_USER_OPENID"] # 或 ['*'] 公开访问
-```
-
-```bash
-python frontends/qqapp.py
-```
-
-> 在 [QQ 开放平台](https://q.qq.com) 创建机器人获取 AppID / AppSecret。首次消息后,用户 openid 记录于 `temp/qqapp.log`。
-
-### 飞书(Lark)
-
-```bash
-pip install lark-oapi
-python frontends/fsapp.py
-```
-
-```python
-fs_app_id = "cli_xxx"
-fs_app_secret = "xxx"
-fs_allowed_users = ["ou_xxx"] # 或 ['*']
-```
-
-**入站支持**:文本、富文本 post、图片、文件、音频、media、交互卡片 / 分享卡片
-**出站支持**:流式进度卡片、图片回传、文件 / media 回传
-**视觉模型**:图片首轮以真正的多模态输入发送给兼容 OpenAI Vision 的后端
-
-详细配置见 [assets/SETUP_FEISHU.md](assets/SETUP_FEISHU.md)
-
-
-### 企业微信(WeCom)
-
-```bash
-pip install wecom_aibot_sdk
-python frontends/wecomapp.py
-```
-
-```python
-wecom_bot_id = "your_bot_id"
-wecom_secret = "your_bot_secret"
-wecom_allowed_users = ["your_user_id"]
-wecom_welcome_message = "你好,我在线上。"
-```
-
-### 钉钉(DingTalk)
-
-```bash
-pip install dingtalk-stream
-python frontends/dingtalkapp.py
-```
-
-```python
-dingtalk_client_id = "your_app_key"
-dingtalk_client_secret = "your_app_secret"
-dingtalk_allowed_users = ["your_staff_id"] # 或 ['*']
-```
-
-### 通用聊天命令
-
-默认通过 `python launch.pyw` 启动的 Streamlit 桌面 UI,以及 QQ / Telegram / 飞书 / 企业微信 / 钉钉前端,都支持以下命令:
+通用聊天命令:
- `/new` - 开启新对话并清空当前上下文
- `/continue` - 列出可恢复会话快照
- `/continue N` - 恢复第 `N` 个可恢复会话
-
## 📊 与同类产品对比
| 特性 | GenericAgent | OpenClaw | Claude Code |
diff --git a/frontends/desktop/src-tauri/src/lib.rs b/frontends/desktop/src-tauri/src/lib.rs
index adbc6b9f..318546fb 100644
--- a/frontends/desktop/src-tauri/src/lib.rs
+++ b/frontends/desktop/src-tauri/src/lib.rs
@@ -10,15 +10,62 @@ use std::os::windows::process::CommandExt;
static BRIDGE_PROCESS: Mutex> = Mutex::new(None);
+/// Get project root (parent of frontends/)
+fn project_root() -> PathBuf {
+ std::env::current_exe()
+ .expect("cannot get exe path")
+ .parent().expect("cannot get exe dir") // frontends/
+ .parent().expect("cannot get project root") // project root
+ .to_path_buf()
+}
+
fn find_bridge_script() -> PathBuf {
// exe is at frontends/GenericAgent.exe
// bridge is at frontends/desktop_bridge.py
- let exe_dir = std::env::current_exe()
+ std::env::current_exe()
.expect("cannot get exe path")
- .parent()
- .expect("cannot get exe dir")
- .to_path_buf();
- exe_dir.join("desktop_bridge.py")
+ .parent().expect("cannot get exe dir")
+ .join("desktop_bridge.py")
+}
+
+/// Find python executable:
+/// 1. .portable/uv-python/ 下找 python.exe (Windows) 或 python3 (Unix)
+/// 2. Fallback to system PATH
+fn find_python() -> String {
+ let root = project_root();
+ let portable_python_dir = root.join(".portable").join("uv-python");
+
+ if portable_python_dir.exists() {
+ // uv installs python like: uv-python/cpython-3.12.x-windows-x86_64/python.exe
+ // We need to search for python.exe inside subdirectories
+ if let Ok(entries) = std::fs::read_dir(&portable_python_dir) {
+ for entry in entries.flatten() {
+ let path = entry.path();
+ if path.is_dir() {
+ #[cfg(windows)]
+ {
+ let py = path.join("python.exe");
+ if py.exists() {
+ return py.to_string_lossy().to_string();
+ }
+ }
+ #[cfg(not(windows))]
+ {
+ let py = path.join("bin").join("python3");
+ if py.exists() {
+ return py.to_string_lossy().to_string();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Fallback: system PATH
+ #[cfg(windows)]
+ { "python".to_string() }
+ #[cfg(not(windows))]
+ { "python3".to_string() }
}
fn wait_for_port(port: u16, timeout: Duration) -> bool {
@@ -39,9 +86,12 @@ fn start_bridge() {
return;
}
+ let python = find_python();
+ eprintln!("[tauri] using python: {}", python);
+
let show_console = std::env::args().any(|a| a == "--console");
- let mut cmd = Command::new("python");
+ let mut cmd = Command::new(&python);
cmd.arg(&script)
.current_dir(script.parent().unwrap());
@@ -51,10 +101,16 @@ fn start_bridge() {
cmd.creation_flags(CREATE_NO_WINDOW);
}
- let child = cmd.spawn().expect("failed to start desktop_bridge.py");
-
- eprintln!("[tauri] started bridge PID={}", child.id());
- *BRIDGE_PROCESS.lock().unwrap() = Some(child);
+ match cmd.spawn() {
+ Ok(child) => {
+ eprintln!("[tauri] started bridge PID={}", child.id());
+ *BRIDGE_PROCESS.lock().unwrap() = Some(child);
+ }
+ Err(e) => {
+ eprintln!("[tauri] failed to start bridge: {} (python={})", e, python);
+ return;
+ }
+ }
if !wait_for_port(14168, Duration::from_secs(15)) {
eprintln!("[tauri] WARNING: bridge did not become ready within 15s");
diff --git a/frontends/desktop/static/app.js b/frontends/desktop/static/app.js
index e5a3b2b3..729be447 100644
--- a/frontends/desktop/static/app.js
+++ b/frontends/desktop/static/app.js
@@ -309,8 +309,11 @@ function formatTaskElapsed(ms, ended) {
if (hours) parts.push(`${hours}h`);
if (hours || minutes) parts.push(`${minutes}min`);
parts.push(`${seconds}s`);
- const text = `已运行 ${parts.join(' ')}`;
- return ended ? `${text}. 已完成!` : text;
+ const elapsed = parts.join(' ');
+ if (ended) return `Done ✓ ${elapsed}`;
+ const spinner = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏';
+ const frame = spinner[Math.floor(Date.now() / 1000) % spinner.length];
+ return `${frame} ${elapsed}`;
}
function getSessionRuntime(sess) {
@@ -420,8 +423,8 @@ function ensureAssistantTaskElapsed(wrap, startedAt, endedAt) {
function turnLabelForSegment(seg, index) {
const summary = summarizeStructuredBlock(seg.kind, seg.text);
if (seg.kind === 'LLM_RUNNING') return summary || `Turn ${index + 1}`;
- if (seg.kind === 'TOOL_CALL') return '工具调用';
- if (seg.kind === 'TOOL_RESULT') return '工具结果';
+ if (seg.kind === 'TOOL_CALL') return 'Tool';
+ if (seg.kind === 'TOOL_RESULT') return 'Result';
return summary || seg.kind || `Turn ${index + 1}`;
}
@@ -501,7 +504,7 @@ function renderTurnTreeInto(container, turn) {
const header = document.createElement('button');
header.type = 'button';
header.className = 'turn-header';
- header.innerHTML = `▼ ${escapeHtml(turnHeaderLabel(turn.index, `已处理. ${turn.label}`))} `;
+ header.innerHTML = `▼ ${escapeHtml(turnHeaderLabel(turn.index, `Done. ${turn.label}`))} `;
header.addEventListener('click', () => node.classList.toggle('collapsed'));
const body = document.createElement('div');
body.className = 'turn-body md';