Metadata-Version: 2.5
Name: pixhub
Version: 0.1.1
Summary: 本地优先的生图/视频模型统一访问路由 (CLI + MCP)
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: mcp>=2.0
Requires-Dist: pydantic>=2.7
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.7
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: http
Requires-Dist: fastapi>=0.115; extra == 'http'
Requires-Dist: uvicorn>=0.32; extra == 'http'
Description-Content-Type: text/markdown

# pixhub

本地优先的生图/视频模型统一访问路由。对外 CLI + MCP 接口稳定，对内把阿里云万相、商汤 SenseNova、腾讯混元、本地 ComfyUI 等收编成可插拔 Provider。

## 设计

- **中立协议**：Pydantic schema（ImageGen / VideoGen 两套），超集 + extra_params 兜底
- **Provider Adapter**：每厂商一个模块，`@provider` 装饰器注册，热插拔
- **本地优先**：结果默认落盘，SQLite 任务库持久化，重启可续轮询
- **Router**：alias → 规范名，顺序 fallback（local → cloud）

## 安装

```bash
uv sync                      # 建虚拟环境 + 装依赖
uv tool install -e .         # 或全局安装 CLI
```

默认只装 CLI + MCP 出口；HTTP API 是可选出口（见「部署」），默认不启动，需要时才加 `--extra http`。

## 使用

```bash
# 列出可用模型
pixhub models

# 生成图像（结果落盘到 ~/Pictures/pixhub，同参数自动命中缓存）
pixhub image "一只猫" --model wanx-turbo
pixhub image "一只猫" --model wanx-turbo --no-cache   # 强制重新生成

# 生成视频（提交并等待）
pixhub video "一只猫跑过草地" --model agnes-video

# 只提交不等待，任务后台轮询 / 断点续查
pixhub video "一只猫跑过草地" --model agnes-video --submit
pixhub resume                                    # 恢复全部 pending 任务
pixhub resume <task_id>                          # 恢复单个

# 图生图 / 多图合成（本地图片自动转 Data URI 上传，远程 URL 用 --image-url）
pixhub image "改成赛博朋克风格" --model agnes-image-flash --image ref.png

# 视频关键帧动画（2 张起）/ 图生视频（单张输入用 --image-url）
pixhub video "镜头缓慢推进" --model agnes-video-v20 --keyframe a.png b.png

# 语音识别（音频 → 文本，本地文件或 --audio-url）
pixhub asr speech.wav --model qwen-asr-flash
pixhub asr --audio-url https://example.com/a.mp3 --model qwen-asr-flash --format mp3 \
  --language zh,en --vocabulary "通义千问:5"

# 语音合成（文本 → 音频，默认落盘，URL 仅 24h 有效）
pixhub tts "你好，我是通义千问。" --model qwen-tts-plus --voice longanhuan_v3.6
pixhub tts "Hello!" --model qwen-tts-plus --format mp3 --sample-rate 48000
# 不传 --voice 时用 models.yaml 里该模型的 voice 配置（见下）

# 查看任务历史 / 详情
pixhub tasks
pixhub task <task_id>

# 密钥与配置体检（密钥来源、缺失项、配置警告）
pixhub config
```

### MCP server（stdio）

```jsonc
// Claude Desktop / Zed / 其它 MCP 客户端配置
{
  "mcpServers": {
    "pixhub": {
      "command": "pixhub-mcp"
    }
  }
}
```

工具集（新增模型只改 `models.yaml`，不碰这里）：`list_models`、`generate_image`、`generate_video`（默认提交即返回 task_id，后台续轮询；`wait=true` 阻塞到完成）、`transcribe_audio`（语音识别）、`synthesize_speech`（语音合成）、`get_task`、`cancel_task`、`list_tasks`、`resume`。自定义配置：`PIXHUB_CONFIG` 指向 models.yaml，`PIXHUB_DATA_DIR` 覆盖数据目录，日志级别用 `PIXHUB_LOG_LEVEL`（默认 INFO）。

### fallback 链

`models.yaml` 里 `fallback: [...]` 配置备用模型，主 provider 失败/超时自动顺延：

```yaml
models:
  wanx-turbo:
    provider: dashscope
    model: wanx2.1-t2i-turbo
    fallback: [sensenova-u15]   # 图像：商汤兜底
  agnes-video-v20:
    provider: agnes
    model: agnes-video-v2.0
    fallback: [wanx-t2v-turbo]  # 视频：万相兜底
```

语音模型（`kind: asr` / `kind: tts`）同样支持 fallback，如给 `qwen-tts-plus` 配 `fallback: [qwen-tts-flash]`。

TTs 模型可在条目里配置默认音色与可选音色清单（调用方不传 `--voice` 时用 `voice`）：

```yaml
models:
  qwen-tts-plus:
    provider: dashscope
    model: qwen-audio-3.0-tts-plus
    kind: tts
    voice: longanhuan_v3.6                    # 默认音色
    voices: [longanhuan_v3.6, longanyang, longshange_v3]  # 可选音色清单（提示用）
```

## 部署

CLI / MCP / HTTP 三种出口共享同一套核心（fallback、缓存、轮询、落盘）、同一个 `models.yaml` 和同一个 SQLite 任务库，选一种即可，混用不冲突。其中 CLI 和 MCP 开箱即用；**HTTP 是可选出口，默认不启动**：

```mermaid
graph LR
    CLI[pixhub CLI] --> CORE[核心层<br/>fallback + 缓存 + 轮询 + 落盘]
    MCP[MCP server stdio] --> CORE
    HTTP[HTTP API :8668] --> CORE
    CORE --> DB[(SQLite 任务库)]
    CORE --> OUT[(输出目录)]
```

### HTTP 出口（serve，可选）

HTTP API 默认不启动，仅在需要给同机其他进程 / 远程调用时才启用（不装 `http` 依赖时 `pixhub serve` 会直接报缺依赖提示）：

```bash
uv sync --extra http                          # 仓库内开发
uv tool install -e ".[http]"                  # 全局 CLI 带上 HTTP（不带则 serve 会报缺依赖）
```

启动与自测：

```bash
pixhub serve --host 127.0.0.1 --port 8668     # 默认只监听本机
curl http://127.0.0.1:8668/health             # 健康检查
```

端点一览（详见 `src/pixhub/http_server.py`）：

| 方法与路径 | 说明 |
|---|---|
| `GET /health` | 健康检查 |
| `GET /models` | 模型列表 |
| `POST /images` | 生图（同步等待） |
| `POST /videos` | 生视频，默认提交即返回 task_id 后台续轮询；`wait=true` 阻塞到完成 |
| `POST /audio/asr` | 语音识别（audio_path / audio_url，同步） |
| `POST /audio/tts` | 语音合成（text + voice，同步，默认落盘） |
| `GET /tasks?limit=&status=` | 任务历史 |
| `GET /tasks/{task_id}` | 任务详情（pending 顺带刷新远端） |
| `POST /tasks/{task_id}/cancel` | 取消 |
| `POST /resume` | 续轮询全部 pending |

```bash
curl -X POST http://127.0.0.1:8668/images -H "Content-Type: application/json" \
  -d '{"prompt": "一只猫", "model": "wanx-turbo"}'
curl -X POST http://127.0.0.1:8668/videos -H "Content-Type: application/json" \
  -d '{"prompt": "一只猫跑过草地", "model": "agnes-video", "wait": false}'
curl -X POST http://127.0.0.1:8668/audio/asr -H "Content-Type: application/json" \
  -d '{"audio_url": "https://example.com/a.wav", "model": "qwen-asr-flash"}'
curl -X POST http://127.0.0.1:8668/audio/tts -H "Content-Type: application/json" \
  -d '{"text": "你好", "model": "qwen-tts-plus", "voice": "longanhuan_v3.6"}'
curl http://127.0.0.1:8668/tasks?limit=10
```

### 长期运行（守护进程）

任务库在 SQLite，重启不丢；服务中断后重启，`POST /resume`（或 CLI `pixhub resume`）接着轮询即可，无需额外持久化配置。

Windows（计划任务，开机启动）：

```bat
schtasks /create /tn pixhub /tr "uv run pixhub serve --host 127.0.0.1 --port 8668" /sc onlogon
:: 或用 NSSM 包成 Windows 服务
:: nssm install pixhub C:\path\to\pixhub\.venv\Scripts\pixhub.exe serve --host 127.0.0.1 --port 8668
```

Linux（systemd）：

```ini
[Unit]
Description=pixhub HTTP API
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/pixhub
ExecStart=/opt/pixhub/.venv/bin/pixhub serve --host 127.0.0.1 --port 8668
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target
```

> HTTP 出口无鉴权。监听 `127.0.0.1` 只服务本机；跨机（`--host 0.0.0.0`）一定要放在可信内网或自己加网关鉴权。

### 数据与配置目录

| 内容 | 默认位置 | 覆盖方式 |
|---|---|---|
| 配置 `models.yaml` | `./models.yaml` 或 `~/.config/pixhub/models.yaml` | `PIXHUB_CONFIG` / `--config` |
| 密钥 `.env` | `~/.config/pixhub/.env`（或系统环境变量） | `pixhub init --key KEY=...` |
| SQLite 任务库 | `~/.local/share/pixhub/pixhub.db` | `PIXHUB_DATA_DIR` |
| 生成结果 | `~/Pictures/pixhub` | `models.yaml` 的 `output_dir` |

Windows 上 `~` 即 `%USERPROFILE%`。MCP / HTTP 进程通过环境变量隔离到独立数据目录时，注意共享任务库的场景（如 server 用独立目录就不会看到 CLI 的任务），默认同用户共库。

## 配置

先一键生成全局配置模板（幂等，已有配置不覆盖）：

```bash
pixhub init                        # 生成 ~/.config/pixhub/models.yaml + .env
pixhub init --key DASHSCOPE_API_KEY=sk-xxx   # 顺便写入密钥
pixhub init --force                # 覆盖重建（.env 里已填的密钥会保留）
```

三层优先级：**CLI 参数 > 配置文件 > 环境变量兜底**。

### 1. 配置文件（`models.yaml`）

放在当前目录或 `~/.config/pixhub/models.yaml`：

```yaml
providers:
  sensenova:
    api_key_env: SENSENOVA_API_KEY   # 推荐：引用环境变量
    base_url: https://custom.url/v1   # 可选：覆盖默认 URL
  agnes:
    api_key: sk-xxx                   # 明文密钥（不推荐，需保护权限）
```

语音识别/合成（ASR/TTS）复用 `dashscope` provider，即 `DASHSCOPE_API_KEY`。

models:
  sensenova-u15:
    provider: sensenova
    model: sensenova-u1.5-lite
    aliases: [sensenova]
```

### 2. 环境变量（最安全）

```bash
set SENSENOVA_API_KEY=sk-xxx
set AGNES_API_KEY=sk-xxx
set DASHSCOPE_API_KEY=sk-xxx
```

Provider 无配置时自动从环境变量读取。

### 3. CLI 参数（临时覆盖）

```bash
pixhub image "猫" -m sensenova-u15 --api-key sk-xxx --base-url https://custom.url
pixhub video "猫" -m agnes-video-v20 --api-key sk-xxx
```

覆盖 `--model` 对应 provider 的配置，不进文件。

## 更新

当前项目从源码安装（非 PyPI 发布），更新分三种情况：

1. **源码更新（editable 安装）**：代码改动即生效，无需重装。依赖有变化时：

   ```bash
   uv sync        # pyproject.toml / uv.lock 变更后同步依赖
   ```

   CLI / HTTP 重开进程即用新代码；常驻的 MCP 客户端需要重启客户端进程才能加载新代码。

2. **PyPI 发布后**：`uv tool upgrade pixhub`（非 editable 安装才支持升级，若当初是 `-e` 安装需先重装非 editable）。

3. **配置模板同步**：新版本模型/字段会进入 `pixhub init` 生成的模板，旧配置不会被覆盖；需要新模板做参考时：

   ```bash
   pixhub init --force     # 重建模板；.env 里已填的密钥会保留
   ```

   然后手动把新增的 `models` 条目合并进自己的 `models.yaml`（新增模型不强制，按需取用）。

跨版本升级前建议备份 `~/.config/pixhub`（配置+密钥）与 `~/.local/share/pixhub`（任务库）；未来若任务库 schema 变更会提供迁移命令。

## 开发

```bash
uv run pytest                # 测试
uv run ruff check src        # lint
```

## 路线

- [x] Phase 0：协议 schema + DashScope provider + CLI + 落盘
- [x] Phase 1：MCP server + SQLite 任务续轮询
- [x] Phase 2：视频异步全链路 + fallback + 商汤 SenseNova
- [x] Phase 3：可选 HTTP 出口 + 缓存去重
