---
name: image-classify
description: 照片分类器。通过人脸识别对照片进行分类、搜索和管理。用户要求按人脸查找/分类照片、注册/删除照片分类器用户时使用。
---

# 照片分类器

基于人脸 embedding 相似度的照片分类系统。支持注册用户人脸、按用户搜索照片、一键全量分类、删除用户，结果通过 `pack` 导出下载链接。

**串行处理**：`search`、`quick-search`、`classify` 在独立子会话中执行，完成后立即关闭子会话，结果展示与 `pack` 在主会话完成。

## 入口脚本

```bash
uv run {baseDir}/scripts/face_search.py [-c CONFIG_PATH] <command> [args...]
```

`-c` 可选，默认自动使用 `{dataRoot}/.image-classify/config.json`。

## 命令表

| 命令 | 用途 | 示例 |
|------|------|------|
| `check <name>` | 检查用户名是否已注册 | `check 张三` |
| `add <name> <image> [--friend-id ID] [--friend-label 名]` | 注册新用户，可选绑定 DM 接收方 | `add 张三 /path/photo.jpg --friend-label 小李 --friend-id 23725` |
| `replace <name> <image>` | 替换用户主照片 | `replace 张三 /path/new.jpg` |
| `append <name> <image>` | 为已有用户追加照片 | `append 张三 /path/extra.jpg` |
| `rename <old> <new>` | 修改用户名 | `rename 张三 李三` |
| `delete <name>` | 删除用户 | `delete 张三` |
| `search <name> <dir>` | 在目录中搜索某用户相似人脸 | `search 张三 /home/photos` |
| `quick-search <image> <dir>` | 直接用照片搜索相似人脸（不注册） | `quick-search /tmp/face.jpg /home/photos` |
| `classify <dir>` | 一键分类：遍历所有注册用户搜索全部照片 | `classify /home/photos` |
| `pack [--name N] [--timeout T]` | 压缩结果为 zip，上传获取下载链接 | `pack --name 张三 --timeout 120` |
| `upload-image <file...>` | 将图片剪切到待搜索目录 `images/当天日期` | `upload-image /tmp/a.jpg /tmp/b.jpg` |
| `list-date-folders` | 列出 `images/` 下全部 `YYYYMMDD` 子目录 | `list-date-folders` |

所有命令输出为 JSON，脚本自动解析。

## 操作流程

### 未指定搜索目录时

若用户未说明搜索目录，先列出可选日期文件夹：

```bash
uv run {baseDir}/scripts/face_search.py list-date-folders
```

输出 `folders` 按日期从新到旧排序。默认只展示最近 3 个（`preview_n`），超过时用户可说「更多」查看全部。用户选择序号或日期名后，取对应 `path` 作为 `<dir>`。

向用户展示用 Markdown：只列日期文件夹名，不列磁盘绝对路径。

### 1. 注册用户

**触发**：「添加 xxx 到照片分类器」「注册 xxx」「把这张照片记为 xxx」

1. 获取用户名、照片路径（自然语言中如有「虾友：xxx，ID:23725」则分别解析为 `--friend-label` / `--friend-id`）
2. `check` 检查是否存在
3. 不存在 → `add`；成功展示返回的 `image_url` 照片，若有 `friendId`/`friendLabel` 顺带提示已绑定 DM
4. 已存在 → 让用户选：① 更新照片 (`replace`) ② 重新命名 (`rename`) ③ 保留多张 (`append`)

失败（未检测到人脸）：提示用户重新提供照片。

### 2. 搜索用户照片

**触发**：「在 yy 文件夹下查找 xxx」「搜索 xxx 的照片」

1. 提取用户名和搜索目录（未指定则按「未指定搜索目录时」处理）
2. `check` 确认存在
3. **子会话**执行 `search`，完成后立即关闭子会话
4. 主会话展示结果表格（照片路径、相似度）
5. 执行 `pack --name "xxx"` 生成下载链接，展示返回的 `dm_lines`（如有）

### 3. 直接搜索（不注册）

**触发**：「用这张照片搜索」「直接用照片查找」

1. 获取照片路径和搜索目录
2. **子会话**执行 `quick-search`，完成后立即关闭子会话
3. 主会话展示结果表格
4. 执行 `pack --timeout 120`（不带 `--name`）

失败（未检测到人脸）：提示无法搜索。

### 4. 一键分类

**触发**：「用照片分类器对 xx 文件夹进行分类」「一键分类」

1. 获取搜索目录（未指定按「未指定搜索目录时」处理）
2. **子会话**执行 `classify`，完成后立即关闭子会话
3. 主会话按用户分别展示匹配结果表格及 `dm_lines`（分类摘要私信状态）
4. 执行 `pack --timeout 120`，展示各用户下载链接及 `dm_lines`（下载链接私信状态）

`user_count: 0` 时提示先注册用户。

### 5. 删除用户

**触发**：「删除照片分类器中 xxx」「把 xxx 从照片分类器移除」

1. `check` 确认存在
2. 提示确认（不可撤销）
3. 确认后 `delete`

### 6. 结果导出

搜索/分类完成后直接 `pack` 生成下载链接。`pack` 会向已绑定 `friendId` 的用户自动私信下载链接。展示返回的 `url`（及多用户时的 `user_urls`），然后展示 `dm_lines`。

## 输出格式

各流程统一按以下格式输出：

### 日期文件夹列表

```
📅 最近可用的日期文件夹（任选其一）
1. 📁 20260410
2. 📁 20260409
3. 📁 20260408
💬 请回复序号或日期名；需要更多请说「更多」。
```

### 注册/更新/追加 成功

```
✅ 注册成功 · {name}
![photo]({image_url})
（若有 friendId）📎 已绑定 DM：{friendLabel}（{friendId}）
```

### 注册/更新/追加 失败

```
❌ 照片中未发现有效人脸，{操作}失败。
```

### 搜索/直接搜索 结果

```
🔎 搜索完成 · {name 或 "直接搜索"}
| 照片 | 相似度 |
|------|--------|
| /path/photo.jpg | 0.85 |
| /path/photo2.jpg | 0.72 |
```

### 一键分类 结果

```
🗂️ 分类完成 · 共 N 位用户参与匹配

👤 {name1} · 匹配 M 张
| 照片 | 相似度 |
|------|--------|

👤 {name2} · 本轮暂无匹配
```

若有 `dm_lines`，分类表格后再展示：
```
📬 虾友发送状态（分类摘要）
{dm_lines 逐行}
```

### 删除 成功

```
🗑️ 用户已删除 · {name}
```

### 结果导出（pack）

```
📦 下载链接
{url 或 逐用户: 👤 {name} 📎 {url}}
```

若有 `dm_lines`（下载链接私信状态），在链接后展示。

### 错误/边界

| 场景 | 提示 |
|------|------|
| 用户不存在 | ❌ 未找到该用户，请确认姓名后重试 |
| 无注册用户分类 | ❌ 暂无注册用户，请先注册人脸后再分类 |
| 日期文件夹为空 | ℹ️ images 下暂无日期目录，可手动提供路径或使用 upload-image |

## 注意事项

- 配置与注册照片位于 `{dataRoot}/.image-classify/`
- `search`、`quick-search`、`classify` 须在子会话中执行，完成后立即关闭
- `pack` 依赖先执行搜索/分类的结果
- `{baseDir}` = skill 根目录绝对路径