# Roneia Client - 本地客户端

Roneia 数字人助手的本地客户端，连接云端服务器执行本地任务。

## 架构角色

```
Client 是 Skill 的"执行者"

Server 发送工具调用请求
         │
         ▼
┌─────────────────────────────────────────────┐
│                   Client                     │
│                                              │
│  ┌────────────────────────────────────────┐ │
│  │          LocalSkillExecutor            │ │
│  │  ┌──────────────────────────────────┐  │ │
│  │  │  SKILL_MODULES = {               │  │ │
│  │  │    "local_file": local_file,     │  │ │
│  │  │    "web_search": web_search,     │  │ │
│  │  │    "screen_capture": screen_cap, │  │ │
│  │  │  }                               │  │ │
│  │  └──────────────────────────────────┘  │ │
│  └────────────────────────────────────────┘ │
│                      │                       │
│                      ▼                       │
│  ┌────────────────────────────────────────┐ │
│  │      builtin_skills/local_file.py      │ │
│  │  ┌──────────────────────────────────┐  │ │
│  │  │  async def open_file(path):      │  │ │
│  │  │      os.startfile(path)          │  │ │
│  │  │      return {"success": True}    │  │ │
│  │  │                                  │  │ │
│  │  │  TOOLS = {"open_file": open_file}│  │ │
│  │  └──────────────────────────────────┘  │ │
│  └────────────────────────────────────────┘ │
│                                              │
└─────────────────────────────────────────────┘
         │
         ▼
返回执行结果给 Server
```

---

## 安装

```bash
# 从 pip 安装
pip install roneia

# 或从源码安装
cd roneia-client
pip install -e .
```

### 安装可选依赖

```bash
# 语音功能
pip install roneia[voice]

# 网页搜索功能（Tavily + DuckDuckGo 双引擎）
pip install roneia[search]

# 屏幕截图功能
pip install roneia[screen]

# 全部可选依赖
pip install roneia[all]
```

> 可选依赖也可以在 `roneia setup` 或 `/skills enable` 时自动安装，无需手动操作。

---

## 快速开始

### 方式 1：一键配置（推荐）

```bash
roneia setup
```

配置向导会引导你完成：
1. 输入服务器地址和认证令牌
2. 自动测试连接
3. 选择要启用的 Skill（自动安装所需依赖）
4. 保存配置到 `~/.roneia/config.yaml`

```
============================================================
           Roneia - 初始配置向导
============================================================

1) 服务器连接
   服务器地址 [ws://localhost:8765/ws]: ws://my-server:8765/ws
   认证令牌: ********

   正在测试连接... 连接成功!

2) Skill 配置
   从服务器获取到 3 个可用 Skill:

     [1] local_file          - 本地文件 (无额外依赖)
     [2] web_search          - 网页搜索 (需安装依赖)
     [3] screen_capture      - 屏幕截图 (需安装依赖)

   选择要启用的 Skill (逗号分隔, 回车全部跳过): 1,2

   正在安装 web_search 依赖...
   依赖安装完成
   已启用: local_file, web_search

3) 保存配置
   配置已保存到: C:\Users\xxx\.roneia\config.yaml

============================================================
   配置完成! 运行 roneia 开始使用
============================================================
```

配置完成后启动：

```bash
roneia
```

### 方式 2：手动配置

```yaml
# ~/.roneia/config.yaml
server:
  url: "ws://your-server-ip:8765/ws"
  token: "your-auth-token"
```

```bash
roneia
# 或指定配置文件
roneia --config config.yaml
```

### 安装并启用 Skill

```
你: /skills list
你: /skills install local_file
你: /skills enable local_file    # 自动检测并提示安装缺失依赖
```

---

## 语音模式

支持语音聊天，类似 GPT/豆包语音对话。基于 sherpa-onnx 流式识别，支持 **YouTube 风格实时字幕**。语音配置自动从 Server 拉取，Client 零配置。

TTS 采用**分句流水线**模式：长文本自动按句拆分，第一句合成完即开始播放，同时并行合成后续句子，首句延迟从 3-5 秒降至 0.5-1.5 秒。短文本（单句）走快速路径，无额外开销。

### 使用

```
你: /voice
[语音] 正在初始化语音模块...
[语音] 正在下载模型 sherpa-onnx-streaming-zipformer-bilingual-zh-en (~191MB)...
[语音] 模型下载完成
[语音] 模块初始化完成
已切换到语音模式（按 Ctrl+C 切回文字模式）

  [等待说话...]
  [正在听...]
  [听到] 今天                        ← 流式逐字更新
  [听到] 今天天气怎么样              ← 自动修正前文
你: 今天天气怎么样                   # 检测到说完

Roneia: 今天北京天气晴朗...          # 文字 + 语音回复
(TTS 播放中，大声说话可打断)

你: /text                            # 切回文字模式
```

### 技术栈

| 组件 | 库 | 说明 |
|------|----|------|
| STT | sherpa-onnx | 流式语音识别（逐帧转录，无 torch 依赖，模型 ~191MB） |
| TTS | edge-tts | 微软 Edge TTS（免费，中文质量好） |
| VAD | webrtcvad | 语音活动检测（轻量 C 库，~200KB） |
| 录音 | sounddevice | 麦克风实时录音 |
| 播放 | pydub | MP3 音频播放 |

### 覆盖 Server 配置

Client 可以在 config.yaml 中覆盖 Server 下发的语音配置：

```yaml
voice:
  tts_voice: "zh-CN-YunxiNeural"    # 覆盖为男声
  tts_rate: "+10%"                   # 覆盖语速
  tts_pitch: "+5Hz"                  # 覆盖音调
  tts_volume: "+10%"                 # 覆盖音量
  silence_duration_ms: 2000          # 等待更久再判定说完
```

---

## /skills 命令

| 命令 | 说明 |
|------|------|
| `/skills list` | 显示云端可用的 Skill |
| `/skills installed` | 显示本地已安装的 Skill |
| `/skills install <name>` | 安装 Skill（标记为已安装） |
| `/skills enable <name>` | 启用 Skill（同步到服务端） |
| `/skills disable <name>` | 禁用 Skill |
| `/skills remove <name>` | 删除本地 Skill |

---

## /persona 命令

管理数字人的性格和说话风格，实时生效无需重启。

| 命令 | 说明 |
|------|------|
| `/persona` | 显示当前人设 |
| `/persona set` | 进入多行输入模式，设置新人设（输入空行结束） |
| `/persona reset` | 恢复默认人设 |

### 示例

```
你: /persona
当前人设:
────────────────────────────────────────
你是 Roneia，用户最好的朋友。
...
────────────────────────────────────────

你: /persona set
请输入新的人设（输入空行结束）:
> 你是一个冷酷的机器人，只用最简短的话回答
>
人设已更新！

你: /persona reset
已恢复默认人设
```

---

## 内置 Skills

| Skill | 功能 | 所需依赖 |
|-------|------|----------|
| local_file | 本地文件管理 | 无 |
| web_search | 网页搜索（Tavily/DuckDuckGo 双引擎） | tavily-python (可选), ddgs, requests, beautifulsoup4 |
| screen_capture | 屏幕截图 | pillow, pyautogui |

### local_file 工具

| 工具 | 说明 |
|------|------|
| `open_file(path)` | 使用系统默认程序打开文件/文件夹 |
| `read_file(path, encoding, max_lines)` | 读取文本文件内容 |
| `search_files(pattern, directory, recursive, max_results)` | 搜索文件 |

### web_search 工具

支持 **Tavily**（主力）+ **DuckDuckGo**（降级）双引擎搜索。Tavily 专为 AI Agent 设计，返回结构化结果和 AI 摘要，免费额度 1000 次/月。Server 配置 `TAVILY_API_KEY` 后自动启用 Tavily；未配置或 Tavily 失败时自动降级到 DuckDuckGo。

| 工具 | 说明 |
|------|------|
| `search(query, num_results)` | 网页搜索（自动选择 Tavily 或 DuckDuckGo） |
| `fetch_page(url, extract_text, max_length)` | 获取网页内容 |

### screen_capture 工具

| 工具 | 说明 |
|------|------|
| `capture_screen(region, save_path)` | 截取屏幕 |
| `find_on_screen(target, confidence)` | 在屏幕上查找目标图像 |

---

## 目录结构

```
roneia-client/
├── pyproject.toml              # pip 配置（包名: roneia）
│
├── roneia_client/              # 客户端模块
│   ├── __main__.py             # python -m roneia_client 入口
│   ├── main.py                 # 主入口（含 setup 子命令）
│   ├── client.py               # WebSocket 客户端
│   ├── config.py               # 配置加载
│   ├── setup_wizard.py         # 初始配置向导 [新增]
│   ├── dep_manager.py          # 依赖管理器 [新增]
│   ├── skill_store.py          # Skill 状态存储
│   ├── local_skill_executor.py # Skill 执行器 [核心]
│   ├── permission_ui.py        # 权限 UI
│   └── voice/                  # 语音模块 [内置功能]
│       ├── __init__.py         # 模块导出 + 自动注册
│       ├── voice_manager.py    # 核心协调器（分句流水线 TTS）
│       ├── text_utils.py       # 文本分句工具
│       ├── base_stt.py         # STT 抽象基类 + 工厂
│       ├── base_tts.py         # TTS 抽象基类 + 工厂
│       ├── sherpa_stt.py       # 流式 STT (sherpa-onnx)
│       ├── edge_tts_engine.py  # Edge TTS
│       ├── vad.py              # WebRTC VAD
│       └── audio_io.py         # 麦克风录音 + 播放（含队列播放）
│
└── roneia_core/                # 核心框架
    ├── protocol.py             # 通信协议
    ├── permission_manager.py   # 权限管理
    └── builtin_skills/         # Skill 代码实现 [核心]
        ├── __init__.py
        ├── local_file.py       # 文件操作
        ├── web_search.py       # 网页搜索
        └── screen_capture.py   # 屏幕截图
```

---

## 工作流程

```
用户输入任务
     │
     ▼
┌────────────────┐
│ WebSocket 发送 │
│ task.request   │
└───────┬────────┘
        │
        ▼
   [Server 处理]
   Phase 1: 若启用多个 Skill，
            先选择相关 Skill
   Phase 2: 仅加载选中 Skill 的工具
        │
        ▼
┌────────────────┐
│ 接收 skill.call│  <── Server 决定调用哪个工具
└───────┬────────┘
        │
        ▼
┌────────────────────────────────────────┐
│          LocalSkillExecutor            │
│                                        │
│  1. 查找 SKILL_MODULES[skill_name]     │
│  2. 查找 module.TOOLS[tool_name]       │
│  3. 执行 await tool_func(**arguments)  │
│  4. 返回结果                            │
└───────┬────────────────────────────────┘
        │
        ▼
┌────────────────┐
│ 发送 skill.result│
└───────┬────────┘
        │
        ▼
   [Server 处理]
        │
        ▼
┌────────────────┐
│ 接收 task.response│
└───────┬────────┘
        │
        ▼
   显示给用户
```

> **注意**：两阶段 Skill 选择完全在 Server 端完成，Client 无需任何改动。当启用多个 Skill 时，Server 会先用轻量 LLM 调用筛选相关 Skill，再只加载选中 Skill 的工具定义，减少 token 消耗。

---

## 添加新 Skill

### 步骤 1：创建 Skill 代码

新建 `roneia_core/builtin_skills/my_skill.py`：

```python
"""我的 Skill 实现"""

from typing import Any, Dict

async def my_tool(param1: str, param2: int = 10) -> Dict[str, Any]:
    """
    我的工具

    Args:
        param1: 参数1
        param2: 参数2（可选）

    Returns:
        执行结果
    """
    try:
        # 实现你的逻辑
        result = f"处理了 {param1}，数量 {param2}"

        return {
            "success": True,
            "message": result,
        }
    except Exception as e:
        return {
            "success": False,
            "error": str(e),
        }

# 工具映射（必需）
TOOLS = {
    "my_tool": my_tool,
}
```

### 步骤 2：注册到执行器

编辑 `roneia_client/local_skill_executor.py`：

```python
from roneia_core.builtin_skills import local_file, web_search, screen_capture, my_skill

SKILL_MODULES = {
    "local_file": local_file,
    "web_search": web_search,
    "screen_capture": screen_capture,
    "my_skill": my_skill,  # 添加这行
}
```

### 步骤 3：Server 端添加定义

在 Server 的 `skill_registry/builtin_skills.py` 中添加 Skill 定义（参数、描述等）。

---

## 命令行参数

```
roneia [命令] [选项]

命令:
  setup                 运行初始配置向导
  (无)                  启动交互模式

选项:
  --config, -c FILE     配置文件路径
  --server URL          服务器 WebSocket URL
  --token TOKEN         认证令牌
  --no-gui              禁用 GUI 弹窗
  --debug               调试模式

交互命令:
  /voice                切换到语音模式
  /text                 切回文字模式
  /skills ...           Skill 管理（enable 时自动检测依赖）
  /persona              人设管理
  /status               显示状态
  /help                 帮助
```

---

## 配置文件

```yaml
server:
  url: "ws://localhost:8765/ws"   # 服务器地址
  token: ""                        # 认证令牌
  reconnect_interval: 5            # 重连间隔(秒)
  max_reconnect_attempts: -1       # 最大重连次数 (-1=无限)
  heartbeat_interval: 30           # 心跳间隔(秒)

permissions:
  auto_grant: []                   # 自动授权的权限
  use_gui: true                    # 使用GUI弹窗请求权限
```

---

## 数据存储

客户端数据存储位置：

- `~/.roneia/config.yaml` - 客户端配置（`roneia setup` 生成）
- `~/.roneia/skills.json` - 已安装 Skill 的状态

---

## 故障排除

### 无法连接服务器

1. 检查服务器地址是否正确
2. 检查网络是否通畅
3. 检查认证令牌是否正确
4. 使用 `--debug` 查看详细日志

### Skill 不可用

1. 检查是否已安装：`/skills installed`
2. 检查是否已启用：`/status`
3. 检查依赖是否安装（如 web_search 需要 requests）

### 工具执行失败

1. 检查参数是否正确
2. 检查权限是否已授予
3. 查看日志中的错误信息

---

## 开发

### 从源码运行

```bash
cd roneia-client
pip install -e .
roneia setup                       # 初始配置
roneia --debug                     # 启动（调试模式）
python -m roneia_client             # 或通过 python -m 启动
```

### 运行测试

```bash
# 测试 Skill 模块导入
python -c "from roneia_core.builtin_skills import local_file; print(local_file.TOOLS)"

# 测试工具执行
python -c "
import asyncio
from roneia_core.builtin_skills import local_file
result = asyncio.run(local_file.search_files('*.py', '.', max_results=3))
print(result)
"
```
