koishi-plugin-image-prompt 2.0.4 → 2.1.0
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.d.ts +43 -1
- package/lib/index.js +258 -17
- package/package.json +1 -1
- package/readme.md +60 -6
- package/src/index.ts +330 -17
package/lib/index.d.ts
CHANGED
|
@@ -76,6 +76,30 @@ export declare function renderAgentInstructions(template: string, vars: Record<s
|
|
|
76
76
|
* 这里压成一行、去掉 markdown 记号和包裹引号、限长,让它永远是「对用户说的一句话」。
|
|
77
77
|
*/
|
|
78
78
|
export declare function cleanAskMessage(raw: any, max?: number): string;
|
|
79
|
+
/**
|
|
80
|
+
* 从用户的回答里判断他要不要润色提示词。
|
|
81
|
+
* 默认 false —— 用户的原话原封不动交给绘图模型才是对的默认值。
|
|
82
|
+
*/
|
|
83
|
+
export declare function parsePolishIntent(text: string): boolean;
|
|
84
|
+
/** 从消息内容里解析 @ 到的人。type="all"(@全体成员)和 @机器人自己 会被跳过 */
|
|
85
|
+
export declare function extractAtIds(content: string, selfId?: string): {
|
|
86
|
+
id: string;
|
|
87
|
+
name: string;
|
|
88
|
+
}[];
|
|
89
|
+
/**
|
|
90
|
+
* 拼头像地址。优先级:自定义模板 > 适配器直接给的 > 平台内置规则。
|
|
91
|
+
* 拼不出来返回空字符串(调用方再决定是问适配器还是放弃)。
|
|
92
|
+
*/
|
|
93
|
+
export declare function buildAvatarUrl(input: {
|
|
94
|
+
userId: string;
|
|
95
|
+
platform?: string;
|
|
96
|
+
selfId?: string;
|
|
97
|
+
appId?: string;
|
|
98
|
+
/** 发送者自己的头像(适配器已经在消息里带过来了) */
|
|
99
|
+
authorAvatar?: string;
|
|
100
|
+
template?: string;
|
|
101
|
+
size?: number;
|
|
102
|
+
}): string;
|
|
79
103
|
/**
|
|
80
104
|
* 图库容量裁剪:超出容量时淘汰最旧的(按 time 升序),只保留每个组最近 capacity 条
|
|
81
105
|
*/
|
|
@@ -206,6 +230,7 @@ interface CommandConfig {
|
|
|
206
230
|
referenceGroups?: ReferenceGroup[];
|
|
207
231
|
aiSelector?: AISelectorConfig;
|
|
208
232
|
agent?: AgentConfig;
|
|
233
|
+
avatar?: AvatarConfig;
|
|
209
234
|
showPrompt?: boolean;
|
|
210
235
|
promptMaxLength?: number;
|
|
211
236
|
appendUserInput?: boolean;
|
|
@@ -317,6 +342,23 @@ interface AgentConfig {
|
|
|
317
342
|
historyTurns: number;
|
|
318
343
|
/** 把每一轮的工具调用打到日志里,方便排查 */
|
|
319
344
|
debugLog: boolean;
|
|
345
|
+
/** 用户没给任何参考图时,开画前先问一句「要不要参考图」 */
|
|
346
|
+
askIfNoReference: boolean;
|
|
347
|
+
/** 开画前先问一句「要不要润色提示词」(默认不润色,原话直出) */
|
|
348
|
+
askBeforePolish: boolean;
|
|
349
|
+
}
|
|
350
|
+
/** 头像参考图配置 */
|
|
351
|
+
interface AvatarConfig {
|
|
352
|
+
/** 总开关:能不能拿用户头像当参考图 */
|
|
353
|
+
enabled: boolean;
|
|
354
|
+
/** 用户 @ 了谁,就自动把那个人的头像作为参考图(@ 本身就是明确指定) */
|
|
355
|
+
autoAt: boolean;
|
|
356
|
+
/** 用户没发图也没 @ 人时,自动拿他自己的头像当参考图 */
|
|
357
|
+
autoSelf: boolean;
|
|
358
|
+
/** 头像尺寸(边长像素) */
|
|
359
|
+
size: number;
|
|
360
|
+
/** 自定义头像地址模板,留空用内置规则。占位符:{userId} {platform} {selfId} {appId} {size} */
|
|
361
|
+
urlTemplate: string;
|
|
320
362
|
}
|
|
321
363
|
/** agent 参考图登记表里的一条 */
|
|
322
364
|
export interface RefEntry {
|
|
@@ -324,7 +366,7 @@ export interface RefEntry {
|
|
|
324
366
|
url: string;
|
|
325
367
|
description: string;
|
|
326
368
|
group: string;
|
|
327
|
-
source: 'gallery' | 'user' | 'cmd';
|
|
369
|
+
source: 'gallery' | 'user' | 'cmd' | 'avatar';
|
|
328
370
|
}
|
|
329
371
|
/** 交给 AI 挑选的候选图片 */
|
|
330
372
|
interface CandidateImage {
|
package/lib/index.js
CHANGED
|
@@ -33,12 +33,14 @@ __export(src_exports, {
|
|
|
33
33
|
Config: () => Config,
|
|
34
34
|
apply: () => apply,
|
|
35
35
|
buildAgentTools: () => buildAgentTools,
|
|
36
|
+
buildAvatarUrl: () => buildAvatarUrl,
|
|
36
37
|
buildMarkdownImage: () => buildMarkdownImage,
|
|
37
38
|
buildPromptBlock: () => buildPromptBlock,
|
|
38
39
|
buildPromptEcho: () => buildPromptEcho,
|
|
39
40
|
cleanAskMessage: () => cleanAskMessage,
|
|
40
41
|
computeRetryDelay: () => computeRetryDelay,
|
|
41
42
|
describeSelectorError: () => describeSelectorError,
|
|
43
|
+
extractAtIds: () => extractAtIds,
|
|
42
44
|
extractJsonObject: () => extractJsonObject,
|
|
43
45
|
extractResponseText: () => extractResponseText,
|
|
44
46
|
extractServerMessage: () => extractServerMessage,
|
|
@@ -50,6 +52,7 @@ __export(src_exports, {
|
|
|
50
52
|
mergePrompt: () => mergePrompt,
|
|
51
53
|
name: () => name,
|
|
52
54
|
parseJsonToolCall: () => parseJsonToolCall,
|
|
55
|
+
parsePolishIntent: () => parsePolishIntent,
|
|
53
56
|
parseRetryAfterHeader: () => parseRetryAfterHeader,
|
|
54
57
|
pickAgentMessage: () => pickAgentMessage,
|
|
55
58
|
previewJson: () => previewJson,
|
|
@@ -226,7 +229,7 @@ function resolveReferences(refs, registry) {
|
|
|
226
229
|
}
|
|
227
230
|
return { ids, unknown };
|
|
228
231
|
}
|
|
229
|
-
var AGENT_TOOL_NAMES = ["gallery_search", "ask_user", "draw"];
|
|
232
|
+
var AGENT_TOOL_NAMES = ["gallery_search", "ask_user", "get_avatar", "draw"];
|
|
230
233
|
function buildAgentTools() {
|
|
231
234
|
return [
|
|
232
235
|
{
|
|
@@ -258,6 +261,20 @@ function buildAgentTools() {
|
|
|
258
261
|
}
|
|
259
262
|
}
|
|
260
263
|
},
|
|
264
|
+
{
|
|
265
|
+
type: "function",
|
|
266
|
+
function: {
|
|
267
|
+
name: "get_avatar",
|
|
268
|
+
description: "\u53D6\u67D0\u4E2A\u4EBA\u7684\u5934\u50CF\u5F53\u53C2\u8003\u56FE\uFF0C\u8FD4\u56DE\u5B83\u7684\u7F16\u53F7\u3002userId \u7559\u7A7A = \u53D6\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\u7684\u5934\u50CF\uFF1B\u4E5F\u53EF\u4EE5\u586B\u300C\u88AB @ \u7684\u4EBA\u300D\u5217\u8868\u91CC\u7684 id\u3002\u7528\u6237\u8BF4\u300C\u753B\u6211\u300D\u300C\u7528\u6211\u7684\u5934\u50CF\u300D\u300C\u753B @\u67D0\u67D0\u300D\u65F6\u7528\u5B83\u3002",
|
|
269
|
+
parameters: {
|
|
270
|
+
type: "object",
|
|
271
|
+
properties: {
|
|
272
|
+
userId: { type: "string", description: "\u8981\u53D6\u5934\u50CF\u7684\u7528\u6237 id\uFF08\u53D6\u81EA\u4E0A\u4E0B\u6587\u91CC\u7684\u300C\u88AB @ \u7684\u4EBA\u300D\uFF09\uFF0C\u7559\u7A7A\u6216\u5199 me/self \u8868\u793A\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1" }
|
|
273
|
+
},
|
|
274
|
+
required: []
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
},
|
|
261
278
|
{
|
|
262
279
|
type: "function",
|
|
263
280
|
function: {
|
|
@@ -266,8 +283,8 @@ function buildAgentTools() {
|
|
|
266
283
|
parameters: {
|
|
267
284
|
type: "object",
|
|
268
285
|
properties: {
|
|
269
|
-
//
|
|
270
|
-
prompt: { type: "string", description: "\u7ED8\u56FE\u63D0\u793A\u8BCD\u3002\u8BF4\u4EBA\u8BDD\
|
|
286
|
+
// 提示词要短;用户没说要润色就原封不动用他的原话
|
|
287
|
+
prompt: { type: "string", description: "\u7ED8\u56FE\u63D0\u793A\u8BCD\u3002**\u9ED8\u8BA4\u539F\u5C01\u4E0D\u52A8\u7528\u7528\u6237\u7684\u8BDD**\uFF1A\u7B80\u77ED\u3001\u8BF4\u4EBA\u8BDD\uFF0C\u4E0D\u8981\u6269\u5199\u6210\u51E0\u767E\u5B57\uFF0C\u4E0D\u8981\u8865\u753B\u98CE/\u5149\u7EBF/\u6750\u8D28\u672F\u8BED\uFF0C\u4E0D\u8981\u7FFB\u6210\u82F1\u6587\uFF1B\u53EA\u6709\u7528\u6237\u8BF4\u8FC7\u300C\u5E2E\u6211\u6DA6\u8272 / \u4F18\u5316\u4E00\u4E0B\u300D\u624D\u52A0\u5DE5\u3002\u4E2D\u6587\u5373\u53EF\uFF08\u7ED8\u56FE\u6A21\u578B\u542C\u5F97\u61C2\u4E2D\u6587\uFF09\u3002\u753B\u9762\u4E0A\u5FC5\u987B\u51FA\u73B0\u7684\u6587\u5B57\u8981\u539F\u6837\u5199\u8FDB\u53BB" },
|
|
271
288
|
references: {
|
|
272
289
|
type: "array",
|
|
273
290
|
items: { type: "string" },
|
|
@@ -346,6 +363,53 @@ function cleanAskMessage(raw, max = 200) {
|
|
|
346
363
|
if (text.length > max) text = text.slice(0, max) + "\u2026";
|
|
347
364
|
return text;
|
|
348
365
|
}
|
|
366
|
+
function parsePolishIntent(text) {
|
|
367
|
+
const t = String(text || "");
|
|
368
|
+
if (!t) return false;
|
|
369
|
+
if (/不(用|要|必)?(润色|优化|加工|改)|别(润色|优化|加工)|原样|原封不动|直接画|不用了/.test(t)) return false;
|
|
370
|
+
return /润色|优化一下|加工|丰富|扩写|细化|详细[一点些]|写(得)?(更)?(完整|详细)|帮我(写|改)/.test(t);
|
|
371
|
+
}
|
|
372
|
+
function extractAtIds(content, selfId) {
|
|
373
|
+
if (!content) return [];
|
|
374
|
+
const out = [];
|
|
375
|
+
const seen = /* @__PURE__ */ new Set();
|
|
376
|
+
for (const el of import_koishi.h.select(content, "at")) {
|
|
377
|
+
const id = String(el.attrs.id ?? el.attrs.user ?? "").trim();
|
|
378
|
+
if (!id || id === selfId) continue;
|
|
379
|
+
if (String(el.attrs.type || "") === "all") continue;
|
|
380
|
+
if (seen.has(id)) continue;
|
|
381
|
+
seen.add(id);
|
|
382
|
+
out.push({ id, name: String(el.attrs.name || el.attrs.nick || "").trim() });
|
|
383
|
+
}
|
|
384
|
+
return out;
|
|
385
|
+
}
|
|
386
|
+
function buildAvatarUrl(input) {
|
|
387
|
+
const userId = String(input?.userId || "").trim();
|
|
388
|
+
if (!userId) return "";
|
|
389
|
+
const size = Number(input.size) > 0 ? Number(input.size) : 640;
|
|
390
|
+
const platform = String(input.platform || "");
|
|
391
|
+
const appId = String(input.appId || "");
|
|
392
|
+
if (input.template) {
|
|
393
|
+
return String(input.template).replace(/\{(\w+)\}/g, (_m, key) => {
|
|
394
|
+
const vars = {
|
|
395
|
+
userId,
|
|
396
|
+
platform,
|
|
397
|
+
size: String(size),
|
|
398
|
+
selfId: String(input.selfId || ""),
|
|
399
|
+
appId
|
|
400
|
+
};
|
|
401
|
+
return vars[key] ?? "";
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
if (input.authorAvatar) return String(input.authorAvatar);
|
|
405
|
+
if (appId && (platform === "qq" || platform === "qqguild")) {
|
|
406
|
+
return `https://q.qlogo.cn/qqapp/${appId}/${userId}/${size}`;
|
|
407
|
+
}
|
|
408
|
+
if (/^(qq|onebot|red|qqguild)$/.test(platform) && /^\d{5,12}$/.test(userId)) {
|
|
409
|
+
return `https://q1.qlogo.cn/g?b=qq&nk=${userId}&s=${size}`;
|
|
410
|
+
}
|
|
411
|
+
return "";
|
|
412
|
+
}
|
|
349
413
|
function trimGallery(records, capacity) {
|
|
350
414
|
const limit = capacity > 0 ? capacity : 50;
|
|
351
415
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -721,6 +785,7 @@ var DEFAULT_AGENT_INSTRUCTIONS = `\u4F60\u662F QQ \u7FA4\u91CC\u7684\u7ED8\u56FE
|
|
|
721
785
|
- gallery_search\uFF1A\u6309\u5173\u952E\u8BCD\u641C\u7D22\u53C2\u8003\u56FE\u5E93\u3002**\u6D89\u53CA\u5177\u4F53\u89D2\u8272/\u7ACB\u7ED8\u65F6\u5FC5\u987B\u5148\u641C\u4E00\u6B21**\u3002
|
|
722
786
|
\u591A\u4E2A\u5173\u952E\u8BCD\u7528\u7A7A\u683C\u5206\u9694\uFF08\u5982\u300C\u767D\u53D1 \u7ACB\u7ED8\u300D\uFF09\uFF0C\u5168\u90E8\u547D\u4E2D\u7684\u6392\u5728\u6700\u524D\uFF1B\u641C\u4E0D\u5230\u5C31\u6362\u4E2A\u8BCD\u518D\u641C\u3002
|
|
723
787
|
- ask_user\uFF1A\u628A\u95EE\u9898\u53D1\u7ED9\u7528\u6237**\u5E76\u7B49\u4ED6\u56DE\u590D**\uFF08\u4ED6\u53D1\u7684\u56FE\u4E5F\u4F1A\u4E00\u8D77\u5E26\u56DE\u6765\uFF09\u3002
|
|
788
|
+
- get_avatar\uFF1A\u53D6\u67D0\u4E2A\u4EBA\u7684\u5934\u50CF\u5F53\u53C2\u8003\u56FE\u3002userId \u7559\u7A7A\u53D6\u53D1\u6307\u4EE4\u8005\u81EA\u5DF1\uFF1B\u4E5F\u53EF\u4EE5\u53D6\u88AB @ \u7684\u4EBA\u7684\u5934\u50CF\u3002
|
|
724
789
|
- draw\uFF1A\u771F\u6B63\u5F00\u59CB\u753B\u3002\u628A prompt \u548C\u6311\u4E2D\u7684\u53C2\u8003\u56FE\u7F16\u53F7\u4EA4\u8FDB\u53BB\uFF0C\u753B\u597D\u4F1A\u81EA\u52A8\u53D1\u5230\u7FA4\u91CC\u3002
|
|
725
790
|
|
|
726
791
|
\u5DE5\u4F5C\u89C4\u5219\uFF1A
|
|
@@ -735,25 +800,39 @@ var DEFAULT_AGENT_INSTRUCTIONS = `\u4F60\u662F QQ \u7FA4\u91CC\u7684\u7ED8\u56FE
|
|
|
735
800
|
4. \u753B\u9762\u4E0A\u8981\u51FA\u73B0\u6587\u5B57\uFF08\u53F0\u8BCD\u3001\u6807\u9898\u3001\u62DB\u724C\u3001\u5B57\u5E55\uFF09\u65F6\uFF0C\u628A\u6587\u5B57\u539F\u6837\u5199\u8FDB prompt\uFF0C\u5E76\u8BF4\u660E\u300C\u8FD9\u4E9B\u5B57\u5FC5\u987B\u539F\u6837\u51FA\u73B0\uFF0C\u4E0D\u80FD\u53D8\u5F62\u6216\u81EA\u521B\u5B57\u5F62\u300D\u3002
|
|
736
801
|
5. draw \u8FD4\u56DE ok=false \u65F6\uFF0C\u770B error \u51B3\u5B9A\uFF1A\u80FD\u6539\u7684\uFF08\u63D0\u793A\u8BCD\u95EE\u9898\uFF09\u5C31\u6362\u4E2A\u5199\u6CD5\u91CD\u8BD5\uFF0C\u6539\u4E0D\u4E86\u7684\uFF08\u9650\u6D41/\u914D\u989D\uFF09\u5C31\u5982\u5B9E\u544A\u8BC9\u7528\u6237\u3002
|
|
737
802
|
6. \u6D41\u7A0B\u7ED3\u675F\u540E\u7528\u4E2D\u6587\u56DE\u4E00\u53E5\u7B80\u77ED\u7684\u7ED3\u679C\u8BF4\u660E\uFF08\u4E0D\u8981\u590D\u8FF0 prompt\uFF0C\u4E0D\u8981\u5199\u5C0F\u4F5C\u6587\uFF0C\u4E0D\u8981\u518D\u95EE\u300C\u8FD8\u8981\u6211\u505A\u4EC0\u4E48\u300D\uFF09\u3002
|
|
738
|
-
7. \u56FE\u7247\u7F16\u53F7\uFF1Agallery_search \u7ED3\u679C\u91CC\u7684 id\u3001\u7528\u6237\u53D1\u7684\u56FE\
|
|
739
|
-
\u53EA\u586B\
|
|
740
|
-
|
|
803
|
+
7. \u56FE\u7247\u7F16\u53F7\uFF1Agallery_search \u7ED3\u679C\u91CC\u7684 id\u3001\u7528\u6237\u968F\u6D88\u606F\u53D1\u7684\u56FE\u3001\u5934\u50CF\u3001\u6307\u4EE4\u9ED8\u8BA4\u56FE\u90FD\u53EF\u4EE5\u76F4\u63A5\u5199\u8FDB draw \u7684 references\u3002
|
|
804
|
+
\u53EA\u586B\u4E0A\u4E0B\u6587\u91CC**\u660E\u786E\u5217\u51FA\u8FC7**\u7684\u7F16\u53F7\uFF0C**\u7EDD\u5BF9\u4E0D\u8981\u7F16\u9020 id**\u3002
|
|
805
|
+
- \u7528\u6237\u968F\u6D88\u606F\u53D1\u7684\u56FE\u4F1A\u81EA\u52A8\u5E26\u4E0A\uFF0C\u4E0D\u7528\u4F60\u518D\u586B\uFF1B
|
|
806
|
+
- \u4ED6 @ \u4E86\u8C01\uFF0C\u90A3\u4E2A\u4EBA\u7684\u5934\u50CF\u4E5F\u4F1A\u81EA\u52A8\u5E26\u4E0A\u3002
|
|
807
|
+
8. \u3010\u6CA1\u6307\u5B9A\u53C2\u8003\u56FE\u65F6\u5148\u95EE\u4E00\u53E5\u3011\u7528\u6237\u8FD9\u6B21\u6CA1\u53D1\u56FE\u3001\u6CA1 @ \u4EBA\u3001\u6307\u4EE4\u672C\u8EAB\u4E5F\u6CA1\u6709\u9ED8\u8BA4\u53C2\u8003\u56FE\uFF0C\u4F60\u4E5F\u641C\u4E0D\u5230\u5408\u9002\u7684\u7ACB\u7ED8\u65F6\uFF0C
|
|
808
|
+
**\u4E0D\u8981\u76F4\u63A5 draw**\u3002\u5148\u7528 ask_user \u95EE\u4E00\u53E5\u8981\u4E0D\u8981\u53C2\u8003\u56FE\uFF0C\u5E76\u7ED9\u51FA\u9009\u9879\uFF08\u4F8B\u5982\u300C\u7528\u4F60\u81EA\u5DF1\u7684\u5934\u50CF / \u7528 @\u67D0\u67D0 \u7684\u5934\u50CF /
|
|
809
|
+
\u6211\u5E2E\u4F60\u4ECE\u56FE\u5E93\u627E\u4E00\u5F20 / \u4E0D\u7528\u53C2\u8003\u76F4\u63A5\u753B\u300D\uFF09\u3002\u7528\u6237\u8BF4\u300C\u4E0D\u7528 / \u968F\u4FBF / \u76F4\u63A5\u753B\u300D\u5C31\u7ACB\u523B\u5F00\u753B\uFF0C**\u4E0D\u8981\u95EE\u7B2C\u4E8C\u6B21**\u3002
|
|
810
|
+
9. \u3010\u8981\u753B\u300C\u6211\u300D\u5C31\u7528\u5934\u50CF\u3011\u7528\u6237\u8BF4\u300C\u753B\u6211\u300D\u300C\u7528\u6211\u7684\u5F62\u8C61\u300D\u300C\u753B\u6211\u548C xxx\u300D\u5374\u6CA1\u7ED9\u56FE\u65F6\uFF0C\u7528 get_avatar \u53D6\u4ED6\u81EA\u5DF1\u7684\u5934\u50CF\uFF1B
|
|
811
|
+
\u8BF4\u300C\u753B @\u67D0\u67D0\u300D\u65F6\u53D6\u90A3\u4E2A\u4EBA\u7684\u5934\u50CF\uFF08\u4E00\u822C\u5DF2\u7ECF\u81EA\u52A8\u5E26\u4E0A\u4E86\uFF0C\u76F4\u63A5\u7528\u90A3\u4E2A\u7F16\u53F7\u5373\u53EF\uFF09\u3002
|
|
812
|
+
10. \u3010\u9ED8\u8BA4\u4E0D\u6DA6\u8272\uFF0C\u7528\u6237\u70B9\u5934\u624D\u6DA6\u8272\u3011\u628A\u7528\u6237\u7684\u8BDD**\u539F\u5C01\u4E0D\u52A8**\u4EA4\u7ED9\u7ED8\u56FE\u6A21\u578B\u5C31\u662F\u6700\u597D\u7684\u9ED8\u8BA4\u503C\uFF1A
|
|
813
|
+
- **\u4E0D\u8981**\u4E3B\u52A8\u6269\u5199\u3001\u4E0D\u8981\u8865\u753B\u98CE/\u5149\u7EBF/\u6750\u8D28/\u955C\u5934\u3001\u4E0D\u8981\u7FFB\u6210\u82F1\u6587\u3001\u4E0D\u8981\u52A0\u300Cmasterpiece\u300D\u8FD9\u7C7B\u8BCD\uFF1B
|
|
814
|
+
- \u7528\u6237\u660E\u786E\u8BF4\u300C\u5E2E\u6211\u6DA6\u8272 / \u4F18\u5316\u4E00\u4E0B / \u5199\u8BE6\u7EC6\u70B9\u300D\u65F6\uFF0C\u4F60\u624D\u628A\u63D0\u793A\u8BCD\u5199\u5F97\u66F4\u5B8C\u6574\uFF1B
|
|
815
|
+
- draw \u5DE5\u5177\u8FD4\u56DE asked=true \u65F6\u770B\u5B83\u7ED9\u7684 polish \u5B57\u6BB5\uFF1Apolish=true \u624D\u6DA6\u8272\uFF0Cfalse \u6216\u6CA1\u7ED9\u5C31**\u4E00\u4E2A\u5B57\u90FD\u522B\u6539**\uFF1B
|
|
741
816
|
- \u7528\u6237\u7528\u4E2D\u6587\u63D0\u7684\u9700\u6C42\uFF0Cprompt \u5C31\u5199\u4E2D\u6587\uFF08\u7ED8\u56FE\u6A21\u578B\u542C\u5F97\u61C2\u4E2D\u6587\uFF0C\u7406\u89E3\u529B\u4E5F\u591F\u7528\uFF09\uFF1B
|
|
742
|
-
- \u522B\u4E3A\u4E86\u663E\u5F97\u4E13\u4E1A\u5C31\u53BB\u5806\u98CE\u683C / \u6750\u8D28 / \u5149\u7EBF / \u955C\u5934\u672F\u8BED\uFF0C\u66F4\u522B\u523B\u610F\u7FFB\u6210\u82F1\u6587\uFF1B
|
|
743
817
|
- \u53EA\u4FDD\u7559\u7528\u6237\u771F\u6B63\u8BF4\u8FC7\u7684\u5185\u5BB9 + \u753B\u9762\u5FC5\u987B\u6709\u7684\u8981\u7D20\uFF0C**\u4E0D\u8981\u66FF\u4ED6\u8111\u8865\u7EC6\u8282**\uFF1B
|
|
744
818
|
- \u63D0\u793A\u8BCD\u8D8A\u957F\uFF0C\u5404\u8981\u7D20\u4E92\u76F8\u7A00\u91CA\uFF0C\u51FA\u56FE\u53CD\u800C\u8D8A\u8D70\u6837\u3002\u4E00\u4E24\u53E5\u8BDD\u80FD\u628A\u753B\u9762\u8BF4\u6E05\u5C31\u591F\u4E86\u3002
|
|
745
819
|
|
|
746
820
|
\u5982\u679C\u4F60\u6240\u5728\u7684\u63A5\u53E3\u4E0D\u652F\u6301\u51FD\u6570\u8C03\u7528\uFF0C\u5C31**\u53EA\u8F93\u51FA\u4E00\u884C JSON** \u6765\u8C03\u7528\u5DE5\u5177\uFF0C\u4F8B\u5982\uFF1A
|
|
747
821
|
{"tool":"gallery_search","args":{"keyword":"\u767D\u53D1 \u7ACB\u7ED8"}}
|
|
748
822
|
{"tool":"ask_user","args":{"question":"\u60F3\u8981\u4EC0\u4E48\u753B\u98CE\uFF1F"}}
|
|
823
|
+
{"tool":"get_avatar","args":{"userId":""}}
|
|
749
824
|
{"tool":"draw","args":{"prompt":"...","references":["ref1","user1"]}}
|
|
750
825
|
\u4E0D\u60F3\u8C03\u7528\u5DE5\u5177\u65F6\uFF0C\u6B63\u5E38\u8F93\u51FA\u7ED9\u7528\u6237\u770B\u7684\u4E2D\u6587\u5373\u53EF\u3002`;
|
|
751
|
-
var AGENT_CONFIRM_RULE_ON = `**\u5FC5\u987B\u5148 ask_user**\uFF1A\u7528\u4E00\u53E5\u8BDD\u544A\u8BC9\u7528\u6237\u300C\u8FD9\u6B21\u4F1A\u53C2\u8003\u54EA\u51E0\u5F20\u56FE + \u5927\u81F4\u753B\u6210\u4EC0\u4E48\u6837\u300D\uFF0C
|
|
826
|
+
var AGENT_CONFIRM_RULE_ON = `**\u5FC5\u987B\u5148 ask_user**\uFF1A\u7528\u4E00\u53E5\u8BDD\u544A\u8BC9\u7528\u6237\u300C\u8FD9\u6B21\u4F1A\u53C2\u8003\u54EA\u51E0\u5F20\u56FE + \u5927\u81F4\u753B\u6210\u4EC0\u4E48\u6837\u300D\uFF0C
|
|
827
|
+
\u5E76\u987A\u5E26\u95EE\u4E00\u53E5\u300C\u63D0\u793A\u8BCD\u8981\u4E0D\u8981\u6211\u5E2E\u4F60\u6DA6\u8272\uFF08\u9ED8\u8BA4\u76F4\u63A5\u7528\u4F60\u7684\u539F\u8BDD\uFF09\u300D\uFF0C\u7528\u6237\u70B9\u5934\u540E\u624D\u80FD\u8C03\u7528 draw\u3002
|
|
828
|
+
- \u7528\u6237\u8BF4\u300C\u6DA6\u8272 / \u4F18\u5316 / \u4E30\u5BCC\u4E00\u4E0B\u300D\u2192 \u4F60\u518D\u628A\u63D0\u793A\u8BCD\u5199\u5F97\u66F4\u5B8C\u6574\uFF1B
|
|
829
|
+
- \u7528\u6237\u8BF4\u300C\u76F4\u63A5\u753B / \u4E0D\u7528 / \u539F\u6837\u300D\u6216\u6CA1\u63D0\u8FD9\u4EF6\u4E8B \u2192 **\u539F\u5C01\u4E0D\u52A8\u7528\u4ED6\u7684\u539F\u8BDD**\uFF0C\u4E00\u4E2A\u5B57\u90FD\u522B\u6DA6\u8272\u3002
|
|
752
830
|
\u7528\u6237\u56DE\u590D\u4E86\u5426\u5B9A\u6216\u63D0\u51FA\u4FEE\u6539\u610F\u89C1\uFF0C\u5C31\u6309\u4ED6\u8BF4\u7684\u6539\uFF0C\u518D\u786E\u8BA4\u4E00\u6B21\u6216\u76F4\u63A5\u753B\uFF08\u4ED6\u8BF4\u300C\u76F4\u63A5\u753B\u300D\u5C31\u4E0D\u7528\u518D\u95EE\uFF09\u3002`;
|
|
753
831
|
var AGENT_CONFIRM_RULE_OFF = `\u7528\u6237\u9700\u6C42\u5DF2\u7ECF\u660E\u786E\u7684\uFF0C\u76F4\u63A5\u8C03\u7528 draw\uFF0C\u4E0D\u7528\u591A\u95EE\u4E00\u53E5\u3002\u53EA\u6709\u771F\u7684\u7F3A\u5173\u952E\u4FE1\u606F\u624D ask_user\u3002`;
|
|
754
832
|
var AGENT_JSON_FALLBACK_RULE = `\uFF08\u6CE8\u610F\uFF1A\u5F53\u524D\u63A5\u53E3\u4E0D\u652F\u6301\u51FD\u6570\u8C03\u7528\uFF09\u8BF7\u4F60**\u53EA\u7528\u4E00\u884C JSON** \u6765\u8C03\u7528\u5DE5\u5177\uFF0C\u4E0D\u8981\u8F93\u51FA\u522B\u7684\u5185\u5BB9\uFF1A
|
|
755
833
|
{"tool":"gallery_search","args":{"keyword":"\u767D\u53D1 \u7ACB\u7ED8"}}
|
|
756
834
|
{"tool":"ask_user","args":{"question":"\u60F3\u8981\u4EC0\u4E48\u753B\u98CE\uFF1F"}}
|
|
835
|
+
{"tool":"get_avatar","args":{"userId":""}}
|
|
757
836
|
{"tool":"draw","args":{"prompt":"...","references":["ref1","ref2"]}}
|
|
758
837
|
\u4E0D\u9700\u8981\u8C03\u7528\u5DE5\u5177\u65F6\uFF0C\u6B63\u5E38\u8F93\u51FA\u7ED9\u7528\u6237\u770B\u7684\u4E2D\u6587\u5373\u53EF\u3002`;
|
|
759
838
|
var defaultCommands = [
|
|
@@ -1299,9 +1378,20 @@ var Config = import_koishi.Schema.intersect([
|
|
|
1299
1378
|
model: import_koishi.Schema.string().description("agent \u4E13\u7528\u7684\u6A21\u578B\uFF08\u7559\u7A7A\u5219\u7528\u300CAI \u6A21\u578B\u63A5\u53E3\u300D\u91CC\u7684\u6A21\u578B\uFF09\u3002**\u9700\u8981\u652F\u6301 function calling**\uFF1B\u4E0D\u652F\u6301\u65F6\u63D2\u4EF6\u4F1A\u81EA\u52A8\u6539\u7528\u300C\u4E00\u884C JSON\u300D\u534F\u8BAE"),
|
|
1300
1379
|
baseUrl: import_koishi.Schema.string().role("link").description("agent \u4E13\u7528\u63A5\u53E3\u5730\u5740\uFF08\u7559\u7A7A\u5219\u590D\u7528\u300CAI \u6A21\u578B\u63A5\u53E3\u300D\u7684\u5730\u5740\uFF09"),
|
|
1301
1380
|
apiKey: import_koishi.Schema.string().role("secret").description("agent \u4E13\u7528\u5BC6\u94A5\uFF08\u7559\u7A7A\u5219\u590D\u7528\u300CAI \u6A21\u578B\u63A5\u53E3\u300D\u7684\u5BC6\u94A5\uFF09"),
|
|
1302
|
-
instructions: import_koishi.Schema.string().role("textarea", { rows: [16, 8] }).default(DEFAULT_AGENT_INSTRUCTIONS).description("\u9A71\u52A8 agent \u884C\u4E3A\u7684\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF08\u53EA\u5199\u300C\u6709\u54EA\u4E9B\u5DE5\u5177 + \u4EC0\u4E48\u65F6\u5019\u7528\u300D\uFF0C\u522B\u5199\u6B7B\u6D41\u7A0B\uFF09\u3002\u53EF\u7528\u5360\u4F4D\u7B26\uFF1A{confirmRule} \u5F00\u753B\u786E\u8BA4\u89C4\u5219\u3001{command} \u6307\u4EE4\u540D\u3001{prompt} \u6307\u4EE4\u63D0\u793A\u8BCD\u3001{userInput} \u7528\u6237\u9644\u52A0\u9700\u6C42\u3001{imageCount} \u7528\u6237\u968F\u6D88\u606F\u53D1\u7684\u56FE\u6570\u91CF\u3001{refCount} \u53EF\u68C0\u7D22\u7684\u53C2\u8003\u56FE\u6570\u91CF\u3002\u6307\u4EE4\u540D/\u63D0\u793A\u8BCD\u7B49\u4E0A\u4E0B\u6587\u4F1A\u81EA\u52A8\u9644\u5728\u63D0\u793A\u8BCD\u672B\u5C3E\uFF0C\u4E0D\u7528\u81EA\u5DF1\u5F15\u7528")
|
|
1381
|
+
instructions: import_koishi.Schema.string().role("textarea", { rows: [16, 8] }).default(DEFAULT_AGENT_INSTRUCTIONS).description("\u9A71\u52A8 agent \u884C\u4E3A\u7684\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF08\u53EA\u5199\u300C\u6709\u54EA\u4E9B\u5DE5\u5177 + \u4EC0\u4E48\u65F6\u5019\u7528\u300D\uFF0C\u522B\u5199\u6B7B\u6D41\u7A0B\uFF09\u3002\u53EF\u7528\u5360\u4F4D\u7B26\uFF1A{confirmRule} \u5F00\u753B\u786E\u8BA4\u89C4\u5219\u3001{command} \u6307\u4EE4\u540D\u3001{prompt} \u6307\u4EE4\u63D0\u793A\u8BCD\u3001{userInput} \u7528\u6237\u9644\u52A0\u9700\u6C42\u3001{imageCount} \u7528\u6237\u968F\u6D88\u606F\u53D1\u7684\u56FE\u6570\u91CF\u3001{refCount} \u53EF\u68C0\u7D22\u7684\u53C2\u8003\u56FE\u6570\u91CF\u3002\u6307\u4EE4\u540D/\u63D0\u793A\u8BCD\u7B49\u4E0A\u4E0B\u6587\u4F1A\u81EA\u52A8\u9644\u5728\u63D0\u793A\u8BCD\u672B\u5C3E\uFF0C\u4E0D\u7528\u81EA\u5DF1\u5F15\u7528"),
|
|
1382
|
+
askIfNoReference: import_koishi.Schema.boolean().default(true).description("\u7528\u6237\u8FD9\u6B21\u65E2\u6CA1\u53D1\u56FE\u3001\u4E5F\u6CA1 @ \u4EBA\u3001\u6307\u4EE4\u672C\u8EAB\u4E5F\u6CA1\u6709\u9ED8\u8BA4\u53C2\u8003\u56FE\u65F6\uFF0C\u5F00\u753B\u524D\u5148\u95EE\u4E00\u53E5\u300C\u8981\u4E0D\u8981\u53C2\u8003\u56FE\u300D\uFF08\u53EF\u4EE5\u7528\u4ED6\u81EA\u5DF1\u7684\u5934\u50CF / @ \u522B\u4EBA\u7684\u5934\u50CF\uFF09\u3002\u8FD9\u6761\u662F\u63D2\u4EF6\u515C\u5E95\u6267\u884C\u7684\uFF0C\u6A21\u578B\u81EA\u5DF1\u5148\u95EE\u8FC7\u5C31\u4E0D\u518D\u95EE"),
|
|
1383
|
+
askBeforePolish: import_koishi.Schema.boolean().default(true).description("\u5F00\u753B\u524D\u5148\u95EE\u7528\u6237\u300C\u63D0\u793A\u8BCD\u8981\u4E0D\u8981\u6211\u5E2E\u4F60\u6DA6\u8272\u300D\u3002**\u9ED8\u8BA4\u662F\u539F\u5C01\u4E0D\u52A8\u628A\u7528\u6237\u7684\u539F\u8BDD\u4EA4\u7ED9\u7ED8\u56FE\u6A21\u578B**\uFF0C\u53EA\u6709\u7528\u6237\u8BF4\u300C\u6DA6\u8272 / \u4F18\u5316\u300D\u624D\u52A0\u5DE5\u3002\u8FD9\u6761\u540C\u6837\u662F\u63D2\u4EF6\u515C\u5E95\uFF0C\u6A21\u578B\u81EA\u5DF1\u5148\u95EE\u8FC7\u5C31\u4E0D\u518D\u95EE")
|
|
1303
1384
|
}).collapse().description("Agent \u914D\u7F6E\uFF08\u6307\u4EE4\u8F83\u957F\uFF0C\u5DF2\u6298\u53E0\uFF09")
|
|
1304
1385
|
}).description("Agent \u6A21\u5F0F"),
|
|
1386
|
+
import_koishi.Schema.object({
|
|
1387
|
+
avatar: import_koishi.Schema.object({
|
|
1388
|
+
enabled: import_koishi.Schema.boolean().default(true).description("\u5F00\u542F\u5934\u50CF\u53C2\u8003\u56FE\uFF1A\u53EF\u4EE5\u628A\u7FA4\u53CB\u7684\u5934\u50CF\u5F53\u53C2\u8003\u56FE\u6765\u753B"),
|
|
1389
|
+
autoAt: import_koishi.Schema.boolean().default(true).description("\u7528\u6237 @ \u4E86\u8C01\uFF0C\u5C31\u81EA\u52A8\u628A\u90A3\u4E2A\u4EBA\u7684\u5934\u50CF\u4F5C\u4E3A\u53C2\u8003\u56FE\uFF08@ \u672C\u8EAB\u5C31\u662F\u660E\u786E\u7684\u6307\u5B9A\uFF09"),
|
|
1390
|
+
autoSelf: import_koishi.Schema.boolean().default(false).description("\u7528\u6237\u6CA1\u53D1\u56FE\u4E5F\u6CA1 @ \u4EBA\u65F6\uFF0C\u81EA\u52A8\u62FF\u4ED6\u81EA\u5DF1\u7684\u5934\u50CF\u5F53\u53C2\u8003\u56FE\u3002\u5173\u6389\u4E5F\u6CA1\u5173\u7CFB\uFF0C\u300C\u7528\u6211\u7684\u5934\u50CF\u753B\u300D\u8FD9\u7C7B\u9700\u6C42 agent \u4F1A\u81EA\u5DF1\u8C03\u7528 get_avatar"),
|
|
1391
|
+
size: import_koishi.Schema.number().default(640).min(64).max(1024).step(64).description("\u5934\u50CF\u8FB9\u957F\uFF08\u50CF\u7D20\uFF09"),
|
|
1392
|
+
urlTemplate: import_koishi.Schema.string().default("").description("\u81EA\u5B9A\u4E49\u5934\u50CF\u5730\u5740\u6A21\u677F\uFF0C\u7559\u7A7A\u7528\u5185\u7F6E\u89C4\u5219\u3002\u5360\u4F4D\u7B26\uFF1A{userId} \u7528\u6237 id\u3001{platform} \u5E73\u53F0\u3001{selfId} \u673A\u5668\u4EBA id\u3001{appId} \u673A\u5668\u4EBA appid\u3001{size} \u5C3A\u5BF8")
|
|
1393
|
+
}).description("\u5934\u50CF\u53C2\u8003\u56FE")
|
|
1394
|
+
}).description("\u5934\u50CF\u53C2\u8003\u56FE"),
|
|
1305
1395
|
import_koishi.Schema.object({
|
|
1306
1396
|
appendUserInput: import_koishi.Schema.boolean().default(true).description("\u628A\u7528\u6237\u968F\u6307\u4EE4\u53D1\u7684\u9644\u52A0\u9700\u6C42\u5E76\u5165\u7ED8\u56FE\u63D0\u793A\u8BCD\uFF08\u4F8B\u5982\u300C\u624B\u529E\u5316 xxx \u5728\u5077\u5403\u767D\u996D\u88AB\u53D1\u73B0\u7684\u8868\u60C5\u300D\uFF09"),
|
|
1307
1397
|
promptOptimize: import_koishi.Schema.union([
|
|
@@ -1470,6 +1560,8 @@ function apply(ctx, config) {
|
|
|
1470
1560
|
needimages: "\u8BF7\u63D0\u4F9B\u81F3\u5C11\u4E00\u5F20\u56FE\u7247",
|
|
1471
1561
|
agentfailed: "\u7ED8\u56FE\u52A9\u624B\u8FD9\u6B21\u6CA1\u8DD1\u8D77\u6765\uFF08{0}\uFF09\u3002\u53EF\u4EE5\u5148\u5173\u6389\u300CAgent \u6A21\u5F0F\u300D\u7167\u5E38\u753B\u56FE\uFF0C\u6216\u6362\u4E00\u4E2A\u652F\u6301\u5DE5\u5177\u8C03\u7528\u7684\u6A21\u578B\u3002",
|
|
1472
1562
|
agentnoresult: "\u8FD9\u6B21\u6CA1\u80FD\u7406\u51FA\u4E2A\u7ED3\u679C\u6765\uFF0C\u9EBB\u70E6\u518D\u8BF4\u4E00\u6B21\u4F60\u60F3\u753B\u4EC0\u4E48\uFF5E",
|
|
1563
|
+
askreference: "\u8FD9\u6B21\u6CA1\u6709\u6307\u5B9A\u53C2\u8003\u56FE\u3002\u8981\u53C2\u8003\u56FE\u7247\u5417\uFF1F\u53EF\u4EE5\u76F4\u63A5\u53D1\u56FE\u3001@ \u4E00\u4E2A\u4EBA\u7528\u4ED6\u7684\u5934\u50CF\uFF0C\u6216\u8005\u8BF4\u300C\u7528\u6211\u7684\u5934\u50CF\u300D\uFF1B\u4E0D\u9700\u8981\u5C31\u56DE\u300C\u76F4\u63A5\u753B\u300D\u3002\n\u53E6\u5916\uFF0C\u63D0\u793A\u8BCD\u8981\u6211\u5E2E\u4F60\u6DA6\u8272\u4E00\u4E0B\u5417\uFF1F\u9ED8\u8BA4\u76F4\u63A5\u7528\u4F60\u7684\u539F\u8BDD\u3002",
|
|
1564
|
+
askpolish: "\u8FD9\u6B21\u76F4\u63A5\u6309\u4F60\u7684\u539F\u8BDD\u5F00\u753B\u3002\u8981\u6211\u5148\u5E2E\u4F60\u6DA6\u8272\u4E00\u4E0B\u63D0\u793A\u8BCD\u5417\uFF1F\u56DE\u300C\u6DA6\u8272\u300D\u6211\u52A0\u5DE5\u4E00\u4E0B\uFF0C\u56DE\u300C\u76F4\u63A5\u753B\u300D\u5C31\u539F\u6837\u4EA4\u7ED9\u7ED8\u56FE\u6A21\u578B\u3002",
|
|
1473
1565
|
optimizefailed: "\uFF08\u63D0\u793A\u8BCD\u878D\u5408\u5931\u8D25\uFF0C\u5DF2\u6539\u4E3A\u8FFD\u52A0\u6A21\u5F0F\uFF09",
|
|
1474
1566
|
bgstart: "\u5DF2\u5F00\u59CB\u540E\u53F0\u7ED8\u56FE\uFF08{0} \u5F20\u56FE\uFF09\uFF0C\u753B\u597D\u540E\u4F1A\u5728\u8FD9\u91CC\u901A\u77E5\u4F60...",
|
|
1475
1567
|
bgqueue: "\uFF08\u5F53\u524D\u6709 {0} \u4E2A\u4EFB\u52A1\u5728\u6392\u961F\uFF09",
|
|
@@ -1538,7 +1630,28 @@ function apply(ctx, config) {
|
|
|
1538
1630
|
for (const item of pool) register("gallery", item.url, item.description, item.group);
|
|
1539
1631
|
const baseIds = (defaultImageUrls || []).filter(Boolean).map((url) => register("cmd", url, "", "\u6307\u4EE4\u9ED8\u8BA4\u56FE"));
|
|
1540
1632
|
const userIds = extractImagesFromSession(session).map((url) => register("user", url, "", "\u7528\u6237\u53D1\u9001"));
|
|
1541
|
-
|
|
1633
|
+
const avatarCfg = config.avatar || {};
|
|
1634
|
+
const avatarOn = avatarCfg.enabled !== false;
|
|
1635
|
+
const atTargets = avatarOn ? collectAtTargets(session) : [];
|
|
1636
|
+
let selfAvatarId = "";
|
|
1637
|
+
const atAvatarIds = [];
|
|
1638
|
+
const atAvatarById = /* @__PURE__ */ new Map();
|
|
1639
|
+
if (avatarOn) {
|
|
1640
|
+
const selfUrl = await fetchAvatarUrl(session, session.userId);
|
|
1641
|
+
if (selfUrl) {
|
|
1642
|
+
selfAvatarId = register("avatar", selfUrl, "\u53D1\u6307\u4EE4\u7684\u4EBA\u7684\u5934\u50CF", "\u5934\u50CF");
|
|
1643
|
+
}
|
|
1644
|
+
if (avatarCfg.autoAt !== false) {
|
|
1645
|
+
for (const target of atTargets) {
|
|
1646
|
+
const url = await fetchAvatarUrl(session, target.id);
|
|
1647
|
+
if (!url) continue;
|
|
1648
|
+
const id = register("avatar", url, `${target.name || target.id} \u7684\u5934\u50CF`, "\u5934\u50CF");
|
|
1649
|
+
if (!atAvatarIds.includes(id)) atAvatarIds.push(id);
|
|
1650
|
+
atAvatarById.set(target.id, id);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
logInfo2(`[${cmdConfig.name}] agent \u542F\u52A8\uFF1A\u53EF\u641C\u7D22\u53C2\u8003\u56FE ${pool.length} \u5F20\uFF0C\u9ED8\u8BA4\u56FE ${baseIds.length} \u5F20\uFF0C\u7528\u6237\u968F\u6D88\u606F\u53D1\u7684\u56FE ${userIds.length} \u5F20\uFF0C\u5934\u50CF ${atAvatarIds.length + (selfAvatarId ? 1 : 0)} \u5F20`);
|
|
1542
1655
|
const historyKey = agentHistoryKey(session);
|
|
1543
1656
|
const history = historyTurns > 0 ? (agentHistories.get(historyKey) || []).slice() : [];
|
|
1544
1657
|
const newHistory = [];
|
|
@@ -1547,7 +1660,13 @@ function apply(ctx, config) {
|
|
|
1547
1660
|
};
|
|
1548
1661
|
remember({ role: "user", content: userInputText || "\uFF08\u7528\u6237\u6CA1\u6709\u9644\u52A0\u8BF4\u660E\uFF09" });
|
|
1549
1662
|
const messages = [
|
|
1550
|
-
{ role: "system", content: buildAgentSystemPrompt(cmdConfig, userInputText,
|
|
1663
|
+
{ role: "system", content: buildAgentSystemPrompt(cmdConfig, userInputText, pool.length, {
|
|
1664
|
+
userIds,
|
|
1665
|
+
baseIds,
|
|
1666
|
+
atAvatarIds,
|
|
1667
|
+
selfAvatarId,
|
|
1668
|
+
atTargets
|
|
1669
|
+
}, agentCfg) },
|
|
1551
1670
|
...history,
|
|
1552
1671
|
{ role: "user", content: userInputText ? userInputText : "\uFF08\u7528\u6237\u6CA1\u6709\u9644\u52A0\u8BF4\u660E\uFF0C\u8BF7\u6309\u6307\u4EE4\u81EA\u5E26\u7684\u63D0\u793A\u8BCD\u6765\uFF09" }
|
|
1553
1672
|
];
|
|
@@ -1557,6 +1676,8 @@ function apply(ctx, config) {
|
|
|
1557
1676
|
let finalText = "";
|
|
1558
1677
|
let drew = false;
|
|
1559
1678
|
let failed = "";
|
|
1679
|
+
let askedOnce = false;
|
|
1680
|
+
let drawAsked = false;
|
|
1560
1681
|
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
1561
1682
|
if (!isActive || !ctx.scope.isActive) break;
|
|
1562
1683
|
const res = await requestAgentChat(messages, toolsSupported ? tools : void 0);
|
|
@@ -1607,10 +1728,12 @@ ${output}` }
|
|
|
1607
1728
|
return toolGallerySearch(args);
|
|
1608
1729
|
case "ask_user":
|
|
1609
1730
|
return toolAskUser(args);
|
|
1731
|
+
case "get_avatar":
|
|
1732
|
+
return toolGetAvatar(args);
|
|
1610
1733
|
case "draw":
|
|
1611
1734
|
return toolDraw(args);
|
|
1612
1735
|
default:
|
|
1613
|
-
return JSON.stringify({ ok: false, error: `\u6CA1\u6709\u53EB\u300C${name2}\u300D\u7684\u5DE5\u5177\uFF0C\u53EF\u7528\u7684\u662F gallery_search / ask_user / draw` });
|
|
1736
|
+
return JSON.stringify({ ok: false, error: `\u6CA1\u6709\u53EB\u300C${name2}\u300D\u7684\u5DE5\u5177\uFF0C\u53EF\u7528\u7684\u662F gallery_search / ask_user / get_avatar / draw` });
|
|
1614
1737
|
}
|
|
1615
1738
|
}
|
|
1616
1739
|
function toolGallerySearch(args) {
|
|
@@ -1639,9 +1762,30 @@ ${output}` }
|
|
|
1639
1762
|
}))
|
|
1640
1763
|
});
|
|
1641
1764
|
}
|
|
1765
|
+
async function toolGetAvatar(args) {
|
|
1766
|
+
if (!avatarOn) return JSON.stringify({ ok: false, error: "\u5934\u50CF\u53C2\u8003\u56FE\u529F\u80FD\u5DF2\u5173\u95ED" });
|
|
1767
|
+
const raw = String(args?.userId ?? args?.id ?? args?.user ?? "").trim();
|
|
1768
|
+
if (!raw || /^(me|self|自己|我)$/i.test(raw) || raw === session.userId) {
|
|
1769
|
+
if (selfAvatarId) {
|
|
1770
|
+
return JSON.stringify({ ok: true, id: selfAvatarId, userId: session.userId, note: "\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\u7684\u5934\u50CF" });
|
|
1771
|
+
}
|
|
1772
|
+
const url2 = await fetchAvatarUrl(session, session.userId);
|
|
1773
|
+
if (!url2) return JSON.stringify({ ok: false, error: "\u6CA1\u53D6\u5230\u4ED6\u81EA\u5DF1\u7684\u5934\u50CF\uFF08\u8FD9\u4E2A\u5E73\u53F0\u53EF\u80FD\u4E0D\u63D0\u4F9B\u5934\u50CF\u5730\u5740\uFF09" });
|
|
1774
|
+
selfAvatarId = register("avatar", url2, "\u53D1\u6307\u4EE4\u7684\u4EBA\u7684\u5934\u50CF", "\u5934\u50CF");
|
|
1775
|
+
return JSON.stringify({ ok: true, id: selfAvatarId, userId: session.userId, note: "\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\u7684\u5934\u50CF" });
|
|
1776
|
+
}
|
|
1777
|
+
const cached = atAvatarById.get(raw);
|
|
1778
|
+
if (cached) return JSON.stringify({ ok: true, id: cached, userId: raw, note: "\u88AB @ \u7684\u4EBA\u7684\u5934\u50CF" });
|
|
1779
|
+
const url = await fetchAvatarUrl(session, raw);
|
|
1780
|
+
if (!url) return JSON.stringify({ ok: false, error: `\u6CA1\u53D6\u5230 ${raw} \u7684\u5934\u50CF` });
|
|
1781
|
+
const id = register("avatar", url, `${raw} \u7684\u5934\u50CF`, "\u5934\u50CF");
|
|
1782
|
+
atAvatarById.set(raw, id);
|
|
1783
|
+
return JSON.stringify({ ok: true, id, userId: raw, note: "\u8FD9\u4E2A\u4EBA\u7684\u5934\u50CF" });
|
|
1784
|
+
}
|
|
1642
1785
|
async function toolAskUser(args) {
|
|
1643
1786
|
const question = cleanAskMessage(args?.question ?? args?.text ?? args?.message);
|
|
1644
1787
|
if (!question) return JSON.stringify({ ok: false, error: "question \u4E0D\u80FD\u4E3A\u7A7A" });
|
|
1788
|
+
askedOnce = true;
|
|
1645
1789
|
try {
|
|
1646
1790
|
await sendNotice(session, question);
|
|
1647
1791
|
} catch (error) {
|
|
@@ -1666,10 +1810,31 @@ ${output}` }
|
|
|
1666
1810
|
const ids = images.map((url) => register("user", url, "", "\u7528\u6237\u53D1\u9001"));
|
|
1667
1811
|
remember({ role: "user", content: text || (ids.length ? `\uFF08\u7528\u6237\u53D1\u4E86 ${ids.length} \u5F20\u56FE\u7247\uFF09` : "\uFF08\u7528\u6237\u4EC0\u4E48\u90FD\u6CA1\u8BF4\uFF09") });
|
|
1668
1812
|
logInfo2(`agent \u63D0\u95EE\u300C${question}\u300D-> \u7528\u6237\u56DE\u590D: ${text || "\uFF08\u65E0\u6587\u5B57\uFF09"}\uFF0C\u56FE\u7247 ${ids.length} \u5F20`);
|
|
1813
|
+
const avatars = [];
|
|
1814
|
+
if (avatarOn) {
|
|
1815
|
+
if (avatarCfg.autoAt !== false) {
|
|
1816
|
+
for (const target of extractAtIds(String(answer || ""), session.selfId)) {
|
|
1817
|
+
const cached = atAvatarById.get(target.id);
|
|
1818
|
+
if (cached) {
|
|
1819
|
+
if (!avatars.some((a) => a.id === cached)) avatars.push({ id: cached, note: `${target.name || target.id} \u7684\u5934\u50CF` });
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1822
|
+
const url = await fetchAvatarUrl(session, target.id);
|
|
1823
|
+
if (!url) continue;
|
|
1824
|
+
const id = register("avatar", url, `${target.name || target.id} \u7684\u5934\u50CF`, "\u5934\u50CF");
|
|
1825
|
+
atAvatarById.set(target.id, id);
|
|
1826
|
+
avatars.push({ id, note: `${target.name || target.id} \u7684\u5934\u50CF` });
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
if (/头像|我的形象|画我|我自己/.test(text) && selfAvatarId) {
|
|
1830
|
+
if (!avatars.some((a) => a.id === selfAvatarId)) avatars.push({ id: selfAvatarId, note: "\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\u7684\u5934\u50CF" });
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1669
1833
|
return JSON.stringify({
|
|
1670
1834
|
ok: true,
|
|
1671
1835
|
text,
|
|
1672
|
-
images: ids.map((id, i) => ({ id, note: `\u7528\u6237\u8FD9\u6B21\u53D1\u7684\u7B2C ${i + 1} \u5F20\u56FE` }))
|
|
1836
|
+
images: ids.map((id, i) => ({ id, note: `\u7528\u6237\u8FD9\u6B21\u53D1\u7684\u7B2C ${i + 1} \u5F20\u56FE` })),
|
|
1837
|
+
...avatars.length ? { avatars } : {}
|
|
1673
1838
|
});
|
|
1674
1839
|
}
|
|
1675
1840
|
async function toolDraw(args) {
|
|
@@ -1682,9 +1847,35 @@ ${output}` }
|
|
|
1682
1847
|
if (id && !wanted.includes(id)) wanted.push(id);
|
|
1683
1848
|
};
|
|
1684
1849
|
baseIds.forEach(pushId);
|
|
1850
|
+
userIds.forEach(pushId);
|
|
1851
|
+
atAvatarIds.forEach(pushId);
|
|
1852
|
+
if (avatarOn && avatarCfg.autoSelf) pushId(selfAvatarId);
|
|
1685
1853
|
ids.forEach(pushId);
|
|
1686
1854
|
const capped = wanted.slice(0, maxSelect);
|
|
1687
1855
|
const imageUrls = capped.map((id) => registry.get(id)?.url).filter(Boolean);
|
|
1856
|
+
const needRef = !capped.length && agentCfg.askIfNoReference !== false;
|
|
1857
|
+
const needPolish = agentCfg.askBeforePolish !== false;
|
|
1858
|
+
if (!drawAsked && !askedOnce && (needRef || needPolish)) {
|
|
1859
|
+
drawAsked = true;
|
|
1860
|
+
const asked = await toolAskUser({
|
|
1861
|
+
question: needRef ? session.text("image-prompt.messages.askreference") : session.text("image-prompt.messages.askpolish")
|
|
1862
|
+
});
|
|
1863
|
+
let parsed = {};
|
|
1864
|
+
try {
|
|
1865
|
+
parsed = JSON.parse(asked);
|
|
1866
|
+
} catch {
|
|
1867
|
+
parsed = {};
|
|
1868
|
+
}
|
|
1869
|
+
const polish = parsed.ok ? parsePolishIntent(parsed.text) : false;
|
|
1870
|
+
return JSON.stringify({
|
|
1871
|
+
ok: false,
|
|
1872
|
+
asked: true,
|
|
1873
|
+
polish,
|
|
1874
|
+
...parsed.images?.length ? { newImages: parsed.images } : {},
|
|
1875
|
+
...parsed.avatars?.length ? { newAvatars: parsed.avatars } : {},
|
|
1876
|
+
note: polish ? "\u5DF2\u7ECF\u66FF\u4F60\u95EE\u8FC7\u7528\u6237\u4E86\uFF1A\u4ED6\u8BF4\u8981\u6DA6\u8272\uFF0C\u53EF\u4EE5\u628A\u63D0\u793A\u8BCD\u5199\u5F97\u66F4\u5B8C\u6574\u3002\u4ED6\u7ED9\u4E86\u56FE/\u5934\u50CF\u4E5F\u8981\u5199\u8FDB references\u3002\u91CD\u65B0\u8C03\u7528 draw \u5F00\u753B\uFF0C\u4E0D\u8981\u518D\u95EE\u7B2C\u4E8C\u6B21" : "\u5DF2\u7ECF\u66FF\u4F60\u95EE\u8FC7\u7528\u6237\u4E86\uFF1A\u63D0\u793A\u8BCD**\u539F\u5C01\u4E0D\u52A8\u7528\u4ED6\u7684\u539F\u8BDD**\uFF0C\u4E0D\u8981\u6DA6\u8272\u3001\u4E0D\u8981\u6269\u5199\u3001\u4E0D\u8981\u7FFB\u8BD1\u3002\u4ED6\u7ED9\u4E86\u56FE/\u5934\u50CF\u5C31\u5199\u8FDB references\u3002\u91CD\u65B0\u8C03\u7528 draw \u5F00\u753B\uFF0C\u4E0D\u8981\u518D\u95EE\u7B2C\u4E8C\u6B21"
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1688
1879
|
const finalPrompt = args?.negative_prompt ? `${prompt}
|
|
1689
1880
|
|
|
1690
1881
|
Negative prompt: ${String(args.negative_prompt).trim()}` : prompt;
|
|
@@ -1701,12 +1892,12 @@ Negative prompt: ${String(args.negative_prompt).trim()}` : prompt;
|
|
|
1701
1892
|
});
|
|
1702
1893
|
}
|
|
1703
1894
|
}
|
|
1704
|
-
function buildAgentSystemPrompt(cmdConfig, userInputText,
|
|
1895
|
+
function buildAgentSystemPrompt(cmdConfig, userInputText, refCount, refs, agentCfg) {
|
|
1705
1896
|
const instructions = renderAgentInstructions(agentCfg.instructions || DEFAULT_AGENT_INSTRUCTIONS, {
|
|
1706
1897
|
command: cmdConfig.name || "",
|
|
1707
1898
|
prompt: cmdConfig.prompt || "",
|
|
1708
1899
|
userInput: userInputText || "",
|
|
1709
|
-
imageCount,
|
|
1900
|
+
imageCount: refs.userIds.length,
|
|
1710
1901
|
refCount,
|
|
1711
1902
|
confirmRule: agentCfg.confirmBeforeDraw !== false ? AGENT_CONFIRM_RULE_ON : AGENT_CONFIRM_RULE_OFF
|
|
1712
1903
|
});
|
|
@@ -1716,10 +1907,17 @@ Negative prompt: ${String(args.negative_prompt).trim()}` : prompt;
|
|
|
1716
1907
|
`- \u7ED8\u56FE\u6307\u4EE4\uFF1A${cmdConfig.name}`,
|
|
1717
1908
|
`- \u6307\u4EE4\u81EA\u5E26\u7684\u63D0\u793A\u8BCD\uFF1A${cmdConfig.prompt || "\uFF08\u7A7A\uFF0C\u8FD9\u662F\u81EA\u5B9A\u4E49\u6307\u4EE4\uFF09"}`,
|
|
1718
1909
|
`- \u7528\u6237\u8FD9\u6B21\u8BF4\u7684\u8BDD\uFF1A${userInputText || "\uFF08\u7528\u6237\u6CA1\u8BF4\u8BDD\uFF09"}`,
|
|
1719
|
-
`- \u7528\u6237\u968F\u6D88\u606F\u53D1\u7684\u56FE\uFF1A${
|
|
1910
|
+
`- \u7528\u6237\u968F\u6D88\u606F\u53D1\u7684\u56FE\uFF1A${refs.userIds.length ? `${refs.userIds.join("\u3001")}\uFF08\u5DF2\u81EA\u52A8\u5E26\u4E0A\uFF0C\u4E0D\u7528\u518D\u586B\uFF09` : "0 \u5F20"}`,
|
|
1720
1911
|
`- \u56FE\u5E93\u91CC\u53EF\u4EE5\u641C\u5230\u7684\u53C2\u8003\u56FE\uFF1A${refCount} \u5F20`
|
|
1721
1912
|
];
|
|
1722
|
-
if (baseIds.length) lines.push(`- \u6307\u4EE4\u9ED8\u8BA4\u53C2\u8003\u56FE\uFF08\u5DF2\u81EA\u52A8\u5E26\u4E0A\uFF0C\u4E0D\u7528\u518D\u586B\uFF09\uFF1A${baseIds.join("\u3001")}`);
|
|
1913
|
+
if (refs.baseIds.length) lines.push(`- \u6307\u4EE4\u9ED8\u8BA4\u53C2\u8003\u56FE\uFF08\u5DF2\u81EA\u52A8\u5E26\u4E0A\uFF0C\u4E0D\u7528\u518D\u586B\uFF09\uFF1A${refs.baseIds.join("\u3001")}`);
|
|
1914
|
+
if (refs.selfAvatarId) lines.push(`- \u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\u7684\u5934\u50CF\uFF1A${refs.selfAvatarId}\uFF08\u4ED6\u60F3\u753B\u300C\u6211\u300D\u65F6\u7528\u8FD9\u4E2A\uFF0C\u6216\u7528 get_avatar \u53D6\uFF09`);
|
|
1915
|
+
if (refs.atAvatarIds.length) {
|
|
1916
|
+
const who = refs.atTargets.map((t) => `${t.name || t.id}`).join("\u3001");
|
|
1917
|
+
lines.push(`- \u88AB @ \u7684\u4EBA\u7684\u5934\u50CF\uFF08\u5DF2\u81EA\u52A8\u5E26\u4E0A\uFF0C\u4E0D\u7528\u518D\u586B\uFF09\uFF1A${refs.atAvatarIds.join("\u3001")}\uFF08\u5206\u522B\u662F ${who} \u7684\u5934\u50CF\uFF09`);
|
|
1918
|
+
} else if (refs.atTargets.length) {
|
|
1919
|
+
lines.push(`- \u7528\u6237 @ \u4E86 ${refs.atTargets.map((t) => t.name || t.id).join("\u3001")}\uFF0C\u4F46\u6CA1\u53D6\u5230\u4ED6\u4EEC\u7684\u5934\u50CF\uFF0C\u9700\u8981\u7684\u8BDD\u7528 get_avatar \u518D\u8BD5`);
|
|
1920
|
+
}
|
|
1723
1921
|
if (cmdConfig.custom) lines.push("- \u8FD9\u662F\u81EA\u5B9A\u4E49\u6307\u4EE4\uFF1A\u7528\u6237\u6CA1\u8BF4\u6E05\u695A\u8981\u753B\u4EC0\u4E48\u65F6\uFF0C\u5148\u7528 ask_user \u95EE\u4E00\u53E5");
|
|
1724
1922
|
return instructions + lines.join("\n");
|
|
1725
1923
|
}
|
|
@@ -1870,6 +2068,19 @@ ${echo}` : echo);
|
|
|
1870
2068
|
promptText = mode === "rewrite" && await optimizePromptText("", text) || text;
|
|
1871
2069
|
}
|
|
1872
2070
|
const images = [...defaultImageUrls || [], ...extractImagesFromSession(session)];
|
|
2071
|
+
const avatarCfg = config.avatar || {};
|
|
2072
|
+
if (avatarCfg.enabled !== false) {
|
|
2073
|
+
if (avatarCfg.autoAt !== false) {
|
|
2074
|
+
for (const target of collectAtTargets(session)) {
|
|
2075
|
+
const url = await fetchAvatarUrl(session, target.id);
|
|
2076
|
+
if (url) images.push(url);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
if (avatarCfg.autoSelf && !images.length) {
|
|
2080
|
+
const url = await fetchAvatarUrl(session, session.userId);
|
|
2081
|
+
if (url) images.push(url);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
1873
2084
|
const remaining = Math.max(0, maxImages - images.length);
|
|
1874
2085
|
if (remaining > 0) {
|
|
1875
2086
|
const [msgId] = await session.send(
|
|
@@ -2288,6 +2499,33 @@ ${lines.join("\n")}`]);
|
|
|
2288
2499
|
if (!content) return "";
|
|
2289
2500
|
return import_koishi.h.select(content, "text").map((el) => el.attrs.content || "").join(" ").trim();
|
|
2290
2501
|
}
|
|
2502
|
+
function collectAtTargets(session) {
|
|
2503
|
+
const raw = String(session?.content || session.stripped?.content || "");
|
|
2504
|
+
return extractAtIds(raw, session.selfId);
|
|
2505
|
+
}
|
|
2506
|
+
async function fetchAvatarUrl(session, userId) {
|
|
2507
|
+
if (!userId) return "";
|
|
2508
|
+
const cfg = config.avatar || {};
|
|
2509
|
+
const own = userId === session.userId;
|
|
2510
|
+
const url = buildAvatarUrl({
|
|
2511
|
+
userId,
|
|
2512
|
+
platform: session.platform,
|
|
2513
|
+
selfId: session.selfId,
|
|
2514
|
+
appId: session?.bot?.config?.id,
|
|
2515
|
+
authorAvatar: own ? String(session?.author?.avatar || "") : "",
|
|
2516
|
+
template: cfg.urlTemplate,
|
|
2517
|
+
size: Number(cfg.size) > 0 ? Number(cfg.size) : 640
|
|
2518
|
+
});
|
|
2519
|
+
if (url) return url;
|
|
2520
|
+
try {
|
|
2521
|
+
const member = session.guildId ? await session.bot?.getGuildMember?.(session.guildId, userId) : null;
|
|
2522
|
+
const got = member?.avatar || member?.user?.avatar;
|
|
2523
|
+
if (got) return String(got);
|
|
2524
|
+
} catch (error) {
|
|
2525
|
+
logInfo2(`\u5411\u9002\u914D\u5668\u7D22\u53D6\u5934\u50CF\u5931\u8D25: ${error}`);
|
|
2526
|
+
}
|
|
2527
|
+
return "";
|
|
2528
|
+
}
|
|
2291
2529
|
function extractImagesFromSession(session) {
|
|
2292
2530
|
const images = [];
|
|
2293
2531
|
const currentImages = extractImagesFromMessage(session.stripped.content);
|
|
@@ -2453,12 +2691,14 @@ ${lines.join("\n")}`]);
|
|
|
2453
2691
|
Config,
|
|
2454
2692
|
apply,
|
|
2455
2693
|
buildAgentTools,
|
|
2694
|
+
buildAvatarUrl,
|
|
2456
2695
|
buildMarkdownImage,
|
|
2457
2696
|
buildPromptBlock,
|
|
2458
2697
|
buildPromptEcho,
|
|
2459
2698
|
cleanAskMessage,
|
|
2460
2699
|
computeRetryDelay,
|
|
2461
2700
|
describeSelectorError,
|
|
2701
|
+
extractAtIds,
|
|
2462
2702
|
extractJsonObject,
|
|
2463
2703
|
extractResponseText,
|
|
2464
2704
|
extractServerMessage,
|
|
@@ -2470,6 +2710,7 @@ ${lines.join("\n")}`]);
|
|
|
2470
2710
|
mergePrompt,
|
|
2471
2711
|
name,
|
|
2472
2712
|
parseJsonToolCall,
|
|
2713
|
+
parsePolishIntent,
|
|
2473
2714
|
parseRetryAfterHeader,
|
|
2474
2715
|
pickAgentMessage,
|
|
2475
2716
|
previewJson,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-image-prompt",
|
|
3
3
|
"description": "多功能 AI 图像处理插件:Agent 模式把「查不查图库 / 要不要追问 / 什么时候开画」全交给模型自己决定,支持手办化、Coser 化、Minecraft 化等 47 种风格与自定义提示词",
|
|
4
|
-
"version": "2.0
|
|
4
|
+
"version": "2.1.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
package/readme.md
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
基于 OpenAI 兼容的 Chat Completions 接口,支持多种 AI 绘图模型,让你的图片瞬间变成精美手办。
|
|
8
8
|
|
|
9
9
|
**特色能力**:**Agent 模式**(把「查不查图库 / 要不要追问 / 什么时候开画」全交给模型,像 NeoBot 一样多轮对话)·
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
**头像参考图**(@ 谁就用谁的头像画,也能画「我」)· **用户发的图直接进参考图** ·
|
|
11
|
+
参考图片分组 + 关键词检索 · **默认不润色**(原话直出,用户点头才加工)·
|
|
12
|
+
提示词回显(QQ 走 markdown 代码块)· **指令一触发就回「收到」** ·
|
|
13
|
+
生成结果自动入库形成闭环 · 后台绘图 + 完成推送 · 参考图描述 AI 自动生成 · 429/限流自动退避重试。
|
|
13
14
|
|
|
14
15
|
## ⚙️ 插件设置
|
|
15
16
|
|
|
@@ -49,9 +50,10 @@
|
|
|
49
50
|
↓
|
|
50
51
|
「收到,正在准备...」 ← 立刻回,不让用户干等
|
|
51
52
|
↓
|
|
52
|
-
模型拿到
|
|
53
|
+
模型拿到 4 个工具,自己决定怎么走:
|
|
53
54
|
├─ gallery_search(关键词) → 自己想关键词、自己搜图库
|
|
54
55
|
├─ ask_user(问题) → 自己开口问、自己等用户回话
|
|
56
|
+
├─ get_avatar(用户id) → 取群友头像当参考图
|
|
55
57
|
└─ draw(prompt, references) → 自己写提示词、自己挑参考图、开画
|
|
56
58
|
↓
|
|
57
59
|
模型说最后一句话 → 发给用户
|
|
@@ -66,6 +68,7 @@
|
|
|
66
68
|
|---|---|
|
|
67
69
|
| `gallery_search` | 按关键词搜参考图库(关键词由模型自己拟,空格分隔多词)。返回每条的 `id`,写进 `draw` 的 `references` |
|
|
68
70
|
| `ask_user` | 把一句话发给用户**并等他的下一条消息**(他发的图片也会一起带回来,会拿到新编号) |
|
|
71
|
+
| `get_avatar` | 取某个人的头像当参考图。`userId` 留空 = 取发指令的人自己;也可以填被 @ 的人的 id |
|
|
69
72
|
| `draw` | 真正开画。传最终提示词 + 参考图编号,画好自动发到群里 |
|
|
70
73
|
|
|
71
74
|
### 工作规则(写进提示词,由模型执行)
|
|
@@ -73,9 +76,11 @@
|
|
|
73
76
|
1. **涉及角色时先搜图库**:有立绘就填进 `references`;没有就如实说「图库里没有这个立绘,将按描述创作」,然后正常画。
|
|
74
77
|
2. 用户明说「不用参考 / 随意画 / 自由发挥」→ 跳过搜索,一次都不搜。
|
|
75
78
|
3. **信息不够才开口**,而且一次只问一个最关键的问题(不连珠炮、不顺带聊别的)。
|
|
76
|
-
4. **开画前先问一句**(默认开):把「这次参考哪几张图 +
|
|
79
|
+
4. **开画前先问一句**(默认开):把「这次参考哪几张图 + 大致画成什么样」告诉你,并顺带问「提示词要不要我帮你润色」,你点头它才画。
|
|
77
80
|
5. 画面上要出文字(台词/标题/招牌)→ 原样写进提示词,并说明「必须原样出现,不能变形或自创字形」。
|
|
78
81
|
6. 只填它确认存在的编号,**不许编造 id**(编了会被丢掉并告诉它不存在)。
|
|
82
|
+
7. **没指定参考图时先问一句**:用户这次没发图、没 @ 人、指令本身也没默认参考图时,不直接开画,先问要不要参考图。
|
|
83
|
+
8. **默认不润色**:你的原话原封不动交给绘图模型。只有你说「帮我润色 / 优化一下」它才加工。
|
|
79
84
|
|
|
80
85
|
### Agent 配置
|
|
81
86
|
|
|
@@ -91,6 +96,8 @@
|
|
|
91
96
|
| 采样温度 | — | 0.3 |
|
|
92
97
|
| 输出长度上限 | **推理模型会把额度耗在思考上**,太小会一个字都生成不出来。0 = 不限制 | 8000 |
|
|
93
98
|
| 记住最近几轮对话 | 同一频道记住最近 N 轮,「再画一张」「换个风格」才能接上;0 = 不记忆(重启即失效) | 6 |
|
|
99
|
+
| **没指定参考图时先问一句** | 用户没发图、没 @ 人、指令也没默认图时,开画前先问「要不要参考图」。插件兜底执行,模型自己先问过就不再问 | 开 |
|
|
100
|
+
| **开画前先问要不要润色** | 先问「提示词要不要我帮你润色」,**默认直接用你的原话**,你说润色才加工。同样是插件兜底 | 开 |
|
|
94
101
|
| 工具调用日志 | 把每一轮的模型输出与工具调用写进日志,排查用 | 关 |
|
|
95
102
|
| agent 专用模型 / 地址 / 密钥 | 留空则复用「AI 模型接口」里的配置 | 留空 |
|
|
96
103
|
| 指令模板 | 只写「有哪些工具 + 什么时候用」,别写死流程。占位符 `{confirmRule}` 等 | 内置模板 |
|
|
@@ -103,6 +110,36 @@
|
|
|
103
110
|
(带 tool 支持的版本)、`glm-4` 系等。老的小模型(如 `Qwen2.5-7B-Instruct`)可能不支持工具调用,
|
|
104
111
|
这时就走 JSON 兜底协议,效果取决于模型听不听话。
|
|
105
112
|
|
|
113
|
+
### 🧑 头像参考图(@ 谁画谁 / 画「我」)
|
|
114
|
+
|
|
115
|
+
除了图库里的立绘,**群友的头像也能直接当参考图**。三种用法:
|
|
116
|
+
|
|
117
|
+
| 场景 | 怎么写 | 效果 |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| 画某个群友 | `画图/手办化 @小明 和我一起吃火锅` | 小明的头像**自动**作为参考图带上 |
|
|
120
|
+
| 画自己 | `画图/手办化 画我吃火锅` | 模型会调 `get_avatar` 取你自己的头像 |
|
|
121
|
+
| 追问里补图 | 模型问「要不要参考图」时直接**发一张图**、或 `@` 一个人、或说「用我的头像」 | 图/头像登记进参考图,下一轮就能用 |
|
|
122
|
+
|
|
123
|
+
**用户随消息发的图**(包括指令里直接带的、以及引用消息里的)**一律自动进参考图**,
|
|
124
|
+
不用指望模型记得把它填进 `references` —— 之前这一步是模型自选,经常会漏掉。
|
|
125
|
+
|
|
126
|
+
| 配置项 | 说明 | 默认值 |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| 开启头像参考图 | 总开关 | 开 |
|
|
129
|
+
| @ 谁自动带谁的头像 | `@` 本身就是明确的指定,关掉就只能靠模型调 `get_avatar` | 开 |
|
|
130
|
+
| 没图时自动用自己的头像 | 开了的话,你没发图也没 @ 人时会自动带上自己的头像 | **关** |
|
|
131
|
+
| 头像边长 | 像素 | 640 |
|
|
132
|
+
| 自定义头像地址模板 | 留空用内置规则。占位符 `{userId}` `{platform}` `{selfId}` `{appId}` `{size}` | 空 |
|
|
133
|
+
|
|
134
|
+
**内置规则**(按平台走):
|
|
135
|
+
|
|
136
|
+
- **QQ 官方机器人**:`https://q.qlogo.cn/qqapp/{appid}/{openid}/{size}`(appid 从机器人配置里取)
|
|
137
|
+
- **onebot / red 这类拿得到真 QQ 号**:`https://q1.qlogo.cn/g?b=qq&nk={QQ号}&s={size}`
|
|
138
|
+
- 其它平台(Discord / KOOK 等):内置规则拼不出来,会去问适配器要成员头像;再拿不到就放弃(不影响正常出图)
|
|
139
|
+
|
|
140
|
+
> 平台不提供头像地址时,日志里不会有报错,只是这次没有头像参考图。
|
|
141
|
+
> 想换头像服务就填「自定义头像地址模板」。
|
|
142
|
+
|
|
106
143
|
### 🖼️ 参考图片组 / 图库
|
|
107
144
|
|
|
108
145
|
参考图以**分组**的方式注册,agent 通过 `gallery_search` 按需搜。
|
|
@@ -156,9 +193,23 @@ bot:画好啦,按 ref1 的立绘做的~
|
|
|
156
193
|
```
|
|
157
194
|
|
|
158
195
|
- 你回「算了」/ 不回 → 模型自己决定这次不画,**不会白画一张**。
|
|
159
|
-
- 你直接发图 →
|
|
196
|
+
- 你直接发图 → 图会被登记成编号并**自动**作为参考图带上,不用模型再填。
|
|
197
|
+
- 你 `@` 一个人 → 那个人的头像自动作为参考图带上。
|
|
160
198
|
- 「收到」那条不会重复发,提示词也只回显一次。
|
|
161
199
|
|
|
200
|
+
再来一个用头像的例子:
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
你:表情包 @小明 我俩一起吃瓜
|
|
204
|
+
|
|
205
|
+
bot:收到,正在准备...
|
|
206
|
+
bot:这次用小明的头像当参考,画成表情包可以吗?提示词要我帮你润色吗(默认直接用你的原话)?
|
|
207
|
+
你:直接画
|
|
208
|
+
bot:这两个人一起吃瓜
|
|
209
|
+
bot:
|
|
210
|
+
bot:画好啦~
|
|
211
|
+
```
|
|
212
|
+
|
|
162
213
|
### 📝 提示词回显
|
|
163
214
|
|
|
164
215
|
处理时会把**最终交给绘图模型的提示词**一起发出来,方便你确认它到底用了什么。
|
|
@@ -239,6 +290,9 @@ bot:画好啦,按 ref1 的立绘做的~
|
|
|
239
290
|
> **现在的绘图模型听得懂中文,也有基本的理解力。** 把用户那句话原样交给它就够了 ——
|
|
240
291
|
> 反过来,把它「优化」成一两百字的英文术语堆砌,各要素互相稀释,出图反而更容易走样。
|
|
241
292
|
> 所以 2.0.3 起默认是**做减法**。
|
|
293
|
+
>
|
|
294
|
+
> 2.1.0 起连「要不要润色」也交给你决定:**默认原封不动**,
|
|
295
|
+
> 只有你明确说「帮我润色 / 优化一下」模型才加工(agent 模式会在开画前问你这一句)。
|
|
242
296
|
|
|
243
297
|
| 配置项 | 说明 | 默认 |
|
|
244
298
|
|---|---|---|
|
package/src/index.ts
CHANGED
|
@@ -212,7 +212,7 @@ export function resolveReferences(
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
/** agent 可用的工具名(JSON 兜底协议只对这几个名字生效,避免把正常回复当工具调用) */
|
|
215
|
-
export const AGENT_TOOL_NAMES = ['gallery_search', 'ask_user', 'draw']
|
|
215
|
+
export const AGENT_TOOL_NAMES = ['gallery_search', 'ask_user', 'get_avatar', 'draw']
|
|
216
216
|
|
|
217
217
|
/** OpenAI function-calling 格式的工具定义 */
|
|
218
218
|
export function buildAgentTools(): any[] {
|
|
@@ -246,6 +246,20 @@ export function buildAgentTools(): any[] {
|
|
|
246
246
|
},
|
|
247
247
|
},
|
|
248
248
|
},
|
|
249
|
+
{
|
|
250
|
+
type: 'function',
|
|
251
|
+
function: {
|
|
252
|
+
name: 'get_avatar',
|
|
253
|
+
description: '取某个人的头像当参考图,返回它的编号。userId 留空 = 取发指令的人自己的头像;也可以填「被 @ 的人」列表里的 id。用户说「画我」「用我的头像」「画 @某某」时用它。',
|
|
254
|
+
parameters: {
|
|
255
|
+
type: 'object',
|
|
256
|
+
properties: {
|
|
257
|
+
userId: { type: 'string', description: '要取头像的用户 id(取自上下文里的「被 @ 的人」),留空或写 me/self 表示发指令的人自己' },
|
|
258
|
+
},
|
|
259
|
+
required: [],
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
249
263
|
{
|
|
250
264
|
type: 'function',
|
|
251
265
|
function: {
|
|
@@ -254,8 +268,8 @@ export function buildAgentTools(): any[] {
|
|
|
254
268
|
parameters: {
|
|
255
269
|
type: 'object',
|
|
256
270
|
properties: {
|
|
257
|
-
//
|
|
258
|
-
prompt: { type: 'string', description: '
|
|
271
|
+
// 提示词要短;用户没说要润色就原封不动用他的原话
|
|
272
|
+
prompt: { type: 'string', description: '绘图提示词。**默认原封不动用用户的话**:简短、说人话,不要扩写成几百字,不要补画风/光线/材质术语,不要翻成英文;只有用户说过「帮我润色 / 优化一下」才加工。中文即可(绘图模型听得懂中文)。画面上必须出现的文字要原样写进去' },
|
|
259
273
|
references: {
|
|
260
274
|
type: 'array',
|
|
261
275
|
items: { type: 'string' },
|
|
@@ -369,6 +383,78 @@ export function cleanAskMessage(raw: any, max = 200): string {
|
|
|
369
383
|
return text
|
|
370
384
|
}
|
|
371
385
|
|
|
386
|
+
/**
|
|
387
|
+
* 从用户的回答里判断他要不要润色提示词。
|
|
388
|
+
* 默认 false —— 用户的原话原封不动交给绘图模型才是对的默认值。
|
|
389
|
+
*/
|
|
390
|
+
export function parsePolishIntent(text: string): boolean {
|
|
391
|
+
const t = String(text || '')
|
|
392
|
+
if (!t) return false
|
|
393
|
+
if (/不(用|要|必)?(润色|优化|加工|改)|别(润色|优化|加工)|原样|原封不动|直接画|不用了/.test(t)) return false
|
|
394
|
+
return /润色|优化一下|加工|丰富|扩写|细化|详细[一点些]|写(得)?(更)?(完整|详细)|帮我(写|改)/.test(t)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** 从消息内容里解析 @ 到的人。type="all"(@全体成员)和 @机器人自己 会被跳过 */
|
|
398
|
+
export function extractAtIds(content: string, selfId?: string): { id: string, name: string }[] {
|
|
399
|
+
if (!content) return []
|
|
400
|
+
const out: { id: string, name: string }[] = []
|
|
401
|
+
const seen = new Set<string>()
|
|
402
|
+
for (const el of h.select(content, 'at')) {
|
|
403
|
+
const id = String(el.attrs.id ?? el.attrs.user ?? '').trim()
|
|
404
|
+
if (!id || id === selfId) continue
|
|
405
|
+
if (String(el.attrs.type || '') === 'all') continue
|
|
406
|
+
if (seen.has(id)) continue
|
|
407
|
+
seen.add(id)
|
|
408
|
+
out.push({ id, name: String(el.attrs.name || el.attrs.nick || '').trim() })
|
|
409
|
+
}
|
|
410
|
+
return out
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* 拼头像地址。优先级:自定义模板 > 适配器直接给的 > 平台内置规则。
|
|
415
|
+
* 拼不出来返回空字符串(调用方再决定是问适配器还是放弃)。
|
|
416
|
+
*/
|
|
417
|
+
export function buildAvatarUrl(input: {
|
|
418
|
+
userId: string
|
|
419
|
+
platform?: string
|
|
420
|
+
selfId?: string
|
|
421
|
+
appId?: string
|
|
422
|
+
/** 发送者自己的头像(适配器已经在消息里带过来了) */
|
|
423
|
+
authorAvatar?: string
|
|
424
|
+
template?: string
|
|
425
|
+
size?: number
|
|
426
|
+
}): string {
|
|
427
|
+
const userId = String(input?.userId || '').trim()
|
|
428
|
+
if (!userId) return ''
|
|
429
|
+
|
|
430
|
+
const size = Number(input.size) > 0 ? Number(input.size) : 640
|
|
431
|
+
const platform = String(input.platform || '')
|
|
432
|
+
const appId = String(input.appId || '')
|
|
433
|
+
|
|
434
|
+
if (input.template) {
|
|
435
|
+
return String(input.template).replace(/\{(\w+)\}/g, (_m, key) => {
|
|
436
|
+
const vars: Record<string, string> = {
|
|
437
|
+
userId, platform, size: String(size),
|
|
438
|
+
selfId: String(input.selfId || ''),
|
|
439
|
+
appId,
|
|
440
|
+
}
|
|
441
|
+
return vars[key] ?? ''
|
|
442
|
+
})
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (input.authorAvatar) return String(input.authorAvatar)
|
|
446
|
+
|
|
447
|
+
// QQ 官方机器人:https://q.qlogo.cn/qqapp/{appid}/{openid}/{size}
|
|
448
|
+
if (appId && (platform === 'qq' || platform === 'qqguild')) {
|
|
449
|
+
return `https://q.qlogo.cn/qqapp/${appId}/${userId}/${size}`
|
|
450
|
+
}
|
|
451
|
+
// 拿得到真 QQ 号时走通用头像服务(onebot / red 等)
|
|
452
|
+
if (/^(qq|onebot|red|qqguild)$/.test(platform) && /^\d{5,12}$/.test(userId)) {
|
|
453
|
+
return `https://q1.qlogo.cn/g?b=qq&nk=${userId}&s=${size}`
|
|
454
|
+
}
|
|
455
|
+
return ''
|
|
456
|
+
}
|
|
457
|
+
|
|
372
458
|
/**
|
|
373
459
|
* 图库容量裁剪:超出容量时淘汰最旧的(按 time 升序),只保留每个组最近 capacity 条
|
|
374
460
|
*/
|
|
@@ -880,6 +966,7 @@ interface CommandConfig {
|
|
|
880
966
|
referenceGroups?: ReferenceGroup[]
|
|
881
967
|
aiSelector?: AISelectorConfig
|
|
882
968
|
agent?: AgentConfig
|
|
969
|
+
avatar?: AvatarConfig
|
|
883
970
|
showPrompt?: boolean
|
|
884
971
|
promptMaxLength?: number
|
|
885
972
|
appendUserInput?: boolean
|
|
@@ -999,6 +1086,24 @@ interface AgentConfig {
|
|
|
999
1086
|
historyTurns: number
|
|
1000
1087
|
/** 把每一轮的工具调用打到日志里,方便排查 */
|
|
1001
1088
|
debugLog: boolean
|
|
1089
|
+
/** 用户没给任何参考图时,开画前先问一句「要不要参考图」 */
|
|
1090
|
+
askIfNoReference: boolean
|
|
1091
|
+
/** 开画前先问一句「要不要润色提示词」(默认不润色,原话直出) */
|
|
1092
|
+
askBeforePolish: boolean
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/** 头像参考图配置 */
|
|
1096
|
+
interface AvatarConfig {
|
|
1097
|
+
/** 总开关:能不能拿用户头像当参考图 */
|
|
1098
|
+
enabled: boolean
|
|
1099
|
+
/** 用户 @ 了谁,就自动把那个人的头像作为参考图(@ 本身就是明确指定) */
|
|
1100
|
+
autoAt: boolean
|
|
1101
|
+
/** 用户没发图也没 @ 人时,自动拿他自己的头像当参考图 */
|
|
1102
|
+
autoSelf: boolean
|
|
1103
|
+
/** 头像尺寸(边长像素) */
|
|
1104
|
+
size: number
|
|
1105
|
+
/** 自定义头像地址模板,留空用内置规则。占位符:{userId} {platform} {selfId} {appId} {size} */
|
|
1106
|
+
urlTemplate: string
|
|
1002
1107
|
}
|
|
1003
1108
|
|
|
1004
1109
|
/** agent 参考图登记表里的一条 */
|
|
@@ -1007,7 +1112,7 @@ export interface RefEntry {
|
|
|
1007
1112
|
url: string
|
|
1008
1113
|
description: string
|
|
1009
1114
|
group: string
|
|
1010
|
-
source: 'gallery' | 'user' | 'cmd'
|
|
1115
|
+
source: 'gallery' | 'user' | 'cmd' | 'avatar'
|
|
1011
1116
|
}
|
|
1012
1117
|
|
|
1013
1118
|
/** 交给 AI 挑选的候选图片 */
|
|
@@ -1056,6 +1161,7 @@ const DEFAULT_AGENT_INSTRUCTIONS = `你是 QQ 群里的绘图助手。用户发
|
|
|
1056
1161
|
- gallery_search:按关键词搜索参考图库。**涉及具体角色/立绘时必须先搜一次**。
|
|
1057
1162
|
多个关键词用空格分隔(如「白发 立绘」),全部命中的排在最前;搜不到就换个词再搜。
|
|
1058
1163
|
- ask_user:把问题发给用户**并等他回复**(他发的图也会一起带回来)。
|
|
1164
|
+
- get_avatar:取某个人的头像当参考图。userId 留空取发指令者自己;也可以取被 @ 的人的头像。
|
|
1059
1165
|
- draw:真正开始画。把 prompt 和挑中的参考图编号交进去,画好会自动发到群里。
|
|
1060
1166
|
|
|
1061
1167
|
工作规则:
|
|
@@ -1070,22 +1176,35 @@ const DEFAULT_AGENT_INSTRUCTIONS = `你是 QQ 群里的绘图助手。用户发
|
|
|
1070
1176
|
4. 画面上要出现文字(台词、标题、招牌、字幕)时,把文字原样写进 prompt,并说明「这些字必须原样出现,不能变形或自创字形」。
|
|
1071
1177
|
5. draw 返回 ok=false 时,看 error 决定:能改的(提示词问题)就换个写法重试,改不了的(限流/配额)就如实告诉用户。
|
|
1072
1178
|
6. 流程结束后用中文回一句简短的结果说明(不要复述 prompt,不要写小作文,不要再问「还要我做什么」)。
|
|
1073
|
-
7. 图片编号:gallery_search 结果里的 id
|
|
1074
|
-
|
|
1075
|
-
|
|
1179
|
+
7. 图片编号:gallery_search 结果里的 id、用户随消息发的图、头像、指令默认图都可以直接写进 draw 的 references。
|
|
1180
|
+
只填上下文里**明确列出过**的编号,**绝对不要编造 id**。
|
|
1181
|
+
- 用户随消息发的图会自动带上,不用你再填;
|
|
1182
|
+
- 他 @ 了谁,那个人的头像也会自动带上。
|
|
1183
|
+
8. 【没指定参考图时先问一句】用户这次没发图、没 @ 人、指令本身也没有默认参考图,你也搜不到合适的立绘时,
|
|
1184
|
+
**不要直接 draw**。先用 ask_user 问一句要不要参考图,并给出选项(例如「用你自己的头像 / 用 @某某 的头像 /
|
|
1185
|
+
我帮你从图库找一张 / 不用参考直接画」)。用户说「不用 / 随便 / 直接画」就立刻开画,**不要问第二次**。
|
|
1186
|
+
9. 【要画「我」就用头像】用户说「画我」「用我的形象」「画我和 xxx」却没给图时,用 get_avatar 取他自己的头像;
|
|
1187
|
+
说「画 @某某」时取那个人的头像(一般已经自动带上了,直接用那个编号即可)。
|
|
1188
|
+
10. 【默认不润色,用户点头才润色】把用户的话**原封不动**交给绘图模型就是最好的默认值:
|
|
1189
|
+
- **不要**主动扩写、不要补画风/光线/材质/镜头、不要翻成英文、不要加「masterpiece」这类词;
|
|
1190
|
+
- 用户明确说「帮我润色 / 优化一下 / 写详细点」时,你才把提示词写得更完整;
|
|
1191
|
+
- draw 工具返回 asked=true 时看它给的 polish 字段:polish=true 才润色,false 或没给就**一个字都别改**;
|
|
1076
1192
|
- 用户用中文提的需求,prompt 就写中文(绘图模型听得懂中文,理解力也够用);
|
|
1077
|
-
- 别为了显得专业就去堆风格 / 材质 / 光线 / 镜头术语,更别刻意翻成英文;
|
|
1078
1193
|
- 只保留用户真正说过的内容 + 画面必须有的要素,**不要替他脑补细节**;
|
|
1079
1194
|
- 提示词越长,各要素互相稀释,出图反而越走样。一两句话能把画面说清就够了。
|
|
1080
1195
|
|
|
1081
1196
|
如果你所在的接口不支持函数调用,就**只输出一行 JSON** 来调用工具,例如:
|
|
1082
1197
|
{"tool":"gallery_search","args":{"keyword":"白发 立绘"}}
|
|
1083
1198
|
{"tool":"ask_user","args":{"question":"想要什么画风?"}}
|
|
1199
|
+
{"tool":"get_avatar","args":{"userId":""}}
|
|
1084
1200
|
{"tool":"draw","args":{"prompt":"...","references":["ref1","user1"]}}
|
|
1085
1201
|
不想调用工具时,正常输出给用户看的中文即可。`
|
|
1086
1202
|
|
|
1087
1203
|
/** 「开画前先问一句」的两种提示词分支 */
|
|
1088
|
-
const AGENT_CONFIRM_RULE_ON = `**必须先 ask_user**:用一句话告诉用户「这次会参考哪几张图 +
|
|
1204
|
+
const AGENT_CONFIRM_RULE_ON = `**必须先 ask_user**:用一句话告诉用户「这次会参考哪几张图 + 大致画成什么样」,
|
|
1205
|
+
并顺带问一句「提示词要不要我帮你润色(默认直接用你的原话)」,用户点头后才能调用 draw。
|
|
1206
|
+
- 用户说「润色 / 优化 / 丰富一下」→ 你再把提示词写得更完整;
|
|
1207
|
+
- 用户说「直接画 / 不用 / 原样」或没提这件事 → **原封不动用他的原话**,一个字都别润色。
|
|
1089
1208
|
用户回复了否定或提出修改意见,就按他说的改,再确认一次或直接画(他说「直接画」就不用再问)。`
|
|
1090
1209
|
const AGENT_CONFIRM_RULE_OFF = `用户需求已经明确的,直接调用 draw,不用多问一句。只有真的缺关键信息才 ask_user。`
|
|
1091
1210
|
|
|
@@ -1093,6 +1212,7 @@ const AGENT_CONFIRM_RULE_OFF = `用户需求已经明确的,直接调用 draw
|
|
|
1093
1212
|
const AGENT_JSON_FALLBACK_RULE = `(注意:当前接口不支持函数调用)请你**只用一行 JSON** 来调用工具,不要输出别的内容:
|
|
1094
1213
|
{"tool":"gallery_search","args":{"keyword":"白发 立绘"}}
|
|
1095
1214
|
{"tool":"ask_user","args":{"question":"想要什么画风?"}}
|
|
1215
|
+
{"tool":"get_avatar","args":{"userId":""}}
|
|
1096
1216
|
{"tool":"draw","args":{"prompt":"...","references":["ref1","ref2"]}}
|
|
1097
1217
|
不需要调用工具时,正常输出给用户看的中文即可。`
|
|
1098
1218
|
|
|
@@ -1648,9 +1768,21 @@ export const Config: Schema = Schema.intersect([
|
|
|
1648
1768
|
baseUrl: Schema.string().role('link').description('agent 专用接口地址(留空则复用「AI 模型接口」的地址)'),
|
|
1649
1769
|
apiKey: Schema.string().role('secret').description('agent 专用密钥(留空则复用「AI 模型接口」的密钥)'),
|
|
1650
1770
|
instructions: Schema.string().role('textarea', { rows: [16, 8] }).default(DEFAULT_AGENT_INSTRUCTIONS).description('驱动 agent 行为的系统提示词(只写「有哪些工具 + 什么时候用」,别写死流程)。可用占位符:{confirmRule} 开画确认规则、{command} 指令名、{prompt} 指令提示词、{userInput} 用户附加需求、{imageCount} 用户随消息发的图数量、{refCount} 可检索的参考图数量。指令名/提示词等上下文会自动附在提示词末尾,不用自己引用'),
|
|
1771
|
+
askIfNoReference: Schema.boolean().default(true).description('用户这次既没发图、也没 @ 人、指令本身也没有默认参考图时,开画前先问一句「要不要参考图」(可以用他自己的头像 / @ 别人的头像)。这条是插件兜底执行的,模型自己先问过就不再问'),
|
|
1772
|
+
askBeforePolish: Schema.boolean().default(true).description('开画前先问用户「提示词要不要我帮你润色」。**默认是原封不动把用户的原话交给绘图模型**,只有用户说「润色 / 优化」才加工。这条同样是插件兜底,模型自己先问过就不再问'),
|
|
1651
1773
|
}).collapse().description('Agent 配置(指令较长,已折叠)'),
|
|
1652
1774
|
}).description('Agent 模式'),
|
|
1653
1775
|
|
|
1776
|
+
Schema.object({
|
|
1777
|
+
avatar: Schema.object({
|
|
1778
|
+
enabled: Schema.boolean().default(true).description('开启头像参考图:可以把群友的头像当参考图来画'),
|
|
1779
|
+
autoAt: Schema.boolean().default(true).description('用户 @ 了谁,就自动把那个人的头像作为参考图(@ 本身就是明确的指定)'),
|
|
1780
|
+
autoSelf: Schema.boolean().default(false).description('用户没发图也没 @ 人时,自动拿他自己的头像当参考图。关掉也没关系,「用我的头像画」这类需求 agent 会自己调用 get_avatar'),
|
|
1781
|
+
size: Schema.number().default(640).min(64).max(1024).step(64).description('头像边长(像素)'),
|
|
1782
|
+
urlTemplate: Schema.string().default('').description('自定义头像地址模板,留空用内置规则。占位符:{userId} 用户 id、{platform} 平台、{selfId} 机器人 id、{appId} 机器人 appid、{size} 尺寸'),
|
|
1783
|
+
}).description('头像参考图'),
|
|
1784
|
+
}).description('头像参考图'),
|
|
1785
|
+
|
|
1654
1786
|
Schema.object({
|
|
1655
1787
|
appendUserInput: Schema.boolean().default(true).description('把用户随指令发的附加需求并入绘图提示词(例如「手办化 xxx 在偷吃白饭被发现的表情」)'),
|
|
1656
1788
|
promptOptimize: Schema.union([
|
|
@@ -1890,6 +2022,8 @@ export function apply(ctx: Context, config: CommandConfig) {
|
|
|
1890
2022
|
needimages: '请提供至少一张图片',
|
|
1891
2023
|
agentfailed: '绘图助手这次没跑起来({0})。可以先关掉「Agent 模式」照常画图,或换一个支持工具调用的模型。',
|
|
1892
2024
|
agentnoresult: '这次没能理出个结果来,麻烦再说一次你想画什么~',
|
|
2025
|
+
askreference: '这次没有指定参考图。要参考图片吗?可以直接发图、@ 一个人用他的头像,或者说「用我的头像」;不需要就回「直接画」。\n另外,提示词要我帮你润色一下吗?默认直接用你的原话。',
|
|
2026
|
+
askpolish: '这次直接按你的原话开画。要我先帮你润色一下提示词吗?回「润色」我加工一下,回「直接画」就原样交给绘图模型。',
|
|
1893
2027
|
optimizefailed: '(提示词融合失败,已改为追加模式)',
|
|
1894
2028
|
bgstart: '已开始后台绘图({0} 张图),画好后会在这里通知你...',
|
|
1895
2029
|
bgqueue: '(当前有 {0} 个任务在排队)',
|
|
@@ -2002,7 +2136,31 @@ export function apply(ctx: Context, config: CommandConfig) {
|
|
|
2002
2136
|
const baseIds = (defaultImageUrls || []).filter(Boolean).map(url => register('cmd', url, '', '指令默认图'))
|
|
2003
2137
|
const userIds = extractImagesFromSession(session).map(url => register('user', url, '', '用户发送'))
|
|
2004
2138
|
|
|
2005
|
-
|
|
2139
|
+
// ---- 头像参考图 ----
|
|
2140
|
+
// 自己的头像登记进表(模型要画「我」时可以取用);@ 到的人的头像直接作为参考图带上
|
|
2141
|
+
const avatarCfg: AvatarConfig = config.avatar || ({} as AvatarConfig)
|
|
2142
|
+
const avatarOn = avatarCfg.enabled !== false
|
|
2143
|
+
const atTargets = avatarOn ? collectAtTargets(session) : []
|
|
2144
|
+
let selfAvatarId = ''
|
|
2145
|
+
const atAvatarIds: string[] = []
|
|
2146
|
+
const atAvatarById = new Map<string, string>()
|
|
2147
|
+
if (avatarOn) {
|
|
2148
|
+
const selfUrl = await fetchAvatarUrl(session, session.userId)
|
|
2149
|
+
if (selfUrl) {
|
|
2150
|
+
selfAvatarId = register('avatar', selfUrl, '发指令的人的头像', '头像')
|
|
2151
|
+
}
|
|
2152
|
+
if (avatarCfg.autoAt !== false) {
|
|
2153
|
+
for (const target of atTargets) {
|
|
2154
|
+
const url = await fetchAvatarUrl(session, target.id)
|
|
2155
|
+
if (!url) continue
|
|
2156
|
+
const id = register('avatar', url, `${target.name || target.id} 的头像`, '头像')
|
|
2157
|
+
if (!atAvatarIds.includes(id)) atAvatarIds.push(id)
|
|
2158
|
+
atAvatarById.set(target.id, id)
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
logInfo(`[${cmdConfig.name}] agent 启动:可搜索参考图 ${pool.length} 张,默认图 ${baseIds.length} 张,用户随消息发的图 ${userIds.length} 张,头像 ${atAvatarIds.length + (selfAvatarId ? 1 : 0)} 张`)
|
|
2006
2164
|
|
|
2007
2165
|
// ---- 历史(同一频道记住最近几轮,让「再画一张」能接上)----
|
|
2008
2166
|
const historyKey = agentHistoryKey(session)
|
|
@@ -2012,7 +2170,9 @@ export function apply(ctx: Context, config: CommandConfig) {
|
|
|
2012
2170
|
remember({ role: 'user', content: userInputText || '(用户没有附加说明)' })
|
|
2013
2171
|
|
|
2014
2172
|
const messages: any[] = [
|
|
2015
|
-
{ role: 'system', content: buildAgentSystemPrompt(cmdConfig, userInputText,
|
|
2173
|
+
{ role: 'system', content: buildAgentSystemPrompt(cmdConfig, userInputText, pool.length, {
|
|
2174
|
+
userIds, baseIds, atAvatarIds, selfAvatarId, atTargets,
|
|
2175
|
+
}, agentCfg) },
|
|
2016
2176
|
...history,
|
|
2017
2177
|
{ role: 'user', content: userInputText ? userInputText : '(用户没有附加说明,请按指令自带的提示词来)' },
|
|
2018
2178
|
]
|
|
@@ -2023,6 +2183,10 @@ export function apply(ctx: Context, config: CommandConfig) {
|
|
|
2023
2183
|
let finalText = ''
|
|
2024
2184
|
let drew = false
|
|
2025
2185
|
let failed = ''
|
|
2186
|
+
/** 这轮里模型自己有没有问过用户(问过就不再替它追问参考图的事) */
|
|
2187
|
+
let askedOnce = false
|
|
2188
|
+
/** 开画前的兜底确认已经问过了,一次流程只问一次 */
|
|
2189
|
+
let drawAsked = false
|
|
2026
2190
|
|
|
2027
2191
|
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
2028
2192
|
if (!isActive || !ctx.scope.isActive) break
|
|
@@ -2088,8 +2252,9 @@ export function apply(ctx: Context, config: CommandConfig) {
|
|
|
2088
2252
|
switch (name) {
|
|
2089
2253
|
case 'gallery_search': return toolGallerySearch(args)
|
|
2090
2254
|
case 'ask_user': return toolAskUser(args)
|
|
2255
|
+
case 'get_avatar': return toolGetAvatar(args)
|
|
2091
2256
|
case 'draw': return toolDraw(args)
|
|
2092
|
-
default: return JSON.stringify({ ok: false, error: `没有叫「${name}」的工具,可用的是 gallery_search / ask_user / draw` })
|
|
2257
|
+
default: return JSON.stringify({ ok: false, error: `没有叫「${name}」的工具,可用的是 gallery_search / ask_user / get_avatar / draw` })
|
|
2093
2258
|
}
|
|
2094
2259
|
}
|
|
2095
2260
|
|
|
@@ -2114,10 +2279,35 @@ export function apply(ctx: Context, config: CommandConfig) {
|
|
|
2114
2279
|
})
|
|
2115
2280
|
}
|
|
2116
2281
|
|
|
2282
|
+
/** 取某人头像:留空取发指令的人,也可以取被 @ 的人 */
|
|
2283
|
+
async function toolGetAvatar(args: any): Promise<string> {
|
|
2284
|
+
if (!avatarOn) return JSON.stringify({ ok: false, error: '头像参考图功能已关闭' })
|
|
2285
|
+
const raw = String(args?.userId ?? args?.id ?? args?.user ?? '').trim()
|
|
2286
|
+
if (!raw || /^(me|self|自己|我)$/i.test(raw) || raw === session.userId) {
|
|
2287
|
+
if (selfAvatarId) {
|
|
2288
|
+
return JSON.stringify({ ok: true, id: selfAvatarId, userId: session.userId, note: '发指令的人自己的头像' })
|
|
2289
|
+
}
|
|
2290
|
+
const url = await fetchAvatarUrl(session, session.userId)
|
|
2291
|
+
if (!url) return JSON.stringify({ ok: false, error: '没取到他自己的头像(这个平台可能不提供头像地址)' })
|
|
2292
|
+
selfAvatarId = register('avatar', url, '发指令的人的头像', '头像')
|
|
2293
|
+
return JSON.stringify({ ok: true, id: selfAvatarId, userId: session.userId, note: '发指令的人自己的头像' })
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
const cached = atAvatarById.get(raw)
|
|
2297
|
+
if (cached) return JSON.stringify({ ok: true, id: cached, userId: raw, note: '被 @ 的人的头像' })
|
|
2298
|
+
|
|
2299
|
+
const url = await fetchAvatarUrl(session, raw)
|
|
2300
|
+
if (!url) return JSON.stringify({ ok: false, error: `没取到 ${raw} 的头像` })
|
|
2301
|
+
const id = register('avatar', url, `${raw} 的头像`, '头像')
|
|
2302
|
+
atAvatarById.set(raw, id)
|
|
2303
|
+
return JSON.stringify({ ok: true, id, userId: raw, note: '这个人的头像' })
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2117
2306
|
/** 提问并等用户的下一条消息(图也会一起带回来) */
|
|
2118
2307
|
async function toolAskUser(args: any): Promise<string> {
|
|
2119
2308
|
const question = cleanAskMessage(args?.question ?? args?.text ?? args?.message)
|
|
2120
2309
|
if (!question) return JSON.stringify({ ok: false, error: 'question 不能为空' })
|
|
2310
|
+
askedOnce = true
|
|
2121
2311
|
|
|
2122
2312
|
try {
|
|
2123
2313
|
await sendNotice(session, question)
|
|
@@ -2146,10 +2336,34 @@ export function apply(ctx: Context, config: CommandConfig) {
|
|
|
2146
2336
|
remember({ role: 'user', content: text || (ids.length ? `(用户发了 ${ids.length} 张图片)` : '(用户什么都没说)') })
|
|
2147
2337
|
logInfo(`agent 提问「${question}」-> 用户回复: ${text || '(无文字)'},图片 ${ids.length} 张`)
|
|
2148
2338
|
|
|
2339
|
+
// 回复里 @ 了谁 / 说「用我的头像」→ 把对应头像也登记进来
|
|
2340
|
+
const avatars: { id: string, note: string }[] = []
|
|
2341
|
+
if (avatarOn) {
|
|
2342
|
+
if (avatarCfg.autoAt !== false) {
|
|
2343
|
+
for (const target of extractAtIds(String(answer || ''), session.selfId)) {
|
|
2344
|
+
const cached = atAvatarById.get(target.id)
|
|
2345
|
+
if (cached) {
|
|
2346
|
+
if (!avatars.some(a => a.id === cached)) avatars.push({ id: cached, note: `${target.name || target.id} 的头像` })
|
|
2347
|
+
continue
|
|
2348
|
+
}
|
|
2349
|
+
const url = await fetchAvatarUrl(session, target.id)
|
|
2350
|
+
if (!url) continue
|
|
2351
|
+
const id = register('avatar', url, `${target.name || target.id} 的头像`, '头像')
|
|
2352
|
+
atAvatarById.set(target.id, id)
|
|
2353
|
+
avatars.push({ id, note: `${target.name || target.id} 的头像` })
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
// 「用我的头像」「画我」——就算关闭了 autoSelf,用户明确开口了就给他
|
|
2357
|
+
if (/头像|我的形象|画我|我自己/.test(text) && selfAvatarId) {
|
|
2358
|
+
if (!avatars.some(a => a.id === selfAvatarId)) avatars.push({ id: selfAvatarId, note: '发指令的人自己的头像' })
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2149
2362
|
return JSON.stringify({
|
|
2150
2363
|
ok: true,
|
|
2151
2364
|
text,
|
|
2152
2365
|
images: ids.map((id, i) => ({ id, note: `用户这次发的第 ${i + 1} 张图` })),
|
|
2366
|
+
...(avatars.length ? { avatars } : {}),
|
|
2153
2367
|
})
|
|
2154
2368
|
}
|
|
2155
2369
|
|
|
@@ -2168,10 +2382,41 @@ export function apply(ctx: Context, config: CommandConfig) {
|
|
|
2168
2382
|
const wanted: string[] = []
|
|
2169
2383
|
const pushId = (id: string) => { if (id && !wanted.includes(id)) wanted.push(id) }
|
|
2170
2384
|
baseIds.forEach(pushId)
|
|
2385
|
+
// 用户随消息发的图、他 @ 的人的头像:都是他明确指定的,直接进参考图,不依赖模型记得填
|
|
2386
|
+
userIds.forEach(pushId)
|
|
2387
|
+
atAvatarIds.forEach(pushId)
|
|
2388
|
+
if (avatarOn && avatarCfg.autoSelf) pushId(selfAvatarId)
|
|
2171
2389
|
ids.forEach(pushId)
|
|
2172
2390
|
const capped = wanted.slice(0, maxSelect)
|
|
2173
2391
|
const imageUrls = capped.map(id => registry.get(id)?.url).filter(Boolean) as string[]
|
|
2174
2392
|
|
|
2393
|
+
// 开画前兜底确认:一次流程最多问一次;模型自己已经问过就不再打扰
|
|
2394
|
+
// 两个话题合并进同一句话 —— 参考图要不要 + 提示词要不要润色
|
|
2395
|
+
const needRef = !capped.length && agentCfg.askIfNoReference !== false
|
|
2396
|
+
const needPolish = agentCfg.askBeforePolish !== false
|
|
2397
|
+
if (!drawAsked && !askedOnce && (needRef || needPolish)) {
|
|
2398
|
+
drawAsked = true
|
|
2399
|
+
const asked = await toolAskUser({
|
|
2400
|
+
question: needRef
|
|
2401
|
+
? session.text('image-prompt.messages.askreference')
|
|
2402
|
+
: session.text('image-prompt.messages.askpolish'),
|
|
2403
|
+
})
|
|
2404
|
+
let parsed: any = {}
|
|
2405
|
+
try { parsed = JSON.parse(asked) } catch { parsed = {} }
|
|
2406
|
+
// 「不用润色 / 直接画 / 原样」→ false;明确说「润色 / 优化」→ true。默认不润色
|
|
2407
|
+
const polish = parsed.ok ? parsePolishIntent(parsed.text) : false
|
|
2408
|
+
return JSON.stringify({
|
|
2409
|
+
ok: false,
|
|
2410
|
+
asked: true,
|
|
2411
|
+
polish,
|
|
2412
|
+
...(parsed.images?.length ? { newImages: parsed.images } : {}),
|
|
2413
|
+
...(parsed.avatars?.length ? { newAvatars: parsed.avatars } : {}),
|
|
2414
|
+
note: polish
|
|
2415
|
+
? '已经替你问过用户了:他说要润色,可以把提示词写得更完整。他给了图/头像也要写进 references。重新调用 draw 开画,不要再问第二次'
|
|
2416
|
+
: '已经替你问过用户了:提示词**原封不动用他的原话**,不要润色、不要扩写、不要翻译。他给了图/头像就写进 references。重新调用 draw 开画,不要再问第二次',
|
|
2417
|
+
})
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2175
2420
|
const finalPrompt = args?.negative_prompt
|
|
2176
2421
|
? `${prompt}\n\nNegative prompt: ${String(args.negative_prompt).trim()}`
|
|
2177
2422
|
: prompt
|
|
@@ -2202,16 +2447,21 @@ export function apply(ctx: Context, config: CommandConfig) {
|
|
|
2202
2447
|
function buildAgentSystemPrompt(
|
|
2203
2448
|
cmdConfig: CommandConfig['nested']['commands'][number],
|
|
2204
2449
|
userInputText: string,
|
|
2205
|
-
imageCount: number,
|
|
2206
2450
|
refCount: number,
|
|
2207
|
-
|
|
2451
|
+
refs: {
|
|
2452
|
+
userIds: string[]
|
|
2453
|
+
baseIds: string[]
|
|
2454
|
+
atAvatarIds: string[]
|
|
2455
|
+
selfAvatarId: string
|
|
2456
|
+
atTargets: { id: string, name: string }[]
|
|
2457
|
+
},
|
|
2208
2458
|
agentCfg: AgentConfig
|
|
2209
2459
|
): string {
|
|
2210
2460
|
const instructions = renderAgentInstructions(agentCfg.instructions || DEFAULT_AGENT_INSTRUCTIONS, {
|
|
2211
2461
|
command: cmdConfig.name || '',
|
|
2212
2462
|
prompt: cmdConfig.prompt || '',
|
|
2213
2463
|
userInput: userInputText || '',
|
|
2214
|
-
imageCount,
|
|
2464
|
+
imageCount: refs.userIds.length,
|
|
2215
2465
|
refCount,
|
|
2216
2466
|
confirmRule: agentCfg.confirmBeforeDraw !== false ? AGENT_CONFIRM_RULE_ON : AGENT_CONFIRM_RULE_OFF,
|
|
2217
2467
|
})
|
|
@@ -2220,10 +2470,17 @@ export function apply(ctx: Context, config: CommandConfig) {
|
|
|
2220
2470
|
`- 绘图指令:${cmdConfig.name}`,
|
|
2221
2471
|
`- 指令自带的提示词:${cmdConfig.prompt || '(空,这是自定义指令)'}`,
|
|
2222
2472
|
`- 用户这次说的话:${userInputText || '(用户没说话)'}`,
|
|
2223
|
-
`- 用户随消息发的图:${
|
|
2473
|
+
`- 用户随消息发的图:${refs.userIds.length ? `${refs.userIds.join('、')}(已自动带上,不用再填)` : '0 张'}`,
|
|
2224
2474
|
`- 图库里可以搜到的参考图:${refCount} 张`,
|
|
2225
2475
|
]
|
|
2226
|
-
if (baseIds.length) lines.push(`- 指令默认参考图(已自动带上,不用再填):${baseIds.join('、')}`)
|
|
2476
|
+
if (refs.baseIds.length) lines.push(`- 指令默认参考图(已自动带上,不用再填):${refs.baseIds.join('、')}`)
|
|
2477
|
+
if (refs.selfAvatarId) lines.push(`- 发指令的人自己的头像:${refs.selfAvatarId}(他想画「我」时用这个,或用 get_avatar 取)`)
|
|
2478
|
+
if (refs.atAvatarIds.length) {
|
|
2479
|
+
const who = refs.atTargets.map(t => `${t.name || t.id}`).join('、')
|
|
2480
|
+
lines.push(`- 被 @ 的人的头像(已自动带上,不用再填):${refs.atAvatarIds.join('、')}(分别是 ${who} 的头像)`)
|
|
2481
|
+
} else if (refs.atTargets.length) {
|
|
2482
|
+
lines.push(`- 用户 @ 了 ${refs.atTargets.map(t => t.name || t.id).join('、')},但没取到他们的头像,需要的话用 get_avatar 再试`)
|
|
2483
|
+
}
|
|
2227
2484
|
if (cmdConfig.custom) lines.push('- 这是自定义指令:用户没说清楚要画什么时,先用 ask_user 问一句')
|
|
2228
2485
|
|
|
2229
2486
|
return instructions + lines.join('\n')
|
|
@@ -2428,6 +2685,22 @@ export function apply(ctx: Context, config: CommandConfig) {
|
|
|
2428
2685
|
}
|
|
2429
2686
|
|
|
2430
2687
|
const images: string[] = [...(defaultImageUrls || []), ...extractImagesFromSession(session)]
|
|
2688
|
+
|
|
2689
|
+
// 头像参考图:@ 了谁就带上谁的头像;一张图都没有且开了 autoSelf 时带上自己的
|
|
2690
|
+
const avatarCfg: AvatarConfig = config.avatar || ({} as AvatarConfig)
|
|
2691
|
+
if (avatarCfg.enabled !== false) {
|
|
2692
|
+
if (avatarCfg.autoAt !== false) {
|
|
2693
|
+
for (const target of collectAtTargets(session)) {
|
|
2694
|
+
const url = await fetchAvatarUrl(session, target.id)
|
|
2695
|
+
if (url) images.push(url)
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
if (avatarCfg.autoSelf && !images.length) {
|
|
2699
|
+
const url = await fetchAvatarUrl(session, session.userId)
|
|
2700
|
+
if (url) images.push(url)
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2431
2704
|
const remaining = Math.max(0, maxImages - images.length)
|
|
2432
2705
|
if (remaining > 0) {
|
|
2433
2706
|
const [msgId] = await session.send(
|
|
@@ -2987,6 +3260,46 @@ export function apply(ctx: Context, config: CommandConfig) {
|
|
|
2987
3260
|
.trim()
|
|
2988
3261
|
}
|
|
2989
3262
|
|
|
3263
|
+
/**
|
|
3264
|
+
* 取这条消息里 @ 到的人。
|
|
3265
|
+
* 注意不能只看 stripped.content —— Koishi 的 stripped 会把开头连续的 <at> 元素剥掉,
|
|
3266
|
+
* 「/画图 @某某」这种写法里 @ 就丢了,必须看原始 content。
|
|
3267
|
+
*/
|
|
3268
|
+
function collectAtTargets(session: Session): { id: string, name: string }[] {
|
|
3269
|
+
const raw = String((session as any)?.content || session.stripped?.content || '')
|
|
3270
|
+
return extractAtIds(raw, session.selfId)
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
/** 取某个人的头像地址:先按平台规则拼,拼不出来再去问适配器要 */
|
|
3274
|
+
async function fetchAvatarUrl(session: Session, userId: string): Promise<string> {
|
|
3275
|
+
if (!userId) return ''
|
|
3276
|
+
const cfg: AvatarConfig = config.avatar || ({} as AvatarConfig)
|
|
3277
|
+
const own = userId === session.userId
|
|
3278
|
+
|
|
3279
|
+
const url = buildAvatarUrl({
|
|
3280
|
+
userId,
|
|
3281
|
+
platform: session.platform,
|
|
3282
|
+
selfId: session.selfId,
|
|
3283
|
+
appId: (session as any)?.bot?.config?.id,
|
|
3284
|
+
authorAvatar: own ? String((session as any)?.author?.avatar || '') : '',
|
|
3285
|
+
template: cfg.urlTemplate,
|
|
3286
|
+
size: Number(cfg.size) > 0 ? Number(cfg.size) : 640,
|
|
3287
|
+
})
|
|
3288
|
+
if (url) return url
|
|
3289
|
+
|
|
3290
|
+
// 内置规则拼不出来(Discord / KOOK / 频道类平台)时,让适配器直接给
|
|
3291
|
+
try {
|
|
3292
|
+
const member = session.guildId
|
|
3293
|
+
? await (session.bot as any)?.getGuildMember?.(session.guildId, userId)
|
|
3294
|
+
: null
|
|
3295
|
+
const got = member?.avatar || member?.user?.avatar
|
|
3296
|
+
if (got) return String(got)
|
|
3297
|
+
} catch (error) {
|
|
3298
|
+
logInfo(`向适配器索取头像失败: ${error}`)
|
|
3299
|
+
}
|
|
3300
|
+
return ''
|
|
3301
|
+
}
|
|
3302
|
+
|
|
2990
3303
|
function extractImagesFromSession(session: Session): string[] {
|
|
2991
3304
|
const images: string[] = []
|
|
2992
3305
|
|