koishi-plugin-aka-ai-generator 0.7.7 → 0.7.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.js CHANGED
@@ -1185,7 +1185,9 @@ var UserManager = class {
1185
1185
  usersCache = null;
1186
1186
  pendingVideoCache = null;
1187
1187
  activeTasks = /* @__PURE__ */ new Map();
1188
- // userId -> requestId
1188
+ // userId -> requestId (图像任务锁)
1189
+ activeVideoTasks = /* @__PURE__ */ new Map();
1190
+ // userId -> requestId (视频任务锁,独立于图像任务)
1189
1191
  rateLimitMap = /* @__PURE__ */ new Map();
1190
1192
  // userId -> timestamps
1191
1193
  securityBlockMap = /* @__PURE__ */ new Map();
@@ -1204,6 +1206,7 @@ var UserManager = class {
1204
1206
  }
1205
1207
  }
1206
1208
  // --- 任务管理 ---
1209
+ // 图像任务锁(原有逻辑保持不变)
1207
1210
  startTask(userId) {
1208
1211
  if (this.activeTasks.has(userId)) return false;
1209
1212
  this.activeTasks.set(userId, "processing");
@@ -1215,6 +1218,18 @@ var UserManager = class {
1215
1218
  isTaskActive(userId) {
1216
1219
  return this.activeTasks.has(userId);
1217
1220
  }
1221
+ // 视频任务锁(独立于图像任务,不影响图像生成)
1222
+ startVideoTask(userId) {
1223
+ if (this.activeVideoTasks.has(userId)) return false;
1224
+ this.activeVideoTasks.set(userId, "processing");
1225
+ return true;
1226
+ }
1227
+ endVideoTask(userId) {
1228
+ this.activeVideoTasks.delete(userId);
1229
+ }
1230
+ isVideoTaskActive(userId) {
1231
+ return this.activeVideoTasks.has(userId);
1232
+ }
1218
1233
  // --- 权限管理 ---
1219
1234
  isAdmin(userId, config) {
1220
1235
  return config.adminUsers && config.adminUsers.includes(userId);
@@ -1349,6 +1364,49 @@ var UserManager = class {
1349
1364
  });
1350
1365
  return tasks[0] || null;
1351
1366
  }
1367
+ /**
1368
+ * 统计某个用户未扣费的待结算视频任务数量
1369
+ */
1370
+ async countPendingVideoTasksForUser(userId) {
1371
+ const data = await this.loadPendingVideoTasks();
1372
+ return Object.values(data.tasks).filter((t) => t.userId === userId && !t.charged).length;
1373
+ }
1374
+ /**
1375
+ * 列出某个用户所有未扣费的待结算视频任务
1376
+ */
1377
+ async listPendingVideoTasksForUser(userId) {
1378
+ const data = await this.loadPendingVideoTasks();
1379
+ return Object.values(data.tasks).filter((t) => t.userId === userId && !t.charged).sort((a, b) => {
1380
+ const ta = Date.parse(a.createdAt || "") || 0;
1381
+ const tb = Date.parse(b.createdAt || "") || 0;
1382
+ return tb - ta;
1383
+ });
1384
+ }
1385
+ /**
1386
+ * 添加待结算视频任务,并检查上限(默认max=1)
1387
+ * @returns {Promise<{success: boolean, message?: string}>} 成功返回 {success: true},失败返回 {success: false, message: '错误信息'}
1388
+ */
1389
+ async addPendingVideoTaskWithLimit(task, max = 1) {
1390
+ await this.loadPendingVideoTasks();
1391
+ return await this.pendingLock.acquire(async () => {
1392
+ if (!this.pendingVideoCache) {
1393
+ this.pendingVideoCache = { version: "1.0.0", lastUpdate: (/* @__PURE__ */ new Date()).toISOString(), tasks: {} };
1394
+ }
1395
+ const currentCount = Object.values(this.pendingVideoCache.tasks).filter((t) => t.userId === task.userId && !t.charged).length;
1396
+ if (currentCount >= max) {
1397
+ return {
1398
+ success: false,
1399
+ message: `您当前已有 ${currentCount} 个视频正在生成中(最多允许 ${max} 个),请先使用"查询视频"查看进度或等待完成`
1400
+ };
1401
+ }
1402
+ if (this.pendingVideoCache.tasks[task.taskId]) {
1403
+ return { success: true };
1404
+ }
1405
+ this.pendingVideoCache.tasks[task.taskId] = task;
1406
+ await this.savePendingVideoTasksInternal();
1407
+ return { success: true };
1408
+ });
1409
+ }
1352
1410
  // 获取特定用户数据
1353
1411
  async getUserData(userId, userName) {
1354
1412
  await this.loadUsersData();
@@ -2339,8 +2397,8 @@ ${infoParts.join("\n")}`;
2339
2397
  if (!limitCheck.allowed) {
2340
2398
  return limitCheck.message;
2341
2399
  }
2342
- if (!userManager.startTask(userId)) {
2343
- return "您有一个任务正在进行中,请等待完成";
2400
+ if (!userManager.startVideoTask(userId)) {
2401
+ return "您有一个视频任务正在进行中,请等待完成";
2344
2402
  }
2345
2403
  let createdTaskId = null;
2346
2404
  try {
@@ -2375,15 +2433,6 @@ ${infoParts.join("\n")}`;
2375
2433
  if (!validRatios.includes(ratio)) {
2376
2434
  return `宽高比必须是以下之一: ${validRatios.join(", ")}`;
2377
2435
  }
2378
- await session.send(
2379
- `🎬 开始生成视频...
2380
- 📝 描述:${prompt}
2381
- ⏱️ 时长:${duration}秒
2382
- 📐 宽高比:${ratio}
2383
- ⚠️ 预计需要 1-3 分钟,请耐心等待
2384
- 💡 提示:生成过程中可继续使用其他功能`
2385
- );
2386
- const startTime = Date.now();
2387
2436
  const taskId = await videoProvider.createVideoTask(
2388
2437
  prompt,
2389
2438
  imageUrls[0],
@@ -2393,7 +2442,7 @@ ${infoParts.join("\n")}`;
2393
2442
  }
2394
2443
  );
2395
2444
  createdTaskId = taskId;
2396
- await userManager.addPendingVideoTask({
2445
+ const addResult = await userManager.addPendingVideoTaskWithLimit({
2397
2446
  taskId,
2398
2447
  userId,
2399
2448
  userName,
@@ -2401,64 +2450,119 @@ ${infoParts.join("\n")}`;
2401
2450
  credits: videoCredits,
2402
2451
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2403
2452
  charged: false
2404
- });
2405
- await new Promise((resolve) => setTimeout(resolve, 3e3));
2406
- const videoUrl = await videoProvider.waitForVideo(taskId, config.videoMaxWaitTime);
2407
- await session.send(import_koishi2.h.video(videoUrl));
2408
- await recordUserUsage(session, "图生视频", videoCredits, false);
2409
- await userManager.markPendingVideoTaskCharged(taskId);
2410
- await userManager.deletePendingVideoTask(taskId);
2411
- const totalTime = Math.floor((Date.now() - startTime) / 1e3);
2412
- await session.send(`✅ 视频生成完成!(耗时 ${totalTime} 秒)`);
2413
- } catch (error) {
2414
- logger.error("视频生成失败", { userId, error: sanitizeError(error) });
2415
- const errorMsg = error.message || "";
2416
- if (errorMsg.includes("任务ID:")) {
2417
- return "⏳ 视频生成超时(任务仍在后台继续生成),请稍后发送“查询视频”获取结果";
2453
+ }, 1);
2454
+ if (!addResult.success) {
2455
+ try {
2456
+ await userManager.deletePendingVideoTask(taskId);
2457
+ } catch {
2458
+ }
2459
+ return addResult.message || "队列已满,请先查询已有任务";
2418
2460
  }
2419
- try {
2420
- if (createdTaskId) await userManager.deletePendingVideoTask(createdTaskId);
2421
- } catch {
2461
+ await session.send("开始生成视频...");
2462
+ } catch (error) {
2463
+ logger.error("视频生成任务提交失败", { userId, error: sanitizeError(error) });
2464
+ if (createdTaskId) {
2465
+ try {
2466
+ await userManager.deletePendingVideoTask(createdTaskId);
2467
+ } catch {
2468
+ }
2422
2469
  }
2423
- return `视频生成失败:${sanitizeString(errorMsg)}`;
2470
+ const errorMsg = error.message || "";
2471
+ return `视频生成任务提交失败:${sanitizeString(errorMsg)}`;
2424
2472
  } finally {
2425
- userManager.endTask(userId);
2473
+ userManager.endVideoTask(userId);
2426
2474
  }
2427
2475
  });
2428
2476
  }
2429
2477
  if (config.enableVideoGeneration && videoProvider) {
2430
- ctx.command("查询视频 [taskId:string]", "查询视频生成状态(不传任务ID则查询自己最近一次任务)").action(async ({ session }, taskId) => {
2478
+ ctx.command("查询视频 [taskId:string]", "查询视频生成状态(不传任务ID则查询自己所有待生成任务)").action(async ({ session }, taskId) => {
2431
2479
  if (!session?.userId) return "会话无效";
2432
2480
  const trimmedTaskId = (taskId || "").trim();
2433
- const resolvedTaskId = trimmedTaskId ? trimmedTaskId : (await userManager.getLatestPendingVideoTaskForUser(session.userId))?.taskId;
2434
- if (!resolvedTaskId) {
2435
- return "你当前没有可查询的待生成视频任务";
2436
- }
2437
- try {
2438
- await session.send("🔍 正在查询视频生成状态...");
2439
- const status = await videoProvider.queryTaskStatus(resolvedTaskId);
2440
- if (status.status === "completed" && status.videoUrl) {
2441
- const pending = await userManager.getPendingVideoTask(resolvedTaskId);
2481
+ if (trimmedTaskId) {
2482
+ try {
2483
+ await session.send("正在查询视频生成状态...");
2484
+ const status = await videoProvider.queryTaskStatus(trimmedTaskId);
2485
+ const pending = await userManager.getPendingVideoTask(trimmedTaskId);
2442
2486
  if (pending && pending.userId && pending.userId !== session.userId) {
2443
2487
  return "该任务ID不属于当前用户,无法查询";
2444
2488
  }
2445
- await session.send(import_koishi2.h.video(status.videoUrl));
2446
- if (pending && !pending.charged) {
2447
- await recordUserUsage(session, pending.commandName, pending.credits, false);
2448
- await userManager.markPendingVideoTaskCharged(resolvedTaskId);
2449
- await userManager.deletePendingVideoTask(resolvedTaskId);
2489
+ if (status.status === "completed" && status.videoUrl) {
2490
+ await session.send(import_koishi2.h.video(status.videoUrl));
2491
+ if (pending && !pending.charged) {
2492
+ await recordUserUsage(session, pending.commandName, pending.credits, false);
2493
+ await userManager.markPendingVideoTaskCharged(trimmedTaskId);
2494
+ await userManager.deletePendingVideoTask(trimmedTaskId);
2495
+ }
2496
+ return "视频生成完成!";
2497
+ } else if (status.status === "processing" || status.status === "pending") {
2498
+ const progressText = status.progress ? `(进度:${status.progress}%)` : "";
2499
+ return `视频正在生成中${progressText},请稍后再次查询`;
2500
+ } else if (status.status === "failed") {
2501
+ if (pending && !pending.charged) {
2502
+ await userManager.deletePendingVideoTask(trimmedTaskId);
2503
+ }
2504
+ return `视频生成失败:${status.error || "未知错误"}`;
2505
+ } else {
2506
+ return `❓ 未知状态:${status.status}`;
2507
+ }
2508
+ } catch (error) {
2509
+ logger.error("查询视频任务失败", { taskId: trimmedTaskId, error: sanitizeError(error) });
2510
+ return `查询失败:${sanitizeString(error.message)}`;
2511
+ }
2512
+ }
2513
+ try {
2514
+ const pendingTasks = await userManager.listPendingVideoTasksForUser(session.userId);
2515
+ if (pendingTasks.length === 0) {
2516
+ return "你当前没有可查询的待生成视频任务";
2517
+ }
2518
+ await session.send(`正在查询 ${pendingTasks.length} 个视频任务状态...`);
2519
+ let completedCount = 0;
2520
+ let processingCount = 0;
2521
+ let failedCount = 0;
2522
+ const messages = [];
2523
+ for (const task of pendingTasks) {
2524
+ try {
2525
+ const status = await videoProvider.queryTaskStatus(task.taskId);
2526
+ if (status.status === "completed" && status.videoUrl) {
2527
+ await session.send(import_koishi2.h.video(status.videoUrl));
2528
+ if (!task.charged) {
2529
+ await recordUserUsage(session, task.commandName, task.credits, false);
2530
+ await userManager.markPendingVideoTaskCharged(task.taskId);
2531
+ await userManager.deletePendingVideoTask(task.taskId);
2532
+ }
2533
+ completedCount++;
2534
+ messages.push(`任务 ${task.taskId.substring(0, 20)}... 已完成`);
2535
+ } else if (status.status === "processing" || status.status === "pending") {
2536
+ processingCount++;
2537
+ const progressText = status.progress ? `(进度:${status.progress}%)` : "";
2538
+ messages.push(`任务 ${task.taskId.substring(0, 20)}... 生成中${progressText}`);
2539
+ } else if (status.status === "failed") {
2540
+ if (!task.charged) {
2541
+ await userManager.deletePendingVideoTask(task.taskId);
2542
+ }
2543
+ failedCount++;
2544
+ messages.push(`任务 ${task.taskId.substring(0, 20)}... 失败:${status.error || "未知错误"}`);
2545
+ } else {
2546
+ messages.push(`❓ 任务 ${task.taskId.substring(0, 20)}... 状态:${status.status}`);
2547
+ }
2548
+ } catch (error) {
2549
+ logger.error("查询单个视频任务失败", { taskId: task.taskId, error: sanitizeError(error) });
2550
+ messages.push(`⚠️ 任务 ${task.taskId.substring(0, 20)}... 查询失败:${sanitizeString(error.message)}`);
2450
2551
  }
2451
- return "✅ 视频已生成完成!";
2452
- } else if (status.status === "processing" || status.status === "pending") {
2453
- const progressText = status.progress ? `(进度:${status.progress}%)` : "";
2454
- return `⏳ 视频正在生成中${progressText},请稍后再次查询`;
2455
- } else if (status.status === "failed") {
2456
- return `❌ 视频生成失败:${status.error || "未知错误"}`;
2457
- } else {
2458
- return `❓ 未知状态:${status.status}`;
2459
2552
  }
2553
+ let summary = `查询结果汇总:
2554
+ `;
2555
+ if (completedCount > 0) summary += `已完成:${completedCount} 个
2556
+ `;
2557
+ if (processingCount > 0) summary += `生成中:${processingCount} 个
2558
+ `;
2559
+ if (failedCount > 0) summary += `失败:${failedCount} 个
2560
+ `;
2561
+ summary += `
2562
+ ${messages.join("\n")}`;
2563
+ return summary;
2460
2564
  } catch (error) {
2461
- logger.error("查询视频任务失败", { taskId: resolvedTaskId, error: sanitizeError(error) });
2565
+ logger.error("查询视频任务列表失败", { userId: session.userId, error: sanitizeError(error) });
2462
2566
  return `查询失败:${sanitizeString(error.message)}`;
2463
2567
  }
2464
2568
  });
@@ -2480,8 +2584,8 @@ ${infoParts.join("\n")}`;
2480
2584
  if (!limitCheck.allowed) {
2481
2585
  return limitCheck.message;
2482
2586
  }
2483
- if (!userManager.startTask(userId)) {
2484
- return "您有一个任务正在进行中,请等待完成";
2587
+ if (!userManager.startVideoTask(userId)) {
2588
+ return "您有一个视频任务正在进行中,请等待完成";
2485
2589
  }
2486
2590
  let createdTaskId = null;
2487
2591
  try {
@@ -2497,11 +2601,6 @@ ${infoParts.join("\n")}`;
2497
2601
  if (extraText) {
2498
2602
  finalPrompt += " - " + extraText;
2499
2603
  }
2500
- await session.send(
2501
- `🎬 开始生成视频(${style.commandName})...
2502
- 📝 描述:${finalPrompt}
2503
- ⏱️ 预计需要 1-3 分钟`
2504
- );
2505
2604
  const taskId = await videoProvider.createVideoTask(
2506
2605
  finalPrompt,
2507
2606
  imageUrls[0],
@@ -2511,7 +2610,7 @@ ${infoParts.join("\n")}`;
2511
2610
  }
2512
2611
  );
2513
2612
  createdTaskId = taskId;
2514
- await userManager.addPendingVideoTask({
2613
+ const addResult = await userManager.addPendingVideoTaskWithLimit({
2515
2614
  taskId,
2516
2615
  userId,
2517
2616
  userName,
@@ -2519,27 +2618,27 @@ ${infoParts.join("\n")}`;
2519
2618
  credits: videoCredits,
2520
2619
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2521
2620
  charged: false
2522
- });
2523
- await new Promise((resolve) => setTimeout(resolve, 3e3));
2524
- const videoUrl = await videoProvider.waitForVideo(taskId, config.videoMaxWaitTime);
2525
- await session.send(import_koishi2.h.video(videoUrl));
2526
- await recordUserUsage(session, style.commandName, videoCredits, false);
2527
- await userManager.markPendingVideoTaskCharged(taskId);
2528
- await userManager.deletePendingVideoTask(taskId);
2529
- await session.send(`✅ 视频生成完成!`);
2530
- } catch (error) {
2531
- logger.error("视频风格转换失败", { userId, style: style.commandName, error: sanitizeError(error) });
2532
- const errorMsg = error.message || "";
2533
- if (errorMsg.includes("任务ID:")) {
2534
- return "⏳ 视频生成超时(任务仍在后台继续生成),请稍后发送“查询视频”获取结果";
2621
+ }, 1);
2622
+ if (!addResult.success) {
2623
+ try {
2624
+ await userManager.deletePendingVideoTask(taskId);
2625
+ } catch {
2626
+ }
2627
+ return addResult.message || "队列已满,请先查询已有任务";
2535
2628
  }
2536
- try {
2537
- if (createdTaskId) await userManager.deletePendingVideoTask(createdTaskId);
2538
- } catch {
2629
+ await session.send(`开始生成视频(${style.commandName})...`);
2630
+ } catch (error) {
2631
+ logger.error("视频风格转换任务提交失败", { userId, style: style.commandName, error: sanitizeError(error) });
2632
+ if (createdTaskId) {
2633
+ try {
2634
+ await userManager.deletePendingVideoTask(createdTaskId);
2635
+ } catch {
2636
+ }
2539
2637
  }
2540
- return `视频生成失败:${sanitizeString(errorMsg)}`;
2638
+ const errorMsg = error.message || "";
2639
+ return `视频生成任务提交失败:${sanitizeString(errorMsg)}`;
2541
2640
  } finally {
2542
- userManager.endTask(userId);
2641
+ userManager.endVideoTask(userId);
2543
2642
  }
2544
2643
  });
2545
2644
  logger.info(`已注册视频风格命令: ${style.commandName}`);
@@ -67,6 +67,7 @@ export declare class UserManager {
67
67
  private usersCache;
68
68
  private pendingVideoCache;
69
69
  private activeTasks;
70
+ private activeVideoTasks;
70
71
  private rateLimitMap;
71
72
  private securityBlockMap;
72
73
  private securityWarningMap;
@@ -74,6 +75,9 @@ export declare class UserManager {
74
75
  startTask(userId: string): boolean;
75
76
  endTask(userId: string): void;
76
77
  isTaskActive(userId: string): boolean;
78
+ startVideoTask(userId: string): boolean;
79
+ endVideoTask(userId: string): void;
80
+ isVideoTaskActive(userId: string): boolean;
77
81
  isAdmin(userId: string, config: Config): boolean;
78
82
  private loadUsersData;
79
83
  private saveUsersDataInternal;
@@ -90,6 +94,22 @@ export declare class UserManager {
90
94
  * 获取某个用户最近一次未扣费的待结算视频任务
91
95
  */
92
96
  getLatestPendingVideoTaskForUser(userId: string): Promise<PendingVideoTask | null>;
97
+ /**
98
+ * 统计某个用户未扣费的待结算视频任务数量
99
+ */
100
+ countPendingVideoTasksForUser(userId: string): Promise<number>;
101
+ /**
102
+ * 列出某个用户所有未扣费的待结算视频任务
103
+ */
104
+ listPendingVideoTasksForUser(userId: string): Promise<PendingVideoTask[]>;
105
+ /**
106
+ * 添加待结算视频任务,并检查上限(默认max=1)
107
+ * @returns {Promise<{success: boolean, message?: string}>} 成功返回 {success: true},失败返回 {success: false, message: '错误信息'}
108
+ */
109
+ addPendingVideoTaskWithLimit(task: PendingVideoTask, max?: number): Promise<{
110
+ success: boolean;
111
+ message?: string;
112
+ }>;
93
113
  getUserData(userId: string, userName: string): Promise<UserData>;
94
114
  getAllUsers(): Promise<UsersData>;
95
115
  updateUsersBatch(updates: (data: UsersData) => void): Promise<void>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-aka-ai-generator",
3
3
  "description": "自用AI生成插件(GPTGod & Yunwu)",
4
- "version": "0.7.7",
4
+ "version": "0.7.8",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [