koishi-plugin-imgdraw-selfuse 0.0.7 → 0.0.8

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 CHANGED
@@ -26,6 +26,11 @@ 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>;
29
34
  apiList: Schema<Schemastery.ObjectS<{
30
35
  enable: Schema<boolean, boolean>;
31
36
  apiKey: Schema<string, string>;
@@ -105,6 +110,11 @@ export declare const Config: Schema<Schemastery.ObjectS<{
105
110
  txt2imgModel: Schema<string, string>;
106
111
  img2imgModel: Schema<string, string>;
107
112
  maxImages: Schema<number, number>;
113
+ enableImgCompress: Schema<boolean, boolean>;
114
+ imgMaxWidth: Schema<number, number>;
115
+ imgMaxHeight: Schema<number, number>;
116
+ imgQuality: Schema<number, number>;
117
+ imgMaxFileSize: Schema<number, number>;
108
118
  apiList: Schema<Schemastery.ObjectS<{
109
119
  enable: Schema<boolean, boolean>;
110
120
  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'],
@@ -30,6 +38,13 @@ exports.Config = koishi_1.Schema.object({
30
38
  txt2imgModel: koishi_1.Schema.string().default('').description('文生图专用模型,留空则使用通用模型'),
31
39
  img2imgModel: koishi_1.Schema.string().default('').description('图生图专用模型,留空则使用通用模型'),
32
40
  maxImages: koishi_1.Schema.number().default(5).description('图生图最大支持图片数量'),
41
+ // ==================== 新增:图片压缩配置 ====================
42
+ enableImgCompress: koishi_1.Schema.boolean().default(true).description('启用图生图图片压缩(推荐开启,可防止大图超时)'),
43
+ imgMaxWidth: koishi_1.Schema.number().default(1536).description('图片压缩最大宽度(像素)'),
44
+ imgMaxHeight: koishi_1.Schema.number().default(1536).description('图片压缩最大高度(像素)'),
45
+ imgQuality: koishi_1.Schema.number().default(85).description('JPEG 压缩质量 1-100(越高越清晰,建议 80-90)'),
46
+ imgMaxFileSize: koishi_1.Schema.number().default(3).description('图片最大体积(MB),超过会进一步压缩'),
47
+ // ==========================================================
33
48
  apiList: koishi_1.Schema.array(koishi_1.Schema.object({
34
49
  enable: koishi_1.Schema.boolean().default(true).description('启用此 API'),
35
50
  apiKey: koishi_1.Schema.string().description('API Key'),
@@ -74,6 +89,11 @@ exports.Config = koishi_1.Schema.object({
74
89
  // ==================== 主函数 ====================
75
90
  async function apply(ctx, cfg) {
76
91
  const debug = cfg.debug;
92
+ // 检查 sharp 是否安装
93
+ if (cfg.enableImgCompress && !sharp) {
94
+ logger.warn('图片压缩已启用,但未检测到 sharp 库。请运行:npm install sharp');
95
+ logger.warn('在未安装 sharp 的情况下,将跳过压缩,可能导致大图超时');
96
+ }
77
97
  // 加载本地化文件
78
98
  try {
79
99
  const loc = path_1.default.join(__dirname, 'locales', 'zh-CN.yml');
@@ -126,11 +146,11 @@ async function apply(ctx, cfg) {
126
146
  if (!str)
127
147
  return '';
128
148
  // 1. 清理标准 HTML 标签
129
- let cleaned = str.replace(/<<[^>]+>/g, ' ');
149
+ let cleaned = str.replace(/<[^>]+>/g, ' ');
130
150
  // 2. 清理 QQ XML 图片标签(如 <img src="..." file="..."/>)
131
- cleaned = cleaned.replace(/<<img\s+[^>]+\/>/gi, ' ');
151
+ cleaned = cleaned.replace(/<img\s+[^>]+\/>/gi, ' ');
132
152
  // 3. 清理其他 XML 标签
133
- cleaned = cleaned.replace(/<<[^>]+>/g, ' ');
153
+ cleaned = cleaned.replace(/<[^>]+>/g, ' ');
134
154
  // 4. 清理多余空格和换行
135
155
  cleaned = cleaned.replace(/\s+/g, ' ').trim();
136
156
  return cleaned;
@@ -153,11 +173,74 @@ async function apply(ctx, cfg) {
153
173
  return markdownMatch[1];
154
174
  return null;
155
175
  }
156
- // ==================== 新增:URL 转 base64 ====================
176
+ // ==================== 新增:图片压缩函数 ====================
177
+ async function compressImage(buffer) {
178
+ if (!sharp || !cfg.enableImgCompress) {
179
+ return buffer;
180
+ }
181
+ try {
182
+ let image = sharp(buffer);
183
+ const metadata = await image.metadata();
184
+ // 计算缩放比例
185
+ let width = metadata.width || cfg.imgMaxWidth;
186
+ let height = metadata.height || cfg.imgMaxHeight;
187
+ const ratio = Math.min(cfg.imgMaxWidth / width, cfg.imgMaxHeight / height, 1 // 不放大
188
+ );
189
+ if (ratio < 1) {
190
+ width = Math.round(width * ratio);
191
+ height = Math.round(height * ratio);
192
+ image = image.resize(width, height, { fit: 'inside' });
193
+ }
194
+ // 压缩并转换格式
195
+ let quality = Math.max(1, Math.min(100, cfg.imgQuality));
196
+ let compressed = await image
197
+ .jpeg({ quality, progressive: true, mozjpeg: true })
198
+ .toBuffer();
199
+ // 如果还超过限制,进一步降低质量
200
+ const maxBytes = cfg.imgMaxFileSize * 1024 * 1024;
201
+ while (compressed.length > maxBytes && quality > 40) {
202
+ quality -= 5;
203
+ compressed = await image
204
+ .jpeg({ quality, progressive: true, mozjpeg: true })
205
+ .toBuffer();
206
+ }
207
+ if (debug) {
208
+ logger.info(`图片压缩: ${buffer.length} -> ${compressed.length} bytes ` +
209
+ `(${Math.round(compressed.length / buffer.length * 100)}%), ` +
210
+ `尺寸: ${Math.round(width)}x${Math.round(height)}, 质量: ${quality}`);
211
+ }
212
+ return compressed;
213
+ }
214
+ catch (e) {
215
+ logger.error('图片压缩失败,使用原图', e);
216
+ return buffer;
217
+ }
218
+ }
219
+ async function compressBase64Image(base64Url) {
220
+ if (!sharp || !cfg.enableImgCompress) {
221
+ return base64Url;
222
+ }
223
+ try {
224
+ const base64Data = base64Url.split(',')[1];
225
+ const buffer = Buffer.from(base64Data, 'base64');
226
+ const compressed = await compressImage(buffer);
227
+ return `data:image/jpeg;base64,${compressed.toString('base64')}`;
228
+ }
229
+ catch (e) {
230
+ logger.error('base64 图片压缩失败,使用原图', e);
231
+ return base64Url;
232
+ }
233
+ }
234
+ // ==========================================================
235
+ // ==================== 修改:URL 转 base64(带压缩)====================
157
236
  async function urlToBase64(url) {
158
237
  if (!url)
159
238
  return null;
160
239
  if (url.startsWith('data:image/')) {
240
+ // 如果已经是 base64,检查是否需要压缩
241
+ if (cfg.enableImgCompress && sharp) {
242
+ return await compressBase64Image(url);
243
+ }
161
244
  return url;
162
245
  }
163
246
  try {
@@ -165,9 +248,9 @@ async function apply(ctx, cfg) {
165
248
  responseType: 'arraybuffer',
166
249
  timeout: 30000,
167
250
  });
168
- const base64 = Buffer.from(res.data).toString('base64');
169
- const mime = res.headers['content-type'] || 'image/jpeg';
170
- return `data:${mime};base64,${base64}`;
251
+ // 压缩图片
252
+ const compressed = await compressImage(Buffer.from(res.data));
253
+ return `data:image/jpeg;base64,${compressed.toString('base64')}`;
171
254
  }
172
255
  catch (e) {
173
256
  logger.error('图片转 base64 失败', e);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-imgdraw-selfuse",
3
3
  "description": "修改自ai-image的画图插件,支持openai兼容api,增加base64转换以适配GPTimg2,支持发送合并消息图生图",
4
- "version": "0.0.7",
4
+ "version": "0.0.8",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [