koishi-plugin-hhs-huatu 1.2.2 → 1.2.4

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.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/lib/config.d.ts CHANGED
@@ -175,7 +175,7 @@ interface ParamConfig {
175
175
  export interface Config extends PromptConfig, ParamConfig {
176
176
  type: 'token' | 'login' | 'naifu' | 'sd-webui' | 'stable-horde' | 'comfyui';
177
177
  token?: string | string[];
178
- tokenStrategy?: 'round-robin' | 'random' | 'fallback' | 'parallel';
178
+ tokenStrategy?: 'round-robin' | 'first-available' | 'random' | 'fallback' | 'parallel';
179
179
  email?: string;
180
180
  password?: string;
181
181
  authLv?: Computed<number>;
File without changes
package/lib/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Context } from 'koishi';
2
2
  import { Config } from './config';
3
- export declare const usage = "\n# \uD83C\uDFA8 hhs-huatu \u63D2\u4EF6\n\n> **\u57FA\u4E8E [novelai-bot](https://bot.novelai.dev/) \u7684\u589E\u5F3A\u7248 AI \u7ED8\u56FE\u63D2\u4EF6\uFF0C\u63D0\u4F9B\u66F4\u667A\u80FD\u3001\u4FBF\u6377\u7684 NovelAI \u4F53\u9A8C\u3002**\n\n[![](https://img.shields.io/badge/QQ\u7FA4-112879548-blue)](https://qm.qq.com/q/4nKKvckKbu) [![](https://img.shields.io/badge/GitHub-\u4ED3\u5E93\u5730\u5740-black)](https://github.com/hhs2275/koishi-plugin-hhs-huatu)\n\n### \u2728 \u6838\u5FC3\u4EAE\u70B9\n\u65B0\u7248\u672C\u6539\u7528\u6570\u636E\u5E93\u3002\u4F60\u7684\u4F1A\u5458\u6570\u636E\u5728 \u4F60\u7684 Koishi \u6839\u76EE\u5F55/data/hhs-huatu-user-data.json\u3002\u628A\u5B83\u653E\u5728hhs-huatu-import\u6587\u4EF6\u5939\u4E0B\u9762\uFF0C\u7136\u540E\u53D1\u201C\u4F1A\u5458\u8C03\u8BD5--import\u201D\u6307\u4EE4\uFF0C\u5C31\u53EF\u4EE5\u628A\u6570\u636E\u5BFC\u5165\u6570\u636E\u5E93\u3002\n\n\u672C\u63D2\u4EF6\u9488\u5BF9 **NovelAI V4 / V4.5 / V5** \u6A21\u578B\u8FDB\u884C\u4E86\u6DF1\u5EA6\u9002\u914D\uFF0C\u9884\u8BBE\u5FEB\u6377\u6307\u4EE4\uFF0C\u52A9\u4F60\u5FEB\u901F\u5207\u6362\u6A21\u578B\uFF1A\n\n| \u6307\u4EE4 | \u5BF9\u5E94\u6A21\u578B (Model) | \u8BF4\u660E |\n| :--- | :--- | :--- |\n| `nai4` | `nai-diffusion-4-full` | V4 \u5168\u91CF\u6A21\u578B |\n| `nai4c` | `nai-diffusion-4-curated` | V4 \u7CBE\u9009\u6A21\u578B |\n| `nai4-5` | `nai-diffusion-4-5-full` | V4.5 \u5168\u91CF\u6A21\u578B |\n| `nai4-5c` | `nai-diffusion-4-5-curated` | V4.5 \u7CBE\u9009\u6A21\u578B |\n| `nai5` | `nai-diffusion-5-full` | V5 \u5168\u91CF\u6A21\u578B |\n| `nai5c` | `nai-diffusion-5-curated` | V5 \u7CBE\u9009\u6A21\u578B |\n\n### \uD83D\uDEE0\uFE0F \u529F\u80FD\u5217\u8868\n\n**\uD83C\uDFA8 \u7ED8\u56FE\u6838\u5FC3**\n* **\u5168\u9762\u652F\u6301**\uFF1A\u6587\u751F\u56FE\u3001\u56FE\u751F\u56FE\u57FA\u7840\u529F\u80FD\u5B8C\u6574\u3002\n* **\u5C40\u90E8\u91CD\u7ED8 (Inpaint)**\uFF1A\u652F\u6301\u5BF9\u56FE\u7247\u7279\u5B9A\u533A\u57DF\u8FDB\u884C\u91CD\u7ED8\u3002\n* **\u5BFC\u6F14\u5DE5\u5177**\uFF1Anovelai\u5B98\u7F51\u7684\u5BFC\u6F14\u5DE5\u5177\u529F\u80FD\u3002\n* **V4 \u89D2\u8272\u63D0\u793A\u8BCD**\uFF1Anovelai\u5B98\u7F51\u7684\u591A\u89D2\u8272\u63D0\u793A\u8BCD\u7CFB\u7EDF\u3002\n\n**\u2699\uFE0F \u7CFB\u7EDF\u4E0E\u7BA1\u7406**\n* **\u9AD8\u5E76\u53D1\u4F18\u5316**\uFF1A\u5185\u7F6E\u961F\u5217\u7CFB\u7EDF\u4E0E Token \u6C60\u8F6E\u8BE2\uFF0C\u591A\u8D26\u53F7\u8D1F\u8F7D\u5747\u8861\u3002\n* **\u4F1A\u5458\u7BA1\u7406**\uFF1A\u652F\u6301\u7528\u6237\u5206\u7EA7\u7BA1\u7406\uFF1B\u53EF\u914D\u7F6E\u4EC5\u4F1A\u5458\u4F7F\u7528\u4F1A\u6D88\u8017\u914D\u989D\u7684 nai5 / nai5c\u3002\n* **\u667A\u80FD\u5BA1\u6838**\uFF1A\u96C6\u6210\u817E\u8BAF AI \u6216 API4AI\uFF0C\u81EA\u52A8\u8FC7\u6EE4\u8FDD\u89C4\u5185\u5BB9\u3002\n\n### \uD83D\uDDD3\uFE0F \u5F00\u53D1\u8BA1\u5212\n- [\u221A] **\u70B9\u6570\u63A7\u5236\u7CFB\u7EDF**\uFF1A\u7CBE\u7EC6\u5316\u63A7\u5236\u7528\u6237\u70B9\u6570\u6D88\u8017\u8FDB\u884C\u672C\u5730\u8BA1\u7B97\uFF0C\u53EF\u80FD\u5B58\u5728\u8BEF\u5DEE\uFF0C\u8BF7\u4EE5\u5B9E\u9645\u6263\u8D39\u4E3A\u51C6\u3002\n- [x] ~~**\u6C1B\u56F4\u4F20\u8F93\u529F\u80FD**\uFF1A\u5B9E\u73B0novelai\u5B98\u7F51\u7684\u6C1B\u56F4\u4F20\u8F93\u529F\u80FD\u3002v4\u7248\u672C\u7684\u98CE\u5473\u4F20\u8F93api\u4E0D\u5141\u8BB8\u76F4\u63A5\u53D1\u56FE\uFF0C\u9700\u8981\u5148\u53BB\u5B98\u7F51\u751F\u6210\u5BF9\u5E94\u6587\u4EF6\uFF0C\u6211\u505A\u4E0D\u5230\u3002~~\n- [\u221A] **\u89D2\u8272\u53C2\u8003\u529F\u80FD**\uFF1A\u5B9E\u73B0novelai\u5B98\u7F51\u7684\u89D2\u8272\u53C2\u8003\u529F\u80FD\u3002\uFF08\u5DF2\u88AB\u7CBE\u786E\u53C2\u8003\u66FF\u4EE3\uFF09\n---\n\n### \uD83D\uDCD6 \u66F4\u591A\u8D44\u6E90\n* \u8BE6\u7EC6\u6559\u7A0B\u8BF7\u79FB\u6B65 [GitHub \u4ED3\u5E93](https://github.com/hhs2275/koishi-plugin-hhs-huatu)\n* \u9047\u5230\u95EE\u9898\uFF1F\u6B22\u8FCE\u52A0\u5165\u4EA4\u6D41\u7FA4\uFF1A[112879548](https://qm.qq.com/q/4nKKvckKbu) (\u8054\u7CFB\u7FA4\u4E3B\u53CD\u9988\u95EE\u9898/\u63D0\u4EA4\u5EFA\u8BAE/\u6109\u5FEB\u73A9\u800D)\n";
3
+ export declare const usage = "\n# \uD83C\uDFA8 hhs-huatu \u63D2\u4EF6\n\n> **\u57FA\u4E8E [novelai-bot](https://bot.novelai.dev/) \u7684\u589E\u5F3A\u7248 AI \u7ED8\u56FE\u63D2\u4EF6\uFF0C\u63D0\u4F9B\u66F4\u667A\u80FD\u3001\u4FBF\u6377\u7684 NovelAI \u4F53\u9A8C\u3002**\n\n[![](https://img.shields.io/badge/QQ\u7FA4-112879548-blue)](https://qm.qq.com/q/4nKKvckKbu) [![](https://img.shields.io/badge/GitHub-\u4ED3\u5E93\u5730\u5740-black)](https://github.com/hhs2275/koishi-plugin-hhs-huatu)\n\n### \u2728 \u6838\u5FC3\u4EAE\u70B9\n\u65B0\u7248\u672C\u6539\u7528\u6570\u636E\u5E93\u3002\u4F60\u7684\u4F1A\u5458\u6570\u636E\u5728 \u4F60\u7684 Koishi \u6839\u76EE\u5F55/data/hhs-huatu-user-data.json\u3002\u628A\u5B83\u653E\u5728hhs-huatu-import\u6587\u4EF6\u5939\u4E0B\u9762\uFF0C\u7136\u540E\u53D1\u201C\u4F1A\u5458\u8C03\u8BD5--import\u201D\u6307\u4EE4\uFF0C\u5C31\u53EF\u4EE5\u628A\u6570\u636E\u5BFC\u5165\u6570\u636E\u5E93\u3002\n\n\u672C\u63D2\u4EF6\u9488\u5BF9 **NovelAI V4 / V4.5 / V5** \u6A21\u578B\u8FDB\u884C\u4E86\u6DF1\u5EA6\u9002\u914D\uFF0C\u9884\u8BBE\u5FEB\u6377\u6307\u4EE4\uFF0C\u52A9\u4F60\u5FEB\u901F\u5207\u6362\u6A21\u578B\uFF1A\n\n| \u6307\u4EE4 | \u5BF9\u5E94\u6A21\u578B (Model) | \u8BF4\u660E |\n| :--- | :--- | :--- |\n| `nai4` | `nai-diffusion-4-full` | V4 \u5168\u91CF\u6A21\u578B |\n| `nai4c` | `nai-diffusion-4-curated` | V4 \u7CBE\u9009\u6A21\u578B |\n| `nai4-5` | `nai-diffusion-4-5-full` | V4.5 \u5168\u91CF\u6A21\u578B |\n| `nai4-5c` | `nai-diffusion-4-5-curated` | V4.5 \u7CBE\u9009\u6A21\u578B |\n| `nai5` | `nai-diffusion-5-full` | V5 \u5168\u91CF\u6A21\u578B |\n| `nai5c` | `nai-diffusion-5-curated` | V5 \u7CBE\u9009\u6A21\u578B |\n\n### \uD83D\uDEE0\uFE0F \u529F\u80FD\u5217\u8868\n\n**\uD83C\uDFA8 \u7ED8\u56FE\u6838\u5FC3**\n* **\u5168\u9762\u652F\u6301**\uFF1A\u6587\u751F\u56FE\u3001\u56FE\u751F\u56FE\u57FA\u7840\u529F\u80FD\u5B8C\u6574\u3002\n* **\u5C40\u90E8\u91CD\u7ED8 (Inpaint)**\uFF1A\u652F\u6301\u5BF9\u56FE\u7247\u7279\u5B9A\u533A\u57DF\u8FDB\u884C\u91CD\u7ED8\u3002\n* **\u5BFC\u6F14\u5DE5\u5177**\uFF1Anovelai\u5B98\u7F51\u7684\u5BFC\u6F14\u5DE5\u5177\u529F\u80FD\u3002\n* **V4 \u89D2\u8272\u63D0\u793A\u8BCD**\uFF1Anovelai\u5B98\u7F51\u7684\u591A\u89D2\u8272\u63D0\u793A\u8BCD\u7CFB\u7EDF\u3002\n\n**\u2699\uFE0F \u7CFB\u7EDF\u4E0E\u7BA1\u7406**\n* **\u9AD8\u5E76\u53D1\u4F18\u5316**\uFF1A\u5185\u7F6E\u961F\u5217\u7CFB\u7EDF\u4E0E Token \u6C60\u8F6E\u8BE2\uFF0C\u591A\u8D26\u53F7\u8D1F\u8F7D\u5747\u8861\u3002\n* **\u4F1A\u5458\u7BA1\u7406**\uFF1A\u652F\u6301\u7528\u6237\u5206\u7EA7\u7BA1\u7406\uFF1B\u53EF\u914D\u7F6E\u4EC5\u4F1A\u5458\u4F7F\u7528\u4F1A\u6D88\u8017\u914D\u989D\u7684 nai5 / nai5c\u3002\n* **\u667A\u80FD\u5BA1\u6838**\uFF1A\u96C6\u6210\u817E\u8BAF AI \u6216 API4AI\uFF0C\u81EA\u52A8\u8FC7\u6EE4\u8FDD\u89C4\u5185\u5BB9\u3002\n\n### \uD83D\uDDD3\uFE0F \u5F00\u53D1\u8BA1\u5212\n- [\u221A] **\u70B9\u6570\u63A7\u5236\u7CFB\u7EDF**\uFF1A\u6309 NovelAI \u5B98\u7F51\u524D\u7AEF\u516C\u5F0F\u9884\u6263\u672C\u5730\u70B9\u6570\uFF08V5 \u4E3A V4.5 \u7684 1.5 \u500D\uFF09\u3002\u5B9E\u9645\u6263\u8D39\u4EE5 NovelAI \u8D26\u6237\u4E3A\u51C6\u3002\n- [x] ~~**\u6C1B\u56F4\u4F20\u8F93\u529F\u80FD**\uFF1A\u5B9E\u73B0novelai\u5B98\u7F51\u7684\u6C1B\u56F4\u4F20\u8F93\u529F\u80FD\u3002v4\u7248\u672C\u7684\u98CE\u5473\u4F20\u8F93api\u4E0D\u5141\u8BB8\u76F4\u63A5\u53D1\u56FE\uFF0C\u9700\u8981\u5148\u53BB\u5B98\u7F51\u751F\u6210\u5BF9\u5E94\u6587\u4EF6\uFF0C\u6211\u505A\u4E0D\u5230\u3002~~\n- [\u221A] **\u89D2\u8272\u53C2\u8003\u529F\u80FD**\uFF1A\u5B9E\u73B0novelai\u5B98\u7F51\u7684\u89D2\u8272\u53C2\u8003\u529F\u80FD\u3002\uFF08\u5DF2\u88AB\u7CBE\u786E\u53C2\u8003\u66FF\u4EE3\uFF09\n---\n\n### \uD83D\uDCD6 \u66F4\u591A\u8D44\u6E90\n* \u8BE6\u7EC6\u6559\u7A0B\u8BF7\u79FB\u6B65 [GitHub \u4ED3\u5E93](https://github.com/hhs2275/koishi-plugin-hhs-huatu)\n* \u9047\u5230\u95EE\u9898\uFF1F\u6B22\u8FCE\u52A0\u5165\u4EA4\u6D41\u7FA4\uFF1A[112879548](https://qm.qq.com/q/4nKKvckKbu) (\u8054\u7CFB\u7FA4\u4E3B\u53CD\u9988\u95EE\u9898/\u63D0\u4EA4\u5EFA\u8BAE/\u6109\u5FEB\u73A9\u800D)\n";
4
4
  export * from './config';
5
5
  export declare const reactive = true;
6
6
  export declare const name = "hhs-huatu";
package/lib/index.js CHANGED
@@ -1083,9 +1083,18 @@ var Config = import_koishi2.Schema.intersect([
1083
1083
  import_koishi2.Schema.object({
1084
1084
  type: import_koishi2.Schema.const("token"),
1085
1085
  token: import_koishi2.Schema.union([
1086
- import_koishi2.Schema.string().description("授权令牌。"),
1087
- import_koishi2.Schema.array(import_koishi2.Schema.string()).description("多个授权令牌(使用令牌池,每个令牌同时仅处理一个任务)")
1088
- ]).role("secret").required()
1086
+ import_koishi2.Schema.string().role("secret").description("单个授权令牌。"),
1087
+ import_koishi2.Schema.array(
1088
+ import_koishi2.Schema.string().role("secret").default("")
1089
+ ).role("table").default([""]).description("多个授权令牌。每行填写一个令牌,可直接新增空白行,不需要手写 JSON 引号。")
1090
+ ]).required(),
1091
+ tokenStrategy: import_koishi2.Schema.union([
1092
+ import_koishi2.Schema.const("round-robin").description("轮询(推荐:按顺序轮流使用;目标令牌忙时跳过,下一次从后续位置继续)"),
1093
+ import_koishi2.Schema.const("first-available").description("首个可用(总是优先前面的空闲令牌,可能让第一个令牌消耗更多)"),
1094
+ import_koishi2.Schema.const("random").description("随机(从空闲令牌中随机选择,长期大致均衡但不保证严格轮流)"),
1095
+ import_koishi2.Schema.const("fallback").description("回退(兼容旧配置:按首个可用分配;请求失败不会自动换令牌重试)"),
1096
+ import_koishi2.Schema.const("parallel").description("并行(兼容旧配置:按空闲令牌并发处理,并发上限受令牌数量限制)")
1097
+ ]).description("多个授权令牌的使用策略(token 为数组时生效;单个令牌时无影响)").default("round-robin")
1089
1098
  }),
1090
1099
  import_koishi2.Schema.object({
1091
1100
  type: import_koishi2.Schema.const("login"),
@@ -1512,12 +1521,15 @@ var QueueSystem = class {
1512
1521
  this.generateImageFn = generateImageFn;
1513
1522
  this.membershipSystem = membershipSystem || null;
1514
1523
  if (Array.isArray(config.token) && config.token.length > 0) {
1515
- this.tokenPool = new Array(config.token.length).fill(false);
1524
+ this.tokenIndices = config.token.map((token, index) => typeof token === "string" && token.trim() ? index : -1).filter((index) => index >= 0);
1525
+ this.tokenPool = new Array(this.tokenIndices.length).fill(false);
1516
1526
  this.maxConcurrentTasks = this.tokenPool.length;
1517
1527
  } else if (typeof config.token === "string" && config.token) {
1528
+ this.tokenIndices = [0];
1518
1529
  this.tokenPool = [false];
1519
1530
  this.maxConcurrentTasks = 1;
1520
1531
  } else {
1532
+ this.tokenIndices = [0];
1521
1533
  this.tokenPool = [false];
1522
1534
  this.maxConcurrentTasks = 1;
1523
1535
  }
@@ -1537,7 +1549,6 @@ var QueueSystem = class {
1537
1549
  userLastTask = /* @__PURE__ */ Object.create(null);
1538
1550
  // 重画相关
1539
1551
  lastRedrawTime = 0;
1540
- usedTokenIndices = /* @__PURE__ */ new Set();
1541
1552
  redrawLock = false;
1542
1553
  redrawWaitQueue = [];
1543
1554
  // 最大并发任务数
@@ -1546,6 +1557,11 @@ var QueueSystem = class {
1546
1557
  membershipSystem = null;
1547
1558
  // Token管理相关
1548
1559
  tokenPool = [];
1560
+ // tokenPool 使用紧凑槽位,tokenIndices 保留配置数组中的真实下标,
1561
+ // 这样可以忽略配置中为了方便填写而保留的空白行。
1562
+ tokenIndices = [];
1563
+ // 轮询分配的起始索引。每次成功分配后指向下一个 token。
1564
+ nextTokenIndex = 0;
1549
1565
  // 获取锁
1550
1566
  async acquireRedrawLock() {
1551
1567
  if (!this.redrawLock) {
@@ -1567,29 +1583,34 @@ var QueueSystem = class {
1567
1583
  this.redrawLock = false;
1568
1584
  }
1569
1585
  }
1570
- // 生成唯一且未被最近使用的 token 索引
1571
- getUniqueTokenIndex(currentIndex, tokenCount) {
1572
- if (tokenCount <= 1) return 0;
1573
- if (this.usedTokenIndices.size >= tokenCount) {
1574
- this.usedTokenIndices.clear();
1575
- }
1576
- let newIndex = currentIndex;
1577
- let attempts = 0;
1578
- const maxAttempts = tokenCount * 2;
1579
- while (this.usedTokenIndices.has(newIndex) && attempts < maxAttempts) {
1580
- newIndex = (newIndex + 1) % tokenCount;
1581
- attempts++;
1582
- }
1583
- this.usedTokenIndices.add(newIndex);
1584
- return newIndex;
1585
- }
1586
- // 从 Token 池中获取一个空闲的 token 索引(并标记为占用)
1586
+ // 从 Token 池中获取一个空闲的 token 索引(并标记为占用)。
1587
+ // 轮询时从 nextTokenIndex 开始查找,因此即使任务是串行执行,
1588
+ // 也会在每次任务之间切换到下一个 token,而不是始终命中 token[0]。
1587
1589
  acquireTokenIndex() {
1588
1590
  if (!this.tokenPool.length) return null;
1589
- for (let i = 0; i < this.tokenPool.length; i++) {
1590
- if (!this.tokenPool[i]) {
1591
- this.tokenPool[i] = true;
1592
- return i;
1591
+ const strategy = this.config.tokenStrategy || "round-robin";
1592
+ if (strategy === "random") {
1593
+ const available = this.tokenPool.map((busy, slot2) => busy ? -1 : slot2).filter((slot2) => slot2 >= 0);
1594
+ if (!available.length) return null;
1595
+ const slot = available[Math.floor(Math.random() * available.length)];
1596
+ this.tokenPool[slot] = true;
1597
+ return this.tokenIndices[slot];
1598
+ }
1599
+ if (strategy === "round-robin") {
1600
+ for (let offset = 0; offset < this.tokenPool.length; offset++) {
1601
+ const slot = (this.nextTokenIndex + offset) % this.tokenPool.length;
1602
+ if (!this.tokenPool[slot]) {
1603
+ this.tokenPool[slot] = true;
1604
+ this.nextTokenIndex = (slot + 1) % this.tokenPool.length;
1605
+ return this.tokenIndices[slot];
1606
+ }
1607
+ }
1608
+ return null;
1609
+ }
1610
+ for (let slot = 0; slot < this.tokenPool.length; slot++) {
1611
+ if (!this.tokenPool[slot]) {
1612
+ this.tokenPool[slot] = true;
1613
+ return this.tokenIndices[slot];
1593
1614
  }
1594
1615
  }
1595
1616
  return null;
@@ -1598,8 +1619,9 @@ var QueueSystem = class {
1598
1619
  releaseTokenIndex(index) {
1599
1620
  if (index == null) return;
1600
1621
  if (!this.tokenPool.length) return;
1601
- if (index >= 0 && index < this.tokenPool.length) {
1602
- this.tokenPool[index] = false;
1622
+ const slot = this.tokenIndices.indexOf(index);
1623
+ if (slot >= 0) {
1624
+ this.tokenPool[slot] = false;
1603
1625
  }
1604
1626
  }
1605
1627
  // 提供给外部任务借用/归还 token 索引
@@ -1938,8 +1960,14 @@ var MembershipSystem = class {
1938
1960
  }
1939
1961
  // ========== 点数计算相关 ==========
1940
1962
  /**
1941
- * 计算预估消耗点数(基于 Opus 套餐)
1942
- * 社区逆向工程的 NAI Anlas 估算公式
1963
+ * 按 NovelAI 官网前端公式计算 Anlas。
1964
+ * 来源:novelai.net/_next/static/chunks/1052-*.js
1965
+ * SDXL / V4 / V4.5 / V5:
1966
+ * M = ceil(2.951823174884865e-6 * pixels + 5.753298233447344e-7 * pixels * steps)
1967
+ * M *= sm_dyn ? 1.4 : smea ? 1.2 : 1
1968
+ * V5 再乘 1.5
1969
+ * cost = max(ceil(M * strength), 2)
1970
+ * Opus 免费:无角色参考、像素 ≤ 1048576、步数 ≤ 28 时第一张为 0。
1943
1971
  */
1944
1972
  calculatePointsCost(params) {
1945
1973
  if (!this.config.pointsEnabled) return 0;
@@ -1952,18 +1980,19 @@ var MembershipSystem = class {
1952
1980
  strength = 1,
1953
1981
  isImg2Img = false,
1954
1982
  preciseRefCount = 0,
1955
- chargeOpusFreeRange = false
1983
+ chargeOpusFreeRange = false,
1984
+ model
1956
1985
  } = params;
1957
- const pixels = width * height;
1958
- if (!chargeOpusFreeRange && pixels <= 1048576 && steps <= 28) {
1959
- return preciseRefCount * 5;
1960
- }
1961
- let L = Math.ceil((2951823174884865e-30 * pixels + 5753298233447344e-22 * pixels * steps) * 1.21);
1962
- if (smeaDyn) L = Math.ceil(L * 1.4);
1963
- else if (smea) L = Math.ceil(L * 1.2);
1964
- if (isImg2Img) {
1965
- L = Math.max(Math.ceil(L * strength), 2);
1966
- }
1986
+ let pixels = width * height;
1987
+ if (pixels < 65536) pixels = 65536;
1988
+ const opusFree = !chargeOpusFreeRange && preciseRefCount <= 0 && pixels <= 1048576 && steps <= 28;
1989
+ if (opusFree) return 0;
1990
+ let L = Math.ceil(2951823174884865e-21 * pixels + 5753298233447344e-22 * pixels * steps);
1991
+ if (smeaDyn) L *= 1.4;
1992
+ else if (smea) L *= 1.2;
1993
+ if (this.isNai5Model(model)) L *= 1.5;
1994
+ const resolvedStrength = isImg2Img ? strength : 1;
1995
+ L = Math.max(Math.ceil(L * resolvedStrength), 2);
1967
1996
  L += preciseRefCount * 5;
1968
1997
  return L;
1969
1998
  }
@@ -3622,7 +3651,7 @@ var import_koishi9 = require("koishi");
3622
3651
  var import_koishi7 = require("koishi");
3623
3652
 
3624
3653
  // src/services/points.ts
3625
- function getUnitPointsCost(runtime, session, options2, width, height, isImg2Img, preciseRefCount, chargeOpusFreeRange = false) {
3654
+ function getUnitPointsCost(runtime, session, options2, width, height, isImg2Img, preciseRefCount, chargeOpusFreeRange = false, model) {
3626
3655
  const steps = options2.steps ?? session.resolve(isImg2Img ? runtime.config.imageSteps : runtime.config.textSteps) ?? 23;
3627
3656
  const resolvedSmeaDyn = options2.smeaDyn ?? runtime.config.smeaDyn ?? false;
3628
3657
  const resolvedSmea = (options2.smea ?? runtime.config.smea) || resolvedSmeaDyn;
@@ -3635,7 +3664,8 @@ function getUnitPointsCost(runtime, session, options2, width, height, isImg2Img,
3635
3664
  strength: options2.strength,
3636
3665
  isImg2Img,
3637
3666
  preciseRefCount,
3638
- chargeOpusFreeRange
3667
+ chargeOpusFreeRange,
3668
+ model: model ?? options2.model
3639
3669
  });
3640
3670
  }
3641
3671
  __name(getUnitPointsCost, "getUnitPointsCost");
@@ -3643,7 +3673,7 @@ function calculateTaskPointsCost(runtime, session, options2, width, height, isIm
3643
3673
  const uid = userId || session.userId;
3644
3674
  const resolvedModel = model ?? options2.model;
3645
3675
  const count = Math.max(1, drawCount);
3646
- const unitFree = getUnitPointsCost(runtime, session, options2, width, height, isImg2Img, preciseRefCount);
3676
+ const unitFree = getUnitPointsCost(runtime, session, options2, width, height, isImg2Img, preciseRefCount, false, resolvedModel);
3647
3677
  const perImage = [];
3648
3678
  if (!runtime.membershipSystem.isNai5Model(resolvedModel)) {
3649
3679
  for (let i = 0; i < count; i++) perImage.push(unitFree);
@@ -3651,7 +3681,7 @@ function calculateTaskPointsCost(runtime, session, options2, width, height, isIm
3651
3681
  }
3652
3682
  const overageCount = runtime.membershipSystem.getNai5OverageCount(uid, count);
3653
3683
  const freeCount = count - overageCount;
3654
- const unitOverage = overageCount > 0 ? getUnitPointsCost(runtime, session, options2, width, height, isImg2Img, preciseRefCount, true) : unitFree;
3684
+ const unitOverage = overageCount > 0 ? getUnitPointsCost(runtime, session, options2, width, height, isImg2Img, preciseRefCount, true, resolvedModel) : unitFree;
3655
3685
  for (let i = 0; i < freeCount; i++) perImage.push(unitFree);
3656
3686
  for (let i = 0; i < overageCount; i++) perImage.push(unitOverage);
3657
3687
  return {
@@ -3929,6 +3959,23 @@ function extractOptionsFromUndesired(undesired) {
3929
3959
  __name(extractOptionsFromUndesired, "extractOptionsFromUndesired");
3930
3960
  function registerNovelai(ctx, config, runtime) {
3931
3961
  const { membershipSystem, queueSystem, useFilter, useBackend, thirdParty, noImage, some, step, resolution: resolution2 } = runtime;
3962
+ const recentCommandMessages = /* @__PURE__ */ new Map();
3963
+ const commandDedupWindow = 3e4;
3964
+ const isDuplicateCommand = /* @__PURE__ */ __name((session) => {
3965
+ const messageId = session.messageId;
3966
+ if (!messageId) return false;
3967
+ const now = Date.now();
3968
+ for (const [key2, timestamp] of recentCommandMessages) {
3969
+ if (now - timestamp > commandDedupWindow) recentCommandMessages.delete(key2);
3970
+ }
3971
+ const key = [session.platform, session.selfId, session.channelId, session.userId, messageId].join(":");
3972
+ if (recentCommandMessages.has(key)) {
3973
+ ctx.logger.warn(`检测到重复绘图消息,已忽略:messageId=${messageId}`);
3974
+ return true;
3975
+ }
3976
+ recentCommandMessages.set(key, now);
3977
+ return false;
3978
+ }, "isDuplicateCommand");
3932
3979
  ctx.before("attach", (session) => {
3933
3980
  session.stripped.content = session.stripped.content.replace(
3934
3981
  /^(\S*?nai4)[\u2010-\u2015\u2212\uFE63\uFF0D](5c?)(?=\s|$)/i,
@@ -3939,6 +3986,7 @@ function registerNovelai(ctx, config, runtime) {
3939
3986
  hidden: /* @__PURE__ */ __name(() => config.type === "naifu", "hidden"),
3940
3987
  type: ["token", "login"].includes(config.type) ? scheduler.nai : config.type === "sd-webui" ? scheduler.sd : config.type === "stable-horde" ? scheduler.horde : []
3941
3988
  }).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("inpaint", "-M", { hidden: thirdParty }).option("preciseRef", "-P", { hidden: thirdParty }).option("preciseRefParams", "-p <params:string>").action(async ({ session, options: options2, name: name2 }, ...prompts) => {
3989
+ if (isDuplicateCommand(session)) return;
3942
3990
  let input = prompts.join(" ");
3943
3991
  const imageUrls = import_koishi9.h.select(session.elements ?? [], "img").map((el) => el.attrs.src);
3944
3992
  if (imageUrls.length) {
@@ -4289,26 +4337,9 @@ function registerRedraw(ctx, config, runtime) {
4289
4337
  try {
4290
4338
  const currentChannelId = session.channelId;
4291
4339
  const commandId = Date.now() % 1e4;
4292
- if (config.tokenStrategy === "round-robin" && Array.isArray(ctx.config.token)) {
4293
- if (!ctx.runtime) {
4294
- ctx.runtime = { currentTokenIndex: 0 };
4295
- }
4296
- const oldTokenIndex = ctx.runtime.currentTokenIndex;
4297
- const lastRedrawTime = queueSystem.getLastRedrawTime();
4298
- const timeSinceLastRedraw = now - lastRedrawTime;
4299
- let newTokenIndex;
4300
- if (timeSinceLastRedraw < 5e3) {
4301
- const timeOffset = timeSinceLastRedraw % ctx.config.token.length;
4302
- const randomOffset = Math.floor(Math.random() * ctx.config.token.length);
4303
- const baseIndex = (oldTokenIndex + timeOffset + randomOffset + commandId) % ctx.config.token.length;
4304
- newTokenIndex = queueSystem.getUniqueTokenIndex(baseIndex, ctx.config.token.length);
4305
- } else {
4306
- const randomOffset = Math.floor(Math.random() * ctx.config.token.length);
4307
- newTokenIndex = (oldTokenIndex + randomOffset + 1) % ctx.config.token.length;
4308
- }
4309
- ctx.runtime.currentTokenIndex = newTokenIndex;
4340
+ if (Array.isArray(ctx.config.token)) {
4310
4341
  queueSystem.setLastRedrawTime(now);
4311
- ctx.logger.debug(`重画命令(${commandId})开始,token 索引从 ${oldTokenIndex} 更新为 ${newTokenIndex},间隔: ${timeSinceLastRedraw}ms`);
4342
+ ctx.logger.debug(`重画命令(${commandId})开始,token 使用策略: ${config.tokenStrategy || "round-robin"}`);
4312
4343
  }
4313
4344
  const addRedrawTask = /* @__PURE__ */ __name(async (index, delay = 0) => {
4314
4345
  if (delay > 0) {
@@ -4324,61 +4355,12 @@ function registerRedraw(ctx, config, runtime) {
4324
4355
  taskSession.isRedraw = true;
4325
4356
  const taskUniqueId = commandId * 100 + index;
4326
4357
  if (Array.isArray(ctx.config.token)) {
4327
- if (config.tokenStrategy === "parallel") {
4328
- taskSession.runtime = {
4329
- currentTokenIndex: void 0,
4330
- tokenUsage: {},
4331
- // 空对象,避免共享引用
4332
- _timeStamp: Date.now() + index,
4333
- _taskId: taskUniqueId
4334
- };
4335
- ctx.logger.debug(`为重画任务 ${taskUniqueId} 创建独立的session对象,确保能够独立获取token`);
4336
- } else if (config.tokenStrategy === "round-robin") {
4337
- if (ctx.runtime) {
4338
- let forcedTokenIndex;
4339
- if (ctx.config.token.length <= 1) {
4340
- forcedTokenIndex = 0;
4341
- } else {
4342
- const baseIndex = ctx.runtime.currentTokenIndex;
4343
- const taskOffset = (index * 3 + taskUniqueId) % ctx.config.token.length;
4344
- const candidateIndex = (baseIndex + taskOffset) % ctx.config.token.length;
4345
- forcedTokenIndex = queueSystem.getUniqueTokenIndex(candidateIndex, ctx.config.token.length);
4346
- ctx.logger.debug(`重画任务 ${taskUniqueId} token索引计算: 基础=${baseIndex}, 任务偏移=${taskOffset}, 最终=${forcedTokenIndex}`);
4347
- }
4348
- taskSession.runtime = {
4349
- _timeStamp: Date.now() + index,
4350
- _redraw: true,
4351
- _forcedTokenIndex: forcedTokenIndex,
4352
- // 强制指定 token 索引
4353
- _taskIndex: index,
4354
- // 任务索引
4355
- _taskId: taskUniqueId
4356
- // 任务唯一ID
4357
- };
4358
- ctx.logger.debug(`为重画任务 ${taskUniqueId} 强制指定 token 索引: ${forcedTokenIndex}`);
4359
- } else {
4360
- taskSession.runtime = {
4361
- _timeStamp: Date.now() + index,
4362
- _redraw: true,
4363
- _taskId: taskUniqueId
4364
- };
4365
- ctx.logger.debug(`为重画任务 ${taskUniqueId} 创建轮询session对象,将使用下一个可用token`);
4366
- }
4367
- } else if (config.tokenStrategy === "random") {
4368
- taskSession.runtime = {
4369
- _timeStamp: Date.now() + index,
4370
- _redraw: true,
4371
- _taskId: taskUniqueId
4372
- };
4373
- ctx.logger.debug(`为重画任务 ${taskUniqueId} 创建随机策略session对象,将随机选择token`);
4374
- } else if (config.tokenStrategy === "fallback") {
4375
- taskSession.runtime = {
4376
- _timeStamp: Date.now() + index,
4377
- _redraw: true,
4378
- _taskId: taskUniqueId
4379
- };
4380
- ctx.logger.debug(`为重画任务 ${taskUniqueId} 创建备用策略session对象,将从第一个token开始尝试`);
4381
- }
4358
+ taskSession.runtime = {
4359
+ _timeStamp: Date.now() + index,
4360
+ _redraw: true,
4361
+ _taskIndex: index,
4362
+ _taskId: taskUniqueId
4363
+ };
4382
4364
  }
4383
4365
  const taskOptions = { ...lastTask.options };
4384
4366
  if (config.membershipEnabled && config.pointsEnabled) {
@@ -5709,7 +5691,7 @@ var usage = `
5709
5691
  * **智能审核**:集成腾讯 AI 或 API4AI,自动过滤违规内容。
5710
5692
 
5711
5693
  ### 🗓️ 开发计划
5712
- - [√] **点数控制系统**:精细化控制用户点数消耗进行本地计算,可能存在误差,请以实际扣费为准。
5694
+ - [√] **点数控制系统**:按 NovelAI 官网前端公式预扣本地点数(V5 为 V4.5 的 1.5 倍)。实际扣费以 NovelAI 账户为准。
5713
5695
  - [x] ~~**氛围传输功能**:实现novelai官网的氛围传输功能。v4版本的风味传输api不允许直接发图,需要先去官网生成对应文件,我做不到。~~
5714
5696
  - [√] **角色参考功能**:实现novelai官网的角色参考功能。(已被精确参考替代)
5715
5697
  ---
File without changes
File without changes
@@ -18,8 +18,14 @@ export declare class MembershipSystem {
18
18
  syncUserToDB(userId: string): Promise<void>;
19
19
  saveUserData(): Promise<void>;
20
20
  /**
21
- * 计算预估消耗点数(基于 Opus 套餐)
22
- * 社区逆向工程的 NAI Anlas 估算公式
21
+ * 按 NovelAI 官网前端公式计算 Anlas。
22
+ * 来源:novelai.net/_next/static/chunks/1052-*.js
23
+ * SDXL / V4 / V4.5 / V5:
24
+ * M = ceil(2.951823174884865e-6 * pixels + 5.753298233447344e-7 * pixels * steps)
25
+ * M *= sm_dyn ? 1.4 : smea ? 1.2 : 1
26
+ * V5 再乘 1.5
27
+ * cost = max(ceil(M * strength), 2)
28
+ * Opus 免费:无角色参考、像素 ≤ 1048576、步数 ≤ 28 时第一张为 0。
23
29
  */
24
30
  calculatePointsCost(params: {
25
31
  width: number;
@@ -31,6 +37,7 @@ export declare class MembershipSystem {
31
37
  isImg2Img?: boolean;
32
38
  preciseRefCount?: number;
33
39
  chargeOpusFreeRange?: boolean;
40
+ model?: string;
34
41
  }): number;
35
42
  /**
36
43
  * 导演工具点数计算
@@ -24,16 +24,16 @@ export declare class QueueSystem {
24
24
  pointsCost?: number;
25
25
  }>;
26
26
  private lastRedrawTime;
27
- private usedTokenIndices;
28
27
  private redrawLock;
29
28
  private redrawWaitQueue;
30
29
  maxConcurrentTasks: number;
31
30
  private membershipSystem;
32
31
  private tokenPool;
32
+ private tokenIndices;
33
+ private nextTokenIndex;
33
34
  constructor(ctx: Context, config: Config, generateImageFn: (session: Session<'authority'>, options: any, input: string) => Promise<any>, membershipSystem?: MembershipSystem, initialTokenUsage?: Dict<boolean>);
34
35
  acquireRedrawLock(): Promise<void>;
35
36
  releaseRedrawLock(): void;
36
- getUniqueTokenIndex(currentIndex: number, tokenCount: number): number;
37
37
  private acquireTokenIndex;
38
38
  private releaseTokenIndex;
39
39
  borrowTokenIndex(): number | null;
package/lib/runtime.d.ts CHANGED
File without changes
File without changes
File without changes
File without changes
package/lib/types.d.ts CHANGED
File without changes
package/lib/utils.d.ts CHANGED
File without changes
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": "1.2.2",
4
+ "version": "1.2.4",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/readme.md CHANGED
@@ -139,13 +139,14 @@ nai4-5 <提示词> -P -p "cs,1,1;c,0.8,0.9"
139
139
 
140
140
  ### 💰 点数控制系统
141
141
 
142
- 基于本地算法精确模拟各功能的 Anlas 消耗并进行预扣费,确保消耗透明且防止恶意刷图。
142
+ 按 NovelAI 官网前端公式模拟 Anlas 消耗并预扣费,确保消耗透明且防止恶意刷图。
143
143
 
144
144
  **计费细则:**
145
- - **默认消耗**:根据生成目标的像素总数进行基础扣费。
146
- - **SMEA / DYN 加成**:一旦开启基于采样算法的优化SMEA 单独开启(nai -S tags) **1.2 倍** 放大,同时开启SMEA 和 DYN 消耗(nai -d tags) **1.4 倍** 放大。
147
- - **以图生图**:系统会根据设定的去噪强度(`strength`)等比缩放扣除额度。
148
- - **精确参考(-P)**:由于其附带复杂的预处理,每挂载一张参考图均会**额外扣除 5 点数**。
145
+ - **默认消耗**:`ceil(2.951823174884865e-6 × 像素 + 5.753298233447344e-7 × 像素 × 步数)`。
146
+ - **V5(nai5 / nai5c)**:在上述结果上再乘 **1.5**。例如 832×1216、28 步超额时,V4.5 为 20、V5 为 30。
147
+ - **SMEA / DYN 加成**:SMEA 单独开启 **1.2 倍**,同时开启 SMEA 和 DYN **1.4 倍**。
148
+ - **以图生图**:按去噪强度(`strength`)缩放后向上取整,最少 2 点。
149
+ - **精确参考(-P)**:有参考图时取消 Opus 免费档,并每张参考图额外扣除 5 点数。
149
150
  - **导演工具(Director Tools)**:
150
151
  - 普清免扣:分辨率在 `1024×1024`(或 1,048,576 像素)及以下时,处理**免收基础点数**。
151
152
  - 高清计费:超过安全像素后根据公式 `像素数 ÷ 50000 - 1.8` 收取超额积分。
@@ -1,31 +0,0 @@
1
- interface ProcessedImageResult {
2
- cleanImage: Buffer;
3
- dimmedImage: Buffer;
4
- width: number;
5
- height: number;
6
- }
7
- interface InpaintPayload {
8
- input?: string;
9
- model: string;
10
- action: 'infill';
11
- parameters: any;
12
- }
13
- export declare class NaiInpaintService {
14
- /**
15
- * 预处理输入图像
16
- * 1. 移除 Alpha 通道 (强制 RGB)
17
- * 2. 调整尺寸为 64 的倍数 (NovelAI 限制)
18
- * 3. 生成一张变暗的引导图供用户涂抹
19
- */
20
- processInputImage(inputBuffer: Buffer): Promise<ProcessedImageResult>;
21
- /**
22
- * 从用户涂抹的图中提取蒙版
23
- * 用户会在暗图上用白色画笔涂抹
24
- */
25
- extractMaskFromUserDrawing(cleanImageBuffer: Buffer, userDrawnBuffer: Buffer): Promise<Buffer>;
26
- /**
27
- * 构造 NovelAI API Payload
28
- */
29
- constructInpaintPayload(model: string, imageBase64: string, maskBase64: string, prompt: string, negativePrompt: string, params: any): InpaintPayload;
30
- }
31
- export {};
@@ -1,59 +0,0 @@
1
- /**
2
- * 将数值向下或向上对齐到 64 的倍数
3
- * @param value 原始数值
4
- * @param direction 对齐方向:'floor' 向下,'ceil' 向上
5
- */
6
- export declare function alignTo64Grid(value: number, direction: 'floor' | 'ceil'): number;
7
- /**
8
- * Buffer 转 Base64 字符串(不含 data URL 前缀)
9
- */
10
- export declare function bufferToBase64(buffer: Buffer): string;
11
- /**
12
- * 将图片尺寸调整为 64 的倍数
13
- * @param buffer 图片 ArrayBuffer
14
- * @returns 调整后的图片 Buffer 和尺寸
15
- */
16
- export declare function resizeToMultipleOf64(buffer: ArrayBuffer): Promise<{
17
- buffer: Buffer;
18
- width: number;
19
- height: number;
20
- }>;
21
- /**
22
- * 将图片亮度降低(变暗),方便用户在上面涂白
23
- * @param buffer 原图 ArrayBuffer
24
- * @param brightness 亮度系数,默认 0.4(即降低到 40%)
25
- * @returns 变暗后的图片 Buffer
26
- */
27
- export declare function darkenImage(buffer: ArrayBuffer, brightness?: number): Promise<Buffer>;
28
- /**
29
- * 从用户涂抹的图片中生成 Inpainting 蒙版
30
- *
31
- * 处理流程:
32
- * 1. 灰度化 -> 阈值提取白色区域 -> 模糊 -> 再次阈值化
33
- * 这个流程可以平滑蒙版边缘,避免 AI 重绘时产生明显边界
34
- * 2. 计算白色区域的包围盒
35
- * 3. 将包围盒扩展至 64 的倍数对齐
36
- * 4. 生成最终蒙版
37
- *
38
- * @param buffer 用户发回的涂抹图片 ArrayBuffer
39
- * @returns 蒙版 Buffer、宽度、高度
40
- */
41
- export declare function createInpaintMask(buffer: ArrayBuffer): Promise<{
42
- maskBuffer: Buffer;
43
- width: number;
44
- height: number;
45
- }>;
46
- /**
47
- * 创建精确的 Inpainting 蒙版(保留原始涂抹形状)
48
- *
49
- * 与 createInpaintMask 不同,此函数保留用户涂抹的精确形状,
50
- * 而不是扩展为矩形。适用于需要精确控制重绘区域的场景。
51
- *
52
- * @param buffer 用户发回的涂抹图片 ArrayBuffer
53
- * @returns 蒙版 Buffer、宽度、高度
54
- */
55
- export declare function createPreciseInpaintMask(buffer: ArrayBuffer): Promise<{
56
- maskBuffer: Buffer;
57
- width: number;
58
- height: number;
59
- }>;
@@ -1,94 +0,0 @@
1
- /**
2
- * NovelAI Inpainting API Payload 构造器
3
- * 用于构建 infill 请求的参数
4
- */
5
- /**
6
- * Infill Payload 选项
7
- */
8
- export interface InfillOptions {
9
- /** 基础模型名称(如 'nai-diffusion-3',将自动添加 -inpainting 后缀) */
10
- baseModel: string;
11
- /** 正向提示词 */
12
- prompt: string;
13
- /** 负向提示词 */
14
- negativePrompt?: string;
15
- /** 原图 Base64(不含前缀) */
16
- imageBase64: string;
17
- /** 蒙版 Base64(不含前缀) */
18
- maskBase64: string;
19
- /** 重绘强度,默认 1.0 */
20
- strength?: number;
21
- /** 噪声强度,默认 0 */
22
- noise?: number;
23
- /** 生成种子 */
24
- seed?: number;
25
- /** CFG Scale */
26
- scale?: number;
27
- /** 采样器 */
28
- sampler?: string;
29
- /** 调度器 */
30
- scheduler?: string;
31
- /** 步数 */
32
- steps?: number;
33
- /** 图片宽度 */
34
- width?: number;
35
- /** 图片高度 */
36
- height?: number;
37
- }
38
- /**
39
- * Infill API 请求 Payload
40
- */
41
- export interface InfillPayload {
42
- model: string;
43
- input: string;
44
- action: 'infill';
45
- parameters: {
46
- image: string;
47
- mask: string;
48
- add_original_image: boolean;
49
- strength: number;
50
- noise: number;
51
- seed: number;
52
- prompt: string;
53
- negative_prompt: string;
54
- scale: number;
55
- sampler: string;
56
- noise_schedule?: string;
57
- steps: number;
58
- width: number;
59
- height: number;
60
- n_samples: number;
61
- ucPreset: number;
62
- qualityToggle: boolean;
63
- params_version: number;
64
- legacy: boolean;
65
- [key: string]: any;
66
- };
67
- }
68
- /**
69
- * 获取 Inpainting 模型名称
70
- * @param baseModel 基础模型名称
71
- * @returns 带 -inpainting 后缀的模型名称
72
- */
73
- export declare function getInpaintingModel(baseModel: string): string;
74
- /**
75
- * 检查模型是否支持 Inpainting
76
- * @param model 模型名称
77
- */
78
- export declare function supportsInpainting(model: string): boolean;
79
- /**
80
- * 构造 Infill 请求 Payload
81
- *
82
- * @param options 选项参数
83
- * @returns 完整的 Infill API 请求 Payload
84
- */
85
- export declare function constructInfillPayload(options: InfillOptions): InfillPayload;
86
- /**
87
- * 从 Buffer 创建 Infill Payload(便捷方法)
88
- *
89
- * @param options 选项参数(使用 Buffer 而非 Base64)
90
- */
91
- export declare function constructInfillPayloadFromBuffers(options: Omit<InfillOptions, 'imageBase64' | 'maskBase64'> & {
92
- imageBuffer: Buffer;
93
- maskBuffer: Buffer;
94
- }): InfillPayload;
@@ -1,46 +0,0 @@
1
- import { Context, Session } from 'koishi';
2
- import { Config } from '../config';
3
- /**
4
- * 组合支付结果。
5
- * 当点数不足以支付费用时,缺口按兑换比例折算为外部货币补足(组合支付)。
6
- */
7
- export interface PayResult {
8
- success: boolean;
9
- /** 实际支付的点数 */
10
- pointsPaid: number;
11
- /** 实际支付的外部货币数量 */
12
- currencyPaid: number;
13
- /** 失败时(success=false)附带以下提示数据 */
14
- pointsBalance?: number;
15
- requiredPoints?: number;
16
- currencyBalance?: number;
17
- requiredCurrency?: number;
18
- }
19
- /**
20
- * 外部货币适配器。
21
- * 用于「点数不足时使用外部货币兜底支付」:直接读写外部数据库表(默认即
22
- * xiaohua-monetary 插件的 monetary 表)。外部货币的发放/刷新由对应插件管理,
23
- * 本插件仅负责消费(查询 / 扣减 / 退还)。
24
- */
25
- export declare class ExternalCurrency {
26
- private ctx;
27
- private config;
28
- constructor(ctx: Context, config: Config);
29
- private get fc();
30
- /**
31
- * 解析用户的 uid(对应 Koishi binding 表的 aid)。
32
- * 优先使用 session.user.id(Koishi 在挂载用户时总是会加载 id 字段);
33
- * 无 session 时经 binding 表由 userId(platform:pid)反查。
34
- */
35
- static resolveUid(session?: Session, userId?: string): number | null;
36
- /** 经 binding 表由 userId(platform:pid)反查 uid,查不到返回 null */
37
- static resolveUidByUserId(ctx: Context, userId: string): Promise<number | null>;
38
- /** 构造查询条件:按 uid + (可选的)货币类型 */
39
- private buildQuery;
40
- /** 查询用户外部货币余额,无记录视为 0;表/字段配置错误时记日志并按 0 处理(不免费放行) */
41
- getBalance(uid: number): Promise<number>;
42
- /** 扣减外部货币(原子 $.sub),余额不足返回 false */
43
- deduct(uid: number, amount: number): Promise<boolean>;
44
- /** 退还外部货币(原子 $.add;无记录时创建新记录,不丢钱) */
45
- refund(uid: number, amount: number): Promise<void>;
46
- }