koishi-plugin-imgdraw-selfuse 0.0.7 → 0.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 +40 -0
- package/lib/index.js +264 -30
- package/package.json +2 -2
package/lib/index.d.ts
CHANGED
|
@@ -26,6 +26,26 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
26
26
|
txt2imgModel: Schema<string, string>;
|
|
27
27
|
img2imgModel: Schema<string, string>;
|
|
28
28
|
maxImages: Schema<number, number>;
|
|
29
|
+
enableImgCompress: Schema<boolean, boolean>;
|
|
30
|
+
imgMaxWidth: Schema<number, number>;
|
|
31
|
+
imgMaxHeight: Schema<number, number>;
|
|
32
|
+
imgQuality: Schema<number, number>;
|
|
33
|
+
imgMaxFileSize: Schema<number, number>;
|
|
34
|
+
enableImg2ImgBase64: Schema<boolean, boolean>;
|
|
35
|
+
enablePresets: Schema<boolean, boolean>;
|
|
36
|
+
presets: Schema<Schemastery.ObjectS<{
|
|
37
|
+
enable: Schema<boolean, boolean>;
|
|
38
|
+
text: Schema<string, string>;
|
|
39
|
+
command: Schema<string, string>;
|
|
40
|
+
keyword: Schema<string, string>;
|
|
41
|
+
enableKeywordMatch: Schema<boolean, boolean>;
|
|
42
|
+
}>[], Schemastery.ObjectT<{
|
|
43
|
+
enable: Schema<boolean, boolean>;
|
|
44
|
+
text: Schema<string, string>;
|
|
45
|
+
command: Schema<string, string>;
|
|
46
|
+
keyword: Schema<string, string>;
|
|
47
|
+
enableKeywordMatch: Schema<boolean, boolean>;
|
|
48
|
+
}>[]>;
|
|
29
49
|
apiList: Schema<Schemastery.ObjectS<{
|
|
30
50
|
enable: Schema<boolean, boolean>;
|
|
31
51
|
apiKey: Schema<string, string>;
|
|
@@ -105,6 +125,26 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
105
125
|
txt2imgModel: Schema<string, string>;
|
|
106
126
|
img2imgModel: Schema<string, string>;
|
|
107
127
|
maxImages: Schema<number, number>;
|
|
128
|
+
enableImgCompress: Schema<boolean, boolean>;
|
|
129
|
+
imgMaxWidth: Schema<number, number>;
|
|
130
|
+
imgMaxHeight: Schema<number, number>;
|
|
131
|
+
imgQuality: Schema<number, number>;
|
|
132
|
+
imgMaxFileSize: Schema<number, number>;
|
|
133
|
+
enableImg2ImgBase64: Schema<boolean, boolean>;
|
|
134
|
+
enablePresets: Schema<boolean, boolean>;
|
|
135
|
+
presets: Schema<Schemastery.ObjectS<{
|
|
136
|
+
enable: Schema<boolean, boolean>;
|
|
137
|
+
text: Schema<string, string>;
|
|
138
|
+
command: Schema<string, string>;
|
|
139
|
+
keyword: Schema<string, string>;
|
|
140
|
+
enableKeywordMatch: Schema<boolean, boolean>;
|
|
141
|
+
}>[], Schemastery.ObjectT<{
|
|
142
|
+
enable: Schema<boolean, boolean>;
|
|
143
|
+
text: Schema<string, string>;
|
|
144
|
+
command: Schema<string, string>;
|
|
145
|
+
keyword: Schema<string, string>;
|
|
146
|
+
enableKeywordMatch: Schema<boolean, boolean>;
|
|
147
|
+
}>[]>;
|
|
108
148
|
apiList: Schema<Schemastery.ObjectS<{
|
|
109
149
|
enable: Schema<boolean, boolean>;
|
|
110
150
|
apiKey: Schema<string, string>;
|
package/lib/index.js
CHANGED
|
@@ -10,6 +10,14 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
10
10
|
const yaml_1 = __importDefault(require("yaml"));
|
|
11
11
|
const fs_1 = __importDefault(require("fs"));
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
|
+
// 尝试导入 sharp,如果未安装则给出提示
|
|
14
|
+
let sharp;
|
|
15
|
+
try {
|
|
16
|
+
sharp = require('sharp');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
sharp = null;
|
|
20
|
+
}
|
|
13
21
|
exports.name = 'ai-image';
|
|
14
22
|
exports.inject = {
|
|
15
23
|
required: ['console', 'i18n', 'database'],
|
|
@@ -17,6 +25,14 @@ exports.inject = {
|
|
|
17
25
|
};
|
|
18
26
|
const logger = new koishi_1.Logger('ai-image');
|
|
19
27
|
// ==================== 配置 Schema ====================
|
|
28
|
+
// 预置提示词单项配置
|
|
29
|
+
const PresetItem = koishi_1.Schema.object({
|
|
30
|
+
enable: koishi_1.Schema.boolean().default(true).description('启用此预置提示词'),
|
|
31
|
+
text: koishi_1.Schema.string().default('').description('预置提示词文本(将自动添加到 prompt 前)'),
|
|
32
|
+
command: koishi_1.Schema.string().default('').description('触发指令(如 draw0,留空则自动生成 drawN)'),
|
|
33
|
+
keyword: koishi_1.Schema.string().default('').description('匹配关键词(如 猫娘,留空则不启用关键词匹配)'),
|
|
34
|
+
enableKeywordMatch: koishi_1.Schema.boolean().default(false).description('启用关键词匹配(用户 prompt 包含关键词时自动添加)'),
|
|
35
|
+
}).description('预置提示词配置项');
|
|
20
36
|
exports.Config = koishi_1.Schema.object({
|
|
21
37
|
debug: koishi_1.Schema.boolean().default(false).description('开启调试模式,输出完整请求日志'),
|
|
22
38
|
apiStrategy: koishi_1.Schema.union([
|
|
@@ -30,6 +46,20 @@ exports.Config = koishi_1.Schema.object({
|
|
|
30
46
|
txt2imgModel: koishi_1.Schema.string().default('').description('文生图专用模型,留空则使用通用模型'),
|
|
31
47
|
img2imgModel: koishi_1.Schema.string().default('').description('图生图专用模型,留空则使用通用模型'),
|
|
32
48
|
maxImages: koishi_1.Schema.number().default(5).description('图生图最大支持图片数量'),
|
|
49
|
+
// ==================== 图片压缩配置 ====================
|
|
50
|
+
enableImgCompress: koishi_1.Schema.boolean().default(true).description('启用图生图图片压缩(推荐开启,可防止大图超时)'),
|
|
51
|
+
imgMaxWidth: koishi_1.Schema.number().default(1536).description('图片压缩最大宽度(像素)'),
|
|
52
|
+
imgMaxHeight: koishi_1.Schema.number().default(1536).description('图片压缩最大高度(像素)'),
|
|
53
|
+
imgQuality: koishi_1.Schema.number().default(85).description('JPEG 压缩质量 1-100(越高越清晰,建议 80-90)'),
|
|
54
|
+
imgMaxFileSize: koishi_1.Schema.number().default(3).description('图片最大体积(MB),超过会进一步压缩'),
|
|
55
|
+
// ==========================================================
|
|
56
|
+
// ==================== 新增:图生图 base64 转换开关 ====================
|
|
57
|
+
enableImg2ImgBase64: koishi_1.Schema.boolean().default(true).description('图生图将图片转换为 base64(关闭则直接传 URL,部分 API 不需要 base64)'),
|
|
58
|
+
// ==========================================================
|
|
59
|
+
// ==================== 预置提示词配置 ====================
|
|
60
|
+
enablePresets: koishi_1.Schema.boolean().default(false).description('启用预置提示词功能(仅用于文生图)'),
|
|
61
|
+
presets: koishi_1.Schema.array(PresetItem).default([]).description('预置提示词列表(可添加多个,支持指令触发和关键词匹配)'),
|
|
62
|
+
// ==========================================================
|
|
33
63
|
apiList: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
34
64
|
enable: koishi_1.Schema.boolean().default(true).description('启用此 API'),
|
|
35
65
|
apiKey: koishi_1.Schema.string().description('API Key'),
|
|
@@ -37,7 +67,7 @@ exports.Config = koishi_1.Schema.object({
|
|
|
37
67
|
})).default([]).description('API 配置列表(支持多账号负载)'),
|
|
38
68
|
enableTxt2Img: koishi_1.Schema.boolean().default(true).description('启用文生图'),
|
|
39
69
|
enableImg2Img: koishi_1.Schema.boolean().default(true).description('启用图生图'),
|
|
40
|
-
command: koishi_1.Schema.string().default('draw').description('
|
|
70
|
+
command: koishi_1.Schema.string().default('draw').description('文生图主指令'),
|
|
41
71
|
aliases: koishi_1.Schema.array(String).default([]).description('文生图指令别名'),
|
|
42
72
|
img2imgCommand: koishi_1.Schema.string().default('imgdraw').description('图生图指令'),
|
|
43
73
|
img2imgAliases: koishi_1.Schema.array(String).default([]).description('图生图指令别名'),
|
|
@@ -74,6 +104,11 @@ exports.Config = koishi_1.Schema.object({
|
|
|
74
104
|
// ==================== 主函数 ====================
|
|
75
105
|
async function apply(ctx, cfg) {
|
|
76
106
|
const debug = cfg.debug;
|
|
107
|
+
// 检查 sharp 是否安装
|
|
108
|
+
if (cfg.enableImgCompress && !sharp) {
|
|
109
|
+
logger.warn('图片压缩已启用,但未检测到 sharp 库。请运行:npm install sharp');
|
|
110
|
+
logger.warn('在未安装 sharp 的情况下,将跳过压缩,可能导致大图超时');
|
|
111
|
+
}
|
|
77
112
|
// 加载本地化文件
|
|
78
113
|
try {
|
|
79
114
|
const loc = path_1.default.join(__dirname, 'locales', 'zh-CN.yml');
|
|
@@ -126,11 +161,11 @@ async function apply(ctx, cfg) {
|
|
|
126
161
|
if (!str)
|
|
127
162
|
return '';
|
|
128
163
|
// 1. 清理标准 HTML 标签
|
|
129
|
-
let cleaned = str.replace(
|
|
164
|
+
let cleaned = str.replace(/<[^>]+>/g, ' ');
|
|
130
165
|
// 2. 清理 QQ XML 图片标签(如 <img src="..." file="..."/>)
|
|
131
|
-
cleaned = cleaned.replace(
|
|
166
|
+
cleaned = cleaned.replace(/<img\s+[^>]+\/>/gi, ' ');
|
|
132
167
|
// 3. 清理其他 XML 标签
|
|
133
|
-
cleaned = cleaned.replace(
|
|
168
|
+
cleaned = cleaned.replace(/<[^>]+>/g, ' ');
|
|
134
169
|
// 4. 清理多余空格和换行
|
|
135
170
|
cleaned = cleaned.replace(/\s+/g, ' ').trim();
|
|
136
171
|
return cleaned;
|
|
@@ -153,27 +188,104 @@ async function apply(ctx, cfg) {
|
|
|
153
188
|
return markdownMatch[1];
|
|
154
189
|
return null;
|
|
155
190
|
}
|
|
156
|
-
// ====================
|
|
157
|
-
async function
|
|
191
|
+
// ==================== 图片压缩函数 ====================
|
|
192
|
+
async function compressImage(buffer) {
|
|
193
|
+
if (!sharp || !cfg.enableImgCompress) {
|
|
194
|
+
return buffer;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
let image = sharp(buffer);
|
|
198
|
+
const metadata = await image.metadata();
|
|
199
|
+
// 计算缩放比例
|
|
200
|
+
let width = metadata.width || cfg.imgMaxWidth;
|
|
201
|
+
let height = metadata.height || cfg.imgMaxHeight;
|
|
202
|
+
const ratio = Math.min(cfg.imgMaxWidth / width, cfg.imgMaxHeight / height, 1 // 不放大
|
|
203
|
+
);
|
|
204
|
+
if (ratio < 1) {
|
|
205
|
+
width = Math.round(width * ratio);
|
|
206
|
+
height = Math.round(height * ratio);
|
|
207
|
+
image = image.resize(width, height, { fit: 'inside' });
|
|
208
|
+
}
|
|
209
|
+
// 压缩并转换格式
|
|
210
|
+
let quality = Math.max(1, Math.min(100, cfg.imgQuality));
|
|
211
|
+
let compressed = await image
|
|
212
|
+
.jpeg({ quality, progressive: true, mozjpeg: true })
|
|
213
|
+
.toBuffer();
|
|
214
|
+
// 如果还超过限制,进一步降低质量
|
|
215
|
+
const maxBytes = cfg.imgMaxFileSize * 1024 * 1024;
|
|
216
|
+
while (compressed.length > maxBytes && quality > 40) {
|
|
217
|
+
quality -= 5;
|
|
218
|
+
compressed = await image
|
|
219
|
+
.jpeg({ quality, progressive: true, mozjpeg: true })
|
|
220
|
+
.toBuffer();
|
|
221
|
+
}
|
|
222
|
+
if (debug) {
|
|
223
|
+
logger.info(`图片压缩: ${buffer.length} -> ${compressed.length} bytes ` +
|
|
224
|
+
`(${Math.round(compressed.length / buffer.length * 100)}%), ` +
|
|
225
|
+
`尺寸: ${Math.round(width)}x${Math.round(height)}, 质量: ${quality}`);
|
|
226
|
+
}
|
|
227
|
+
return compressed;
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
logger.error('图片压缩失败,使用原图', e);
|
|
231
|
+
return buffer;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function compressBase64Image(base64Url) {
|
|
235
|
+
if (!sharp || !cfg.enableImgCompress) {
|
|
236
|
+
return base64Url;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const base64Data = base64Url.split(',')[1];
|
|
240
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
241
|
+
const compressed = await compressImage(buffer);
|
|
242
|
+
return `data:image/jpeg;base64,${compressed.toString('base64')}`;
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
logger.error('base64 图片压缩失败,使用原图', e);
|
|
246
|
+
return base64Url;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// ==========================================================
|
|
250
|
+
// ==================== 修改:URL 转 base64(支持开关)====================
|
|
251
|
+
/**
|
|
252
|
+
* 处理图片 URL,根据配置决定返回 base64 还是原始 URL
|
|
253
|
+
* @param url 图片 URL
|
|
254
|
+
* @param forceBase64 强制使用 base64(覆盖全局配置)
|
|
255
|
+
*/
|
|
256
|
+
async function processImageUrl(url, forceBase64) {
|
|
158
257
|
if (!url)
|
|
159
258
|
return null;
|
|
259
|
+
// 如果已经是 base64,检查是否需要压缩
|
|
160
260
|
if (url.startsWith('data:image/')) {
|
|
261
|
+
if (cfg.enableImgCompress && sharp) {
|
|
262
|
+
return await compressBase64Image(url);
|
|
263
|
+
}
|
|
161
264
|
return url;
|
|
162
265
|
}
|
|
266
|
+
// 判断是否转换为 base64
|
|
267
|
+
const needBase64 = forceBase64 !== undefined ? forceBase64 : cfg.enableImg2ImgBase64;
|
|
268
|
+
if (!needBase64) {
|
|
269
|
+
// 直接返回 URL(不转换 base64)
|
|
270
|
+
if (debug)
|
|
271
|
+
logger.info('图生图使用原始 URL:', url.slice(0, 100));
|
|
272
|
+
return url;
|
|
273
|
+
}
|
|
274
|
+
// 转换为 base64
|
|
163
275
|
try {
|
|
164
276
|
const res = await axios_1.default.get(url, {
|
|
165
277
|
responseType: 'arraybuffer',
|
|
166
278
|
timeout: 30000,
|
|
167
279
|
});
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
return `data:${mime};base64,${base64}`;
|
|
280
|
+
const compressed = await compressImage(Buffer.from(res.data));
|
|
281
|
+
return `data:image/jpeg;base64,${compressed.toString('base64')}`;
|
|
171
282
|
}
|
|
172
283
|
catch (e) {
|
|
173
284
|
logger.error('图片转 base64 失败', e);
|
|
174
285
|
throw new Error('图片下载失败,请检查 selfUrl 是否可访问');
|
|
175
286
|
}
|
|
176
287
|
}
|
|
288
|
+
// ==========================================================
|
|
177
289
|
// 统一发送图片函数
|
|
178
290
|
async function sendImage(session, imgUrl) {
|
|
179
291
|
const trimmed = imgUrl.trim();
|
|
@@ -318,6 +430,68 @@ async function apply(ctx, cfg) {
|
|
|
318
430
|
}
|
|
319
431
|
return { success, fail };
|
|
320
432
|
}
|
|
433
|
+
// ==================== 预置提示词处理函数 ====================
|
|
434
|
+
/**
|
|
435
|
+
* 获取所有启用的预置提示词配置
|
|
436
|
+
*/
|
|
437
|
+
function getEnabledPresets() {
|
|
438
|
+
if (!cfg.enablePresets || !cfg.presets)
|
|
439
|
+
return [];
|
|
440
|
+
return cfg.presets.filter((p) => p.enable && p.text);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* 根据指令名查找匹配的预置提示词
|
|
444
|
+
* @param cmd 指令名,如 "draw0"
|
|
445
|
+
*/
|
|
446
|
+
function findPresetByCommand(cmd) {
|
|
447
|
+
const presets = getEnabledPresets();
|
|
448
|
+
for (const preset of presets) {
|
|
449
|
+
const presetCmd = preset.command?.trim();
|
|
450
|
+
if (presetCmd && presetCmd === cmd) {
|
|
451
|
+
return preset;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* 根据用户 prompt 关键词匹配预置提示词
|
|
458
|
+
* @param prompt 用户输入的 prompt
|
|
459
|
+
*/
|
|
460
|
+
function findPresetsByKeyword(prompt) {
|
|
461
|
+
const presets = getEnabledPresets();
|
|
462
|
+
const matched = [];
|
|
463
|
+
if (!prompt)
|
|
464
|
+
return matched;
|
|
465
|
+
const lowerPrompt = prompt.toLowerCase();
|
|
466
|
+
for (const preset of presets) {
|
|
467
|
+
if (preset.enableKeywordMatch && preset.keyword) {
|
|
468
|
+
const keywords = preset.keyword.split(/[,,|\/\s]+/).map((k) => k.trim().toLowerCase()).filter(Boolean);
|
|
469
|
+
for (const kw of keywords) {
|
|
470
|
+
if (lowerPrompt.includes(kw)) {
|
|
471
|
+
matched.push(preset);
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return matched;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* 拼接预置提示词和用户 prompt
|
|
481
|
+
* @param prompt 用户输入的原始 prompt
|
|
482
|
+
* @param presets 要应用的预置提示词列表
|
|
483
|
+
*/
|
|
484
|
+
function buildPromptWithPresets(prompt, presets) {
|
|
485
|
+
if (!presets || presets.length === 0)
|
|
486
|
+
return prompt;
|
|
487
|
+
const presetTexts = presets.map((p) => p.text.trim()).filter(Boolean);
|
|
488
|
+
if (presetTexts.length === 0)
|
|
489
|
+
return prompt;
|
|
490
|
+
const combinedPreset = presetTexts.join(',');
|
|
491
|
+
// 预置提示词放在前面,用户 prompt 在后面
|
|
492
|
+
return `${combinedPreset},${prompt}`;
|
|
493
|
+
}
|
|
494
|
+
// ==========================================================
|
|
321
495
|
// ==================== 核心生成函数 ====================
|
|
322
496
|
async function generate(session, prompt, imageUrl, modelOverride) {
|
|
323
497
|
if (!checkRateLimit()) {
|
|
@@ -334,14 +508,14 @@ async function apply(ctx, cfg) {
|
|
|
334
508
|
const model = modelOverride || cfg.model;
|
|
335
509
|
let content;
|
|
336
510
|
if (imageUrl) {
|
|
337
|
-
const
|
|
338
|
-
if (!
|
|
339
|
-
await safeSend(session, cfg.messages.fail + '
|
|
511
|
+
const processedUrl = await processImageUrl(imageUrl);
|
|
512
|
+
if (!processedUrl) {
|
|
513
|
+
await safeSend(session, cfg.messages.fail + '(图片处理失败)');
|
|
340
514
|
return;
|
|
341
515
|
}
|
|
342
516
|
content = [
|
|
343
517
|
{ type: 'text', text: prompt },
|
|
344
|
-
{ type: 'image_url', image_url: { url:
|
|
518
|
+
{ type: 'image_url', image_url: { url: processedUrl } },
|
|
345
519
|
];
|
|
346
520
|
}
|
|
347
521
|
else {
|
|
@@ -400,14 +574,14 @@ async function apply(ctx, cfg) {
|
|
|
400
574
|
}
|
|
401
575
|
const model = modelOverride || cfg.model;
|
|
402
576
|
const finalPrompt = prompt.replace('{url}', imageUrls.join(', '));
|
|
403
|
-
const
|
|
404
|
-
if (
|
|
405
|
-
await safeSend(session, cfg.messages.fail + '
|
|
577
|
+
const processedUrls = (await Promise.all(imageUrls.map(url => processImageUrl(url)))).filter((url) => url !== null);
|
|
578
|
+
if (processedUrls.length === 0) {
|
|
579
|
+
await safeSend(session, cfg.messages.fail + '(图片处理失败)');
|
|
406
580
|
return;
|
|
407
581
|
}
|
|
408
582
|
const content = [
|
|
409
583
|
{ type: 'text', text: finalPrompt },
|
|
410
|
-
...
|
|
584
|
+
...processedUrls.map(url => ({ type: 'image_url', image_url: { url } })),
|
|
411
585
|
];
|
|
412
586
|
const body = {
|
|
413
587
|
model,
|
|
@@ -494,30 +668,90 @@ async function apply(ctx, cfg) {
|
|
|
494
668
|
}
|
|
495
669
|
return cleanHtmlTags(textParts.join(' '));
|
|
496
670
|
}
|
|
671
|
+
// ==================== 新增:文生图核心逻辑(支持预置提示词)====================
|
|
672
|
+
async function doTxt2Img(session, rawPrompt, explicitPresets) {
|
|
673
|
+
if (!session)
|
|
674
|
+
return;
|
|
675
|
+
if (await isBlacklisted(session.userId)) {
|
|
676
|
+
await safeSend(session, cfg.messages.blacklisted);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (!cfg.enableTxt2Img) {
|
|
680
|
+
await safeSend(session, cfg.messages.txt2imgDisabled);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
let prompt = cleanHtmlTags(rawPrompt || '');
|
|
684
|
+
if (!prompt) {
|
|
685
|
+
await safeSend(session, cfg.messages.empty);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
// 处理预置提示词
|
|
689
|
+
const appliedPresets = [];
|
|
690
|
+
// 1. 显式指定的预置提示词(通过指令触发)
|
|
691
|
+
if (explicitPresets && explicitPresets.length > 0) {
|
|
692
|
+
appliedPresets.push(...explicitPresets);
|
|
693
|
+
}
|
|
694
|
+
// 2. 关键词匹配(仅在未通过指令触发时,或允许叠加时)
|
|
695
|
+
if (cfg.enablePresets) {
|
|
696
|
+
const keywordPresets = findPresetsByKeyword(prompt);
|
|
697
|
+
for (const kp of keywordPresets) {
|
|
698
|
+
// 避免重复添加
|
|
699
|
+
if (!appliedPresets.some((p) => p.text === kp.text)) {
|
|
700
|
+
appliedPresets.push(kp);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
// 拼接预置提示词
|
|
705
|
+
if (appliedPresets.length > 0) {
|
|
706
|
+
prompt = buildPromptWithPresets(prompt, appliedPresets);
|
|
707
|
+
if (debug) {
|
|
708
|
+
logger.info(`应用预置提示词: ${appliedPresets.map((p) => p.command || 'keyword').join(', ')}`);
|
|
709
|
+
logger.info(`最终 prompt: ${prompt.slice(0, 200)}...`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
await safeSend(session, cfg.messages.generating);
|
|
713
|
+
const finalPrompt = cfg.txt2imgPrompt.replace('{prompt}', prompt);
|
|
714
|
+
const model = cfg.txt2imgModel || cfg.model;
|
|
715
|
+
await generate(session, finalPrompt, undefined, model);
|
|
716
|
+
}
|
|
717
|
+
// ==========================================================
|
|
497
718
|
// ==================== 命令注册 ====================
|
|
719
|
+
// 主文生图指令
|
|
498
720
|
const cmd = ctx.command(`${cfg.command} <raw:text>`, 'draw');
|
|
499
721
|
cfg.aliases.forEach((alias) => cmd.alias(alias));
|
|
500
722
|
cmd.action(async ({ session }, raw) => {
|
|
501
723
|
try {
|
|
502
|
-
|
|
503
|
-
return;
|
|
504
|
-
if (await isBlacklisted(session.userId))
|
|
505
|
-
return safeSend(session, cfg.messages.blacklisted);
|
|
506
|
-
if (!cfg.enableTxt2Img)
|
|
507
|
-
return safeSend(session, cfg.messages.txt2imgDisabled);
|
|
508
|
-
const prompt = cleanHtmlTags(raw || '');
|
|
509
|
-
if (!prompt)
|
|
510
|
-
return safeSend(session, cfg.messages.empty);
|
|
511
|
-
await safeSend(session, cfg.messages.generating);
|
|
512
|
-
const finalPrompt = cfg.txt2imgPrompt.replace('{prompt}', prompt);
|
|
513
|
-
const model = cfg.txt2imgModel || cfg.model;
|
|
514
|
-
await generate(session, finalPrompt, undefined, model);
|
|
724
|
+
await doTxt2Img(session, raw);
|
|
515
725
|
}
|
|
516
726
|
catch (e) {
|
|
517
727
|
logger.error('文生图命令异常', e);
|
|
518
728
|
await safeSend(session, cfg.messages.fail);
|
|
519
729
|
}
|
|
520
730
|
});
|
|
731
|
+
// ==================== 新增:预置提示词指令注册 ====================
|
|
732
|
+
if (cfg.enablePresets && cfg.presets) {
|
|
733
|
+
const presets = getEnabledPresets();
|
|
734
|
+
for (let i = 0; i < presets.length; i++) {
|
|
735
|
+
const preset = presets[i];
|
|
736
|
+
const presetCmd = preset.command?.trim() || `draw${i}`;
|
|
737
|
+
// 注册指令
|
|
738
|
+
const pCmd = ctx.command(`${presetCmd} <raw:text>`, `使用预置提示词: ${preset.text.slice(0, 20)}...`);
|
|
739
|
+
pCmd.action(async ({ session }, raw) => {
|
|
740
|
+
try {
|
|
741
|
+
if (debug)
|
|
742
|
+
logger.info(`预置指令触发: ${presetCmd}, 预置文本: ${preset.text.slice(0, 50)}`);
|
|
743
|
+
await doTxt2Img(session, raw, [preset]);
|
|
744
|
+
}
|
|
745
|
+
catch (e) {
|
|
746
|
+
logger.error(`预置指令 ${presetCmd} 异常`, e);
|
|
747
|
+
await safeSend(session, cfg.messages.fail);
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
if (debug)
|
|
751
|
+
logger.info(`注册预置提示词指令: ${presetCmd}`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
// ==========================================================
|
|
521
755
|
// ==================== 修改:图生图命令支持合并消息 ====================
|
|
522
756
|
const imgCmd = ctx.command(`${cfg.img2imgCommand} <raw:text>`, 'imgdraw');
|
|
523
757
|
cfg.img2imgAliases.forEach((alias) => imgCmd.alias(alias));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-imgdraw-selfuse",
|
|
3
|
-
"description": "修改自ai-image的画图插件,支持openai兼容api,增加base64
|
|
4
|
-
"version": "0.0
|
|
3
|
+
"description": "修改自ai-image的画图插件,支持openai兼容api,增加base64转换自由开启,增加图文合并消息图生图,增加预置提示词",
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|