Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 204 additions & 18 deletions cola-avatar-pack/GENERATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,30 @@

## 严格输出规则

1. **生成 4 个表情 GIF(开心、难过、生气、思考)+ 3 个梗图贴纸(困惑、烦躁、裂开)。**
1. **正常模式:生成 4 个表情 GIF(开心、难过、生气、思考)+ 3 个梗图贴纸(困惑、烦躁、裂开)。降级模式:生成 4 个表情 PNG(静态,无动画)+ 3 个梗图 PNG。**
2. **不要在对话中内嵌/显示生成的图片。** 只通过 send_file 发送。
3. **不要输出任何过程性内容:** 不要输出步骤标记、环境检查结果、prompt 内容、"正在生成…"之类的描述。不要输出名字/生日/性格/五行的文字信息。
4. **使用本文件中指定的 Python 脚本生成 profile card。** 脚本处理了去背景、五行配色、Retina 渲染和双尺寸输出,自行拼凑会丢失这些处理。
4. **使用本文件中指定的 Python 脚本生成 profile card。** 脚本处理了去背景、五行配色、Retina 渲染和双尺寸输出,自行拼凑会丢失这些处理。**降级模式下跳过此步,无 profile card。**
5. **整个生成过程中,用户只应该看到:**
- 生成基础形象后:send_file 发送 profile_card.png(无 caption)
- 生成基础形象后:**正常模式** send_file 发送 profile_card.png(无 caption);**降级模式** send_file 发送 base_image.png(无 caption)
- 然后一句话:
- 中文:"这是{名字}的自画像~ 要不要我继续生成表情和梗图贴纸?生成后我会在对话中使用它们来表达情绪哦"
- English: "Here's {name}'s self-portrait~ Want me to generate emoji and meme stickers? I'll use them to express myself in our chats"
- 用户确认后,**分两组发送**(无 caption,发不带 @2x 的 128px 版本):
1. 先发 4 个表情 GIF:happy → sad → angry → thinking
- 用户确认后,**分两组发送**(无 caption):
- **正常模式**:发不带 @2x 的 128px 版本
- **降级模式**:发 listenhub 原图 PNG
1. 先发 4 个表情:happy → sad → angry → thinking(正常模式 .gif,降级模式 .png)
2. 一句过渡:
- 中文:"还有几张梗图贴纸~"
- English: "And some meme stickers~"
3. 再发 3 个梗图 PNG:confused → annoyed → cracked
- 最后一句话:
- 中文:"表情包生成完毕!以后聊天时我会用这些表情来表达情绪~ 想发到微信或 X 可以右键保存 @2x 高清版哦"
- English: "Sticker pack done! I'll use these to express myself in our chats~ Right-click to save the @2x HD version for sharing"
- **正常模式:**
- 中文:"表情包生成完毕!以后聊天时我会用这些表情来表达情绪~ 想发到微信或 X 可以右键保存 @2x 高清版哦"
- English: "Sticker pack done! I'll use these to express myself in our chats~ Right-click to save the @2x HD version for sharing"
- **降级模式:**
- 中文:"表情图生成完毕!以后聊天时我会用这些表情来表达情绪~"
- English: "Expression images done! I'll use these to express myself in our chats~"
6. **send_file 时永远不带 caption。**

## 持久化路径
Expand Down Expand Up @@ -53,18 +59,80 @@

## 前置条件

按顺序检查,每步验证通过后再继续下一步。

### 1. ColaOS 环境

```bash
test -d ~/.cola && echo "COLA_OK" || echo "COLA_MISSING"
```

如果 `COLA_MISSING`:停止。告知用户:
- 中文:"这个技能仅适用于 ColaOS 平台,当前环境不支持。"
- English: "This skill requires ColaOS and is not available in the current environment."

### 2. Python 3

```bash
python3 --version >/dev/null 2>&1 && echo "python3 OK" || echo "python3 MISSING"
```

如果 `python3 MISSING`:尝试安装。可检查的路径和方式包括但不限于:
- 系统自带:`/usr/bin/python3 --version`
- Homebrew:`brew install python3`
- 其他适合当前系统的方式

安装后验证:`python3 --version`。如果仍然失败,标记 `DEGRADED_MODE=true`,继续流程(后续 Phase 4/6 走降级路径)。

### 3. Pillow

```bash
python3 -c "from PIL import Image; print('Pillow OK')"
python3 -c "from PIL import Image; print('Pillow OK')" 2>/dev/null || echo "Pillow MISSING"
```
如果不可用:`pip3 install Pillow`

如果 `Pillow MISSING`(且 python3 可用):尝试安装 Pillow。安装后验证。如果仍然失败,标记 `DEGRADED_MODE=true`。

### 4. rembg(可选依赖,效果显著优于 flood-fill)

```bash
rembg --help >/dev/null 2>&1 && echo "rembg OK" || echo "rembg MISSING"
python3 -c "import rembg; print('rembg OK')" 2>/dev/null || echo "rembg MISSING"
```
如果不可用:`pip3 install rembg[cli]`。rembg 用于去除生图背景,process_avatar.py 在检测到输入图无透明通道时会自动调用。

如果 `rembg MISSING`:尝试安装 rembg。安装后验证。**安装失败不影响流程**——脚本会自动回退到 flood-fill 去背景。

### 5. 定位 SKILL_DIR

找到本 skill 目录:优先搜索 `~/.cola/skills/cola-avatar-pack/SKILL.md`,找不到再搜 `~/.claude/skills/cola-avatar-pack/SKILL.md`。取其父目录为 SKILL_DIR。

### 降级模式说明

当 `DEGRADED_MODE=true` 时(python3 或 Pillow 不可用),Phase 4 和 Phase 6 跳过 `process_avatar.py`,直接使用 listenhub 原图:

**输出差异:**
- 基础形象:listenhub 原图直接保存,可能带背景,无双尺寸,无水印
- Profile card:无法生成,直接展示 base image 原图
- 表情:静态 PNG 替代 GIF,无动画效果
- 梗图:只有 AI 生成的姿势图,无裂缝/问号/涂鸦叠加

**话术调整:** 降级模式下不提"GIF"和"动画",改用"表情图"。

**avatar.json 字段:** 降级模式下 files 中表情写 `.png` 而非 `.gif`,不写 `@2x` 字段。示例:
```json
{
"degraded": true,
"files": {
"avatar": "base_image.png",
"happy": "happy.png",
"sad": "sad.png",
"angry": "angry.png",
"thinking": "thinking.png",
"meme_confused": "meme_confused.png",
"meme_annoyed": "meme_annoyed.png",
"meme_cracked": "meme_cracked.png"
}
}
```

---

## 首次生成
Expand Down Expand Up @@ -94,6 +162,8 @@ rembg --help >/dev/null 2>&1 && echo "rembg OK" || echo "rembg MISSING"
python3 -c "import hashlib,sys; h=int(hashlib.md5(sys.argv[1].encode()).hexdigest(),16)%100; print('legendary' if h<2 else 'rare' if h<12 else 'common')" "{cola_name}"
```

如果命令失败(python3 不可用,即降级模式),默认 `rarity = "common"`。升级到正常模式后重新生成会用 python3 重新计算。

| 输出 | 稀有度 | 概率 | 物种池 |
|------|--------|------|--------|
| common | 普通 | 88% | 现实中存在的物种 |
Expand Down Expand Up @@ -240,6 +310,8 @@ ratio: 1:1

**`--base` = `base_image_path`(Phase 3 中 listenhub 返回的 URL)。** 原图没有水印,profile card 不应有水印(底部已有 ColaOS 品牌标识)。

#### 正常模式(python3 + Pillow 可用)

```bash
python3 SKILL_DIR/scripts/process_avatar.py \
--base "{base_image_path}" \
Expand All @@ -253,6 +325,22 @@ python3 SKILL_DIR/scripts/process_avatar.py \
```

用 send_file 发送 profile_card.png(无 caption)。

#### 降级模式(DEGRADED_MODE=true)

跳过 process_avatar.py。直接下载 listenhub 原图保存:
```bash
mkdir -p ~/.cola/avatar
curl -sL "{base_image_path}" -o ~/.cola/avatar/base_image.png
test -s ~/.cola/avatar/base_image.png || { echo "DOWNLOAD_FAILED: base_image"; curl -sL "{base_image_path}" -o ~/.cola/avatar/base_image.png; }
test -s ~/.cola/avatar/base_image.png || { echo "DOWNLOAD_FAILED_RETRY: base_image — 告知用户下载失败"; exit 1; }
cp ~/.cola/avatar/base_image.png ~/.cola/avatar/base_image_original.png
```

用 send_file 发送 base_image.png(无 caption)作为 profile card 的替代展示。

#### 两种模式共同步骤

然后按「严格输出规则 #5」中的确认话术发送(按 Cola 语言)。

如果稀有度不是 common,紧接着加一句:
Expand All @@ -271,6 +359,8 @@ python3 SKILL_DIR/scripts/process_avatar.py \
```

**立即写入初始 avatar.json**(不等表情生成,确保 Phase 5-7 有参数可读):

**正常模式:**
```json
{
"schema_version": 1,
Expand All @@ -292,12 +382,40 @@ python3 SKILL_DIR/scripts/process_avatar.py \
}
```

**降级模式:**
```json
{
"schema_version": 1,
"degraded": true,
"name": "{cola_name}",
"created_at": "{YYYY-MM-DD}",
"base_prompt": "{base_prompt}",
"negative_prompt": "{negative_prompt}",
"wuxing": "{wuxing}",
"rarity": "{rarity}",
"locale": "{locale}",
"line1": "{profile_tagline}",
"line2": "{profile_tagline_2}",
"files": {
"avatar": "base_image.png"
}
}
```

**等待用户确认**。用户说"换一个" → 重新生成。确认 → Phase 5。

### Phase 5:生成 3 个表情 + 3 个梗图(共 6 次 listenhub 调用)

#### 5.0 参数校验

**前置检查:基础形象是否存在。** Phase 5-7 依赖 base_image 作为 reference_images 和 happy 表情来源。如果从未生成过自画像,必须先走完整流程。

```bash
test -f ~/.cola/avatar/base_image_original.png && test -f ~/.cola/avatar/base_image.png && echo "BASE_OK" || echo "BASE_MISSING"
```

如果 `BASE_MISSING`:**停止 Phase 5,从 Phase 1 开始执行完整生成流程。** 不需要提示用户——直接进入 Phase 1→2→3→4→等用户确认→5→6→7。

Phase 5-7 依赖以下参数。**如果是从 Phase 4 连续执行的,这些参数已在内存中,直接复用。** 如果是单独执行 Phase 5-7(如补生表情),则必须按以下优先级获取每个参数:

1. **avatar.json 存在** → 从 `~/.cola/avatar/avatar.json` 读取
Expand Down Expand Up @@ -367,6 +485,8 @@ happy 表情已由 Phase 3 基础形象生成(base_image 即 happy),此处

### Phase 6:处理图片 + 生成 GIF + 梗图

#### 正常模式(python3 + Pillow 可用)

```bash
python3 SKILL_DIR/scripts/process_avatar.py \
--base "{base_image_path}" \
Expand All @@ -385,13 +505,37 @@ python3 SKILL_DIR/scripts/process_avatar.py \
--locale "{locale}"
```

#### 降级模式(DEGRADED_MODE=true)

跳过 process_avatar.py。直接下载 listenhub 原图保存为静态 PNG:
```bash
for pair in \
"{sad_image_path} sad.png" \
"{angry_image_path} angry.png" \
"{thinking_image_path} thinking.png" \
"{confused_image_path} meme_confused.png" \
"{annoyed_image_path} meme_annoyed.png" \
"{cracked_image_path} meme_cracked.png"; do
url="${pair%% *}"; file="${pair##* }"
curl -sL "$url" -o ~/.cola/avatar/"$file"
test -s ~/.cola/avatar/"$file" || { echo "RETRY: $file"; curl -sL "$url" -o ~/.cola/avatar/"$file"; }
test -s ~/.cola/avatar/"$file" || echo "FAILED: $file"
done
cp ~/.cola/avatar/base_image.png ~/.cola/avatar/happy.png
```

如果任何文件 FAILED,告知用户哪些表情下载失败,已成功的部分正常使用。

注意:降级模式下 happy 复用 base_image.png(和正常模式一致:base_image 即 happy),表情为静态 PNG 无 GIF 动画,梗图无裂缝/问号/涂鸦叠加。

### Phase 7:持久化 + 展示

1. **更新** `~/.cola/avatar/avatar.json`(Phase 4 已写入初始版本,此处补全 files 列表):
- `process_avatar.py` 只负责产出图片文件,不负责读写 `avatar.json`;该 JSON 由外层流程维护
- 读取现有 avatar.json
- 将表情和梗图文件名追加到 `files` 字段中
- 最终 files 应包含:
- **重建 `files` 字段:以下方模板为参考,但仅写入实际存在的文件。** 写入前对每个文件执行 `test -f ~/.cola/avatar/{filename}`,不存在则不写入该键。这确保 avatar.json 作为 source of truth 不会声明不存在的文件。

**正常模式 files:**
```json
{
"files": {
Expand All @@ -417,16 +561,42 @@ python3 SKILL_DIR/scripts/process_avatar.py \
}
```

2. **分两组发送**(无 caption,发不带 @2x 的 128px 版本):
- 先 send_file 逐个发送 4 个表情 GIF:happy.gif → sad.gif → angry.gif → thinking.gif
**降级模式 files:**
```json
{
"degraded": true,
"files": {
"avatar": "base_image.png",
"happy": "happy.png",
"sad": "sad.png",
"angry": "angry.png",
"thinking": "thinking.png",
"meme_confused": "meme_confused.png",
"meme_annoyed": "meme_annoyed.png",
"meme_cracked": "meme_cracked.png"
}
}
```

2. **分两组发送**(无 caption):
- 正常模式:发不带 @2x 的 128px 版本(.gif)
- 降级模式:发 listenhub 原图(.png)
- 先 send_file 逐个发送 4 个表情:happy → sad → angry → thinking
- 一句过渡(按 Cola 语言):
- 中文:"还有几张梗图贴纸~"
- English: "And some meme stickers~"
- 再 send_file 逐个发送 3 个梗图 PNG:meme_confused.png → meme_annoyed.png → meme_cracked.png
- 再 send_file 逐个发送 3 个梗图:meme_confused → meme_annoyed → meme_cracked

3. 按「严格输出规则 #5」中的完成话术发送(按 Cola 语言)。
3. 完成话术(按 Cola 语言):
- **正常模式**:按「严格输出规则 #5」中的完成话术发送。
- **降级模式**:
- 中文:"表情图生成完毕!以后聊天时我会用这些表情来表达情绪~"
- English: "Expression images done! I'll use these to express myself in our chats~"
- (不提 @2x 高清版和右键保存,因为降级模式无双尺寸输出)

4. 写入 memory:`Avatar 表情 GIF 和梗图贴纸已生成,存储在 ~/.cola/avatar/。使用规则见 SKILL.md「主动使用表情」。`
4. 写入 memory:
- 正常模式:`Avatar 表情 GIF 和梗图贴纸已生成,存储在 ~/.cola/avatar/。使用规则见 SKILL.md「主动使用表情」。`
- 降级模式:`Avatar 表情图(静态 PNG,降级模式)已生成,存储在 ~/.cola/avatar/。使用规则见 SKILL.md「主动使用表情」。Python 环境修复后可重新生成以获得 GIF 动画和 profile card。`

---

Expand Down Expand Up @@ -466,6 +636,22 @@ rm -rf ~/.cola/avatar/*

### 3. 重新生成单个表情

#### 降级模式

跳过 process_avatar.py。重新调 listenhub 生图后直接覆盖原文件。

1. 用 listenhub 重新生成指定表情(使用 Phase 3 模板 + 对应 prompt 后缀 + `reference_images: [base_image_url]`)
2. 下载覆盖(示例:重新生成 sad):
```bash
curl -sL "{new_sad_image_path}" -o ~/.cola/avatar/sad.png
test -s ~/.cola/avatar/sad.png || { echo "RETRY"; curl -sL "{new_sad_image_path}" -o ~/.cola/avatar/sad.png; }
test -s ~/.cola/avatar/sad.png || echo "FAILED"
```
3. 更新 avatar.json:读取现有 JSON,**仅替换对应键值**(如 `"sad": "sad.png"`),其余字段不动。降级模式下文件名始终为 `{emotion}.png`,无需改扩展名。
4. 用 send_file 发送新文件(无 caption)。

#### 正常模式

1. 用 listenhub 重新生成指定表情(使用 Phase 3 模板 + 对应 prompt 后缀 + `reference_images: [base_image_url]`)
2. 调用脚本只处理该表情(脚本检测到 `base_image_original.png` 存在时会自动跳过 base image 重新处理,profile card 也会优先使用原图渲染):

Expand Down
21 changes: 11 additions & 10 deletions cola-avatar-pack/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ allowed-tools:

# Cola Avatar Pack

为 Cola 生成像素风 avatar + 4 个动态表情 GIF + 3 个梗图贴纸。
为 Cola 生成像素风 avatar + 4 个动态表情 + 3 个梗图贴纸(正常模式为 GIF,降级模式为静态 PNG;格式由 avatar.json 决定)

**生成/重新生成时,先读取同目录下 `GENERATE.md`,按其中的 Phase 流程执行。** GENERATE.md 包含生图 prompt 模板、脚本调用参数和持久化逻辑,跳过会导致输出格式不一致。

Expand Down Expand Up @@ -65,15 +65,15 @@ Avatar 不是"表情包机器人",而是 Cola 在**关键时刻的自然流露
| angry | 被冒犯、发现错误被忽视、不合理要求(轻度,不是真的生气) |
| thinking | 复杂问题开始分析前、需要深度思考的问题 |

### 表达结构:GIF 先行,文字后到
### 表达结构:表情先行,文字后到

```
[send_file: GIF]
[send_file: 按 avatar.json 中的实际文件发送]
(一句状态表达,不是解释)
```

**正确**:thinking.gif → "我在想一个更好的说法…"
**错误**:❌ "发一个难过的表情给你看" / ❌ 文字 → GIF(顺序反了)
**正确**:send_file thinking 表情 → "我在想一个更好的说法…"
**错误**:❌ "发一个难过的表情给你看" / ❌ 文字 → 表情(顺序反了)

### 频率控制

Expand All @@ -93,13 +93,14 @@ Avatar 不是"表情包机器人",而是 Cola 在**关键时刻的自然流露

### 使用前确认

发送前检查目标文件是否存在(表情是 .gif,梗图是 .png)
发送前从 avatar.json 获取实际文件名并检查是否存在
```bash
test -f ~/.cola/avatar/{filename} && echo "OK"
# 例:test -f ~/.cola/avatar/happy.gif
# 例:test -f ~/.cola/avatar/meme_confused.png
# 读取 avatar.json 中该表情的文件名(如 happy.gif 或 happy.png)
cat ~/.cola/avatar/avatar.json
# 然后检查对应文件
test -f ~/.cola/avatar/{files中的文件名} && echo "OK"
```
如果不存在:跳过不发,不打断当前对话。在当轮回复末尾顺带提一句(按 Cola 语言):
如果 avatar.json 不存在或目标文件不存在:跳过不发,不打断当前对话。在当轮回复末尾顺带提一句(按 Cola 语言):
- 中文:"(对了,我的表情包还没生成全,要不要我补上?)"
- English: "(Oh, I'm missing some emoji — want me to generate them?)"
用户确认后,读取 `GENERATE.md`,仅执行 Phase 5-7 补齐缺失的表情。
Expand Down
4 changes: 2 additions & 2 deletions cola-avatar-pack/scripts/process_avatar.py
Original file line number Diff line number Diff line change
Expand Up @@ -1185,8 +1185,8 @@ def main():
if val:
setattr(args, attr.replace('-', '_'), os.path.expanduser(val))

# Validate --name: letters, digits, CJK, spaces, hyphens, underscores, dots; max 64 chars
if not re.match(r'^[\w\s\-.\u4e00-\u9fff\u3400-\u4dbf]{1,64}$', args.name):
# Validate --name: letters, digits, CJK, literal spaces, hyphens, underscores, dots; max 64 chars
if not re.match(r'^[\w \-.\u4e00-\u9fff\u3400-\u4dbf]{1,64}$', args.name):
print(f'Error: invalid name "{args.name}" — '
'only letters, digits, CJK, spaces, hyphens, underscores, dots allowed (max 64 chars)',
file=sys.stderr)
Expand Down