@mokuyoaxis/dsh-iris 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +47 -2
- package/README.en.md +65 -29
- package/README.md +65 -29
- package/bin/dsh-iris.js +222 -21
- package/docs/AGENT_QUICKSTART.md +10 -6
- package/docs/ARCHITECTURE.md +25 -18
- package/docs/ARTIFACT_LIBRARY.md +5 -5
- package/docs/ARTIFACT_MANIFEST.md +1 -1
- package/docs/CLI_MANAGEMENT.md +181 -0
- package/docs/COMPOSITE_VISION.md +35 -0
- package/docs/CORE_RUNTIME_CONTRACT.md +8 -7
- package/docs/DSH_CORE_MIGRATION.md +48 -19
- package/docs/DSH_RC2_ADAPTATION.md +4 -2
- package/docs/FAULT_INJECTION.md +12 -2
- package/docs/HEADLESS_CLI.md +13 -7
- package/docs/HOST_ADAPTER_CONTRACT.md +10 -10
- package/docs/HOST_DOCTOR.md +3 -3
- package/docs/MODEL_PORT_CONTRACT.md +19 -14
- package/docs/OCR_MODEL.md +39 -0
- package/docs/PROMPT_OPTIMIZER.md +47 -0
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +6 -4
- 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 +29 -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/lib/actions.js +187 -270
- package/lib/adapters.js +133 -22
- package/lib/api.js +49 -7
- package/lib/chromium-browser.js +117 -0
- package/lib/client.js +671 -101
- package/lib/command-service.js +121 -7
- package/lib/composite-vision-routing.js +42 -0
- package/lib/config.js +89 -10
- package/lib/core-artifact-store.js +45 -13
- package/lib/core-maintenance.js +213 -0
- package/lib/core-tasks.js +12 -5
- package/lib/core-user-projection.js +1 -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 +10 -5
- package/lib/ocr-model-routing.js +26 -0
- package/lib/ocr.js +110 -64
- package/lib/prompt-optimizer-config.js +2 -1
- package/lib/prompt-optimizer-core.js +121 -0
- package/lib/prompt-optimizer.js +37 -193
- package/lib/provider-adapters.js +85 -9
- package/lib/provider-catalog.js +19 -7
- package/lib/provider-config-service.js +236 -0
- package/lib/provider-contract.js +3 -2
- package/lib/provider-health.js +80 -1
- package/lib/provider-protocol.js +23 -0
- package/lib/provider-task-runner.js +58 -16
- 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 +52 -22
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.1';
|
|
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) */
|
|
@@ -281,8 +309,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
281
309
|
'.iris-po-route { padding: 7px 10px; border-radius: 10px; background: rgba(255,255,255,.045); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
|
|
282
310
|
'.iris-po-note { padding: 8px 10px; border-radius: 10px; color: var(--dsw-alias-state-warn-primary, #dfbd69); background: rgba(217,169,65,.08); }',
|
|
283
311
|
'.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); }',
|
|
312
|
+
'.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; }',
|
|
313
|
+
'.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); }',
|
|
314
|
+
'.iris-po-field textarea { min-height: 60px; resize: vertical; line-height: 1.55; }',
|
|
315
|
+
'.iris-po-rules { display: grid; gap: 10px; }',
|
|
316
|
+
'.iris-po-diff { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 28dvh; overflow-y: auto; font-size: 12px; line-height: 1.6; }',
|
|
317
|
+
'.iris-po-diff del, .iris-po-diff ins { display: block; padding: 6px 8px; border-radius: 7px; margin: 5px 0; }',
|
|
318
|
+
'.iris-po-diff del { background: rgba(226,113,126,.12); }',
|
|
319
|
+
'.iris-po-diff ins { background: rgba(99,198,156,.12); text-decoration: none; }',
|
|
286
320
|
'.iris-po-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }',
|
|
287
321
|
'.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
322
|
'.iris-po-btn { background: rgba(255,255,255,.045); color: var(--dsw-alias-label-secondary, #b0b0bb); }',
|
|
@@ -419,7 +453,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
419
453
|
unconfigured: { label: '未配置', symbol: '○' },
|
|
420
454
|
configured: { label: '已配置,待验证', symbol: '◌' },
|
|
421
455
|
verified: { label: '近期验证成功', symbol: '●' },
|
|
422
|
-
failed: { label: '
|
|
456
|
+
failed: { label: '调用受阻', symbol: '×' }
|
|
423
457
|
};
|
|
424
458
|
|
|
425
459
|
function capabilityHealth(state, capability) {
|
|
@@ -443,9 +477,22 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
443
477
|
return Math.floor(ms / 86400000) + ' 天前';
|
|
444
478
|
}
|
|
445
479
|
|
|
480
|
+
function rateLimitLabel(health) {
|
|
481
|
+
return health.retryAt ? '429 冷却中' : health.reason === 'budget' ? '预算耗尽已停用' : '额度耗尽已停用';
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function healthClass(health) {
|
|
485
|
+
return health.retryAt ? 'cooling' : health.rateLimited ? 'rate-limited' : health.status;
|
|
486
|
+
}
|
|
487
|
+
|
|
446
488
|
function healthText(health) {
|
|
447
489
|
var status = health && HEALTH_META[health.status] ? health.status : 'unconfigured';
|
|
448
|
-
var text =
|
|
490
|
+
var text = health && health.rateLimited
|
|
491
|
+
? rateLimitLabel(health) + (health.retryAt
|
|
492
|
+
? ',' + new Date(health.retryAt).toLocaleTimeString() + ' 恢复候选资格;可提前实测'
|
|
493
|
+
: ',实测成功后恢复')
|
|
494
|
+
: (status === 'failed' && health && ['authentication', 'auth', 'permission'].indexOf(health.category) >= 0
|
|
495
|
+
? '认证失败' : HEALTH_META[status].label);
|
|
449
496
|
var age = relativeHealthTime(health && health.observedAt);
|
|
450
497
|
return text + (age ? ' · ' + age : '');
|
|
451
498
|
}
|
|
@@ -462,9 +509,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
462
509
|
return React.createElement('div', { className: 'iris-health-grid' }, rows.map(function (row) {
|
|
463
510
|
var health = capabilityHealth(state, row[0]);
|
|
464
511
|
var meta = HEALTH_META[health.status] || HEALTH_META.unconfigured;
|
|
465
|
-
return React.createElement('div', { key: row[0], className: 'iris-health-row ' + health
|
|
512
|
+
return React.createElement('div', { key: row[0], className: 'iris-health-row ' + healthClass(health) },
|
|
466
513
|
React.createElement('span', { className: 'iris-health-name' },
|
|
467
|
-
React.createElement('span', { className: 'iris-pm-dot ' + health
|
|
514
|
+
React.createElement('span', { className: 'iris-pm-dot ' + healthClass(health) }, health.retryAt ? '◷' : meta.symbol),
|
|
468
515
|
row[1]),
|
|
469
516
|
React.createElement('span', { className: 'iris-health-state', title: healthText(health) }, healthText(health)),
|
|
470
517
|
React.createElement('span', { className: 'iris-wb-muted' }, String(health.candidateCount || 0) + ' 路'));
|
|
@@ -743,12 +790,16 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
743
790
|
var newId = addPair[0];
|
|
744
791
|
var setNewId = addPair[1];
|
|
745
792
|
var models = p.models || [];
|
|
793
|
+
function setImageProtocol(m, value) {
|
|
794
|
+
postAction('providers_set_model_image_protocol', { id: p.id, model_id: m.id, imageProtocol: value })
|
|
795
|
+
.then(function (r) { props.onNote(r.ok ? '模型图片协议已更新' : (r.d.error || '更新失败')); if (r.ok) props.onDone(); });
|
|
796
|
+
}
|
|
746
797
|
function verBadge(m, c) {
|
|
747
798
|
var health = m.health && m.health[c];
|
|
748
799
|
var status = health && HEALTH_META[health.status] ? health.status : 'configured';
|
|
749
|
-
var cls = 'iris-pm-ver ' + status;
|
|
800
|
+
var cls = 'iris-pm-ver ' + status + (health && health.rateLimited ? ' ' + healthClass(health) : '');
|
|
750
801
|
var title = healthText(health || { status: status });
|
|
751
|
-
var symbol = status === 'verified' ? '✓' : (status === 'failed' ? '×' : (status === 'unconfigured' ? '○' : '?'));
|
|
802
|
+
var symbol = health && health.retryAt ? '◷' : status === 'verified' ? '✓' : (status === 'failed' ? '×' : (status === 'unconfigured' ? '○' : '?'));
|
|
752
803
|
return React.createElement('button', {
|
|
753
804
|
key: c,
|
|
754
805
|
className: cls,
|
|
@@ -758,9 +809,21 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
758
809
|
}
|
|
759
810
|
var rows = models.map(function (m) {
|
|
760
811
|
var caps = m.capabilities || [];
|
|
812
|
+
var blocked = caps.map(function (c) { return m.health && m.health[c]; }).find(function (health) { return health && health.rateLimited; });
|
|
761
813
|
return React.createElement('div', { key: m.id, className: 'iris-pm-mrow' },
|
|
762
814
|
React.createElement('span', { className: 'iris-pm-mname' }, m.id, m.source === 'manual' ? React.createElement('span', { className: 'iris-pm-mtag' }, '手动') : null),
|
|
763
815
|
React.createElement('span', { className: 'iris-pm-mcaps' }, caps.map(function (c) { return verBadge(m, c); })),
|
|
816
|
+
caps.includes('image-gen') ? React.createElement('label', { className: 'iris-pm-note' }, '图片协议 ',
|
|
817
|
+
React.createElement('select', { value: m.imageProtocol || 'auto', onChange: function (e) { setImageProtocol(m, e.target.value); } },
|
|
818
|
+
React.createElement('option', { value: 'auto' }, '继承账号默认(' + (p.mediaProtocol || '自动') + ')'),
|
|
819
|
+
React.createElement('option', { value: 'dashscope' }, 'DashScope'),
|
|
820
|
+
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images'),
|
|
821
|
+
React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
|
|
822
|
+
React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图')),
|
|
823
|
+
m.imageRouting && m.imageRouting.protocolInferred ? ' · 协议待确认' : null) : null,
|
|
824
|
+
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,
|
|
825
|
+
onNote: props.onNote, onDone: props.onDone }) : null,
|
|
826
|
+
blocked ? React.createElement('span', { className: 'iris-pm-ver failed ' + healthClass(blocked), title: healthText(blocked) }, rateLimitLabel(blocked)) : null,
|
|
764
827
|
React.createElement('button', { className: 'iris-pm-btn danger', title: '从池移除', onClick: function () { props.removeModel(p, m); } }, '✕'));
|
|
765
828
|
});
|
|
766
829
|
return React.createElement('div', { className: 'iris-pm-pool' },
|
|
@@ -771,6 +834,39 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
771
834
|
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { props.addModel(p, newId.trim()); setNewId(''); } }, '+ 添加')));
|
|
772
835
|
}
|
|
773
836
|
|
|
837
|
+
function VisionInputEditor(props) {
|
|
838
|
+
var p = props.provider;
|
|
839
|
+
var m = props.model;
|
|
840
|
+
var limits = (m ? m.visionInput : p.visionInput) || {};
|
|
841
|
+
var bytesPair = React.useState(limits.maxBytes === undefined ? '' : String(limits.maxBytes / 1048576));
|
|
842
|
+
var dimensionPair = React.useState(limits.maxDimension === undefined ? '' : String(limits.maxDimension));
|
|
843
|
+
React.useEffect(function () {
|
|
844
|
+
bytesPair[1](limits.maxBytes === undefined ? '' : String(limits.maxBytes / 1048576));
|
|
845
|
+
dimensionPair[1](limits.maxDimension === undefined ? '' : String(limits.maxDimension));
|
|
846
|
+
}, [limits.maxBytes, limits.maxDimension]);
|
|
847
|
+
function save(reset) {
|
|
848
|
+
var value = {};
|
|
849
|
+
if (bytesPair[0] !== '') value.maxBytes = Number(bytesPair[0]) * 1048576;
|
|
850
|
+
if (dimensionPair[0] !== '') value.maxDimension = Number(dimensionPair[0]);
|
|
851
|
+
if (!reset && Object.values(value).some(function (number) { return !Number.isSafeInteger(number) || number <= 0; })) {
|
|
852
|
+
props.onNote('请输入正数 MiB 和正整数像素'); return;
|
|
853
|
+
}
|
|
854
|
+
var action = m ? 'providers_set_model_vision_input' : 'providers_upsert';
|
|
855
|
+
postAction(action, { id: p.id, ...(m ? { model_id: m.id } : {}), visionInput: reset ? null : value })
|
|
856
|
+
.then(function (r) { props.onNote(r.ok ? '视觉输入预算已更新' : (r.d.error || '更新失败')); if (r.ok) props.onDone(); });
|
|
857
|
+
}
|
|
858
|
+
return React.createElement('div', { className: 'iris-pm-field' },
|
|
859
|
+
React.createElement('label', {}, m ? '模型视觉预算(留空继承账号)' : '账号视觉预算(留空使用默认 8 MiB)'),
|
|
860
|
+
React.createElement('div', { className: 'iris-wb-row' },
|
|
861
|
+
React.createElement('input', { type: 'number', min: '0', step: 'any', 'aria-label': '视觉大小上限 MiB',
|
|
862
|
+
placeholder: 'MiB', value: bytesPair[0], onChange: function (e) { bytesPair[1](e.target.value); } }),
|
|
863
|
+
React.createElement('input', { type: 'number', min: '1', step: '1', 'aria-label': '视觉最长边像素',
|
|
864
|
+
placeholder: '最长边 px(可选)', value: dimensionPair[0], onChange: function (e) { dimensionPair[1](e.target.value); } }),
|
|
865
|
+
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { save(false); } }, '保存预算'),
|
|
866
|
+
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { save(true); } }, '恢复继承')),
|
|
867
|
+
React.createElement('span', { className: 'iris-pm-note' }, '用于看图、OCR 每块输入和定位。作品原图保留,定位结果换算回原图。'));
|
|
868
|
+
}
|
|
869
|
+
|
|
774
870
|
function ProviderEndpointEditor(props) {
|
|
775
871
|
var p = props.provider;
|
|
776
872
|
var valuePair = React.useState(p.mediaBaseUrl || '');
|
|
@@ -807,7 +903,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
807
903
|
function refresh() {
|
|
808
904
|
postAction('providers_list', {}).then(function (r) { if (r.ok && r.d.providers) setList(r.d.providers); });
|
|
809
905
|
}
|
|
810
|
-
React.useEffect(function () {
|
|
906
|
+
React.useEffect(function () {
|
|
907
|
+
refresh();
|
|
908
|
+
var timer = setInterval(refresh, 5000);
|
|
909
|
+
return function () { clearInterval(timer); };
|
|
910
|
+
}, []);
|
|
811
911
|
function addProvider() {
|
|
812
912
|
var f = newForm[0];
|
|
813
913
|
if (!f.baseUrl || !f.apiKey) { setNote('baseUrl 和 apiKey 必填'); return; }
|
|
@@ -863,11 +963,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
863
963
|
React.createElement('div', { className: 'iris-pm-note' }, 'Key ' + (p.apiKeyHint || '未配置') + ' · ' + (p.type || '') + ' · ' + (p.mediaProtocol || '')),
|
|
864
964
|
p.protocolInferred ? React.createElement('div', { className: 'iris-pm-note' }, '协议为推断值,请确认') : null,
|
|
865
965
|
React.createElement(ProviderEndpointEditor, { provider: p, onNote: setNote, onDone: refresh }),
|
|
966
|
+
React.createElement(VisionInputEditor, { provider: p, onNote: setNote, onDone: refresh }),
|
|
866
967
|
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
968
|
React.createElement('option', { value: 'auto' }, '自动'),
|
|
868
|
-
p.mediaProtocol && !['dashscope', 'openai-images', 'auto'].includes(p.mediaProtocol) ? React.createElement('option', { value: p.mediaProtocol }, p.mediaProtocol + '(未支持)') : null,
|
|
969
|
+
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
970
|
React.createElement('option', { value: 'dashscope' }, 'DashScope(仅阿里云官方 HTTPS)'),
|
|
870
|
-
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容')
|
|
971
|
+
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'),
|
|
972
|
+
React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
|
|
973
|
+
React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图'))),
|
|
871
974
|
React.createElement('div', { className: 'iris-pm-note' }, '能力: ' + ((p.capabilities || []).join(' / ') || '无')),
|
|
872
975
|
React.createElement(ModelPool, { provider: p, onNote: setNote, onDone: refresh, testModel: testModel, removeModel: removeModel, addModel: addModel }),
|
|
873
976
|
React.createElement('div', { className: 'iris-wb-row' },
|
|
@@ -891,7 +994,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
891
994
|
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
995
|
React.createElement('option', { value: 'auto' }, '自动(按媒体 Base URL 安全判断)'),
|
|
893
996
|
React.createElement('option', { value: 'dashscope' }, 'DashScope(阿里云官方)'),
|
|
894
|
-
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容')
|
|
997
|
+
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'),
|
|
998
|
+
React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
|
|
999
|
+
React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图'))),
|
|
895
1000
|
React.createElement('div', { className: 'iris-wb-row' },
|
|
896
1001
|
React.createElement('button', { className: 'iris-act-run', onClick: addProvider }, '保存'),
|
|
897
1002
|
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { setAdding(false); } }, '取消')));
|
|
@@ -1062,7 +1167,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1062
1167
|
var shown = (vals.model && vals.model.trim()) || (modelInfo && modelInfo.assigned) || '';
|
|
1063
1168
|
modelBar = React.createElement('div', { className: 'iris-act-field' },
|
|
1064
1169
|
React.createElement('label', {},
|
|
1065
|
-
React.createElement('span', { className: 'iris-pm-dot ' + cardHealth
|
|
1170
|
+
React.createElement('span', { className: 'iris-pm-dot ' + healthClass(cardHealth) }, cardHealth.retryAt ? '◷' : HEALTH_META[cardHealth.status].symbol),
|
|
1066
1171
|
' 模型:' + (shown || '自动') + (available ? '' : '(无可用模型)')),
|
|
1067
1172
|
React.createElement('div', { className: 'iris-wb-muted' },
|
|
1068
1173
|
healthText(cardHealth) + ' · 改分配 → 上方「能力分配(failover 顺序)」'));
|
|
@@ -1082,9 +1187,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1082
1187
|
React.createElement('div', { className: 'iris-act-head', onClick: function () { setOpen(!open); } },
|
|
1083
1188
|
React.createElement('span', { className: 'iris-act-title' }, title),
|
|
1084
1189
|
capability ? React.createElement('span', {
|
|
1085
|
-
className: 'iris-pm-dot ' +
|
|
1190
|
+
className: 'iris-pm-dot ' + healthClass(cardHealth),
|
|
1086
1191
|
title: headTitle
|
|
1087
|
-
}, HEALTH_META[headStatus].symbol) : null,
|
|
1192
|
+
}, cardHealth.retryAt ? '◷' : HEALTH_META[headStatus].symbol) : null,
|
|
1088
1193
|
React.createElement('span', { className: 'iris-act-arrow' }, '▶')),
|
|
1089
1194
|
body);
|
|
1090
1195
|
}
|
|
@@ -1328,7 +1433,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1328
1433
|
|
|
1329
1434
|
function coreGalleryItems(data, limit) {
|
|
1330
1435
|
return (((data && data.artifacts && data.artifacts.recent) || []).filter(function (artifact) {
|
|
1331
|
-
return artifact.kind !== 'host-input'
|
|
1436
|
+
return artifact.kind !== 'host-input';
|
|
1332
1437
|
}).map(function (artifact) {
|
|
1333
1438
|
return {
|
|
1334
1439
|
id: artifact.id, file: artifact.id, mime: artifact.mediaType,
|
|
@@ -1364,99 +1469,458 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1364
1469
|
return pair[0];
|
|
1365
1470
|
}
|
|
1366
1471
|
|
|
1367
|
-
function
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
var
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1472
|
+
function galleryKindLabel(kind) {
|
|
1473
|
+
return ({ __proto__: null, 'generated-image': '生成图片', 'generated-video': '生成视频', 'generated-audio': '生成音频',
|
|
1474
|
+
'tts-audio': '合成语音', transcript: '转写文本', 'video-frame': '视频帧', crop: '裁剪图片',
|
|
1475
|
+
'cropped-image': '裁剪图片', 'pixel-diff': '差异热图', 'diff-heatmap': '差异热图', 'html-screenshot': 'HTML 截图',
|
|
1476
|
+
'legacy-output': '旧版作品' })[kind] || kind;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
function fetchWorkbenchJson(url, input, signal) {
|
|
1480
|
+
var options = { signal: signal };
|
|
1481
|
+
if (input) { options.method = 'POST'; options.headers = { 'Content-Type': 'application/json' }; options.body = JSON.stringify(input); }
|
|
1482
|
+
return fetch(url, options).then(function (res) { return res.json().then(function (data) {
|
|
1483
|
+
if (!res.ok || data.ok === false) throw new Error((data.error || '作品操作失败') + (data.transactionId ? '(事务 ' + data.transactionId + ')' : ''));
|
|
1484
|
+
return data;
|
|
1485
|
+
}); });
|
|
1486
|
+
}
|
|
1487
|
+
function copyWorkbenchText(text, setNote) {
|
|
1488
|
+
if (!navigator.clipboard || typeof navigator.clipboard.writeText !== 'function') { setNote('请手动选择并复制 ID:\n' + text); return; }
|
|
1489
|
+
navigator.clipboard.writeText(text).then(function () { setNote('已复制'); }).catch(function () { setNote('请手动选择并复制 ID:\n' + text); });
|
|
1490
|
+
}
|
|
1491
|
+
function saveWorkbenchBlob(blob, name) {
|
|
1492
|
+
var url = URL.createObjectURL(blob), link = document.createElement('a');
|
|
1493
|
+
link.href = url; link.download = name; link.click();
|
|
1494
|
+
setTimeout(function () { URL.revokeObjectURL(url); }, 0);
|
|
1495
|
+
}
|
|
1496
|
+
function deletionBlockerLabel(blocker) {
|
|
1497
|
+
return ({ __proto__: null, task_not_settled: '任务尚未结束或结果未知', task_artifact_reference: '被任务记录引用', task_input_reference: '被改图任务作为原图引用',
|
|
1498
|
+
artifact_task_reference: '该任务仍关联其他作品', retry_reference: '被后续重试任务引用',
|
|
1499
|
+
artifact_relation: '被其他作品引用', uncommitted_relation: '被尚未提交的产物引用' })[blocker.reason] || '仍有引用';
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function WorkbenchManagement(props) {
|
|
1503
|
+
var type = props.type;
|
|
1504
|
+
var dataPair = React.useState(null), data = dataPair[0], setData = dataPair[1];
|
|
1505
|
+
var busyPair = React.useState(true), busy = busyPair[0], setBusy = busyPair[1];
|
|
1506
|
+
var writingPair = React.useState(false), writing = writingPair[0], setWriting = writingPair[1];
|
|
1507
|
+
var notePair = React.useState(''), note = notePair[0], setNote = notePair[1];
|
|
1508
|
+
var tasksPair = React.useState([]), taskIds = tasksPair[0], setTaskIds = tasksPair[1];
|
|
1509
|
+
var request = React.useRef(null), view = React.useRef(null);
|
|
1510
|
+
var artifactIds = (props.items || []).map(function (item) { return item.id; });
|
|
1511
|
+
function query(tasks) {
|
|
1512
|
+
if (request.current) request.current.abort();
|
|
1513
|
+
var controller = new AbortController(); request.current = controller; setBusy(true); setNote('');
|
|
1514
|
+
var url = type === 'details' ? '/iris/api/works/' + props.item.source + '/' + props.item.id
|
|
1515
|
+
: type === 'transactions' ? '/iris/api/works/transactions' : '/iris/api/works/delete';
|
|
1516
|
+
fetchWorkbenchJson(url, type === 'delete' ? { artifact_ids: artifactIds, task_ids: tasks || [] } : null, controller.signal)
|
|
1517
|
+
.then(function (value) { if (request.current === controller && !controller.signal.aborted) setData(value); })
|
|
1518
|
+
.catch(function (error) { if (request.current === controller && !controller.signal.aborted) { setData(null); setNote(error.message); } })
|
|
1519
|
+
.finally(function () { if (request.current === controller) { request.current = null; setBusy(false); } });
|
|
1520
|
+
}
|
|
1521
|
+
React.useEffect(function () {
|
|
1522
|
+
query([]);
|
|
1523
|
+
if (view.current) view.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
1524
|
+
return function () { if (request.current) request.current.abort(); request.current = null; };
|
|
1525
|
+
}, []);
|
|
1526
|
+
function chooseTask(id, checked) {
|
|
1527
|
+
var next = checked ? taskIds.concat(id) : taskIds.filter(function (value) { return value !== id; });
|
|
1528
|
+
setTaskIds(next); query(next);
|
|
1529
|
+
}
|
|
1530
|
+
function execute(transaction) {
|
|
1531
|
+
var restoring = type === 'transactions';
|
|
1532
|
+
var message = restoring ? '恢复事务 ' + transaction.transactionId + ' 中的 ' + transaction.files + ' 个文件?已有文件不会被覆盖。'
|
|
1533
|
+
: '将 ' + artifactIds.length + ' 个 Core 作品' + (taskIds.length ? '及 ' + taskIds.length + ' 个已结束任务记录' : '') + '移入回收区?之后可以恢复。';
|
|
1534
|
+
if (!window.confirm(message)) return;
|
|
1535
|
+
var controller = new AbortController(); request.current = controller;
|
|
1536
|
+
setWriting(true); setBusy(true); setNote(''); props.onBusy(true);
|
|
1537
|
+
fetchWorkbenchJson('/iris/api/works/' + (restoring ? 'restore' : 'delete'), restoring
|
|
1538
|
+
? { transaction_id: transaction.transactionId, confirm_restore: true }
|
|
1539
|
+
: { artifact_ids: artifactIds, task_ids: taskIds, confirm_delete: true }, controller.signal)
|
|
1540
|
+
.then(function (value) {
|
|
1541
|
+
if (request.current !== controller || controller.signal.aborted) return;
|
|
1542
|
+
props.onChanged(value);
|
|
1543
|
+
if (restoring) query([]); else props.onClose();
|
|
1544
|
+
})
|
|
1545
|
+
.catch(function (error) { if (request.current === controller && !controller.signal.aborted) setNote(error.message); })
|
|
1546
|
+
.finally(function () {
|
|
1547
|
+
props.onBusy(false); setWriting(false);
|
|
1548
|
+
if (request.current === controller) { request.current = null; setBusy(false); }
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
var item = data && data.item;
|
|
1552
|
+
var titles = { details: '作品详情', delete: '删除预览', transactions: 'Core 回收区' };
|
|
1553
|
+
return React.createElement('section', { ref: view, className: 'iris-gallery-management', 'aria-label': titles[type], 'aria-busy': busy },
|
|
1554
|
+
React.createElement('div', { className: 'iris-gallery-analysis-head' }, React.createElement('strong', {}, titles[type]),
|
|
1555
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: writing, onClick: props.onClose }, '关闭面板')),
|
|
1556
|
+
busy ? React.createElement('div', { role: 'status', className: 'iris-wb-muted' }, writing ? '正在处理…' : '正在读取…') : null,
|
|
1557
|
+
item ? React.createElement('dl', {}, [
|
|
1558
|
+
['ID', item.id], ['来源', item.source === 'core' ? 'Core' : '旧版'], ['文件名', item.file], ['媒体类型', item.mime],
|
|
1559
|
+
['产物类型', galleryKindLabel(item.kind)], ['大小', fmtBytes(item.size)], ['创建时间', fmtTime(item.createdAt)],
|
|
1560
|
+
['内容核验', item.integrity === 'verified' ? 'SHA-256 已核验' : '文件存在;旧版没有已存哈希'],
|
|
1561
|
+
['SHA-256', item.digest], ['关联', (item.relations || []).map(function (edge) { return edge.type + ' → ' + edge.artifactId; }).join('\n')],
|
|
1562
|
+
['元数据', item.metadata && Object.keys(item.metadata).length ? JSON.stringify(item.metadata, null, 2) : '']
|
|
1563
|
+
].filter(function (row) { return !!row[1]; }).map(function (row) {
|
|
1564
|
+
return [React.createElement('dt', { key: row[0] + '-label' }, row[0]), React.createElement('dd', { key: row[0] }, row[1])];
|
|
1565
|
+
})) : null,
|
|
1566
|
+
item ? React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1567
|
+
React.createElement('a', { className: 'iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开原文件'),
|
|
1568
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: function () { copyWorkbenchText(item.id, setNote); } }, '复制 ID')) : null,
|
|
1569
|
+
type === 'delete' && data ? React.createElement('div', { className: 'iris-gallery-management-list' },
|
|
1570
|
+
React.createElement('div', {}, '选中 ' + artifactIds.length + ' 个作品 · ' + data.files + ' 个文件 · ' + fmtBytes(data.bytes)),
|
|
1571
|
+
artifactIds.map(function (id) { return React.createElement('div', { key: id }, id); }),
|
|
1572
|
+
(data.referencingTasks || []).length ? React.createElement('div', { className: 'iris-wb-muted' }, '如需同时移除关联任务记录,请逐项勾选;未结束任务及其他引用会阻止删除。') : null,
|
|
1573
|
+
(data.referencingTasks || []).map(function (task) { return React.createElement('label', { key: task.id, className: 'iris-gallery-select' },
|
|
1574
|
+
React.createElement('input', { type: 'checkbox', 'aria-label': '关联任务 ' + task.id, checked: taskIds.indexOf(task.id) >= 0,
|
|
1575
|
+
disabled: busy || !task.settled, onChange: function (event) { chooseTask(task.id, event.target.checked); } }),
|
|
1576
|
+
'同时移除任务记录 ' + task.id + (task.settled ? '' : '(未结束或结果未知)')); }),
|
|
1577
|
+
(data.blockers || []).map(function (blocker, index) { return React.createElement('div', { key: index, className: 'iris-wb-muted' },
|
|
1578
|
+
deletionBlockerLabel(blocker) + ':' + blocker.id + (blocker.referencedBy ? ' ← ' + blocker.referencedBy : '')); })) : null,
|
|
1579
|
+
type === 'delete' ? React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1580
|
+
React.createElement('button', { className: 'iris-pm-btn danger', type: 'button', disabled: busy || !data || !data.allowed,
|
|
1581
|
+
onClick: function () { execute(); } }, '确认移入回收区'),
|
|
1582
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', disabled: busy, onClick: function () { query(taskIds); } }, '重新预览')) : null,
|
|
1583
|
+
type === 'transactions' && data ? React.createElement('div', { className: 'iris-gallery-management-list' },
|
|
1584
|
+
data.transactions.length ? data.transactions.map(function (transaction) {
|
|
1585
|
+
var states = { __proto__: null, prepared: '准备中', committed: '已隔离', interrupted: '操作中断', restored: '已恢复', 'rolled-back': '已回退' };
|
|
1586
|
+
return React.createElement('div', { key: transaction.transactionId, className: 'iris-gallery-transaction' },
|
|
1587
|
+
React.createElement('div', {}, transaction.transactionId),
|
|
1588
|
+
React.createElement('div', { className: 'iris-wb-muted' }, (transaction.operation === 'cleanup' ? '清理' : '删除') + ' · '
|
|
1589
|
+
+ (states[transaction.state] || transaction.state) + ' · ' + transaction.files + ' 文件 · ' + fmtTime(transaction.createdAt)),
|
|
1590
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn',
|
|
1591
|
+
disabled: busy || ['prepared', 'committed', 'interrupted'].indexOf(transaction.state) < 0,
|
|
1592
|
+
onClick: function () { execute(transaction); } }, '恢复'));
|
|
1593
|
+
}) : React.createElement('div', { className: 'iris-wb-muted' }, '回收区为空。')) : null,
|
|
1594
|
+
type === 'transactions' ? React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: busy, onClick: function () { query([]); } }, '刷新回收区') : null,
|
|
1595
|
+
note ? React.createElement('div', { role: 'status', className: 'iris-clean-note', style: { whiteSpace: 'pre-wrap' } }, note) : null);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
function ArtifactAnalysis(props) {
|
|
1599
|
+
var item = props.item, action = props.action;
|
|
1600
|
+
var resultPair = React.useState(null), result = resultPair[0], setResult = resultPair[1];
|
|
1601
|
+
var optionsPair = React.useState({ question: '', chunk_height: '1200', overlap: '120' });
|
|
1602
|
+
var options = optionsPair[0], setOptions = optionsPair[1];
|
|
1603
|
+
var notePair = React.useState(''), note = notePair[0], setNote = notePair[1];
|
|
1604
|
+
var request = React.useRef(null);
|
|
1605
|
+
var view = React.useRef(null);
|
|
1606
|
+
var running = result && result.status === 'running';
|
|
1607
|
+
var text = result && result.text || '';
|
|
1608
|
+
function execute() {
|
|
1609
|
+
if (request.current) request.current.abort();
|
|
1610
|
+
var controller = new AbortController();
|
|
1611
|
+
request.current = controller;
|
|
1612
|
+
var input = { artifact_id: item.id };
|
|
1613
|
+
if (action === 'look') input.question = options.question;
|
|
1614
|
+
else {
|
|
1615
|
+
input.chunk_height = Number(options.chunk_height);
|
|
1616
|
+
input.overlap = Number(options.overlap);
|
|
1617
|
+
}
|
|
1618
|
+
setResult({ status: 'running', text: '' }); setNote('');
|
|
1619
|
+
fetch('/iris/api/actions/' + action, { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
1620
|
+
body: JSON.stringify(input), signal: controller.signal })
|
|
1621
|
+
.then(function (res) { return res.json().then(function (data) { return { ok: res.ok, data: data }; }); })
|
|
1622
|
+
.then(function (response) {
|
|
1623
|
+
if (request.current !== controller || controller.signal.aborted) return;
|
|
1624
|
+
var data = response.data;
|
|
1625
|
+
setResult({ status: !response.ok || data.ok === false ? 'failed' : (data.status || 'complete'),
|
|
1626
|
+
text: data.text || data.error || '未返回识别结果' });
|
|
1627
|
+
})
|
|
1628
|
+
.catch(function (error) {
|
|
1629
|
+
if (request.current !== controller || controller.signal.aborted) return;
|
|
1630
|
+
setResult({ status: 'failed', text: '识别请求失败:' + error.message });
|
|
1631
|
+
})
|
|
1632
|
+
.finally(function () { if (request.current === controller) request.current = null; });
|
|
1633
|
+
}
|
|
1634
|
+
function cancel() {
|
|
1635
|
+
if (request.current) request.current.abort();
|
|
1636
|
+
request.current = null;
|
|
1637
|
+
setResult({ status: 'canceled', text: '' });
|
|
1638
|
+
}
|
|
1639
|
+
React.useEffect(function () {
|
|
1640
|
+
execute();
|
|
1641
|
+
if (view.current) view.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
1642
|
+
return function () { if (request.current) request.current.abort(); request.current = null; };
|
|
1643
|
+
}, []);
|
|
1644
|
+
function option(key, value) { setOptions(function (current) { var next = Object.assign({}, current); next[key] = value; return next; }); }
|
|
1645
|
+
function copy() {
|
|
1646
|
+
if (!navigator.clipboard || typeof navigator.clipboard.writeText !== 'function') { setNote('请选中下方文字手动复制'); return; }
|
|
1647
|
+
navigator.clipboard.writeText(text).then(function () { setNote('已复制识别结果'); }).catch(function () { setNote('复制失败,请手动选择文字'); });
|
|
1648
|
+
}
|
|
1649
|
+
function download() {
|
|
1650
|
+
var url = URL.createObjectURL(new Blob([text], { type: 'text/plain;charset=utf-8' }));
|
|
1651
|
+
var link = document.createElement('a'); link.href = url; link.download = item.id + '-' + action + '.txt'; link.click();
|
|
1652
|
+
setTimeout(function () { URL.revokeObjectURL(url); }, 0);
|
|
1653
|
+
}
|
|
1654
|
+
var status = result && result.status || 'running';
|
|
1655
|
+
var labels = { running: '识别中…', complete: '完成', partial: '部分完成', failed: '失败', canceled: '已取消' };
|
|
1656
|
+
return React.createElement('section', { ref: view, className: 'iris-gallery-analysis', 'aria-label': '作品识别', 'aria-busy': !!running },
|
|
1657
|
+
artifactPreview(item),
|
|
1658
|
+
React.createElement('div', { className: 'iris-gallery-analysis-body' },
|
|
1659
|
+
React.createElement('div', { className: 'iris-gallery-analysis-head' },
|
|
1660
|
+
React.createElement('strong', {}, action === 'look' ? '看图结果' : 'OCR 结果'),
|
|
1661
|
+
React.createElement('span', { role: 'status', className: 'iris-wb-badge ' + (status === 'partial' ? 'warn' : status === 'failed' ? 'err' : status === 'complete' ? 'ok' : '') }, labels[status]),
|
|
1662
|
+
running ? React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: cancel }, '取消识别') : null,
|
|
1663
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: props.onClose }, '关闭结果')),
|
|
1664
|
+
React.createElement('details', {}, React.createElement('summary', {}, '更多选项'),
|
|
1665
|
+
React.createElement('div', { className: 'iris-gallery-analysis-options' }, action === 'look'
|
|
1666
|
+
? React.createElement('label', {}, '看图问题', React.createElement('input', { value: options.question, placeholder: '默认:详细描述这张图片', onChange: function (e) { option('question', e.target.value); } }))
|
|
1667
|
+
: ['chunk_height', 'overlap'].map(function (key) {
|
|
1668
|
+
return React.createElement('label', { key: key }, key === 'chunk_height' ? '分块高度' : '重叠像素',
|
|
1669
|
+
React.createElement('input', { type: 'number', min: key === 'chunk_height' ? 100 : 0, value: options[key], onChange: function (e) { option(key, e.target.value); } }));
|
|
1670
|
+
}),
|
|
1671
|
+
React.createElement('button', { className: 'iris-clean-btn', type: 'button', disabled: !!running, onClick: execute }, '按当前选项重新识别'))),
|
|
1672
|
+
text ? React.createElement('textarea', { 'aria-label': '识别结果', readOnly: true, value: text }) : null,
|
|
1673
|
+
text ? React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1674
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: copy }, '复制文本'),
|
|
1675
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: download }, '下载文本')) : null,
|
|
1676
|
+
note ? React.createElement('div', { role: 'status', className: 'iris-clean-note' }, note) : null));
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
function ArtifactImageEdit(props) {
|
|
1680
|
+
var item = props.item;
|
|
1681
|
+
var promptPair = React.useState(''), prompt = promptPair[0], setPrompt = promptPair[1];
|
|
1682
|
+
var modelPair = React.useState(''), model = modelPair[0], setModel = modelPair[1];
|
|
1683
|
+
var modelsPair = React.useState(null), models = modelsPair[0], setModels = modelsPair[1];
|
|
1684
|
+
var resultPair = React.useState(null), result = resultPair[0], setResult = resultPair[1];
|
|
1685
|
+
var request = React.useRef(null);
|
|
1686
|
+
var running = result && result.status === 'running';
|
|
1687
|
+
React.useEffect(function () {
|
|
1688
|
+
var active = true;
|
|
1689
|
+
postAction('providers_list', {}).then(function (response) {
|
|
1690
|
+
if (!active) return;
|
|
1691
|
+
if (!response.ok || !response.d.ok) throw new Error('模型列表读取失败');
|
|
1692
|
+
var choices = [];
|
|
1693
|
+
(response.d.providers || []).forEach(function (provider) {
|
|
1694
|
+
if (!provider.enabled) return;
|
|
1695
|
+
(provider.models || []).forEach(function (entry) {
|
|
1696
|
+
if ((entry.capabilities || []).indexOf('image-gen') < 0
|
|
1697
|
+
|| !entry.imageRouting || entry.imageRouting.mediaProtocol !== 'openai-chat-images') return;
|
|
1698
|
+
var health = entry.health && entry.health['image-gen'];
|
|
1699
|
+
choices.push({ id: provider.id + '::' + entry.id, label: provider.name + ' · ' + entry.id,
|
|
1700
|
+
blocked: !!(health && health.rateLimited) });
|
|
1701
|
+
});
|
|
1702
|
+
});
|
|
1703
|
+
setModels(choices);
|
|
1704
|
+
}).catch(function (error) { if (active) { setModels([]); setResult({ status: 'failed', text: error.message }); } });
|
|
1705
|
+
return function () { active = false; if (request.current) request.current.abort(); request.current = null; };
|
|
1706
|
+
}, []);
|
|
1707
|
+
function execute() {
|
|
1708
|
+
if (request.current || !prompt.trim()) return;
|
|
1709
|
+
var controller = new AbortController(); request.current = controller;
|
|
1710
|
+
setResult({ status: 'running', text: '' });
|
|
1711
|
+
fetch('/iris/api/actions/image_edit', { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
1712
|
+
body: JSON.stringify({ source_artifact_id: item.id, prompt: prompt.trim(), ...(model ? { model: model } : {}) }), signal: controller.signal })
|
|
1713
|
+
.then(function (res) { return res.json().then(function (data) { return { ok: res.ok && data.ok !== false, data: data }; }); })
|
|
1714
|
+
.then(function (response) {
|
|
1715
|
+
if (request.current !== controller || controller.signal.aborted) return;
|
|
1716
|
+
var data = response.data;
|
|
1717
|
+
setResult({ status: response.ok ? 'complete' : 'failed', text: data.text || data.error || '未返回编辑结果',
|
|
1718
|
+
artifactIds: response.ok ? data.artifactIds || [] : [], taskId: data.taskId });
|
|
1719
|
+
if (response.ok) window.dispatchEvent(new CustomEvent('iris-core-artifacts-changed'));
|
|
1720
|
+
})
|
|
1721
|
+
.catch(function (error) {
|
|
1722
|
+
if (request.current === controller && !controller.signal.aborted) setResult({ status: 'failed', text: '改图请求失败:' + error.message + '。请在任务区检查结果。' });
|
|
1723
|
+
})
|
|
1724
|
+
.finally(function () { if (request.current === controller) request.current = null; });
|
|
1725
|
+
}
|
|
1726
|
+
function cancel() {
|
|
1727
|
+
if (request.current) request.current.abort(); request.current = null;
|
|
1728
|
+
setResult({ status: 'canceled', text: '已停止等待,请在任务区检查是否已生成。不会自动重新提交。' });
|
|
1729
|
+
}
|
|
1730
|
+
return React.createElement('section', { className: 'iris-gallery-analysis', 'aria-label': '作品改图', 'aria-busy': !!running },
|
|
1731
|
+
artifactPreview(item),
|
|
1732
|
+
React.createElement('div', { className: 'iris-gallery-analysis-body' },
|
|
1733
|
+
React.createElement('div', { className: 'iris-gallery-analysis-head' }, React.createElement('strong', {}, '编辑图片'),
|
|
1734
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: props.onClose }, '关闭改图')),
|
|
1735
|
+
React.createElement('div', { className: 'iris-wb-muted' }, '原图保留,修改版会保存为新作品。此操作会调用生图模型。'),
|
|
1736
|
+
React.createElement('label', {}, '修改指令', React.createElement('textarea', { 'aria-label': '修改指令', value: prompt, disabled: !!running,
|
|
1737
|
+
placeholder: '例如:把蓝色发饰改成白色,保留人物、服装和构图', onChange: function (e) { setPrompt(e.target.value); } })),
|
|
1738
|
+
React.createElement('label', {}, '改图模型', React.createElement('select', { 'aria-label': '改图模型', value: model, disabled: !!running,
|
|
1739
|
+
onChange: function (e) { setModel(e.target.value); } }, React.createElement('option', { value: '' }, '按当前顺序选择可用改图模型'),
|
|
1740
|
+
(models || []).map(function (entry) { return React.createElement('option', { key: entry.id, value: entry.id, disabled: entry.blocked }, entry.label + (entry.blocked ? ' · 冷却或额度耗尽' : '')); }))),
|
|
1741
|
+
models && !models.some(function (entry) { return !entry.blocked; }) ? React.createElement('div', { role: 'status' }, '暂无可用改图模型,请在模型设置中配置聊天生图协议,或手动实测恢复模型。') : null,
|
|
1742
|
+
React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1743
|
+
React.createElement('button', { className: 'iris-clean-btn', type: 'button', disabled: !!running || !prompt.trim() || !models || !models.some(function (entry) { return !entry.blocked; }), onClick: execute }, running ? '编辑中…' : '生成修改版'),
|
|
1744
|
+
running ? React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: cancel }, '停止等待') : null),
|
|
1745
|
+
result ? React.createElement('div', { role: 'status', className: 'iris-clean-note', style: { whiteSpace: 'pre-wrap' } }, result.text || '编辑中…',
|
|
1746
|
+
result.taskId ? React.createElement('div', {}, 'task: ' + result.taskId) : null) : null,
|
|
1747
|
+
result && result.artifactIds ? result.artifactIds.map(function (id) { return React.createElement('div', { key: id },
|
|
1748
|
+
React.createElement('a', { href: '/iris/api/core/artifact/' + encodeURIComponent(id) + '/media', target: '_blank', rel: 'noreferrer' }, '打开新作品:' + id)); }) : null));
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
function ArtifactGallery() {
|
|
1752
|
+
var dataPair = React.useState({ total: 0, offset: 0, limit: 24, items: [], kinds: [], counts: { core: 0, legacy: 0 } });
|
|
1753
|
+
var data = dataPair[0], setData = dataPair[1];
|
|
1754
|
+
var queryPair = React.useState({ offset: 0, media_type: 'all', source: 'all', kind: 'all' });
|
|
1755
|
+
var query = queryPair[0], setQuery = queryPair[1];
|
|
1756
|
+
var currentQuery = React.useRef(query); currentQuery.current = query;
|
|
1757
|
+
var busyPair = React.useState(false), busy = busyPair[0], setBusy = busyPair[1];
|
|
1758
|
+
var mutationPair = React.useState(false), mutating = mutationPair[0], setMutating = mutationPair[1];
|
|
1380
1759
|
var notePair = React.useState('');
|
|
1381
1760
|
var note = notePair[0];
|
|
1382
1761
|
var setNote = notePair[1];
|
|
1762
|
+
var analysisPair = React.useState(null), analysis = analysisPair[0], setAnalysis = analysisPair[1];
|
|
1763
|
+
var selectionPair = React.useState([]), selected = selectionPair[0], setSelected = selectionPair[1];
|
|
1764
|
+
var managementPair = React.useState(null), management = managementPair[0], setManagement = managementPair[1];
|
|
1765
|
+
var operationNotePair = React.useState(''), operationNote = operationNotePair[0], setOperationNote = operationNotePair[1];
|
|
1766
|
+
var request = React.useRef(null), sequence = React.useRef(0);
|
|
1767
|
+
var downloadRequest = React.useRef(null);
|
|
1383
1768
|
|
|
1384
|
-
function load(
|
|
1385
|
-
if (
|
|
1769
|
+
function load() {
|
|
1770
|
+
if (request.current) request.current.abort();
|
|
1771
|
+
var controller = new AbortController(); request.current = controller;
|
|
1386
1772
|
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
|
-
}
|
|
1773
|
+
var input = currentQuery.current;
|
|
1774
|
+
var url = '/iris/api/works?offset=' + input.offset + '&limit=24&media_type=' + input.media_type
|
|
1775
|
+
+ '&source=' + input.source + '&kind=' + encodeURIComponent(input.kind);
|
|
1776
|
+
fetch(url, { signal: controller.signal })
|
|
1777
|
+
.then(function (res) { if (!res.ok) throw new Error(res.status === 404 ? '请重新加载 DSH 后端以启用作品管理' : '作品读取失败(HTTP ' + res.status + ')'); return res.json(); })
|
|
1778
|
+
.then(function (value) {
|
|
1779
|
+
if (request.current !== controller || controller.signal.aborted) return;
|
|
1780
|
+
setData(value);
|
|
1781
|
+
setNote(value.coreError ? 'Core 作品暂时不可用,当前计数仅含可读取作品。'
|
|
1782
|
+
: value.droppedCore ? '已跳过 ' + value.droppedCore + ' 条不可读取的 Core 作品。' : '');
|
|
1407
1783
|
})
|
|
1408
|
-
.catch(function (error) {
|
|
1409
|
-
.finally(function () { setBusy(false); });
|
|
1784
|
+
.catch(function (error) { if (request.current === controller && !controller.signal.aborted) setNote(error.message); })
|
|
1785
|
+
.finally(function () { if (request.current === controller) { request.current = null; setBusy(false); } });
|
|
1410
1786
|
}
|
|
1411
1787
|
|
|
1412
1788
|
function run(action, body, confirmText) {
|
|
1413
1789
|
if (confirmText && !window.confirm(confirmText)) return;
|
|
1414
|
-
|
|
1790
|
+
setMutating(true);
|
|
1415
1791
|
postAction(action, body || {}).then(function (result) {
|
|
1416
1792
|
var succeeded = result.ok && result.d && result.d.ok !== false;
|
|
1417
1793
|
setNote(succeeded ? (result.d.text || '完成') : ('失败:' + ((result.d && (result.d.error || result.d.text)) || '未知错误')));
|
|
1418
|
-
if (succeeded)
|
|
1419
|
-
|
|
1420
|
-
|
|
1794
|
+
if (succeeded) {
|
|
1795
|
+
if (action === 'artifacts_clear' || action === 'artifacts_delete') setSelected(function (current) {
|
|
1796
|
+
return current.filter(function (item) { return item.source !== 'legacy' || (action !== 'artifacts_clear' && item.id !== body.artifact_id); });
|
|
1797
|
+
});
|
|
1798
|
+
load();
|
|
1799
|
+
}
|
|
1800
|
+
}).catch(function (error) { setNote('失败:' + error.message); }).finally(function () { setMutating(false); });
|
|
1421
1801
|
}
|
|
1422
1802
|
|
|
1423
1803
|
React.useEffect(function () {
|
|
1424
|
-
function changed() { load(
|
|
1804
|
+
function changed() { load(); }
|
|
1425
1805
|
window.addEventListener('iris-core-artifacts-changed', changed);
|
|
1426
1806
|
window.addEventListener('iris-core-refresh-tick', changed);
|
|
1427
|
-
load(
|
|
1807
|
+
load();
|
|
1428
1808
|
return function () {
|
|
1429
1809
|
window.removeEventListener('iris-core-artifacts-changed', changed);
|
|
1430
1810
|
window.removeEventListener('iris-core-refresh-tick', changed);
|
|
1811
|
+
if (request.current) request.current.abort(); request.current = null;
|
|
1431
1812
|
};
|
|
1432
|
-
}, [
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1813
|
+
}, [query.offset, query.media_type, query.source, query.kind]);
|
|
1814
|
+
React.useEffect(function () { return function () { if (downloadRequest.current) downloadRequest.current.abort(); downloadRequest.current = null; }; }, []);
|
|
1815
|
+
function filter(key, value) {
|
|
1816
|
+
setSelected([]); if (management && management.type === 'delete') setManagement(null);
|
|
1817
|
+
setQuery(function (current) { var next = Object.assign({}, current, { offset: 0 }); next[key] = value; if (key !== 'kind') next.kind = 'all'; return next; });
|
|
1818
|
+
}
|
|
1819
|
+
function page(offset) { setQuery(function (current) { return Object.assign({}, current, { offset: offset }); }); }
|
|
1820
|
+
function analyze(item, action) { setManagement(null); sequence.current++; setAnalysis({ item: item, action: action, key: sequence.current }); }
|
|
1821
|
+
function manage(type, items) { setAnalysis(null); sequence.current++; setManagement({ type: type, items: items, item: items && items[0], key: sequence.current }); }
|
|
1822
|
+
function selectedItem(item) { return selected.some(function (value) { return value.source === item.source && value.id === item.id; }); }
|
|
1823
|
+
function choose(item, checked) {
|
|
1824
|
+
if (checked && selected.length >= 200) { setOperationNote('最多选择 200 个作品'); return; }
|
|
1825
|
+
setSelected(function (current) {
|
|
1826
|
+
var present = current.some(function (value) { return value.source === item.source && value.id === item.id; });
|
|
1827
|
+
return checked ? present || current.length >= 200 ? current : current.concat(item)
|
|
1828
|
+
: current.filter(function (value) { return value.source !== item.source || value.id !== item.id; });
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
var pageSelected = data.items.length && data.items.every(selectedItem);
|
|
1832
|
+
function choosePage() {
|
|
1833
|
+
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; }); }); });
|
|
1834
|
+
else {
|
|
1835
|
+
var additions = data.items.filter(function (item) { return !selectedItem(item); });
|
|
1836
|
+
if (selected.length + additions.length > 200) { setOperationNote('最多选择 200 个作品,请减少选择后再选本页'); return; }
|
|
1837
|
+
setSelected(function (current) {
|
|
1838
|
+
var next = current.concat(additions.filter(function (item) {
|
|
1839
|
+
return !current.some(function (value) { return value.source === item.source && value.id === item.id; });
|
|
1840
|
+
}));
|
|
1841
|
+
return next.length <= 200 ? next : current;
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
function changed(value) {
|
|
1846
|
+
if (value.state === 'committed') setSelected([]);
|
|
1847
|
+
setOperationNote((value.state === 'restored' ? '已恢复:' : '已移入回收区:') + value.transactionId);
|
|
1848
|
+
window.dispatchEvent(new CustomEvent('iris-core-artifacts-changed'));
|
|
1849
|
+
}
|
|
1850
|
+
function download(items) {
|
|
1851
|
+
var controller = new AbortController(); downloadRequest.current = controller;
|
|
1852
|
+
setMutating(true); setOperationNote('正在准备下载…');
|
|
1853
|
+
fetch('/iris/api/works/download', { method: 'POST', headers: { 'Content-Type': 'application/json' }, signal: controller.signal,
|
|
1854
|
+
body: JSON.stringify({ items: items.map(function (item) { return { source: item.source, id: item.id }; }) }) })
|
|
1855
|
+
.then(function (res) { if (!res.ok) return res.json().then(function (value) { throw new Error(value.error || '下载失败'); }); return res.blob(); })
|
|
1856
|
+
.then(function (blob) { if (downloadRequest.current === controller && !controller.signal.aborted) { saveWorkbenchBlob(blob, 'iris-works.zip'); setOperationNote('已下载选中作品'); } })
|
|
1857
|
+
.catch(function (error) { if (downloadRequest.current === controller && !controller.signal.aborted) setOperationNote(error.message); })
|
|
1858
|
+
.finally(function () { if (downloadRequest.current === controller) { downloadRequest.current = null; setMutating(false); } });
|
|
1859
|
+
}
|
|
1860
|
+
function cancelDownload() { if (downloadRequest.current) downloadRequest.current.abort(); downloadRequest.current = null; setMutating(false); setOperationNote('已取消下载'); }
|
|
1861
|
+
var pageNumber = Math.floor(data.offset / data.limit) + 1;
|
|
1862
|
+
var pageCount = Math.max(1, Math.ceil(data.total / data.limit));
|
|
1863
|
+
var blocked = busy || mutating;
|
|
1437
1864
|
|
|
1438
1865
|
return React.createElement('div', {},
|
|
1439
1866
|
React.createElement('div', { className: 'iris-gallery-tools' },
|
|
1440
1867
|
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
|
-
|
|
1868
|
+
'匹配 ' + data.total + ' 个作品 · Core ' + data.counts.core + ' · 旧版 ' + data.counts.legacy + ' · 清任务历史不会删除'),
|
|
1869
|
+
React.createElement('button', { className: 'iris-clean-btn', disabled: blocked, onClick: load }, '刷新'),
|
|
1870
|
+
React.createElement('button', { className: 'iris-clean-btn', disabled: mutating, onClick: function () { manage('transactions'); } }, 'Core 回收区'),
|
|
1871
|
+
React.createElement('button', { className: 'iris-clean-btn', disabled: blocked, onClick: function () { run('artifacts_reindex'); } }, '扫描旧版 outputs'),
|
|
1872
|
+
data.counts.legacy ? React.createElement('button', { className: 'iris-clean-btn danger', disabled: blocked, onClick: function () { run('artifacts_clear', {}, '永久删除旧版 outputs 作品?Core 作品不会删除;任务历史会保留。此操作不可逆。'); } }, '清空旧版作品') : null),
|
|
1873
|
+
React.createElement('div', { className: 'iris-gallery-filters' },
|
|
1874
|
+
React.createElement('label', {}, '媒体类型', React.createElement('select', { 'aria-label': '媒体类型', value: query.media_type, disabled: mutating && !!management, onChange: function (e) { filter('media_type', e.target.value); } },
|
|
1875
|
+
[['all', '全部媒体'], ['image', '图片'], ['video', '视频'], ['audio', '音频'], ['text', '文本']].map(function (entry) { return React.createElement('option', { key: entry[0], value: entry[0] }, entry[1]); }))),
|
|
1876
|
+
React.createElement('label', {}, '作品来源', React.createElement('select', { 'aria-label': '作品来源', value: query.source, disabled: mutating && !!management, onChange: function (e) { filter('source', e.target.value); } },
|
|
1877
|
+
[['all', '全部来源'], ['core', 'Core'], ['legacy', '旧版']].map(function (entry) { return React.createElement('option', { key: entry[0], value: entry[0] }, entry[1]); }))),
|
|
1878
|
+
React.createElement('label', {}, '产物类型', React.createElement('select', { 'aria-label': '产物类型', value: query.kind, disabled: mutating && !!management, onChange: function (e) { filter('kind', e.target.value); } },
|
|
1879
|
+
['all'].concat(data.kinds).map(function (kind) { return React.createElement('option', { key: kind, value: kind }, kind === 'all' ? '全部产物' : galleryKindLabel(kind)); })))),
|
|
1880
|
+
React.createElement('div', { className: 'iris-gallery-bulk' },
|
|
1881
|
+
React.createElement('span', { className: 'iris-wb-muted', role: 'status' }, '已选择 ' + selected.length + ' / 200 · 下载最多 128 MiB'),
|
|
1882
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: blocked || !data.items.length, onClick: choosePage }, pageSelected ? '取消本页选择' : '选择本页'),
|
|
1883
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: mutating || !selected.length,
|
|
1884
|
+
onClick: function () { setSelected([]); if (management && management.type === 'delete') setManagement(null); } }, '清空选择'),
|
|
1885
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: !selected.length,
|
|
1886
|
+
onClick: function () { copyWorkbenchText(selected.map(function (item) { return item.id; }).join('\n'), setOperationNote); } }, '复制选中 ID'),
|
|
1887
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: mutating || !selected.length, onClick: function () { download(selected); } }, '下载选中作品'),
|
|
1888
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-btn danger', disabled: mutating || !selected.length || selected.some(function (item) { return item.source !== 'core'; }),
|
|
1889
|
+
title: '仅 Core 作品支持可恢复批量删除;请先按来源筛选', onClick: function () { manage('delete', selected); } }, '删除选中 Core 作品'),
|
|
1890
|
+
downloadRequest.current ? React.createElement('button', { type: 'button', className: 'iris-pm-btn', onClick: cancelDownload }, '取消下载') : null),
|
|
1891
|
+
analysis ? React.createElement(analysis.action === 'image_edit' ? ArtifactImageEdit : ArtifactAnalysis, { key: analysis.key, item: analysis.item, action: analysis.action, onClose: function () { setAnalysis(null); } }) : null,
|
|
1892
|
+
management ? React.createElement(WorkbenchManagement, { key: management.key, type: management.type, items: management.items, item: management.item,
|
|
1893
|
+
onBusy: setMutating, onChanged: changed, onClose: function () { setManagement(null); } }) : null,
|
|
1894
|
+
data.items.length ? React.createElement('div', { className: 'iris-gallery-grid', 'aria-busy': busy }, data.items.map(function (item) {
|
|
1446
1895
|
var isCore = item.source === 'core';
|
|
1447
|
-
return React.createElement('div', { key: (isCore ? 'core-' : 'legacy-') + item.id, className: 'iris-gallery-card' },
|
|
1896
|
+
return React.createElement('div', { key: (isCore ? 'core-' : 'legacy-') + item.id, className: 'iris-gallery-card' + (selectedItem(item) ? ' selected' : '') },
|
|
1448
1897
|
artifactPreview(item),
|
|
1449
1898
|
React.createElement('div', { className: 'iris-gallery-info' },
|
|
1899
|
+
React.createElement('label', { className: 'iris-gallery-select' }, React.createElement('input', { type: 'checkbox',
|
|
1900
|
+
'aria-label': '选择作品 ' + item.id, checked: selectedItem(item), disabled: mutating || (!selectedItem(item) && selected.length >= 200),
|
|
1901
|
+
onChange: function (event) { choose(item, event.target.checked); } }), '选择'),
|
|
1450
1902
|
React.createElement('div', { className: 'iris-gallery-file', title: item.file }, item.file),
|
|
1451
1903
|
React.createElement('div', { className: 'iris-gallery-meta' },
|
|
1452
|
-
(isCore ? 'Core · ' + (item.kind
|
|
1904
|
+
(isCore ? 'Core · ' + galleryKindLabel(item.kind) : '旧版') + ' · ' + fmtTime(item.createdAt) + ' · ' + fmtBytes(item.size)),
|
|
1453
1905
|
React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1454
1906
|
React.createElement('a', { className: 'iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开'),
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1907
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', disabled: mutating, onClick: function () { manage('details', [item]); } }, '详情'),
|
|
1908
|
+
isCore ? React.createElement('button', { className: 'iris-pm-btn danger', type: 'button', disabled: mutating, onClick: function () { manage('delete', [item]); } }, '移入回收区') : null,
|
|
1909
|
+
isCore && /^image\/(png|jpeg|webp|gif)$/.test(item.mime) ? ['look', 'ocr'].map(function (action) {
|
|
1910
|
+
return React.createElement('button', { key: action, type: 'button', className: 'iris-pm-btn', disabled: mutating, onClick: function () { analyze(item, action); } }, action === 'look' ? '看图' : 'OCR');
|
|
1911
|
+
}) : null,
|
|
1912
|
+
isCore && /^image\/(png|jpeg|webp)$/.test(item.mime) ? React.createElement('button', { type: 'button', className: 'iris-pm-btn', disabled: mutating,
|
|
1913
|
+
onClick: function () { analyze(item, 'image_edit'); } }, '改图') : null,
|
|
1914
|
+
isCore ? React.createElement('span', { className: 'iris-wb-muted', title: '打开或识别时核验原文件' }, '内容哈希保护')
|
|
1915
|
+
: React.createElement('button', { className: 'iris-pm-btn danger', disabled: blocked, onClick: function () { run('artifacts_delete', { artifact_id: item.id }, '永久删除作品“' + item.file + '”?任务历史会保留,但媒体将无法打开。'); } }, '删除'))));
|
|
1916
|
+
})) : React.createElement('div', { className: 'iris-wb-empty' }, busy ? '正在读取作品…'
|
|
1917
|
+
: query.media_type !== 'all' || query.source !== 'all' || query.kind !== 'all' ? '没有匹配的作品,请调整筛选条件。' : '尚无作品;旧 outputs 文件可点“扫描旧版 outputs”找回。'),
|
|
1918
|
+
React.createElement('div', { className: 'iris-gallery-pagination' },
|
|
1919
|
+
React.createElement('button', { type: 'button', className: 'iris-clean-btn', disabled: blocked || pageNumber <= 1, onClick: function () { page(Math.max(0, data.offset - data.limit)); } }, '上一页'),
|
|
1920
|
+
React.createElement('span', { className: 'iris-wb-muted', role: 'status' }, '第 ' + pageNumber + ' / ' + pageCount + ' 页'),
|
|
1921
|
+
React.createElement('button', { type: 'button', className: 'iris-clean-btn', disabled: blocked || pageNumber >= pageCount, onClick: function () { page(data.offset + data.limit); } }, '下一页')),
|
|
1922
|
+
note ? React.createElement('div', { className: 'iris-clean-note', style: { marginTop: '7px' } }, note) : null,
|
|
1923
|
+
operationNote ? React.createElement('div', { role: 'status', className: 'iris-clean-note', style: { marginTop: '7px', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, operationNote) : null);
|
|
1460
1924
|
}
|
|
1461
1925
|
|
|
1462
1926
|
function coreCapabilityLabel(value) {
|
|
@@ -1669,9 +2133,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1669
2133
|
if (draft === null) return null;
|
|
1670
2134
|
draft = String(draft).trim();
|
|
1671
2135
|
if (!draft) return null;
|
|
1672
|
-
|
|
2136
|
+
var sourceArtifactId;
|
|
2137
|
+
if (props.row.sourceArtifactId) {
|
|
2138
|
+
sourceArtifactId = window.prompt('请确认新改图任务使用的原图 Artifact ID:', props.row.sourceArtifactId);
|
|
2139
|
+
if (sourceArtifactId === null || !String(sourceArtifactId).trim()) return null;
|
|
2140
|
+
}
|
|
2141
|
+
return isTts ? { text: draft, confirmBilling: true }
|
|
1673
2142
|
: isTranscribe ? { audio_url: draft, confirmBilling: true }
|
|
1674
|
-
: { prompt: draft, confirmBilling: true };
|
|
2143
|
+
: { prompt: draft, confirmBilling: true, ...(sourceArtifactId ? { source_artifact_id: String(sourceArtifactId).trim() } : {}) };
|
|
1675
2144
|
}
|
|
1676
2145
|
});
|
|
1677
2146
|
}
|
|
@@ -1918,7 +2387,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1918
2387
|
React.createElement(CoreRetryButton, {
|
|
1919
2388
|
row: selectedTask && selectedTask.phase === 'terminal'
|
|
1920
2389
|
&& !(selectedTask.outcome === 'succeeded' && selectedTask.deliveryState === 'ready')
|
|
1921
|
-
? { id: selectedTask.id, retryable: true } : null
|
|
2390
|
+
? { id: selectedTask.id, capability: selectedTask.capability, sourceArtifactId: selectedTask.sourceArtifactId, retryable: true } : null
|
|
1922
2391
|
}),
|
|
1923
2392
|
React.createElement(CoreDispositionButtons, {
|
|
1924
2393
|
row: selectedDisposition
|
|
@@ -2105,7 +2574,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2105
2574
|
React.createElement(ProviderManager, {}),
|
|
2106
2575
|
React.createElement('div', { className: 'iris-wb-sec' }, '能力健康'),
|
|
2107
2576
|
React.createElement(CapabilityHealthOverview, {}),
|
|
2108
|
-
React.createElement('div', { className: 'iris-wb-muted' }, '绿色有效 7
|
|
2577
|
+
React.createElement('div', { className: 'iris-wb-muted' }, '绿色有效 7 天;真实成功或显式实测会刷新。429 冷却显示琥珀色,额度或预算耗尽显示红色;点击模型能力可实测恢复。不会后台探测。'),
|
|
2109
2578
|
React.createElement('div', { className: 'iris-wb-sec' }, '能力分配(failover 顺序)'),
|
|
2110
2579
|
React.createElement(CapabilityAssigner, {}),
|
|
2111
2580
|
React.createElement('div', { className: 'iris-wb-sec' }, 'Iris 泡泡快捷卡片(勾选后显示在「⚡ 常用」标签)'),
|
|
@@ -2205,6 +2674,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2205
2674
|
selectedTask ? React.createElement(TaskDetailDrawer, { taskId: selectedTask }) : null);
|
|
2206
2675
|
}
|
|
2207
2676
|
|
|
2677
|
+
// 折叠相同首尾,只显示中间替换区;按 Unicode code point 比较,不拆开 emoji。
|
|
2678
|
+
function promptChanges(original, edited) {
|
|
2679
|
+
var before = Array.from(original);
|
|
2680
|
+
var after = Array.from(edited);
|
|
2681
|
+
var start = 0;
|
|
2682
|
+
while (start < before.length && start < after.length && before[start] === after[start]) start++;
|
|
2683
|
+
var end = 0;
|
|
2684
|
+
while (end < before.length - start && end < after.length - start && before[before.length - end - 1] === after[after.length - end - 1]) end++;
|
|
2685
|
+
return { removed: before.slice(start, before.length - end).join(''), added: after.slice(start, after.length - end).join('') };
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2208
2688
|
function PromptOptimizerControl(props) {
|
|
2209
2689
|
var draft = props.useInput(function (value) { return value.draft; });
|
|
2210
2690
|
var phase = props.useInput(function (value) { return value.phase; });
|
|
@@ -2230,8 +2710,26 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2230
2710
|
var configPair = React.useState(null);
|
|
2231
2711
|
var configState = configPair[0];
|
|
2232
2712
|
var setConfigState = configPair[1];
|
|
2713
|
+
var optimizationPair = React.useState('');
|
|
2714
|
+
var optimizationText = optimizationPair[0];
|
|
2715
|
+
var setOptimizationText = optimizationPair[1];
|
|
2716
|
+
var prefixPair = React.useState('');
|
|
2717
|
+
var prefixText = prefixPair[0];
|
|
2718
|
+
var setPrefixText = prefixPair[1];
|
|
2719
|
+
var suffixPair = React.useState('');
|
|
2720
|
+
var suffixText = suffixPair[0];
|
|
2721
|
+
var setSuffixText = suffixPair[1];
|
|
2722
|
+
var editedPair = React.useState('');
|
|
2723
|
+
var edited = editedPair[0];
|
|
2724
|
+
var setEdited = editedPair[1];
|
|
2233
2725
|
var abortRef = React.useRef(null);
|
|
2234
2726
|
var fileRef = React.useRef(null);
|
|
2727
|
+
var lastWrittenRef = React.useRef(null);
|
|
2728
|
+
var resultSessionRef = React.useRef(null);
|
|
2729
|
+
var sessionRef = React.useRef(props.sessionId);
|
|
2730
|
+
sessionRef.current = props.sessionId;
|
|
2731
|
+
var supportsRules = !!(configState && configState.capabilities && configState.capabilities.rules === true);
|
|
2732
|
+
var supportsAssembly = !!(configState && configState.capabilities && configState.capabilities.assemble === true);
|
|
2235
2733
|
|
|
2236
2734
|
function promptRequest(operation, body, signal) {
|
|
2237
2735
|
return fetch('/iris/api/prompt-optimizer/' + operation, {
|
|
@@ -2257,6 +2755,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2257
2755
|
React.useEffect(function () {
|
|
2258
2756
|
return function () {
|
|
2259
2757
|
if (abortRef.current) abortRef.current.abort();
|
|
2758
|
+
abortRef.current = null;
|
|
2260
2759
|
};
|
|
2261
2760
|
}, []);
|
|
2262
2761
|
|
|
@@ -2267,53 +2766,96 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2267
2766
|
return function () { window.removeEventListener('iris-prompt-config-changed', changed); };
|
|
2268
2767
|
}, []);
|
|
2269
2768
|
|
|
2769
|
+
React.useEffect(function () {
|
|
2770
|
+
cancel();
|
|
2771
|
+
setResult(null);
|
|
2772
|
+
lastWrittenRef.current = null;
|
|
2773
|
+
resultSessionRef.current = null;
|
|
2774
|
+
}, [props.sessionId]);
|
|
2775
|
+
|
|
2270
2776
|
function publishConfig(data) {
|
|
2271
2777
|
setConfigState(data);
|
|
2272
2778
|
try { window.dispatchEvent(new CustomEvent('iris-prompt-config-changed', { detail: data })); } catch (_) {}
|
|
2273
2779
|
}
|
|
2274
2780
|
|
|
2275
|
-
function
|
|
2781
|
+
function selectedRules(mode) {
|
|
2782
|
+
var rules = [];
|
|
2783
|
+
if (!supportsRules) return rules;
|
|
2784
|
+
if (mode === 'optimize' && optimizationText.trim()) rules.push({ id: 'custom-optimization', label: '本次改写规则', kind: 'optimization', text: optimizationText });
|
|
2785
|
+
if (prefixText.trim()) rules.push({ id: 'custom-prefix', label: '输出前缀', kind: 'output', position: 'prefix', text: prefixText });
|
|
2786
|
+
if (suffixText.trim()) rules.push({ id: 'custom-suffix', label: '输出后缀', kind: 'output', position: 'suffix', text: suffixText });
|
|
2787
|
+
return rules;
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
function optimize(mode) {
|
|
2791
|
+
mode = mode === 'assemble' ? 'assemble' : 'optimize';
|
|
2792
|
+
if (mode === 'assemble' && !supportsAssembly) { setNote('当前后端尚未支持只组装,请更新并重新载入'); return; }
|
|
2276
2793
|
if (!draft || !draft.trim()) { setNote('请先在输入框中写入提示词'); return; }
|
|
2277
2794
|
if (refCount) { setNote('当前版本暂不改写含 @ 或 / 结构化引用的草稿,以免引用失效'); return; }
|
|
2278
2795
|
if (phase !== 'plain') { setNote('输入框正忙,请稍后再试'); return; }
|
|
2279
2796
|
if (abortRef.current) abortRef.current.abort();
|
|
2280
2797
|
var controller = new AbortController();
|
|
2798
|
+
var requestSessionId = props.sessionId;
|
|
2281
2799
|
abortRef.current = controller;
|
|
2282
2800
|
setBusy(true);
|
|
2283
|
-
setNote('正在调用模型优化…这可能产生模型费用');
|
|
2801
|
+
setNote(mode === 'assemble' ? '正在组装,不调用模型…' : '正在调用模型优化…这可能产生模型费用');
|
|
2284
2802
|
setResult(null);
|
|
2803
|
+
lastWrittenRef.current = null;
|
|
2285
2804
|
promptRequest('optimize', {
|
|
2286
2805
|
text: draft,
|
|
2287
2806
|
target: target,
|
|
2807
|
+
mode: mode,
|
|
2808
|
+
rules: selectedRules(mode),
|
|
2288
2809
|
sessionId: props.sessionId,
|
|
2289
2810
|
route: selectedRoute || undefined
|
|
2290
2811
|
}, controller.signal).then(function (data) {
|
|
2812
|
+
if (abortRef.current !== controller || controller.signal.aborted) return;
|
|
2813
|
+
if (sessionRef.current !== requestSessionId) throw new Error('会话已切换,请在当前会话重新生成预览');
|
|
2814
|
+
if (!data.ok || typeof data.optimized !== 'string' || typeof data.original !== 'string') throw new Error('未收到完整优化结果');
|
|
2291
2815
|
setResult(data);
|
|
2816
|
+
setEdited(data.optimized);
|
|
2817
|
+
resultSessionRef.current = requestSessionId;
|
|
2292
2818
|
setNote('已生成预览,尚未写回输入框');
|
|
2293
2819
|
}).catch(function (err) {
|
|
2820
|
+
if (abortRef.current !== controller) return;
|
|
2294
2821
|
if (controller.signal.aborted) setNote('已取消');
|
|
2295
2822
|
else setNote(String(err && err.message || err));
|
|
2296
2823
|
}).finally(function () {
|
|
2297
|
-
if (abortRef.current === controller) abortRef.current = null;
|
|
2298
|
-
setBusy(false);
|
|
2824
|
+
if (abortRef.current === controller) { abortRef.current = null; setBusy(false); }
|
|
2299
2825
|
});
|
|
2300
2826
|
}
|
|
2301
2827
|
|
|
2302
2828
|
function cancel() {
|
|
2303
|
-
if (abortRef.current)
|
|
2829
|
+
if (abortRef.current) {
|
|
2830
|
+
abortRef.current.abort();
|
|
2831
|
+
abortRef.current = null;
|
|
2832
|
+
setBusy(false);
|
|
2833
|
+
setNote('已取消');
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
function closePanel() { cancel(); setOpen(false); }
|
|
2838
|
+
|
|
2839
|
+
function validEdited() {
|
|
2840
|
+
if (!edited.trim() || edited.length > 16000) { setNote('结果必须是非空文本且不超过 16000 字符'); return false; }
|
|
2841
|
+
return true;
|
|
2304
2842
|
}
|
|
2305
2843
|
|
|
2306
2844
|
function replaceDraft(text) {
|
|
2307
2845
|
if (!result) return;
|
|
2308
|
-
if (
|
|
2846
|
+
if (resultSessionRef.current !== props.sessionId) { setNote('会话已切换,请在当前会话重新生成预览'); return; }
|
|
2847
|
+
if (refCount || phase !== 'plain') { setNote('当前输入框正忙或含结构化引用,暂不写回'); return; }
|
|
2848
|
+
if (text !== result.original && !validEdited()) return;
|
|
2849
|
+
if (draft !== result.original && draft !== lastWrittenRef.current && !window.confirm('输入框内容在优化期间已变化,仍要替换吗?')) return;
|
|
2309
2850
|
props.inputActions.setDraft(text);
|
|
2851
|
+
lastWrittenRef.current = text;
|
|
2310
2852
|
setNote(text === result.original ? '已恢复优化前原文' : '已写回输入框;不会自动发送');
|
|
2311
2853
|
}
|
|
2312
2854
|
|
|
2313
2855
|
function copyResult() {
|
|
2314
|
-
if (!result) return;
|
|
2856
|
+
if (!result || !validEdited()) return;
|
|
2315
2857
|
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
2316
|
-
navigator.clipboard.writeText(
|
|
2858
|
+
navigator.clipboard.writeText(edited).then(function () { setNote('已复制优化结果'); }).catch(function () { setNote('复制失败,请手动选择文本'); });
|
|
2317
2859
|
} else {
|
|
2318
2860
|
setNote('浏览器不支持自动复制,请手动选择文本');
|
|
2319
2861
|
}
|
|
@@ -2379,15 +2921,21 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2379
2921
|
var reasoningSetting = configState && configState.config && configState.config.generation && configState.config.generation.reasoningEffort || 'off-if-supported';
|
|
2380
2922
|
var reasoningLabel = reasoningSetting === 'off-if-supported' ? '支持时关闭' : reasoningSetting === 'provider-default' ? '供应商默认' : reasoningSetting === 'inherit' ? '跟随会话' : reasoningSetting;
|
|
2381
2923
|
var outputBudget = configState && configState.config && configState.config.generation && configState.config.generation.maxOutputTokens || 1200;
|
|
2924
|
+
var changes = result ? promptChanges(result.original, edited) : null;
|
|
2925
|
+
function ruleField(label, value, update, placeholder) {
|
|
2926
|
+
return React.createElement('label', { className: 'iris-po-field' }, React.createElement('span', {}, label),
|
|
2927
|
+
React.createElement('textarea', { value: value, disabled: busy || !supportsRules, placeholder: placeholder,
|
|
2928
|
+
onChange: function (event) { update(event.target.value); } }));
|
|
2929
|
+
}
|
|
2382
2930
|
|
|
2383
2931
|
var panel = null;
|
|
2384
2932
|
if (open) {
|
|
2385
2933
|
panel = React.createElement(React.Fragment, {},
|
|
2386
|
-
React.createElement('div', { className: 'iris-po-backdrop', onClick:
|
|
2934
|
+
React.createElement('div', { className: 'iris-po-backdrop', onClick: closePanel, 'aria-hidden': 'true' }),
|
|
2387
2935
|
React.createElement('div', { className: 'iris-po-panel', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Iris 提示词优化', onClick: function (e) { e.stopPropagation(); } },
|
|
2388
2936
|
React.createElement('div', { className: 'iris-po-head' },
|
|
2389
2937
|
React.createElement('strong', { className: 'iris-po-title' }, React.createElement('span', { className: 'iris-po-mark' }, '🫧'), 'Iris 提示词优化'),
|
|
2390
|
-
React.createElement('button', { className: 'iris-po-close', onClick:
|
|
2938
|
+
React.createElement('button', { className: 'iris-po-close', onClick: closePanel, title: '关闭' }, '✕')),
|
|
2391
2939
|
React.createElement('div', { className: 'iris-po-muted' }, '只处理当前未发送草稿,不读取会话历史;调用模型可能产生费用。'),
|
|
2392
2940
|
React.createElement('label', { className: 'iris-po-field' },
|
|
2393
2941
|
React.createElement('span', {}, '优化目标'),
|
|
@@ -2397,17 +2945,37 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2397
2945
|
React.createElement('option', { value: 'video' }, '视频生成'),
|
|
2398
2946
|
React.createElement('option', { value: 's2v' }, '首尾帧视频'))),
|
|
2399
2947
|
React.createElement('div', { className: 'iris-po-route', title: routeLabel }, '模型:' + routeLabel + ' · 思考:' + reasoningLabel + ' · 输出:' + outputBudget + ' tokens'),
|
|
2948
|
+
React.createElement('details', { className: 'iris-po-config' },
|
|
2949
|
+
React.createElement('summary', {}, '本次规则(可选)'),
|
|
2950
|
+
React.createElement('div', { className: 'iris-po-rules' },
|
|
2951
|
+
ruleField('改写规则 · 仅智能优化使用', optimizationText, setOptimizationText, '例如:保持简短,保留全部否定条件'),
|
|
2952
|
+
ruleField('输出前缀 · 原样加入结果开头', prefixText, setPrefixText, '例如:请以摄影师的视角描述以下画面。'),
|
|
2953
|
+
ruleField('输出后缀 · 原样加入结果末尾', suffixText, setSuffixText, '例如:禁止添加文字和水印。')),
|
|
2954
|
+
React.createElement('div', { className: 'iris-po-muted' }, supportsRules ? '非空内容表示本次选择;只组装保留原稿,只添加前后缀。刷新后重置,不修改 JSON 配置。' : '当前后端尚未支持规则,请更新后重新载入。')),
|
|
2400
2955
|
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 ? '
|
|
2956
|
+
React.createElement('button', { className: 'iris-po-primary', disabled: busy || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: function () { optimize('optimize'); } }, busy ? '处理中…' : '智能优化'),
|
|
2957
|
+
React.createElement('button', { className: 'iris-po-btn', disabled: busy || !supportsAssembly || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: function () { optimize('assemble'); } }, '只组装(不调用模型)'),
|
|
2402
2958
|
busy ? React.createElement('button', { className: 'iris-po-btn', onClick: cancel }, '取消') : null),
|
|
2403
2959
|
result ? React.createElement('div', { className: 'iris-po-result' },
|
|
2404
|
-
React.createElement('div', { className: 'iris-po-result-label' }, '
|
|
2405
|
-
React.createElement('textarea', {
|
|
2406
|
-
React.createElement('
|
|
2960
|
+
React.createElement('div', { className: 'iris-po-result-label' }, '结果预览 · 可编辑'),
|
|
2961
|
+
React.createElement('textarea', { 'aria-label': '结果预览', value: edited, onChange: function (event) { setEdited(event.target.value); } }),
|
|
2962
|
+
React.createElement('details', { className: 'iris-po-config' }, React.createElement('summary', {}, '原文与改动'),
|
|
2963
|
+
React.createElement('textarea', { 'aria-label': '优化前原文', readOnly: true, value: result.original }),
|
|
2964
|
+
React.createElement('div', { className: 'iris-po-muted' }, '以下折叠相同首尾,显示中间替换区:'),
|
|
2965
|
+
React.createElement('div', { className: 'iris-po-diff' },
|
|
2966
|
+
changes.removed ? React.createElement('del', {}, changes.removed) : null,
|
|
2967
|
+
changes.added ? React.createElement('ins', {}, changes.added) : null,
|
|
2968
|
+
!changes.removed && !changes.added ? '与原文相同' : null)),
|
|
2969
|
+
React.createElement('details', { className: 'iris-po-config' }, React.createElement('summary', {}, '本次参与规则'),
|
|
2970
|
+
(result.rules || []).length ? (result.rules || []).map(function (rule) {
|
|
2971
|
+
return React.createElement('div', { className: 'iris-po-diff', key: rule.id }, rule.label + '(' + (rule.kind === 'optimization' ? '指导改写' : rule.position === 'prefix' ? '输出前缀' : '输出后缀') + ')\n' + rule.text);
|
|
2972
|
+
}) : React.createElement('div', { className: 'iris-po-muted' }, '未选择额外规则'),
|
|
2973
|
+
result.processing ? React.createElement('div', { className: 'iris-po-diff' }, '优化处理规则:\n' + result.processing.systemPrompt + '\n\n目标模板:\n' + result.processing.targetTemplate) : null),
|
|
2974
|
+
React.createElement('div', { className: 'iris-po-route' }, result.route ? '实际使用:' + result.route.provider + ' / ' + result.route.model + ' · 思考:' + (result.route.reasoningEffort === 'provider-default' ? '供应商默认' : result.route.reasoningEffort) : '本次只组装,未调用模型'),
|
|
2407
2975
|
React.createElement('div', { className: 'iris-po-actions' },
|
|
2408
|
-
React.createElement('button', { className: 'iris-po-primary', onClick: function () { replaceDraft(
|
|
2976
|
+
React.createElement('button', { className: 'iris-po-primary', onClick: function () { replaceDraft(edited); } }, '写回输入框'),
|
|
2409
2977
|
React.createElement('button', { className: 'iris-po-btn', onClick: copyResult }, '复制'),
|
|
2410
|
-
React.createElement('button', { className: 'iris-po-btn', onClick: optimize }, '
|
|
2978
|
+
React.createElement('button', { className: 'iris-po-btn', disabled: busy, onClick: function () { optimize(result.mode); } }, '重新生成预览'),
|
|
2411
2979
|
React.createElement('button', { className: 'iris-po-btn', onClick: function () { replaceDraft(result.original); } }, '恢复原文'))) : null,
|
|
2412
2980
|
note ? React.createElement('div', { className: 'iris-po-note' }, note) : null,
|
|
2413
2981
|
React.createElement('details', { className: 'iris-po-config' },
|
|
@@ -2428,7 +2996,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2428
2996
|
type: 'button',
|
|
2429
2997
|
title: refCount ? '含结构化引用的草稿暂不支持优化' : '优化当前输入框提示词',
|
|
2430
2998
|
'aria-expanded': open ? 'true' : 'false',
|
|
2431
|
-
onClick: function () {
|
|
2999
|
+
onClick: function () { if (open) closePanel(); else setOpen(true); }
|
|
2432
3000
|
}, '🫧'),
|
|
2433
3001
|
panel);
|
|
2434
3002
|
}
|
|
@@ -2473,8 +3041,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2473
3041
|
var state = useIrisState(5000);
|
|
2474
3042
|
var providers = (state && state.providers) || [];
|
|
2475
3043
|
var fallbackConfigured = providers.some(function (p) { return p.enabled && p.apiKeyHint; });
|
|
2476
|
-
var
|
|
3044
|
+
var overallState = state && state.health && HEALTH_META[state.health.overall]
|
|
2477
3045
|
? state.health.overall : (fallbackConfigured ? 'configured' : 'unconfigured');
|
|
3046
|
+
var overallCooling = overallState === 'failed' && state.health.retryAt;
|
|
3047
|
+
var overallHealth = overallCooling ? 'cooling' : overallState;
|
|
2478
3048
|
var running = (state && state.tasks && state.tasks.running) || [];
|
|
2479
3049
|
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
2480
3050
|
|
|
@@ -2600,7 +3170,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2600
3170
|
onPointerMove: onPointerMove,
|
|
2601
3171
|
onPointerUp: onPointerUp,
|
|
2602
3172
|
onPointerCancel: onPointerCancel,
|
|
2603
|
-
title: 'Iris ' + healthText({ status:
|
|
3173
|
+
title: 'Iris ' + healthText({ status: overallState, rateLimited: !!overallCooling, retryAt: overallCooling || undefined }) + ':点击打开快捷面板'
|
|
2604
3174
|
},
|
|
2605
3175
|
React.createElement('div', { className: 'iris-bubble-btn' }, React.createElement(IRIS_MARK_SVG)),
|
|
2606
3176
|
badge,
|