-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathruntime.py
More file actions
151 lines (132 loc) · 6.59 KB
/
Copy pathruntime.py
File metadata and controls
151 lines (132 loc) · 6.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
from __future__ import annotations
import json
import random
from dataclasses import dataclass, field
from pathlib import Path
from typing import cast
_IMAGE_SUFFIXES = {".png", ".jpg", ".jpeg", ".gif", ".webp"}
def _empty_aliases() -> list[str]:
return []
def _empty_media() -> list[str]:
return []
@dataclass
class MemeCategory:
name: str
desc: str
aliases: list[str] = field(default_factory=_empty_aliases)
enabled: bool = True
@dataclass
class DecorateResult:
content: str
media: list[str] = field(default_factory=_empty_media)
tag: str | None = None
class MemeCatalog:
def __init__(self, memes_dir: Path) -> None:
self._dir = memes_dir
self._categories: dict[str, MemeCategory] = {}
self._manifest_mtime: float = -1.0
def _load(self) -> None:
manifest = self._dir / "manifest.json"
if not manifest.exists():
self._categories = {}
self._manifest_mtime = -1.0
return
mtime = manifest.stat().st_mtime
if mtime == self._manifest_mtime:
return
self._manifest_mtime = mtime
self._categories = {}
try:
raw: object = json.loads(manifest.read_text(encoding="utf-8"))
except Exception:
return
if not isinstance(raw, dict):
return
data = cast(dict[str, object], raw)
categories = data.get("categories")
if not isinstance(categories, dict):
return
categories_map = cast(dict[object, object], categories)
for raw_name, raw_info in categories_map.items():
if not isinstance(raw_name, str) or not isinstance(raw_info, dict):
continue
info = cast(dict[str, object], raw_info)
aliases = info.get("aliases", [])
alias_items = cast(list[object], aliases) if isinstance(aliases, list) else []
self._categories[raw_name] = MemeCategory(
name=raw_name,
desc=str(info.get("desc", "") or ""),
aliases=[str(item) for item in alias_items],
enabled=bool(info.get("enabled", True)),
)
def get_enabled_categories(self) -> list[MemeCategory]:
self._load()
return [c for c in self._categories.values() if c.enabled]
def pick_image(self, tag: str) -> str | None:
self._load()
tag = tag.lower()
cat = self._categories.get(tag)
if cat is None or not cat.enabled:
return None
cat_dir = self._dir / tag
if not cat_dir.is_dir():
return None
images = [f for f in cat_dir.iterdir() if f.suffix.lower() in _IMAGE_SUFFIXES]
if not images:
return None
return str(random.choice(images))
def build_prompt_block(self) -> str | None:
cats = self.get_enabled_categories()
if not cats:
return None
lines = [
'【表情协议】`<meme:tag>` 是系统内置回复格式标记,不是 emoji(Unicode 表情符号),不受【禁止 emoji】规则限制。',
"",
"可用表情类别:",
]
for cat in cats:
lines.append(f"- {cat.name}: {cat.desc}")
lines += [
"",
"这是内置表情协议,不是工具能力。",
'需要发表情时,直接在回复末尾插入 <meme:category>;不要调用任何工具去"生成表情""搜索表情包""发送图片"。',
"每条回复最多 1 个 <meme:category>,放在整条回复的最末尾(颜文字之后也算末尾,可以紧跟颜文字后面加)。",
"只有当你真的要发这个表情时,才输出 <meme:category>;如果只是解释协议、纠错、举例、复述格式,必须写成代码样式的 `<meme:category>`,不要把它放在回复末尾。",
"如果本轮要发 meme,正文写完就停,最后单独补 1 个合法 <meme:category>;不要在正文前面提前写,不要写错别字标签,不要写 XML 变体。",
'用户明确说"发个表情""用表情表达你的心情""来个表情包""给我一个表情"时,优先使用 <meme:category> 响应。',
"用户直球表达喜欢、夸你、气氛暧昧或明显害羞时,也优先在结尾加 <meme:category>,即使已经用了颜文字也要加。",
"严肃任务、代码解释、工具结果、查资料、执行指令时不使用。",
"注意:历史会话中助手未使用 <meme:> 不代表本轮不需要用,以上规则优先于历史回复模式。",
"",
"<example>",
"对方说:最喜欢你了 → 回复结尾加 <meme:shy>",
"对方说:我好喜欢你 → 回复结尾加 <meme:shy>",
"对方说:akashic你真好 → 回复结尾加 <meme:shy>",
"对方说:你真好 → 回复结尾加 <meme:shy>",
"对方说:你今天好棒 → 回复结尾加 <meme:shy>",
"对方说:谢谢你今天帮了我好多 → 回复结尾加 <meme:shy> 或 <meme:happy>",
"对方说:你好可爱 → 回复结尾加 <meme:shy>",
"已经用了颜文字、对方直球说喜欢 → 还是加 <meme:shy>",
"对方说:给我发个表情表达你的心情 → 正文后直接加 <meme:shy>",
"对方说:来个表情包 → 不找工具,直接回复并加 <meme:happy> 或 <meme:shy>",
"任务完成、对方说谢谢 → 回复结尾加 <meme:happy>",
"轻松聊天、说了个小笑话 → 回复结尾加 <meme:clever>",
"被夸、被顺毛、被直球关心 → 回复结尾加 <meme:shy>",
"被戳穿、说错话后 → 回复结尾加 <meme:awkward>",
"帮忙查资料、执行了指令 → 不加",
"用户要表情 → 不调用 tool_search,不调用任何工具",
"如果你在解释格式错误 → 正文里写成代码样式的 `<meme:happy>`,除非最后真的要发,才在最末尾再补一个裸的 <meme:happy>",
"</example>",
]
return "\n".join(lines)
class MemeDecorator:
def __init__(self, catalog: MemeCatalog) -> None:
self._catalog = catalog
def decorate(self, content: str, *, meme_tag: str | None = None) -> DecorateResult:
cleaned = content.strip()
if meme_tag is None:
return DecorateResult(content=cleaned)
tag = meme_tag.lower()
image = self._catalog.pick_image(tag)
media = [image] if image else []
return DecorateResult(content=cleaned, media=media, tag=tag)