koishi-plugin-hhs-huatu 0.2.0 → 0.2.2

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/config.d.ts CHANGED
@@ -249,5 +249,5 @@ interface Forbidden {
249
249
  strict: boolean;
250
250
  }
251
251
  export declare function parseForbidden(input: string): Forbidden[];
252
- export declare function parseInput(session: Session, input: string, config: Config, override: boolean): string[];
252
+ export declare function parseInput(session: Session, input: string, config: Config, override: boolean, undesired?: string): string[];
253
253
  export {};
package/lib/index.js CHANGED
@@ -1152,7 +1152,60 @@ function parseForbidden(input) {
1152
1152
  }
1153
1153
  __name(parseForbidden, "parseForbidden");
1154
1154
  var backslash = /@@__BACKSLASH__@@/g;
1155
- function parseInput(session, input, config, override) {
1155
+ function cleanUndesiredParameter(undesired) {
1156
+ const optionPatterns = [
1157
+ /-K\s+["']?[^"']*["']?/g,
1158
+ // -K 选项
1159
+ /-m\s+\w+/g,
1160
+ // -m 选项
1161
+ /-s\s+\w+/g,
1162
+ // -s 选项
1163
+ /-r\s+[\w\d]+/g,
1164
+ // -r 选项
1165
+ /-o\s+\w+/g,
1166
+ // -o 选项
1167
+ /-O\s*/g,
1168
+ // -O 选项
1169
+ /-x\s+\d+/g,
1170
+ // -x 选项
1171
+ /-t\s+\d+/g,
1172
+ // -t 选项
1173
+ /-c\s+[\d.]+/g,
1174
+ // -c 选项
1175
+ /-R\s+[\d.]+/g,
1176
+ // -R 选项
1177
+ /-n\s+[\d.]+/g,
1178
+ // -n 选项
1179
+ /-N\s+[\d.]+/g,
1180
+ // -N 选项
1181
+ /-H\s*/g,
1182
+ // -H 选项
1183
+ /-S\s*/g,
1184
+ // -S 选项
1185
+ /-d\s*/g,
1186
+ // -d 选项
1187
+ /-C\s+\w+/g,
1188
+ // -C 选项
1189
+ /-D\s*/g,
1190
+ // -D 选项
1191
+ /-T\s*/g,
1192
+ // -T 选项
1193
+ /-i\s+\d+/g,
1194
+ // -i 选项
1195
+ /-b\s+\d+/g,
1196
+ // -b 选项
1197
+ /-I\s*/g
1198
+ // -I 选项
1199
+ ];
1200
+ let cleaned = undesired.trim();
1201
+ for (const pattern of optionPatterns) {
1202
+ cleaned = cleaned.replace(pattern, "").trim();
1203
+ }
1204
+ cleaned = cleaned.replace(/^["']|["']$/g, "").trim();
1205
+ return cleaned;
1206
+ }
1207
+ __name(cleanUndesiredParameter, "cleanUndesiredParameter");
1208
+ function parseInput(session, input, config, override, undesired) {
1156
1209
  if (!input) {
1157
1210
  return [
1158
1211
  null,
@@ -1167,8 +1220,21 @@ function parseInput(session, input, config, override) {
1167
1220
  }
1168
1221
  const negative = [];
1169
1222
  const placement = session.resolve(config.placement);
1170
- const appendToList = /* @__PURE__ */ __name((words, input2 = "") => {
1171
- const tags = input2.split(/,\s*/g);
1223
+ const appendToList = /* @__PURE__ */ __name((words, input2 = "", isNegative = false) => {
1224
+ let tags;
1225
+ if (isNegative) {
1226
+ if (input2.includes(",")) {
1227
+ tags = input2.split(/,\s*/g);
1228
+ } else {
1229
+ tags = [input2.trim()];
1230
+ }
1231
+ } else {
1232
+ if (input2.includes(",")) {
1233
+ tags = input2.split(/,\s*/g);
1234
+ } else {
1235
+ tags = input2.split(/\s+/g).filter((tag) => tag.trim());
1236
+ }
1237
+ }
1172
1238
  if (placement === "before") tags.reverse();
1173
1239
  for (let tag of tags) {
1174
1240
  tag = tag.trim();
@@ -1184,7 +1250,13 @@ function parseInput(session, input, config, override) {
1184
1250
  const capture = input.match(/(,\s*|\s+)(-u\s+|--undesired\s+|negative prompts?:\s*)([\s\S]+)/m);
1185
1251
  if (capture?.[3]) {
1186
1252
  input = input.slice(0, capture.index).trim();
1187
- appendToList(negative, capture[3]);
1253
+ appendToList(negative, capture[3], true);
1254
+ }
1255
+ if (undesired) {
1256
+ const cleanedUndesired = cleanUndesiredParameter(undesired);
1257
+ if (cleanedUndesired) {
1258
+ appendToList(negative, cleanedUndesired, true);
1259
+ }
1188
1260
  }
1189
1261
  const forbidden = parseForbidden(session.resolve(config.forbidden));
1190
1262
  const positive = input.split(/,\s*/g).filter((word) => {
@@ -1202,15 +1274,30 @@ function parseInput(session, input, config, override) {
1202
1274
  if (/^<.+>$/.test(word)) return word.replace(/ /g, "_");
1203
1275
  return config.lowerCase ? word.toLowerCase() : word;
1204
1276
  });
1205
- if (Math.max(getWordCount(positive), getWordCount(negative)) > (session.resolve(config.maxWords) || Infinity)) {
1277
+ const processedNegative = negative.filter((word) => {
1278
+ word = word.toLowerCase().replace(/[\x00-\x7f]/g, (s) => s.replace(/[^0-9a-zA-Z]/, " ")).replace(/\s+/, " ").trim();
1279
+ if (!word) return false;
1280
+ for (const { pattern, strict } of forbidden) {
1281
+ if (strict && word.split(/\W+/g).includes(pattern)) {
1282
+ return false;
1283
+ } else if (!strict && word.includes(pattern)) {
1284
+ return false;
1285
+ }
1286
+ }
1287
+ return true;
1288
+ }).map((word) => {
1289
+ if (/^<.+>$/.test(word)) return word.replace(/ /g, "_");
1290
+ return config.lowerCase ? word.toLowerCase() : word;
1291
+ });
1292
+ if (Math.max(getWordCount(positive), getWordCount(processedNegative)) > (session.resolve(config.maxWords) || Infinity)) {
1206
1293
  return [".too-many-words"];
1207
1294
  }
1208
1295
  if (!override) {
1209
- appendToList(positive, session.resolve(config.basePrompt));
1210
- appendToList(negative, session.resolve(config.negativePrompt));
1211
- if (config.defaultPromptSw) appendToList(positive, session.resolve(config.defaultPrompt));
1296
+ appendToList(positive, session.resolve(config.basePrompt), false);
1297
+ appendToList(processedNegative, session.resolve(config.negativePrompt), true);
1298
+ if (config.defaultPromptSw) appendToList(positive, session.resolve(config.defaultPrompt), false);
1212
1299
  }
1213
- return [null, positive.join(", "), negative.join(", ")];
1300
+ return [null, positive.join(", "), processedNegative.join(", ")];
1214
1301
  }
1215
1302
  __name(parseInput, "parseInput");
1216
1303
  function getWordCount(words) {
@@ -1244,7 +1331,9 @@ var STRATEGIES = {
1244
1331
  formData
1245
1332
  );
1246
1333
  const nsfw = response.data.results[0].entities[0].classes.nsfw;
1247
- logger.info(`api4ai图片审核结果:${nsfw}`);
1334
+ if (config.debugLog) {
1335
+ logger.info(`api4ai图片审核结果:${nsfw}`);
1336
+ }
1248
1337
  return {
1249
1338
  pass: nsfw <= config.api4ai.nsfwThreshold,
1250
1339
  score: nsfw,
@@ -1264,11 +1353,15 @@ var STRATEGIES = {
1264
1353
  });
1265
1354
  const bucket = config.bucket || "default-1250000000";
1266
1355
  const region = config.region?.toLowerCase() || "ap-chengdu";
1267
- logger.info(`腾讯云图片审核请求参数: Region=${region}, Bucket=${bucket}, BizType=${config.bizType}`);
1356
+ if (config.debugLog) {
1357
+ logger.info(`腾讯云图片审核请求参数: Region=${region}, Bucket=${bucket}, BizType=${config.bizType}`);
1358
+ }
1268
1359
  try {
1269
1360
  const imageBase64 = Buffer.from(buffer).toString("base64");
1270
1361
  const url = `https://${bucket}.ci.${region}.myqcloud.com/image/auditing`;
1271
- logger.info(`腾讯云图片审核请求URL: ${url}`);
1362
+ if (config.debugLog) {
1363
+ logger.info(`腾讯云图片审核请求URL: ${url}`);
1364
+ }
1272
1365
  const requestData = {
1273
1366
  Input: {
1274
1367
  Content: imageBase64
@@ -1298,13 +1391,17 @@ var STRATEGIES = {
1298
1391
  }
1299
1392
  );
1300
1393
  });
1301
- logger.info(`腾讯云图片审核响应:`, JSON.stringify(response));
1394
+ if (config.debugLog) {
1395
+ logger.info(`腾讯云图片审核响应:`, JSON.stringify(response));
1396
+ }
1302
1397
  const result = response.JobsDetail || response.Response && response.Response.JobsDetail;
1303
1398
  if (!result) {
1304
1399
  throw new Error("审核结果解析失败,未找到JobsDetail字段");
1305
1400
  }
1306
1401
  const label = result.Label;
1307
- logger.info(`腾讯云图片审核结果: Label=${label}`);
1402
+ if (config.debugLog) {
1403
+ logger.info(`腾讯云图片审核结果: Label=${label}`);
1404
+ }
1308
1405
  return {
1309
1406
  pass: label === "Normal",
1310
1407
  score: label === "Normal" ? 0 : 1,
@@ -1322,7 +1419,9 @@ var STRATEGIES = {
1322
1419
  if (error.statusCode) {
1323
1420
  errorMessage += ` (状态码: ${error.statusCode})`;
1324
1421
  }
1325
- logger.error(`腾讯云图片审核失败详情:`, JSON.stringify(error, Object.getOwnPropertyNames(error)));
1422
+ if (config.debugLog) {
1423
+ logger.error(`腾讯云图片审核失败详情:`, JSON.stringify(error, Object.getOwnPropertyNames(error)));
1424
+ }
1326
1425
  }
1327
1426
  logger.error(`腾讯云图片审核失败: ${errorMessage}`);
1328
1427
  throw new Error(errorMessage);
@@ -1343,7 +1442,9 @@ async function auditImage(ctx, data, config) {
1343
1442
  buffer = Buffer.from(data);
1344
1443
  }
1345
1444
  const imageSizeKB = Math.round(buffer.length / 1024);
1346
- ctx.logger.debug(`图片大小: ${imageSizeKB}KB`);
1445
+ if (config.debugLog) {
1446
+ ctx.logger.debug(`图片大小: ${imageSizeKB}KB`);
1447
+ }
1347
1448
  const maxSizeKB = config.imageAudit?.maxSizeKB || 4096;
1348
1449
  if (imageSizeKB > maxSizeKB) {
1349
1450
  ctx.logger.warn(`图片大小(${imageSizeKB}KB)超过限制(${maxSizeKB}KB),可能影响审核性能`);
@@ -1373,7 +1474,9 @@ async function auditImage(ctx, data, config) {
1373
1474
  if (!result.processingTime) {
1374
1475
  result.processingTime = totalProcessingTime;
1375
1476
  }
1376
- ctx.logger.debug(`图片审核完成,总耗时: ${totalProcessingTime}ms`);
1477
+ if (config.debugLog) {
1478
+ ctx.logger.debug(`图片审核完成,总耗时: ${totalProcessingTime}ms`);
1479
+ }
1377
1480
  return result;
1378
1481
  } catch (error) {
1379
1482
  let errorMessage = "审核失败";
@@ -1387,7 +1490,7 @@ async function auditImage(ctx, data, config) {
1387
1490
  if (error.statusCode) {
1388
1491
  errorMessage += ` (状态码: ${error.statusCode})`;
1389
1492
  }
1390
- if (config.imageAudit?.debug) {
1493
+ if (config.debugLog) {
1391
1494
  ctx.logger.error(`图片审核失败详情:`, JSON.stringify(error, Object.getOwnPropertyNames(error)));
1392
1495
  } else {
1393
1496
  ctx.logger.error(`图片审核失败: ${errorMessage}`);
@@ -1477,6 +1580,56 @@ var inject = {
1477
1580
  required: ["http"],
1478
1581
  optional: ["translator"]
1479
1582
  };
1583
+ function extractOptionsFromUndesired(undesired) {
1584
+ const extractedOptions = {};
1585
+ let cleanedUndesired = undesired.trim();
1586
+ const optionPatterns = [
1587
+ // 带引号的选项(优先级最高)
1588
+ { pattern: /-K\s+["']([^"']*)["']/g, key: "chars" },
1589
+ { pattern: /-m\s+["']([^"']*)["']/g, key: "model" },
1590
+ { pattern: /-s\s+["']([^"']*)["']/g, key: "sampler" },
1591
+ { pattern: /-r\s+["']([^"']*)["']/g, key: "resolution" },
1592
+ { pattern: /-o\s+["']([^"']*)["']/g, key: "output" },
1593
+ { pattern: /-C\s+["']([^"']*)["']/g, key: "scheduler" },
1594
+ // 不带引号的选项
1595
+ { pattern: /-K\s+(\S+)/g, key: "chars" },
1596
+ { pattern: /-m\s+(\w+)/g, key: "model" },
1597
+ { pattern: /-s\s+(\w+)/g, key: "sampler" },
1598
+ { pattern: /-r\s+([\w\d]+)/g, key: "resolution" },
1599
+ { pattern: /-o\s+(\w+)/g, key: "output" },
1600
+ { pattern: /-O\s*/g, key: "override", value: true },
1601
+ { pattern: /-x\s+(\d+)/g, key: "seed" },
1602
+ { pattern: /-t\s+(\d+)/g, key: "steps" },
1603
+ { pattern: /-c\s+([\d.]+)/g, key: "scale" },
1604
+ { pattern: /-R\s+([\d.]+)/g, key: "rescale" },
1605
+ { pattern: /-n\s+([\d.]+)/g, key: "noise" },
1606
+ { pattern: /-N\s+([\d.]+)/g, key: "strength" },
1607
+ { pattern: /-H\s*/g, key: "hiresFix", value: true },
1608
+ { pattern: /-S\s*/g, key: "smea", value: true },
1609
+ { pattern: /-d\s*/g, key: "smeaDyn", value: true },
1610
+ { pattern: /-C\s+(\w+)/g, key: "scheduler" },
1611
+ { pattern: /-D\s*/g, key: "decrisper", value: true },
1612
+ { pattern: /-T\s*/g, key: "noTranslator", value: true },
1613
+ { pattern: /-i\s+(\d+)/g, key: "iterations" },
1614
+ { pattern: /-b\s+(\d+)/g, key: "batch" },
1615
+ { pattern: /-I\s*/g, key: "ignoreSpace", value: true }
1616
+ ];
1617
+ for (const { pattern, key, value } of optionPatterns) {
1618
+ const matches = [...cleanedUndesired.matchAll(pattern)];
1619
+ for (const match of matches) {
1620
+ if (value !== void 0) {
1621
+ extractedOptions[key] = value;
1622
+ } else if (match[1]) {
1623
+ extractedOptions[key] = match[1];
1624
+ }
1625
+ cleanedUndesired = cleanedUndesired.replace(match[0], "").trim();
1626
+ }
1627
+ }
1628
+ cleanedUndesired = cleanedUndesired.replace(/\s+/g, " ").trim();
1629
+ cleanedUndesired = cleanedUndesired.replace(/^["']|["']$/g, "").trim();
1630
+ return { cleanedUndesired, extractedOptions };
1631
+ }
1632
+ __name(extractOptionsFromUndesired, "extractOptionsFromUndesired");
1480
1633
  function apply(ctx, config) {
1481
1634
  const userTasks = /* @__PURE__ */ Object.create(null);
1482
1635
  const userCooldowns = /* @__PURE__ */ Object.create(null);
@@ -2064,7 +2217,7 @@ function apply(ctx, config) {
2064
2217
  ctx.logger.warn(err);
2065
2218
  }
2066
2219
  }
2067
- const [errPath, prompt, uc] = parseInput(session, input, config, options2.override);
2220
+ const [errPath, prompt, uc] = parseInput(session, input, config, options2.override, options2.undesired);
2068
2221
  if (errPath) return session.text(errPath);
2069
2222
  let token;
2070
2223
  try {
@@ -2633,8 +2786,18 @@ function apply(ctx, config) {
2633
2786
  const cmd = ctx.command("novelai [prompts...]").alias("nai").alias("imagine").alias("nai4", { options: { model: "nai-v4-full", sampler: "k_euler_a", iterations: 1, batch: 1 } }).alias("nai4c", { options: { model: "nai-v4-curated-preview", sampler: "k_euler_a", iterations: 1, batch: 1 } }).alias("nai4-5c", { options: { model: "nai-v4-5-curated", sampler: "k_euler_a", iterations: 1, batch: 1 } }).alias("nai4-5", { options: { model: "nai-v4-5-full", sampler: "k_euler_a", iterations: 1, batch: 1 } }).userFields(["authority"]).shortcut("imagine", { i18n: true, fuzzy: true }).shortcut("enhance", { i18n: true, fuzzy: true, options: { enhance: true } }).option("enhance", "-e", { hidden: some(restricted, thirdParty, noImage) }).option("model", "-m <model>", { type: models, hidden: thirdParty }).option("resolution", "-r <resolution>", { type: resolution2 }).option("output", "-o", { type: ["minimal", "default", "verbose"] }).option("override", "-O", { hidden: restricted }).option("sampler", "-s <sampler>").option("seed", "-x <seed:number>").option("steps", "-t <step>", { type: step, hidden: restricted }).option("scale", "-c <scale:number>").option("rescale", "-R <rescale:number>").option("noise", "-n <noise:number>", { hidden: some(restricted, thirdParty) }).option("strength", "-N <strength:number>", { hidden: restricted }).option("hiresFix", "-H", { hidden: /* @__PURE__ */ __name(() => config.type !== "sd-webui", "hidden") }).option("hiresFixSteps", "<step>", { type: step, hidden: /* @__PURE__ */ __name(() => config.type !== "sd-webui", "hidden") }).option("smea", "-S", { hidden: /* @__PURE__ */ __name(() => config.model !== "nai-v3", "hidden") }).option("smeaDyn", "-d", { hidden: /* @__PURE__ */ __name(() => config.model !== "nai-v3", "hidden") }).option("scheduler", "-C <scheduler:string>", {
2634
2787
  hidden: /* @__PURE__ */ __name(() => config.type === "naifu", "hidden"),
2635
2788
  type: ["token", "login"].includes(config.type) ? scheduler.nai : config.type === "sd-webui" ? scheduler.sd : config.type === "stable-horde" ? scheduler.horde : []
2636
- }).option("decrisper", "-D", { hidden: thirdParty }).option("undesired", "-u <undesired>").option("noTranslator", "-T", { hidden: /* @__PURE__ */ __name(() => !ctx.translator || !config.translator, "hidden") }).option("iterations", "-i <iterations:posint>", { fallback: 1, hidden: /* @__PURE__ */ __name(() => config.maxIterations <= 1, "hidden") }).option("batch", "-b <batch:option>", { fallback: 1, hidden: /* @__PURE__ */ __name(() => config.maxIterations <= 1, "hidden") }).option("chars", "-K <chars>").option("ignoreSpace", "-I", { hidden: true }).action(async ({ session, options: options2, name: name2 }, ...prompts) => {
2637
- const input = prompts.join(" ");
2789
+ }).option("decrisper", "-D", { hidden: thirdParty }).option("undesired", "-u <undesired:text>").option("noTranslator", "-T", { hidden: /* @__PURE__ */ __name(() => !ctx.translator || !config.translator, "hidden") }).option("iterations", "-i <iterations:posint>", { fallback: 1, hidden: /* @__PURE__ */ __name(() => config.maxIterations <= 1, "hidden") }).option("batch", "-b <batch:option>", { fallback: 1, hidden: /* @__PURE__ */ __name(() => config.maxIterations <= 1, "hidden") }).option("chars", "-K <chars>").option("ignoreSpace", "-I", { hidden: true }).action(async ({ session, options: options2, name: name2 }, ...prompts) => {
2790
+ let input = prompts.join(" ");
2791
+ if (options2.undesired) {
2792
+ const { cleanedUndesired, extractedOptions } = extractOptionsFromUndesired(options2.undesired);
2793
+ options2.undesired = cleanedUndesired;
2794
+ Object.assign(options2, extractedOptions);
2795
+ if (config.debugLog) {
2796
+ ctx.logger.info(`[Undesired Debug] 原始 undesired: ${options2.undesired}`);
2797
+ ctx.logger.info(`[Undesired Debug] 清理后 undesired: ${cleanedUndesired}`);
2798
+ ctx.logger.info(`[Undesired Debug] 提取的选项: ${JSON.stringify(extractedOptions)}`);
2799
+ }
2800
+ }
2638
2801
  if (config.debugLog) {
2639
2802
  ctx.logger.info(`[Characters Debug] 接收到的 prompts 数组: ${JSON.stringify(prompts)}`);
2640
2803
  ctx.logger.info(`[Characters Debug] 接收到的 input: ${input}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-hhs-huatu",
3
3
  "description": "Generate images by diffusion models",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/readme.md CHANGED
@@ -32,17 +32,23 @@ nai4-5 场景 -K "1girl, smile@B3 --uc:frown;1boy, tall@D3 --uc:short"
32
32
  nai4c 场景 -K "角色1@A1;角色2@E5"
33
33
  ```
34
34
 
35
- **位置坐标说明:**
36
- A B C D E
37
- 1 [A1] [B1] [C1] [D1] [E1] (顶)
38
- 2 [A2] [B2] [C2] [D2] [E2]
39
- 3 [A3] [B3] [C3] [D3] [E3]
40
- 4 [A4] [B4] [C4] [D4] [E4]
41
- 5 [A5] [B5] [C5] [D5] [E5] (底)
42
- (左) (右)
35
+ **位置坐标表(5×5网格):**
36
+ ```
37
+ A B C D E
38
+ 1 A1 B1 C1 D1 E1 (顶部)
39
+ 2 A2 B2 C2 D2 E2
40
+ 3 A3 B3 C3 D3 E3 (中心)
41
+ 4 A4 B4 C4 D4 E4
42
+ 5 A5 B5 C5 D5 E5 (底部)
43
+ (左) (右)
44
+ ```
45
+
46
+ **语法说明:**
43
47
  - 格式:`A-E`(横向) + `1-5`(纵向),如 `A1`(左上) 到 `E5`(右下)
44
48
  - 默认位置:`C3`(中心)
45
49
  - 分隔符:`;` 或 `;`(支持中英文分号)
50
+ - 位置:`@位置` (如 @B3),可选
51
+ - 负向提示:`--uc:xxx`,可选
46
52
 
47
53
  详细文档请查看插件目录下的 `CHARACTERS_USAGE.md`。
48
54