@mokuyoaxis/dsh-iris 0.1.4 → 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 +99 -1
- package/README.en.md +124 -31
- package/README.md +124 -31
- package/bin/dsh-iris.js +593 -13
- package/docs/AGENT_QUICKSTART.md +39 -0
- package/docs/ARCHITECTURE.md +87 -64
- package/docs/ARTIFACT_LIBRARY.md +5 -5
- package/docs/ARTIFACT_MANIFEST.md +60 -0
- package/docs/CLI_MANAGEMENT.md +181 -0
- package/docs/COMPOSITE_VISION.md +35 -0
- package/docs/CORE_RUNTIME_CONTRACT.md +84 -0
- package/docs/DSH_CORE_MIGRATION.md +135 -0
- package/docs/DSH_RC2_ADAPTATION.md +38 -0
- package/docs/FAULT_INJECTION.md +12 -2
- package/docs/HEADLESS_CLI.md +223 -0
- package/docs/HOST_ADAPTER_CONTRACT.md +36 -17
- package/docs/HOST_DOCTOR.md +4 -2
- package/docs/MODEL_PORT_CONTRACT.md +305 -0
- package/docs/OCR_MODEL.md +39 -0
- package/docs/PROMPT_OPTIMIZER.md +47 -0
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +53 -10
- package/docs/PROVIDER_HEALTH.md +22 -5
- package/docs/PROVIDER_RUNTIME_HARNESS.md +33 -0
- package/docs/PROVIDER_SUBMISSION_CONTRACT.md +2 -2
- package/docs/ROADMAP.md +49 -83
- package/docs/SECURITY.md +8 -7
- 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/assets/logo/README.md +55 -0
- package/docs/assets/logo/iris-bubble-concept.svg +67 -0
- package/docs/assets/logo/iris-bubble.svg +14 -0
- package/docs/assets/logo/iris-interim-flower.png +0 -0
- package/docs/assets/logo/iris-logo-mono-inverse.svg +78 -0
- package/docs/assets/logo/iris-logo-mono.svg +78 -0
- package/docs/assets/logo/iris-logo.svg +78 -0
- package/docs/assets/logo/iris-mark-mono-inverse.svg +42 -0
- package/docs/assets/logo/iris-mark-mono.svg +42 -0
- package/docs/assets/logo/iris-mark.svg +42 -0
- package/docs/file-access-across-environments.md +8 -5
- package/docs/releases/0.2.0.md +48 -0
- package/docs/releases/0.2.1.md +56 -0
- package/lib/actions.js +520 -591
- package/lib/adapters.js +184 -36
- package/lib/api.js +200 -11
- package/lib/chromium-browser.js +117 -0
- package/lib/client.js +1390 -105
- package/lib/command-service.js +534 -0
- package/lib/composite-vision-routing.js +42 -0
- package/lib/config.js +143 -51
- package/lib/core-artifact-store.js +630 -0
- package/lib/core-artifacts.js +4 -0
- package/lib/core-attention.js +103 -0
- package/lib/core-contract.js +105 -0
- package/lib/core-doctor.js +41 -0
- package/lib/core-lease-recovery.js +170 -0
- package/lib/core-maintenance.js +213 -0
- package/lib/core-runtime.js +232 -0
- package/lib/core-tasks.js +567 -0
- package/lib/core-user-projection.js +193 -0
- package/lib/doctor.js +92 -33
- package/lib/dsh-core-adapter.js +653 -0
- package/lib/dsh-host-adapter.js +58 -7
- package/lib/dsh-text-model-adapter.js +222 -0
- package/lib/dsh-vision-model-adapter.js +75 -0
- package/lib/generation-input.js +126 -0
- package/lib/guard.js +3 -3
- 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 +323 -188
- 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 +220 -0
- package/lib/model-port-contract.js +341 -0
- package/lib/models.js +105 -14
- 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-adapter.js +49 -23
- package/lib/provider-adapters.js +165 -26
- package/lib/provider-catalog.js +183 -0
- package/lib/provider-config-service.js +236 -0
- package/lib/provider-contract.js +74 -4
- package/lib/provider-health.js +80 -1
- package/lib/provider-protocol.js +74 -2
- package/lib/provider-task-runner.js +370 -0
- package/lib/summarize.js +31 -20
- package/lib/summary-input.js +52 -0
- package/lib/task-wait.js +40 -0
- package/lib/tasks.js +16 -4
- 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 +31 -9
- package/lib/workbench-artifacts.js +52 -0
- package/lib/workbench-management.js +151 -0
- package/lib/workbench-zip.js +39 -0
- package/package.json +8 -8
- package/user_guide.md +71 -26
package/lib/client.js
CHANGED
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
*
|
|
15
15
|
* 数据通道:host 侧 /iris/api/state 同源 JSON 路由(复用 /iris/media 同款
|
|
16
16
|
* webServer 前缀模式),client 侧 fetch 轮询。全走标量,
|
|
17
|
-
* apiKey
|
|
17
|
+
* apiKey 永不明文;legacy 产物使用 token 链接,Core 产物使用受 Host/浏览器来源守卫保护的 Artifact ID 链接。
|
|
18
18
|
*/
|
|
19
19
|
window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) => {
|
|
20
20
|
var module = { exports: {} };
|
|
21
21
|
var exports = module.exports;
|
|
22
22
|
|
|
23
23
|
var React = require('react');
|
|
24
|
-
var IRIS_CLIENT_VERSION = '0.1
|
|
24
|
+
var IRIS_CLIENT_VERSION = '0.2.1';
|
|
25
25
|
var HOST_CLIENT_PROTOCOL_VERSION = 0;
|
|
26
26
|
var reportedSeats = [];
|
|
27
27
|
|
|
@@ -102,6 +102,35 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
102
102
|
'.iris-doctor-row.error { color: var(--dsw-alias-state-error-primary, #e06c6c); }',
|
|
103
103
|
'.iris-doctor-details { margin-top: 7px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
104
104
|
'.iris-doctor-details summary { cursor: pointer; font-size: 11px; }',
|
|
105
|
+
'.iris-core-runtime { cursor: default; padding: 0; overflow: hidden; }',
|
|
106
|
+
'.iris-core-runtime-head { display: flex; align-items: center; gap: 8px; padding: 9px 10px; }',
|
|
107
|
+
'.iris-core-runtime-title { min-width: 0; flex: 1; display: grid; gap: 1px; }',
|
|
108
|
+
'.iris-core-runtime-title strong { font-size: 12px; }',
|
|
109
|
+
'.iris-core-runtime-body { display: grid; gap: 8px; padding: 0 10px 10px; border-top: 1px dashed var(--dsw-alias-border-l1, #3a3a44); }',
|
|
110
|
+
'.iris-core-runtime-tools { display: flex; align-items: center; gap: 7px; padding-top: 8px; flex-wrap: wrap; }',
|
|
111
|
+
'.iris-core-task-list { display: grid; gap: 6px; }',
|
|
112
|
+
'.iris-core-task-row { width: 100%; box-sizing: border-box; display: grid; grid-template-columns: 9px minmax(0, 1fr) auto; align-items: center; gap: 8px; padding: 8px 9px; text-align: left; color: inherit; background: var(--dsw-alias-bg-layer-2, #32323c); border: 1px solid transparent; border-radius: 8px; cursor: pointer; font-family: inherit; }',
|
|
113
|
+
'.iris-core-task-row:hover, .iris-core-task-row.selected { border-color: var(--dsw-alias-brand-primary, #7aa2f7); }',
|
|
114
|
+
'.iris-core-state-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #73737d); }',
|
|
115
|
+
'.iris-core-state-dot.ok { background: var(--dsw-alias-state-success-primary, #6fcf6f); box-shadow: 0 0 6px rgba(111,207,111,.45); }',
|
|
116
|
+
'.iris-core-state-dot.warn { background: var(--dsw-alias-state-warn-primary, #d9a941); }',
|
|
117
|
+
'.iris-core-state-dot.error { background: #b9656d; }',
|
|
118
|
+
'.iris-core-task-main { min-width: 0; display: grid; gap: 1px; }',
|
|
119
|
+
'.iris-core-task-main strong { font-size: 12px; font-weight: 600; }',
|
|
120
|
+
'.iris-core-task-arrow { color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 10px; }',
|
|
121
|
+
'.iris-core-detail { display: grid; gap: 9px; padding: 10px; border-radius: 9px; background: var(--dsw-alias-bg-layer-2, #32323c); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); }',
|
|
122
|
+
'.iris-core-detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; }',
|
|
123
|
+
'.iris-core-detail-cell { min-width: 0; display: grid; gap: 1px; }',
|
|
124
|
+
'.iris-core-detail-cell span:last-child { overflow-wrap: anywhere; }',
|
|
125
|
+
'.iris-core-id { user-select: all; overflow-wrap: anywhere; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; }',
|
|
126
|
+
'.iris-core-attempt { display: grid; gap: 3px; padding: 7px 8px; border-radius: 7px; background: var(--dsw-alias-bg-layer-1, #26262e); }',
|
|
127
|
+
'.iris-core-artifact { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; padding: 7px 8px; border-radius: 7px; background: var(--dsw-alias-bg-layer-1, #26262e); }',
|
|
128
|
+
'@media (max-width: 420px) { .iris-core-detail-grid { grid-template-columns: 1fr; } .iris-core-task-row { grid-template-columns: 8px minmax(0, 1fr) auto; padding: 8px; } }',
|
|
129
|
+
/* ---- Core 用户侧只读投影(任务区安全 DTO 行) ---- */
|
|
130
|
+
'.iris-core-user-row { cursor: default; }',
|
|
131
|
+
'.iris-core-user-head { min-width: 0; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }',
|
|
132
|
+
'.iris-core-user-model { min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
|
|
133
|
+
'@media (max-width: 420px) { .iris-core-user-head { display: grid; grid-template-columns: auto minmax(0, 1fr); row-gap: 2px; justify-items: start; } .iris-core-user-head .iris-wb-muted, .iris-core-user-row .iris-core-id { grid-column: 1 / -1; } }',
|
|
105
134
|
/* ---- 操作卡片组(阶段 5 GUI 直连) ---- */
|
|
106
135
|
'.iris-act-group { display: grid; gap: 8px; }',
|
|
107
136
|
'.iris-act-card { background: var(--dsw-alias-bg-layer-1, #26262e); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 8px; overflow: hidden; }',
|
|
@@ -203,7 +232,31 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
203
232
|
'.iris-gallery-file { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; font-weight: 600; }',
|
|
204
233
|
'.iris-gallery-meta { color: var(--dsw-alias-label-tertiary, #7a7a85); font-size: 10px; }',
|
|
205
234
|
'.iris-gallery-info { display: grid; gap: 4px; padding: 8px 9px 9px; }',
|
|
206
|
-
'.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; } }',
|
|
207
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; }',
|
|
208
261
|
'.iris-artifact-mini:hover { background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-primary, #e6e6e6); }',
|
|
209
262
|
'.iris-artifact-mini span:nth-child(2) { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
|
|
@@ -219,6 +272,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
219
272
|
'.iris-pm-ver.configured { color: #7da8fa; }',
|
|
220
273
|
'.iris-pm-ver.verified { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
221
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); }',
|
|
222
279
|
'.iris-pm-ver.unconfigured { opacity: .55; }',
|
|
223
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; }',
|
|
224
281
|
/* 文件选择器(阶段 10) */
|
|
@@ -252,8 +309,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
252
309
|
'.iris-po-route { padding: 7px 10px; border-radius: 10px; background: rgba(255,255,255,.045); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
|
|
253
310
|
'.iris-po-note { padding: 8px 10px; border-radius: 10px; color: var(--dsw-alias-state-warn-primary, #dfbd69); background: rgba(217,169,65,.08); }',
|
|
254
311
|
'.iris-po-field { display: grid; gap: 6px; font-size: 11px; color: var(--dsw-alias-label-secondary, #a6a6b2); }',
|
|
255
|
-
'.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; }',
|
|
256
|
-
'.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; }',
|
|
257
320
|
'.iris-po-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }',
|
|
258
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; }',
|
|
259
322
|
'.iris-po-btn { background: rgba(255,255,255,.045); color: var(--dsw-alias-label-secondary, #b0b0bb); }',
|
|
@@ -296,15 +359,30 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
296
359
|
console.log('[iris] client apply start');
|
|
297
360
|
adoptStyles();
|
|
298
361
|
|
|
299
|
-
/* ----
|
|
362
|
+
/* ---- 当前主视图会话(旧 current / 0.2 mainView 持有者;不猜测列表首项) ---- */
|
|
300
363
|
var currentSessionId = null;
|
|
301
364
|
try {
|
|
302
|
-
var sessions = ctx && ctx.sessions;
|
|
365
|
+
var sessions = ctx && (typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions);
|
|
303
366
|
var listObs = sessions && sessions.list;
|
|
304
367
|
if (listObs && typeof listObs.getSnapshot === 'function') {
|
|
305
|
-
|
|
368
|
+
function readCurrentSession() {
|
|
369
|
+
var snap = listObs.getSnapshot() || {};
|
|
370
|
+
if (Object.prototype.hasOwnProperty.call(snap, 'current')) {
|
|
371
|
+
currentSessionId = typeof snap.current === 'string' && snap.current ? snap.current : null;
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
var rows = Object.values(snap.byId || {}).filter(function (row) {
|
|
375
|
+
return row && typeof row.id === 'string' && row.id && row.retainedBy && row.retainedBy.mainView > 0;
|
|
376
|
+
});
|
|
377
|
+
currentSessionId = rows.length === 1 ? rows[0].id : null;
|
|
378
|
+
}
|
|
379
|
+
readCurrentSession();
|
|
306
380
|
if (typeof listObs.subscribe === 'function') {
|
|
307
|
-
|
|
381
|
+
// ObservableSnapshot 的变更通知不承诺携带快照,始终回读权威值。
|
|
382
|
+
var unsubscribeSession = listObs.subscribe(readCurrentSession);
|
|
383
|
+
if (typeof unsubscribeSession === 'function' && typeof ctx.effect === 'function') {
|
|
384
|
+
ctx.effect(function () { return unsubscribeSession; });
|
|
385
|
+
}
|
|
308
386
|
}
|
|
309
387
|
}
|
|
310
388
|
} catch (_) { /* 拿不到会话 → L1 退化为只列 iris 产物 */ }
|
|
@@ -338,7 +416,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
338
416
|
fetch('/iris/api/state')
|
|
339
417
|
.then(function (res) { return res.ok ? res.json() : null; })
|
|
340
418
|
.then(function (data) {
|
|
341
|
-
if (data)
|
|
419
|
+
if (data) {
|
|
420
|
+
apply(data);
|
|
421
|
+
window.dispatchEvent(new CustomEvent('iris-core-refresh-tick'));
|
|
422
|
+
}
|
|
342
423
|
})
|
|
343
424
|
.catch(function () { /* 网络抖动静默 */ });
|
|
344
425
|
}
|
|
@@ -372,7 +453,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
372
453
|
unconfigured: { label: '未配置', symbol: '○' },
|
|
373
454
|
configured: { label: '已配置,待验证', symbol: '◌' },
|
|
374
455
|
verified: { label: '近期验证成功', symbol: '●' },
|
|
375
|
-
failed: { label: '
|
|
456
|
+
failed: { label: '调用受阻', symbol: '×' }
|
|
376
457
|
};
|
|
377
458
|
|
|
378
459
|
function capabilityHealth(state, capability) {
|
|
@@ -396,9 +477,22 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
396
477
|
return Math.floor(ms / 86400000) + ' 天前';
|
|
397
478
|
}
|
|
398
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
|
+
|
|
399
488
|
function healthText(health) {
|
|
400
489
|
var status = health && HEALTH_META[health.status] ? health.status : 'unconfigured';
|
|
401
|
-
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);
|
|
402
496
|
var age = relativeHealthTime(health && health.observedAt);
|
|
403
497
|
return text + (age ? ' · ' + age : '');
|
|
404
498
|
}
|
|
@@ -415,9 +509,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
415
509
|
return React.createElement('div', { className: 'iris-health-grid' }, rows.map(function (row) {
|
|
416
510
|
var health = capabilityHealth(state, row[0]);
|
|
417
511
|
var meta = HEALTH_META[health.status] || HEALTH_META.unconfigured;
|
|
418
|
-
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) },
|
|
419
513
|
React.createElement('span', { className: 'iris-health-name' },
|
|
420
|
-
React.createElement('span', { className: 'iris-pm-dot ' + health
|
|
514
|
+
React.createElement('span', { className: 'iris-pm-dot ' + healthClass(health) }, health.retryAt ? '◷' : meta.symbol),
|
|
421
515
|
row[1]),
|
|
422
516
|
React.createElement('span', { className: 'iris-health-state', title: healthText(health) }, healthText(health)),
|
|
423
517
|
React.createElement('span', { className: 'iris-wb-muted' }, String(health.candidateCount || 0) + ' 路'));
|
|
@@ -425,7 +519,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
425
519
|
}
|
|
426
520
|
|
|
427
521
|
/* ---- 任务详情抽屉:按需拉取 /iris/api/task/:id ---- */
|
|
428
|
-
function useTaskDetail(taskId) {
|
|
522
|
+
function useTaskDetail(taskId, revision) {
|
|
429
523
|
var pair = React.useState(null);
|
|
430
524
|
var setDetail = pair[1];
|
|
431
525
|
React.useEffect(function () {
|
|
@@ -437,7 +531,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
437
531
|
.then(function (data) { if (alive && data) setDetail(data); })
|
|
438
532
|
.catch(function () { /* 静默 */ });
|
|
439
533
|
return function () { alive = false; };
|
|
440
|
-
}, [taskId]);
|
|
534
|
+
}, [taskId, revision]);
|
|
441
535
|
return pair[0];
|
|
442
536
|
}
|
|
443
537
|
|
|
@@ -513,7 +607,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
513
607
|
React.createElement('span', { className: 'iris-wb-muted' }, task.createdAt ? new Date(task.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : ''));
|
|
514
608
|
}
|
|
515
609
|
|
|
516
|
-
function TaskRecoveryActions({ detail }) {
|
|
610
|
+
function TaskRecoveryActions({ detail, onDeleted }) {
|
|
517
611
|
var pair = React.useState('');
|
|
518
612
|
var note = pair[0];
|
|
519
613
|
var setNote = pair[1];
|
|
@@ -530,13 +624,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
530
624
|
var canRedeliver = detail.schemaVersion === 2 && !acknowledged && state === 'artifact_unavailable' && detail.remoteTaskId;
|
|
531
625
|
var canRetry = detail.schemaVersion === 2 && !acknowledged && ['needs_attention', 'failed', 'canceled'].indexOf(state) >= 0
|
|
532
626
|
&& (detail.cap === 'image' || (detail.cap === 'video' && detail.mode === 't2v'));
|
|
533
|
-
|
|
627
|
+
var canDelete = detail.status !== 'running' || (detail.schemaVersion === 2 && attentionState && acknowledged);
|
|
628
|
+
if (!canAcknowledge && !canRestore && !canReobserve && !canRedeliver && !canRetry && !canDelete) return null;
|
|
534
629
|
function run(action, body, confirmMessage) {
|
|
535
630
|
if (confirmMessage && !window.confirm(confirmMessage)) return;
|
|
536
631
|
setBusy(true);
|
|
537
632
|
setNote('处理中…');
|
|
538
633
|
postAction(action, body).then(function (r) {
|
|
539
634
|
setNote(r.ok ? ((r.d && r.d.text) || '操作已接受') : ('失败:' + ((r.d && r.d.error) || '未知错误')));
|
|
635
|
+
if (r.ok && action === 'tasks_delete' && typeof onDeleted === 'function') onDeleted();
|
|
540
636
|
}).catch(function (error) { setNote('失败:' + String((error && error.message) || error)); })
|
|
541
637
|
.finally(function () { setBusy(false); });
|
|
542
638
|
}
|
|
@@ -551,12 +647,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
551
647
|
onClick: function () { run('task_ack_attention', { task_id: detail.id }); } }, '标为已读') : null,
|
|
552
648
|
canRestore ? React.createElement('button', { className: 'iris-pm-btn', disabled: busy,
|
|
553
649
|
onClick: function () { run('task_restore_attention', { task_id: detail.id }); } }, '恢复提醒') : null,
|
|
650
|
+
canDelete ? React.createElement('button', { className: 'iris-pm-btn danger', disabled: busy,
|
|
651
|
+
onClick: function () { run('tasks_delete', { task_id: detail.id },
|
|
652
|
+
attentionState ? '该任务仍含未知远程事实;删除后无法再观察。确认只删除任务记录?' : '确认删除该任务记录?作品文件保留。'); } }, '删除记录') : null,
|
|
554
653
|
note ? React.createElement('span', { className: 'iris-clean-note' }, note) : null);
|
|
555
654
|
}
|
|
556
655
|
|
|
557
656
|
/* ---- 任务详情抽屉:点击任务卡片行展开 ---- */
|
|
558
|
-
function TaskDetailDrawer({ taskId }) {
|
|
559
|
-
var detail = useTaskDetail(taskId);
|
|
657
|
+
function TaskDetailDrawer({ taskId, revision, onDeleted }) {
|
|
658
|
+
var detail = useTaskDetail(taskId, revision);
|
|
560
659
|
if (!detail) {
|
|
561
660
|
return React.createElement('div', { className: 'iris-wb-drawer iris-wb-loading' }, '加载任务详情…');
|
|
562
661
|
}
|
|
@@ -565,7 +664,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
565
664
|
el.push(React.createElement('div', { key: 'p', className: 'iris-wb-prompt' }, detail.prompt || '(空)'));
|
|
566
665
|
if (Number(detail.schemaVersion) === 1) {
|
|
567
666
|
el.push(React.createElement('div', { key: 'legacy', className: 'iris-clean-note' },
|
|
568
|
-
'旧版任务记录 ·
|
|
667
|
+
'旧版任务记录 · 仅支持查看恢复事实。由于缺少可靠的远端受理与交付事实,不提供重新观察、重新交付或知情重试;已终止记录仍可删除。'));
|
|
569
668
|
}
|
|
570
669
|
var dispositionLabel = attentionDispositionLabel(detail);
|
|
571
670
|
if (dispositionLabel) {
|
|
@@ -622,7 +721,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
622
721
|
return React.createElement('span', { key: a.attachmentId, className: 'iris-wb-kv' }, a.attachmentId + (a.file ? ' (' + a.file + ')' : ''));
|
|
623
722
|
})));
|
|
624
723
|
}
|
|
625
|
-
el.push(React.createElement(TaskRecoveryActions, { key: 'recovery', detail: detail }));
|
|
724
|
+
el.push(React.createElement(TaskRecoveryActions, { key: 'recovery', detail: detail, onDeleted: onDeleted }));
|
|
626
725
|
return React.createElement('div', { className: 'iris-wb-drawer' }, ...el);
|
|
627
726
|
}
|
|
628
727
|
|
|
@@ -668,7 +767,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
668
767
|
task.createdAt ? '发起 ' + fmtTime(task.createdAt) : '',
|
|
669
768
|
task.finishedAt ? ' · 完成 ' + fmtTime(task.finishedAt) : ''));
|
|
670
769
|
}
|
|
671
|
-
if (selected) body.push(React.createElement(TaskDetailDrawer, { key: 'drawer', taskId: task.id }));
|
|
770
|
+
if (selected) body.push(React.createElement(TaskDetailDrawer, { key: 'drawer', taskId: task.id, revision: task.revision, onDeleted: function () { onSelect(null); } }));
|
|
672
771
|
return React.createElement('div', {
|
|
673
772
|
key: task.id,
|
|
674
773
|
className: 'iris-wb-card' + (isRunning ? ' iris-running' : '') + (selected ? ' selected' : ''),
|
|
@@ -691,12 +790,16 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
691
790
|
var newId = addPair[0];
|
|
692
791
|
var setNewId = addPair[1];
|
|
693
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
|
+
}
|
|
694
797
|
function verBadge(m, c) {
|
|
695
798
|
var health = m.health && m.health[c];
|
|
696
799
|
var status = health && HEALTH_META[health.status] ? health.status : 'configured';
|
|
697
|
-
var cls = 'iris-pm-ver ' + status;
|
|
800
|
+
var cls = 'iris-pm-ver ' + status + (health && health.rateLimited ? ' ' + healthClass(health) : '');
|
|
698
801
|
var title = healthText(health || { status: status });
|
|
699
|
-
var symbol = status === 'verified' ? '✓' : (status === 'failed' ? '×' : (status === 'unconfigured' ? '○' : '?'));
|
|
802
|
+
var symbol = health && health.retryAt ? '◷' : status === 'verified' ? '✓' : (status === 'failed' ? '×' : (status === 'unconfigured' ? '○' : '?'));
|
|
700
803
|
return React.createElement('button', {
|
|
701
804
|
key: c,
|
|
702
805
|
className: cls,
|
|
@@ -706,9 +809,21 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
706
809
|
}
|
|
707
810
|
var rows = models.map(function (m) {
|
|
708
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; });
|
|
709
813
|
return React.createElement('div', { key: m.id, className: 'iris-pm-mrow' },
|
|
710
814
|
React.createElement('span', { className: 'iris-pm-mname' }, m.id, m.source === 'manual' ? React.createElement('span', { className: 'iris-pm-mtag' }, '手动') : null),
|
|
711
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,
|
|
712
827
|
React.createElement('button', { className: 'iris-pm-btn danger', title: '从池移除', onClick: function () { props.removeModel(p, m); } }, '✕'));
|
|
713
828
|
});
|
|
714
829
|
return React.createElement('div', { className: 'iris-pm-pool' },
|
|
@@ -719,6 +834,59 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
719
834
|
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { props.addModel(p, newId.trim()); setNewId(''); } }, '+ 添加')));
|
|
720
835
|
}
|
|
721
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
|
+
|
|
870
|
+
function ProviderEndpointEditor(props) {
|
|
871
|
+
var p = props.provider;
|
|
872
|
+
var valuePair = React.useState(p.mediaBaseUrl || '');
|
|
873
|
+
var value = valuePair[0];
|
|
874
|
+
var setValue = valuePair[1];
|
|
875
|
+
React.useEffect(function () { setValue(p.mediaBaseUrl || ''); }, [p.mediaBaseUrl]);
|
|
876
|
+
function save() {
|
|
877
|
+
postAction('providers_upsert', { id: p.id, mediaBaseUrl: value.trim() }).then(function (r) {
|
|
878
|
+
props.onNote(r.ok ? '媒体 Base URL 已更新' : (r.d.error || '更新失败'));
|
|
879
|
+
if (r.ok) props.onDone();
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
return React.createElement('div', { className: 'iris-pm-field' },
|
|
883
|
+
React.createElement('label', {}, '媒体 Base URL(可选)'),
|
|
884
|
+
React.createElement('div', { className: 'iris-wb-row' },
|
|
885
|
+
React.createElement('input', { value: value, placeholder: '留空则沿用 Base URL', onChange: function (e) { setValue(e.target.value); } }),
|
|
886
|
+
React.createElement('button', { className: 'iris-pm-btn', onClick: save }, '保存')),
|
|
887
|
+
React.createElement('span', { className: 'iris-pm-note' }, '百炼 Workspace 可填 …maas.aliyuncs.com/compatible-mode/v1;视觉仍使用普通 Base URL。'));
|
|
888
|
+
}
|
|
889
|
+
|
|
722
890
|
function ProviderManager() {
|
|
723
891
|
var listPair = React.useState(null);
|
|
724
892
|
var list = listPair[0];
|
|
@@ -728,14 +896,18 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
728
896
|
var addingPair = React.useState(false);
|
|
729
897
|
var adding = addingPair[0];
|
|
730
898
|
var setAdding = addingPair[1];
|
|
731
|
-
var newForm = React.useState({ name: '', baseUrl: '', apiKey: '', type: 'openai', mediaProtocol: 'auto' });
|
|
899
|
+
var newForm = React.useState({ name: '', baseUrl: '', mediaBaseUrl: '', apiKey: '', type: 'openai', mediaProtocol: 'auto' });
|
|
732
900
|
var notePair = React.useState('');
|
|
733
901
|
var note = notePair[0];
|
|
734
902
|
var setNote = notePair[1];
|
|
735
903
|
function refresh() {
|
|
736
904
|
postAction('providers_list', {}).then(function (r) { if (r.ok && r.d.providers) setList(r.d.providers); });
|
|
737
905
|
}
|
|
738
|
-
React.useEffect(function () {
|
|
906
|
+
React.useEffect(function () {
|
|
907
|
+
refresh();
|
|
908
|
+
var timer = setInterval(refresh, 5000);
|
|
909
|
+
return function () { clearInterval(timer); };
|
|
910
|
+
}, []);
|
|
739
911
|
function addProvider() {
|
|
740
912
|
var f = newForm[0];
|
|
741
913
|
if (!f.baseUrl || !f.apiKey) { setNote('baseUrl 和 apiKey 必填'); return; }
|
|
@@ -789,9 +961,16 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
789
961
|
if (open) {
|
|
790
962
|
body = React.createElement('div', { className: 'iris-pm-body' },
|
|
791
963
|
React.createElement('div', { className: 'iris-pm-note' }, 'Key ' + (p.apiKeyHint || '未配置') + ' · ' + (p.type || '') + ' · ' + (p.mediaProtocol || '')),
|
|
792
|
-
React.createElement('div', { className: 'iris-pm-
|
|
964
|
+
p.protocolInferred ? React.createElement('div', { className: 'iris-pm-note' }, '协议为推断值,请确认') : null,
|
|
965
|
+
React.createElement(ProviderEndpointEditor, { provider: p, onNote: setNote, onDone: refresh }),
|
|
966
|
+
React.createElement(VisionInputEditor, { provider: p, onNote: setNote, onDone: refresh }),
|
|
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); } },
|
|
968
|
+
React.createElement('option', { value: 'auto' }, '自动'),
|
|
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,
|
|
793
970
|
React.createElement('option', { value: 'dashscope' }, 'DashScope(仅阿里云官方 HTTPS)'),
|
|
794
|
-
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 生图'))),
|
|
795
974
|
React.createElement('div', { className: 'iris-pm-note' }, '能力: ' + ((p.capabilities || []).join(' / ') || '无')),
|
|
796
975
|
React.createElement(ModelPool, { provider: p, onNote: setNote, onDone: refresh, testModel: testModel, removeModel: removeModel, addModel: addModel }),
|
|
797
976
|
React.createElement('div', { className: 'iris-wb-row' },
|
|
@@ -810,11 +989,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
810
989
|
addBlock = React.createElement('div', { className: 'iris-pm-card' },
|
|
811
990
|
React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '名称'), React.createElement('input', { value: newForm[0].name, placeholder: '如 阿里云百炼', onChange: function (e) { newForm[1]({ ...newForm[0], name: e.target.value }); } })),
|
|
812
991
|
React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, 'Base URL *'), React.createElement('input', { value: newForm[0].baseUrl, placeholder: 'https://dashscope.aliyuncs.com/compatible-mode/v1', onChange: function (e) { newForm[1]({ ...newForm[0], baseUrl: e.target.value }); } })),
|
|
992
|
+
React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '媒体 Base URL(可选)'), React.createElement('input', { value: newForm[0].mediaBaseUrl, placeholder: 'Workspace 媒体端点;留空沿用 Base URL', onChange: function (e) { newForm[1]({ ...newForm[0], mediaBaseUrl: e.target.value }); } })),
|
|
813
993
|
React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, 'API Key *'), React.createElement('input', { type: 'password', value: newForm[0].apiKey, placeholder: 'sk-...', onChange: function (e) { newForm[1]({ ...newForm[0], apiKey: e.target.value }); } })),
|
|
814
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 }); } },
|
|
815
|
-
React.createElement('option', { value: 'auto' }, '
|
|
995
|
+
React.createElement('option', { value: 'auto' }, '自动(按媒体 Base URL 安全判断)'),
|
|
816
996
|
React.createElement('option', { value: 'dashscope' }, 'DashScope(阿里云官方)'),
|
|
817
|
-
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 生图'))),
|
|
818
1000
|
React.createElement('div', { className: 'iris-wb-row' },
|
|
819
1001
|
React.createElement('button', { className: 'iris-act-run', onClick: addProvider }, '保存'),
|
|
820
1002
|
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { setAdding(false); } }, '取消')));
|
|
@@ -936,7 +1118,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
936
1118
|
body: JSON.stringify(vals)
|
|
937
1119
|
})
|
|
938
1120
|
.then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
|
|
939
|
-
.then(function (res) {
|
|
1121
|
+
.then(function (res) {
|
|
1122
|
+
setRun(res.ok ? { ok: true, text: res.d.text || '', img: res.d.imageUrl || res.d.imageDataUrl || null } : { ok: false, text: res.d.error || '执行失败' });
|
|
1123
|
+
if (res.ok && res.d && res.d.storage === 'core') window.dispatchEvent(new CustomEvent('iris-core-artifacts-changed'));
|
|
1124
|
+
})
|
|
940
1125
|
.catch(function () { setRun({ ok: false, text: '网络错误' }); });
|
|
941
1126
|
}
|
|
942
1127
|
var cardHealth = capability ? capabilityHealth(irisState, capability) : { status: 'verified' };
|
|
@@ -982,7 +1167,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
982
1167
|
var shown = (vals.model && vals.model.trim()) || (modelInfo && modelInfo.assigned) || '';
|
|
983
1168
|
modelBar = React.createElement('div', { className: 'iris-act-field' },
|
|
984
1169
|
React.createElement('label', {},
|
|
985
|
-
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),
|
|
986
1171
|
' 模型:' + (shown || '自动') + (available ? '' : '(无可用模型)')),
|
|
987
1172
|
React.createElement('div', { className: 'iris-wb-muted' },
|
|
988
1173
|
healthText(cardHealth) + ' · 改分配 → 上方「能力分配(failover 顺序)」'));
|
|
@@ -1002,9 +1187,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1002
1187
|
React.createElement('div', { className: 'iris-act-head', onClick: function () { setOpen(!open); } },
|
|
1003
1188
|
React.createElement('span', { className: 'iris-act-title' }, title),
|
|
1004
1189
|
capability ? React.createElement('span', {
|
|
1005
|
-
className: 'iris-pm-dot ' +
|
|
1190
|
+
className: 'iris-pm-dot ' + healthClass(cardHealth),
|
|
1006
1191
|
title: headTitle
|
|
1007
|
-
}, HEALTH_META[headStatus].symbol) : null,
|
|
1192
|
+
}, cardHealth.retryAt ? '◷' : HEALTH_META[headStatus].symbol) : null,
|
|
1008
1193
|
React.createElement('span', { className: 'iris-act-arrow' }, '▶')),
|
|
1009
1194
|
body);
|
|
1010
1195
|
}
|
|
@@ -1165,14 +1350,16 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1165
1350
|
/* ---- 历史浏览器(设置页:按今天/昨天/更早分组折叠) ---- */
|
|
1166
1351
|
function HistoryBrowser(props) {
|
|
1167
1352
|
var recent = props.recent || [];
|
|
1353
|
+
var coreHistory = props.coreHistory || [];
|
|
1168
1354
|
var selected = props.selected;
|
|
1169
1355
|
var onSelect = props.onSelect;
|
|
1170
1356
|
var collapsedPair = React.useState({});
|
|
1171
1357
|
var collapsed = collapsedPair[0];
|
|
1172
1358
|
var setCollapsed = collapsedPair[1];
|
|
1173
|
-
if (!recent.length) return React.createElement('div', { className: 'iris-wb-empty' }, '尚无任务记录');
|
|
1359
|
+
if (!recent.length && !coreHistory.length) return React.createElement('div', { className: 'iris-wb-empty' }, '尚无任务记录');
|
|
1174
1360
|
var buckets = { 今天: [], 昨天: [], 更早: [] };
|
|
1175
|
-
recent.forEach(function (t) { buckets[dayBucket(t.createdAt)].push(t); });
|
|
1361
|
+
recent.forEach(function (t) { buckets[dayBucket(t.createdAt)].push({ legacy: t }); });
|
|
1362
|
+
coreHistory.forEach(function (row) { buckets[dayBucket(row.updatedAt)].push({ core: row }); });
|
|
1176
1363
|
var order = ['今天', '昨天', '更早'];
|
|
1177
1364
|
return React.createElement('div', {}, order.map(function (name) {
|
|
1178
1365
|
var rows = buckets[name];
|
|
@@ -1184,7 +1371,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1184
1371
|
onClick: function () { var n = {}; Object.keys(collapsed).forEach(function (k) { n[k] = collapsed[k]; }); n[name] = !isCollapsed; setCollapsed(n); }
|
|
1185
1372
|
}, React.createElement('span', {}, isCollapsed ? '▶' : '▼'), name + ' · ' + rows.length),
|
|
1186
1373
|
isCollapsed ? null : React.createElement('div', { className: 'iris-wb-box' },
|
|
1187
|
-
rows.map(function (
|
|
1374
|
+
rows.map(function (entry) {
|
|
1375
|
+
return entry.core
|
|
1376
|
+
? coreTaskCard(entry.core)
|
|
1377
|
+
: taskRow(entry.legacy, false, selected === entry.legacy.id, onSelect);
|
|
1378
|
+
})));
|
|
1188
1379
|
}));
|
|
1189
1380
|
}
|
|
1190
1381
|
|
|
@@ -1214,7 +1405,20 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1214
1405
|
var mime = String(item.mime || '');
|
|
1215
1406
|
if (mime.indexOf('image/') === 0) {
|
|
1216
1407
|
return React.createElement('a', { className: 'iris-gallery-preview', href: item.url, target: '_blank', rel: 'noreferrer', title: '打开原图' },
|
|
1217
|
-
React.createElement('img', {
|
|
1408
|
+
React.createElement('img', {
|
|
1409
|
+
src: item.url, alt: 'Iris 作品 ' + item.file, loading: 'lazy',
|
|
1410
|
+
// 媒体文件丢失(404/损坏)时局部降级为占位提示,作品区其余项不受影响
|
|
1411
|
+
onError: function (event) {
|
|
1412
|
+
var img = event && event.target;
|
|
1413
|
+
var box = img && img.parentNode;
|
|
1414
|
+
if (!box || box.querySelector('span')) return;
|
|
1415
|
+
img.style.display = 'none';
|
|
1416
|
+
var note = document.createElement('span');
|
|
1417
|
+
note.className = 'iris-gallery-meta';
|
|
1418
|
+
note.textContent = '作品文件暂时不可用';
|
|
1419
|
+
box.appendChild(note);
|
|
1420
|
+
}
|
|
1421
|
+
}));
|
|
1218
1422
|
}
|
|
1219
1423
|
if (mime.indexOf('video/') === 0) {
|
|
1220
1424
|
return React.createElement('div', { className: 'iris-gallery-preview' },
|
|
@@ -1227,64 +1431,991 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1227
1431
|
return React.createElement('a', { className: 'iris-gallery-preview iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开文件');
|
|
1228
1432
|
}
|
|
1229
1433
|
|
|
1230
|
-
function
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1434
|
+
function coreGalleryItems(data, limit) {
|
|
1435
|
+
return (((data && data.artifacts && data.artifacts.recent) || []).filter(function (artifact) {
|
|
1436
|
+
return artifact.kind !== 'host-input';
|
|
1437
|
+
}).map(function (artifact) {
|
|
1438
|
+
return {
|
|
1439
|
+
id: artifact.id, file: artifact.id, mime: artifact.mediaType,
|
|
1440
|
+
size: artifact.size, createdAt: artifact.createdAt || '', source: 'core',
|
|
1441
|
+
kind: artifact.kind,
|
|
1442
|
+
digest: artifact.digest && artifact.digest.value ? artifact.digest.value.slice(0, 12) : '',
|
|
1443
|
+
url: '/iris/api/core/artifact/' + encodeURIComponent(artifact.id) + '/media'
|
|
1444
|
+
};
|
|
1445
|
+
})).slice(0, limit || 200);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function useCoreWorks(limit) {
|
|
1449
|
+
var pair = React.useState([]);
|
|
1450
|
+
var setItems = pair[1];
|
|
1451
|
+
React.useEffect(function () {
|
|
1452
|
+
var alive = true;
|
|
1453
|
+
function load() {
|
|
1454
|
+
fetch('/iris/api/core/snapshot?limit=' + Math.max(1, Math.min(200, Number(limit) || 6)))
|
|
1455
|
+
.then(function (res) { return res.ok ? res.json() : null; })
|
|
1456
|
+
.then(function (data) { if (alive && data) setItems(coreGalleryItems(data, limit)); })
|
|
1457
|
+
.catch(function () { /* Core 不可用不影响旧作品 */ });
|
|
1458
|
+
}
|
|
1459
|
+
function changed() { load(); }
|
|
1460
|
+
load();
|
|
1461
|
+
window.addEventListener('iris-core-artifacts-changed', changed);
|
|
1462
|
+
window.addEventListener('iris-core-refresh-tick', changed);
|
|
1463
|
+
return function () {
|
|
1464
|
+
alive = false;
|
|
1465
|
+
window.removeEventListener('iris-core-artifacts-changed', changed);
|
|
1466
|
+
window.removeEventListener('iris-core-refresh-tick', changed);
|
|
1467
|
+
};
|
|
1468
|
+
}, [limit]);
|
|
1469
|
+
return pair[0];
|
|
1470
|
+
}
|
|
1471
|
+
|
|
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];
|
|
1240
1759
|
var notePair = React.useState('');
|
|
1241
1760
|
var note = notePair[0];
|
|
1242
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);
|
|
1243
1768
|
|
|
1244
|
-
function load(
|
|
1245
|
-
if (
|
|
1769
|
+
function load() {
|
|
1770
|
+
if (request.current) request.current.abort();
|
|
1771
|
+
var controller = new AbortController(); request.current = controller;
|
|
1246
1772
|
setBusy(true);
|
|
1247
|
-
var
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
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 作品。' : '');
|
|
1253
1783
|
})
|
|
1254
|
-
.catch(function (error) {
|
|
1255
|
-
.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); } });
|
|
1256
1786
|
}
|
|
1257
1787
|
|
|
1258
1788
|
function run(action, body, confirmText) {
|
|
1259
1789
|
if (confirmText && !window.confirm(confirmText)) return;
|
|
1260
|
-
|
|
1790
|
+
setMutating(true);
|
|
1261
1791
|
postAction(action, body || {}).then(function (result) {
|
|
1262
1792
|
var succeeded = result.ok && result.d && result.d.ok !== false;
|
|
1263
1793
|
setNote(succeeded ? (result.d.text || '完成') : ('失败:' + ((result.d && (result.d.error || result.d.text)) || '未知错误')));
|
|
1264
|
-
if (succeeded)
|
|
1265
|
-
|
|
1266
|
-
|
|
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); });
|
|
1267
1801
|
}
|
|
1268
1802
|
|
|
1269
|
-
React.useEffect(function () {
|
|
1803
|
+
React.useEffect(function () {
|
|
1804
|
+
function changed() { load(); }
|
|
1805
|
+
window.addEventListener('iris-core-artifacts-changed', changed);
|
|
1806
|
+
window.addEventListener('iris-core-refresh-tick', changed);
|
|
1807
|
+
load();
|
|
1808
|
+
return function () {
|
|
1809
|
+
window.removeEventListener('iris-core-artifacts-changed', changed);
|
|
1810
|
+
window.removeEventListener('iris-core-refresh-tick', changed);
|
|
1811
|
+
if (request.current) request.current.abort(); request.current = null;
|
|
1812
|
+
};
|
|
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;
|
|
1270
1864
|
|
|
1271
1865
|
return React.createElement('div', {},
|
|
1272
1866
|
React.createElement('div', { className: 'iris-gallery-tools' },
|
|
1273
|
-
React.createElement('span', { className: 'iris-clean-note' },
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1867
|
+
React.createElement('span', { className: 'iris-clean-note' },
|
|
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) {
|
|
1895
|
+
var isCore = item.source === 'core';
|
|
1896
|
+
return React.createElement('div', { key: (isCore ? 'core-' : 'legacy-') + item.id, className: 'iris-gallery-card' + (selectedItem(item) ? ' selected' : '') },
|
|
1278
1897
|
artifactPreview(item),
|
|
1279
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); } }), '选择'),
|
|
1280
1902
|
React.createElement('div', { className: 'iris-gallery-file', title: item.file }, item.file),
|
|
1281
|
-
React.createElement('div', { className: 'iris-gallery-meta' },
|
|
1903
|
+
React.createElement('div', { className: 'iris-gallery-meta' },
|
|
1904
|
+
(isCore ? 'Core · ' + galleryKindLabel(item.kind) : '旧版') + ' · ' + fmtTime(item.createdAt) + ' · ' + fmtBytes(item.size)),
|
|
1282
1905
|
React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1283
1906
|
React.createElement('a', { className: 'iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开'),
|
|
1284
|
-
React.createElement('button', { className: 'iris-pm-btn
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
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);
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
function coreCapabilityLabel(value) {
|
|
1927
|
+
return ({ image: '图片生成', video: '视频生成', tts: '语音合成', transcribe: '音频转写' })[value] || String(value || '媒体任务');
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
/* 高级诊断与用户任务区共用五类用户状态投影;与服务端 lib/core-user-projection.js 保持同一映射,
|
|
1931
|
+
文案必须区分“远端可能还在运行”和“已经明确失败”。 */
|
|
1932
|
+
function coreTaskState(task) {
|
|
1933
|
+
if (task.outcome === 'succeeded' && task.deliveryState === 'ready') return { tone: 'ok', state: 'succeeded', label: '已完成,作品可用' };
|
|
1934
|
+
if (task.outcome === 'succeeded' && task.deliveryState === 'failed') return { tone: 'error', state: 'delivery_failed', label: '已生成,作品取回失败' };
|
|
1935
|
+
if (task.outcome === 'succeeded') return { tone: 'warn', state: 'running', label: '已生成,正在保存作品' };
|
|
1936
|
+
if (task.outcome === 'failed') return { tone: 'error', state: 'attention', label: '已失败' };
|
|
1937
|
+
if (task.outcome === 'canceled') return { tone: 'idle', state: 'attention', label: '已取消' };
|
|
1938
|
+
if (task.outcome === 'unknown') return { tone: 'warn', state: 'attention', label: '任务结果未知,需要检查' };
|
|
1939
|
+
if (task.acceptance === 'unknown') return { tone: 'warn', state: 'attention', label: '提交结果未知,请检查后处理' };
|
|
1940
|
+
if (task.deliveryState === 'failed') return { tone: 'error', state: 'attention', label: '结果状态异常,需要检查' };
|
|
1941
|
+
if (task.watchState === 'suspended' || task.watchState === 'exhausted') return { tone: 'warn', state: 'observation_paused', label: '观察已暂停,远端可能仍在运行' };
|
|
1942
|
+
return { tone: 'idle', state: 'running', label: '运行中' };
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
function coreAttemptState(attempt) {
|
|
1946
|
+
if (attempt.resultKind === 'completed') return '已完成';
|
|
1947
|
+
if (attempt.acceptance === 'not_accepted') return '未受理';
|
|
1948
|
+
if (attempt.acceptance === 'unknown') return '是否受理未知';
|
|
1949
|
+
if (attempt.acceptance === 'accepted') return '已受理';
|
|
1950
|
+
return '正在提交';
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
function coreModelLabel(value) {
|
|
1954
|
+
var model = String(value || '');
|
|
1955
|
+
var separator = model.indexOf('::');
|
|
1956
|
+
return separator >= 0 ? model.slice(separator + 2) : (model || '未记录');
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
/* 诊断层允许更多事实:取消三态如实区分,不把"已请求/无法确认"显示成"已取消"。 */
|
|
1960
|
+
function coreCancelLabel(value) {
|
|
1961
|
+
if (value === 'remote_confirmed') return '供应商已明确确认取消';
|
|
1962
|
+
if (value === 'local_confirmed') return '本地已确认取消(远端未确认)';
|
|
1963
|
+
if (value === 'requested') return '取消请求进行中';
|
|
1964
|
+
if (value === 'unknown') return '已请求取消,远端结果未确认;可显式重新观察';
|
|
1965
|
+
return '无取消请求';
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
/* ---- Core 用户侧只读投影:任务区消费 /iris/api/core/snapshot 的 userTasks 安全 DTO ---- */
|
|
1969
|
+
var EMPTY_CORE_USER = Object.freeze({ rows: [], available: false, degraded: false, droppedTasks: 0 });
|
|
1970
|
+
|
|
1971
|
+
function useCoreUserTasks() {
|
|
1972
|
+
var pair = React.useState(EMPTY_CORE_USER);
|
|
1973
|
+
var setData = pair[1];
|
|
1974
|
+
React.useEffect(function () {
|
|
1975
|
+
var alive = true;
|
|
1976
|
+
function load() {
|
|
1977
|
+
fetch('/iris/api/core/snapshot?limit=200')
|
|
1978
|
+
.then(function (res) { return res.ok ? res.json() : null; })
|
|
1979
|
+
.then(function (data) {
|
|
1980
|
+
if (!alive || !data || !Array.isArray(data.userTasks)) return;
|
|
1981
|
+
setData({
|
|
1982
|
+
rows: data.userTasks,
|
|
1983
|
+
available: !!data.available,
|
|
1984
|
+
degraded: !!data.degraded,
|
|
1985
|
+
droppedTasks: Number(data.droppedTasks || 0)
|
|
1986
|
+
});
|
|
1987
|
+
})
|
|
1988
|
+
.catch(function () { /* Core 不可用不阻塞 legacy 任务区 */ });
|
|
1989
|
+
}
|
|
1990
|
+
function changed() { load(); }
|
|
1991
|
+
load();
|
|
1992
|
+
window.addEventListener('iris-core-artifacts-changed', changed);
|
|
1993
|
+
window.addEventListener('iris-core-refresh-tick', changed);
|
|
1994
|
+
return function () {
|
|
1995
|
+
alive = false;
|
|
1996
|
+
window.removeEventListener('iris-core-artifacts-changed', changed);
|
|
1997
|
+
window.removeEventListener('iris-core-refresh-tick', changed);
|
|
1998
|
+
};
|
|
1999
|
+
}, []);
|
|
2000
|
+
return pair[0];
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
/* 五类用户状态 → 既有 badge 色调;不新增第三套卡片样式,复用 iris-wb/iris-task-mini。 */
|
|
2004
|
+
function coreUserStateClass(state) {
|
|
2005
|
+
if (state === 'succeeded') return 'ok';
|
|
2006
|
+
if (state === 'delivery_failed' || state === 'attention') return 'err';
|
|
2007
|
+
if (state === 'observation_paused') return 'warn';
|
|
2008
|
+
return '';
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
function coreTaskBadge(row) {
|
|
2012
|
+
var tone = row.historical === true ? '' : coreUserStateClass(row.userState);
|
|
2013
|
+
return React.createElement('span', { className: 'iris-wb-badge' + (tone ? ' ' + tone : '') }, row.label);
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
/* 完成提示只出现一次:会话内 Map 记录 core:id@revision,刷新页面后从事实重新计算基线。 */
|
|
2017
|
+
var coreCompletionSeen = null; // null = 尚未建立基线(首屏把既有完成项记为已见)
|
|
2018
|
+
function coreRowKey(row) { return 'core:' + row.id + '@' + Number(row.revision || 0); }
|
|
2019
|
+
function noteCoreCompletions(rows) {
|
|
2020
|
+
var current = {};
|
|
2021
|
+
for (var i = 0; i < rows.length; i++) {
|
|
2022
|
+
if (rows[i].userState === 'succeeded') current[coreRowKey(rows[i])] = true;
|
|
2023
|
+
}
|
|
2024
|
+
if (coreCompletionSeen === null) { coreCompletionSeen = current; return []; }
|
|
2025
|
+
var added = [];
|
|
2026
|
+
for (var j = 0; j < rows.length; j++) {
|
|
2027
|
+
var row = rows[j];
|
|
2028
|
+
if (row.userState === 'succeeded' && !coreCompletionSeen[coreRowKey(row)]) added.push(row);
|
|
2029
|
+
}
|
|
2030
|
+
for (var key in current) coreCompletionSeen[key] = true;
|
|
2031
|
+
return added;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
/* D1/D2 人工动作按钮:投影门满足才出现;每次点击只调用一次显式单步 API,绝不
|
|
2035
|
+
重新提交、也不间接重新生成;点击期间按钮禁用防重复,完成后复用
|
|
2036
|
+
iris-core-refresh-tick 让任务区/高级诊断重拉同一快照。服务端 Command 门仍
|
|
2037
|
+
是最终裁决(binding/能力校验失败返回稳定错误,行内提示,不改任务事实)。 */
|
|
2038
|
+
function CoreTaskManualButton(props) {
|
|
2039
|
+
var busyPair = React.useState(false);
|
|
2040
|
+
var busy = busyPair[0];
|
|
2041
|
+
var setBusy = busyPair[1];
|
|
2042
|
+
var notePair = React.useState('');
|
|
2043
|
+
var note = notePair[0];
|
|
2044
|
+
var setNote = notePair[1];
|
|
2045
|
+
function act() {
|
|
2046
|
+
if (busy) return; // 防重复触发:请求未结束前后端各拦一层
|
|
2047
|
+
var body = props.buildBody ? props.buildBody() : {};
|
|
2048
|
+
if (body === null) return; // 用户取消输入(如重试时放弃重新输入 prompt)
|
|
2049
|
+
if (props.confirm && !window.confirm(props.confirm)) return; // 改变远端事实/计费的动作需要二次确认
|
|
2050
|
+
setBusy(true);
|
|
2051
|
+
setNote('');
|
|
2052
|
+
fetch('/iris/api/core/task/' + encodeURIComponent(props.id) + '/' + props.action, {
|
|
2053
|
+
method: 'POST',
|
|
2054
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2055
|
+
body: JSON.stringify(body)
|
|
2056
|
+
}).then(function (res) {
|
|
2057
|
+
return res.json().then(function (data) { return { status: res.status, data: data }; })
|
|
2058
|
+
.catch(function () { return { status: res.status, data: null }; });
|
|
2059
|
+
}).then(function (result) {
|
|
2060
|
+
var detail = result.data && result.data.error;
|
|
2061
|
+
if (result.status !== 200) {
|
|
2062
|
+
setNote(props.failPrefix + '被拒绝:' + (detail && detail.message || '状态 ' + result.status));
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
var created = result.data && result.data.task;
|
|
2066
|
+
if (props.createdPrefix && created && created.id && created.id !== props.id) {
|
|
2067
|
+
setNote(props.createdPrefix + created.id);
|
|
2068
|
+
}
|
|
2069
|
+
window.dispatchEvent(new CustomEvent('iris-core-refresh-tick'));
|
|
2070
|
+
}).catch(function () {
|
|
2071
|
+
setNote(props.failPrefix + '请求失败;任务事实未改动');
|
|
2072
|
+
}).finally(function () { setBusy(false); });
|
|
2073
|
+
}
|
|
2074
|
+
return React.createElement('span', { className: 'iris-wb-row', style: { alignItems: 'center' } },
|
|
2075
|
+
React.createElement('button', {
|
|
2076
|
+
type: 'button', className: 'iris-pm-btn', disabled: busy,
|
|
2077
|
+
title: props.title,
|
|
2078
|
+
onClick: act
|
|
2079
|
+
}, busy ? props.busyLabel : props.idleLabel),
|
|
2080
|
+
note ? React.createElement('span', { className: 'iris-wb-err' }, note) : null);
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
/* D1 reobserve:显式单步观察入口,投影标记 observable 才出现。 */
|
|
2084
|
+
function CoreReobserveButton(props) {
|
|
2085
|
+
if (!props.row || props.row.observable !== true) return null;
|
|
2086
|
+
return React.createElement(CoreTaskManualButton, {
|
|
2087
|
+
id: props.row.id, action: 'reobserve',
|
|
2088
|
+
idleLabel: '重新观察', busyLabel: '观察中…', failPrefix: '重新观察',
|
|
2089
|
+
title: '只向供应商查询一次该任务的最新状态,绝不重新提交'
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
/* D2 redeliver:delivery_failed 的唯一恢复入口;重新下载已生成的作品,不重新生成。 */
|
|
2094
|
+
function CoreRedeliverButton(props) {
|
|
2095
|
+
if (!props.row || props.row.redeliverable !== true) return null;
|
|
2096
|
+
return React.createElement(CoreTaskManualButton, {
|
|
2097
|
+
id: props.row.id, action: 'redeliver',
|
|
2098
|
+
idleLabel: '重新取回作品', busyLabel: '取回中…', failPrefix: '重新取回作品',
|
|
2099
|
+
title: '重新下载已生成的作品,不会再次生成,也不会增加生成费用'
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
/* D3 cancel:改变远端事实,需二次确认。只有供应商明确确认才会显示「已取消」;
|
|
2104
|
+
不支持或无法确认时保持真实状态(观察暂停/结果未知),绝不伪造已取消。 */
|
|
2105
|
+
function CoreCancelButton(props) {
|
|
2106
|
+
if (!props.row || props.row.cancelable !== true) return null;
|
|
2107
|
+
return React.createElement(CoreTaskManualButton, {
|
|
2108
|
+
id: props.row.id, action: 'cancel',
|
|
2109
|
+
idleLabel: '取消任务', busyLabel: '取消中…', failPrefix: '取消任务',
|
|
2110
|
+
title: '请求远端取消该任务;只有供应商明确确认才会记为已取消',
|
|
2111
|
+
confirm: '请求远端取消该任务?\n只有供应商明确确认后才会记为「已取消」;不支持或无法确认时保持真实状态,绝不伪造已取消。'
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
/* D4 retry as new task:产生新的真实计费。Core 记录不持久化生成指令,所以必须
|
|
2116
|
+
重新输入 prompt(绝不从旧任务"恢复");二次确认文案明确重复计费。 */
|
|
2117
|
+
function CoreRetryButton(props) {
|
|
2118
|
+
if (!props.row || props.row.retryable !== true) return null;
|
|
2119
|
+
return React.createElement(CoreTaskManualButton, {
|
|
2120
|
+
id: props.row.id, action: 'retry',
|
|
2121
|
+
idleLabel: '重试为新任务', busyLabel: '创建中…', failPrefix: '重试',
|
|
2122
|
+
title: '创建一个新任务重新生成,可能产生重复费用;生成指令需要重新输入',
|
|
2123
|
+
confirm: '将创建一个新任务并可能产生重复生成费用,确定继续?',
|
|
2124
|
+
createdPrefix: '已创建新任务:',
|
|
2125
|
+
buildBody: function () {
|
|
2126
|
+
var isTts = props.row.capability === 'tts';
|
|
2127
|
+
var isTranscribe = props.row.capability === 'transcribe';
|
|
2128
|
+
var draft = window.prompt(isTts
|
|
2129
|
+
? 'Core 记录不保存合成文本。请重新输入新任务要合成的文本:'
|
|
2130
|
+
: isTranscribe
|
|
2131
|
+
? 'Core 记录不保存音频地址。请重新输入新任务要转写音频的公网或 oss:// 地址:'
|
|
2132
|
+
: 'Core 记录不保存生成指令。请重新输入新任务使用的生成指令:');
|
|
2133
|
+
if (draft === null) return null;
|
|
2134
|
+
draft = String(draft).trim();
|
|
2135
|
+
if (!draft) return null;
|
|
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 }
|
|
2142
|
+
: isTranscribe ? { audio_url: draft, confirmBilling: true }
|
|
2143
|
+
: { prompt: draft, confirmBilling: true, ...(sourceArtifactId ? { source_artifact_id: String(sourceArtifactId).trim() } : {}) };
|
|
2144
|
+
}
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
/* 注意力处置(Host 偏好,零 Core 写入):不再提醒/恢复提醒/移除/恢复显示。
|
|
2149
|
+
文案明确"仅在本机隐藏提醒,任务与产物记录保留",不使用"消除记录"类措辞。 */
|
|
2150
|
+
function CoreDispositionButtons(props) {
|
|
2151
|
+
var row = props.row;
|
|
2152
|
+
if (!row || row.suppressed === true || row.historical === true) return null;
|
|
2153
|
+
if (row.disposition === 'hidden') {
|
|
2154
|
+
return React.createElement(CoreTaskManualButton, {
|
|
2155
|
+
id: row.id, action: 'unhide',
|
|
2156
|
+
idleLabel: '恢复显示', busyLabel: '恢复中…', failPrefix: '恢复显示',
|
|
2157
|
+
title: '在本机恢复显示该任务;Core 记录一直在,只是被隐藏'
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
var attentionish = row.userState === 'attention' || row.userState === 'delivery_failed';
|
|
2161
|
+
var children = [];
|
|
2162
|
+
if (attentionish && row.disposition !== 'acknowledged') {
|
|
2163
|
+
children.push(React.createElement(CoreTaskManualButton, {
|
|
2164
|
+
key: 'ack', id: row.id, action: 'acknowledge',
|
|
2165
|
+
idleLabel: '不再提醒', busyLabel: '处理中…', failPrefix: '不再提醒',
|
|
2166
|
+
title: '仅在本机不再提醒该任务;任务与产物记录保留在 Core'
|
|
2167
|
+
}));
|
|
2168
|
+
children.push(React.createElement(CoreTaskManualButton, {
|
|
2169
|
+
key: 'hide', id: row.id, action: 'hide',
|
|
2170
|
+
idleLabel: '移除', busyLabel: '移除中…', failPrefix: '移除',
|
|
2171
|
+
title: '仅在本机隐藏提醒,任务与产物记录保留在 Core',
|
|
2172
|
+
confirm: '仅在本机隐藏此任务的提醒;任务与产物记录保留在 Core,可随时在高级诊断恢复显示。确定移除?'
|
|
2173
|
+
}));
|
|
2174
|
+
}
|
|
2175
|
+
if (attentionish && row.disposition === 'acknowledged') {
|
|
2176
|
+
children.push(React.createElement(CoreTaskManualButton, {
|
|
2177
|
+
key: 'restore', id: row.id, action: 'restore',
|
|
2178
|
+
idleLabel: '恢复提醒', busyLabel: '恢复中…', failPrefix: '恢复提醒',
|
|
2179
|
+
title: '取消"不再提醒",该任务重新出现在需要处理分区'
|
|
2180
|
+
}));
|
|
2181
|
+
}
|
|
2182
|
+
return children.length ? React.createElement('span', {}, children) : null;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
/* 只读卡片:显示稳定 Task ID、模型、更新时间和关联作品;不渲染供应商身份、错误原文或路径,无展开/删除入口。
|
|
2186
|
+
「重新观察」/「重新取回作品」/「取消任务」/「重试为新任务」是仅有的动作(子组件,卡片本体仍不 fetch),
|
|
2187
|
+
且仅当投影标记对应门时出现。 */
|
|
2188
|
+
function coreTaskCard(row) {
|
|
2189
|
+
var artifactIds = Array.isArray(row.artifactIds) ? row.artifactIds : [];
|
|
2190
|
+
var links = row.mediaReady && artifactIds.length
|
|
2191
|
+
? React.createElement('div', { className: 'iris-wb-row' }, artifactIds.map(function (id, index) {
|
|
2192
|
+
return React.createElement('a', {
|
|
2193
|
+
key: id, className: 'iris-wb-link', target: '_blank', rel: 'noreferrer',
|
|
2194
|
+
href: '/iris/api/core/artifact/' + encodeURIComponent(id) + '/media'
|
|
2195
|
+
}, '▶ 作品 ' + (index + 1));
|
|
2196
|
+
}))
|
|
2197
|
+
: null;
|
|
2198
|
+
return React.createElement('div', {
|
|
2199
|
+
key: 'core:' + row.id,
|
|
2200
|
+
className: 'iris-wb-card iris-core-user-row',
|
|
2201
|
+
title: '只读投影:更多事实见下方「高级诊断 · Core 任务事实」'
|
|
2202
|
+
},
|
|
2203
|
+
React.createElement('div', { className: 'iris-core-user-head' },
|
|
2204
|
+
React.createElement('span', {}, capIcon(row.capability)),
|
|
2205
|
+
coreTaskBadge(row),
|
|
2206
|
+
React.createElement('span', { className: 'iris-wb-kv iris-core-user-model' }, row.model || '未记录'),
|
|
2207
|
+
React.createElement('span', { className: 'iris-wb-muted' }, '更新 ' + fmtTime(row.updatedAt))),
|
|
2208
|
+
React.createElement('div', { className: 'iris-wb-row' },
|
|
2209
|
+
React.createElement('span', { className: 'iris-core-id' }, row.id)),
|
|
2210
|
+
React.createElement(CoreReobserveButton, { row: row }),
|
|
2211
|
+
React.createElement(CoreRedeliverButton, { row: row }),
|
|
2212
|
+
React.createElement(CoreCancelButton, { row: row }),
|
|
2213
|
+
React.createElement(CoreRetryButton, { row: row }),
|
|
2214
|
+
React.createElement(CoreDispositionButtons, { row: row }),
|
|
2215
|
+
row.userState === 'succeeded' && !row.mediaReady
|
|
2216
|
+
? React.createElement('div', { className: 'iris-wb-row' },
|
|
2217
|
+
React.createElement('span', { className: 'iris-wb-badge warn', title: '关联作品记录不在当前快照中,请稍后刷新' }, '作品文件暂时不可用'))
|
|
2218
|
+
: null,
|
|
2219
|
+
links);
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
function coreTaskMini(row) {
|
|
2223
|
+
return React.createElement('div', {
|
|
2224
|
+
key: 'core:' + row.id, className: 'iris-task-mini',
|
|
2225
|
+
title: row.label + ' · ' + row.id
|
|
2226
|
+
},
|
|
2227
|
+
React.createElement('span', {}, capIcon(row.capability)),
|
|
2228
|
+
coreTaskBadge(row),
|
|
2229
|
+
React.createElement('span', { className: 'iris-task-mini-prompt' }, row.model || row.id),
|
|
2230
|
+
React.createElement('span', { className: 'iris-wb-muted' }, fmtTime(row.updatedAt)));
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
/* hidden 与 suppressed(重试成功自动静默)从用户任务区整体消失;canceled
|
|
2234
|
+
用 historical 进入历史且不计 attention;acknowledged 异常行也进入历史但可恢复。 */
|
|
2235
|
+
function splitCoreUserRows(rows) {
|
|
2236
|
+
var live = [], attention = [], done = [], acknowledged = [];
|
|
2237
|
+
for (var i = 0; i < rows.length; i++) {
|
|
2238
|
+
var row = rows[i];
|
|
2239
|
+
if (!row || row.suppressed === true || row.disposition === 'hidden') continue;
|
|
2240
|
+
if (row.historical === true) done.push(row);
|
|
2241
|
+
else if (row.userState === 'running' || row.userState === 'observation_paused') live.push(row);
|
|
2242
|
+
else if (row.userState === 'attention' || row.userState === 'delivery_failed') {
|
|
2243
|
+
if (row.disposition === 'acknowledged') acknowledged.push(row);
|
|
2244
|
+
else attention.push(row);
|
|
2245
|
+
}
|
|
2246
|
+
else if (row.userState === 'succeeded') done.push(row);
|
|
2247
|
+
}
|
|
2248
|
+
return { live: live, attention: attention, done: done, acknowledged: acknowledged };
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
function CoreRuntimePanel() {
|
|
2252
|
+
var dataPair = React.useState(null);
|
|
2253
|
+
var data = dataPair[0];
|
|
2254
|
+
var setData = dataPair[1];
|
|
2255
|
+
var busyPair = React.useState(false);
|
|
2256
|
+
var busy = busyPair[0];
|
|
2257
|
+
var setBusy = busyPair[1];
|
|
2258
|
+
var errorPair = React.useState('');
|
|
2259
|
+
var error = errorPair[0];
|
|
2260
|
+
var setError = errorPair[1];
|
|
2261
|
+
var openPair = React.useState(false);
|
|
2262
|
+
var open = openPair[0];
|
|
2263
|
+
var setOpen = openPair[1];
|
|
2264
|
+
var selectedPair = React.useState('');
|
|
2265
|
+
var selectedCoreTask = selectedPair[0];
|
|
2266
|
+
var setSelectedCoreTask = selectedPair[1];
|
|
2267
|
+
var copyPair = React.useState('');
|
|
2268
|
+
var copyNote = copyPair[0];
|
|
2269
|
+
var setCopyNote = copyPair[1];
|
|
2270
|
+
function load() {
|
|
2271
|
+
if (busy) return;
|
|
2272
|
+
setBusy(true);
|
|
2273
|
+
setError('');
|
|
2274
|
+
fetch('/iris/api/core/snapshot?limit=12')
|
|
2275
|
+
.then(function (res) {
|
|
2276
|
+
if (res.status === 404) return { available: false, backendReloadRequired: true, tasks: { total: 0, recent: [] }, artifacts: { total: 0, recent: [] } };
|
|
2277
|
+
return res.ok ? res.json() : Promise.reject(new Error('读取失败(HTTP ' + res.status + ')'));
|
|
2278
|
+
}).then(setData)
|
|
2279
|
+
.catch(function (caught) { setError('Core 运行事实读取失败:' + caught.message); })
|
|
2280
|
+
.finally(function () { setBusy(false); });
|
|
2281
|
+
}
|
|
2282
|
+
function copyCoreId(value, label) {
|
|
2283
|
+
setCopyNote('');
|
|
2284
|
+
var clipboard = window.navigator && window.navigator.clipboard;
|
|
2285
|
+
if (!clipboard || typeof clipboard.writeText !== 'function') {
|
|
2286
|
+
setCopyNote('浏览器不允许自动复制;请长按上方 ID 复制。');
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
clipboard.writeText(value)
|
|
2290
|
+
.then(function () { setCopyNote(label + ' 已复制'); })
|
|
2291
|
+
.catch(function () { setCopyNote('复制失败;请长按上方 ID 复制。'); });
|
|
2292
|
+
}
|
|
2293
|
+
React.useEffect(function () {
|
|
2294
|
+
function changed() { load(); }
|
|
2295
|
+
load();
|
|
2296
|
+
window.addEventListener('iris-core-artifacts-changed', changed);
|
|
2297
|
+
window.addEventListener('iris-core-refresh-tick', changed);
|
|
2298
|
+
return function () {
|
|
2299
|
+
window.removeEventListener('iris-core-artifacts-changed', changed);
|
|
2300
|
+
window.removeEventListener('iris-core-refresh-tick', changed);
|
|
2301
|
+
};
|
|
2302
|
+
}, []);
|
|
2303
|
+
var taskData = data && data.tasks || { total: 0, recent: [] };
|
|
2304
|
+
var artifactData = data && data.artifacts || { total: 0, recent: [] };
|
|
2305
|
+
/* 处置偏好投影:诊断层可见 hidden/acknowledged/suppressed,并可恢复显示。 */
|
|
2306
|
+
var dispositionMap = {};
|
|
2307
|
+
((data && data.userTasks) || []).forEach(function (row) { dispositionMap[row.id] = row; });
|
|
2308
|
+
function dispositionLabelOf(row) {
|
|
2309
|
+
if (!row) return '';
|
|
2310
|
+
if (row.suppressed) return '已由新任务成功交付自动静默';
|
|
2311
|
+
if (row.disposition === 'hidden') return '已在本机隐藏(记录保留,可恢复显示)';
|
|
2312
|
+
if (row.disposition === 'acknowledged') return '已设「不再提醒」(本机偏好,记录保留)';
|
|
2313
|
+
return '';
|
|
2314
|
+
}
|
|
2315
|
+
var selectedTask = (taskData.recent || []).find(function (task) { return task.id === selectedCoreTask; });
|
|
2316
|
+
var taskRows = (taskData.recent || []).slice(0, 6).map(function (task) {
|
|
2317
|
+
var state = coreTaskState(task);
|
|
2318
|
+
var disposed = dispositionMap[task.id];
|
|
2319
|
+
var hiddenMark = disposed && disposed.disposition === 'hidden' ? ' · 已隐藏' : '';
|
|
2320
|
+
var selected = selectedCoreTask === task.id;
|
|
2321
|
+
return React.createElement('button', {
|
|
2322
|
+
key: task.id,
|
|
2323
|
+
type: 'button',
|
|
2324
|
+
className: 'iris-core-task-row' + (selected ? ' selected' : ''),
|
|
2325
|
+
'aria-expanded': selected,
|
|
2326
|
+
onClick: function () { setSelectedCoreTask(selected ? '' : task.id); }
|
|
2327
|
+
},
|
|
2328
|
+
React.createElement('span', { className: 'iris-core-state-dot ' + state.tone }),
|
|
2329
|
+
React.createElement('span', { className: 'iris-core-task-main' },
|
|
2330
|
+
React.createElement('strong', {}, coreCapabilityLabel(task.capability) + ' · ' + state.label + hiddenMark),
|
|
2331
|
+
React.createElement('span', { className: 'iris-wb-muted' }, coreModelLabel(task.modelRef) + ' · ' + fmtTime(task.updatedAt))),
|
|
2332
|
+
React.createElement('span', { className: 'iris-core-task-arrow' }, selected ? '▼' : '›'));
|
|
2333
|
+
});
|
|
2334
|
+
var selectedArtifacts = selectedTask ? (selectedTask.artifactIds || []).map(function (id) {
|
|
2335
|
+
return (artifactData.recent || []).find(function (artifact) { return artifact.id === id; }) || { id: id };
|
|
2336
|
+
}) : [];
|
|
2337
|
+
var selectedDisposition = selectedTask ? dispositionMap[selectedTask.id] : null;
|
|
2338
|
+
var dispositionNote = dispositionLabelOf(selectedDisposition);
|
|
2339
|
+
var detail = selectedTask ? React.createElement('div', { className: 'iris-core-detail' },
|
|
2340
|
+
React.createElement('div', { className: 'iris-doctor-top' },
|
|
2341
|
+
React.createElement('strong', { className: 'iris-doctor-score' }, '任务事实'),
|
|
2342
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: function () { copyCoreId(selectedTask.id, 'Task ID'); } }, '复制 Task ID')),
|
|
2343
|
+
React.createElement('div', { className: 'iris-core-id' }, selectedTask.id),
|
|
2344
|
+
React.createElement('div', { className: 'iris-core-detail-grid' },
|
|
2345
|
+
React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '模型'), React.createElement('span', {}, coreModelLabel(selectedTask.modelRef))),
|
|
2346
|
+
React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, 'Provider'), React.createElement('span', { className: 'iris-core-id' }, selectedTask.providerId || '未记录')),
|
|
2347
|
+
React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '取消状态'), React.createElement('span', {}, coreCancelLabel(selectedTask.cancelState))),
|
|
2348
|
+
dispositionNote ? React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '处置状态'), React.createElement('span', {}, dispositionNote)) : null,
|
|
2349
|
+
React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '重试来源'), React.createElement('span', { className: 'iris-core-id' }, selectedTask.retriedFrom || '无')),
|
|
2350
|
+
React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '创建时间'), React.createElement('span', {}, fmtTime(selectedTask.createdAt))),
|
|
2351
|
+
React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '最后更新'), React.createElement('span', {}, fmtTime(selectedTask.updatedAt)))),
|
|
2352
|
+
React.createElement('div', {},
|
|
2353
|
+
React.createElement('div', { className: 'iris-wb-muted', style: { marginBottom: '4px' } }, '尝试记录 · ' + (selectedTask.attempts || []).length),
|
|
2354
|
+
React.createElement('div', { className: 'iris-core-task-list' }, (selectedTask.attempts || []).map(function (attempt) {
|
|
2355
|
+
return React.createElement('div', { key: attempt.id, className: 'iris-core-attempt' },
|
|
2356
|
+
React.createElement('span', {}, '第 ' + attempt.ordinal + ' 次 · ' + coreAttemptState(attempt) + ' · ' + coreModelLabel(attempt.model)),
|
|
2357
|
+
React.createElement('span', { className: 'iris-wb-muted' }, fmtTime(attempt.finishedAt || attempt.startedAt)),
|
|
2358
|
+
attempt.error && attempt.error.safeMessage ? React.createElement('span', { className: 'iris-wb-err' }, attempt.error.safeMessage) : null);
|
|
2359
|
+
}))),
|
|
2360
|
+
React.createElement('div', {},
|
|
2361
|
+
React.createElement('div', { className: 'iris-wb-muted', style: { marginBottom: '4px' } }, '关联作品 · ' + selectedArtifacts.length),
|
|
2362
|
+
selectedArtifacts.length ? React.createElement('div', { className: 'iris-core-task-list' }, selectedArtifacts.map(function (artifact) {
|
|
2363
|
+
var canOpen = selectedTask.capability === 'image' && (!artifact.mediaType || String(artifact.mediaType).indexOf('image/') === 0);
|
|
2364
|
+
return React.createElement('div', { key: artifact.id, className: 'iris-core-artifact' },
|
|
2365
|
+
React.createElement('span', { className: 'iris-core-id', style: { flex: 1 } }, artifact.id),
|
|
2366
|
+
artifact.size ? React.createElement('span', { className: 'iris-wb-muted' }, fmtBytes(artifact.size)) : null,
|
|
2367
|
+
canOpen ? React.createElement('a', { className: 'iris-wb-link', href: '/iris/api/core/artifact/' + encodeURIComponent(artifact.id) + '/media', target: '_blank', rel: 'noreferrer' }, '打开作品') : null,
|
|
2368
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: function () { copyCoreId(artifact.id, 'Artifact ID'); } }, '复制 ID'));
|
|
2369
|
+
})) : React.createElement('div', { className: 'iris-wb-empty' }, '当前任务没有可用作品')),
|
|
2370
|
+
selectedTask.lastError && selectedTask.lastError.safeMessage ? React.createElement('div', { className: 'iris-wb-err' }, selectedTask.lastError.safeMessage) : null,
|
|
2371
|
+
React.createElement(CoreReobserveButton, {
|
|
2372
|
+
row: selectedTask && selectedTask.acceptance === 'accepted' && selectedTask.remoteTaskId
|
|
2373
|
+
&& ['none', 'unknown'].indexOf(selectedTask.outcome) >= 0 && selectedTask.phase !== 'terminal'
|
|
2374
|
+
? { id: selectedTask.id, observable: true } : null
|
|
2375
|
+
}),
|
|
2376
|
+
React.createElement(CoreRedeliverButton, {
|
|
2377
|
+
row: selectedTask && selectedTask.outcome === 'succeeded'
|
|
2378
|
+
&& selectedTask.deliveryState === 'failed' && selectedTask.remoteTaskId
|
|
2379
|
+
? { id: selectedTask.id, redeliverable: true } : null
|
|
2380
|
+
}),
|
|
2381
|
+
React.createElement(CoreCancelButton, {
|
|
2382
|
+
row: selectedTask && selectedTask.acceptance === 'accepted' && selectedTask.remoteTaskId
|
|
2383
|
+
&& ['none', 'unknown'].indexOf(selectedTask.outcome) >= 0 && selectedTask.phase !== 'terminal'
|
|
2384
|
+
&& (!selectedTask.cancelState || selectedTask.cancelState === 'none')
|
|
2385
|
+
? { id: selectedTask.id, cancelable: true } : null
|
|
2386
|
+
}),
|
|
2387
|
+
React.createElement(CoreRetryButton, {
|
|
2388
|
+
row: selectedTask && selectedTask.phase === 'terminal'
|
|
2389
|
+
&& !(selectedTask.outcome === 'succeeded' && selectedTask.deliveryState === 'ready')
|
|
2390
|
+
? { id: selectedTask.id, capability: selectedTask.capability, sourceArtifactId: selectedTask.sourceArtifactId, retryable: true } : null
|
|
2391
|
+
}),
|
|
2392
|
+
React.createElement(CoreDispositionButtons, {
|
|
2393
|
+
row: selectedDisposition
|
|
2394
|
+
? { id: selectedDisposition.id, userState: selectedDisposition.userState,
|
|
2395
|
+
disposition: selectedDisposition.disposition, suppressed: selectedDisposition.suppressed }
|
|
2396
|
+
: null
|
|
2397
|
+
}),
|
|
2398
|
+
React.createElement('div', { className: 'iris-clean-note' }, '事实面板:「重新观察」只查询远端最新状态、「重新取回作品」只重新下载已生成的产物、「取消任务」只有供应商明确确认才记为已取消、「重试为新任务」会创建新任务并可能产生重复费用;这里不会重新提交、删除或修改既有任务。')) : null;
|
|
2399
|
+
var countText = data && data.available
|
|
2400
|
+
? taskData.total + ' 个任务 · ' + artifactData.total + ' 个作品'
|
|
2401
|
+
: (busy ? '正在读取…' : '查看底层任务与作品关系');
|
|
2402
|
+
return React.createElement('div', { className: 'iris-wb-card iris-core-runtime' },
|
|
2403
|
+
React.createElement('div', { className: 'iris-core-runtime-head' },
|
|
2404
|
+
React.createElement('div', { className: 'iris-core-runtime-title' },
|
|
2405
|
+
React.createElement('strong', {}, 'Core 任务事实'),
|
|
2406
|
+
React.createElement('span', { className: 'iris-wb-muted' }, countText)),
|
|
2407
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', 'aria-expanded': open, onClick: function () { setOpen(!open); } }, open ? '收起' : '展开')),
|
|
2408
|
+
open ? React.createElement('div', { className: 'iris-core-runtime-body' },
|
|
2409
|
+
React.createElement('div', { className: 'iris-core-runtime-tools' },
|
|
2410
|
+
React.createElement('span', { className: 'iris-wb-muted', style: { flex: 1 } }, '只读开发诊断 · 点击任务查看尝试与作品'),
|
|
2411
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', disabled: busy, onClick: load }, busy ? '读取中…' : '刷新')),
|
|
2412
|
+
error ? React.createElement('div', { className: 'iris-doctor-row error' }, React.createElement('span', {}, '×'), React.createElement('span', {}, error)) : null,
|
|
2413
|
+
data && !data.available ? React.createElement('div', { className: 'iris-wb-empty' },
|
|
2414
|
+
data.backendReloadRequired ? 'DSH 后端尚未加载 Core 路由;请重启 DSH 后重试。' : '尚无 Core 运行事实;读取不会创建目录。') : null,
|
|
2415
|
+
data && data.available ? (taskRows.length ? React.createElement('div', { className: 'iris-core-task-list' }, taskRows)
|
|
2416
|
+
: React.createElement('div', { className: 'iris-wb-empty' }, '当前没有 Core Task')) : null,
|
|
2417
|
+
detail,
|
|
2418
|
+
copyNote ? React.createElement('div', { className: 'iris-clean-note' }, copyNote) : null) : null);
|
|
1288
2419
|
}
|
|
1289
2420
|
|
|
1290
2421
|
/* ---- 泡泡常用卡片选择器(设置页:多项勾选 → 出现在悬浮窗「常用」标签) ---- */
|
|
@@ -1428,6 +2559,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1428
2559
|
var recent = (state && state.tasks && state.tasks.recent) || [];
|
|
1429
2560
|
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
1430
2561
|
var artifactTotal = (state && state.artifacts && state.artifacts.total) || 0;
|
|
2562
|
+
// 同一任务区合并 legacy 与 Core 只读投影;Core 行仅有的动作是「重新观察」「重新取回作品」与需确认的「取消任务」。
|
|
2563
|
+
var coreUser = useCoreUserTasks();
|
|
2564
|
+
var coreGroups = splitCoreUserRows(coreUser.rows || []);
|
|
1431
2565
|
var selPair = React.useState(null);
|
|
1432
2566
|
var selectedTask = selPair[0];
|
|
1433
2567
|
var setSelectedTask = selPair[1];
|
|
@@ -1440,7 +2574,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1440
2574
|
React.createElement(ProviderManager, {}),
|
|
1441
2575
|
React.createElement('div', { className: 'iris-wb-sec' }, '能力健康'),
|
|
1442
2576
|
React.createElement(CapabilityHealthOverview, {}),
|
|
1443
|
-
React.createElement('div', { className: 'iris-wb-muted' }, '绿色有效 7
|
|
2577
|
+
React.createElement('div', { className: 'iris-wb-muted' }, '绿色有效 7 天;真实成功或显式实测会刷新。429 冷却显示琥珀色,额度或预算耗尽显示红色;点击模型能力可实测恢复。不会后台探测。'),
|
|
1444
2578
|
React.createElement('div', { className: 'iris-wb-sec' }, '能力分配(failover 顺序)'),
|
|
1445
2579
|
React.createElement(CapabilityAssigner, {}),
|
|
1446
2580
|
React.createElement('div', { className: 'iris-wb-sec' }, 'Iris 泡泡快捷卡片(勾选后显示在「⚡ 常用」标签)'),
|
|
@@ -1451,17 +2585,25 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1451
2585
|
React.createElement(HostDoctorPanel, {}),
|
|
1452
2586
|
React.createElement('div', { className: 'iris-wb-sec' }, '运行中任务'),
|
|
1453
2587
|
React.createElement('div', { className: 'iris-wb-box' },
|
|
1454
|
-
running.length
|
|
2588
|
+
running.length || coreGroups.live.length ? [].concat(
|
|
2589
|
+
running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); }),
|
|
2590
|
+
coreGroups.live.map(coreTaskCard))
|
|
1455
2591
|
: React.createElement('div', { className: 'iris-wb-empty' }, '暂无进行中任务')),
|
|
1456
|
-
attention.length ? React.createElement('div', {},
|
|
2592
|
+
attention.length || coreGroups.attention.length || coreUser.degraded ? React.createElement('div', {},
|
|
1457
2593
|
React.createElement('div', { className: 'iris-wb-sec' }, '需要处理'),
|
|
1458
2594
|
React.createElement('div', { className: 'iris-wb-box' },
|
|
1459
|
-
|
|
2595
|
+
coreUser.degraded ? React.createElement('div', { className: 'iris-clean-note' },
|
|
2596
|
+
'部分 Core 记录损坏或媒体缺失,已跳过 ' + coreUser.droppedTasks + ' 条;其余事实照常显示。') : null,
|
|
2597
|
+
[].concat(
|
|
2598
|
+
attention.map(function (t) { return taskRow(t, false, selectedTask === t.id, onSelect); }),
|
|
2599
|
+
coreGroups.attention.map(coreTaskCard)))) : null,
|
|
1460
2600
|
React.createElement('div', { className: 'iris-wb-sec' }, '作品库'),
|
|
1461
2601
|
React.createElement(ArtifactGallery, { total: artifactTotal }),
|
|
2602
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '高级诊断'),
|
|
2603
|
+
React.createElement(CoreRuntimePanel, {}),
|
|
1462
2604
|
React.createElement('div', { className: 'iris-wb-sec' }, '历史任务'),
|
|
1463
2605
|
React.createElement(CleanupBar, {}),
|
|
1464
|
-
React.createElement(HistoryBrowser, { recent: recent, selected: selectedTask, onSelect: onSelect }),
|
|
2606
|
+
React.createElement(HistoryBrowser, { recent: recent, coreHistory: [].concat(coreGroups.done, coreGroups.acknowledged), selected: selectedTask, onSelect: onSelect }),
|
|
1465
2607
|
React.createElement('div', { className: 'iris-wb-sec' }, '操作'),
|
|
1466
2608
|
React.createElement(ActionGroups, {}));
|
|
1467
2609
|
}
|
|
@@ -1473,8 +2615,23 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1473
2615
|
var recent = (state && state.tasks && state.tasks.recent) || [];
|
|
1474
2616
|
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
1475
2617
|
var recentTotal = (state && state.tasks && state.tasks.recentTotal) || recent.length;
|
|
1476
|
-
var
|
|
1477
|
-
var
|
|
2618
|
+
var legacyWorks = (state && state.artifacts && state.artifacts.recent) || [];
|
|
2619
|
+
var coreWorks = useCoreWorks(6);
|
|
2620
|
+
// 同一任务/作品区合并 Core 只读投影;完成提示会话内只出现一次,刷新后从事实重算。
|
|
2621
|
+
var coreUser = useCoreUserTasks();
|
|
2622
|
+
var coreGroups = splitCoreUserRows(coreUser.rows || []);
|
|
2623
|
+
var arrivedPair = React.useState([]);
|
|
2624
|
+
var coreArrived = arrivedPair[0];
|
|
2625
|
+
var setCoreArrived = arrivedPair[1];
|
|
2626
|
+
var coreRowsKey = (coreUser.rows || []).map(function (row) { return row.id + '@' + row.revision; }).join(',');
|
|
2627
|
+
React.useEffect(function () {
|
|
2628
|
+
var added = noteCoreCompletions(coreUser.rows || []);
|
|
2629
|
+
if (added.length) setCoreArrived(added);
|
|
2630
|
+
}, [coreRowsKey]);
|
|
2631
|
+
var works = coreWorks.concat(legacyWorks).sort(function (a, b) {
|
|
2632
|
+
return String(b.createdAt || '').localeCompare(String(a.createdAt || ''));
|
|
2633
|
+
}).slice(0, 6);
|
|
2634
|
+
var artifactTotal = ((state && state.artifacts && state.artifacts.total) || legacyWorks.length) + coreWorks.length;
|
|
1478
2635
|
var cards = useBubbleCards()[0];
|
|
1479
2636
|
var tabPair = React.useState('tasks');
|
|
1480
2637
|
var tab = tabPair[0];
|
|
@@ -1487,12 +2644,16 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1487
2644
|
if (tab === 'tasks') {
|
|
1488
2645
|
// 快捷面板承载当前任务、需要处理的异常事实与独立作品库中的最近作品。
|
|
1489
2646
|
body = React.createElement('div', { className: 'iris-wb-box' },
|
|
1490
|
-
|
|
2647
|
+
coreArrived.length ? React.createElement('div', { className: 'iris-wb-badge ok', role: 'status' },
|
|
2648
|
+
'新完成:' + coreArrived.map(function (row) { return row.model || row.id; }).join('、')) : null,
|
|
2649
|
+
running.length || coreGroups.live.length ? React.createElement('div', {},
|
|
1491
2650
|
React.createElement('div', { className: 'iris-wb-sec' }, '运行中'),
|
|
1492
|
-
running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); })
|
|
1493
|
-
|
|
2651
|
+
running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); }),
|
|
2652
|
+
coreGroups.live.map(coreTaskMini)) : null,
|
|
2653
|
+
attention.length || coreGroups.attention.length ? React.createElement('div', {},
|
|
1494
2654
|
React.createElement('div', { className: 'iris-wb-sec' }, '需要处理'),
|
|
1495
|
-
attention.slice(0, 4).map(function (t) { return taskRowMini(t, selectedTask === t.id, onSelect); })
|
|
2655
|
+
attention.slice(0, 4).map(function (t) { return taskRowMini(t, selectedTask === t.id, onSelect); }),
|
|
2656
|
+
coreGroups.attention.slice(0, 4).map(coreTaskMini)) : null,
|
|
1496
2657
|
React.createElement('div', { className: 'iris-wb-sec' }, '最近作品'),
|
|
1497
2658
|
works.length ? works.map(artifactRowMini)
|
|
1498
2659
|
: React.createElement('div', { className: 'iris-wb-empty' }, '尚无作品'),
|
|
@@ -1513,6 +2674,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1513
2674
|
selectedTask ? React.createElement(TaskDetailDrawer, { taskId: selectedTask }) : null);
|
|
1514
2675
|
}
|
|
1515
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
|
+
|
|
1516
2688
|
function PromptOptimizerControl(props) {
|
|
1517
2689
|
var draft = props.useInput(function (value) { return value.draft; });
|
|
1518
2690
|
var phase = props.useInput(function (value) { return value.phase; });
|
|
@@ -1538,8 +2710,26 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1538
2710
|
var configPair = React.useState(null);
|
|
1539
2711
|
var configState = configPair[0];
|
|
1540
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];
|
|
1541
2725
|
var abortRef = React.useRef(null);
|
|
1542
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);
|
|
1543
2733
|
|
|
1544
2734
|
function promptRequest(operation, body, signal) {
|
|
1545
2735
|
return fetch('/iris/api/prompt-optimizer/' + operation, {
|
|
@@ -1565,6 +2755,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1565
2755
|
React.useEffect(function () {
|
|
1566
2756
|
return function () {
|
|
1567
2757
|
if (abortRef.current) abortRef.current.abort();
|
|
2758
|
+
abortRef.current = null;
|
|
1568
2759
|
};
|
|
1569
2760
|
}, []);
|
|
1570
2761
|
|
|
@@ -1575,53 +2766,96 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1575
2766
|
return function () { window.removeEventListener('iris-prompt-config-changed', changed); };
|
|
1576
2767
|
}, []);
|
|
1577
2768
|
|
|
2769
|
+
React.useEffect(function () {
|
|
2770
|
+
cancel();
|
|
2771
|
+
setResult(null);
|
|
2772
|
+
lastWrittenRef.current = null;
|
|
2773
|
+
resultSessionRef.current = null;
|
|
2774
|
+
}, [props.sessionId]);
|
|
2775
|
+
|
|
1578
2776
|
function publishConfig(data) {
|
|
1579
2777
|
setConfigState(data);
|
|
1580
2778
|
try { window.dispatchEvent(new CustomEvent('iris-prompt-config-changed', { detail: data })); } catch (_) {}
|
|
1581
2779
|
}
|
|
1582
2780
|
|
|
1583
|
-
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; }
|
|
1584
2793
|
if (!draft || !draft.trim()) { setNote('请先在输入框中写入提示词'); return; }
|
|
1585
2794
|
if (refCount) { setNote('当前版本暂不改写含 @ 或 / 结构化引用的草稿,以免引用失效'); return; }
|
|
1586
2795
|
if (phase !== 'plain') { setNote('输入框正忙,请稍后再试'); return; }
|
|
1587
2796
|
if (abortRef.current) abortRef.current.abort();
|
|
1588
2797
|
var controller = new AbortController();
|
|
2798
|
+
var requestSessionId = props.sessionId;
|
|
1589
2799
|
abortRef.current = controller;
|
|
1590
2800
|
setBusy(true);
|
|
1591
|
-
setNote('正在调用模型优化…这可能产生模型费用');
|
|
2801
|
+
setNote(mode === 'assemble' ? '正在组装,不调用模型…' : '正在调用模型优化…这可能产生模型费用');
|
|
1592
2802
|
setResult(null);
|
|
2803
|
+
lastWrittenRef.current = null;
|
|
1593
2804
|
promptRequest('optimize', {
|
|
1594
2805
|
text: draft,
|
|
1595
2806
|
target: target,
|
|
2807
|
+
mode: mode,
|
|
2808
|
+
rules: selectedRules(mode),
|
|
1596
2809
|
sessionId: props.sessionId,
|
|
1597
2810
|
route: selectedRoute || undefined
|
|
1598
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('未收到完整优化结果');
|
|
1599
2815
|
setResult(data);
|
|
2816
|
+
setEdited(data.optimized);
|
|
2817
|
+
resultSessionRef.current = requestSessionId;
|
|
1600
2818
|
setNote('已生成预览,尚未写回输入框');
|
|
1601
2819
|
}).catch(function (err) {
|
|
2820
|
+
if (abortRef.current !== controller) return;
|
|
1602
2821
|
if (controller.signal.aborted) setNote('已取消');
|
|
1603
2822
|
else setNote(String(err && err.message || err));
|
|
1604
2823
|
}).finally(function () {
|
|
1605
|
-
if (abortRef.current === controller) abortRef.current = null;
|
|
1606
|
-
setBusy(false);
|
|
2824
|
+
if (abortRef.current === controller) { abortRef.current = null; setBusy(false); }
|
|
1607
2825
|
});
|
|
1608
2826
|
}
|
|
1609
2827
|
|
|
1610
2828
|
function cancel() {
|
|
1611
|
-
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;
|
|
1612
2842
|
}
|
|
1613
2843
|
|
|
1614
2844
|
function replaceDraft(text) {
|
|
1615
2845
|
if (!result) return;
|
|
1616
|
-
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;
|
|
1617
2850
|
props.inputActions.setDraft(text);
|
|
2851
|
+
lastWrittenRef.current = text;
|
|
1618
2852
|
setNote(text === result.original ? '已恢复优化前原文' : '已写回输入框;不会自动发送');
|
|
1619
2853
|
}
|
|
1620
2854
|
|
|
1621
2855
|
function copyResult() {
|
|
1622
|
-
if (!result) return;
|
|
2856
|
+
if (!result || !validEdited()) return;
|
|
1623
2857
|
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
1624
|
-
navigator.clipboard.writeText(
|
|
2858
|
+
navigator.clipboard.writeText(edited).then(function () { setNote('已复制优化结果'); }).catch(function () { setNote('复制失败,请手动选择文本'); });
|
|
1625
2859
|
} else {
|
|
1626
2860
|
setNote('浏览器不支持自动复制,请手动选择文本');
|
|
1627
2861
|
}
|
|
@@ -1687,15 +2921,21 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1687
2921
|
var reasoningSetting = configState && configState.config && configState.config.generation && configState.config.generation.reasoningEffort || 'off-if-supported';
|
|
1688
2922
|
var reasoningLabel = reasoningSetting === 'off-if-supported' ? '支持时关闭' : reasoningSetting === 'provider-default' ? '供应商默认' : reasoningSetting === 'inherit' ? '跟随会话' : reasoningSetting;
|
|
1689
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
|
+
}
|
|
1690
2930
|
|
|
1691
2931
|
var panel = null;
|
|
1692
2932
|
if (open) {
|
|
1693
2933
|
panel = React.createElement(React.Fragment, {},
|
|
1694
|
-
React.createElement('div', { className: 'iris-po-backdrop', onClick:
|
|
2934
|
+
React.createElement('div', { className: 'iris-po-backdrop', onClick: closePanel, 'aria-hidden': 'true' }),
|
|
1695
2935
|
React.createElement('div', { className: 'iris-po-panel', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Iris 提示词优化', onClick: function (e) { e.stopPropagation(); } },
|
|
1696
2936
|
React.createElement('div', { className: 'iris-po-head' },
|
|
1697
2937
|
React.createElement('strong', { className: 'iris-po-title' }, React.createElement('span', { className: 'iris-po-mark' }, '🫧'), 'Iris 提示词优化'),
|
|
1698
|
-
React.createElement('button', { className: 'iris-po-close', onClick:
|
|
2938
|
+
React.createElement('button', { className: 'iris-po-close', onClick: closePanel, title: '关闭' }, '✕')),
|
|
1699
2939
|
React.createElement('div', { className: 'iris-po-muted' }, '只处理当前未发送草稿,不读取会话历史;调用模型可能产生费用。'),
|
|
1700
2940
|
React.createElement('label', { className: 'iris-po-field' },
|
|
1701
2941
|
React.createElement('span', {}, '优化目标'),
|
|
@@ -1705,17 +2945,37 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1705
2945
|
React.createElement('option', { value: 'video' }, '视频生成'),
|
|
1706
2946
|
React.createElement('option', { value: 's2v' }, '首尾帧视频'))),
|
|
1707
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 配置。' : '当前后端尚未支持规则,请更新后重新载入。')),
|
|
1708
2955
|
React.createElement('div', { className: 'iris-po-actions' },
|
|
1709
|
-
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'); } }, '只组装(不调用模型)'),
|
|
1710
2958
|
busy ? React.createElement('button', { className: 'iris-po-btn', onClick: cancel }, '取消') : null),
|
|
1711
2959
|
result ? React.createElement('div', { className: 'iris-po-result' },
|
|
1712
|
-
React.createElement('div', { className: 'iris-po-result-label' }, '
|
|
1713
|
-
React.createElement('textarea', {
|
|
1714
|
-
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) : '本次只组装,未调用模型'),
|
|
1715
2975
|
React.createElement('div', { className: 'iris-po-actions' },
|
|
1716
|
-
React.createElement('button', { className: 'iris-po-primary', onClick: function () { replaceDraft(
|
|
2976
|
+
React.createElement('button', { className: 'iris-po-primary', onClick: function () { replaceDraft(edited); } }, '写回输入框'),
|
|
1717
2977
|
React.createElement('button', { className: 'iris-po-btn', onClick: copyResult }, '复制'),
|
|
1718
|
-
React.createElement('button', { className: 'iris-po-btn', onClick: optimize }, '
|
|
2978
|
+
React.createElement('button', { className: 'iris-po-btn', disabled: busy, onClick: function () { optimize(result.mode); } }, '重新生成预览'),
|
|
1719
2979
|
React.createElement('button', { className: 'iris-po-btn', onClick: function () { replaceDraft(result.original); } }, '恢复原文'))) : null,
|
|
1720
2980
|
note ? React.createElement('div', { className: 'iris-po-note' }, note) : null,
|
|
1721
2981
|
React.createElement('details', { className: 'iris-po-config' },
|
|
@@ -1736,7 +2996,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1736
2996
|
type: 'button',
|
|
1737
2997
|
title: refCount ? '含结构化引用的草稿暂不支持优化' : '优化当前输入框提示词',
|
|
1738
2998
|
'aria-expanded': open ? 'true' : 'false',
|
|
1739
|
-
onClick: function () {
|
|
2999
|
+
onClick: function () { if (open) closePanel(); else setOpen(true); }
|
|
1740
3000
|
}, '🫧'),
|
|
1741
3001
|
panel);
|
|
1742
3002
|
}
|
|
@@ -1754,12 +3014,37 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1754
3014
|
}));
|
|
1755
3015
|
}
|
|
1756
3016
|
|
|
3017
|
+
/* 曲线鸢尾花:矢量源见 docs/assets/logo/iris-bubble.svg。
|
|
3018
|
+
使用实色与无 ID 的路径,多个座位同时渲染也不会发生 SVG 引用冲突。 */
|
|
3019
|
+
var IRIS_MARK_PARTS = [
|
|
3020
|
+
{ fill: '#7db9a4', d: 'M30.5 39h3v18h-3z' },
|
|
3021
|
+
{ fill: '#7db9a4', d: 'M32 55C24 55 20 50 19 44C26 45 31 48 32 55Z' },
|
|
3022
|
+
{ fill: '#9cd1b7', d: 'M32 54C40 53 44 49 45 43C38 44 33 48 32 54Z' },
|
|
3023
|
+
{ fill: '#b5a0f3', d: 'M29 31C18 30 11 22 15 14C24 13 30 20 29 31Z' },
|
|
3024
|
+
{ fill: '#b5a0f3', d: 'M35 31C46 30 53 22 49 14C40 13 34 20 35 31Z' },
|
|
3025
|
+
{ fill: '#e0d4ff', d: 'M32 7C24 13 23 23 32 34C41 23 40 13 32 7Z' },
|
|
3026
|
+
{ fill: '#9b80df', d: 'M31 31C22 24 11 28 8 36C12 45 24 47 31 34Z' },
|
|
3027
|
+
{ fill: '#9b80df', d: 'M33 31C42 24 53 28 56 36C52 45 40 47 33 34Z' },
|
|
3028
|
+
{ fill: '#c4aff6', d: 'M32 29C24 35 23 44 32 50C41 44 40 35 32 29Z' },
|
|
3029
|
+
{ fill: '#f2ce83', d: 'M32 31C29 32 28 34 29 36L32 40L35 36C36 34 35 32 32 31Z' }
|
|
3030
|
+
];
|
|
3031
|
+
function IRIS_MARK_SVG() {
|
|
3032
|
+
return React.createElement('svg', {
|
|
3033
|
+
width: 26, height: 26, viewBox: '0 0 64 64',
|
|
3034
|
+
role: 'img', 'aria-label': 'Iris', focusable: 'false'
|
|
3035
|
+
}, IRIS_MARK_PARTS.map(function (part, i) {
|
|
3036
|
+
return React.createElement('path', { key: i, d: part.d, fill: part.fill });
|
|
3037
|
+
}));
|
|
3038
|
+
}
|
|
3039
|
+
|
|
1757
3040
|
function FloatingBubble() {
|
|
1758
3041
|
var state = useIrisState(5000);
|
|
1759
3042
|
var providers = (state && state.providers) || [];
|
|
1760
3043
|
var fallbackConfigured = providers.some(function (p) { return p.enabled && p.apiKeyHint; });
|
|
1761
|
-
var
|
|
3044
|
+
var overallState = state && state.health && HEALTH_META[state.health.overall]
|
|
1762
3045
|
? state.health.overall : (fallbackConfigured ? 'configured' : 'unconfigured');
|
|
3046
|
+
var overallCooling = overallState === 'failed' && state.health.retryAt;
|
|
3047
|
+
var overallHealth = overallCooling ? 'cooling' : overallState;
|
|
1763
3048
|
var running = (state && state.tasks && state.tasks.running) || [];
|
|
1764
3049
|
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
1765
3050
|
|
|
@@ -1885,9 +3170,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1885
3170
|
onPointerMove: onPointerMove,
|
|
1886
3171
|
onPointerUp: onPointerUp,
|
|
1887
3172
|
onPointerCancel: onPointerCancel,
|
|
1888
|
-
title: 'Iris ' + healthText({ status:
|
|
3173
|
+
title: 'Iris ' + healthText({ status: overallState, rateLimited: !!overallCooling, retryAt: overallCooling || undefined }) + ':点击打开快捷面板'
|
|
1889
3174
|
},
|
|
1890
|
-
React.createElement('div', { className: 'iris-bubble-btn' },
|
|
3175
|
+
React.createElement('div', { className: 'iris-bubble-btn' }, React.createElement(IRIS_MARK_SVG)),
|
|
1891
3176
|
badge,
|
|
1892
3177
|
panel);
|
|
1893
3178
|
}
|