@mokuyoaxis/dsh-iris 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dsh/skills/iris-compose-media/SKILL.md +10 -5
- package/.dsh/skills/iris-compose-media/references/workflows.md +7 -1
- package/.dsh/skills/iris-verify-ui/SKILL.md +2 -1
- package/CHANGELOG.md +69 -2
- package/README.en.md +67 -31
- package/README.md +67 -31
- package/bin/dsh-iris.js +222 -21
- package/docs/AGENT_QUICKSTART.md +10 -6
- package/docs/ARCHITECTURE.md +34 -19
- package/docs/ARTIFACT_LIBRARY.md +5 -5
- package/docs/ARTIFACT_MANIFEST.md +1 -1
- package/docs/CLI_MANAGEMENT.md +183 -0
- package/docs/COMPOSITE_VISION.md +35 -0
- package/docs/CORE_RUNTIME_CONTRACT.md +16 -7
- package/docs/DSH_CORE_MIGRATION.md +48 -19
- package/docs/DSH_RC2_ADAPTATION.md +4 -2
- package/docs/FAULT_INJECTION.md +21 -2
- package/docs/HEADLESS_CLI.md +15 -7
- package/docs/HOST_ADAPTER_CONTRACT.md +10 -10
- package/docs/HOST_DOCTOR.md +3 -3
- package/docs/MODEL_PORT_CONTRACT.md +21 -14
- package/docs/OCR_MODEL.md +39 -0
- package/docs/PROMPT_OPTIMIZER.md +77 -0
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +15 -5
- package/docs/PROVIDER_HEALTH.md +22 -5
- package/docs/PROVIDER_RUNTIME_HARNESS.md +1 -1
- package/docs/PROVIDER_SUBMISSION_CONTRACT.md +2 -2
- package/docs/ROADMAP.md +37 -19
- package/docs/SECURITY.md +5 -5
- package/docs/TASK_SEMANTICS.md +2 -2
- package/docs/VISION_CLI.md +132 -0
- package/docs/VISION_MODEL.md +51 -0
- package/docs/WORKBENCH_ARTIFACTS.md +67 -0
- package/docs/file-access-across-environments.md +8 -5
- package/docs/releases/0.2.1.md +56 -0
- package/docs/releases/0.2.2.md +31 -0
- package/lib/actions.js +199 -286
- package/lib/adapters.js +216 -41
- package/lib/api.js +49 -7
- package/lib/capability.js +23 -16
- package/lib/chromium-browser.js +117 -0
- package/lib/client.js +933 -117
- package/lib/command-service.js +121 -7
- package/lib/composite-vision-routing.js +42 -0
- package/lib/config.js +102 -14
- package/lib/core-artifact-store.js +45 -13
- package/lib/core-lease-recovery.js +64 -92
- package/lib/core-maintenance.js +213 -0
- package/lib/core-runtime.js +45 -47
- package/lib/core-tasks.js +12 -5
- package/lib/core-user-projection.js +1 -0
- package/lib/core-writer-lease.js +134 -0
- package/lib/dsh-core-adapter.js +63 -9
- package/lib/dsh-text-model-adapter.js +222 -0
- package/lib/dsh-vision-model-adapter.js +75 -0
- package/lib/generation-input.js +36 -5
- package/lib/headless-vision.js +181 -0
- package/lib/host-runtime.js +1 -0
- package/lib/http-vision-model-adapter.js +187 -0
- package/lib/image-edit-input.js +34 -0
- package/lib/index.js +205 -175
- package/lib/locate.js +86 -28
- package/lib/media-probe.js +2 -1
- package/lib/model-call-runtime.js +45 -0
- package/lib/model-invoker.js +4 -2
- package/lib/model-port-contract.js +13 -5
- package/lib/models.js +29 -42
- package/lib/ocr-model-routing.js +26 -0
- package/lib/ocr.js +110 -64
- package/lib/prompt-optimizer-config.js +21 -10
- package/lib/prompt-optimizer-core.js +247 -0
- package/lib/prompt-optimizer.js +37 -193
- package/lib/provider-adapters.js +87 -10
- package/lib/provider-catalog.js +19 -7
- package/lib/provider-config-service.js +235 -0
- package/lib/provider-contract.js +3 -2
- package/lib/provider-health.js +80 -1
- package/lib/provider-protocol.js +84 -0
- package/lib/provider-task-runner.js +63 -19
- package/lib/summarize.js +31 -20
- package/lib/summary-input.js +52 -0
- package/lib/task-wait.js +40 -0
- package/lib/video-input.js +30 -0
- package/lib/vision-core.js +42 -0
- package/lib/vision-image-input.js +83 -0
- package/lib/vision-input.js +31 -0
- package/lib/vision-model-routing.js +180 -0
- package/lib/vision.js +3 -2
- package/lib/workbench-artifacts.js +52 -0
- package/lib/workbench-management.js +151 -0
- package/lib/workbench-zip.js +39 -0
- package/package.json +1 -1
- package/user_guide.md +74 -30
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.
|
|
24
|
+
var IRIS_CLIENT_VERSION = '0.2.2';
|
|
25
25
|
var HOST_CLIENT_PROTOCOL_VERSION = 0;
|
|
26
26
|
var reportedSeats = [];
|
|
27
27
|
|
|
@@ -232,7 +232,31 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
232
232
|
'.iris-gallery-file { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; font-weight: 600; }',
|
|
233
233
|
'.iris-gallery-meta { color: var(--dsw-alias-label-tertiary, #7a7a85); font-size: 10px; }',
|
|
234
234
|
'.iris-gallery-info { display: grid; gap: 4px; padding: 8px 9px 9px; }',
|
|
235
|
-
'.iris-gallery-actions { display: flex; align-items: center; gap: 8px; }',
|
|
235
|
+
'.iris-gallery-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }',
|
|
236
|
+
'.iris-gallery-card.selected { border-color: var(--dsw-alias-brand-primary, #7aa2f7); }',
|
|
237
|
+
'.iris-gallery-select { display: flex; align-items: center; gap: 6px; font-size: 11px; }',
|
|
238
|
+
'.iris-gallery-select input { width: 18px; height: 18px; margin: 0; accent-color: var(--dsw-alias-brand-primary, #7aa2f7); }',
|
|
239
|
+
'.iris-gallery-bulk { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }',
|
|
240
|
+
'.iris-gallery-management { padding: 12px; margin-bottom: 12px; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 13px; display: grid; gap: 10px; }',
|
|
241
|
+
'.iris-gallery-management dl { margin: 0; display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 6px 12px; font-size: 12px; }',
|
|
242
|
+
'.iris-gallery-management dt { color: var(--dsw-alias-label-tertiary, #7a7a85); }',
|
|
243
|
+
'.iris-gallery-management dd { margin: 0; overflow-wrap: anywhere; white-space: pre-wrap; }',
|
|
244
|
+
'.iris-gallery-management-list { display: grid; gap: 8px; max-height: 45vh; overflow-y: auto; overflow-wrap: anywhere; font-size: 12px; }',
|
|
245
|
+
'.iris-gallery-transaction { padding: 8px; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 8px; display: grid; gap: 6px; }',
|
|
246
|
+
'.iris-gallery-filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }',
|
|
247
|
+
'.iris-gallery-filters label { display: grid; gap: 3px; font-size: 11px; flex: 1; min-width: 110px; }',
|
|
248
|
+
'.iris-gallery-filters select { min-height: 34px; padding: 4px 7px; background: var(--dsw-alias-bg-layer-2, #32323c); color: inherit; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 7px; font: inherit; }',
|
|
249
|
+
'.iris-gallery-pagination { display: flex; justify-content: center; align-items: center; gap: 12px; margin-top: 12px; }',
|
|
250
|
+
'.iris-gallery-analysis { display: grid; grid-template-columns: minmax(120px, .6fr) minmax(0, 1fr); gap: 12px; padding: 12px; margin-bottom: 12px; border: 1px solid var(--dsw-alias-brand-primary, #7aa2f7); border-radius: 13px; background: var(--dsw-alias-bg-layer-1, #26262e); }',
|
|
251
|
+
'.iris-gallery-analysis .iris-gallery-preview { max-height: 260px; border-radius: 8px; }',
|
|
252
|
+
'.iris-gallery-analysis-body { display: grid; gap: 8px; min-width: 0; }',
|
|
253
|
+
'.iris-gallery-analysis-head { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }',
|
|
254
|
+
'.iris-gallery-analysis-head strong { flex: 1; }',
|
|
255
|
+
'.iris-gallery-analysis textarea { width: 100%; box-sizing: border-box; min-height: 140px; max-height: 40vh; resize: vertical; font: inherit; line-height: 1.6; padding: 8px; color: inherit; background: var(--dsw-alias-bg-layer-2, #32323c); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 7px; }',
|
|
256
|
+
'.iris-gallery-analysis-options { display: grid; gap: 8px; }',
|
|
257
|
+
'.iris-gallery-analysis-options label { display: grid; gap: 3px; font-size: 11px; }',
|
|
258
|
+
'.iris-gallery-analysis-options input { box-sizing: border-box; width: 100%; padding: 6px; color: inherit; background: var(--dsw-alias-bg-layer-2, #32323c); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 6px; }',
|
|
259
|
+
'@media (max-width: 640px) { .iris-gallery-analysis { grid-template-columns: 1fr; } .iris-gallery-actions button, .iris-gallery-pagination button, .iris-gallery-analysis button, .iris-gallery-management button, .iris-gallery-bulk button { min-height: 40px; } }',
|
|
236
260
|
'.iris-artifact-mini { display: flex; align-items: center; gap: 7px; min-width: 0; padding: 4px 6px; border-radius: 7px; color: var(--dsw-alias-label-secondary, #9a9a9a); text-decoration: none; }',
|
|
237
261
|
'.iris-artifact-mini:hover { background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-primary, #e6e6e6); }',
|
|
238
262
|
'.iris-artifact-mini span:nth-child(2) { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
|
|
@@ -248,6 +272,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
248
272
|
'.iris-pm-ver.configured { color: #7da8fa; }',
|
|
249
273
|
'.iris-pm-ver.verified { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
250
274
|
'.iris-pm-ver.failed { color: #c87a81; border-color: rgba(185,101,109,.5); }',
|
|
275
|
+
'.iris-pm-ver.rate-limited { color: #f87171; border-color: #ef4444; box-shadow: 0 0 7px rgba(239,68,68,.35); }',
|
|
276
|
+
'.iris-pm-ver.cooling, .iris-pm-dot.cooling, .iris-health-row.cooling .iris-health-state { color: #fbbf24; border-color: #f59e0b; box-shadow: none; }',
|
|
277
|
+
'.iris-pm-dot.cooling { background: #fbbf24; opacity: 1; }',
|
|
278
|
+
'.iris-bubble.health-cooling .iris-bubble-btn { border-color: #f59e0b; box-shadow: 0 0 11px rgba(245,158,11,.3); }',
|
|
251
279
|
'.iris-pm-ver.unconfigured { opacity: .55; }',
|
|
252
280
|
'.iris-pm-madd { flex: 1; background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-primary, #e6e6e6); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 4px; padding: 3px 6px; font-size: 12px; }',
|
|
253
281
|
/* 文件选择器(阶段 10) */
|
|
@@ -271,9 +299,31 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
271
299
|
'.iris-po-trigger:hover { transform: translateY(-2px) scale(1.10); filter: drop-shadow(0 5px 10px rgba(112,137,255,.42)); }',
|
|
272
300
|
'.iris-po-trigger:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #7aa2f7); outline-offset: 2px; }',
|
|
273
301
|
'.iris-po-backdrop { position: fixed; inset: 0; z-index: 10019; background: rgba(8,10,18,.30); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); touch-action: none; animation: iris-po-fade .16s ease-out; }',
|
|
274
|
-
'.iris-po-panel
|
|
302
|
+
'.iris-po-panel:where(dialog) { left: auto; top: auto; margin: 0; max-width: none; }',
|
|
303
|
+
'dialog.iris-po-panel::backdrop { background: rgba(8,10,18,.30); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); }',
|
|
304
|
+
'.iris-po-panel { position: fixed; z-index: 10020; right: max(14px, env(safe-area-inset-right)); bottom: max(78px, calc(env(safe-area-inset-bottom) + 68px)); width: min(560px, calc(100vw - 28px)); max-height: min(780px, calc(100dvh - 104px)); overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; touch-action: pan-y; box-sizing: border-box; padding: 18px; display: grid; gap: 14px; background-color: rgba(35,36,47,.84); background: radial-gradient(circle at 10% -5%, rgba(132,160,255,.20), transparent 42%), color-mix(in srgb, var(--dsw-alias-bg-layer-1, #26262e) 92%, transparent); color: var(--dsw-alias-label-primary, #eeeeF2); border: 1px solid rgba(150,164,215,.25); border-radius: 22px; box-shadow: 0 24px 70px rgba(0,0,0,.36), inset 0 1px 0 rgba(255,255,255,.07); backdrop-filter: blur(28px) saturate(1.28); -webkit-backdrop-filter: blur(28px) saturate(1.28); animation: iris-po-rise .20s cubic-bezier(.2,.8,.2,1); scrollbar-width: thin; outline: none; }',
|
|
275
305
|
'.iris-po-head { position: sticky; top: -18px; z-index: 1; margin: -18px -18px 0; padding: 16px 18px 10px; display: flex; align-items: center; justify-content: space-between; gap: 10px; background: linear-gradient(to bottom, color-mix(in srgb, var(--dsw-alias-bg-layer-1, #26262e) 88%, transparent) 72%, transparent); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }',
|
|
276
|
-
'.iris-po-title { display: inline-flex; align-items: center; gap: 9px; font-size:
|
|
306
|
+
'.iris-po-title { display: inline-flex; align-items: center; gap: 9px; font-size: 16px; letter-spacing: .01em; }',
|
|
307
|
+
'.iris-po-intro { display: grid; gap: 9px; }',
|
|
308
|
+
'.iris-po-principles { display: flex; flex-wrap: wrap; gap: 6px; }',
|
|
309
|
+
'.iris-po-principles span { padding: 3px 8px; border-radius: 999px; background: rgba(113,139,219,.09); color: var(--dsw-alias-label-secondary, #b4bdd5); font-size: 10px; }',
|
|
310
|
+
'.iris-po-section { min-width: 0; border: 1px solid rgba(137,155,218,.22); border-radius: 16px; background: rgba(128,150,221,.045); overflow: hidden; }',
|
|
311
|
+
'.iris-po-section > summary { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 14px; cursor: pointer; list-style: none; font-size: 13px; font-weight: 650; }',
|
|
312
|
+
'.iris-po-section > summary::-webkit-details-marker { display: none; }',
|
|
313
|
+
'.iris-po-section > summary::after { content: "⌄"; color: var(--dsw-alias-label-secondary, #a6a6b2); transition: transform .15s; }',
|
|
314
|
+
'.iris-po-section[open] > summary::after { transform: rotate(180deg); }',
|
|
315
|
+
'.iris-po-section-caption { margin-left: auto; font-size: 11px; font-weight: 400; color: var(--dsw-alias-label-secondary, #a6a6b2); }',
|
|
316
|
+
'.iris-po-section-body { min-width: 0; display: grid; gap: 13px; padding: 0 14px 14px; }',
|
|
317
|
+
'.iris-po-section:not([open]) > .iris-po-section-body { display: none; }',
|
|
318
|
+
'.iris-po-targets { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 5px; }',
|
|
319
|
+
'.iris-po-target { min-width: 0; min-height: 42px; padding: 7px 3px; border: 1px solid transparent; border-radius: 10px; font: inherit; font-size: 12px; cursor: pointer; color: var(--dsw-alias-label-secondary, #a6a6b2); background: rgba(128,150,221,.05); }',
|
|
320
|
+
'.iris-po-target[aria-pressed="true"] { color: var(--dsw-alias-label-primary, #eeeef2); border-color: rgba(124,147,225,.48); background: rgba(124,147,225,.18); box-shadow: inset 0 1px 0 rgba(255,255,255,.1); font-weight: 650; }',
|
|
321
|
+
'.iris-po-target:disabled { opacity: .45; cursor: not-allowed; }',
|
|
322
|
+
'.iris-po-target:focus-visible, .iris-po-section > summary:focus-visible, .iris-po-config > summary:focus-visible { outline: 2px solid #8d9fe6; outline-offset: 2px; }',
|
|
323
|
+
'.iris-po-source { min-width: 0; padding: 11px 12px; border-radius: 12px; border: 1px solid rgba(150,164,215,.14); background: rgba(128,150,221,.04); }',
|
|
324
|
+
'.iris-po-source-head { display: flex; justify-content: space-between; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-secondary, #a6a6b2); }',
|
|
325
|
+
'.iris-po-source-text { margin: 8px 0 0; max-height: 112px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12px; line-height: 1.65; }',
|
|
326
|
+
'.iris-po-template { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 200px; overflow: auto; font-size: 11px; line-height: 1.65; padding: 10px; border-radius: 10px; background: rgba(128,150,221,.06); }',
|
|
277
327
|
'.iris-po-mark, .iris-po-settings-mark { display: inline-flex; align-items: center; justify-content: center; width: 29px; height: 29px; border-radius: 999px; background: rgba(255,255,255,.055); box-shadow: inset 0 1px 0 rgba(255,255,255,.10), 0 5px 16px rgba(67,83,168,.16); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); filter: drop-shadow(0 2px 5px rgba(105,128,235,.16)); }',
|
|
278
328
|
'.iris-po-close { width: 30px; height: 30px; display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 999px; background: rgba(255,255,255,.05); color: var(--dsw-alias-label-secondary, #a6a6b2); font-size: 14px; cursor: pointer; transition: background .15s, color .15s; }',
|
|
279
329
|
'.iris-po-close:hover { background: rgba(255,255,255,.10); color: var(--dsw-alias-label-primary, #eeeef2); }',
|
|
@@ -281,8 +331,19 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
281
331
|
'.iris-po-route { padding: 7px 10px; border-radius: 10px; background: rgba(255,255,255,.045); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
|
|
282
332
|
'.iris-po-note { padding: 8px 10px; border-radius: 10px; color: var(--dsw-alias-state-warn-primary, #dfbd69); background: rgba(217,169,65,.08); }',
|
|
283
333
|
'.iris-po-field { display: grid; gap: 6px; font-size: 11px; color: var(--dsw-alias-label-secondary, #a6a6b2); }',
|
|
284
|
-
'.iris-po-field select, .iris-po-result textarea { width: 100%; box-sizing: border-box; background: rgba(255,255,255,.055); color: var(--dsw-alias-label-primary, #eeeef2); border: 1px solid rgba(150,164,215,.20); border-radius: 11px; padding: 9px 11px; font: inherit; font-size: 12px; outline: none; transition: border-color .15s, background .15s, box-shadow .15s; }',
|
|
285
|
-
'.iris-po-field select:focus, .iris-po-result textarea:focus { border-color: rgba(122,162,247,.72); background: rgba(255,255,255,.075); box-shadow: 0 0 0 3px rgba(122,162,247,.11); }',
|
|
334
|
+
'.iris-po-field select, .iris-po-field textarea, .iris-po-result textarea { width: 100%; box-sizing: border-box; background: rgba(255,255,255,.055); color: var(--dsw-alias-label-primary, #eeeef2); border: 1px solid rgba(150,164,215,.20); border-radius: 11px; padding: 9px 11px; font: inherit; font-size: 12px; outline: none; transition: border-color .15s, background .15s, box-shadow .15s; }',
|
|
335
|
+
'.iris-po-field select:focus, .iris-po-field textarea:focus, .iris-po-result textarea:focus { border-color: rgba(122,162,247,.72); background: rgba(255,255,255,.075); box-shadow: 0 0 0 3px rgba(122,162,247,.11); }',
|
|
336
|
+
'.iris-po-field textarea { min-height: 60px; resize: vertical; line-height: 1.55; }',
|
|
337
|
+
'.iris-po-rules { display: grid; gap: 10px; }',
|
|
338
|
+
'.iris-po-editing { display: grid; gap: 8px; padding: 10px 12px; border: 1px solid rgba(150,164,215,.16); border-radius: 12px; }',
|
|
339
|
+
'.iris-po-editing label { display: flex; gap: 8px; align-items: center; font-size: 12px; cursor: pointer; }',
|
|
340
|
+
'.iris-po-suggestion { display: grid; gap: 7px; padding: 10px; margin-top: 8px; border-radius: 10px; background: rgba(128,150,221,.065); overflow-wrap: anywhere; }',
|
|
341
|
+
'.iris-po-suggestion label { display: flex; align-items: start; gap: 8px; font-size: 12px; }',
|
|
342
|
+
'.iris-po-suggestion p { margin: 0; white-space: pre-wrap; font-size: 12px; line-height: 1.6; }',
|
|
343
|
+
'.iris-po-diff { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 28dvh; overflow-y: auto; font-size: 12px; line-height: 1.6; }',
|
|
344
|
+
'.iris-po-diff del, .iris-po-diff ins { display: block; padding: 6px 8px; border-radius: 7px; margin: 5px 0; }',
|
|
345
|
+
'.iris-po-diff del { background: rgba(226,113,126,.12); }',
|
|
346
|
+
'.iris-po-diff ins { background: rgba(99,198,156,.12); text-decoration: none; }',
|
|
286
347
|
'.iris-po-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }',
|
|
287
348
|
'.iris-po-btn, .iris-po-primary { min-height: 34px; border: 1px solid rgba(150,164,215,.20); border-radius: 10px; padding: 6px 11px; font: inherit; font-size: 11px; cursor: pointer; transition: transform .15s, background .15s, border-color .15s; }',
|
|
288
349
|
'.iris-po-btn { background: rgba(255,255,255,.045); color: var(--dsw-alias-label-secondary, #b0b0bb); }',
|
|
@@ -290,7 +351,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
290
351
|
'.iris-po-primary { background: linear-gradient(135deg, #779cf3, #8c79de); color: #fff; border-color: rgba(174,187,255,.55); font-weight: 650; box-shadow: 0 6px 18px rgba(76,92,188,.22); }',
|
|
291
352
|
'.iris-po-primary:hover { transform: translateY(-1px); box-shadow: 0 8px 22px rgba(76,92,188,.32); }',
|
|
292
353
|
'.iris-po-primary:disabled, .iris-po-btn:disabled { opacity: .45; cursor: not-allowed; transform: none; box-shadow: none; }',
|
|
293
|
-
'.iris-po-result { display: grid; gap: 8px; padding: 12px; border: 1px solid rgba(137,155,218,.18); border-radius: 15px; background: rgba(
|
|
354
|
+
'.iris-po-result { display: grid; gap: 8px; padding: 12px; border: 1px solid rgba(137,155,218,.18); border-radius: 15px; background: rgba(128,150,221,.045); }',
|
|
294
355
|
'.iris-po-result-label { font-size: 11px; font-weight: 650; letter-spacing: .02em; }',
|
|
295
356
|
'.iris-po-result textarea { min-height: 156px; max-height: 34dvh; resize: vertical; line-height: 1.58; }',
|
|
296
357
|
'.iris-po-config { padding: 11px 12px; border: 1px solid rgba(150,164,215,.14); border-radius: 13px; background: rgba(255,255,255,.025); font-size: 11px; }',
|
|
@@ -419,7 +480,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
419
480
|
unconfigured: { label: '未配置', symbol: '○' },
|
|
420
481
|
configured: { label: '已配置,待验证', symbol: '◌' },
|
|
421
482
|
verified: { label: '近期验证成功', symbol: '●' },
|
|
422
|
-
failed: { label: '
|
|
483
|
+
failed: { label: '调用受阻', symbol: '×' }
|
|
423
484
|
};
|
|
424
485
|
|
|
425
486
|
function capabilityHealth(state, capability) {
|
|
@@ -443,9 +504,22 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
443
504
|
return Math.floor(ms / 86400000) + ' 天前';
|
|
444
505
|
}
|
|
445
506
|
|
|
507
|
+
function rateLimitLabel(health) {
|
|
508
|
+
return health.retryAt ? '429 冷却中' : health.reason === 'budget' ? '预算耗尽已停用' : '额度耗尽已停用';
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function healthClass(health) {
|
|
512
|
+
return health.retryAt ? 'cooling' : health.rateLimited ? 'rate-limited' : health.status;
|
|
513
|
+
}
|
|
514
|
+
|
|
446
515
|
function healthText(health) {
|
|
447
516
|
var status = health && HEALTH_META[health.status] ? health.status : 'unconfigured';
|
|
448
|
-
var text =
|
|
517
|
+
var text = health && health.rateLimited
|
|
518
|
+
? rateLimitLabel(health) + (health.retryAt
|
|
519
|
+
? ',' + new Date(health.retryAt).toLocaleTimeString() + ' 恢复候选资格;可提前实测'
|
|
520
|
+
: ',实测成功后恢复')
|
|
521
|
+
: (status === 'failed' && health && ['authentication', 'auth', 'permission'].indexOf(health.category) >= 0
|
|
522
|
+
? '认证失败' : HEALTH_META[status].label);
|
|
449
523
|
var age = relativeHealthTime(health && health.observedAt);
|
|
450
524
|
return text + (age ? ' · ' + age : '');
|
|
451
525
|
}
|
|
@@ -462,9 +536,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
462
536
|
return React.createElement('div', { className: 'iris-health-grid' }, rows.map(function (row) {
|
|
463
537
|
var health = capabilityHealth(state, row[0]);
|
|
464
538
|
var meta = HEALTH_META[health.status] || HEALTH_META.unconfigured;
|
|
465
|
-
return React.createElement('div', { key: row[0], className: 'iris-health-row ' + health
|
|
539
|
+
return React.createElement('div', { key: row[0], className: 'iris-health-row ' + healthClass(health) },
|
|
466
540
|
React.createElement('span', { className: 'iris-health-name' },
|
|
467
|
-
React.createElement('span', { className: 'iris-pm-dot ' + health
|
|
541
|
+
React.createElement('span', { className: 'iris-pm-dot ' + healthClass(health) }, health.retryAt ? '◷' : meta.symbol),
|
|
468
542
|
row[1]),
|
|
469
543
|
React.createElement('span', { className: 'iris-health-state', title: healthText(health) }, healthText(health)),
|
|
470
544
|
React.createElement('span', { className: 'iris-wb-muted' }, String(health.candidateCount || 0) + ' 路'));
|
|
@@ -743,12 +817,16 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
743
817
|
var newId = addPair[0];
|
|
744
818
|
var setNewId = addPair[1];
|
|
745
819
|
var models = p.models || [];
|
|
820
|
+
function setImageProtocol(m, value) {
|
|
821
|
+
postAction('providers_set_model_image_protocol', { id: p.id, model_id: m.id, imageProtocol: value })
|
|
822
|
+
.then(function (r) { props.onNote(r.ok ? '模型图片协议已更新' : (r.d.error || '更新失败')); if (r.ok) props.onDone(); });
|
|
823
|
+
}
|
|
746
824
|
function verBadge(m, c) {
|
|
747
825
|
var health = m.health && m.health[c];
|
|
748
826
|
var status = health && HEALTH_META[health.status] ? health.status : 'configured';
|
|
749
|
-
var cls = 'iris-pm-ver ' + status;
|
|
827
|
+
var cls = 'iris-pm-ver ' + status + (health && health.rateLimited ? ' ' + healthClass(health) : '');
|
|
750
828
|
var title = healthText(health || { status: status });
|
|
751
|
-
var symbol = status === 'verified' ? '✓' : (status === 'failed' ? '×' : (status === 'unconfigured' ? '○' : '?'));
|
|
829
|
+
var symbol = health && health.retryAt ? '◷' : status === 'verified' ? '✓' : (status === 'failed' ? '×' : (status === 'unconfigured' ? '○' : '?'));
|
|
752
830
|
return React.createElement('button', {
|
|
753
831
|
key: c,
|
|
754
832
|
className: cls,
|
|
@@ -758,9 +836,22 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
758
836
|
}
|
|
759
837
|
var rows = models.map(function (m) {
|
|
760
838
|
var caps = m.capabilities || [];
|
|
839
|
+
var blocked = caps.map(function (c) { return m.health && m.health[c]; }).find(function (health) { return health && health.rateLimited; });
|
|
761
840
|
return React.createElement('div', { key: m.id, className: 'iris-pm-mrow' },
|
|
762
841
|
React.createElement('span', { className: 'iris-pm-mname' }, m.id, m.source === 'manual' ? React.createElement('span', { className: 'iris-pm-mtag' }, '手动') : null),
|
|
763
|
-
React.createElement('span', { className: 'iris-pm-mcaps' }, caps.map(function (c) { return verBadge(m, c); })),
|
|
842
|
+
React.createElement('span', { className: 'iris-pm-mcaps' }, caps.length ? caps.map(function (c) { return verBadge(m, c); }) : '未标注功能'),
|
|
843
|
+
React.createElement(ModelCapabilityEditor, { provider: p, model: m, onNote: props.onNote, onDone: props.onDone }),
|
|
844
|
+
caps.includes('image-gen') ? React.createElement('label', { className: 'iris-pm-note' }, '图片协议 ',
|
|
845
|
+
React.createElement('select', { value: m.imageProtocol || 'auto', onChange: function (e) { setImageProtocol(m, e.target.value); } },
|
|
846
|
+
React.createElement('option', { value: 'auto' }, '继承账号默认(' + (p.mediaProtocol || '自动') + ')'),
|
|
847
|
+
React.createElement('option', { value: 'dashscope' }, 'DashScope'),
|
|
848
|
+
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images'),
|
|
849
|
+
React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
|
|
850
|
+
React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图')),
|
|
851
|
+
m.imageRouting && m.imageRouting.protocolInferred ? ' · 协议待确认' : null) : null,
|
|
852
|
+
caps.includes('vision') || (caps.includes('image-gen') && m.imageRouting && m.imageRouting.mediaProtocol === 'openai-chat-images') ? React.createElement(VisionInputEditor, { key: 'vision-input', provider: p, model: m,
|
|
853
|
+
onNote: props.onNote, onDone: props.onDone }) : null,
|
|
854
|
+
blocked ? React.createElement('span', { className: 'iris-pm-ver failed ' + healthClass(blocked), title: healthText(blocked) }, rateLimitLabel(blocked)) : null,
|
|
764
855
|
React.createElement('button', { className: 'iris-pm-btn danger', title: '从池移除', onClick: function () { props.removeModel(p, m); } }, '✕'));
|
|
765
856
|
});
|
|
766
857
|
return React.createElement('div', { className: 'iris-pm-pool' },
|
|
@@ -771,6 +862,75 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
771
862
|
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { props.addModel(p, newId.trim()); setNewId(''); } }, '+ 添加')));
|
|
772
863
|
}
|
|
773
864
|
|
|
865
|
+
function ModelCapabilityEditor(props) {
|
|
866
|
+
var openPair = React.useState(false);
|
|
867
|
+
var capsPair = React.useState([]);
|
|
868
|
+
var savingPair = React.useState(false);
|
|
869
|
+
var selected = capsPair[0];
|
|
870
|
+
var saving = savingPair[0];
|
|
871
|
+
function save() {
|
|
872
|
+
if (saving) return;
|
|
873
|
+
savingPair[1](true);
|
|
874
|
+
postAction('providers_set_model_caps', { id: props.provider.id, model_id: props.model.id, capabilities: selected })
|
|
875
|
+
.then(function (r) {
|
|
876
|
+
if (r.ok) {
|
|
877
|
+
openPair[1](false);
|
|
878
|
+
props.onNote('模型功能标签已保存');
|
|
879
|
+
props.onDone();
|
|
880
|
+
} else props.onNote(r.d.error || '保存失败');
|
|
881
|
+
}).finally(function () { savingPair[1](false); });
|
|
882
|
+
}
|
|
883
|
+
if (!openPair[0]) return React.createElement('button', { className: 'iris-pm-btn', title: '设置此模型可用于哪些功能',
|
|
884
|
+
onClick: function () { capsPair[1]((props.model.capabilities || []).slice()); openPair[1](true); } }, '设置功能');
|
|
885
|
+
return React.createElement('div', { className: 'iris-wb-row' },
|
|
886
|
+
CAP_ROWS.map(function (row) {
|
|
887
|
+
return React.createElement('label', { key: row.cap, className: 'iris-pm-note' },
|
|
888
|
+
React.createElement('input', { type: 'checkbox', value: row.cap, checked: selected.includes(row.cap), disabled: saving,
|
|
889
|
+
onChange: function (e) {
|
|
890
|
+
var checked = e.target.checked;
|
|
891
|
+
capsPair[1](function (previous) {
|
|
892
|
+
return checked ? previous.filter(function (cap) { return cap !== row.cap; }).concat(row.cap)
|
|
893
|
+
: previous.filter(function (cap) { return cap !== row.cap; });
|
|
894
|
+
});
|
|
895
|
+
} }), row.label);
|
|
896
|
+
}),
|
|
897
|
+
React.createElement('button', { className: 'iris-pm-btn', disabled: saving, onClick: save }, saving ? '保存中…' : '保存功能'),
|
|
898
|
+
React.createElement('button', { className: 'iris-pm-btn', disabled: saving, onClick: function () { openPair[1](false); } }, '取消'));
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function VisionInputEditor(props) {
|
|
902
|
+
var p = props.provider;
|
|
903
|
+
var m = props.model;
|
|
904
|
+
var limits = (m ? m.visionInput : p.visionInput) || {};
|
|
905
|
+
var bytesPair = React.useState(limits.maxBytes === undefined ? '' : String(limits.maxBytes / 1048576));
|
|
906
|
+
var dimensionPair = React.useState(limits.maxDimension === undefined ? '' : String(limits.maxDimension));
|
|
907
|
+
React.useEffect(function () {
|
|
908
|
+
bytesPair[1](limits.maxBytes === undefined ? '' : String(limits.maxBytes / 1048576));
|
|
909
|
+
dimensionPair[1](limits.maxDimension === undefined ? '' : String(limits.maxDimension));
|
|
910
|
+
}, [limits.maxBytes, limits.maxDimension]);
|
|
911
|
+
function save(reset) {
|
|
912
|
+
var value = {};
|
|
913
|
+
if (bytesPair[0] !== '') value.maxBytes = Number(bytesPair[0]) * 1048576;
|
|
914
|
+
if (dimensionPair[0] !== '') value.maxDimension = Number(dimensionPair[0]);
|
|
915
|
+
if (!reset && Object.values(value).some(function (number) { return !Number.isSafeInteger(number) || number <= 0; })) {
|
|
916
|
+
props.onNote('请输入正数 MiB 和正整数像素'); return;
|
|
917
|
+
}
|
|
918
|
+
var action = m ? 'providers_set_model_vision_input' : 'providers_upsert';
|
|
919
|
+
postAction(action, { id: p.id, ...(m ? { model_id: m.id } : {}), visionInput: reset ? null : value })
|
|
920
|
+
.then(function (r) { props.onNote(r.ok ? '视觉输入预算已更新' : (r.d.error || '更新失败')); if (r.ok) props.onDone(); });
|
|
921
|
+
}
|
|
922
|
+
return React.createElement('div', { className: 'iris-pm-field' },
|
|
923
|
+
React.createElement('label', {}, m ? '模型视觉预算(留空继承账号)' : '账号视觉预算(留空使用默认 8 MiB)'),
|
|
924
|
+
React.createElement('div', { className: 'iris-wb-row' },
|
|
925
|
+
React.createElement('input', { type: 'number', min: '0', step: 'any', 'aria-label': '视觉大小上限 MiB',
|
|
926
|
+
placeholder: 'MiB', value: bytesPair[0], onChange: function (e) { bytesPair[1](e.target.value); } }),
|
|
927
|
+
React.createElement('input', { type: 'number', min: '1', step: '1', 'aria-label': '视觉最长边像素',
|
|
928
|
+
placeholder: '最长边 px(可选)', value: dimensionPair[0], onChange: function (e) { dimensionPair[1](e.target.value); } }),
|
|
929
|
+
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { save(false); } }, '保存预算'),
|
|
930
|
+
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { save(true); } }, '恢复继承')),
|
|
931
|
+
React.createElement('span', { className: 'iris-pm-note' }, '用于看图、OCR 每块输入和定位。作品原图保留,定位结果换算回原图。'));
|
|
932
|
+
}
|
|
933
|
+
|
|
774
934
|
function ProviderEndpointEditor(props) {
|
|
775
935
|
var p = props.provider;
|
|
776
936
|
var valuePair = React.useState(p.mediaBaseUrl || '');
|
|
@@ -807,7 +967,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
807
967
|
function refresh() {
|
|
808
968
|
postAction('providers_list', {}).then(function (r) { if (r.ok && r.d.providers) setList(r.d.providers); });
|
|
809
969
|
}
|
|
810
|
-
React.useEffect(function () {
|
|
970
|
+
React.useEffect(function () {
|
|
971
|
+
refresh();
|
|
972
|
+
var timer = setInterval(refresh, 5000);
|
|
973
|
+
return function () { clearInterval(timer); };
|
|
974
|
+
}, []);
|
|
811
975
|
function addProvider() {
|
|
812
976
|
var f = newForm[0];
|
|
813
977
|
if (!f.baseUrl || !f.apiKey) { setNote('baseUrl 和 apiKey 必填'); return; }
|
|
@@ -863,11 +1027,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
863
1027
|
React.createElement('div', { className: 'iris-pm-note' }, 'Key ' + (p.apiKeyHint || '未配置') + ' · ' + (p.type || '') + ' · ' + (p.mediaProtocol || '')),
|
|
864
1028
|
p.protocolInferred ? React.createElement('div', { className: 'iris-pm-note' }, '协议为推断值,请确认') : null,
|
|
865
1029
|
React.createElement(ProviderEndpointEditor, { provider: p, onNote: setNote, onDone: refresh }),
|
|
1030
|
+
React.createElement(VisionInputEditor, { provider: p, onNote: setNote, onDone: refresh }),
|
|
866
1031
|
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); } },
|
|
867
1032
|
React.createElement('option', { value: 'auto' }, '自动'),
|
|
868
|
-
p.mediaProtocol && !['dashscope', 'openai-images', 'auto'].includes(p.mediaProtocol) ? React.createElement('option', { value: p.mediaProtocol }, p.mediaProtocol + '(未支持)') : null,
|
|
1033
|
+
p.mediaProtocol && !['dashscope', 'openai-images', 'openai-chat-images', 'openai-responses-images', 'auto'].includes(p.mediaProtocol) ? React.createElement('option', { value: p.mediaProtocol }, p.mediaProtocol + '(未支持)') : null,
|
|
869
1034
|
React.createElement('option', { value: 'dashscope' }, 'DashScope(仅阿里云官方 HTTPS)'),
|
|
870
|
-
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容')
|
|
1035
|
+
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'),
|
|
1036
|
+
React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
|
|
1037
|
+
React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图'))),
|
|
871
1038
|
React.createElement('div', { className: 'iris-pm-note' }, '能力: ' + ((p.capabilities || []).join(' / ') || '无')),
|
|
872
1039
|
React.createElement(ModelPool, { provider: p, onNote: setNote, onDone: refresh, testModel: testModel, removeModel: removeModel, addModel: addModel }),
|
|
873
1040
|
React.createElement('div', { className: 'iris-wb-row' },
|
|
@@ -891,7 +1058,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
891
1058
|
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 }); } },
|
|
892
1059
|
React.createElement('option', { value: 'auto' }, '自动(按媒体 Base URL 安全判断)'),
|
|
893
1060
|
React.createElement('option', { value: 'dashscope' }, 'DashScope(阿里云官方)'),
|
|
894
|
-
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容')
|
|
1061
|
+
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'),
|
|
1062
|
+
React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
|
|
1063
|
+
React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图'))),
|
|
895
1064
|
React.createElement('div', { className: 'iris-wb-row' },
|
|
896
1065
|
React.createElement('button', { className: 'iris-act-run', onClick: addProvider }, '保存'),
|
|
897
1066
|
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { setAdding(false); } }, '取消')));
|
|
@@ -1062,7 +1231,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1062
1231
|
var shown = (vals.model && vals.model.trim()) || (modelInfo && modelInfo.assigned) || '';
|
|
1063
1232
|
modelBar = React.createElement('div', { className: 'iris-act-field' },
|
|
1064
1233
|
React.createElement('label', {},
|
|
1065
|
-
React.createElement('span', { className: 'iris-pm-dot ' + cardHealth
|
|
1234
|
+
React.createElement('span', { className: 'iris-pm-dot ' + healthClass(cardHealth) }, cardHealth.retryAt ? '◷' : HEALTH_META[cardHealth.status].symbol),
|
|
1066
1235
|
' 模型:' + (shown || '自动') + (available ? '' : '(无可用模型)')),
|
|
1067
1236
|
React.createElement('div', { className: 'iris-wb-muted' },
|
|
1068
1237
|
healthText(cardHealth) + ' · 改分配 → 上方「能力分配(failover 顺序)」'));
|
|
@@ -1082,9 +1251,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1082
1251
|
React.createElement('div', { className: 'iris-act-head', onClick: function () { setOpen(!open); } },
|
|
1083
1252
|
React.createElement('span', { className: 'iris-act-title' }, title),
|
|
1084
1253
|
capability ? React.createElement('span', {
|
|
1085
|
-
className: 'iris-pm-dot ' +
|
|
1254
|
+
className: 'iris-pm-dot ' + healthClass(cardHealth),
|
|
1086
1255
|
title: headTitle
|
|
1087
|
-
}, HEALTH_META[headStatus].symbol) : null,
|
|
1256
|
+
}, cardHealth.retryAt ? '◷' : HEALTH_META[headStatus].symbol) : null,
|
|
1088
1257
|
React.createElement('span', { className: 'iris-act-arrow' }, '▶')),
|
|
1089
1258
|
body);
|
|
1090
1259
|
}
|
|
@@ -1328,7 +1497,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1328
1497
|
|
|
1329
1498
|
function coreGalleryItems(data, limit) {
|
|
1330
1499
|
return (((data && data.artifacts && data.artifacts.recent) || []).filter(function (artifact) {
|
|
1331
|
-
return artifact.kind !== 'host-input'
|
|
1500
|
+
return artifact.kind !== 'host-input';
|
|
1332
1501
|
}).map(function (artifact) {
|
|
1333
1502
|
return {
|
|
1334
1503
|
id: artifact.id, file: artifact.id, mime: artifact.mediaType,
|
|
@@ -1364,99 +1533,458 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1364
1533
|
return pair[0];
|
|
1365
1534
|
}
|
|
1366
1535
|
|
|
1367
|
-
function
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
var
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1536
|
+
function galleryKindLabel(kind) {
|
|
1537
|
+
return ({ __proto__: null, 'generated-image': '生成图片', 'generated-video': '生成视频', 'generated-audio': '生成音频',
|
|
1538
|
+
'tts-audio': '合成语音', transcript: '转写文本', 'video-frame': '视频帧', crop: '裁剪图片',
|
|
1539
|
+
'cropped-image': '裁剪图片', 'pixel-diff': '差异热图', 'diff-heatmap': '差异热图', 'html-screenshot': 'HTML 截图',
|
|
1540
|
+
'legacy-output': '旧版作品' })[kind] || kind;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
function fetchWorkbenchJson(url, input, signal) {
|
|
1544
|
+
var options = { signal: signal };
|
|
1545
|
+
if (input) { options.method = 'POST'; options.headers = { 'Content-Type': 'application/json' }; options.body = JSON.stringify(input); }
|
|
1546
|
+
return fetch(url, options).then(function (res) { return res.json().then(function (data) {
|
|
1547
|
+
if (!res.ok || data.ok === false) throw new Error((data.error || '作品操作失败') + (data.transactionId ? '(事务 ' + data.transactionId + ')' : ''));
|
|
1548
|
+
return data;
|
|
1549
|
+
}); });
|
|
1550
|
+
}
|
|
1551
|
+
function copyWorkbenchText(text, setNote) {
|
|
1552
|
+
if (!navigator.clipboard || typeof navigator.clipboard.writeText !== 'function') { setNote('请手动选择并复制 ID:\n' + text); return; }
|
|
1553
|
+
navigator.clipboard.writeText(text).then(function () { setNote('已复制'); }).catch(function () { setNote('请手动选择并复制 ID:\n' + text); });
|
|
1554
|
+
}
|
|
1555
|
+
function saveWorkbenchBlob(blob, name) {
|
|
1556
|
+
var url = URL.createObjectURL(blob), link = document.createElement('a');
|
|
1557
|
+
link.href = url; link.download = name; link.click();
|
|
1558
|
+
setTimeout(function () { URL.revokeObjectURL(url); }, 0);
|
|
1559
|
+
}
|
|
1560
|
+
function deletionBlockerLabel(blocker) {
|
|
1561
|
+
return ({ __proto__: null, task_not_settled: '任务尚未结束或结果未知', task_artifact_reference: '被任务记录引用', task_input_reference: '被改图任务作为原图引用',
|
|
1562
|
+
artifact_task_reference: '该任务仍关联其他作品', retry_reference: '被后续重试任务引用',
|
|
1563
|
+
artifact_relation: '被其他作品引用', uncommitted_relation: '被尚未提交的产物引用' })[blocker.reason] || '仍有引用';
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
function WorkbenchManagement(props) {
|
|
1567
|
+
var type = props.type;
|
|
1568
|
+
var dataPair = React.useState(null), data = dataPair[0], setData = dataPair[1];
|
|
1569
|
+
var busyPair = React.useState(true), busy = busyPair[0], setBusy = busyPair[1];
|
|
1570
|
+
var writingPair = React.useState(false), writing = writingPair[0], setWriting = writingPair[1];
|
|
1571
|
+
var notePair = React.useState(''), note = notePair[0], setNote = notePair[1];
|
|
1572
|
+
var tasksPair = React.useState([]), taskIds = tasksPair[0], setTaskIds = tasksPair[1];
|
|
1573
|
+
var request = React.useRef(null), view = React.useRef(null);
|
|
1574
|
+
var artifactIds = (props.items || []).map(function (item) { return item.id; });
|
|
1575
|
+
function query(tasks) {
|
|
1576
|
+
if (request.current) request.current.abort();
|
|
1577
|
+
var controller = new AbortController(); request.current = controller; setBusy(true); setNote('');
|
|
1578
|
+
var url = type === 'details' ? '/iris/api/works/' + props.item.source + '/' + props.item.id
|
|
1579
|
+
: type === 'transactions' ? '/iris/api/works/transactions' : '/iris/api/works/delete';
|
|
1580
|
+
fetchWorkbenchJson(url, type === 'delete' ? { artifact_ids: artifactIds, task_ids: tasks || [] } : null, controller.signal)
|
|
1581
|
+
.then(function (value) { if (request.current === controller && !controller.signal.aborted) setData(value); })
|
|
1582
|
+
.catch(function (error) { if (request.current === controller && !controller.signal.aborted) { setData(null); setNote(error.message); } })
|
|
1583
|
+
.finally(function () { if (request.current === controller) { request.current = null; setBusy(false); } });
|
|
1584
|
+
}
|
|
1585
|
+
React.useEffect(function () {
|
|
1586
|
+
query([]);
|
|
1587
|
+
if (view.current) view.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
1588
|
+
return function () { if (request.current) request.current.abort(); request.current = null; };
|
|
1589
|
+
}, []);
|
|
1590
|
+
function chooseTask(id, checked) {
|
|
1591
|
+
var next = checked ? taskIds.concat(id) : taskIds.filter(function (value) { return value !== id; });
|
|
1592
|
+
setTaskIds(next); query(next);
|
|
1593
|
+
}
|
|
1594
|
+
function execute(transaction) {
|
|
1595
|
+
var restoring = type === 'transactions';
|
|
1596
|
+
var message = restoring ? '恢复事务 ' + transaction.transactionId + ' 中的 ' + transaction.files + ' 个文件?已有文件不会被覆盖。'
|
|
1597
|
+
: '将 ' + artifactIds.length + ' 个 Core 作品' + (taskIds.length ? '及 ' + taskIds.length + ' 个已结束任务记录' : '') + '移入回收区?之后可以恢复。';
|
|
1598
|
+
if (!window.confirm(message)) return;
|
|
1599
|
+
var controller = new AbortController(); request.current = controller;
|
|
1600
|
+
setWriting(true); setBusy(true); setNote(''); props.onBusy(true);
|
|
1601
|
+
fetchWorkbenchJson('/iris/api/works/' + (restoring ? 'restore' : 'delete'), restoring
|
|
1602
|
+
? { transaction_id: transaction.transactionId, confirm_restore: true }
|
|
1603
|
+
: { artifact_ids: artifactIds, task_ids: taskIds, confirm_delete: true }, controller.signal)
|
|
1604
|
+
.then(function (value) {
|
|
1605
|
+
if (request.current !== controller || controller.signal.aborted) return;
|
|
1606
|
+
props.onChanged(value);
|
|
1607
|
+
if (restoring) query([]); else props.onClose();
|
|
1608
|
+
})
|
|
1609
|
+
.catch(function (error) { if (request.current === controller && !controller.signal.aborted) setNote(error.message); })
|
|
1610
|
+
.finally(function () {
|
|
1611
|
+
props.onBusy(false); setWriting(false);
|
|
1612
|
+
if (request.current === controller) { request.current = null; setBusy(false); }
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
var item = data && data.item;
|
|
1616
|
+
var titles = { details: '作品详情', delete: '删除预览', transactions: 'Core 回收区' };
|
|
1617
|
+
return React.createElement('section', { ref: view, className: 'iris-gallery-management', 'aria-label': titles[type], 'aria-busy': busy },
|
|
1618
|
+
React.createElement('div', { className: 'iris-gallery-analysis-head' }, React.createElement('strong', {}, titles[type]),
|
|
1619
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: writing, onClick: props.onClose }, '关闭面板')),
|
|
1620
|
+
busy ? React.createElement('div', { role: 'status', className: 'iris-wb-muted' }, writing ? '正在处理…' : '正在读取…') : null,
|
|
1621
|
+
item ? React.createElement('dl', {}, [
|
|
1622
|
+
['ID', item.id], ['来源', item.source === 'core' ? 'Core' : '旧版'], ['文件名', item.file], ['媒体类型', item.mime],
|
|
1623
|
+
['产物类型', galleryKindLabel(item.kind)], ['大小', fmtBytes(item.size)], ['创建时间', fmtTime(item.createdAt)],
|
|
1624
|
+
['内容核验', item.integrity === 'verified' ? 'SHA-256 已核验' : '文件存在;旧版没有已存哈希'],
|
|
1625
|
+
['SHA-256', item.digest], ['关联', (item.relations || []).map(function (edge) { return edge.type + ' → ' + edge.artifactId; }).join('\n')],
|
|
1626
|
+
['元数据', item.metadata && Object.keys(item.metadata).length ? JSON.stringify(item.metadata, null, 2) : '']
|
|
1627
|
+
].filter(function (row) { return !!row[1]; }).map(function (row) {
|
|
1628
|
+
return [React.createElement('dt', { key: row[0] + '-label' }, row[0]), React.createElement('dd', { key: row[0] }, row[1])];
|
|
1629
|
+
})) : null,
|
|
1630
|
+
item ? React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1631
|
+
React.createElement('a', { className: 'iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开原文件'),
|
|
1632
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: function () { copyWorkbenchText(item.id, setNote); } }, '复制 ID')) : null,
|
|
1633
|
+
type === 'delete' && data ? React.createElement('div', { className: 'iris-gallery-management-list' },
|
|
1634
|
+
React.createElement('div', {}, '选中 ' + artifactIds.length + ' 个作品 · ' + data.files + ' 个文件 · ' + fmtBytes(data.bytes)),
|
|
1635
|
+
artifactIds.map(function (id) { return React.createElement('div', { key: id }, id); }),
|
|
1636
|
+
(data.referencingTasks || []).length ? React.createElement('div', { className: 'iris-wb-muted' }, '如需同时移除关联任务记录,请逐项勾选;未结束任务及其他引用会阻止删除。') : null,
|
|
1637
|
+
(data.referencingTasks || []).map(function (task) { return React.createElement('label', { key: task.id, className: 'iris-gallery-select' },
|
|
1638
|
+
React.createElement('input', { type: 'checkbox', 'aria-label': '关联任务 ' + task.id, checked: taskIds.indexOf(task.id) >= 0,
|
|
1639
|
+
disabled: busy || !task.settled, onChange: function (event) { chooseTask(task.id, event.target.checked); } }),
|
|
1640
|
+
'同时移除任务记录 ' + task.id + (task.settled ? '' : '(未结束或结果未知)')); }),
|
|
1641
|
+
(data.blockers || []).map(function (blocker, index) { return React.createElement('div', { key: index, className: 'iris-wb-muted' },
|
|
1642
|
+
deletionBlockerLabel(blocker) + ':' + blocker.id + (blocker.referencedBy ? ' ← ' + blocker.referencedBy : '')); })) : null,
|
|
1643
|
+
type === 'delete' ? React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1644
|
+
React.createElement('button', { className: 'iris-pm-btn danger', type: 'button', disabled: busy || !data || !data.allowed,
|
|
1645
|
+
onClick: function () { execute(); } }, '确认移入回收区'),
|
|
1646
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', disabled: busy, onClick: function () { query(taskIds); } }, '重新预览')) : null,
|
|
1647
|
+
type === 'transactions' && data ? React.createElement('div', { className: 'iris-gallery-management-list' },
|
|
1648
|
+
data.transactions.length ? data.transactions.map(function (transaction) {
|
|
1649
|
+
var states = { __proto__: null, prepared: '准备中', committed: '已隔离', interrupted: '操作中断', restored: '已恢复', 'rolled-back': '已回退' };
|
|
1650
|
+
return React.createElement('div', { key: transaction.transactionId, className: 'iris-gallery-transaction' },
|
|
1651
|
+
React.createElement('div', {}, transaction.transactionId),
|
|
1652
|
+
React.createElement('div', { className: 'iris-wb-muted' }, (transaction.operation === 'cleanup' ? '清理' : '删除') + ' · '
|
|
1653
|
+
+ (states[transaction.state] || transaction.state) + ' · ' + transaction.files + ' 文件 · ' + fmtTime(transaction.createdAt)),
|
|
1654
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn',
|
|
1655
|
+
disabled: busy || ['prepared', 'committed', 'interrupted'].indexOf(transaction.state) < 0,
|
|
1656
|
+
onClick: function () { execute(transaction); } }, '恢复'));
|
|
1657
|
+
}) : React.createElement('div', { className: 'iris-wb-muted' }, '回收区为空。')) : null,
|
|
1658
|
+
type === 'transactions' ? React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: busy, onClick: function () { query([]); } }, '刷新回收区') : null,
|
|
1659
|
+
note ? React.createElement('div', { role: 'status', className: 'iris-clean-note', style: { whiteSpace: 'pre-wrap' } }, note) : null);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
function ArtifactAnalysis(props) {
|
|
1663
|
+
var item = props.item, action = props.action;
|
|
1664
|
+
var resultPair = React.useState(null), result = resultPair[0], setResult = resultPair[1];
|
|
1665
|
+
var optionsPair = React.useState({ question: '', chunk_height: '1200', overlap: '120' });
|
|
1666
|
+
var options = optionsPair[0], setOptions = optionsPair[1];
|
|
1667
|
+
var notePair = React.useState(''), note = notePair[0], setNote = notePair[1];
|
|
1668
|
+
var request = React.useRef(null);
|
|
1669
|
+
var view = React.useRef(null);
|
|
1670
|
+
var running = result && result.status === 'running';
|
|
1671
|
+
var text = result && result.text || '';
|
|
1672
|
+
function execute() {
|
|
1673
|
+
if (request.current) request.current.abort();
|
|
1674
|
+
var controller = new AbortController();
|
|
1675
|
+
request.current = controller;
|
|
1676
|
+
var input = { artifact_id: item.id };
|
|
1677
|
+
if (action === 'look') input.question = options.question;
|
|
1678
|
+
else {
|
|
1679
|
+
input.chunk_height = Number(options.chunk_height);
|
|
1680
|
+
input.overlap = Number(options.overlap);
|
|
1681
|
+
}
|
|
1682
|
+
setResult({ status: 'running', text: '' }); setNote('');
|
|
1683
|
+
fetch('/iris/api/actions/' + action, { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
1684
|
+
body: JSON.stringify(input), signal: controller.signal })
|
|
1685
|
+
.then(function (res) { return res.json().then(function (data) { return { ok: res.ok, data: data }; }); })
|
|
1686
|
+
.then(function (response) {
|
|
1687
|
+
if (request.current !== controller || controller.signal.aborted) return;
|
|
1688
|
+
var data = response.data;
|
|
1689
|
+
setResult({ status: !response.ok || data.ok === false ? 'failed' : (data.status || 'complete'),
|
|
1690
|
+
text: data.text || data.error || '未返回识别结果' });
|
|
1691
|
+
})
|
|
1692
|
+
.catch(function (error) {
|
|
1693
|
+
if (request.current !== controller || controller.signal.aborted) return;
|
|
1694
|
+
setResult({ status: 'failed', text: '识别请求失败:' + error.message });
|
|
1695
|
+
})
|
|
1696
|
+
.finally(function () { if (request.current === controller) request.current = null; });
|
|
1697
|
+
}
|
|
1698
|
+
function cancel() {
|
|
1699
|
+
if (request.current) request.current.abort();
|
|
1700
|
+
request.current = null;
|
|
1701
|
+
setResult({ status: 'canceled', text: '' });
|
|
1702
|
+
}
|
|
1703
|
+
React.useEffect(function () {
|
|
1704
|
+
execute();
|
|
1705
|
+
if (view.current) view.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
1706
|
+
return function () { if (request.current) request.current.abort(); request.current = null; };
|
|
1707
|
+
}, []);
|
|
1708
|
+
function option(key, value) { setOptions(function (current) { var next = Object.assign({}, current); next[key] = value; return next; }); }
|
|
1709
|
+
function copy() {
|
|
1710
|
+
if (!navigator.clipboard || typeof navigator.clipboard.writeText !== 'function') { setNote('请选中下方文字手动复制'); return; }
|
|
1711
|
+
navigator.clipboard.writeText(text).then(function () { setNote('已复制识别结果'); }).catch(function () { setNote('复制失败,请手动选择文字'); });
|
|
1712
|
+
}
|
|
1713
|
+
function download() {
|
|
1714
|
+
var url = URL.createObjectURL(new Blob([text], { type: 'text/plain;charset=utf-8' }));
|
|
1715
|
+
var link = document.createElement('a'); link.href = url; link.download = item.id + '-' + action + '.txt'; link.click();
|
|
1716
|
+
setTimeout(function () { URL.revokeObjectURL(url); }, 0);
|
|
1717
|
+
}
|
|
1718
|
+
var status = result && result.status || 'running';
|
|
1719
|
+
var labels = { running: '识别中…', complete: '完成', partial: '部分完成', failed: '失败', canceled: '已取消' };
|
|
1720
|
+
return React.createElement('section', { ref: view, className: 'iris-gallery-analysis', 'aria-label': '作品识别', 'aria-busy': !!running },
|
|
1721
|
+
artifactPreview(item),
|
|
1722
|
+
React.createElement('div', { className: 'iris-gallery-analysis-body' },
|
|
1723
|
+
React.createElement('div', { className: 'iris-gallery-analysis-head' },
|
|
1724
|
+
React.createElement('strong', {}, action === 'look' ? '看图结果' : 'OCR 结果'),
|
|
1725
|
+
React.createElement('span', { role: 'status', className: 'iris-wb-badge ' + (status === 'partial' ? 'warn' : status === 'failed' ? 'err' : status === 'complete' ? 'ok' : '') }, labels[status]),
|
|
1726
|
+
running ? React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: cancel }, '取消识别') : null,
|
|
1727
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: props.onClose }, '关闭结果')),
|
|
1728
|
+
React.createElement('details', {}, React.createElement('summary', {}, '更多选项'),
|
|
1729
|
+
React.createElement('div', { className: 'iris-gallery-analysis-options' }, action === 'look'
|
|
1730
|
+
? React.createElement('label', {}, '看图问题', React.createElement('input', { value: options.question, placeholder: '默认:详细描述这张图片', onChange: function (e) { option('question', e.target.value); } }))
|
|
1731
|
+
: ['chunk_height', 'overlap'].map(function (key) {
|
|
1732
|
+
return React.createElement('label', { key: key }, key === 'chunk_height' ? '分块高度' : '重叠像素',
|
|
1733
|
+
React.createElement('input', { type: 'number', min: key === 'chunk_height' ? 100 : 0, value: options[key], onChange: function (e) { option(key, e.target.value); } }));
|
|
1734
|
+
}),
|
|
1735
|
+
React.createElement('button', { className: 'iris-clean-btn', type: 'button', disabled: !!running, onClick: execute }, '按当前选项重新识别'))),
|
|
1736
|
+
text ? React.createElement('textarea', { 'aria-label': '识别结果', readOnly: true, value: text }) : null,
|
|
1737
|
+
text ? React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1738
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: copy }, '复制文本'),
|
|
1739
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: download }, '下载文本')) : null,
|
|
1740
|
+
note ? React.createElement('div', { role: 'status', className: 'iris-clean-note' }, note) : null));
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
function ArtifactImageEdit(props) {
|
|
1744
|
+
var item = props.item;
|
|
1745
|
+
var promptPair = React.useState(''), prompt = promptPair[0], setPrompt = promptPair[1];
|
|
1746
|
+
var modelPair = React.useState(''), model = modelPair[0], setModel = modelPair[1];
|
|
1747
|
+
var modelsPair = React.useState(null), models = modelsPair[0], setModels = modelsPair[1];
|
|
1748
|
+
var resultPair = React.useState(null), result = resultPair[0], setResult = resultPair[1];
|
|
1749
|
+
var request = React.useRef(null);
|
|
1750
|
+
var running = result && result.status === 'running';
|
|
1751
|
+
React.useEffect(function () {
|
|
1752
|
+
var active = true;
|
|
1753
|
+
postAction('providers_list', {}).then(function (response) {
|
|
1754
|
+
if (!active) return;
|
|
1755
|
+
if (!response.ok || !response.d.ok) throw new Error('模型列表读取失败');
|
|
1756
|
+
var choices = [];
|
|
1757
|
+
(response.d.providers || []).forEach(function (provider) {
|
|
1758
|
+
if (!provider.enabled) return;
|
|
1759
|
+
(provider.models || []).forEach(function (entry) {
|
|
1760
|
+
if ((entry.capabilities || []).indexOf('image-gen') < 0
|
|
1761
|
+
|| !entry.imageRouting || entry.imageRouting.mediaProtocol !== 'openai-chat-images') return;
|
|
1762
|
+
var health = entry.health && entry.health['image-gen'];
|
|
1763
|
+
choices.push({ id: provider.id + '::' + entry.id, label: provider.name + ' · ' + entry.id,
|
|
1764
|
+
blocked: !!(health && health.rateLimited) });
|
|
1765
|
+
});
|
|
1766
|
+
});
|
|
1767
|
+
setModels(choices);
|
|
1768
|
+
}).catch(function (error) { if (active) { setModels([]); setResult({ status: 'failed', text: error.message }); } });
|
|
1769
|
+
return function () { active = false; if (request.current) request.current.abort(); request.current = null; };
|
|
1770
|
+
}, []);
|
|
1771
|
+
function execute() {
|
|
1772
|
+
if (request.current || !prompt.trim()) return;
|
|
1773
|
+
var controller = new AbortController(); request.current = controller;
|
|
1774
|
+
setResult({ status: 'running', text: '' });
|
|
1775
|
+
fetch('/iris/api/actions/image_edit', { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
1776
|
+
body: JSON.stringify({ source_artifact_id: item.id, prompt: prompt.trim(), ...(model ? { model: model } : {}) }), signal: controller.signal })
|
|
1777
|
+
.then(function (res) { return res.json().then(function (data) { return { ok: res.ok && data.ok !== false, data: data }; }); })
|
|
1778
|
+
.then(function (response) {
|
|
1779
|
+
if (request.current !== controller || controller.signal.aborted) return;
|
|
1780
|
+
var data = response.data;
|
|
1781
|
+
setResult({ status: response.ok ? 'complete' : 'failed', text: data.text || data.error || '未返回编辑结果',
|
|
1782
|
+
artifactIds: response.ok ? data.artifactIds || [] : [], taskId: data.taskId });
|
|
1783
|
+
if (response.ok) window.dispatchEvent(new CustomEvent('iris-core-artifacts-changed'));
|
|
1784
|
+
})
|
|
1785
|
+
.catch(function (error) {
|
|
1786
|
+
if (request.current === controller && !controller.signal.aborted) setResult({ status: 'failed', text: '改图请求失败:' + error.message + '。请在任务区检查结果。' });
|
|
1787
|
+
})
|
|
1788
|
+
.finally(function () { if (request.current === controller) request.current = null; });
|
|
1789
|
+
}
|
|
1790
|
+
function cancel() {
|
|
1791
|
+
if (request.current) request.current.abort(); request.current = null;
|
|
1792
|
+
setResult({ status: 'canceled', text: '已停止等待,请在任务区检查是否已生成。不会自动重新提交。' });
|
|
1793
|
+
}
|
|
1794
|
+
return React.createElement('section', { className: 'iris-gallery-analysis', 'aria-label': '作品改图', 'aria-busy': !!running },
|
|
1795
|
+
artifactPreview(item),
|
|
1796
|
+
React.createElement('div', { className: 'iris-gallery-analysis-body' },
|
|
1797
|
+
React.createElement('div', { className: 'iris-gallery-analysis-head' }, React.createElement('strong', {}, '编辑图片'),
|
|
1798
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: props.onClose }, '关闭改图')),
|
|
1799
|
+
React.createElement('div', { className: 'iris-wb-muted' }, '原图保留,修改版会保存为新作品。此操作会调用生图模型。'),
|
|
1800
|
+
React.createElement('label', {}, '修改指令', React.createElement('textarea', { 'aria-label': '修改指令', value: prompt, disabled: !!running,
|
|
1801
|
+
placeholder: '例如:把蓝色发饰改成白色,保留人物、服装和构图', onChange: function (e) { setPrompt(e.target.value); } })),
|
|
1802
|
+
React.createElement('label', {}, '改图模型', React.createElement('select', { 'aria-label': '改图模型', value: model, disabled: !!running,
|
|
1803
|
+
onChange: function (e) { setModel(e.target.value); } }, React.createElement('option', { value: '' }, '按当前顺序选择可用改图模型'),
|
|
1804
|
+
(models || []).map(function (entry) { return React.createElement('option', { key: entry.id, value: entry.id, disabled: entry.blocked }, entry.label + (entry.blocked ? ' · 冷却或额度耗尽' : '')); }))),
|
|
1805
|
+
models && !models.some(function (entry) { return !entry.blocked; }) ? React.createElement('div', { role: 'status' }, '暂无可用改图模型,请在模型设置中配置聊天生图协议,或手动实测恢复模型。') : null,
|
|
1806
|
+
React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1807
|
+
React.createElement('button', { className: 'iris-clean-btn', type: 'button', disabled: !!running || !prompt.trim() || !models || !models.some(function (entry) { return !entry.blocked; }), onClick: execute }, running ? '编辑中…' : '生成修改版'),
|
|
1808
|
+
running ? React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: cancel }, '停止等待') : null),
|
|
1809
|
+
result ? React.createElement('div', { role: 'status', className: 'iris-clean-note', style: { whiteSpace: 'pre-wrap' } }, result.text || '编辑中…',
|
|
1810
|
+
result.taskId ? React.createElement('div', {}, 'task: ' + result.taskId) : null) : null,
|
|
1811
|
+
result && result.artifactIds ? result.artifactIds.map(function (id) { return React.createElement('div', { key: id },
|
|
1812
|
+
React.createElement('a', { href: '/iris/api/core/artifact/' + encodeURIComponent(id) + '/media', target: '_blank', rel: 'noreferrer' }, '打开新作品:' + id)); }) : null));
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
function ArtifactGallery() {
|
|
1816
|
+
var dataPair = React.useState({ total: 0, offset: 0, limit: 24, items: [], kinds: [], counts: { core: 0, legacy: 0 } });
|
|
1817
|
+
var data = dataPair[0], setData = dataPair[1];
|
|
1818
|
+
var queryPair = React.useState({ offset: 0, media_type: 'all', source: 'all', kind: 'all' });
|
|
1819
|
+
var query = queryPair[0], setQuery = queryPair[1];
|
|
1820
|
+
var currentQuery = React.useRef(query); currentQuery.current = query;
|
|
1821
|
+
var busyPair = React.useState(false), busy = busyPair[0], setBusy = busyPair[1];
|
|
1822
|
+
var mutationPair = React.useState(false), mutating = mutationPair[0], setMutating = mutationPair[1];
|
|
1380
1823
|
var notePair = React.useState('');
|
|
1381
1824
|
var note = notePair[0];
|
|
1382
1825
|
var setNote = notePair[1];
|
|
1826
|
+
var analysisPair = React.useState(null), analysis = analysisPair[0], setAnalysis = analysisPair[1];
|
|
1827
|
+
var selectionPair = React.useState([]), selected = selectionPair[0], setSelected = selectionPair[1];
|
|
1828
|
+
var managementPair = React.useState(null), management = managementPair[0], setManagement = managementPair[1];
|
|
1829
|
+
var operationNotePair = React.useState(''), operationNote = operationNotePair[0], setOperationNote = operationNotePair[1];
|
|
1830
|
+
var request = React.useRef(null), sequence = React.useRef(0);
|
|
1831
|
+
var downloadRequest = React.useRef(null);
|
|
1383
1832
|
|
|
1384
|
-
function load(
|
|
1385
|
-
if (
|
|
1833
|
+
function load() {
|
|
1834
|
+
if (request.current) request.current.abort();
|
|
1835
|
+
var controller = new AbortController(); request.current = controller;
|
|
1386
1836
|
setBusy(true);
|
|
1387
|
-
var
|
|
1388
|
-
var
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
.then(function (res) {
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
var data = values[0];
|
|
1398
|
-
var core = values[1];
|
|
1399
|
-
setItems(append ? items.concat(data.items || []) : (data.items || []));
|
|
1400
|
-
setTotal(Number(data.total || 0));
|
|
1401
|
-
if (core) {
|
|
1402
|
-
var nextCore = coreGalleryItems(core, 200);
|
|
1403
|
-
setCoreItems(nextCore);
|
|
1404
|
-
if (core.error) setNote(core.error);
|
|
1405
|
-
else if (core.backendReloadRequired) setNote('DSH 后端尚未加载 Core 路由;重启后新作品会出现在这里。');
|
|
1406
|
-
}
|
|
1837
|
+
var input = currentQuery.current;
|
|
1838
|
+
var url = '/iris/api/works?offset=' + input.offset + '&limit=24&media_type=' + input.media_type
|
|
1839
|
+
+ '&source=' + input.source + '&kind=' + encodeURIComponent(input.kind);
|
|
1840
|
+
fetch(url, { signal: controller.signal })
|
|
1841
|
+
.then(function (res) { if (!res.ok) throw new Error(res.status === 404 ? '请重新加载 DSH 后端以启用作品管理' : '作品读取失败(HTTP ' + res.status + ')'); return res.json(); })
|
|
1842
|
+
.then(function (value) {
|
|
1843
|
+
if (request.current !== controller || controller.signal.aborted) return;
|
|
1844
|
+
setData(value);
|
|
1845
|
+
setNote(value.coreError ? 'Core 作品暂时不可用,当前计数仅含可读取作品。'
|
|
1846
|
+
: value.droppedCore ? '已跳过 ' + value.droppedCore + ' 条不可读取的 Core 作品。' : '');
|
|
1407
1847
|
})
|
|
1408
|
-
.catch(function (error) {
|
|
1409
|
-
.finally(function () { setBusy(false); });
|
|
1848
|
+
.catch(function (error) { if (request.current === controller && !controller.signal.aborted) setNote(error.message); })
|
|
1849
|
+
.finally(function () { if (request.current === controller) { request.current = null; setBusy(false); } });
|
|
1410
1850
|
}
|
|
1411
1851
|
|
|
1412
1852
|
function run(action, body, confirmText) {
|
|
1413
1853
|
if (confirmText && !window.confirm(confirmText)) return;
|
|
1414
|
-
|
|
1854
|
+
setMutating(true);
|
|
1415
1855
|
postAction(action, body || {}).then(function (result) {
|
|
1416
1856
|
var succeeded = result.ok && result.d && result.d.ok !== false;
|
|
1417
1857
|
setNote(succeeded ? (result.d.text || '完成') : ('失败:' + ((result.d && (result.d.error || result.d.text)) || '未知错误')));
|
|
1418
|
-
if (succeeded)
|
|
1419
|
-
|
|
1420
|
-
|
|
1858
|
+
if (succeeded) {
|
|
1859
|
+
if (action === 'artifacts_clear' || action === 'artifacts_delete') setSelected(function (current) {
|
|
1860
|
+
return current.filter(function (item) { return item.source !== 'legacy' || (action !== 'artifacts_clear' && item.id !== body.artifact_id); });
|
|
1861
|
+
});
|
|
1862
|
+
load();
|
|
1863
|
+
}
|
|
1864
|
+
}).catch(function (error) { setNote('失败:' + error.message); }).finally(function () { setMutating(false); });
|
|
1421
1865
|
}
|
|
1422
1866
|
|
|
1423
1867
|
React.useEffect(function () {
|
|
1424
|
-
function changed() { load(
|
|
1868
|
+
function changed() { load(); }
|
|
1425
1869
|
window.addEventListener('iris-core-artifacts-changed', changed);
|
|
1426
1870
|
window.addEventListener('iris-core-refresh-tick', changed);
|
|
1427
|
-
load(
|
|
1871
|
+
load();
|
|
1428
1872
|
return function () {
|
|
1429
1873
|
window.removeEventListener('iris-core-artifacts-changed', changed);
|
|
1430
1874
|
window.removeEventListener('iris-core-refresh-tick', changed);
|
|
1875
|
+
if (request.current) request.current.abort(); request.current = null;
|
|
1431
1876
|
};
|
|
1432
|
-
}, [
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1877
|
+
}, [query.offset, query.media_type, query.source, query.kind]);
|
|
1878
|
+
React.useEffect(function () { return function () { if (downloadRequest.current) downloadRequest.current.abort(); downloadRequest.current = null; }; }, []);
|
|
1879
|
+
function filter(key, value) {
|
|
1880
|
+
setSelected([]); if (management && management.type === 'delete') setManagement(null);
|
|
1881
|
+
setQuery(function (current) { var next = Object.assign({}, current, { offset: 0 }); next[key] = value; if (key !== 'kind') next.kind = 'all'; return next; });
|
|
1882
|
+
}
|
|
1883
|
+
function page(offset) { setQuery(function (current) { return Object.assign({}, current, { offset: offset }); }); }
|
|
1884
|
+
function analyze(item, action) { setManagement(null); sequence.current++; setAnalysis({ item: item, action: action, key: sequence.current }); }
|
|
1885
|
+
function manage(type, items) { setAnalysis(null); sequence.current++; setManagement({ type: type, items: items, item: items && items[0], key: sequence.current }); }
|
|
1886
|
+
function selectedItem(item) { return selected.some(function (value) { return value.source === item.source && value.id === item.id; }); }
|
|
1887
|
+
function choose(item, checked) {
|
|
1888
|
+
if (checked && selected.length >= 200) { setOperationNote('最多选择 200 个作品'); return; }
|
|
1889
|
+
setSelected(function (current) {
|
|
1890
|
+
var present = current.some(function (value) { return value.source === item.source && value.id === item.id; });
|
|
1891
|
+
return checked ? present || current.length >= 200 ? current : current.concat(item)
|
|
1892
|
+
: current.filter(function (value) { return value.source !== item.source || value.id !== item.id; });
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
var pageSelected = data.items.length && data.items.every(selectedItem);
|
|
1896
|
+
function choosePage() {
|
|
1897
|
+
if (pageSelected) setSelected(function (current) { return current.filter(function (item) { return !data.items.some(function (value) { return value.source === item.source && value.id === item.id; }); }); });
|
|
1898
|
+
else {
|
|
1899
|
+
var additions = data.items.filter(function (item) { return !selectedItem(item); });
|
|
1900
|
+
if (selected.length + additions.length > 200) { setOperationNote('最多选择 200 个作品,请减少选择后再选本页'); return; }
|
|
1901
|
+
setSelected(function (current) {
|
|
1902
|
+
var next = current.concat(additions.filter(function (item) {
|
|
1903
|
+
return !current.some(function (value) { return value.source === item.source && value.id === item.id; });
|
|
1904
|
+
}));
|
|
1905
|
+
return next.length <= 200 ? next : current;
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
function changed(value) {
|
|
1910
|
+
if (value.state === 'committed') setSelected([]);
|
|
1911
|
+
setOperationNote((value.state === 'restored' ? '已恢复:' : '已移入回收区:') + value.transactionId);
|
|
1912
|
+
window.dispatchEvent(new CustomEvent('iris-core-artifacts-changed'));
|
|
1913
|
+
}
|
|
1914
|
+
function download(items) {
|
|
1915
|
+
var controller = new AbortController(); downloadRequest.current = controller;
|
|
1916
|
+
setMutating(true); setOperationNote('正在准备下载…');
|
|
1917
|
+
fetch('/iris/api/works/download', { method: 'POST', headers: { 'Content-Type': 'application/json' }, signal: controller.signal,
|
|
1918
|
+
body: JSON.stringify({ items: items.map(function (item) { return { source: item.source, id: item.id }; }) }) })
|
|
1919
|
+
.then(function (res) { if (!res.ok) return res.json().then(function (value) { throw new Error(value.error || '下载失败'); }); return res.blob(); })
|
|
1920
|
+
.then(function (blob) { if (downloadRequest.current === controller && !controller.signal.aborted) { saveWorkbenchBlob(blob, 'iris-works.zip'); setOperationNote('已下载选中作品'); } })
|
|
1921
|
+
.catch(function (error) { if (downloadRequest.current === controller && !controller.signal.aborted) setOperationNote(error.message); })
|
|
1922
|
+
.finally(function () { if (downloadRequest.current === controller) { downloadRequest.current = null; setMutating(false); } });
|
|
1923
|
+
}
|
|
1924
|
+
function cancelDownload() { if (downloadRequest.current) downloadRequest.current.abort(); downloadRequest.current = null; setMutating(false); setOperationNote('已取消下载'); }
|
|
1925
|
+
var pageNumber = Math.floor(data.offset / data.limit) + 1;
|
|
1926
|
+
var pageCount = Math.max(1, Math.ceil(data.total / data.limit));
|
|
1927
|
+
var blocked = busy || mutating;
|
|
1437
1928
|
|
|
1438
1929
|
return React.createElement('div', {},
|
|
1439
1930
|
React.createElement('div', { className: 'iris-gallery-tools' },
|
|
1440
1931
|
React.createElement('span', { className: 'iris-clean-note' },
|
|
1441
|
-
'
|
|
1442
|
-
React.createElement('button', { className: 'iris-clean-btn', disabled:
|
|
1443
|
-
React.createElement('button', { className: 'iris-clean-btn', disabled:
|
|
1444
|
-
|
|
1445
|
-
|
|
1932
|
+
'匹配 ' + data.total + ' 个作品 · Core ' + data.counts.core + ' · 旧版 ' + data.counts.legacy + ' · 清任务历史不会删除'),
|
|
1933
|
+
React.createElement('button', { className: 'iris-clean-btn', disabled: blocked, onClick: load }, '刷新'),
|
|
1934
|
+
React.createElement('button', { className: 'iris-clean-btn', disabled: mutating, onClick: function () { manage('transactions'); } }, 'Core 回收区'),
|
|
1935
|
+
React.createElement('button', { className: 'iris-clean-btn', disabled: blocked, onClick: function () { run('artifacts_reindex'); } }, '扫描旧版 outputs'),
|
|
1936
|
+
data.counts.legacy ? React.createElement('button', { className: 'iris-clean-btn danger', disabled: blocked, onClick: function () { run('artifacts_clear', {}, '永久删除旧版 outputs 作品?Core 作品不会删除;任务历史会保留。此操作不可逆。'); } }, '清空旧版作品') : null),
|
|
1937
|
+
React.createElement('div', { className: 'iris-gallery-filters' },
|
|
1938
|
+
React.createElement('label', {}, '媒体类型', React.createElement('select', { 'aria-label': '媒体类型', value: query.media_type, disabled: mutating && !!management, onChange: function (e) { filter('media_type', e.target.value); } },
|
|
1939
|
+
[['all', '全部媒体'], ['image', '图片'], ['video', '视频'], ['audio', '音频'], ['text', '文本']].map(function (entry) { return React.createElement('option', { key: entry[0], value: entry[0] }, entry[1]); }))),
|
|
1940
|
+
React.createElement('label', {}, '作品来源', React.createElement('select', { 'aria-label': '作品来源', value: query.source, disabled: mutating && !!management, onChange: function (e) { filter('source', e.target.value); } },
|
|
1941
|
+
[['all', '全部来源'], ['core', 'Core'], ['legacy', '旧版']].map(function (entry) { return React.createElement('option', { key: entry[0], value: entry[0] }, entry[1]); }))),
|
|
1942
|
+
React.createElement('label', {}, '产物类型', React.createElement('select', { 'aria-label': '产物类型', value: query.kind, disabled: mutating && !!management, onChange: function (e) { filter('kind', e.target.value); } },
|
|
1943
|
+
['all'].concat(data.kinds).map(function (kind) { return React.createElement('option', { key: kind, value: kind }, kind === 'all' ? '全部产物' : galleryKindLabel(kind)); })))),
|
|
1944
|
+
React.createElement('div', { className: 'iris-gallery-bulk' },
|
|
1945
|
+
React.createElement('span', { className: 'iris-wb-muted', role: 'status' }, '已选择 ' + selected.length + ' / 200 · 下载最多 128 MiB'),
|
|
1946
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: blocked || !data.items.length, onClick: choosePage }, pageSelected ? '取消本页选择' : '选择本页'),
|
|
1947
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: mutating || !selected.length,
|
|
1948
|
+
onClick: function () { setSelected([]); if (management && management.type === 'delete') setManagement(null); } }, '清空选择'),
|
|
1949
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: !selected.length,
|
|
1950
|
+
onClick: function () { copyWorkbenchText(selected.map(function (item) { return item.id; }).join('\n'), setOperationNote); } }, '复制选中 ID'),
|
|
1951
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: mutating || !selected.length, onClick: function () { download(selected); } }, '下载选中作品'),
|
|
1952
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn danger', disabled: mutating || !selected.length || selected.some(function (item) { return item.source !== 'core'; }),
|
|
1953
|
+
title: '仅 Core 作品支持可恢复批量删除;请先按来源筛选', onClick: function () { manage('delete', selected); } }, '删除选中 Core 作品'),
|
|
1954
|
+
downloadRequest.current ? React.createElement('button', { type: 'button', className: 'iris-pm-btn', onClick: cancelDownload }, '取消下载') : null),
|
|
1955
|
+
analysis ? React.createElement(analysis.action === 'image_edit' ? ArtifactImageEdit : ArtifactAnalysis, { key: analysis.key, item: analysis.item, action: analysis.action, onClose: function () { setAnalysis(null); } }) : null,
|
|
1956
|
+
management ? React.createElement(WorkbenchManagement, { key: management.key, type: management.type, items: management.items, item: management.item,
|
|
1957
|
+
onBusy: setMutating, onChanged: changed, onClose: function () { setManagement(null); } }) : null,
|
|
1958
|
+
data.items.length ? React.createElement('div', { className: 'iris-gallery-grid', 'aria-busy': busy }, data.items.map(function (item) {
|
|
1446
1959
|
var isCore = item.source === 'core';
|
|
1447
|
-
return React.createElement('div', { key: (isCore ? 'core-' : 'legacy-') + item.id, className: 'iris-gallery-card' },
|
|
1960
|
+
return React.createElement('div', { key: (isCore ? 'core-' : 'legacy-') + item.id, className: 'iris-gallery-card' + (selectedItem(item) ? ' selected' : '') },
|
|
1448
1961
|
artifactPreview(item),
|
|
1449
1962
|
React.createElement('div', { className: 'iris-gallery-info' },
|
|
1963
|
+
React.createElement('label', { className: 'iris-gallery-select' }, React.createElement('input', { type: 'checkbox',
|
|
1964
|
+
'aria-label': '选择作品 ' + item.id, checked: selectedItem(item), disabled: mutating || (!selectedItem(item) && selected.length >= 200),
|
|
1965
|
+
onChange: function (event) { choose(item, event.target.checked); } }), '选择'),
|
|
1450
1966
|
React.createElement('div', { className: 'iris-gallery-file', title: item.file }, item.file),
|
|
1451
1967
|
React.createElement('div', { className: 'iris-gallery-meta' },
|
|
1452
|
-
(isCore ? 'Core · ' + (item.kind
|
|
1968
|
+
(isCore ? 'Core · ' + galleryKindLabel(item.kind) : '旧版') + ' · ' + fmtTime(item.createdAt) + ' · ' + fmtBytes(item.size)),
|
|
1453
1969
|
React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1454
1970
|
React.createElement('a', { className: 'iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开'),
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1971
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', disabled: mutating, onClick: function () { manage('details', [item]); } }, '详情'),
|
|
1972
|
+
isCore ? React.createElement('button', { className: 'iris-pm-btn danger', type: 'button', disabled: mutating, onClick: function () { manage('delete', [item]); } }, '移入回收区') : null,
|
|
1973
|
+
isCore && /^image\/(png|jpeg|webp|gif)$/.test(item.mime) ? ['look', 'ocr'].map(function (action) {
|
|
1974
|
+
return React.createElement('button', { key: action, type: 'button', className: 'iris-pm-btn', disabled: mutating, onClick: function () { analyze(item, action); } }, action === 'look' ? '看图' : 'OCR');
|
|
1975
|
+
}) : null,
|
|
1976
|
+
isCore && /^image\/(png|jpeg|webp)$/.test(item.mime) ? React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: mutating,
|
|
1977
|
+
onClick: function () { analyze(item, 'image_edit'); } }, '改图') : null,
|
|
1978
|
+
isCore ? React.createElement('span', { className: 'iris-wb-muted', title: '打开或识别时核验原文件' }, '内容哈希保护')
|
|
1979
|
+
: React.createElement('button', { className: 'iris-pm-btn danger', disabled: blocked, onClick: function () { run('artifacts_delete', { artifact_id: item.id }, '永久删除作品“' + item.file + '”?任务历史会保留,但媒体将无法打开。'); } }, '删除'))));
|
|
1980
|
+
})) : React.createElement('div', { className: 'iris-wb-empty' }, busy ? '正在读取作品…'
|
|
1981
|
+
: query.media_type !== 'all' || query.source !== 'all' || query.kind !== 'all' ? '没有匹配的作品,请调整筛选条件。' : '尚无作品;旧 outputs 文件可点“扫描旧版 outputs”找回。'),
|
|
1982
|
+
React.createElement('div', { className: 'iris-gallery-pagination' },
|
|
1983
|
+
React.createElement('button', { type: 'button', className: 'iris-clean-btn', disabled: blocked || pageNumber <= 1, onClick: function () { page(Math.max(0, data.offset - data.limit)); } }, '上一页'),
|
|
1984
|
+
React.createElement('span', { className: 'iris-wb-muted', role: 'status' }, '第 ' + pageNumber + ' / ' + pageCount + ' 页'),
|
|
1985
|
+
React.createElement('button', { type: 'button', className: 'iris-clean-btn', disabled: blocked || pageNumber >= pageCount, onClick: function () { page(data.offset + data.limit); } }, '下一页')),
|
|
1986
|
+
note ? React.createElement('div', { className: 'iris-clean-note', style: { marginTop: '7px' } }, note) : null,
|
|
1987
|
+
operationNote ? React.createElement('div', { role: 'status', className: 'iris-clean-note', style: { marginTop: '7px', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, operationNote) : null);
|
|
1460
1988
|
}
|
|
1461
1989
|
|
|
1462
1990
|
function coreCapabilityLabel(value) {
|
|
@@ -1669,9 +2197,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1669
2197
|
if (draft === null) return null;
|
|
1670
2198
|
draft = String(draft).trim();
|
|
1671
2199
|
if (!draft) return null;
|
|
1672
|
-
|
|
2200
|
+
var sourceArtifactId;
|
|
2201
|
+
if (props.row.sourceArtifactId) {
|
|
2202
|
+
sourceArtifactId = window.prompt('请确认新改图任务使用的原图 Artifact ID:', props.row.sourceArtifactId);
|
|
2203
|
+
if (sourceArtifactId === null || !String(sourceArtifactId).trim()) return null;
|
|
2204
|
+
}
|
|
2205
|
+
return isTts ? { text: draft, confirmBilling: true }
|
|
1673
2206
|
: isTranscribe ? { audio_url: draft, confirmBilling: true }
|
|
1674
|
-
: { prompt: draft, confirmBilling: true };
|
|
2207
|
+
: { prompt: draft, confirmBilling: true, ...(sourceArtifactId ? { source_artifact_id: String(sourceArtifactId).trim() } : {}) };
|
|
1675
2208
|
}
|
|
1676
2209
|
});
|
|
1677
2210
|
}
|
|
@@ -1918,7 +2451,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1918
2451
|
React.createElement(CoreRetryButton, {
|
|
1919
2452
|
row: selectedTask && selectedTask.phase === 'terminal'
|
|
1920
2453
|
&& !(selectedTask.outcome === 'succeeded' && selectedTask.deliveryState === 'ready')
|
|
1921
|
-
? { id: selectedTask.id, retryable: true } : null
|
|
2454
|
+
? { id: selectedTask.id, capability: selectedTask.capability, sourceArtifactId: selectedTask.sourceArtifactId, retryable: true } : null
|
|
1922
2455
|
}),
|
|
1923
2456
|
React.createElement(CoreDispositionButtons, {
|
|
1924
2457
|
row: selectedDisposition
|
|
@@ -2105,7 +2638,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2105
2638
|
React.createElement(ProviderManager, {}),
|
|
2106
2639
|
React.createElement('div', { className: 'iris-wb-sec' }, '能力健康'),
|
|
2107
2640
|
React.createElement(CapabilityHealthOverview, {}),
|
|
2108
|
-
React.createElement('div', { className: 'iris-wb-muted' }, '绿色有效 7
|
|
2641
|
+
React.createElement('div', { className: 'iris-wb-muted' }, '绿色有效 7 天;真实成功或显式实测会刷新。429 冷却显示琥珀色,额度或预算耗尽显示红色;点击模型能力可实测恢复。不会后台探测。'),
|
|
2109
2642
|
React.createElement('div', { className: 'iris-wb-sec' }, '能力分配(failover 顺序)'),
|
|
2110
2643
|
React.createElement(CapabilityAssigner, {}),
|
|
2111
2644
|
React.createElement('div', { className: 'iris-wb-sec' }, 'Iris 泡泡快捷卡片(勾选后显示在「⚡ 常用」标签)'),
|
|
@@ -2205,6 +2738,25 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2205
2738
|
selectedTask ? React.createElement(TaskDetailDrawer, { taskId: selectedTask }) : null);
|
|
2206
2739
|
}
|
|
2207
2740
|
|
|
2741
|
+
var PROMPT_TARGET_OPTIONS = [
|
|
2742
|
+
{ value: 'general', label: '通用', description: '整理目标、背景、约束与输出要求。' },
|
|
2743
|
+
{ value: 'image', label: '画图', description: '整理已有主体、场景与构图要求,未指定细节不自动补齐。' },
|
|
2744
|
+
{ value: 'image-edit', label: '改图', description: '明确要修改的部分,保留原图其余特征。' },
|
|
2745
|
+
{ value: 'video', label: '视频', description: '组织动作、镜头与时间上的连续性。' },
|
|
2746
|
+
{ value: 's2v', label: '数字人', description: '围绕已有首帧与音频设计表情、动作和镜头。' }
|
|
2747
|
+
];
|
|
2748
|
+
|
|
2749
|
+
// 折叠相同首尾,只显示中间替换区;按 Unicode code point 比较,不拆开 emoji。
|
|
2750
|
+
function promptChanges(original, edited) {
|
|
2751
|
+
var before = Array.from(original);
|
|
2752
|
+
var after = Array.from(edited);
|
|
2753
|
+
var start = 0;
|
|
2754
|
+
while (start < before.length && start < after.length && before[start] === after[start]) start++;
|
|
2755
|
+
var end = 0;
|
|
2756
|
+
while (end < before.length - start && end < after.length - start && before[before.length - end - 1] === after[after.length - end - 1]) end++;
|
|
2757
|
+
return { removed: before.slice(start, before.length - end).join(''), added: after.slice(start, after.length - end).join('') };
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2208
2760
|
function PromptOptimizerControl(props) {
|
|
2209
2761
|
var draft = props.useInput(function (value) { return value.draft; });
|
|
2210
2762
|
var phase = props.useInput(function (value) { return value.phase; });
|
|
@@ -2215,6 +2767,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2215
2767
|
var openPair = React.useState(false);
|
|
2216
2768
|
var open = openPair[0];
|
|
2217
2769
|
var setOpen = openPair[1];
|
|
2770
|
+
var generalPair = React.useState(true);
|
|
2771
|
+
var generalOpen = generalPair[0];
|
|
2218
2772
|
var targetPair = React.useState('general');
|
|
2219
2773
|
var target = targetPair[0];
|
|
2220
2774
|
var setTarget = targetPair[1];
|
|
@@ -2230,8 +2784,49 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2230
2784
|
var configPair = React.useState(null);
|
|
2231
2785
|
var configState = configPair[0];
|
|
2232
2786
|
var setConfigState = configPair[1];
|
|
2787
|
+
var optimizationPair = React.useState('');
|
|
2788
|
+
var optimizationText = optimizationPair[0];
|
|
2789
|
+
var setOptimizationText = optimizationPair[1];
|
|
2790
|
+
var prefixPair = React.useState('');
|
|
2791
|
+
var prefixText = prefixPair[0];
|
|
2792
|
+
var setPrefixText = prefixPair[1];
|
|
2793
|
+
var suffixPair = React.useState('');
|
|
2794
|
+
var suffixText = suffixPair[0];
|
|
2795
|
+
var setSuffixText = suffixPair[1];
|
|
2796
|
+
var editedPair = React.useState('');
|
|
2797
|
+
var edited = editedPair[0];
|
|
2798
|
+
var setEdited = editedPair[1];
|
|
2799
|
+
var editingPair = React.useState('faithful');
|
|
2800
|
+
var editing = editingPair[0];
|
|
2801
|
+
var suggestionsPair = React.useState(null);
|
|
2802
|
+
var suggestions = suggestionsPair[0];
|
|
2803
|
+
var setSuggestions = suggestionsPair[1];
|
|
2804
|
+
var selectedSuggestionsPair = React.useState([]);
|
|
2805
|
+
var selectedSuggestions = selectedSuggestionsPair[0];
|
|
2806
|
+
var setSelectedSuggestions = selectedSuggestionsPair[1];
|
|
2807
|
+
var undoPair = React.useState([]);
|
|
2808
|
+
var suggestionUndo = undoPair[0];
|
|
2809
|
+
var setSuggestionUndo = undoPair[1];
|
|
2810
|
+
var editedRef = React.useRef(edited);
|
|
2811
|
+
var resultRef = React.useRef(result);
|
|
2812
|
+
editedRef.current = edited;
|
|
2813
|
+
resultRef.current = result;
|
|
2233
2814
|
var abortRef = React.useRef(null);
|
|
2234
2815
|
var fileRef = React.useRef(null);
|
|
2816
|
+
var panelRef = React.useRef(null);
|
|
2817
|
+
var lastWrittenRef = React.useRef(null);
|
|
2818
|
+
var resultSessionRef = React.useRef(null);
|
|
2819
|
+
var sessionRef = React.useRef(props.sessionId);
|
|
2820
|
+
sessionRef.current = props.sessionId;
|
|
2821
|
+
var supportsRules = !!(configState && configState.capabilities && configState.capabilities.rules === true);
|
|
2822
|
+
var supportsAssembly = !!(configState && configState.capabilities && configState.capabilities.assemble === true);
|
|
2823
|
+
var editingCapabilities = configState && configState.capabilities && configState.capabilities.editing;
|
|
2824
|
+
var supportsFaithful = Array.isArray(editingCapabilities) && editingCapabilities.indexOf('faithful') >= 0 && editingCapabilities.indexOf('rewrite') >= 0;
|
|
2825
|
+
var supportsSuggestions = Array.isArray(editingCapabilities) && editingCapabilities.indexOf('suggestions') >= 0;
|
|
2826
|
+
function supportsTarget(value) {
|
|
2827
|
+
var available = configState && configState.capabilities && configState.capabilities.targets;
|
|
2828
|
+
return Array.isArray(available) ? available.indexOf(value) >= 0 : value !== 'image-edit';
|
|
2829
|
+
}
|
|
2235
2830
|
|
|
2236
2831
|
function promptRequest(operation, body, signal) {
|
|
2237
2832
|
return fetch('/iris/api/prompt-optimizer/' + operation, {
|
|
@@ -2257,6 +2852,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2257
2852
|
React.useEffect(function () {
|
|
2258
2853
|
return function () {
|
|
2259
2854
|
if (abortRef.current) abortRef.current.abort();
|
|
2855
|
+
abortRef.current = null;
|
|
2260
2856
|
};
|
|
2261
2857
|
}, []);
|
|
2262
2858
|
|
|
@@ -2267,53 +2863,193 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2267
2863
|
return function () { window.removeEventListener('iris-prompt-config-changed', changed); };
|
|
2268
2864
|
}, []);
|
|
2269
2865
|
|
|
2866
|
+
React.useEffect(function () {
|
|
2867
|
+
cancel();
|
|
2868
|
+
setResult(null);
|
|
2869
|
+
setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
|
|
2870
|
+
lastWrittenRef.current = null;
|
|
2871
|
+
resultSessionRef.current = null;
|
|
2872
|
+
}, [props.sessionId]);
|
|
2873
|
+
|
|
2874
|
+
React.useEffect(function () {
|
|
2875
|
+
if (!open) return;
|
|
2876
|
+
var previous = document.activeElement;
|
|
2877
|
+
var dialog = panelRef.current;
|
|
2878
|
+
if (dialog) { dialog.showModal(); dialog.focus(); }
|
|
2879
|
+
function keyboard(event) {
|
|
2880
|
+
if (event.key === 'Escape') { event.preventDefault(); closePanel(); }
|
|
2881
|
+
if (event.key !== 'Tab' || !panelRef.current) return;
|
|
2882
|
+
var items = Array.from(panelRef.current.querySelectorAll('button:not(:disabled), textarea:not(:disabled), summary, input:not(:disabled)'))
|
|
2883
|
+
.filter(function (item) { return item.getClientRects().length > 0; });
|
|
2884
|
+
if (!items.length) return;
|
|
2885
|
+
var first = items[0], last = items[items.length - 1];
|
|
2886
|
+
if (event.shiftKey && (document.activeElement === first || document.activeElement === panelRef.current)) {
|
|
2887
|
+
event.preventDefault(); last.focus();
|
|
2888
|
+
} else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
|
|
2889
|
+
}
|
|
2890
|
+
window.addEventListener('keydown', keyboard);
|
|
2891
|
+
return function () {
|
|
2892
|
+
window.removeEventListener('keydown', keyboard);
|
|
2893
|
+
if (dialog && dialog.open) dialog.close();
|
|
2894
|
+
if (previous && previous.isConnected) previous.focus();
|
|
2895
|
+
};
|
|
2896
|
+
}, [open]);
|
|
2897
|
+
|
|
2270
2898
|
function publishConfig(data) {
|
|
2271
2899
|
setConfigState(data);
|
|
2272
2900
|
try { window.dispatchEvent(new CustomEvent('iris-prompt-config-changed', { detail: data })); } catch (_) {}
|
|
2273
2901
|
}
|
|
2274
2902
|
|
|
2275
|
-
function
|
|
2903
|
+
function selectedRules(mode) {
|
|
2904
|
+
var rules = [];
|
|
2905
|
+
if (!supportsRules) return rules;
|
|
2906
|
+
if (mode === 'optimize' && optimizationText.trim()) rules.push({ id: 'custom-optimization', label: '本次改写规则', kind: 'optimization', text: optimizationText });
|
|
2907
|
+
if (prefixText.trim()) rules.push({ id: 'custom-prefix', label: '输出前缀', kind: 'output', position: 'prefix', text: prefixText });
|
|
2908
|
+
if (suffixText.trim()) rules.push({ id: 'custom-suffix', label: '输出后缀', kind: 'output', position: 'suffix', text: suffixText });
|
|
2909
|
+
return rules;
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
function optimize(mode) {
|
|
2913
|
+
mode = mode === 'assemble' ? 'assemble' : 'optimize';
|
|
2914
|
+
if (!supportsTarget(target)) { setNote('当前后端尚未支持此优化目标,请更新并重新载入'); return; }
|
|
2915
|
+
if (mode === 'assemble' && !supportsAssembly) { setNote('当前后端尚未支持只组装,请更新并重新载入'); return; }
|
|
2276
2916
|
if (!draft || !draft.trim()) { setNote('请先在输入框中写入提示词'); return; }
|
|
2277
2917
|
if (refCount) { setNote('当前版本暂不改写含 @ 或 / 结构化引用的草稿,以免引用失效'); return; }
|
|
2278
2918
|
if (phase !== 'plain') { setNote('输入框正忙,请稍后再试'); return; }
|
|
2919
|
+
if (mode === 'optimize' && supportsFaithful && editing === 'faithful' && optimizationText.trim()) {
|
|
2920
|
+
setNote('忠实整理只调整分段;使用本次改写规则前,请选择“改写表达”。'); return;
|
|
2921
|
+
}
|
|
2279
2922
|
if (abortRef.current) abortRef.current.abort();
|
|
2280
2923
|
var controller = new AbortController();
|
|
2924
|
+
var requestSessionId = props.sessionId;
|
|
2281
2925
|
abortRef.current = controller;
|
|
2282
2926
|
setBusy(true);
|
|
2283
|
-
setNote('正在调用模型优化…这可能产生模型费用');
|
|
2927
|
+
setNote(mode === 'assemble' ? '正在组装,不调用模型…' : '正在调用模型优化…这可能产生模型费用');
|
|
2284
2928
|
setResult(null);
|
|
2929
|
+
setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
|
|
2930
|
+
lastWrittenRef.current = null;
|
|
2285
2931
|
promptRequest('optimize', {
|
|
2286
2932
|
text: draft,
|
|
2287
2933
|
target: target,
|
|
2934
|
+
mode: mode,
|
|
2935
|
+
editing: mode === 'optimize' && supportsFaithful ? editing : undefined,
|
|
2936
|
+
rules: selectedRules(mode),
|
|
2288
2937
|
sessionId: props.sessionId,
|
|
2289
2938
|
route: selectedRoute || undefined
|
|
2290
2939
|
}, controller.signal).then(function (data) {
|
|
2940
|
+
if (abortRef.current !== controller || controller.signal.aborted) return;
|
|
2941
|
+
if (sessionRef.current !== requestSessionId) throw new Error('会话已切换,请在当前会话重新生成预览');
|
|
2942
|
+
if (!data.ok || typeof data.optimized !== 'string' || typeof data.original !== 'string') throw new Error('未收到完整优化结果');
|
|
2943
|
+
if (data.original !== draft) throw new Error('结果原稿与本次请求不一致');
|
|
2944
|
+
if (mode === 'optimize' && supportsFaithful && editing === 'faithful'
|
|
2945
|
+
&& (data.editing?.strategy !== 'faithful' || data.editing.coverage !== 'exact' || data.editing.order !== 'preserved')) throw new Error('后端未确认完整的忠实整理结果');
|
|
2291
2946
|
setResult(data);
|
|
2947
|
+
setEdited(data.optimized);
|
|
2948
|
+
resultSessionRef.current = requestSessionId;
|
|
2292
2949
|
setNote('已生成预览,尚未写回输入框');
|
|
2293
2950
|
}).catch(function (err) {
|
|
2951
|
+
if (abortRef.current !== controller) return;
|
|
2294
2952
|
if (controller.signal.aborted) setNote('已取消');
|
|
2295
2953
|
else setNote(String(err && err.message || err));
|
|
2296
2954
|
}).finally(function () {
|
|
2297
|
-
if (abortRef.current === controller) abortRef.current = null;
|
|
2298
|
-
setBusy(false);
|
|
2955
|
+
if (abortRef.current === controller) { abortRef.current = null; setBusy(false); }
|
|
2299
2956
|
});
|
|
2300
2957
|
}
|
|
2301
2958
|
|
|
2959
|
+
function requestSuggestions() {
|
|
2960
|
+
if (!supportsSuggestions || !result || busy || !validEdited()) return;
|
|
2961
|
+
if (resultSessionRef.current !== props.sessionId) { setNote('会话已切换,请重新生成预览'); return; }
|
|
2962
|
+
var snapshot = edited;
|
|
2963
|
+
var previousResult = result;
|
|
2964
|
+
var requestSessionId = props.sessionId;
|
|
2965
|
+
var controller = new AbortController();
|
|
2966
|
+
abortRef.current = controller;
|
|
2967
|
+
setBusy(true); setNote('正在获取可选建议,正文保持原样;这会调用文本模型。');
|
|
2968
|
+
promptRequest('optimize', { text: snapshot, target: result.target, mode: 'optimize', editing: 'suggestions', rules: [],
|
|
2969
|
+
sessionId: props.sessionId, route: selectedRoute || undefined }, controller.signal).then(function (data) {
|
|
2970
|
+
if (abortRef.current !== controller || controller.signal.aborted) return;
|
|
2971
|
+
if (sessionRef.current !== requestSessionId || resultRef.current !== previousResult || editedRef.current !== snapshot) {
|
|
2972
|
+
setNote('预览已变化,本次建议未载入;请针对当前文字重新获取。'); return;
|
|
2973
|
+
}
|
|
2974
|
+
var ids = [];
|
|
2975
|
+
if (!data.ok || data.original !== snapshot || data.optimized !== snapshot || data.target !== previousResult.target || data.editing?.strategy !== 'suggestions'
|
|
2976
|
+
|| !Array.isArray(data.suggestions) || data.suggestions.length > 6 || !Array.isArray(data.questions) || data.questions.length > 6
|
|
2977
|
+
|| !data.suggestions.every(function (item) {
|
|
2978
|
+
if (!item || typeof item.id !== 'string' || !/^suggestion-[1-6]$/.test(item.id) || ids.indexOf(item.id) >= 0
|
|
2979
|
+
|| typeof item.label !== 'string' || !item.label.trim() || item.label.length > 80
|
|
2980
|
+
|| typeof item.text !== 'string' || !item.text.trim() || item.text.length > 600) return false;
|
|
2981
|
+
ids.push(item.id); return true;
|
|
2982
|
+
}) || !data.questions.every(function (text) { return typeof text === 'string' && text.trim() && text.length <= 600; })) throw new Error('未收到完整补充建议');
|
|
2983
|
+
setSuggestions({ source: snapshot, items: data.suggestions, questions: data.questions, adopted: [] });
|
|
2984
|
+
setSelectedSuggestions([]); setSuggestionUndo([]);
|
|
2985
|
+
setNote('建议尚未加入正文;只有采纳所选建议才会改变预览。');
|
|
2986
|
+
}).catch(function (err) {
|
|
2987
|
+
if (abortRef.current !== controller) return;
|
|
2988
|
+
setNote(controller.signal.aborted ? '已取消,原有预览保留' : String(err && err.message || err));
|
|
2989
|
+
}).finally(function () {
|
|
2990
|
+
if (abortRef.current === controller) { abortRef.current = null; setBusy(false); }
|
|
2991
|
+
});
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
function adoptSuggestions() {
|
|
2995
|
+
if (!suggestions || busy || !selectedSuggestions.length) return;
|
|
2996
|
+
var items = suggestions.items.filter(function (item) { return selectedSuggestions.indexOf(item.id) >= 0 && suggestions.adopted.indexOf(item.id) < 0; });
|
|
2997
|
+
if (!items.length) return;
|
|
2998
|
+
var next = edited + '\n\n' + items.map(function (item) { return item.text; }).join('\n\n');
|
|
2999
|
+
if (next.length > 16000) { setNote('采纳后将超过 16000 字符,请减少建议或手动编辑'); return; }
|
|
3000
|
+
var ids = items.map(function (item) { return item.id; });
|
|
3001
|
+
setSuggestionUndo(suggestionUndo.concat([{ before: edited, after: next, ids: ids }]));
|
|
3002
|
+
setSuggestions(Object.assign({}, suggestions, { adopted: suggestions.adopted.concat(ids) }));
|
|
3003
|
+
setEdited(next); editedRef.current = next; setSelectedSuggestions([]);
|
|
3004
|
+
setNote('已将所选建议加入预览,尚未写回;可以撤回上次采纳。');
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
function undoSuggestions() {
|
|
3008
|
+
var last = suggestionUndo[suggestionUndo.length - 1];
|
|
3009
|
+
if (!last || busy) return;
|
|
3010
|
+
if (edited !== last.after) { setNote('预览已被手动修改;请手动移除建议,撤回不会覆盖你的编辑。'); return; }
|
|
3011
|
+
setEdited(last.before); editedRef.current = last.before;
|
|
3012
|
+
setSuggestionUndo(suggestionUndo.slice(0, -1));
|
|
3013
|
+
if (suggestions) setSuggestions(Object.assign({}, suggestions, { adopted: suggestions.adopted.filter(function (id) { return last.ids.indexOf(id) < 0; }) }));
|
|
3014
|
+
setNote('已撤回上次采纳,尚未写回输入框。');
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
function editPreview(value) {
|
|
3018
|
+
setEdited(value); editedRef.current = value;
|
|
3019
|
+
setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
|
|
3020
|
+
}
|
|
3021
|
+
|
|
2302
3022
|
function cancel() {
|
|
2303
|
-
if (abortRef.current)
|
|
3023
|
+
if (abortRef.current) {
|
|
3024
|
+
abortRef.current.abort();
|
|
3025
|
+
abortRef.current = null;
|
|
3026
|
+
setBusy(false);
|
|
3027
|
+
setNote('已取消');
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
function closePanel() { cancel(); setOpen(false); }
|
|
3032
|
+
|
|
3033
|
+
function validEdited() {
|
|
3034
|
+
if (!edited.trim() || edited.length > 16000) { setNote('结果必须是非空文本且不超过 16000 字符'); return false; }
|
|
3035
|
+
return true;
|
|
2304
3036
|
}
|
|
2305
3037
|
|
|
2306
3038
|
function replaceDraft(text) {
|
|
2307
3039
|
if (!result) return;
|
|
2308
|
-
if (
|
|
3040
|
+
if (resultSessionRef.current !== props.sessionId) { setNote('会话已切换,请在当前会话重新生成预览'); return; }
|
|
3041
|
+
if (refCount || phase !== 'plain') { setNote('当前输入框正忙或含结构化引用,暂不写回'); return; }
|
|
3042
|
+
if (text !== result.original && !validEdited()) return;
|
|
3043
|
+
if (draft !== result.original && draft !== lastWrittenRef.current && !window.confirm('输入框内容在优化期间已变化,仍要替换吗?')) return;
|
|
2309
3044
|
props.inputActions.setDraft(text);
|
|
3045
|
+
lastWrittenRef.current = text;
|
|
2310
3046
|
setNote(text === result.original ? '已恢复优化前原文' : '已写回输入框;不会自动发送');
|
|
2311
3047
|
}
|
|
2312
3048
|
|
|
2313
3049
|
function copyResult() {
|
|
2314
|
-
if (!result) return;
|
|
3050
|
+
if (!result || !validEdited()) return;
|
|
2315
3051
|
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
2316
|
-
navigator.clipboard.writeText(
|
|
3052
|
+
navigator.clipboard.writeText(edited).then(function () { setNote('已复制优化结果'); }).catch(function () { setNote('复制失败,请手动选择文本'); });
|
|
2317
3053
|
} else {
|
|
2318
3054
|
setNote('浏览器不支持自动复制,请手动选择文本');
|
|
2319
3055
|
}
|
|
@@ -2379,39 +3115,117 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2379
3115
|
var reasoningSetting = configState && configState.config && configState.config.generation && configState.config.generation.reasoningEffort || 'off-if-supported';
|
|
2380
3116
|
var reasoningLabel = reasoningSetting === 'off-if-supported' ? '支持时关闭' : reasoningSetting === 'provider-default' ? '供应商默认' : reasoningSetting === 'inherit' ? '跟随会话' : reasoningSetting;
|
|
2381
3117
|
var outputBudget = configState && configState.config && configState.config.generation && configState.config.generation.maxOutputTokens || 1200;
|
|
3118
|
+
var changes = result ? promptChanges(result.original, edited) : null;
|
|
3119
|
+
var targetInfo = PROMPT_TARGET_OPTIONS.find(function (item) { return item.value === target; }) || PROMPT_TARGET_OPTIONS[0];
|
|
3120
|
+
var resultTarget = result && PROMPT_TARGET_OPTIONS.find(function (item) { return item.value === result.target; });
|
|
3121
|
+
var config = configState && configState.config;
|
|
3122
|
+
var targetTemplate = config && config.targets && config.targets[target];
|
|
3123
|
+
function ruleField(label, value, update, placeholder) {
|
|
3124
|
+
return React.createElement('label', { className: 'iris-po-field' }, React.createElement('span', {}, label),
|
|
3125
|
+
React.createElement('textarea', { value: value, disabled: busy || !supportsRules, placeholder: placeholder,
|
|
3126
|
+
onChange: function (event) { update(event.target.value); } }));
|
|
3127
|
+
}
|
|
2382
3128
|
|
|
2383
3129
|
var panel = null;
|
|
2384
3130
|
if (open) {
|
|
2385
3131
|
panel = React.createElement(React.Fragment, {},
|
|
2386
|
-
React.createElement('
|
|
2387
|
-
|
|
3132
|
+
React.createElement('dialog', { ref: panelRef, tabIndex: -1, className: 'iris-po-panel', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Iris 提示词优化',
|
|
3133
|
+
onCancel: function (event) { event.preventDefault(); closePanel(); },
|
|
3134
|
+
onClick: function (event) {
|
|
3135
|
+
event.stopPropagation();
|
|
3136
|
+
if (event.target !== event.currentTarget) return;
|
|
3137
|
+
var bounds = event.currentTarget.getBoundingClientRect();
|
|
3138
|
+
if (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom) closePanel();
|
|
3139
|
+
} },
|
|
2388
3140
|
React.createElement('div', { className: 'iris-po-head' },
|
|
2389
3141
|
React.createElement('strong', { className: 'iris-po-title' }, React.createElement('span', { className: 'iris-po-mark' }, '🫧'), 'Iris 提示词优化'),
|
|
2390
|
-
React.createElement('button', { className: 'iris-po-close', onClick:
|
|
2391
|
-
React.createElement('div', { className: 'iris-po-
|
|
2392
|
-
|
|
2393
|
-
React.createElement('
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
React.createElement('
|
|
2399
|
-
|
|
3142
|
+
React.createElement('button', { className: 'iris-po-close', onClick: closePanel, title: '关闭', 'aria-label': '关闭提示词优化' }, '✕')),
|
|
3143
|
+
React.createElement('div', { className: 'iris-po-intro' },
|
|
3144
|
+
React.createElement('div', { className: 'iris-po-muted' }, '按用途整理草稿,先预览和编辑,再决定是否写回。'),
|
|
3145
|
+
React.createElement('div', { className: 'iris-po-principles' },
|
|
3146
|
+
React.createElement('span', {}, '保留原意'), React.createElement('span', {}, '可查看改动'), React.createElement('span', {}, '确认后写回'))),
|
|
3147
|
+
React.createElement('details', { className: 'iris-po-section', open: generalOpen,
|
|
3148
|
+
onToggle: function (event) { generalPair[1](event.currentTarget.open); } },
|
|
3149
|
+
React.createElement('summary', {}, '一般功能',
|
|
3150
|
+
React.createElement('span', { className: 'iris-po-section-caption' }, targetInfo.label + ' · 提示词优化' + (result ? ' · 有预览' : ''))),
|
|
3151
|
+
React.createElement('div', { className: 'iris-po-section-body' },
|
|
3152
|
+
React.createElement('div', { className: 'iris-po-targets', role: 'group', 'aria-label': '优化目标' },
|
|
3153
|
+
PROMPT_TARGET_OPTIONS.map(function (item) {
|
|
3154
|
+
return React.createElement('button', { key: item.value, type: 'button', className: 'iris-po-target',
|
|
3155
|
+
'aria-pressed': target === item.value, value: item.value,
|
|
3156
|
+
disabled: busy || !supportsTarget(item.value), title: supportsTarget(item.value) ? item.description : '当前后端未提供此目标',
|
|
3157
|
+
onClick: function () { setTarget(item.value); } }, item.label);
|
|
3158
|
+
})),
|
|
3159
|
+
React.createElement('div', { className: 'iris-po-muted' }, targetInfo.description),
|
|
3160
|
+
supportsFaithful ? React.createElement('div', { className: 'iris-po-editing', role: 'group', 'aria-label': '编辑方式' },
|
|
3161
|
+
['faithful', 'rewrite'].map(function (strategy) {
|
|
3162
|
+
return React.createElement('label', { key: strategy }, React.createElement('input', { type: 'radio', name: 'iris-prompt-editing',
|
|
3163
|
+
value: strategy, checked: editing === strategy, disabled: busy, onChange: function () { editingPair[1](strategy); } }), strategy === 'faithful' ? '忠实整理(默认)' : '改写表达');
|
|
3164
|
+
}),
|
|
3165
|
+
React.createElement('div', { className: 'iris-po-muted' }, editing === 'faithful' ? '保持原文和句序,只调整分组与分段;不补充细节。' : '按本次规则改写,结果需逐项核对;未授权内容保持原意。')) : null,
|
|
3166
|
+
React.createElement('div', { className: 'iris-po-source' },
|
|
3167
|
+
React.createElement('div', { className: 'iris-po-source-head' }, '当前草稿', React.createElement('span', {}, (draft || '').length + ' 字符')),
|
|
3168
|
+
React.createElement('p', { className: 'iris-po-source-text' }, draft || '先在对话输入框写下你的想法。')),
|
|
3169
|
+
React.createElement('details', { className: 'iris-po-config' },
|
|
3170
|
+
React.createElement('summary', {}, '本次规则(可选)'),
|
|
3171
|
+
React.createElement('div', { className: 'iris-po-rules' },
|
|
3172
|
+
ruleField('改写规则 · 仅智能优化使用', optimizationText, setOptimizationText, '例如:保持简短,保留全部否定条件'),
|
|
3173
|
+
ruleField('输出前缀 · 原样加入结果开头', prefixText, setPrefixText, '例如:请以摄影师的视角描述以下画面。'),
|
|
3174
|
+
ruleField('输出后缀 · 原样加入结果末尾', suffixText, setSuffixText, '例如:禁止添加文字和水印。')),
|
|
3175
|
+
React.createElement('div', { className: 'iris-po-muted' }, supportsRules ? '非空内容表示本次选择;只组装保留原稿,只添加前后缀。刷新后重置,不修改 JSON 配置。' : '当前后端尚未支持规则,请更新后重新载入。')),
|
|
2400
3176
|
React.createElement('div', { className: 'iris-po-actions' },
|
|
2401
|
-
React.createElement('button', { className: 'iris-po-primary', disabled: busy || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: optimize }, busy ? '
|
|
3177
|
+
React.createElement('button', { className: 'iris-po-primary', disabled: busy || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: function () { optimize('optimize'); } }, busy ? '处理中…' : supportsFaithful ? editing === 'faithful' ? '忠实整理' : '改写表达' : '智能优化'),
|
|
3178
|
+
React.createElement('button', { className: 'iris-po-btn', disabled: busy || !supportsAssembly || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: function () { optimize('assemble'); } }, '只组装(不调用模型)'),
|
|
2402
3179
|
busy ? React.createElement('button', { className: 'iris-po-btn', onClick: cancel }, '取消') : null),
|
|
3180
|
+
React.createElement('div', { className: 'iris-po-muted' }, '智能优化调用文本模型,可能产生费用;只组装原稿与前后缀。'),
|
|
2403
3181
|
result ? React.createElement('div', { className: 'iris-po-result' },
|
|
2404
|
-
React.createElement('div', { className: 'iris-po-result-label' }, '
|
|
2405
|
-
React.createElement('textarea', {
|
|
2406
|
-
React.createElement('div', { className: 'iris-po-
|
|
3182
|
+
React.createElement('div', { className: 'iris-po-result-label' }, '结果预览 · ' + (resultTarget ? resultTarget.label + ' · ' : '') + '可编辑 · ' + edited.length + ' 字符'),
|
|
3183
|
+
React.createElement('textarea', { 'aria-label': '结果预览', value: edited, onChange: function (event) { editPreview(event.target.value); } }),
|
|
3184
|
+
result.editing?.strategy === 'faithful' ? React.createElement('div', { className: 'iris-po-muted' }, '本次整理保留了全部原文片段与句序;手动编辑或采纳建议后,请重新核对内容。') : null,
|
|
3185
|
+
supportsSuggestions ? React.createElement('details', { className: 'iris-po-config' },
|
|
3186
|
+
React.createElement('summary', {}, '可选补充与待确认'),
|
|
3187
|
+
React.createElement('div', { className: 'iris-po-muted' }, '建议独立生成,默认未采纳;不会自动加入正文或写回。每次获取会调用文本模型。'),
|
|
3188
|
+
React.createElement('button', { className: 'iris-po-btn', disabled: busy || !edited.trim(), onClick: requestSuggestions }, '获取补充建议'),
|
|
3189
|
+
suggestions ? React.createElement('div', {},
|
|
3190
|
+
suggestions.items.map(function (item) {
|
|
3191
|
+
var adopted = suggestions.adopted.indexOf(item.id) >= 0;
|
|
3192
|
+
return React.createElement('div', { className: 'iris-po-suggestion', key: item.id },
|
|
3193
|
+
React.createElement('label', {}, React.createElement('input', { type: 'checkbox', checked: adopted || selectedSuggestions.indexOf(item.id) >= 0, disabled: busy || adopted,
|
|
3194
|
+
onChange: function (event) { setSelectedSuggestions(event.target.checked ? selectedSuggestions.concat([item.id]) : selectedSuggestions.filter(function (id) { return id !== item.id; })); } }), item.label + (adopted ? ' · 已加入预览' : '')),
|
|
3195
|
+
React.createElement('p', {}, item.text));
|
|
3196
|
+
}),
|
|
3197
|
+
!suggestions.items.length ? React.createElement('div', { className: 'iris-po-muted' }, '没有额外补充建议。') : null,
|
|
3198
|
+
suggestions.questions.length ? React.createElement('div', { className: 'iris-po-suggestion' }, React.createElement('strong', {}, '待确认 · 尚未作为要求'),
|
|
3199
|
+
suggestions.questions.map(function (question, index) { return React.createElement('p', { key: index }, question); })) : null,
|
|
3200
|
+
React.createElement('div', { className: 'iris-po-actions' },
|
|
3201
|
+
React.createElement('button', { className: 'iris-po-btn', disabled: busy || !selectedSuggestions.length, onClick: adoptSuggestions }, '采纳所选建议'),
|
|
3202
|
+
React.createElement('button', { className: 'iris-po-btn', disabled: busy || !suggestionUndo.length, onClick: undoSuggestions }, '撤回上次采纳'))) : null) : null,
|
|
3203
|
+
React.createElement('details', { className: 'iris-po-config' }, React.createElement('summary', {}, '原文与改动'),
|
|
3204
|
+
React.createElement('textarea', { 'aria-label': '优化前原文', readOnly: true, value: result.original }),
|
|
3205
|
+
React.createElement('div', { className: 'iris-po-muted' }, '以下折叠相同首尾,显示中间替换区:'),
|
|
3206
|
+
React.createElement('div', { className: 'iris-po-diff' },
|
|
3207
|
+
changes.removed ? React.createElement('del', {}, changes.removed) : null,
|
|
3208
|
+
changes.added ? React.createElement('ins', {}, changes.added) : null,
|
|
3209
|
+
!changes.removed && !changes.added ? '与原文相同' : null)),
|
|
3210
|
+
React.createElement('details', { className: 'iris-po-config' }, React.createElement('summary', {}, '本次参与规则'),
|
|
3211
|
+
(result.rules || []).length ? (result.rules || []).map(function (rule) {
|
|
3212
|
+
return React.createElement('div', { className: 'iris-po-diff', key: rule.id }, rule.label + '(' + (rule.kind === 'optimization' ? '指导改写' : rule.position === 'prefix' ? '输出前缀' : '输出后缀') + ')\n' + rule.text);
|
|
3213
|
+
}) : React.createElement('div', { className: 'iris-po-muted' }, '未选择额外规则'),
|
|
3214
|
+
result.processing ? React.createElement('div', { className: 'iris-po-diff' }, '优化处理规则:\n' + result.processing.systemPrompt + '\n\n目标模板:\n' + result.processing.targetTemplate) : null),
|
|
3215
|
+
React.createElement('div', { className: 'iris-po-route' }, result.route ? '实际使用:' + result.route.provider + ' / ' + result.route.model + ' · 思考:' + (result.route.reasoningEffort === 'provider-default' ? '供应商默认' : result.route.reasoningEffort) : '本次只组装,未调用模型'),
|
|
2407
3216
|
React.createElement('div', { className: 'iris-po-actions' },
|
|
2408
|
-
React.createElement('button', { className: 'iris-po-primary', onClick: function () { replaceDraft(
|
|
3217
|
+
React.createElement('button', { className: 'iris-po-primary', onClick: function () { replaceDraft(edited); } }, '写回输入框'),
|
|
2409
3218
|
React.createElement('button', { className: 'iris-po-btn', onClick: copyResult }, '复制'),
|
|
2410
|
-
React.createElement('button', { className: 'iris-po-btn', onClick: optimize }, '
|
|
3219
|
+
React.createElement('button', { className: 'iris-po-btn', disabled: busy, onClick: function () { optimize(result.mode); } }, '重新生成预览'),
|
|
2411
3220
|
React.createElement('button', { className: 'iris-po-btn', onClick: function () { replaceDraft(result.original); } }, '恢复原文'))) : null,
|
|
2412
|
-
note ? React.createElement('div', { className: 'iris-po-note' }, note) : null,
|
|
3221
|
+
note ? React.createElement('div', { className: 'iris-po-note', role: 'status' }, note) : null)),
|
|
2413
3222
|
React.createElement('details', { className: 'iris-po-config' },
|
|
2414
|
-
React.createElement('summary', {}, '
|
|
3223
|
+
React.createElement('summary', {}, '指令与模型设置'),
|
|
3224
|
+
React.createElement('div', { className: 'iris-po-route', title: routeLabel }, '模型:' + routeLabel + ' · 思考:' + reasoningLabel + ' · 输出:' + outputBudget + ' tokens'),
|
|
3225
|
+
React.createElement('details', {}, React.createElement('summary', {}, '查看当前功能的优化指令'),
|
|
3226
|
+
React.createElement('div', { className: 'iris-po-template' }, config && config.systemPrompt || '配置尚未载入'),
|
|
3227
|
+
React.createElement('div', { className: 'iris-po-template' }, targetTemplate || '当前后端未提供此目标模板')),
|
|
3228
|
+
React.createElement('div', { className: 'iris-po-result-label' }, 'JSON 配置'),
|
|
2415
3229
|
React.createElement('div', { className: 'iris-po-muted' }, '可导出后修改 systemPrompt、targets、route 和 generation,再重新导入。'),
|
|
2416
3230
|
React.createElement('div', { className: 'iris-po-actions' },
|
|
2417
3231
|
React.createElement('button', { className: 'iris-po-btn', onClick: exportConfig }, '导出'),
|
|
@@ -2428,7 +3242,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2428
3242
|
type: 'button',
|
|
2429
3243
|
title: refCount ? '含结构化引用的草稿暂不支持优化' : '优化当前输入框提示词',
|
|
2430
3244
|
'aria-expanded': open ? 'true' : 'false',
|
|
2431
|
-
onClick: function () {
|
|
3245
|
+
onClick: function () { if (open) closePanel(); else setOpen(true); }
|
|
2432
3246
|
}, '🫧'),
|
|
2433
3247
|
panel);
|
|
2434
3248
|
}
|
|
@@ -2473,8 +3287,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2473
3287
|
var state = useIrisState(5000);
|
|
2474
3288
|
var providers = (state && state.providers) || [];
|
|
2475
3289
|
var fallbackConfigured = providers.some(function (p) { return p.enabled && p.apiKeyHint; });
|
|
2476
|
-
var
|
|
3290
|
+
var overallState = state && state.health && HEALTH_META[state.health.overall]
|
|
2477
3291
|
? state.health.overall : (fallbackConfigured ? 'configured' : 'unconfigured');
|
|
3292
|
+
var overallCooling = overallState === 'failed' && state.health.retryAt;
|
|
3293
|
+
var overallHealth = overallCooling ? 'cooling' : overallState;
|
|
2478
3294
|
var running = (state && state.tasks && state.tasks.running) || [];
|
|
2479
3295
|
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
2480
3296
|
|
|
@@ -2600,7 +3416,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2600
3416
|
onPointerMove: onPointerMove,
|
|
2601
3417
|
onPointerUp: onPointerUp,
|
|
2602
3418
|
onPointerCancel: onPointerCancel,
|
|
2603
|
-
title: 'Iris ' + healthText({ status:
|
|
3419
|
+
title: 'Iris ' + healthText({ status: overallState, rateLimited: !!overallCooling, retryAt: overallCooling || undefined }) + ':点击打开快捷面板'
|
|
2604
3420
|
},
|
|
2605
3421
|
React.createElement('div', { className: 'iris-bubble-btn' }, React.createElement(IRIS_MARK_SVG)),
|
|
2606
3422
|
badge,
|