koishi-plugin-memesluna 0.5.4 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1481,23 +1481,39 @@ var MemesLunaService = class extends import_koishi.Service {
1481
1481
  await this.ctx.database.remove("memesluna_endpoints", { name: name2 });
1482
1482
  return true;
1483
1483
  }
1484
- async buildRouteInventory(backendPath) {
1484
+ async buildRouteInventory(backendPath, synonymGroups) {
1485
1485
  const endpoints = await this.getEndpoints();
1486
1486
  const collections = await this.getCollections();
1487
- const lines = [];
1488
- for (const endpoint of endpoints) {
1489
- const desc = endpoint.description || endpoint.name;
1490
- lines.push(`- 端点名: ${endpoint.name} | 描述: ${desc} | 接口地址: ${backendPath}/${endpoint.name}`);
1487
+ const sections = [];
1488
+ if (endpoints.length > 0) {
1489
+ const endpointLines = endpoints.map((ep) => {
1490
+ const desc = ep.description || ep.name;
1491
+ return ` - ${ep.name}:${desc} → ${backendPath}/${ep.name}`;
1492
+ });
1493
+ sections.push(`【端点转发】
1494
+ ${endpointLines.join("\n")}`);
1491
1495
  }
1496
+ const collectionLines = [];
1492
1497
  for (const collection of collections) {
1493
1498
  const info = await this.getCollectionInfo(collection);
1494
1499
  if (info?.hasContent) {
1495
- const desc = info.description || collection;
1496
- const displayName = desc.endsWith("表情包") ? desc : `${desc}表情包`;
1497
- lines.push(`- ${displayName} | 描述: ${collection} | 表情包路径: ${backendPath}/${collection}`);
1500
+ const desc = info.description ? `(${info.description})` : "";
1501
+ collectionLines.push(` - ${collection}${desc} ${backendPath}/${collection}`);
1498
1502
  }
1499
1503
  }
1500
- return lines.join("\n");
1504
+ if (collectionLines.length > 0) {
1505
+ sections.push(`【表情包合集】
1506
+ ${collectionLines.join("\n")}`);
1507
+ }
1508
+ if (synonymGroups && synonymGroups.length > 0) {
1509
+ const tagNames = synonymGroups.map((g) => g[0]).filter(Boolean);
1510
+ if (tagNames.length > 0) {
1511
+ sections.push(`【情绪/标签】(按心情选一个)
1512
+ ${tagNames.join(" ")}`);
1513
+ sections.push(` 标签路由格式:${backendPath}/标签名`);
1514
+ }
1515
+ }
1516
+ return sections.join("\n\n");
1501
1517
  }
1502
1518
  };
1503
1519
 
@@ -1728,44 +1744,40 @@ aliases: 字符串数组(辅助检索)
1728
1744
  - 只返回合法 JSON,格式:{"aliases": [...], "tags": [...]}`;
1729
1745
  var Config = import_koishi2.Schema.intersect([
1730
1746
  import_koishi2.Schema.object({
1731
- backendPath: import_koishi2.Schema.string().default("/memesluna").description("后端服务路径前缀"),
1732
- selfUrl: import_koishi2.Schema.string().default("").description("服务公开地址,不填则优先使用 server.selfUrl"),
1733
- injectVariables: import_koishi2.Schema.boolean().default(true).description("是否向 ChatLuna 注入 {{endpoint}} {{memesluna}} 变量"),
1734
- variableRefreshIntervalMs: import_koishi2.Schema.number().min(30 * 1e3).max(60 * 60 * 1e3).default(5 * 60 * 1e3).description("变量刷新间隔(毫秒)"),
1735
- injectVariablesPrompt: import_koishi2.Schema.string().role("textarea").default(`你可以使用表情包来丰富你的回复。可用的表情包合集如下:
1747
+ backendPath: import_koishi2.Schema.string().default("/memesluna").description("插件 HTTP 路由的前缀路径,默认 /memesluna,修改后需重启"),
1748
+ selfUrl: import_koishi2.Schema.string().default("").description("插件对外暴露的完整地址(含协议和端口),留空则自动使用 Koishi server.selfUrl"),
1749
+ injectVariables: import_koishi2.Schema.boolean().default(true).description("开启后向 ChatLuna 注入 {{endpoint}} 路由列表和 {{memesluna}} 使用说明,让 AI 能发图"),
1750
+ variableRefreshIntervalMs: import_koishi2.Schema.number().min(30 * 1e3).max(60 * 60 * 1e3).default(5 * 60 * 1e3).description("ChatLuna 变量自动刷新间隔(毫秒),新增合集或路由后最多等待这么久才生效"),
1751
+ injectVariablesPrompt: import_koishi2.Schema.string().role("textarea").default(`你可以根据自己当前的心情或者动作从标签中选择合适的表情发送,也可以根据需要选择合适的表情包合集或端点。
1736
1752
 
1737
- {endpoint}
1753
+ 可用的路由如下:
1738
1754
 
1739
- 使用方法:
1740
- - 合集名路由:{base_url}/memesluna/合集名 自动随机返回该合集的图片
1741
- - 端点名路由: {base_url}/memesluna/端点名 自动随机返回该端点的图片
1742
- - 标签名路由:{base_url}/memesluna/标签名 按标签跨合集随机返回图片(可用标签如:{tags})
1743
- - 合集搜索:{base_url}/memesluna/合集名?q=关键词 在合集中按语义搜索
1755
+ {endpoint}
1744
1756
 
1745
- 使用方法:
1746
- 直接用 {base_url} 拼接路径即可,合集名和标签名不要重名,合集优先匹配。只使用上面列出的合集名和标签名,不要自己编造路径。
1747
- 例如:
1748
- 如果你想发送"开心"标签下的表情包,请在回复中包含类似{base_url}/memesluna/开心
1749
- 如果你想发送"vrchat" 合集下的表情包,请在回复中包含类似{base_url}/memesluna/vrchat
1750
- 如果你想发送"lai" 端点的表情包,请在回复中包含类似{base_url}/memesluna/lai`).description("注入到 ChatLuna {{memesluna}} 变量的提示词模板,支持 {endpoint}{base_url}{tags} 占位符")
1757
+ 使用规则:
1758
+ - 直接将路径拼接到 {base_url} 后即可,例如 {base_url}/memesluna/开心
1759
+ - 合集名和标签名不要重名,合集优先匹配
1760
+ - 只使用上面列出的名称,不要自己编造路径
1761
+ - 标签路由按情绪跨合集随机返图,合集路由在指定合集内随机,端点路由转发到外部图源
1762
+ - 合集搜索:{base_url}/memesluna/合集名?q=关键词 可按语义搜索指定合集`).description("注入 ChatLuna 的提示词模板,支持占位符:{endpoint}(路由列表)、{base_url}(服务地址)、{tags}(所有可用标签)")
1751
1763
  }).description("基础配置"),
1752
1764
  import_koishi2.Schema.object({
1753
- autoCollect: import_koishi2.Schema.boolean().default(false).description("是否自动监听群聊里的高频图片并放入暂缓区"),
1754
- whitelistGroups: import_koishi2.Schema.array(import_koishi2.Schema.string()).role("table").default([]).description("自动暂存群白名单。不填表示监听所有群,填写后仅这些群会参与高频图片统计"),
1755
- emojiFrequencyWindowMinutes: import_koishi2.Schema.number().min(1).max(1440).default(10).description("频率统计时间窗口(分钟)"),
1756
- emojiFrequencyThreshold: import_koishi2.Schema.number().min(1).max(50).default(3).description("在统计窗口内同一图片出现多少次后放入暂缓区"),
1757
- minEmojiSize: import_koishi2.Schema.number().min(1).max(1024).default(50).description("自动暂存图片最小大小(KB"),
1758
- maxEmojiSize: import_koishi2.Schema.number().min(1).max(100).default(15).description("自动暂存图片最大大小(MB"),
1759
- groupAutoCollectLimit: import_koishi2.Schema.number().min(1).max(5e3).default(300).description("每个群每天最多自动放入暂缓区的图片数量")
1765
+ autoCollect: import_koishi2.Schema.boolean().default(false).description("开启后自动监听群聊消息,高频出现的图片会自动进入暂缓区等待审核"),
1766
+ whitelistGroups: import_koishi2.Schema.array(import_koishi2.Schema.string()).role("table").default([]).description("自动暂存白名单群号,留空表示监听所有群;填写后只有这些群的图片会被统计"),
1767
+ emojiFrequencyWindowMinutes: import_koishi2.Schema.number().min(1).max(1440).default(10).description("高频统计的时间窗口(分钟),窗口内同一张图出现次数超过阈值才触发暂存"),
1768
+ emojiFrequencyThreshold: import_koishi2.Schema.number().min(1).max(50).default(3).description("同一张图在统计窗口内出现几次后放入暂缓区"),
1769
+ minEmojiSize: import_koishi2.Schema.number().min(1).max(1024).default(50).description("自动暂存的图片最小体积(KB),低于此大小的图片会被忽略"),
1770
+ maxEmojiSize: import_koishi2.Schema.number().min(1).max(100).default(15).description("自动暂存的图片最大体积(MB),超过此大小的图片会被忽略"),
1771
+ groupAutoCollectLimit: import_koishi2.Schema.number().min(1).max(5e3).default(300).description("每个群每天最多触发自动暂存的图片数量上限,防止刷屏导致暂缓区过大")
1760
1772
  }).description("自动暂存配置"),
1761
1773
  import_koishi2.Schema.object({
1762
- similarityThreshold: import_koishi2.Schema.number().min(0.5).max(1).step(0.01).role("slider").default(0.9).description("暂缓区相似图片筛选阈值,只用于聚类展示,不会自动删除图片"),
1763
- stagingRetentionDays: import_koishi2.Schema.number().min(0).max(365).default(0).description("暂缓区图片自动清理天数,0 表示永不自动清理")
1764
- }).description("暂缓区复核配置"),
1774
+ similarityThreshold: import_koishi2.Schema.number().min(0.5).max(1).step(0.01).role("slider").default(0.9).description("暂缓区相似图片聚合阈值(0~1),越高越严格,仅用于管理界面分组展示,不会自动删除"),
1775
+ stagingRetentionDays: import_koishi2.Schema.number().min(0).max(365).default(0).description("暂缓区图片自动过期天数,0 表示永久保留不自动清理")
1776
+ }).description("暂缓区配置"),
1765
1777
  import_koishi2.Schema.object({
1766
- model: import_koishi2.Schema.dynamic("model").description("用于 AI 标注的模型"),
1767
- autoAnnotate: import_koishi2.Schema.boolean().default(false).description("上传图片时是否自动进行 AI 语义标注"),
1768
- annotatePrompt: import_koishi2.Schema.string().role("textarea").default(DEFAULT_ANNOTATE_PROMPT).description("AI 标注提示词模板,要求输出 aliases tags 两个字段的 JSON"),
1778
+ model: import_koishi2.Schema.dynamic("model").description("AI 标注使用的模型,需已在 ChatLuna 中配置"),
1779
+ autoAnnotate: import_koishi2.Schema.boolean().default(false).description("上传图片时自动触发 AI 语义标注(生成 tags 和 aliases),需先配置模型"),
1780
+ annotatePrompt: import_koishi2.Schema.string().role("textarea").default(DEFAULT_ANNOTATE_PROMPT).description("AI 标注的 System 提示词,要求模型输出 { aliases: string[], tags: string[] } 格式 JSON"),
1769
1781
  synonymGroups: import_koishi2.Schema.array(import_koishi2.Schema.string()).role("table").default([
1770
1782
  "幸福,开心,高兴,快乐,治愈,满足",
1771
1783
  "委屈,难过,伤心,沮丧,流泪,大哭",
@@ -1782,11 +1794,11 @@ var Config = import_koishi2.Schema.intersect([
1782
1794
  "摆烂,咸鱼,躺平,不想动,无所谓,累了,疲惫",
1783
1795
  "害怕,发抖,瑟瑟发抖,惊恐,怂,慌张",
1784
1796
  "求求,拜托,求你,拜托了"
1785
- ]).description("搜索同义词组,每行填写一组同义词,词与词之间用逗号(,,)隔开"),
1786
- aiConcurrency: import_koishi2.Schema.number().min(1).max(10).default(2).description("AI 标注并发数"),
1787
- aiBatchDelay: import_koishi2.Schema.number().min(0).max(5e3).default(500).description("AI 批次间延迟(毫秒)"),
1788
- aiMaxAttempts: import_koishi2.Schema.number().min(1).max(10).default(3).description("AI 标注最大重试次数"),
1789
- aiBackoffBase: import_koishi2.Schema.number().min(100).max(1e4).default(1e3).description("AI 重试退避基数(毫秒)")
1797
+ ]).description("同义词分组,每行一组,组内用逗号(,,)分隔;同组词会被合并为同一标签,也用于标签路由的跨词匹配"),
1798
+ aiConcurrency: import_koishi2.Schema.number().min(1).max(10).default(2).description("AI 标注的并发请求数,越大速度越快但对模型负载更高"),
1799
+ aiBatchDelay: import_koishi2.Schema.number().min(0).max(5e3).default(500).description("批量标注时每张图之间的等待时间(毫秒),用于避免触发模型限流"),
1800
+ aiMaxAttempts: import_koishi2.Schema.number().min(1).max(10).default(3).description("AI 标注失败后的最大重试次数"),
1801
+ aiBackoffBase: import_koishi2.Schema.number().min(100).max(1e4).default(1e3).description("重试退避基数(毫秒),每次重试等待时间 = 基数 × 重试次数")
1790
1802
  }).description("AI 标注配置")
1791
1803
  ]);
1792
1804
  var name = "memesluna";
@@ -2192,7 +2204,8 @@ async function getPromptTags(ctx, config) {
2192
2204
  __name(getPromptTags, "getPromptTags");
2193
2205
  async function updateMemesVariable(ctx, config, service) {
2194
2206
  const baseUrl = toAbsoluteBaseUrl(ctx, config);
2195
- const inventory = await service.buildRouteInventory(config.backendPath);
2207
+ const synonymGroups = parseSynonymGroups(config.synonymGroups || []);
2208
+ const inventory = await service.buildRouteInventory(config.backendPath, synonymGroups);
2196
2209
  const tagsStr = await getPromptTags(ctx, config);
2197
2210
  ctx.chatluna.promptRenderer.setVariable("endpoint", inventory || "- 暂无可用路由");
2198
2211
  const memeslunaText = config.injectVariablesPrompt.replaceAll("{endpoint}", inventory || "- 暂无可用路由").replaceAll("{base_url}", baseUrl).replaceAll("{tags}", tagsStr);
@@ -2569,7 +2582,8 @@ function applyServer(ctx, config, service) {
2569
2582
  const endpoints = await service.getEndpoints();
2570
2583
  const collections = await service.getCollections();
2571
2584
  const collectionInfos = await Promise.all(collections.map((name2) => service.getCollectionInfo(name2)));
2572
- const inventory = await service.buildRouteInventory(basePath);
2585
+ const synonymGroups = parseSynonymGroups(config.synonymGroups || []);
2586
+ const inventory = await service.buildRouteInventory(basePath, synonymGroups);
2573
2587
  const tagsStr = await getPromptTags(ctx, config);
2574
2588
  const llmPrompt = config.injectVariablesPrompt.replaceAll("{endpoint}", inventory || "- 暂无可用路由").replaceAll("{base_url}", baseUrl).replaceAll("{tags}", tagsStr);
2575
2589
  koa.body = {
@@ -3140,8 +3154,8 @@ function apply(ctx, config) {
3140
3154
  }
3141
3155
  __name(apply, "apply");
3142
3156
  var inject = {
3143
- required: ["database"],
3144
- optional: ["memesluna", "chatluna", "server", "console"]
3157
+ required: ["database", "chatluna", "server"],
3158
+ optional: ["memesluna", "console"]
3145
3159
  };
3146
3160
  // Annotate the CommonJS export names for ESM import in node:
3147
3161
  0 && (module.exports = {
package/lib/service.d.ts CHANGED
@@ -176,7 +176,7 @@ export declare class MemesLunaService extends Service {
176
176
  addEndpoint(input: ApiEndpointInput): Promise<string>;
177
177
  updateEndpoint(name: string, input: Partial<ApiEndpointInput>): Promise<boolean>;
178
178
  deleteEndpoint(name: string): Promise<boolean>;
179
- buildRouteInventory(backendPath: string): Promise<string>;
179
+ buildRouteInventory(backendPath: string, synonymGroups?: string[][]): Promise<string>;
180
180
  }
181
181
  declare module 'koishi' {
182
182
  interface Context {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-memesluna",
3
3
  "description": "Image Forward service for Koishi with ChatLuna integration",
4
- "version": "0.5.4",
4
+ "version": "0.5.6",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "types": "lib/index.d.ts",