koishi-plugin-p-draw 1.3.2 → 1.3.3

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/index.js +72 -53
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -488,17 +488,27 @@ exports.apply = async function apply(ctx, cfg) {
488
488
  }
489
489
  }
490
490
 
491
- // 生成图片使用合并转发;不附原消息引用。发送失败时回退普通图片消息。
491
+ // 生成图片使用合并转发;每张图后紧跟实际使用的最终提示词,不附原消息引用。
492
492
  async function sendImagesAsForward(session, outputs) {
493
- const imageElements = await Promise.all(outputs.map(async (src) => {
493
+ const nodes = []
494
+ const fallback = []
495
+ for (const output of outputs) {
496
+ const src = typeof output === 'string' ? output : output.src
497
+ const prompt = typeof output === 'string' ? '' : String(output.prompt || '')
494
498
  const materialized = await materializeImageSource(src)
495
- return Buffer.isBuffer(materialized) ? h.image(materialized) : h.image(src)
496
- }))
499
+ const image = Buffer.isBuffer(materialized) ? h.image(materialized) : h.image(src)
500
+ nodes.push(h('message', image))
501
+ fallback.push(image)
502
+ if (prompt) {
503
+ nodes.push(h('message', prompt))
504
+ fallback.push(prompt)
505
+ }
506
+ }
497
507
  try {
498
- await session.send(h('figure', imageElements))
508
+ await session.send(h('figure', nodes))
499
509
  } catch (e) {
500
510
  logger.warn(`发送转发图片失败:${e.message}`)
501
- await session.send(imageElements)
511
+ await session.send(fallback)
502
512
  }
503
513
  }
504
514
 
@@ -1189,7 +1199,7 @@ exports.apply = async function apply(ctx, cfg) {
1189
1199
  const verifyBaseUrl = String(cfg.verifyLlmBaseUrl || '').trim()
1190
1200
  const verifyModel = String(cfg.verifyLlmModel || '').trim()
1191
1201
  if (!verifyBaseUrl || !verifyModel) {
1192
- return { ok: true, degraded: true, message: '', verdict: null, outputs: images }
1202
+ return { ok: true, degraded: true, message: '', verdict: null, outputs: images, prompt }
1193
1203
  }
1194
1204
  const passScore = Math.max(0, Math.min(10, parseInt(cfg.multiVerifyPassScore) || 6))
1195
1205
  const candidateCount = Math.max(1, Math.min(3, parseInt(cfg.multiCandidateCount) || 2))
@@ -1297,11 +1307,11 @@ exports.apply = async function apply(ctx, cfg) {
1297
1307
  data = extractVerifyJson(reply)
1298
1308
  } catch (e) {
1299
1309
  logger.warn(`多人视觉校验失败:${e.message}`)
1300
- return { ok: true, degraded: true, message: session.text('.multi-verify-error', [String(e && e.message || e)]), verdict: null, outputs: images }
1310
+ return { ok: true, degraded: true, message: session.text('.multi-verify-error', [String(e && e.message || e)]), verdict: null, outputs: images, prompt: currentPrompt }
1301
1311
  }
1302
1312
  if (!data) {
1303
1313
  logger.warn(`多人视觉校验返回无法解析:${reply.slice(0, 200)}`)
1304
- return { ok: true, degraded: true, message: '', verdict: null, outputs: images }
1314
+ return { ok: true, degraded: true, message: '', verdict: null, outputs: images, prompt: currentPrompt }
1305
1315
  }
1306
1316
  const verdict = verdictFromData(data)
1307
1317
  verdict.skipped = false
@@ -1335,7 +1345,7 @@ exports.apply = async function apply(ctx, cfg) {
1335
1345
  selectedOutputs = best.outputs
1336
1346
  selectedVerdict = best.verdict
1337
1347
  if (!multiAccepted && !cfg.multiSendDegradedCandidate) {
1338
- return { ok: false, discarded: true, message: session.text('.multi-verify-discarded'), verdict: selectedVerdict, outputs: [] }
1348
+ return { ok: false, discarded: true, message: session.text('.multi-verify-discarded'), verdict: selectedVerdict, outputs: [], prompt: best.prompt }
1339
1349
  }
1340
1350
  const noteParts = []
1341
1351
  if (multiAccepted) {
@@ -1344,7 +1354,7 @@ exports.apply = async function apply(ctx, cfg) {
1344
1354
  noteParts.push(session.text('.multi-verify-degraded', selectedVerdict.issues.length ? '(' + selectedVerdict.issues.join(';').slice(0, 80) + ')' : ''))
1345
1355
  }
1346
1356
  if (retries) noteParts.push(session.text('.multi-verify-failed', [selectedVerdict.issues.length ? ':' + selectedVerdict.issues.join(';').slice(0, 80) : '', retries]))
1347
- return { ok: true, degraded: false, message: noteParts.join('\n'), verdict: selectedVerdict, outputs: selectedOutputs }
1357
+ return { ok: true, degraded: false, message: noteParts.join('\n'), verdict: selectedVerdict, outputs: selectedOutputs, prompt: best.prompt }
1348
1358
  }
1349
1359
 
1350
1360
  function buildVerifySystemPrompt(multiPerson, planCount) {
@@ -1357,7 +1367,7 @@ exports.apply = async function apply(ctx, cfg) {
1357
1367
  }
1358
1368
 
1359
1369
  // 共享批量执行器:一次性扣除总价,逐张生成,单张失败只退该张单价。
1360
- // runOne(i) 需返回 { ok, outputs, seed, message? };返回数组为多张输出(如视觉校验候选)。
1370
+ // runOne(i) 需返回 { ok, outputs, seed, prompt, message? };返回数组为多张输出(如视觉校验候选)。
1361
1371
  async function executeBatch(USERID, isAdmin, count, unitPrice, runOne) {
1362
1372
  const results = []
1363
1373
  let successCount = 0
@@ -1371,7 +1381,7 @@ exports.apply = async function apply(ctx, cfg) {
1371
1381
  const outputs = Array.isArray(item.outputs) ? item.outputs : (item.outputs ? [item.outputs] : [])
1372
1382
  if (item.ok && outputs.length) {
1373
1383
  successCount += 1
1374
- results.push({ i, ok: true, outputs, seed: item.seed, note: item.note || '' })
1384
+ results.push({ i, ok: true, outputs, seed: item.seed, prompt: item.prompt || '', note: item.note || '' })
1375
1385
  } else {
1376
1386
  if (!isAdmin) await refundP(USERID, unitPrice)
1377
1387
  if (cfg.outputLogs) logger.warn(`批量第 ${i + 1} 张生成失败(${USERID}):${item.message || '无输出'}`)
@@ -1465,25 +1475,27 @@ exports.apply = async function apply(ctx, cfg) {
1465
1475
  } else {
1466
1476
  try { result = await runComfyGenerate(finalPrompt, size, { negativePrompt: multiNegative, unet }) } catch (e) { result = { ok: false, message: `生成失败:${e.message}` } }
1467
1477
  }
1468
- if (!result.ok || !result.outputs || !result.outputs.length) return result
1469
- if (!cfg.multiVerifyEnabled) {
1470
- return { ok: true, outputs: result.outputs, seed: result.seed, note: session.text('.multi-degraded', ['(未启用校验或未配置视觉模型)']) }
1471
- }
1472
- const verified = await verifyGeneratedImages(session, result.outputs, text, finalPrompt, size, plan.characters.length, unet)
1473
- if (!verified.ok) return { ok: false, message: verified.message }
1474
- return { ok: true, outputs: verified.outputs, seed: result.seed, note: verified.message || '' }
1478
+ if (!result.ok || !result.outputs || !result.outputs.length) return result
1479
+ if (!cfg.multiVerifyEnabled) {
1480
+ return { ok: true, outputs: result.outputs, seed: result.seed, prompt: finalPrompt, note: session.text('.multi-degraded', ['(未启用校验或未配置视觉模型)']) }
1481
+ }
1482
+ const verified = await verifyGeneratedImages(session, result.outputs, text, finalPrompt, size, plan.characters.length, unet)
1483
+ if (!verified.ok) return { ok: false, message: verified.message }
1484
+ return { ok: true, outputs: verified.outputs, seed: result.seed, prompt: verified.prompt || finalPrompt, note: verified.message || '' }
1475
1485
  }
1476
1486
 
1477
1487
  const { results, successCount } = await executeBatch(USERID, isAdmin, count, price, runOne)
1478
1488
 
1479
- // 汇总
1480
- const allOutputs = []
1481
- const notes = []
1489
+ // 汇总
1490
+ const allOutputs = []
1491
+ const forwardOutputs = []
1492
+ const notes = []
1482
1493
  const seeds = []
1483
1494
  const failures = []
1484
1495
  for (const item of results) {
1485
- if (item.ok) {
1486
- allOutputs.push(...item.outputs)
1496
+ if (item.ok) {
1497
+ allOutputs.push(...item.outputs)
1498
+ forwardOutputs.push(...item.outputs.map(src => ({ src, prompt: item.prompt || finalPrompt })))
1487
1499
  if (item.seed != null) seeds.push(item.seed)
1488
1500
  if (item.note) notes.push(item.note)
1489
1501
  } else {
@@ -1499,7 +1511,7 @@ exports.apply = async function apply(ctx, cfg) {
1499
1511
  if (cfg.outputLogs) logger.success(`${USERID} 多人生成成功 ${successCount}/${count} 张`)
1500
1512
 
1501
1513
  // 发图:合并转发,不引用原指令
1502
- await sendImagesAsForward(session, allOutputs)
1514
+ await sendImagesAsForward(session, forwardOutputs)
1503
1515
 
1504
1516
  const reply = []
1505
1517
  if (count > 1) {
@@ -1638,23 +1650,26 @@ exports.apply = async function apply(ctx, cfg) {
1638
1650
  // 单阶段生成(共用 seed)
1639
1651
  const runOne = async (i) => {
1640
1652
  let result
1641
- if (cfg.queueEnabled) {
1642
- try { result = await queuedTasks[i] } catch (e) { result = { ok: false, message: `生成失败:${e.message}` } }
1643
- } else {
1644
- try { result = await runComfyGenerate(stagePrompts[i], size, { seed, unet }) } catch (e) { result = { ok: false, message: `生成失败:${e.message}` } }
1645
- }
1646
- return result
1653
+ if (cfg.queueEnabled) {
1654
+ try { result = await queuedTasks[i] } catch (e) { result = { ok: false, message: `生成失败:${e.message}` } }
1655
+ } else {
1656
+ try { result = await runComfyGenerate(stagePrompts[i], size, { seed, unet }) } catch (e) { result = { ok: false, message: `生成失败:${e.message}` } }
1657
+ }
1658
+ result.prompt = stagePrompts[i]
1659
+ return result
1647
1660
  }
1648
1661
 
1649
1662
  const { results, successCount } = await executeBatch(USERID, isAdmin, count, price, runOne)
1650
1663
 
1651
- // 汇总
1652
- const allOutputs = []
1653
- const seeds = []
1664
+ // 汇总
1665
+ const allOutputs = []
1666
+ const forwardOutputs = []
1667
+ const seeds = []
1654
1668
  const failures = []
1655
1669
  for (const item of results) {
1656
- if (item.ok) {
1657
- allOutputs.push(...item.outputs)
1670
+ if (item.ok) {
1671
+ allOutputs.push(...item.outputs)
1672
+ forwardOutputs.push(...item.outputs.map(src => ({ src, prompt: item.prompt || stagePrompts[item.i] || '' })))
1658
1673
  if (item.seed != null) seeds.push(item.seed)
1659
1674
  } else {
1660
1675
  failures.push(`第 ${item.i + 1} 阶段:${item.message}`)
@@ -1669,7 +1684,7 @@ exports.apply = async function apply(ctx, cfg) {
1669
1684
  if (cfg.outputLogs) logger.success(`${USERID} 连续图生成成功 ${successCount}/${count} 阶段(seed=${seed})`)
1670
1685
 
1671
1686
  // 发图:合并转发,不引用原指令
1672
- await sendImagesAsForward(session, allOutputs)
1687
+ await sendImagesAsForward(session, forwardOutputs)
1673
1688
 
1674
1689
  const reply = []
1675
1690
  reply.push(session.text('.series-ok', [count * price, successCount, seed]))
@@ -2314,7 +2329,8 @@ exports.apply = async function apply(ctx, cfg) {
2314
2329
  const optimized = await optimizePrompt(session, userPrompt, true, i2iRule, searchCache)
2315
2330
  p = appendInlineProtectedTags(composePrompt(optimized.prompt || userPrompt, raw).prompt, userPrompt, raw)
2316
2331
  }
2317
- return runComfyGenerate(p, parsedSize.size, Object.assign({ unet, seed }, i2iRun))
2332
+ const generated = await runComfyGenerate(p, parsedSize.size, Object.assign({ unet, seed }, i2iRun))
2333
+ return { ...generated, prompt: p }
2318
2334
  }, { USERID, isAdmin, totalPrice: count * cfg.price })
2319
2335
  if (!queued.ok) return queued.message
2320
2336
  const queuedTasks = queued.tasks
@@ -2352,22 +2368,25 @@ exports.apply = async function apply(ctx, cfg) {
2352
2368
  let result
2353
2369
  if (cfg.queueEnabled) {
2354
2370
  try { result = await queuedTasks[i] } catch (e) { result = { ok: false, message: `生成失败:${e.message}` } }
2355
- } else {
2356
- try { result = await runComfyGenerate(p, parsedSize.size, Object.assign({ unet, seed }, i2iRun)) } catch (e) { result = { ok: false, message: `生成失败:${e.message}` } }
2357
- }
2358
- return result
2371
+ } else {
2372
+ try { result = await runComfyGenerate(p, parsedSize.size, Object.assign({ unet, seed }, i2iRun)) } catch (e) { result = { ok: false, message: `生成失败:${e.message}` } }
2373
+ }
2374
+ if (!result.prompt) result.prompt = p
2375
+ return result
2359
2376
  }
2360
2377
 
2361
2378
  const { results, successCount } = await executeBatch(USERID, isAdmin, count, cfg.price, runOne)
2362
2379
 
2363
- // 汇总
2364
- const allOutputs = []
2365
- const seeds = []
2366
- const failures = []
2367
- for (const item of results) {
2368
- if (item.ok) {
2369
- allOutputs.push(...item.outputs)
2370
- if (item.seed != null) seeds.push(item.seed)
2380
+ // 汇总
2381
+ const allOutputs = []
2382
+ const forwardOutputs = []
2383
+ const seeds = []
2384
+ const failures = []
2385
+ for (const item of results) {
2386
+ if (item.ok) {
2387
+ allOutputs.push(...item.outputs)
2388
+ forwardOutputs.push(...item.outputs.map(src => ({ src, prompt: item.prompt || finalPrompt })))
2389
+ if (item.seed != null) seeds.push(item.seed)
2371
2390
  } else {
2372
2391
  failures.push(`第 ${item.i + 1} 张:${item.message}`)
2373
2392
  }
@@ -2381,7 +2400,7 @@ exports.apply = async function apply(ctx, cfg) {
2381
2400
  if (cfg.outputLogs) logger.success(`${USERID} 生成成功 ${successCount}/${count} 张`)
2382
2401
 
2383
2402
  // 发图:合并转发,不引用原指令
2384
- await sendImagesAsForward(session, allOutputs)
2403
+ await sendImagesAsForward(session, forwardOutputs)
2385
2404
 
2386
2405
  const reply = []
2387
2406
  if (count > 1) {
@@ -2534,5 +2553,5 @@ exports.apply = async function apply(ctx, cfg) {
2534
2553
  })
2535
2554
 
2536
2555
  // 暴露内部接口供自动化测试调用(Koishi 忽略 apply 返回值,不影响生产行为)
2537
- return { couponConfirmFlow, buyCouponsAndConsume, normalizeConfirm, resolveCouponPrice, handleGenerateI2I, extractImageFromSession, uploadImageToComfyui, taggerImage, animaI2IWorkflow, animaStyleI2IWorkflow, animaOotdI2IWorkflow, buildI2IWorkflow, detectI2ICapabilities, parseDenoise, sendNotices, sendImagesAsForward }
2556
+ return { couponConfirmFlow, buyCouponsAndConsume, normalizeConfirm, resolveCouponPrice, handleGenerateI2I, extractImageFromSession, uploadImageToComfyui, taggerImage, animaI2IWorkflow, animaStyleI2IWorkflow, animaOotdI2IWorkflow, buildI2IWorkflow, detectI2ICapabilities, parseDenoise, sendNotices, sendImagesAsForward, executeBatch }
2538
2557
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-p-draw",
3
3
  "description": "p点-绘图,连接本地 ComfyUI 生图。支持自然语言优化、尺寸选择、画师组、固定角色、生成队列,需要配合 p-qiandao 使用",
4
- "version": "1.3.2",
4
+ "version": "1.3.3",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "index.js",