koishi-plugin-ai-video 0.0.2 → 0.0.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.
Files changed (2) hide show
  1. package/lib/index.js +144 -90
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -39,12 +39,12 @@ exports.Config = koishi_1.Schema.intersect([
39
39
  }).description('基本设置'),
40
40
  koishi_1.Schema.object({
41
41
  proxyEnabled: koishi_1.Schema.boolean().default(false).description('启用代理'),
42
- proxyProtocol: koishi_1.Schema.union([koishi_1.Schema.const('http'), koishi_1.Schema.const('https')]).default('http'),
43
- proxyHost: koishi_1.Schema.string().default(''),
44
- proxyPort: koishi_1.Schema.number().default(8080),
45
- proxyAuth: koishi_1.Schema.boolean().default(false),
46
- proxyUsername: koishi_1.Schema.string().default(''),
47
- proxyPassword: koishi_1.Schema.string().role('secret').default(''),
42
+ proxyProtocol: koishi_1.Schema.union([koishi_1.Schema.const('http'), koishi_1.Schema.const('https')]).default('http').description('代理协议'),
43
+ proxyHost: koishi_1.Schema.string().default('').description('代理主机地址'),
44
+ proxyPort: koishi_1.Schema.number().default(8080).description('代理端口'),
45
+ proxyAuth: koishi_1.Schema.boolean().default(false).description('启用代理认证'),
46
+ proxyUsername: koishi_1.Schema.string().default('').description('代理用户名'),
47
+ proxyPassword: koishi_1.Schema.string().role('secret').default('').description('代理密码'),
48
48
  }).description('代理设置'),
49
49
  koishi_1.Schema.object({
50
50
  useCustomApi: koishi_1.Schema.boolean().default(false).description('使用自定义API'),
@@ -52,26 +52,30 @@ exports.Config = koishi_1.Schema.intersect([
52
52
  apiKey: koishi_1.Schema.string().role('secret').default('').description('API密钥'),
53
53
  model: koishi_1.Schema.string().default('agnes-video-v2.0').description('模型'),
54
54
  img2videoModel: koishi_1.Schema.string().default('').description('图生视频模型'),
55
- videoDuration: koishi_1.Schema.number().default(0).description('时长'),
56
- videoResolution: koishi_1.Schema.string().default('').description('分辨率'),
55
+ videoDuration: koishi_1.Schema.number().default(0).description('默认视频时长(秒)'),
56
+ videoResolution: koishi_1.Schema.string().default('').description('默认视频分辨率(宽x高)'),
57
57
  txt2videoPrompt: koishi_1.Schema.string().default('').description('文生视频提示模板'),
58
58
  img2videoPrompt: koishi_1.Schema.string().default('').description('图生视频提示模板'),
59
- customHeaders: koishi_1.Schema.string().role('textarea').default('{}').description('自定义请求头'),
59
+ customHeaders: koishi_1.Schema.string().role('textarea').default('{}').description('自定义请求头(JSON)'),
60
60
  }).description('内置API'),
61
61
  koishi_1.Schema.object({
62
- apiStrategy: koishi_1.Schema.union([koishi_1.Schema.const('sequence'), koishi_1.Schema.const('roundrobin')]).default('roundrobin'),
62
+ apiStrategy: koishi_1.Schema.union([
63
+ koishi_1.Schema.const('sequence').description('顺序使用'),
64
+ koishi_1.Schema.const('roundrobin').description('轮询使用')
65
+ ]).default('roundrobin').description('API调用策略'),
63
66
  customApiList: koishi_1.Schema.array(koishi_1.Schema.object({
64
- enable: koishi_1.Schema.boolean().default(true),
65
- endpoint: koishi_1.Schema.string().default('https://apihub.agnes-ai.com/v1/videos'),
66
- apiKey: koishi_1.Schema.string().role('secret').default(''),
67
- model: koishi_1.Schema.string().default('agnes-video-v2.0'),
68
- img2videoModel: koishi_1.Schema.string().default(''),
69
- videoDuration: koishi_1.Schema.number().default(0),
70
- videoResolution: koishi_1.Schema.string().default(''),
71
- txt2videoPrompt: koishi_1.Schema.string().default(''),
72
- img2videoPrompt: koishi_1.Schema.string().default(''),
67
+ enable: koishi_1.Schema.boolean().default(true).description('启用'),
68
+ endpoint: koishi_1.Schema.string().default('https://apihub.agnes-ai.com/v1/videos').description('API端点'),
69
+ apiKey: koishi_1.Schema.string().role('secret').default('').description('API密钥'),
70
+ model: koishi_1.Schema.string().default('agnes-video-v2.0').description('模型'),
71
+ img2videoModel: koishi_1.Schema.string().default('').description('图生视频模型'),
72
+ videoDuration: koishi_1.Schema.number().default(0).description('默认视频时长(秒)'),
73
+ videoResolution: koishi_1.Schema.string().default('').description('默认视频分辨率(宽x高)'),
74
+ txt2videoPrompt: koishi_1.Schema.string().default('').description('文生视频提示模板'),
75
+ img2videoPrompt: koishi_1.Schema.string().default('').description('图生视频提示模板'),
73
76
  customHeaders: koishi_1.Schema.string().role('textarea')
74
- .default('{"Authorization":"Bearer {apiKey}","Content-Type":"application/json"}'),
77
+ .default('{"Authorization":"Bearer {apiKey}","Content-Type":"application/json"}')
78
+ .description('自定义请求头(JSON)'),
75
79
  bodyTemplate: koishi_1.Schema.string().role('textarea')
76
80
  .default(JSON.stringify({
77
81
  txt2videoBody: { model: '{model}', prompt: '{prompt}', height: 768, width: 1152, num_frames: 121, frame_rate: 24 },
@@ -80,41 +84,41 @@ exports.Config = koishi_1.Schema.intersect([
80
84
  pollUrlTemplate: 'https://apihub.agnes-ai.com/agnesapi?video_id={task_id}',
81
85
  taskIdPath: 'video_id',
82
86
  }, null, 2))
83
- .description('自定义请求体'),
87
+ .description('自定义请求体(JSON模板)'),
84
88
  })).default([]).description('自定义API列表'),
85
89
  }).description('自定义API配置'),
86
90
  koishi_1.Schema.object({
87
- blacklistAdmins: koishi_1.Schema.array(String).default([]).description('管理员QQ'),
91
+ blacklistAdmins: koishi_1.Schema.array(String).default([]).description('管理员QQ'),
88
92
  }).description('权限管理'),
89
93
  koishi_1.Schema.object({
90
94
  messages: koishi_1.Schema.object({
91
- generating: koishi_1.Schema.string().default('视频生成中,请耐心等待...'),
92
- enterCollect: koishi_1.Schema.string().default('已进入收集模式,请继续发送图片/文字。发送「开始」触发生成,发送「取消」退出。当前已收集: 0 张图片, 0 段文字'),
93
- collectUpdate: koishi_1.Schema.string().default('当前已收集: {images} 张图片, 文字已更新'),
94
- collectTimeout: koishi_1.Schema.string().default('收集超时,已自动退出'),
95
- empty: koishi_1.Schema.string().default('[提示] 请输入提示词或上传图片'),
96
- noApi: koishi_1.Schema.string().default('[提示] 未配置可用API'),
97
- fail: koishi_1.Schema.string().default('[提示] 视频生成失败'),
98
- noContent: koishi_1.Schema.string().default('(未返回任何视频内容)'),
99
- templateError: koishi_1.Schema.string().default('(模板配置错误)'),
100
- txt2videoDisabled: koishi_1.Schema.string().default('[提示] 文生视频功能未启用'),
101
- img2videoDisabled: koishi_1.Schema.string().default('[提示] 图生视频功能未启用'),
102
- rateLimit: koishi_1.Schema.string().default('[提示] 调用次数已达上限'),
103
- needAssets: koishi_1.Schema.string().default('[提示] 图生视频需要正确配置 assets 服务'),
104
- blacklisted: koishi_1.Schema.string().default('[提示] 你已被加入黑名单'),
105
- noPermission: koishi_1.Schema.string().default('[提示] 无权限管理黑名单'),
106
- blacklistAddSuccess: koishi_1.Schema.string().default('已将 {targets} 加入黑名单'),
107
- blacklistRemoveSuccess: koishi_1.Schema.string().default('已将 {targets} 移出黑名单'),
108
- blacklistAddFail: koishi_1.Schema.string().default('{targets} 已在黑名单或无效'),
109
- blacklistRemoveFail: koishi_1.Schema.string().default('{targets} 不在黑名单'),
110
- invalidUserId: koishi_1.Schema.string().default('无效QQ号:{targets}'),
111
- blacklistListEmpty: koishi_1.Schema.string().default('黑名单为空'),
112
- blacklistListTitle: koishi_1.Schema.string().default('当前黑名单:'),
113
- noLastTask: koishi_1.Schema.string().default('没有上一次记录,无法重绘'),
114
- redrawing: koishi_1.Schema.string().default('正在重绘上一次文生视频...'),
115
- redrawImg2Video: koishi_1.Schema.string().default('[提示] 重绘仅支持文生视频'),
116
- cancelCollect: koishi_1.Schema.string().default('已取消收集模式'),
117
- pollWaiting: koishi_1.Schema.string().default('视频生成中(异步轮询),请稍后...'),
95
+ generating: koishi_1.Schema.string().default('视频生成中,请耐心等待...').description('生成中提示'),
96
+ enterCollect: koishi_1.Schema.string().default('已进入收集模式,请继续发送图片/文字。发送「开始」触发生成,发送「取消」退出。当前已收集: 0 张图片, 0 段文字').description('进入收集模式提示'),
97
+ collectUpdate: koishi_1.Schema.string().default('当前已收集: {images} 张图片, 文字已更新').description('收集更新提示'),
98
+ collectTimeout: koishi_1.Schema.string().default('收集超时,已自动退出').description('收集超时提示'),
99
+ empty: koishi_1.Schema.string().default('[提示] 请输入提示词或上传图片').description('空输入提示'),
100
+ noApi: koishi_1.Schema.string().default('[提示] 未配置可用API').description('无API提示'),
101
+ fail: koishi_1.Schema.string().default('[提示] 视频生成失败').description('生成失败提示'),
102
+ noContent: koishi_1.Schema.string().default('(未返回任何视频内容)').description('无内容提示'),
103
+ templateError: koishi_1.Schema.string().default('(模板配置错误)').description('模板错误提示'),
104
+ txt2videoDisabled: koishi_1.Schema.string().default('[提示] 文生视频功能未启用').description('文生视频禁用提示'),
105
+ img2videoDisabled: koishi_1.Schema.string().default('[提示] 图生视频功能未启用').description('图生视频禁用提示'),
106
+ rateLimit: koishi_1.Schema.string().default('[提示] 调用次数已达上限').description('频率限制提示'),
107
+ needAssets: koishi_1.Schema.string().default('[提示] 图生视频需要正确配置 assets 服务').description('需要Assets提示'),
108
+ blacklisted: koishi_1.Schema.string().default('[提示] 你已被加入黑名单').description('黑名单提示'),
109
+ noPermission: koishi_1.Schema.string().default('[提示] 无权限管理黑名单').description('无权限提示'),
110
+ blacklistAddSuccess: koishi_1.Schema.string().default('已将 {targets} 加入黑名单').description('添加黑名单成功提示'),
111
+ blacklistRemoveSuccess: koishi_1.Schema.string().default('已将 {targets} 移出黑名单').description('移除黑名单成功提示'),
112
+ blacklistAddFail: koishi_1.Schema.string().default('{targets} 已在黑名单或无效').description('添加黑名单失败提示'),
113
+ blacklistRemoveFail: koishi_1.Schema.string().default('{targets} 不在黑名单').description('移除黑名单失败提示'),
114
+ invalidUserId: koishi_1.Schema.string().default('无效QQ号:{targets}').description('无效用户ID提示'),
115
+ blacklistListEmpty: koishi_1.Schema.string().default('黑名单为空').description('黑名单为空提示'),
116
+ blacklistListTitle: koishi_1.Schema.string().default('当前黑名单:').description('黑名单标题提示'),
117
+ noLastTask: koishi_1.Schema.string().default('没有上一次记录,无法重绘').description('无上一次任务提示'),
118
+ redrawing: koishi_1.Schema.string().default('正在重绘上一次文生视频...').description('重绘中文生视频提示'),
119
+ redrawImg2Video: koishi_1.Schema.string().default('[提示] 重绘仅支持文生视频').description('重绘图生视频提示'),
120
+ cancelCollect: koishi_1.Schema.string().default('已取消收集模式').description('取消收集提示'),
121
+ pollWaiting: koishi_1.Schema.string().default('视频生成中(异步轮询),请稍后...').description('轮询等待提示'),
118
122
  }).description('消息文本'),
119
123
  }).description('消息文本'),
120
124
  ]);
@@ -147,8 +151,6 @@ async function apply(ctx, cfg) {
147
151
  return apiCallTimestamps.length + 1 <= cfg.rateLimit;
148
152
  }
149
153
  function recordApiCall() { apiCallTimestamps.push(Date.now()); }
150
- const BUILTIN_TXT2VIDEO = { model: '{model}', prompt: '{prompt}', duration: '{duration}', size: '{size}' };
151
- const BUILTIN_IMG2VIDEO = { model: '{model}', prompt: '{prompt}', duration: '{duration}', size: '{size}', image_url: '{url}' };
152
154
  function parseApiEntry(entry) {
153
155
  if (!entry.endpoint)
154
156
  return null;
@@ -167,8 +169,8 @@ async function apply(ctx, cfg) {
167
169
  if (entry.bodyTemplate) {
168
170
  try {
169
171
  const tmpl = JSON.parse(entry.bodyTemplate);
170
- txt2videoBody = tmpl.txt2videoBody || BUILTIN_TXT2VIDEO;
171
- img2videoBody = tmpl.img2videoBody || BUILTIN_IMG2VIDEO;
172
+ txt2videoBody = tmpl.txt2videoBody || { model: '{model}', prompt: '{prompt}', duration: '{duration}', size: '{size}' };
173
+ img2videoBody = tmpl.img2videoBody || { model: '{model}', prompt: '{prompt}', duration: '{duration}', size: '{size}', image_url: '{url}' };
172
174
  responseVideoPath = tmpl.responseVideoPath || 'video_url';
173
175
  pollUrlTemplate = tmpl.pollUrlTemplate || '';
174
176
  taskIdPath = tmpl.taskIdPath || '';
@@ -178,8 +180,8 @@ async function apply(ctx, cfg) {
178
180
  }
179
181
  }
180
182
  else {
181
- txt2videoBody = BUILTIN_TXT2VIDEO;
182
- img2videoBody = BUILTIN_IMG2VIDEO;
183
+ txt2videoBody = { model: '{model}', prompt: '{prompt}', duration: '{duration}', size: '{size}' };
184
+ img2videoBody = { model: '{model}', prompt: '{prompt}', duration: '{duration}', size: '{size}', image_url: '{url}' };
183
185
  responseVideoPath = 'video_url';
184
186
  pollUrlTemplate = '';
185
187
  taskIdPath = '';
@@ -215,8 +217,8 @@ async function apply(ctx, cfg) {
215
217
  return {
216
218
  endpoint: cfg.apiEndpoint || 'https://apihub.agnes-ai.com/v1/videos',
217
219
  headers,
218
- txt2videoBody: BUILTIN_TXT2VIDEO,
219
- img2videoBody: BUILTIN_IMG2VIDEO,
220
+ txt2videoBody: { model: '{model}', prompt: '{prompt}', duration: '{duration}', size: '{size}' },
221
+ img2videoBody: { model: '{model}', prompt: '{prompt}', duration: '{duration}', size: '{size}', image_url: '{url}' },
220
222
  responseVideoPath: 'video_url',
221
223
  pollUrlTemplate: '',
222
224
  taskIdPath: '',
@@ -249,15 +251,27 @@ async function apply(ctx, cfg) {
249
251
  return buildBuiltinApi();
250
252
  }
251
253
  }
252
- function resolveTemplate(template, vars) {
253
- const jsonStr = JSON.stringify(template);
254
- let processed = jsonStr;
255
- for (const [key, value] of Object.entries(vars)) {
256
- if (value === undefined || value === null)
257
- continue;
258
- processed = processed.replace(new RegExp(`\\{${key}\\}`, 'g'), JSON.stringify(String(value)).slice(1, -1));
259
- }
260
- return JSON.parse(processed);
254
+ function resolveTemplate(templateObj, vars) {
255
+ if (templateObj === null || templateObj === undefined)
256
+ return templateObj;
257
+ if (typeof templateObj === 'string') {
258
+ const match = templateObj.match(/^\{(\w+)\}$/);
259
+ if (match && vars.hasOwnProperty(match[1])) {
260
+ return vars[match[1]];
261
+ }
262
+ return templateObj;
263
+ }
264
+ if (Array.isArray(templateObj)) {
265
+ return templateObj.map(item => resolveTemplate(item, vars));
266
+ }
267
+ if (typeof templateObj === 'object') {
268
+ const result = {};
269
+ for (const key of Object.keys(templateObj)) {
270
+ result[key] = resolveTemplate(templateObj[key], vars);
271
+ }
272
+ return result;
273
+ }
274
+ return templateObj;
261
275
  }
262
276
  function getValueByPath(obj, path) {
263
277
  if (!obj || !path)
@@ -333,7 +347,7 @@ async function apply(ctx, cfg) {
333
347
  logger.error('发送失败', e);
334
348
  }
335
349
  }
336
- async function sendVideo(session, url) {
350
+ async function sendSingleVideo(session, url) {
337
351
  const mode = cfg.videoSendMode;
338
352
  if (mode === 'url')
339
353
  await safeSend(session, url);
@@ -366,7 +380,12 @@ async function apply(ctx, cfg) {
366
380
  if (promptTemplate)
367
381
  finalPrompt = promptTemplate.replace('{prompt}', prompt).replace('{url}', imageUrl);
368
382
  const bodyTemplate = isImg2Video ? api.img2videoBody : api.txt2videoBody;
369
- const vars = { model, prompt: finalPrompt, duration: String(duration), size: resolution };
383
+ const vars = {
384
+ model,
385
+ prompt: finalPrompt,
386
+ duration,
387
+ size: resolution,
388
+ };
370
389
  if (isImg2Video)
371
390
  vars.url = imageUrl;
372
391
  let body;
@@ -374,10 +393,13 @@ async function apply(ctx, cfg) {
374
393
  body = resolveTemplate(bodyTemplate, vars);
375
394
  }
376
395
  catch {
377
- return safeSend(session, cfg.messages.fail + cfg.messages.templateError);
396
+ await safeSend(session, cfg.messages.fail + cfg.messages.templateError);
397
+ return null;
398
+ }
399
+ if (!validateEndpoint(api.endpoint)) {
400
+ await safeSend(session, cfg.messages.fail + '(端点无效)');
401
+ return null;
378
402
  }
379
- if (!validateEndpoint(api.endpoint))
380
- return safeSend(session, cfg.messages.fail + '(端点无效)');
381
403
  const sensitive = api.headers?.Authorization?.split(' ')[1] || '';
382
404
  if (debug)
383
405
  logger.info('API请求', JSON.stringify(sanitizeForLog(body, sensitive)));
@@ -393,32 +415,62 @@ async function apply(ctx, cfg) {
393
415
  if (cfg.pollEnabled && api.taskIdPath) {
394
416
  const taskId = getValueByPath(res, api.taskIdPath);
395
417
  if (taskId) {
396
- const pollUrl = api.pollUrlTemplate.replace('{endpoint}', api.endpoint).replace('{task_id}', taskId);
397
- safeSend(session, cfg.messages.pollWaiting);
418
+ const pollUrl = api.pollUrlTemplate.replace('{task_id}', taskId);
419
+ await safeSend(session, cfg.messages.pollWaiting);
398
420
  res = await pollForResult(pollUrl, api.headers, cfg.pollInterval, cfg.pollTimeout);
399
421
  }
400
422
  }
401
423
  const videoUrl = getValueByPath(res, api.responseVideoPath) || findVideoUrl(res);
402
- if (videoUrl)
403
- await sendVideo(session, videoUrl);
404
- else
405
- await safeSend(session, cfg.messages.fail + cfg.messages.noContent);
406
- const userId = `${session.guildId || 'private'}-${session.userId}`;
407
- lastTaskMap.set(userId, { prompt, imageUrl, isImg2Video, model });
424
+ return videoUrl || null;
408
425
  }
409
426
  catch (err) {
410
427
  logger.error('视频生成失败', err);
411
- safeSend(session, cfg.messages.fail + ' [' + (err.message?.slice(0, 100) || '未知错误') + ']');
428
+ await safeSend(session, cfg.messages.fail + ' [' + (err.message?.slice(0, 100) || '未知错误') + ']');
429
+ return null;
412
430
  }
413
431
  }
414
- async function generateVideo(session, prompt, imageUrl = '') {
415
- if (!checkRateLimit())
416
- return safeSend(session, cfg.messages.rateLimit);
417
- const api = getApi();
418
- if (!api)
419
- return safeSend(session, cfg.messages.noApi);
420
- recordApiCall();
421
- return customGenerateVideo(session, api, prompt, imageUrl);
432
+ async function generateVideos(session, prompt, imageUrl, count) {
433
+ const videoUrls = [];
434
+ for (let i = 0; i < count; i++) {
435
+ if (!checkRateLimit()) {
436
+ await safeSend(session, cfg.messages.rateLimit);
437
+ break;
438
+ }
439
+ const api = getApi();
440
+ if (!api) {
441
+ await safeSend(session, cfg.messages.noApi);
442
+ break;
443
+ }
444
+ recordApiCall();
445
+ const url = await customGenerateVideo(session, api, prompt, imageUrl);
446
+ if (url)
447
+ videoUrls.push(url);
448
+ if (i < count - 1)
449
+ await new Promise(r => setTimeout(r, 1000));
450
+ }
451
+ if (videoUrls.length === 0) {
452
+ await safeSend(session, cfg.messages.fail + cfg.messages.noContent);
453
+ return;
454
+ }
455
+ if (videoUrls.length === 1 || !cfg.enableForward) {
456
+ for (const url of videoUrls) {
457
+ await sendSingleVideo(session, url);
458
+ }
459
+ }
460
+ else {
461
+ const children = videoUrls.map(url => (0, koishi_1.h)('message', koishi_1.h.video(url)));
462
+ try {
463
+ await safeSend(session, (0, koishi_1.h)('message', { forward: true }, ...children));
464
+ }
465
+ catch {
466
+ for (const url of videoUrls)
467
+ await sendSingleVideo(session, url);
468
+ }
469
+ }
470
+ const userId = `${session.guildId || 'private'}-${session.userId}`;
471
+ if (videoUrls.length > 0) {
472
+ lastTaskMap.set(userId, { prompt, imageUrl, isImg2Video: !!imageUrl, model: 'multiple' });
473
+ }
422
474
  }
423
475
  function startTimer(session, key, collect) {
424
476
  return setTimeout(() => {
@@ -459,6 +511,8 @@ async function apply(ctx, cfg) {
459
511
  await safeSend(session, cfg.messages.enterCollect);
460
512
  });
461
513
  ctx.on('message', async (session) => {
514
+ if (session.command)
515
+ return;
462
516
  if (!session || !session.elements)
463
517
  return;
464
518
  const key = `${session.guildId || 'private'}-${session.userId}`;
@@ -478,7 +532,7 @@ async function apply(ctx, cfg) {
478
532
  if (!collect.prompt && !collect.imageUrl)
479
533
  return safeSend(session, cfg.messages.empty);
480
534
  await safeSend(session, cfg.messages.generating);
481
- return generateVideo(session, collect.prompt || '默认', collect.imageUrl);
535
+ return generateVideos(session, collect.prompt || '默认', collect.imageUrl, cfg.maxVideos);
482
536
  }
483
537
  if (imgs.length > 0 && !collect.imageUrl) {
484
538
  const assets = ctx.assets;
@@ -515,7 +569,7 @@ async function apply(ctx, cfg) {
515
569
  if (last.isImg2Video)
516
570
  return safeSend(session, cfg.messages.redrawImg2Video);
517
571
  await safeSend(session, cfg.messages.redrawing);
518
- return generateVideo(session, last.prompt);
572
+ return generateVideos(session, last.prompt, '', cfg.maxVideos);
519
573
  });
520
574
  function isValidQQ(id) { return /^\d{5,11}$/.test(id); }
521
575
  async function isBlacklisted(userId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-ai-video",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Koishi AI 视频生成插件,支持文生视频、图生视频",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",