@mokuyoaxis/dsh-iris 0.2.7 → 0.3.0-beta.1

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 (234) hide show
  1. package/CHANGELOG.md +143 -1
  2. package/README.en.md +109 -311
  3. package/README.md +106 -309
  4. package/bin/dsh-iris.js +452 -92
  5. package/docs/AGENT_CORE_QUICKSTART.md +64 -0
  6. package/docs/AGENT_QUICKSTART.md +2 -0
  7. package/docs/ARCHITECTURE.md +13 -3
  8. package/docs/ARTIFACT_MANIFEST.md +4 -0
  9. package/docs/AUDIO_FLOWS.md +87 -0
  10. package/docs/BETA_SCOPE.md +42 -0
  11. package/docs/CONTACT_SHEET.md +62 -0
  12. package/docs/CORE_CONNECTIONS.md +101 -0
  13. package/docs/CORE_QUICKSTART.md +110 -0
  14. package/docs/CORE_RUNTIME_CONTRACT.md +7 -2
  15. package/docs/CORE_SERVICE.md +101 -0
  16. package/docs/CORE_SKILLS.md +35 -3
  17. package/docs/DSH_COMPATIBILITY.md +21 -1
  18. package/docs/DSH_CORE_MIGRATION.md +6 -0
  19. package/docs/DSH_RC2_ADAPTATION.md +1 -1
  20. package/docs/DSH_RUNTIME.md +6 -2
  21. package/docs/HEADLESS_CLI.md +30 -5
  22. package/docs/HOST_ADAPTER_CONTRACT.md +5 -3
  23. package/docs/IMAGE_FLOW.md +4 -0
  24. package/docs/IMAGE_GENERATION.md +127 -0
  25. package/docs/LOCAL_API.md +4 -2
  26. package/docs/LOCAL_API_CONTRACT.md +42 -15
  27. package/docs/LOCAL_CREATION.md +7 -1
  28. package/docs/LOCAL_OPERATIONS.md +46 -0
  29. package/docs/LOCAL_SELECTION.md +57 -0
  30. package/docs/LOCAL_WEB.md +207 -0
  31. package/docs/MCP.md +212 -0
  32. package/docs/MEDIA_SCHEDULING.md +72 -0
  33. package/docs/MUSIC.md +2 -0
  34. package/docs/NETWORK_RESOURCES.md +106 -0
  35. package/docs/NETWORK_ROUTING.md +103 -0
  36. package/docs/NETWORK_WORKBENCH.md +65 -0
  37. package/docs/PANEL_EXTENSIONS.md +64 -0
  38. package/docs/PLUGINS.md +193 -0
  39. package/docs/PLUGIN_DEVELOPMENT.md +39 -0
  40. package/docs/PLUGIN_STARTUP.md +113 -0
  41. package/docs/PROVIDER_ADAPTER_CONTRACT.md +8 -2
  42. package/docs/PROVIDER_CREDENTIALS.md +67 -0
  43. package/docs/PROVIDER_EXTENSIONS.md +82 -0
  44. package/docs/PROVIDER_HEALTH.md +5 -1
  45. package/docs/PROVIDER_NETWORK.md +45 -0
  46. package/docs/PROVIDER_PLUGIN_CONTRACT.md +24 -0
  47. package/docs/PROVIDER_SETTINGS.md +91 -0
  48. package/docs/PROVIDER_SUBMISSION_CONTRACT.md +8 -0
  49. package/docs/ROADMAP.md +18 -8
  50. package/docs/SECURITY.md +1 -1
  51. package/docs/SYNC_DELIVERY_RECOVERY.md +5 -1
  52. package/docs/VIDEO_FLOWS.md +89 -0
  53. package/docs/VISION_CLI.md +1 -1
  54. package/docs/VISION_MODEL.md +2 -0
  55. package/docs/WEB_PROTOTYPE.md +21 -6
  56. package/docs/WEB_RECOVERY.md +46 -0
  57. package/docs/WORKBENCH_ARTIFACTS.md +6 -0
  58. package/docs/WORKBENCH_LOGIN.md +63 -0
  59. package/docs/assets/screenshots/core-connections-preview.webp +0 -0
  60. package/docs/assets/screenshots/core-model-pools-preview.webp +0 -0
  61. package/docs/assets/screenshots/core-overview-preview.webp +0 -0
  62. package/docs/assets/screenshots/core-spectrum-preview.webp +0 -0
  63. package/docs/assets/screenshots/core-studio-dark-preview.webp +0 -0
  64. package/docs/assets/screenshots/core-studio-mobile-preview.webp +0 -0
  65. package/docs/assets/screenshots/core-studio-preview.webp +0 -0
  66. package/docs/releases/0.3.0-alpha.1.md +51 -0
  67. package/docs/releases/0.3.0-beta.1.md +34 -0
  68. package/docs/screenshots.md +41 -1
  69. package/lib/actions.js +56 -4
  70. package/lib/adapters.js +75 -49
  71. package/lib/api.js +23 -13
  72. package/lib/application-runtime.js +73 -17
  73. package/lib/application-writer.js +193 -0
  74. package/lib/audio-flow-input.js +37 -0
  75. package/lib/client.d.ts +3 -3
  76. package/lib/client.js +402 -72
  77. package/lib/comfyui-adapter.js +95 -0
  78. package/lib/comfyui-client.js +60 -0
  79. package/lib/comfyui-inspection.js +73 -0
  80. package/lib/comfyui-workflow.js +94 -0
  81. package/lib/command-service.js +18 -5
  82. package/lib/config.js +49 -10
  83. package/lib/configured-image-runtime.js +69 -0
  84. package/lib/connection-skill.js +98 -0
  85. package/lib/connection-template.js +23 -0
  86. package/lib/core-artifact-store.js +84 -28
  87. package/lib/core-bridge-selection.js +31 -0
  88. package/lib/core-bridge-tools.js +85 -0
  89. package/lib/core-capability-catalog.js +192 -0
  90. package/lib/core-connection-diagnostics.js +49 -0
  91. package/lib/core-connection-store.js +277 -0
  92. package/lib/core-connections.js +225 -0
  93. package/lib/core-delivery-cache.js +8 -3
  94. package/lib/core-maintenance.js +9 -3
  95. package/lib/core-runtime.js +2 -1
  96. package/lib/core-service-client.js +174 -0
  97. package/lib/core-tasks.js +113 -21
  98. package/lib/core-writer-lease.js +16 -0
  99. package/lib/creation-request-store.js +41 -64
  100. package/lib/doctor.js +2 -2
  101. package/lib/dsh-core-adapter.js +61 -9
  102. package/lib/dsh-host-adapter.js +4 -1
  103. package/lib/dsh-provider-config.js +1 -1
  104. package/lib/dsh-service-bridge.js +67 -0
  105. package/lib/execution-ownership.js +43 -0
  106. package/lib/flow-store.js +156 -14
  107. package/lib/generation-input.js +10 -1
  108. package/lib/headless-vision.js +7 -7
  109. package/lib/image-edit-input.js +1 -1
  110. package/lib/image-flow.js +97 -31
  111. package/lib/index.js +47 -22
  112. package/lib/local-api-projection.js +15 -6
  113. package/lib/local-api-token-directory.js +31 -0
  114. package/lib/local-api.js +861 -71
  115. package/lib/local-creation.js +96 -24
  116. package/lib/local-operations.js +36 -0
  117. package/lib/local-request-root.js +86 -0
  118. package/lib/local-web-assets.js +39 -0
  119. package/lib/mcp-bridge.js +58 -0
  120. package/lib/mcp-check.js +26 -0
  121. package/lib/mcp-config.js +42 -0
  122. package/lib/mcp-delivery.js +46 -0
  123. package/lib/mcp-http.js +174 -0
  124. package/lib/mcp-server.js +69 -0
  125. package/lib/media-scheduler.js +194 -0
  126. package/lib/model-directory-service.js +37 -10
  127. package/lib/model-directory.js +3 -3
  128. package/lib/models.js +2 -2
  129. package/lib/music-transport.js +6 -3
  130. package/lib/network-config-service.js +123 -0
  131. package/lib/network-config.js +127 -0
  132. package/lib/network-doctor.js +94 -0
  133. package/lib/network-management.js +51 -0
  134. package/lib/network-policy.js +19 -0
  135. package/lib/network-route-evidence.js +38 -0
  136. package/lib/network-routing.js +283 -0
  137. package/lib/operation-runner.js +281 -0
  138. package/lib/operation-store.js +216 -0
  139. package/lib/panel-extension.js +36 -0
  140. package/lib/plugin-contract.js +194 -0
  141. package/lib/plugin-execution.js +51 -0
  142. package/lib/plugin-launch.js +86 -0
  143. package/lib/plugin-operation-store.js +101 -0
  144. package/lib/plugin-operations.js +28 -0
  145. package/lib/plugin-registry.js +109 -0
  146. package/lib/plugin-selection.js +15 -0
  147. package/lib/plugin-source-image.js +5 -0
  148. package/lib/plugin-web-assets.js +54 -0
  149. package/lib/plugins.d.ts +307 -0
  150. package/lib/plugins.js +59 -0
  151. package/lib/provider-adapter.js +7 -3
  152. package/lib/provider-adapters.js +92 -22
  153. package/lib/provider-catalog.js +64 -36
  154. package/lib/provider-config-format.js +51 -0
  155. package/lib/provider-config-service.js +143 -31
  156. package/lib/provider-context.d.ts +90 -0
  157. package/lib/provider-context.js +42 -0
  158. package/lib/provider-credentials.js +97 -0
  159. package/lib/provider-diagnostics.js +56 -0
  160. package/lib/provider-extensions.js +29 -0
  161. package/lib/provider-health.js +18 -6
  162. package/lib/provider-monitor.js +81 -0
  163. package/lib/provider-network.js +52 -0
  164. package/lib/provider-protocol.js +73 -22
  165. package/lib/provider-registry.js +128 -0
  166. package/lib/provider-response.js +38 -0
  167. package/lib/provider-settings.js +136 -0
  168. package/lib/provider-task-runner.js +132 -43
  169. package/lib/provider-transport.js +16 -0
  170. package/lib/render.js +11 -1
  171. package/lib/scheduling-policy.js +66 -0
  172. package/lib/skill-installation.js +23 -11
  173. package/lib/tasks.js +3 -2
  174. package/lib/video-flow-input.js +16 -0
  175. package/lib/video-input.js +12 -5
  176. package/lib/vision-model-routing.js +5 -0
  177. package/lib/web/app.js +703 -0
  178. package/lib/web/appearance.js +111 -0
  179. package/lib/web/comfyui.js +146 -0
  180. package/lib/web/connections.js +516 -0
  181. package/lib/web/creation.js +359 -0
  182. package/lib/web/flow-view.js +34 -0
  183. package/lib/web/gallery.js +133 -0
  184. package/lib/web/index.html +344 -0
  185. package/lib/web/media-client.js +218 -0
  186. package/lib/web/network.js +232 -0
  187. package/lib/web/plugin-panel.js +223 -0
  188. package/lib/web/plugin-status.js +140 -0
  189. package/lib/web/plugin-surface.css +17 -0
  190. package/lib/web/providers.js +513 -0
  191. package/lib/web/recovery.js +105 -0
  192. package/lib/web/startup.js +18 -0
  193. package/lib/web/styles.css +571 -0
  194. package/lib/web/transport.js +101 -0
  195. package/lib/web/workbench-login.js +165 -0
  196. package/lib/workbench-artifacts.js +3 -2
  197. package/lib/workbench-auth-cli.js +47 -0
  198. package/lib/workbench-auth.js +200 -0
  199. package/package.json +24 -6
  200. package/plugins/contact-sheet/index.d.mts +4 -0
  201. package/plugins/contact-sheet/index.mjs +33 -0
  202. package/plugins/contact-sheet/web/board.css +14 -0
  203. package/plugins/contact-sheet/web/board.mjs +93 -0
  204. package/skills/iris-core/SKILL.md +8 -4
  205. package/skills/iris-core/references/cli.md +2 -2
  206. package/skills/iris-core/references/comfyui.md +69 -0
  207. package/skills/iris-core/references/tasks.md +2 -0
  208. package/templates/plugin-starter/LICENSE +21 -0
  209. package/templates/plugin-starter/README.md +74 -0
  210. package/templates/plugin-starter/assets/peach.png +0 -0
  211. package/templates/plugin-starter/assets/violet.png +0 -0
  212. package/templates/plugin-starter/index.mjs +27 -0
  213. package/templates/plugin-starter/package.json +18 -0
  214. package/templates/plugin-starter/scripts/core-cli.mjs +20 -0
  215. package/templates/plugin-starter/scripts/environment.mjs +65 -0
  216. package/templates/plugin-starter/scripts/mcp-probe.mjs +22 -0
  217. package/templates/plugin-starter/scripts/setup.mjs +42 -0
  218. package/templates/plugin-starter/scripts/smoke.mjs +76 -0
  219. package/templates/plugin-starter/web/board.css +27 -0
  220. package/templates/plugin-starter/web/board.mjs +160 -0
  221. package/templates/plugin-starter/web/png.mjs +23 -0
  222. package/templates/provider-starter/LICENSE +21 -0
  223. package/templates/provider-starter/README.md +50 -0
  224. package/templates/provider-starter/index.d.mts +5 -0
  225. package/templates/provider-starter/index.mjs +165 -0
  226. package/templates/provider-starter/package.json +13 -0
  227. package/templates/provider-starter/scripts/core-cli.mjs +22 -0
  228. package/templates/provider-starter/scripts/environment.mjs +122 -0
  229. package/templates/provider-starter/scripts/fixture.mjs +54 -0
  230. package/templates/provider-starter/scripts/mcp-probe.mjs +21 -0
  231. package/templates/provider-starter/scripts/protocol-smoke.mjs +48 -0
  232. package/templates/provider-starter/scripts/setup.mjs +44 -0
  233. package/templates/provider-starter/scripts/smoke.mjs +145 -0
  234. package/user_guide.md +5 -1
package/lib/client.js CHANGED
@@ -21,7 +21,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
21
21
  var exports = module.exports;
22
22
 
23
23
  var React = require('react');
24
- var IRIS_CLIENT_VERSION = '0.2.7';
24
+ var IRIS_CLIENT_VERSION = '0.3.0-beta.1';
25
25
  var HOST_CLIENT_PROTOCOL_VERSION = 0;
26
26
  var reportedSeats = [];
27
27
 
@@ -109,6 +109,27 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
109
109
  '.iris-workbench-page :is(input, textarea, select) { font-family: inherit; accent-color: var(--iris-workbench-accent); }',
110
110
  '.iris-workbench-page input:not([type=checkbox]):not([type=radio]):not([type=file]), .iris-workbench-page textarea, .iris-workbench-page select { min-width: 0; max-width: 100%; min-height: 34px; padding: 5px 8px; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 8px; background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-primary, #e6e6e6); font-size: 12px; }',
111
111
  '.iris-workbench-page .iris-pm-note { line-height: 1.65; }',
112
+ '.iris-comfy-panel { position: relative; isolation: isolate; overflow: hidden; min-width: 0; display: grid; gap: 10px; padding: 12px; border: 1px solid color-mix(in srgb, #6F69B8 30%, var(--dsw-alias-border-l1, #3a3a44)); border-radius: 12px; background: linear-gradient(105deg, rgba(111,105,184,.07), rgba(229,155,118,.04)), var(--dsw-alias-bg-layer-1, #26262e); }',
113
+ '.iris-comfy-panel::before { content: ""; position: absolute; z-index: -1; pointer-events: none; inset: 0; background: linear-gradient(105deg, transparent 20%, #45317D18, #6F69B830, #BB78AD30, #E59B7630, #ED9C9120, #F0D09C30, transparent 80%); transform: translateX(110%); animation: iris-comfy-arrive .65s ease-out 1; }',
114
+ '@keyframes iris-comfy-arrive { from { transform: translateX(-110%); opacity: 0; } 25% { opacity: 1; } to { transform: translateX(110%); opacity: 0; } }',
115
+ '.iris-comfy-head { display: flex; gap: 8px; align-items: center; justify-content: space-between; flex-wrap: wrap; }',
116
+ '.iris-comfy-head strong { font-size: 13px; }',
117
+ '.iris-comfy-tag { border: 1px solid color-mix(in srgb, #BB78AD 30%, var(--dsw-alias-border-l1, #3a3a44)); border-radius: 20px; padding: 2px 8px; font-size: 11px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
118
+ '.iris-comfy-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }',
119
+ '.iris-comfy-panel label, .iris-comfy-grid label { display: grid; min-width: 0; gap: 5px; font-size: 12px; }',
120
+ '.iris-comfy-panel :is(input,select,textarea) { min-width: 0; max-width: 100%; width: 100%; box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 8px; padding: 6px 8px; background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-primary, #e6e6e6); font: inherit; }',
121
+ '.iris-comfy-panel input[type=checkbox] { width: auto; min-height: 0; }',
122
+ '.iris-comfy-panel summary { cursor: pointer; font-size: 12px; padding: 4px 0; }',
123
+ '.iris-comfy-panel details > div { margin-top: 8px; }',
124
+ '.iris-comfy-slots { display: grid; gap: 6px; max-height: 280px; overflow: auto; }',
125
+ '.iris-comfy-slot { display: flex; gap: 8px; align-items: center; min-width: 0; padding: 7px 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #3a3a44); }',
126
+ '.iris-comfy-slot label { flex: 1; display: flex; align-items: center; gap: 7px; overflow-wrap: anywhere; }',
127
+ '.iris-comfy-slot select { width: auto; max-width: 100px; }',
128
+ '.iris-comfy-result { font-size: 12px; line-height: 1.65; overflow-wrap: anywhere; }',
129
+ '.iris-comfy-result ul { padding-left: 20px; margin: 5px 0; }',
130
+ '.iris-comfy-panel .iris-act-run, .iris-comfy-card .iris-act-run { background: #6F69B8; color: #fff; border-color: transparent; }',
131
+ '@media (max-width: 640px) { .iris-comfy-grid { grid-template-columns: minmax(0, 1fr); } .iris-comfy-panel { padding: 10px; } }',
132
+ '@media (prefers-reduced-motion: reduce) { .iris-comfy-panel::before { animation: none; opacity: 0; } }',
112
133
  '.iris-workbench-page .iris-pm-mrows { max-height: 400px; gap: 8px; }',
113
134
  '.iris-workbench-page .iris-pm-mrow { min-width: 0; padding: 10px; gap: 6px; flex-wrap: wrap; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 10px; }',
114
135
  '.iris-workbench-page .iris-pm-mname { flex-basis: 100%; white-space: normal; overflow-wrap: anywhere; font-size: 13px; }',
@@ -183,7 +204,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
183
204
  /* ---- 操作卡片组(阶段 5 GUI 直连) ---- */
184
205
  '.iris-act-group { display: grid; gap: 8px; }',
185
206
  '.iris-act-card { background: var(--dsw-alias-bg-layer-1, #26262e); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 8px; overflow: hidden; }',
186
- '.iris-act-head { display: flex; align-items: center; gap: 8px; padding: 8px 10px; cursor: pointer; user-select: none; }',
207
+ '.iris-act-head { display: flex; align-items: center; width:100%; gap: 8px; padding: 8px 10px; border:0; background:transparent; color:inherit; font:inherit; text-align:left; cursor: pointer; user-select: none; }',
187
208
  '.iris-act-head:hover { background: var(--dsw-alias-bg-layer-2, #32323c); }',
188
209
  '.iris-act-title { font-size: 13px; font-weight: 600; flex: 1; }',
189
210
  '.iris-act-arrow { color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 10px; transition: transform 0.15s; }',
@@ -317,7 +338,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
317
338
  '.iris-bubble-more { font-size: 11px; color: var(--dsw-alias-brand-primary, #7aa2f7); background: transparent; border: none; cursor: pointer; padding: 4px 2px; text-align: left; }',
318
339
  /* 历史浏览器(设置页按日期分组折叠) */
319
340
  '.iris-hist-group { margin-bottom: 4px; }',
320
- '.iris-hist-head { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-secondary, #9a9a9a); cursor: pointer; padding: 4px 2px; }',
341
+ '.iris-hist-head { display: flex; align-items: center; width:100%; gap: 6px; border:0; background:transparent; text-align:left; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-secondary, #9a9a9a); cursor: pointer; padding: 4px 2px; }',
321
342
  '.iris-hist-head:hover { color: var(--dsw-alias-label-primary, #e6e6e6); }',
322
343
  /* 常用卡片选择器 + 清理区 */
323
344
  '.iris-pick-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; }',
@@ -396,7 +417,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
396
417
  '.iris-ff-val { font-size: 11px; color: var(--dsw-alias-label-secondary, #9a9a9a); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }',
397
418
  '.iris-ff-val.set { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
398
419
  '.iris-ff-list { display: grid; gap: 2px; max-height: 160px; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 6px; padding: 4px; background: var(--dsw-alias-bg-layer-2, #32323c); }',
399
- '.iris-ff-item { display: flex; align-items: center; gap: 6px; font-size: 11px; cursor: pointer; padding: 2px 4px; border-radius: 4px; }',
420
+ '.iris-ff-item { display: flex; align-items: center; width:100%; gap: 6px; border:0; background:transparent; color:inherit; text-align:left; font-size: 11px; cursor: pointer; padding: 2px 4px; border-radius: 4px; }',
400
421
  '.iris-ff-item:hover { background: var(--dsw-alias-bg-layer-1, #26262e); }',
401
422
  '.iris-ff-item .iris-ff-src { color: var(--dsw-alias-brand-primary, #7aa2f7); font-size: 10px; }',
402
423
  /* 对话框级提示词优化器:桌面浮层 + 窄屏底部 Sheet */
@@ -546,6 +567,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
546
567
  for (var i = 0; i < sharedListeners.length; i++) {
547
568
  sharedListeners[i](data);
548
569
  }
570
+ window.dispatchEvent(new CustomEvent('iris-core-refresh-tick'));
549
571
  }
550
572
  function load() {
551
573
  fetch('/iris/api/state')
@@ -553,7 +575,6 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
553
575
  .then(function (data) {
554
576
  if (data) {
555
577
  apply(data);
556
- window.dispatchEvent(new CustomEvent('iris-core-refresh-tick'));
557
578
  }
558
579
  })
559
580
  .catch(function () { /* 网络抖动静默 */ });
@@ -676,7 +697,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
676
697
  : option.protocolSupported === false ? option.reason || '当前接口协议不支持此功能。'
677
698
  : option.unavailable && !health.rateLimited && !health.modelUnavailable && !option.modelUnavailable ? option.reason || '连接尚未就绪,请先检查供应商设置。' : '';
678
699
  return { ...option, providerName: provider ? provider.name : 'DSH 宿主', health: health,
679
- taskOnly: row.taskOnly || host, reason: reason, canTest: !reason };
700
+ taskOnly: row.taskOnly || host || option.imageRouting && option.imageRouting.mediaProtocol === 'comfyui', reason: reason, canTest: !reason };
680
701
  });
681
702
  }
682
703
  function CapabilityHealthOverview() {
@@ -745,7 +766,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
745
766
  if (field) { revealWorkbenchItem(field); field.scrollIntoView({ block: 'center', behavior: 'smooth' }); field.focus({ preventScroll: true }); }
746
767
  }
747
768
  function openTask(row) {
748
- var card = document.querySelector('[data-iris-action="' + row.action + '"]');
769
+ var card = document.querySelector('[data-iris-action="' + (row.cap === 'image-gen' ? 'image' : row.action) + '"]');
749
770
  if (!card) return;
750
771
  revealWorkbenchItem(card);
751
772
  if (!card.classList.contains('open')) card.querySelector('.iris-act-head').click();
@@ -841,7 +862,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
841
862
  React.createElement('button', { className: 'iris-health-btn' + (!option.taskOnly ? ' primary' : ''),
842
863
  disabled: !fresh || !option.canTest || !!busy || !!(result && result.phase === 'unknown'),
843
864
  onClick: function () { if (running.current) return; if (option.taskOnly) openTask(row); else confirmPair[1]({ cap: row.cap, ref: option.ref }); } },
844
- option.taskOnly ? '前往功能验证 ↗' : result && result.phase === 'running' ? '正在实测…' : '实测此模型')),
865
+ option.taskOnly ? option.comfyWorkflow ? '前往画图选择工作流 ↗' : '前往功能验证 ↗' : result && result.phase === 'running' ? '正在实测…' : '实测此模型')),
845
866
  option.reason ? React.createElement('p', { className: 'iris-health-caption' }, option.reason) : null,
846
867
  option.ref === 'host:current-vision' ? React.createElement('p', { className: 'iris-health-caption' },
847
868
  '宿主视觉通过真实图片任务验证,请在打开的表单中选择 DSH 当前默认模型。') : null,
@@ -1176,6 +1197,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1176
1197
  React.createElement('option', { value: 'dashscope' }, 'DashScope'),
1177
1198
  React.createElement('option', { value: 'openai-images' }, 'OpenAI Images'),
1178
1199
  React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
1200
+ p.mediaProtocol === 'comfyui' || m.imageProtocol === 'comfyui' ? React.createElement('option', { value: 'comfyui' }, 'ComfyUI · API 工作流') : null,
1179
1201
  React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图')),
1180
1202
  m.imageRouting && m.imageRouting.protocolInferred ? ' · 协议待确认' : null) : null,
1181
1203
  !m.directoryOnly && caps.includes('music-gen') ? React.createElement('label', { className: 'iris-pm-note' }, '音乐协议 ',
@@ -1190,9 +1212,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1190
1212
  return React.createElement('div', { className: 'iris-pm-pool' },
1191
1213
  React.createElement('div', { className: 'iris-pm-note' }, '模型池(' + models.filter(function (m) { return !m.directoryOnly; }).length + ')'
1192
1214
  + (models.some(function (m) { return m.directoryOnly; }) ? ' · 目录待确认 ' + models.filter(function (m) { return m.directoryOnly; }).length + ' 项' : '') + ':'),
1193
- rows.length ? React.createElement('div', { className: 'iris-pm-mrows' }, rows) : React.createElement('div', { className: 'iris-pm-note' }, '暂无模型 — 展开「模型目录」同步,或在下方手动添加'),
1194
- React.createElement(ModelUsageForm, { provider: p, setupSuggested: props.setupSuggested,
1195
- onNote: props.onNote, onDone: props.onDone }));
1215
+ rows.length ? React.createElement('div', { className: 'iris-pm-mrows' }, rows) : React.createElement('div', { className: 'iris-pm-note' }, p.mediaProtocol === 'comfyui'
1216
+ ? '尚无工作流,请在上方导入 API 图并保存。' : '暂无模型 — 展开「模型目录」同步,或在下方手动添加'),
1217
+ p.mediaProtocol !== 'comfyui' ? React.createElement(ModelUsageForm, { provider: p, setupSuggested: props.setupSuggested,
1218
+ onNote: props.onNote, onDone: props.onDone }) : null);
1196
1219
  }
1197
1220
 
1198
1221
  function ModelUsageForm(props) {
@@ -1343,7 +1366,172 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1343
1366
  React.createElement('div', { className: 'iris-wb-row' },
1344
1367
  React.createElement('input', { value: value, placeholder: '留空则沿用 Base URL', onChange: function (e) { setValue(e.target.value); } }),
1345
1368
  React.createElement('button', { className: 'iris-pm-btn', onClick: save }, '保存')),
1346
- React.createElement('span', { className: 'iris-pm-note' }, '百炼 Workspace 可填 …maas.aliyuncs.com/compatible-mode/v1;视觉仍使用普通 Base URL。'));
1369
+ React.createElement('span', { className: 'iris-pm-note' }, p.mediaProtocol === 'comfyui' ? '留空沿用已保存地址;反向代理有独立媒体地址时才填写。'
1370
+ : '百炼 Workspace 可填 …maas.aliyuncs.com/compatible-mode/v1;视觉仍使用普通 Base URL。'));
1371
+ }
1372
+
1373
+ function ProviderAuthenticationEditor(props) {
1374
+ var p = props.provider, authPair = React.useState(p.auth), keyPair = React.useState(''), notePair = React.useState('');
1375
+ var busyPair = React.useState(false), active = React.useRef(null), unknownPair = React.useState(false);
1376
+ React.useEffect(function () { authPair[1](p.auth); }, [p.auth]);
1377
+ React.useEffect(function () { return function () { if (active.current) active.current.abort(); }; }, []);
1378
+ async function save() {
1379
+ if (active.current || unknownPair[0]) return;
1380
+ var controller = new AbortController(), timer = setTimeout(function () { controller.abort(); }, 15000);
1381
+ active.current = controller; busyPair[1](true);
1382
+ try {
1383
+ var result = await postAction('providers_upsert', { id: p.id, auth: authPair[0], ...(authPair[0] === 'bearer' && keyPair[0].trim() ? { apiKey: keyPair[0].trim() } : {}) }, { signal: controller.signal });
1384
+ if (controller.signal.aborted) throw new Error('请求已结束');
1385
+ if (!result.ok) { notePair[1](result.d.error || '保存失败'); return; }
1386
+ keyPair[1](''); notePair[1]('认证设置已保存'); props.onDone();
1387
+ } catch (_) { unknownPair[1](true); notePair[1]('保存结果未知,请重新打开此供应商核对;未自动重试。'); }
1388
+ finally { clearTimeout(timer); active.current = null; busyPair[1](false); }
1389
+ }
1390
+ return React.createElement('details', {}, React.createElement('summary', {}, '认证设置'),
1391
+ React.createElement('fieldset', { disabled: busyPair[0] || unknownPair[0], style: { border: 0, padding: 0, margin: '8px 0', minWidth: 0 } },
1392
+ React.createElement('label', { className: 'iris-pm-field' }, '认证方式', React.createElement('select', { value: authPair[0], onChange: function (event) { authPair[1](event.target.value); } },
1393
+ React.createElement('option', { value: 'none' }, '无需认证'), React.createElement('option', { value: 'bearer' }, 'Bearer · API Key'))),
1394
+ authPair[0] === 'bearer' ? React.createElement('label', { className: 'iris-pm-field' }, '替换 API Key(留空保留)', React.createElement('input', { type: 'password', value: keyPair[0], onChange: function (event) { keyPair[1](event.target.value); } })) : null,
1395
+ React.createElement('p', { className: 'iris-pm-note' }, '无需认证时不会发送保留的 Key;更改认证会使旧的健康证据失效。'),
1396
+ React.createElement('button', { className: 'iris-pm-btn', onClick: save }, busyPair[0] ? '保存中…' : '保存认证')),
1397
+ notePair[0] ? React.createElement('div', { role: 'status', className: 'iris-pm-note' }, notePair[0]) : null);
1398
+ }
1399
+
1400
+ function ComfyCheckResult(props) {
1401
+ var report = props.report;
1402
+ if (!report) return null;
1403
+ return React.createElement('div', { className: 'iris-comfy-result', role: 'status' },
1404
+ React.createElement('strong', {}, report.ok ? '静态检查通过 · 未生成图片' : '需要修正 ' + report.issues.length + ' 项 · 未生成图片'),
1405
+ report.issues.length ? React.createElement('ul', {}, report.issues.map(function (issue, i) {
1406
+ return React.createElement('li', { key: i }, issue.message);
1407
+ })) : null,
1408
+ React.createElement('div', { className: 'iris-pm-note' }, '只读取节点定义;不证明 GPU、显存、模型家族或自定义节点能够实际运行。'));
1409
+ }
1410
+
1411
+ function comfySlots(draft) {
1412
+ var result = [], labels = { lora_name: 'LoRA', strength_model: '模型权重', strength_clip: 'CLIP 权重',
1413
+ ckpt_name: 'Checkpoint', seed: '种子', steps: '步数', cfg: 'CFG', sampler_name: '采样器', scheduler: '调度器', text: '文本' };
1414
+ Object.entries(draft.graph).forEach(function (pair) {
1415
+ var id = pair[0], node = pair[1];
1416
+ Object.entries(node.inputs).forEach(function (field) {
1417
+ var input = field[0], value = field[1];
1418
+ if (!['string', 'boolean', 'number'].includes(typeof value) || id === draft.promptNode && input === 'text'
1419
+ || id === draft.sizeNode && ['width', 'height'].includes(input)) return;
1420
+ var parameter = draft.parameters.find(function (item) { return item.node === id && item.input === input; });
1421
+ result.push({ key: id + '/' + input, value: value, selected: !!parameter, parameter: parameter || {
1422
+ id: ('n' + id.replace(/[^A-Za-z0-9_]/g, '_') + '_' + input.replace(/[^A-Za-z0-9_]/g, '_')).slice(0, 54) + '_' + result.length,
1423
+ label: (labels[input] || input) + ' · ' + node.class_type + ' #' + id, node: id, input: input,
1424
+ type: typeof value === 'number' ? Number.isSafeInteger(value) && !/^(strength_|cfg$|denoise$)/.test(input) ? 'integer' : 'number' : typeof value
1425
+ } });
1426
+ });
1427
+ });
1428
+ return result;
1429
+ }
1430
+ function comfyDraftWorkflow(draft) {
1431
+ if (!draft.promptNode || !draft.outputNode) throw new Error('请明确选择正向提示词与图片输出节点');
1432
+ var parameters = draft.parameters;
1433
+ return { version: parameters.length ? 1 : 0, graph: draft.graph, promptNode: draft.promptNode, outputNode: draft.outputNode,
1434
+ ...(draft.sizeNode ? { sizeNode: draft.sizeNode } : {}), ...(parameters.length ? { parameters: parameters } : {}) };
1435
+ }
1436
+
1437
+ function ComfyWorkflowEditor(props) {
1438
+ var p = props.provider, draftPair = React.useState(null), draft = draftPair[0], namePair = React.useState('');
1439
+ var autoPair = React.useState(props.setupSuggested === true), reportPair = React.useState(null), notePair = React.useState('');
1440
+ var busyPair = React.useState(''), unknownPair = React.useState(false), replacePair = React.useState(false);
1441
+ var active = React.useRef(null), importVersion = React.useRef(0), original = React.useRef({});
1442
+ React.useEffect(function () { return function () { importVersion.current++; if (active.current) active.current.abort(); }; }, []);
1443
+ function change(field, value) {
1444
+ var next = { ...draft, [field]: value };
1445
+ if (field === 'promptNode' || field === 'sizeNode') next.parameters = next.parameters.filter(function (item) {
1446
+ return !(item.node === next.promptNode && item.input === 'text' || item.node === next.sizeNode && ['width', 'height'].includes(item.input));
1447
+ });
1448
+ draftPair[1](next); reportPair[1](null); replacePair[1](false); notePair[1]('');
1449
+ }
1450
+ async function read(event) {
1451
+ var file = event.target.files[0], ticket = ++importVersion.current;
1452
+ if (!file) return;
1453
+ try {
1454
+ if (file.size > 40 * 1024) throw new Error('工作流最多 40 KiB');
1455
+ var raw = JSON.parse(await file.text()), template = raw && raw.graph && [0, 1].includes(raw.version) ? raw : null;
1456
+ if (ticket !== importVersion.current) return;
1457
+ var graph = template ? template.graph : raw, entries = graph && typeof graph === 'object' && !Array.isArray(graph) ? Object.entries(graph) : [];
1458
+ if (!entries.length || entries.length > 256 || entries.some(function (entry) { var node = entry[1]; return !node || typeof node.class_type !== 'string'
1459
+ || !node.inputs || typeof node.inputs !== 'object' || Array.isArray(node.inputs); })) throw new Error('请使用 Export (API) 节点图或 Iris 参数模板,不支持画布 JSON');
1460
+ var outputs = entries.filter(function (entry) { return entry[1].class_type === 'SaveImage'; });
1461
+ if (template && (template.version === 0 && template.parameters !== undefined || template.version === 1 && (!Array.isArray(template.parameters) || !template.parameters.length))
1462
+ || template && template.parameters !== undefined && (!Array.isArray(template.parameters) || template.parameters.some(function (item) {
1463
+ return !item || typeof item.id !== 'string' || typeof item.node !== 'string' || typeof item.input !== 'string' || !['string', 'integer', 'number', 'boolean'].includes(item.type);
1464
+ }))) throw new Error('参数声明格式不正确');
1465
+ draftPair[1]({ graph: graph, promptNode: template ? template.promptNode : '', outputNode: template ? template.outputNode : outputs.length === 1 ? outputs[0][0] : '',
1466
+ sizeNode: template && template.sizeNode || '', parameters: template && template.parameters || [] });
1467
+ original.current = Object.fromEntries((p.models || []).map(function (model) { return [model.id, model.comfyWorkflow && model.comfyWorkflow.sha256 || null]; }));
1468
+ namePair[1](file.name.replace(/\.json$/i, '').slice(0, 120)); reportPair[1](null); replacePair[1](false);
1469
+ notePair[1]('已读取 ' + entries.length + ' 个节点。请核对绑定,再勾选希望在画图时调整的参数。');
1470
+ } catch (error) { if (ticket === importVersion.current) notePair[1]('导入失败:' + error.message + ';原草稿保留。'); }
1471
+ }
1472
+ async function operate(kind, confirmed) {
1473
+ if (active.current || unknownPair[0]) return;
1474
+ var workflow, name = namePair[0].trim();
1475
+ try { workflow = comfyDraftWorkflow(draft); if (kind === 'save' && !name) throw new Error('请填写工作流名称'); }
1476
+ catch (error) { notePair[1](error.message); return; }
1477
+ var exists = Object.hasOwn(original.current, name);
1478
+ if (kind === 'save' && exists && !confirmed) { replacePair[1](true); return; }
1479
+ var controller = new AbortController(); active.current = controller; busyPair[1](kind); notePair[1]('');
1480
+ var timer = setTimeout(function () { controller.abort(); }, 20000);
1481
+ try {
1482
+ var result = await postAction(kind === 'check' ? 'providers_check_comfy_workflow' : 'providers_save_comfy_workflow', kind === 'check'
1483
+ ? { id: p.id, workflow: workflow } : { id: p.id, model_id: name, workflow: workflow,
1484
+ expectedSha: exists ? original.current[name] : null, enableAutomatic: autoPair[0] }, { signal: controller.signal });
1485
+ if (controller.signal.aborted) throw new Error('请求已结束');
1486
+ if (!result.ok || result.d.ok !== true) { notePair[1](result.d.error || '操作未完成'); return; }
1487
+ if (kind === 'check') reportPair[1](result.d.report);
1488
+ else {
1489
+ original.current[name] = result.d.comfyWorkflow.sha256; replacePair[1](false); notePair[1](result.d.text);
1490
+ props.onDone(); window.dispatchEvent(new CustomEvent('iris-function-configuration-changed'));
1491
+ }
1492
+ } catch (_) {
1493
+ if (kind === 'save') { unknownPair[1](true); notePair[1]('保存结果未知,未自动重试。请关闭并重新打开此供应商,核对已保存工作流;草稿仍在此处。'); }
1494
+ else { reportPair[1](null); notePair[1]('检查未完成,请核对连接后重试;未发起生成。'); }
1495
+ } finally { clearTimeout(timer); active.current = null; busyPair[1](''); }
1496
+ }
1497
+ var entries = draft ? Object.entries(draft.graph) : [], slots = draft ? comfySlots(draft) : [];
1498
+ function select(field, label, options) {
1499
+ return React.createElement('label', {}, label, React.createElement('select', { value: draft[field], onChange: function (event) { change(field, event.target.value); } },
1500
+ React.createElement('option', { value: '' }, field === 'sizeNode' ? '沿用工作流尺寸' : '请选择'),
1501
+ options.map(function (entry) { return React.createElement('option', { key: entry[0], value: entry[0] }, '#' + entry[0] + ' · ' + entry[1].class_type); })));
1502
+ }
1503
+ return React.createElement('section', { className: 'iris-comfy-panel', 'aria-label': 'ComfyUI 工作流' },
1504
+ React.createElement('div', { className: 'iris-comfy-head' }, React.createElement('strong', {}, 'ComfyUI · 工作流'), React.createElement('span', { className: 'iris-comfy-tag' }, '已有服务 · 单张文生图')),
1505
+ React.createElement('p', { className: 'iris-pm-note' }, '导入 API 工作流,保留已有节点与 LoRA。检查和保存不会生成图片。'),
1506
+ React.createElement('fieldset', { disabled: !!busyPair[0] || unknownPair[0], style: { border: 0, padding: 0, margin: 0, minWidth: 0, display: 'grid', gap: 10 } },
1507
+ React.createElement('label', {}, '导入工作流 JSON', React.createElement('input', { type: 'file', accept: '.json,application/json', onChange: read, 'data-comfy-file': p.id })),
1508
+ draft ? React.createElement(React.Fragment, {},
1509
+ React.createElement('label', {}, '工作流名称', React.createElement('input', { value: namePair[0], maxLength: 120, onChange: function (event) { namePair[1](event.target.value); replacePair[1](false); } })),
1510
+ React.createElement('div', { className: 'iris-comfy-grid' },
1511
+ select('promptNode', '正向提示词节点', entries.filter(function (entry) { return typeof entry[1].inputs.text === 'string'; })),
1512
+ select('outputNode', '图片输出节点', entries),
1513
+ select('sizeNode', '尺寸节点', entries.filter(function (entry) { return ['width', 'height'].every(function (key) { return Number.isSafeInteger(entry[1].inputs[key]) && entry[1].inputs[key] > 0; }); }))),
1514
+ React.createElement('details', {}, React.createElement('summary', {}, '公开参数 · 已选 ' + slots.filter(function (slot) { return slot.selected; }).length + ' / 32'),
1515
+ React.createElement('div', { className: 'iris-comfy-slots' }, slots.map(function (slot) {
1516
+ return React.createElement('div', { key: slot.key, className: 'iris-comfy-slot' },
1517
+ React.createElement('label', {}, React.createElement('input', { type: 'checkbox', checked: slot.selected, onChange: function (event) {
1518
+ if (event.target.checked && draft.parameters.length >= 32) { notePair[1]('最多公开 32 个参数'); return; }
1519
+ change('parameters', draft.parameters.filter(function (item) { return item.node !== slot.parameter.node || item.input !== slot.parameter.input; }).concat(event.target.checked ? [slot.parameter] : []));
1520
+ } }), slot.parameter.label || slot.parameter.id),
1521
+ slot.selected ? React.createElement('select', { 'aria-label': (slot.parameter.label || slot.parameter.id) + '类型', value: slot.parameter.type, onChange: function (event) {
1522
+ change('parameters', draft.parameters.map(function (item) { return item.id === slot.parameter.id ? { id: item.id, node: item.node, input: item.input, label: item.label, type: event.target.value } : item; }));
1523
+ } }, (typeof slot.value === 'number' ? [['integer', '整数'], ['number', '数值']] : [[typeof slot.value, typeof slot.value === 'boolean' ? '开关' : '文本']]).map(function (pair) { return React.createElement('option', { key: pair[0], value: pair[0] }, pair[1]); })) : null);
1524
+ }))),
1525
+ React.createElement('label', { style: { display: 'flex', alignItems: 'center' } }, React.createElement('input', { type: 'checkbox', checked: autoPair[0], onChange: function (event) { autoPair[1](event.target.checked); } }), '保存时将图片功能设为自动池'),
1526
+ React.createElement('div', { className: 'iris-wb-row' },
1527
+ React.createElement('button', { className: 'iris-pm-btn', onClick: function () { operate('check'); } }, busyPair[0] === 'check' ? '检查中…' : '只读检查'),
1528
+ React.createElement('button', { className: 'iris-act-run', onClick: function () { operate('save'); } }, busyPair[0] === 'save' ? '保存中…' : '保存工作流')),
1529
+ replacePair[0] ? React.createElement('div', { className: 'iris-comfy-result', role: 'group', 'aria-label': '确认替换工作流' },
1530
+ React.createElement('p', {}, '替换「' + namePair[0] + '」?旧任务不会改绑,更换模板可能影响旧任务的继续观察。'),
1531
+ React.createElement('button', { className: 'iris-pm-btn', onClick: function () { operate('save', true); } }, '确认替换'),
1532
+ React.createElement('button', { className: 'iris-pm-btn', onClick: function () { replacePair[1](false); } }, '取消')) : null) : null),
1533
+ notePair[0] ? React.createElement('div', { className: 'iris-comfy-result', role: 'status' }, notePair[0]) : null,
1534
+ React.createElement(ComfyCheckResult, { report: reportPair[0] }));
1347
1535
  }
1348
1536
 
1349
1537
  function ProviderManager() {
@@ -1355,7 +1543,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1355
1543
  var addingPair = React.useState(false);
1356
1544
  var adding = addingPair[0];
1357
1545
  var setAdding = addingPair[1];
1358
- var newForm = React.useState({ name: '', baseUrl: '', mediaBaseUrl: '', apiKey: '', type: 'openai', mediaProtocol: 'auto' });
1546
+ var newForm = React.useState({ name: '', baseUrl: '', mediaBaseUrl: '', apiKey: '', auth: 'bearer', type: 'openai', mediaProtocol: 'auto' });
1547
+ var savingRef = React.useRef(false), savingPair = React.useState(false), unknownPair = React.useState(false);
1359
1548
  var hostConnections = React.useState([]);
1360
1549
  var hostChoice = React.useState('');
1361
1550
  var setupPair = React.useState(false);
@@ -1379,23 +1568,25 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1379
1568
  return function () { clearInterval(timer); };
1380
1569
  }, []);
1381
1570
  function addProvider() {
1571
+ if (savingRef.current || unknownPair[0]) return;
1382
1572
  var f = newForm[0];
1573
+ var payload;
1383
1574
  if (hostChoice[0]) {
1384
1575
  var connection = hostConnections[0].find(function (item) { return JSON.stringify([item.settingsNs, item.provider]) === hostChoice[0]; });
1385
1576
  if (!connection || !connection.available) { setNote('请重新选择可用的 DSH 连接'); return; }
1386
- postAction('providers_upsert', { name: f.name || connection.name, type: 'openai', enabled: true,
1387
- mediaProtocol: f.mediaProtocol, dshProvider: { provider: connection.provider, settingsNs: connection.settingsNs } })
1388
- .then(function (r) {
1389
- if (r.ok) { setAdding(false); openPair[1](r.d.providerId); setNote('已引用 DSH 连接,请在下方确认模型用途,即可启用自动池'); refresh(); }
1390
- else setNote(r.d.error || '添加失败');
1391
- });
1392
- return;
1577
+ payload = { name: f.name || connection.name, type: 'openai', enabled: true,
1578
+ mediaProtocol: f.mediaProtocol, dshProvider: { provider: connection.provider, settingsNs: connection.settingsNs } };
1579
+ } else {
1580
+ if (!f.baseUrl.trim() || (f.auth !== 'none' && !f.apiKey.trim())) { setNote('请填写 Base URL;Bearer 认证还需要 API Key'); return; }
1581
+ payload = { ...f, ...(f.auth === 'none' ? { apiKey: '' } : {}) };
1393
1582
  }
1394
- if (!f.baseUrl || !f.apiKey) { setNote('baseUrl 和 apiKey 必填'); return; }
1395
- postAction('providers_upsert', f).then(function (r) {
1396
- if (r.ok) { setAdding(false); openPair[1](r.d.providerId); setNote('已连接,请在下方确认模型用途'); refresh(); }
1583
+ savingRef.current = true; savingPair[1](true);
1584
+ var controller = new AbortController(), timer = setTimeout(function () { controller.abort(); }, 15000);
1585
+ postAction('providers_upsert', payload, { signal: controller.signal }).then(function (r) {
1586
+ if (r.ok) { setAdding(false); openPair[1](r.d.providerId); setNote(f.mediaProtocol === 'comfyui' ? '连接已保存,请导入 API 工作流;尚未生成图片' : '已连接,请在下方确认模型用途'); refresh(); }
1397
1587
  else setNote(r.d.error || '添加失败');
1398
- });
1588
+ }).catch(function () { unknownPair[1](true); setNote('保存结果未知,请关闭添加表单并核对供应商列表;未自动重试。'); })
1589
+ .finally(function () { clearTimeout(timer); savingRef.current = false; savingPair[1](false); });
1399
1590
  }
1400
1591
  function toggle(p) {
1401
1592
  postAction('providers_upsert', { id: p.id, enabled: !p.enabled }).then(function (r) {
@@ -1426,19 +1617,26 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1426
1617
  var body = null;
1427
1618
  if (open) {
1428
1619
  body = React.createElement('div', { className: 'iris-pm-body', id: 'iris-provider-' + p.id },
1429
- React.createElement('div', { className: 'iris-pm-note' }, 'Key ' + (p.apiKeyHint || '未配置') + ' · ' + (p.type || '') + ' · ' + (p.mediaProtocol || '')),
1620
+ React.createElement('div', { className: 'iris-pm-note' }, (p.auth === 'none' ? '无需认证' : 'Key ' + (p.apiKeyHint || '未配置')) + ' · ' + (p.type || '') + ' · ' + (p.mediaProtocol || '')),
1430
1621
  p.dshProvider ? React.createElement('div', { className: 'iris-pm-note' }, '引用 DSH:' + p.dshProvider.provider
1431
1622
  + ' · 地址和密钥由宿主管理;' + (p.connection && p.connection.available ? '连接可用' : ((p.connection && p.connection.reason) || '连接不可用'))) : null,
1432
1623
  p.protocolInferred ? React.createElement('div', { className: 'iris-pm-note' }, '协议为推断值,请确认') : null,
1433
- React.createElement(ProviderEndpointEditor, { provider: p, onNote: setNote, onDone: refresh }),
1434
- React.createElement(VisionInputEditor, { provider: p, onNote: setNote, onDone: refresh }),
1624
+ p.mediaProtocol === 'comfyui' ? React.createElement('details', {}, React.createElement('summary', {}, '连接设置'),
1625
+ React.createElement('div', { className: 'iris-pm-note' }, '服务地址:' + (p.mediaBaseUrl || p.baseUrl)),
1626
+ !p.dshProvider ? React.createElement(ProviderEndpointEditor, { provider: p, onNote: setNote, onDone: refresh }) : null,
1627
+ !p.dshProvider ? React.createElement(ProviderAuthenticationEditor, { provider: p, onDone: refresh }) : null)
1628
+ : React.createElement(React.Fragment, {}, !p.dshProvider ? React.createElement(ProviderEndpointEditor, { provider: p, onNote: setNote, onDone: refresh }) : null,
1629
+ !p.dshProvider ? React.createElement(ProviderAuthenticationEditor, { provider: p, onDone: refresh }) : null,
1630
+ React.createElement(VisionInputEditor, { provider: p, onNote: setNote, onDone: refresh })),
1435
1631
  React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '媒体协议'), React.createElement('select', { value: p.protocolInferred ? 'auto' : (p.mediaProtocol || 'auto'), onChange: function (e) { setProtocol(p, e.target.value); } },
1436
1632
  React.createElement('option', { value: 'auto' }, '自动'),
1437
- p.mediaProtocol && !['dashscope', 'openai-images', 'openai-chat-images', 'openai-responses-images', 'auto'].includes(p.mediaProtocol) ? React.createElement('option', { value: p.mediaProtocol }, p.mediaProtocol + '(未支持)') : null,
1633
+ p.mediaProtocol && !['dashscope', 'openai-images', 'openai-chat-images', 'openai-responses-images', 'comfyui', 'auto'].includes(p.mediaProtocol) ? React.createElement('option', { value: p.mediaProtocol }, p.mediaProtocol + '(未支持)') : null,
1438
1634
  React.createElement('option', { value: 'dashscope' }, 'DashScope(仅阿里云官方 HTTPS)'),
1439
1635
  React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'),
1440
1636
  React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
1441
- React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图'))),
1637
+ React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图'),
1638
+ !p.dshProvider ? React.createElement('option', { value: 'comfyui' }, 'ComfyUI · API 工作流') : null)),
1639
+ p.mediaProtocol === 'comfyui' ? React.createElement(ComfyWorkflowEditor, { key: p.id, provider: p, setupSuggested: setupPair[0], onDone: refresh }) : null,
1442
1640
  React.createElement('div', { className: 'iris-pm-note' }, '能力: ' + ((p.capabilities || []).join(' / ') || '无')),
1443
1641
  React.createElement(ProviderDirectory, { provider: p, onDone: refresh }),
1444
1642
  React.createElement(ModelPool, { provider: p, setupSuggested: setupPair[0], onNote: setNote, onDone: refresh, testModel: testModel, removeModel: removeModel }),
@@ -1455,9 +1653,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1455
1653
  });
1456
1654
  var addBlock = null;
1457
1655
  if (adding) {
1458
- addBlock = React.createElement('div', { className: 'iris-pm-card' },
1656
+ addBlock = React.createElement('fieldset', { className: 'iris-pm-card' + (newForm[0].mediaProtocol === 'comfyui' ? ' iris-comfy-panel' : ''), disabled: savingPair[0] || unknownPair[0] },
1657
+ !hostChoice[0] ? React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '连接预设'),
1658
+ React.createElement('select', { value: newForm[0].mediaProtocol === 'comfyui' ? 'comfyui' : 'custom', onChange: function (e) {
1659
+ newForm[1](e.target.value === 'comfyui' ? { name: 'ComfyUI', baseUrl: 'http://127.0.0.1:8188', mediaBaseUrl: '', apiKey: '', type: 'openai', auth: 'none', mediaProtocol: 'comfyui' }
1660
+ : { name: '', baseUrl: '', mediaBaseUrl: '', apiKey: '', type: 'openai', auth: 'bearer', mediaProtocol: 'auto' });
1661
+ } }, React.createElement('option', { value: 'custom' }, '自定义供应商'), React.createElement('option', { value: 'comfyui' }, 'ComfyUI · 已有服务'))) : null,
1459
1662
  React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '连接来源'),
1460
- React.createElement('select', { value: hostChoice[0], onChange: function (e) { hostChoice[1](e.target.value); } },
1663
+ React.createElement('select', { value: hostChoice[0], onChange: function (e) {
1664
+ hostChoice[1](e.target.value);
1665
+ if (e.target.value && newForm[0].mediaProtocol === 'comfyui') newForm[1]({ name: '', baseUrl: '', mediaBaseUrl: '', apiKey: '', auth: 'bearer', type: 'openai', mediaProtocol: 'auto' });
1666
+ } },
1461
1667
  React.createElement('option', { value: '' }, '手动填写供应商'),
1462
1668
  hostConnections[0].map(function (connection) { return React.createElement('option', {
1463
1669
  key: JSON.stringify([connection.settingsNs, connection.provider]), value: JSON.stringify([connection.settingsNs, connection.provider]), disabled: !connection.available
@@ -1466,26 +1672,82 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1466
1672
  React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '名称'), React.createElement('input', { value: newForm[0].name, placeholder: '如 阿里云百炼', onChange: function (e) { newForm[1]({ ...newForm[0], name: e.target.value }); } })),
1467
1673
  !hostChoice[0] ? React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, 'Base URL *'), React.createElement('input', { value: newForm[0].baseUrl, placeholder: 'https://dashscope.aliyuncs.com/compatible-mode/v1', onChange: function (e) { newForm[1]({ ...newForm[0], baseUrl: e.target.value }); } })) : null,
1468
1674
  !hostChoice[0] ? React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '媒体 Base URL(可选)'), React.createElement('input', { value: newForm[0].mediaBaseUrl, placeholder: 'Workspace 媒体端点;留空沿用 Base URL', onChange: function (e) { newForm[1]({ ...newForm[0], mediaBaseUrl: e.target.value }); } })) : null,
1469
- !hostChoice[0] ? React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, 'API Key *'), React.createElement('input', { type: 'password', value: newForm[0].apiKey, placeholder: 'sk-...', onChange: function (e) { newForm[1]({ ...newForm[0], apiKey: e.target.value }); } })) : null,
1675
+ !hostChoice[0] ? React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '认证方式'), React.createElement('select', { value: newForm[0].auth, onChange: function (e) { newForm[1]({ ...newForm[0], auth: e.target.value }); } },
1676
+ React.createElement('option', { value: 'bearer' }, 'Bearer · API Key'), React.createElement('option', { value: 'none' }, '无需认证(本地或内网服务)'))) : null,
1677
+ !hostChoice[0] && newForm[0].auth !== 'none' ? React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, 'API Key *'), React.createElement('input', { type: 'password', value: newForm[0].apiKey, placeholder: 'sk-...', onChange: function (e) { newForm[1]({ ...newForm[0], apiKey: e.target.value }); } })) : null,
1470
1678
  React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '媒体协议'), React.createElement('select', { value: newForm[0].mediaProtocol, onChange: function (e) { newForm[1]({ ...newForm[0], mediaProtocol: e.target.value }); } },
1471
1679
  React.createElement('option', { value: 'auto' }, hostChoice[0] ? '请选择该服务支持的媒体协议' : '自动(按媒体 Base URL 安全判断)'),
1472
1680
  React.createElement('option', { value: 'dashscope' }, 'DashScope(阿里云官方)'),
1473
1681
  React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'),
1474
1682
  React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
1475
- React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图'))),
1683
+ React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图'),
1684
+ !hostChoice[0] ? React.createElement('option', { value: 'comfyui' }, 'ComfyUI · API 工作流') : null)),
1685
+ newForm[0].mediaProtocol === 'comfyui' ? React.createElement('p', { className: 'iris-pm-note' }, '连接已有 ComfyUI 服务。先保存连接,再导入 Export (API) 工作流;Iris 不安装服务或模型。') : null,
1476
1686
  React.createElement('div', { className: 'iris-wb-row' },
1477
- React.createElement('button', { className: 'iris-act-run', onClick: addProvider }, '保存'),
1687
+ React.createElement('button', { className: 'iris-act-run', onClick: addProvider }, savingPair[0] ? '保存中…' : '保存'),
1478
1688
  React.createElement('button', { className: 'iris-pm-btn', onClick: function () { setAdding(false); } }, '取消')));
1479
1689
  }
1480
1690
  return React.createElement('div', { className: 'iris-pm' },
1481
1691
  React.createElement('div', { className: 'iris-pm-head' },
1482
1692
  React.createElement('span', { className: 'iris-wb-muted' }, (list ? list.length : 0) + ' 个供应商'),
1483
- React.createElement('button', { className: 'iris-pm-add', onClick: function () { if (!adding) { hostChoice[1](''); loadHostConnections(); } setAdding(!adding); } }, adding ? '收起' : '+ 添加供应商')),
1693
+ React.createElement('button', { className: 'iris-pm-add', disabled: savingPair[0], onClick: function () { if (!adding) { hostChoice[1](''); loadHostConnections(); } unknownPair[1](false); setAdding(!adding); } }, adding ? '收起' : '+ 添加供应商')),
1484
1694
  addBlock,
1485
1695
  cards.length ? cards : React.createElement('div', { className: 'iris-wb-empty' }, '暂无供应商 — 点右上角「+ 添加供应商」'),
1486
1696
  note ? React.createElement('div', { className: 'iris-pm-act' }, note) : null);
1487
1697
  }
1488
1698
 
1699
+ function comfyParameterLabel(parameter) {
1700
+ return parameter.label || ({ lora_name: 'LoRA', strength_model: '模型权重', strength_clip: 'CLIP 权重',
1701
+ ckpt_name: 'Checkpoint', seed: '种子', steps: '步数', sampler_name: '采样器', scheduler: '调度器', cfg: 'CFG' })[parameter.input]
1702
+ || (parameter.id === 'negative' && parameter.input === 'text' ? '负面提示词' : parameter.id);
1703
+ }
1704
+ function ComfyParameters(props) {
1705
+ var option = props.option, parameters = option.comfyWorkflow.parameters || [];
1706
+ var reportPair = React.useState(null), choicesPair = React.useState([]), notePair = React.useState(''), busyPair = React.useState(false);
1707
+ var active = React.useRef(null), sequence = React.useRef(0);
1708
+ React.useEffect(function () { return function () { sequence.current++; if (active.current) active.current.abort(); }; }, []);
1709
+ function change(id, value) {
1710
+ sequence.current++; if (active.current) active.current.abort(); active.current = null; busyPair[1](false);
1711
+ reportPair[1](null); notePair[1]('参数已更改,提交前将重新预检。'); props.onChange({ ...props.values, [id]: value });
1712
+ }
1713
+ async function check() {
1714
+ if (active.current || props.disabled) return;
1715
+ var controller = new AbortController(), ticket = ++sequence.current; active.current = controller; busyPair[1](true); notePair[1]('');
1716
+ var timer = setTimeout(function () { controller.abort(); }, 20000);
1717
+ try {
1718
+ var result = await postAction('providers_check_comfy_workflow', { id: option.providerId, model_id: option.id,
1719
+ ...(Object.keys(props.values).length ? { workflow_parameters: props.values } : {}) }, { signal: controller.signal });
1720
+ if (ticket !== sequence.current) return;
1721
+ if (!result.ok || result.d.ok !== true) { reportPair[1](null); notePair[1](result.d.error || '检查失败'); return; }
1722
+ reportPair[1](result.d.report); choicesPair[1](result.d.report.parameters || []);
1723
+ } catch (_) { if (ticket === sequence.current) { reportPair[1](null); notePair[1]('检查未完成;未发起生成。可核对服务后重试。'); } }
1724
+ finally { clearTimeout(timer); if (ticket === sequence.current) { active.current = null; busyPair[1](false); } }
1725
+ }
1726
+ return React.createElement('section', { className: 'iris-comfy-panel', 'aria-label': 'ComfyUI 画图参数' },
1727
+ React.createElement('div', { className: 'iris-comfy-head' }, React.createElement('strong', {}, 'ComfyUI · ' + option.id), React.createElement('span', { className: 'iris-comfy-tag' }, '单张文生图')),
1728
+ React.createElement('div', { className: 'iris-pm-note' }, '沿用工作流默认值,只调整已公开的参数。LoRA 候选可通过只读检查获取。'),
1729
+ parameters.length ? React.createElement('details', {}, React.createElement('summary', {}, '工作流参数 · ' + parameters.length),
1730
+ React.createElement('fieldset', { className: 'iris-comfy-grid', disabled: props.disabled, style: { border: 0, padding: 0, margin: '8px 0 0', minWidth: 0 } }, parameters.map(function (parameter) {
1731
+ var value = Object.hasOwn(props.values, parameter.id) ? props.values[parameter.id] : parameter.default;
1732
+ var checked = choicesPair[0].find(function (item) { return item.id === parameter.id; });
1733
+ var choices = checked && checked.availableChoices || parameter.choices, input;
1734
+ if (choices) input = React.createElement('select', { value: JSON.stringify(value), onChange: function (event) { change(parameter.id, JSON.parse(event.target.value)); } },
1735
+ !choices.includes(value) ? React.createElement('option', { value: JSON.stringify(value) }, String(value) + '(当前未列出)') : null,
1736
+ choices.map(function (choice) { return React.createElement('option', { key: JSON.stringify(choice), value: JSON.stringify(choice) }, String(choice)); }));
1737
+ else if (parameter.type === 'boolean') input = React.createElement('input', { type: 'checkbox', checked: value, onChange: function (event) { change(parameter.id, event.target.checked); } });
1738
+ else input = React.createElement(parameter.type === 'string' && parameter.input === 'text' ? 'textarea' : 'input', {
1739
+ type: parameter.type === 'string' ? 'text' : 'number', value: value, min: parameter.min, max: parameter.max, step: parameter.type === 'integer' ? 1 : 'any',
1740
+ onChange: function (event) { var text = event.target.value; change(parameter.id, parameter.type === 'string' || text === '' ? text : Number(text)); }
1741
+ });
1742
+ return React.createElement('label', { key: parameter.id }, comfyParameterLabel(parameter), input,
1743
+ checked && checked.choicesTruncated ? React.createElement('span', { className: 'iris-pm-note' }, '选项仅列前 256 项,可手填名称:',
1744
+ React.createElement('input', { type: 'text', value: value, onChange: function (event) { change(parameter.id, event.target.value); } })) : null);
1745
+ }))) : React.createElement('div', { className: 'iris-pm-note' }, '此模板未公开额外参数。'),
1746
+ React.createElement('div', {}, React.createElement('button', { className: 'iris-pm-btn', disabled: props.disabled || busyPair[0], onClick: check }, busyPair[0] ? '检查中…' : '只读检查并读取选项')),
1747
+ notePair[0] ? React.createElement('div', { className: 'iris-comfy-result', role: 'status' }, notePair[0]) : null,
1748
+ React.createElement(ComfyCheckResult, { report: reportPair[0] }));
1749
+ }
1750
+
1489
1751
  /* ---- 文件字段(阶段 10):让用户"看见并选文件",统一产出一个宿主路径 ---- */
1490
1752
  function FileField(props) {
1491
1753
  var value = props.value || '';
@@ -1546,7 +1808,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1546
1808
  manual ? React.createElement('input', { className: 'iris-pm-madd', placeholder: '高级:粘贴 DSH 宿主上的绝对路径,如 /home/user/pic.png', value: value, onChange: function (e) { onChange(e.target.value); } }) : null,
1547
1809
  menu === 'att' ? React.createElement('div', { className: 'iris-ff-list' },
1548
1810
  (atts && atts.length) ? atts.map(function (a) {
1549
- return React.createElement('div', { key: a.attachmentId, className: 'iris-ff-item', onClick: function () { pickAttachment(a); } },
1811
+ return React.createElement('button', { type: 'button', key: a.attachmentId, className: 'iris-ff-item', onClick: function () { pickAttachment(a); } },
1550
1812
  React.createElement('span', { className: 'iris-ff-src' }, a.source === 'iris' ? '🫧' : '🖼'),
1551
1813
  React.createElement('span', {}, a.name || a.attachmentId.slice(0, 18)));
1552
1814
  }) : React.createElement('div', { className: 'iris-wb-empty' }, '无可选附件(会话无图或 iris 未生成过)')) : null);
@@ -1570,6 +1832,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1570
1832
  var modelPair = React.useState(null); // { assigned, options:[{id,providerId}], tested }
1571
1833
  var modelInfo = modelPair[0];
1572
1834
  var setModelInfo = modelPair[1];
1835
+ var submitting = React.useRef(false);
1836
+ var selectedOption = modelInfo && modelInfo.options.find(function (option) { return option.ref === vals.model; });
1837
+ var selectedComfy = action === 'image' && selectedOption && selectedOption.comfyWorkflow;
1573
1838
  var irisState = useIrisState(5000);
1574
1839
  function setVal(key, value) { var nv = {}; Object.keys(vals).forEach(function (k) { nv[k] = vals[k]; }); nv[key] = value; setVals(nv); }
1575
1840
  function loadModels(event) {
@@ -1595,19 +1860,30 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1595
1860
  return function () { window.removeEventListener('iris-function-configuration-changed', loadModels); };
1596
1861
  }, [open]);
1597
1862
  function submit() {
1598
- if (run === 'running') return;
1863
+ if (submitting.current || run === 'running' || run && run.unknown) return;
1864
+ var payload = vals;
1865
+ if (selectedComfy) {
1866
+ var values = vals.workflow_parameters || {}, declared = selectedComfy.parameters || [];
1867
+ if (Object.keys(values).some(function (id) { var field = declared.find(function (item) { return item.id === id; }), value = values[id];
1868
+ return !field || (field.type === 'integer' ? !Number.isSafeInteger(value) : field.type === 'number' ? typeof value !== 'number' || !Number.isFinite(value) : typeof value !== field.type)
1869
+ || field.min !== undefined && value < field.min || field.max !== undefined && value > field.max || field.choices && !field.choices.includes(value);
1870
+ })) { setRun({ ok: false, text: '请检查工作流参数的类型、范围和选项;尚未发起生成。' }); return; }
1871
+ payload = { ...vals, workflow_sha: selectedComfy.sha256 };
1872
+ }
1873
+ submitting.current = true;
1599
1874
  setRun('running');
1600
1875
  fetch('/iris/api/actions/' + action, {
1601
1876
  method: 'POST',
1602
1877
  headers: { 'Content-Type': 'application/json' },
1603
- body: JSON.stringify(vals)
1878
+ body: JSON.stringify(payload)
1604
1879
  })
1605
1880
  .then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
1606
1881
  .then(function (res) {
1607
1882
  setRun(res.ok ? { ok: true, text: res.d.text || '', img: res.d.imageUrl || res.d.imageDataUrl || null, audio: res.d.audioUrl || null } : { ok: false, text: res.d.error || '执行失败' });
1608
1883
  if (res.ok && res.d && res.d.storage === 'core') window.dispatchEvent(new CustomEvent('iris-core-artifacts-changed'));
1609
1884
  })
1610
- .catch(function () { setRun({ ok: false, text: '网络错误' }); });
1885
+ .catch(function () { setRun({ ok: false, unknown: !!selectedComfy, text: selectedComfy ? '提交结果未知,请到任务列表核对;未自动重试。' : '网络错误' }); })
1886
+ .finally(function () { submitting.current = false; });
1611
1887
  }
1612
1888
  var cardHealth = capability ? capabilityHealth(irisState, capability) : { status: 'verified' };
1613
1889
  // Host references can be ready without raw endpoint/key fields in the state snapshot.
@@ -1620,7 +1896,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1620
1896
  var inputs = fields.map(function (f) {
1621
1897
  var node;
1622
1898
  if (f.key === 'model' && capability) {
1623
- node = React.createElement('select', { value: vals.model || '', onChange: function (e) { setVal('model', e.target.value); } },
1899
+ node = React.createElement('select', { value: vals.model || '', disabled: run === 'running', onChange: function (e) {
1900
+ var next = { ...vals, model: e.target.value }; delete next.workflow_parameters; delete next.workflow_sha; setVals(next);
1901
+ } },
1624
1902
  React.createElement('option', { value: '' }, modelInfo && modelInfo.mode === 'auto' ? '按功能池顺序自动选择' : '按所选模型顺序'),
1625
1903
  ((modelInfo && modelInfo.options) || []).map(function (option) {
1626
1904
  return React.createElement('option', { key: option.ref, value: option.ref, disabled: option.unavailable }, option.label + (option.unavailable ? ' · 当前不可用' : ''));
@@ -1678,16 +1956,18 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1678
1956
  body = React.createElement('div', { className: 'iris-act-body' },
1679
1957
  modelBar,
1680
1958
  inputs,
1681
- React.createElement('button', { className: 'iris-act-run', disabled: run === 'running', onClick: submit },
1682
- run === 'running' ? '执行中…' : (props.submitLabel || '执行')),
1959
+ selectedComfy ? React.createElement(ComfyParameters, { key: selectedOption.ref + '@' + selectedComfy.sha256, option: selectedOption,
1960
+ values: vals.workflow_parameters || {}, disabled: run === 'running', onChange: function (values) { setVal('workflow_parameters', values); } }) : null,
1961
+ React.createElement('button', { className: 'iris-act-run', disabled: run === 'running' || !!(run && run.unknown), onClick: submit },
1962
+ run === 'running' ? '执行中…' : (props.submitLabel || (selectedComfy ? '生成图片' : '执行'))),
1683
1963
  result);
1684
1964
  }
1685
1965
  var headStatus = capability ? cardHealth.status : 'verified';
1686
1966
  var headTitle = capability ? healthText(cardHealth) : '本地功能';
1687
1967
  return React.createElement('div', {
1688
- className: 'iris-act-card' + (open ? ' open' : '') + (capability ? ' health-' + headStatus : ''), 'data-iris-action': action
1968
+ className: 'iris-act-card' + (open ? ' open' : '') + (selectedComfy ? ' iris-comfy-card' : '') + (capability ? ' health-' + headStatus : ''), 'data-iris-action': action
1689
1969
  },
1690
- React.createElement('div', { className: 'iris-act-head', onClick: function () { setOpen(!open); } },
1970
+ React.createElement('button', { type: 'button', className: 'iris-act-head', 'aria-expanded': open, onClick: function () { setOpen(!open); } },
1691
1971
  React.createElement('span', { className: 'iris-act-title' }, title),
1692
1972
  capability ? React.createElement('span', {
1693
1973
  className: 'iris-pm-dot ' + healthClass(cardHealth),
@@ -1987,8 +2267,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1987
2267
  if (!rows.length) return null;
1988
2268
  var isCollapsed = !!collapsed[name];
1989
2269
  return React.createElement('div', { key: name, className: 'iris-hist-group' },
1990
- React.createElement('div', {
1991
- className: 'iris-hist-head',
2270
+ React.createElement('button', {
2271
+ type: 'button', className: 'iris-hist-head', 'aria-expanded': !isCollapsed,
1992
2272
  onClick: function () { var n = {}; Object.keys(collapsed).forEach(function (k) { n[k] = collapsed[k]; }); n[name] = !isCollapsed; setCollapsed(n); }
1993
2273
  }, React.createElement('span', {}, isCollapsed ? '▶' : '▼'), name + ' · ' + rows.length),
1994
2274
  isCollapsed ? null : React.createElement('div', { className: 'iris-wb-box' },
@@ -2070,11 +2350,12 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2070
2350
  var pair = React.useState([]);
2071
2351
  var setItems = pair[1];
2072
2352
  React.useEffect(function () {
2073
- var alive = true;
2353
+ var alive = true, sequence = 0;
2074
2354
  function load() {
2355
+ var current = ++sequence;
2075
2356
  fetch('/iris/api/core/snapshot?limit=' + Math.max(1, Math.min(200, Number(limit) || 6)))
2076
2357
  .then(function (res) { return res.ok ? res.json() : null; })
2077
- .then(function (data) { if (alive && data) setItems(coreGalleryItems(data, limit)); })
2358
+ .then(function (data) { if (alive && current === sequence && data) setItems(coreGalleryItems(data, limit)); })
2078
2359
  .catch(function () { /* Core 不可用不影响旧作品 */ });
2079
2360
  }
2080
2361
  function changed() { load(); }
@@ -2395,8 +2676,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2395
2676
  .then(function (value) {
2396
2677
  if (request.current !== controller || controller.signal.aborted) return;
2397
2678
  setData(value);
2398
- setNote(value.coreError ? 'Core 作品暂时不可用,当前计数仅含可读取作品。'
2399
- : value.droppedCore ? '已跳过 ' + value.droppedCore + ' 条不可读取的 Core 作品。' : '');
2679
+ var notes = [];
2680
+ if (value.coreError) notes.push('Core 作品暂时不可用,当前计数仅含可读取作品。');
2681
+ if (value.droppedCore) notes.push('已跳过 ' + value.droppedCore + ' 条损坏、缺失或不支持格式的 Core 作品。');
2682
+ if (value.legacyCoreSkipped) notes.push(value.legacyCoreSkipped + ' 条早期 Core 作品尚未编入当前目录;可按 ID 读取,或显式重建索引后显示。');
2683
+ setNote(notes.join(' '));
2400
2684
  })
2401
2685
  .catch(function (error) { if (request.current === controller && !controller.signal.aborted) setNote(error.message); })
2402
2686
  .finally(function () { if (request.current === controller) { request.current = null; setBusy(false); } });
@@ -2587,32 +2871,46 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2587
2871
  var EMPTY_CORE_USER = Object.freeze({ rows: [], available: false, degraded: false, droppedTasks: 0 });
2588
2872
 
2589
2873
  function useCoreUserTasks() {
2590
- var pair = React.useState(EMPTY_CORE_USER);
2874
+ var store = useCoreUserTasks.shared;
2875
+ if (!store) store = useCoreUserTasks.shared = { data: EMPTY_CORE_USER, listeners: [], sequence: 0, refresh: null };
2876
+ var pair = React.useState(store.data);
2591
2877
  var setData = pair[1];
2592
2878
  React.useEffect(function () {
2593
- var alive = true;
2879
+ function listener(data) { setData(data); }
2880
+ store.listeners.push(listener);
2881
+ setData(store.data);
2594
2882
  function load() {
2883
+ var current = ++store.sequence;
2595
2884
  fetch('/iris/api/core/snapshot?limit=200')
2596
2885
  .then(function (res) { return res.ok ? res.json() : null; })
2597
2886
  .then(function (data) {
2598
- if (!alive || !data || !Array.isArray(data.userTasks)) return;
2599
- setData({
2887
+ if (!store.listeners.length || current !== store.sequence || !data || !Array.isArray(data.userTasks)) return;
2888
+ store.data = {
2600
2889
  rows: data.userTasks,
2890
+ activity: data.activity,
2601
2891
  available: !!data.available,
2602
2892
  degraded: !!data.degraded,
2603
2893
  droppedTasks: Number(data.droppedTasks || 0)
2604
- });
2894
+ };
2895
+ store.listeners.forEach(function (notify) { notify(store.data); });
2605
2896
  })
2606
2897
  .catch(function () { /* Core 不可用不阻塞 legacy 任务区 */ });
2607
2898
  }
2608
- function changed() { load(); }
2609
- load();
2610
- window.addEventListener('iris-core-artifacts-changed', changed);
2611
- window.addEventListener('iris-core-refresh-tick', changed);
2899
+ if (store.listeners.length === 1) {
2900
+ store.refresh = load;
2901
+ load();
2902
+ window.addEventListener('iris-core-artifacts-changed', load);
2903
+ window.addEventListener('iris-core-refresh-tick', load);
2904
+ }
2612
2905
  return function () {
2613
- alive = false;
2614
- window.removeEventListener('iris-core-artifacts-changed', changed);
2615
- window.removeEventListener('iris-core-refresh-tick', changed);
2906
+ var index = store.listeners.indexOf(listener);
2907
+ if (index >= 0) store.listeners.splice(index, 1);
2908
+ if (!store.listeners.length) {
2909
+ store.sequence++;
2910
+ window.removeEventListener('iris-core-artifacts-changed', store.refresh);
2911
+ window.removeEventListener('iris-core-refresh-tick', store.refresh);
2912
+ store.refresh = null; store.data = EMPTY_CORE_USER;
2913
+ }
2616
2914
  };
2617
2915
  }, []);
2618
2916
  return pair[0];
@@ -2736,11 +3034,16 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2736
3034
  if (!props.row || props.row.retryable !== true) return null;
2737
3035
  return React.createElement(CoreTaskManualButton, {
2738
3036
  id: props.row.id, action: 'retry',
2739
- idleLabel: '重试为新任务', busyLabel: '创建中…', failPrefix: '重试',
3037
+ idleLabel: props.row.capability === 'video' ? '重新创建文生视频' : '重试为新任务', busyLabel: '创建中…', failPrefix: '重试',
2740
3038
  title: '创建一个新任务重新生成,可能产生重复费用;生成指令需要重新输入',
2741
3039
  confirm: '将创建一个新任务并可能产生重复生成费用,确定继续?',
2742
3040
  createdPrefix: '已创建新任务:',
2743
3041
  buildBody: function () {
3042
+ var videoMode;
3043
+ if (props.row.capability === 'video') {
3044
+ videoMode = window.prompt('旧视频 Task 不保存模式、首帧和音频。本按钮仅创建新的文生视频;图生视频或数字人视频请取消,并通过视频工具或 CLI 重新提供首帧/音频。\n如明确选择文生视频,请输入 t2v:');
3045
+ if (videoMode === null || String(videoMode).trim() !== 't2v') return null;
3046
+ }
2744
3047
  var isTts = props.row.capability === 'tts';
2745
3048
  var isTranscribe = props.row.capability === 'transcribe';
2746
3049
  var draft = window.prompt(isTts
@@ -2758,7 +3061,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2758
3061
  }
2759
3062
  return isTts ? { text: draft, confirmBilling: true }
2760
3063
  : isTranscribe ? { audio_url: draft, confirmBilling: true }
2761
- : { prompt: draft, confirmBilling: true, ...(sourceArtifactId ? { source_artifact_id: String(sourceArtifactId).trim() } : {}) };
3064
+ : { prompt: draft, confirmBilling: true, ...(videoMode ? { video_mode: 't2v' } : {}),
3065
+ ...(sourceArtifactId ? { source_artifact_id: String(sourceArtifactId).trim() } : {}) };
2762
3066
  }
2763
3067
  });
2764
3068
  }
@@ -2885,17 +3189,20 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2885
3189
  var copyPair = React.useState('');
2886
3190
  var copyNote = copyPair[0];
2887
3191
  var setCopyNote = copyPair[1];
3192
+ var requestRef = React.useRef({ active: false, sequence: 0 });
2888
3193
  function load() {
2889
- if (busy) return;
3194
+ var request = requestRef.current;
3195
+ if (busy || !request.active) return;
3196
+ var current = ++request.sequence;
2890
3197
  setBusy(true);
2891
3198
  setError('');
2892
3199
  fetch('/iris/api/core/snapshot?limit=12')
2893
3200
  .then(function (res) {
2894
3201
  if (res.status === 404) return { available: false, backendReloadRequired: true, tasks: { total: 0, recent: [] }, artifacts: { total: 0, recent: [] } };
2895
3202
  return res.ok ? res.json() : Promise.reject(new Error('读取失败(HTTP ' + res.status + ')'));
2896
- }).then(setData)
2897
- .catch(function (caught) { setError('Core 运行事实读取失败:' + caught.message); })
2898
- .finally(function () { setBusy(false); });
3203
+ }).then(function (value) { if (request.active && current === request.sequence) setData(value); })
3204
+ .catch(function (caught) { if (request.active && current === request.sequence) setError('Core 运行事实读取失败:' + caught.message); })
3205
+ .finally(function () { if (request.active && current === request.sequence) setBusy(false); });
2899
3206
  }
2900
3207
  function copyCoreId(value, label) {
2901
3208
  setCopyNote('');
@@ -2909,11 +3216,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2909
3216
  .catch(function () { setCopyNote('复制失败;请长按上方 ID 复制。'); });
2910
3217
  }
2911
3218
  React.useEffect(function () {
3219
+ requestRef.current.active = true;
2912
3220
  function changed() { load(); }
2913
3221
  load();
2914
3222
  window.addEventListener('iris-core-artifacts-changed', changed);
2915
3223
  window.addEventListener('iris-core-refresh-tick', changed);
2916
3224
  return function () {
3225
+ requestRef.current.active = false;
3226
+ requestRef.current.sequence++;
2917
3227
  window.removeEventListener('iris-core-artifacts-changed', changed);
2918
3228
  window.removeEventListener('iris-core-refresh-tick', changed);
2919
3229
  };
@@ -3957,13 +4267,21 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
3957
4267
  function ProgressDock() {
3958
4268
  var state = useIrisState(5000);
3959
4269
  var running = (state && state.tasks && state.tasks.running) || [];
3960
- if (!running.length) return null;
4270
+ var coreUser = useCoreUserTasks();
4271
+ var coreRows = coreUser.activity && coreUser.activity.recent || [];
4272
+ if (!running.length && !coreRows.length) return null;
3961
4273
  return React.createElement('div', { className: 'iris-progress-dock' },
3962
4274
  running.map(function (t) {
3963
4275
  return React.createElement('div', { key: t.id, className: 'iris-progress-row' },
3964
4276
  React.createElement('span', {}, '🫧 ' + t.cap),
3965
4277
  React.createElement('span', {}, t.model || ''),
3966
4278
  React.createElement('span', {}, String(t.progress || '')));
4279
+ }),
4280
+ coreRows.map(function (row) {
4281
+ return React.createElement('div', { key: 'core:' + row.id, className: 'iris-progress-row' },
4282
+ React.createElement('span', {}, 'Core · ' + row.capability),
4283
+ React.createElement('span', {}, row.model),
4284
+ React.createElement('span', {}, row.label));
3967
4285
  }));
3968
4286
  }
3969
4287
 
@@ -3992,6 +4310,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
3992
4310
 
3993
4311
  function FloatingBubble() {
3994
4312
  var state = useIrisState(5000);
4313
+ var coreUser = useCoreUserTasks();
4314
+ var activity = coreUser.activity;
3995
4315
  var providers = (state && state.providers) || [];
3996
4316
  var fallbackConfigured = providers.some(function (p) { return p.enabled && p.apiKeyHint; });
3997
4317
  var overallState = state && state.health && HEALTH_META[state.health.overall]
@@ -4098,9 +4418,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
4098
4418
  style = { right: DEFAULT_X + 'px', bottom: DEFAULT_Y + 'px' };
4099
4419
  }
4100
4420
 
4101
- var badgeCount = running.length + attention.length;
4102
- var badge = badgeCount
4103
- ? React.createElement('span', { className: 'iris-bubble-badge', title: attention.length ? attention.length + ' 个任务需要处理' : '' }, badgeCount)
4421
+ var coreCount = activity ? activity.running + activity.paused + activity.attention : 0;
4422
+ var badgeCount = running.length + attention.length + coreCount;
4423
+ var badgeTitle = '旧任务:' + running.length + ' 运行中,' + attention.length + ' 待处理';
4424
+ if (activity) badgeTitle += ';Core:' + activity.running + ' 运行中,' + activity.paused + ' 观察暂停,' + activity.attention + ' 待处理'
4425
+ + (activity.truncated ? '(部分较早的 Core 记录尚未统计)' : '');
4426
+ var badge = badgeCount || activity && activity.truncated
4427
+ ? React.createElement('span', { className: 'iris-bubble-badge', title: badgeTitle, 'aria-label': badgeTitle },
4428
+ badgeCount ? String(badgeCount) + (activity && activity.truncated ? '+' : '') : '…')
4104
4429
  : null;
4105
4430
 
4106
4431
  var panel = null;
@@ -4118,6 +4443,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
4118
4443
 
4119
4444
  return React.createElement('div', {
4120
4445
  className: 'iris-bubble health-' + overallHealth + (dragRef.current ? ' dragging' : ''),
4446
+ role: 'button', tabIndex: 0, 'aria-label': 'Iris 快捷面板', 'aria-expanded': open,
4447
+ onKeyDown: function (event) {
4448
+ if (event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' ')) return;
4449
+ event.preventDefault(); setOpen(!open);
4450
+ },
4121
4451
  style: style,
4122
4452
  onPointerDown: onPointerDown,
4123
4453
  onPointerMove: onPointerMove,