@mokuyoaxis/dsh-iris 0.1.4 → 0.2.0
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/CHANGELOG.md +54 -1
- package/README.en.md +72 -15
- package/README.md +72 -15
- package/bin/dsh-iris.js +392 -13
- package/docs/AGENT_QUICKSTART.md +35 -0
- package/docs/ARCHITECTURE.md +80 -64
- package/docs/ARTIFACT_MANIFEST.md +60 -0
- package/docs/CORE_RUNTIME_CONTRACT.md +83 -0
- package/docs/DSH_CORE_MIGRATION.md +106 -0
- package/docs/DSH_RC2_ADAPTATION.md +36 -0
- package/docs/HEADLESS_CLI.md +217 -0
- package/docs/HOST_ADAPTER_CONTRACT.md +31 -12
- package/docs/HOST_DOCTOR.md +2 -0
- package/docs/MODEL_PORT_CONTRACT.md +300 -0
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +51 -10
- package/docs/PROVIDER_RUNTIME_HARNESS.md +33 -0
- package/docs/PROVIDER_SUBMISSION_CONTRACT.md +1 -1
- package/docs/ROADMAP.md +42 -86
- package/docs/SECURITY.md +4 -3
- 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/releases/0.2.0.md +48 -0
- package/lib/actions.js +374 -362
- package/lib/adapters.js +52 -15
- package/lib/api.js +152 -5
- package/lib/client.js +763 -48
- package/lib/command-service.js +420 -0
- package/lib/config.js +56 -43
- package/lib/core-artifact-store.js +598 -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-runtime.js +232 -0
- package/lib/core-tasks.js +560 -0
- package/lib/core-user-projection.js +192 -0
- package/lib/doctor.js +92 -33
- package/lib/dsh-core-adapter.js +599 -0
- package/lib/dsh-host-adapter.js +58 -7
- package/lib/generation-input.js +95 -0
- package/lib/guard.js +3 -3
- package/lib/index.js +159 -54
- package/lib/model-invoker.js +218 -0
- package/lib/model-port-contract.js +333 -0
- package/lib/models.js +97 -11
- package/lib/provider-adapter.js +49 -23
- package/lib/provider-adapters.js +84 -21
- package/lib/provider-catalog.js +171 -0
- package/lib/provider-contract.js +72 -3
- package/lib/provider-protocol.js +51 -2
- package/lib/provider-task-runner.js +328 -0
- package/lib/tasks.js +16 -4
- package/lib/vision.js +28 -7
- package/package.json +8 -8
- package/user_guide.md +27 -12
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.
|
|
24
|
+
var IRIS_CLIENT_VERSION = '0.2.0';
|
|
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; }',
|
|
@@ -296,15 +325,30 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
296
325
|
console.log('[iris] client apply start');
|
|
297
326
|
adoptStyles();
|
|
298
327
|
|
|
299
|
-
/* ----
|
|
328
|
+
/* ---- 当前主视图会话(旧 current / 0.2 mainView 持有者;不猜测列表首项) ---- */
|
|
300
329
|
var currentSessionId = null;
|
|
301
330
|
try {
|
|
302
|
-
var sessions = ctx && ctx.sessions;
|
|
331
|
+
var sessions = ctx && (typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions);
|
|
303
332
|
var listObs = sessions && sessions.list;
|
|
304
333
|
if (listObs && typeof listObs.getSnapshot === 'function') {
|
|
305
|
-
|
|
334
|
+
function readCurrentSession() {
|
|
335
|
+
var snap = listObs.getSnapshot() || {};
|
|
336
|
+
if (Object.prototype.hasOwnProperty.call(snap, 'current')) {
|
|
337
|
+
currentSessionId = typeof snap.current === 'string' && snap.current ? snap.current : null;
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
var rows = Object.values(snap.byId || {}).filter(function (row) {
|
|
341
|
+
return row && typeof row.id === 'string' && row.id && row.retainedBy && row.retainedBy.mainView > 0;
|
|
342
|
+
});
|
|
343
|
+
currentSessionId = rows.length === 1 ? rows[0].id : null;
|
|
344
|
+
}
|
|
345
|
+
readCurrentSession();
|
|
306
346
|
if (typeof listObs.subscribe === 'function') {
|
|
307
|
-
|
|
347
|
+
// ObservableSnapshot 的变更通知不承诺携带快照,始终回读权威值。
|
|
348
|
+
var unsubscribeSession = listObs.subscribe(readCurrentSession);
|
|
349
|
+
if (typeof unsubscribeSession === 'function' && typeof ctx.effect === 'function') {
|
|
350
|
+
ctx.effect(function () { return unsubscribeSession; });
|
|
351
|
+
}
|
|
308
352
|
}
|
|
309
353
|
}
|
|
310
354
|
} catch (_) { /* 拿不到会话 → L1 退化为只列 iris 产物 */ }
|
|
@@ -338,7 +382,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
338
382
|
fetch('/iris/api/state')
|
|
339
383
|
.then(function (res) { return res.ok ? res.json() : null; })
|
|
340
384
|
.then(function (data) {
|
|
341
|
-
if (data)
|
|
385
|
+
if (data) {
|
|
386
|
+
apply(data);
|
|
387
|
+
window.dispatchEvent(new CustomEvent('iris-core-refresh-tick'));
|
|
388
|
+
}
|
|
342
389
|
})
|
|
343
390
|
.catch(function () { /* 网络抖动静默 */ });
|
|
344
391
|
}
|
|
@@ -425,7 +472,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
425
472
|
}
|
|
426
473
|
|
|
427
474
|
/* ---- 任务详情抽屉:按需拉取 /iris/api/task/:id ---- */
|
|
428
|
-
function useTaskDetail(taskId) {
|
|
475
|
+
function useTaskDetail(taskId, revision) {
|
|
429
476
|
var pair = React.useState(null);
|
|
430
477
|
var setDetail = pair[1];
|
|
431
478
|
React.useEffect(function () {
|
|
@@ -437,7 +484,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
437
484
|
.then(function (data) { if (alive && data) setDetail(data); })
|
|
438
485
|
.catch(function () { /* 静默 */ });
|
|
439
486
|
return function () { alive = false; };
|
|
440
|
-
}, [taskId]);
|
|
487
|
+
}, [taskId, revision]);
|
|
441
488
|
return pair[0];
|
|
442
489
|
}
|
|
443
490
|
|
|
@@ -513,7 +560,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
513
560
|
React.createElement('span', { className: 'iris-wb-muted' }, task.createdAt ? new Date(task.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : ''));
|
|
514
561
|
}
|
|
515
562
|
|
|
516
|
-
function TaskRecoveryActions({ detail }) {
|
|
563
|
+
function TaskRecoveryActions({ detail, onDeleted }) {
|
|
517
564
|
var pair = React.useState('');
|
|
518
565
|
var note = pair[0];
|
|
519
566
|
var setNote = pair[1];
|
|
@@ -530,13 +577,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
530
577
|
var canRedeliver = detail.schemaVersion === 2 && !acknowledged && state === 'artifact_unavailable' && detail.remoteTaskId;
|
|
531
578
|
var canRetry = detail.schemaVersion === 2 && !acknowledged && ['needs_attention', 'failed', 'canceled'].indexOf(state) >= 0
|
|
532
579
|
&& (detail.cap === 'image' || (detail.cap === 'video' && detail.mode === 't2v'));
|
|
533
|
-
|
|
580
|
+
var canDelete = detail.status !== 'running' || (detail.schemaVersion === 2 && attentionState && acknowledged);
|
|
581
|
+
if (!canAcknowledge && !canRestore && !canReobserve && !canRedeliver && !canRetry && !canDelete) return null;
|
|
534
582
|
function run(action, body, confirmMessage) {
|
|
535
583
|
if (confirmMessage && !window.confirm(confirmMessage)) return;
|
|
536
584
|
setBusy(true);
|
|
537
585
|
setNote('处理中…');
|
|
538
586
|
postAction(action, body).then(function (r) {
|
|
539
587
|
setNote(r.ok ? ((r.d && r.d.text) || '操作已接受') : ('失败:' + ((r.d && r.d.error) || '未知错误')));
|
|
588
|
+
if (r.ok && action === 'tasks_delete' && typeof onDeleted === 'function') onDeleted();
|
|
540
589
|
}).catch(function (error) { setNote('失败:' + String((error && error.message) || error)); })
|
|
541
590
|
.finally(function () { setBusy(false); });
|
|
542
591
|
}
|
|
@@ -551,12 +600,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
551
600
|
onClick: function () { run('task_ack_attention', { task_id: detail.id }); } }, '标为已读') : null,
|
|
552
601
|
canRestore ? React.createElement('button', { className: 'iris-pm-btn', disabled: busy,
|
|
553
602
|
onClick: function () { run('task_restore_attention', { task_id: detail.id }); } }, '恢复提醒') : null,
|
|
603
|
+
canDelete ? React.createElement('button', { className: 'iris-pm-btn danger', disabled: busy,
|
|
604
|
+
onClick: function () { run('tasks_delete', { task_id: detail.id },
|
|
605
|
+
attentionState ? '该任务仍含未知远程事实;删除后无法再观察。确认只删除任务记录?' : '确认删除该任务记录?作品文件保留。'); } }, '删除记录') : null,
|
|
554
606
|
note ? React.createElement('span', { className: 'iris-clean-note' }, note) : null);
|
|
555
607
|
}
|
|
556
608
|
|
|
557
609
|
/* ---- 任务详情抽屉:点击任务卡片行展开 ---- */
|
|
558
|
-
function TaskDetailDrawer({ taskId }) {
|
|
559
|
-
var detail = useTaskDetail(taskId);
|
|
610
|
+
function TaskDetailDrawer({ taskId, revision, onDeleted }) {
|
|
611
|
+
var detail = useTaskDetail(taskId, revision);
|
|
560
612
|
if (!detail) {
|
|
561
613
|
return React.createElement('div', { className: 'iris-wb-drawer iris-wb-loading' }, '加载任务详情…');
|
|
562
614
|
}
|
|
@@ -565,7 +617,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
565
617
|
el.push(React.createElement('div', { key: 'p', className: 'iris-wb-prompt' }, detail.prompt || '(空)'));
|
|
566
618
|
if (Number(detail.schemaVersion) === 1) {
|
|
567
619
|
el.push(React.createElement('div', { key: 'legacy', className: 'iris-clean-note' },
|
|
568
|
-
'旧版任务记录 ·
|
|
620
|
+
'旧版任务记录 · 仅支持查看恢复事实。由于缺少可靠的远端受理与交付事实,不提供重新观察、重新交付或知情重试;已终止记录仍可删除。'));
|
|
569
621
|
}
|
|
570
622
|
var dispositionLabel = attentionDispositionLabel(detail);
|
|
571
623
|
if (dispositionLabel) {
|
|
@@ -622,7 +674,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
622
674
|
return React.createElement('span', { key: a.attachmentId, className: 'iris-wb-kv' }, a.attachmentId + (a.file ? ' (' + a.file + ')' : ''));
|
|
623
675
|
})));
|
|
624
676
|
}
|
|
625
|
-
el.push(React.createElement(TaskRecoveryActions, { key: 'recovery', detail: detail }));
|
|
677
|
+
el.push(React.createElement(TaskRecoveryActions, { key: 'recovery', detail: detail, onDeleted: onDeleted }));
|
|
626
678
|
return React.createElement('div', { className: 'iris-wb-drawer' }, ...el);
|
|
627
679
|
}
|
|
628
680
|
|
|
@@ -668,7 +720,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
668
720
|
task.createdAt ? '发起 ' + fmtTime(task.createdAt) : '',
|
|
669
721
|
task.finishedAt ? ' · 完成 ' + fmtTime(task.finishedAt) : ''));
|
|
670
722
|
}
|
|
671
|
-
if (selected) body.push(React.createElement(TaskDetailDrawer, { key: 'drawer', taskId: task.id }));
|
|
723
|
+
if (selected) body.push(React.createElement(TaskDetailDrawer, { key: 'drawer', taskId: task.id, revision: task.revision, onDeleted: function () { onSelect(null); } }));
|
|
672
724
|
return React.createElement('div', {
|
|
673
725
|
key: task.id,
|
|
674
726
|
className: 'iris-wb-card' + (isRunning ? ' iris-running' : '') + (selected ? ' selected' : ''),
|
|
@@ -719,6 +771,26 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
719
771
|
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { props.addModel(p, newId.trim()); setNewId(''); } }, '+ 添加')));
|
|
720
772
|
}
|
|
721
773
|
|
|
774
|
+
function ProviderEndpointEditor(props) {
|
|
775
|
+
var p = props.provider;
|
|
776
|
+
var valuePair = React.useState(p.mediaBaseUrl || '');
|
|
777
|
+
var value = valuePair[0];
|
|
778
|
+
var setValue = valuePair[1];
|
|
779
|
+
React.useEffect(function () { setValue(p.mediaBaseUrl || ''); }, [p.mediaBaseUrl]);
|
|
780
|
+
function save() {
|
|
781
|
+
postAction('providers_upsert', { id: p.id, mediaBaseUrl: value.trim() }).then(function (r) {
|
|
782
|
+
props.onNote(r.ok ? '媒体 Base URL 已更新' : (r.d.error || '更新失败'));
|
|
783
|
+
if (r.ok) props.onDone();
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
return React.createElement('div', { className: 'iris-pm-field' },
|
|
787
|
+
React.createElement('label', {}, '媒体 Base URL(可选)'),
|
|
788
|
+
React.createElement('div', { className: 'iris-wb-row' },
|
|
789
|
+
React.createElement('input', { value: value, placeholder: '留空则沿用 Base URL', onChange: function (e) { setValue(e.target.value); } }),
|
|
790
|
+
React.createElement('button', { className: 'iris-pm-btn', onClick: save }, '保存')),
|
|
791
|
+
React.createElement('span', { className: 'iris-pm-note' }, '百炼 Workspace 可填 …maas.aliyuncs.com/compatible-mode/v1;视觉仍使用普通 Base URL。'));
|
|
792
|
+
}
|
|
793
|
+
|
|
722
794
|
function ProviderManager() {
|
|
723
795
|
var listPair = React.useState(null);
|
|
724
796
|
var list = listPair[0];
|
|
@@ -728,7 +800,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
728
800
|
var addingPair = React.useState(false);
|
|
729
801
|
var adding = addingPair[0];
|
|
730
802
|
var setAdding = addingPair[1];
|
|
731
|
-
var newForm = React.useState({ name: '', baseUrl: '', apiKey: '', type: 'openai', mediaProtocol: 'auto' });
|
|
803
|
+
var newForm = React.useState({ name: '', baseUrl: '', mediaBaseUrl: '', apiKey: '', type: 'openai', mediaProtocol: 'auto' });
|
|
732
804
|
var notePair = React.useState('');
|
|
733
805
|
var note = notePair[0];
|
|
734
806
|
var setNote = notePair[1];
|
|
@@ -789,7 +861,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
789
861
|
if (open) {
|
|
790
862
|
body = React.createElement('div', { className: 'iris-pm-body' },
|
|
791
863
|
React.createElement('div', { className: 'iris-pm-note' }, 'Key ' + (p.apiKeyHint || '未配置') + ' · ' + (p.type || '') + ' · ' + (p.mediaProtocol || '')),
|
|
792
|
-
React.createElement('div', { className: 'iris-pm-
|
|
864
|
+
p.protocolInferred ? React.createElement('div', { className: 'iris-pm-note' }, '协议为推断值,请确认') : null,
|
|
865
|
+
React.createElement(ProviderEndpointEditor, { provider: p, onNote: setNote, onDone: refresh }),
|
|
866
|
+
React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '媒体协议'), React.createElement('select', { value: p.protocolInferred ? 'auto' : (p.mediaProtocol || 'auto'), onChange: function (e) { setProtocol(p, e.target.value); } },
|
|
867
|
+
React.createElement('option', { value: 'auto' }, '自动'),
|
|
868
|
+
p.mediaProtocol && !['dashscope', 'openai-images', 'auto'].includes(p.mediaProtocol) ? React.createElement('option', { value: p.mediaProtocol }, p.mediaProtocol + '(未支持)') : null,
|
|
793
869
|
React.createElement('option', { value: 'dashscope' }, 'DashScope(仅阿里云官方 HTTPS)'),
|
|
794
870
|
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'))),
|
|
795
871
|
React.createElement('div', { className: 'iris-pm-note' }, '能力: ' + ((p.capabilities || []).join(' / ') || '无')),
|
|
@@ -810,9 +886,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
810
886
|
addBlock = React.createElement('div', { className: 'iris-pm-card' },
|
|
811
887
|
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
888
|
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 }); } })),
|
|
889
|
+
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
890
|
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
891
|
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' }, '
|
|
892
|
+
React.createElement('option', { value: 'auto' }, '自动(按媒体 Base URL 安全判断)'),
|
|
816
893
|
React.createElement('option', { value: 'dashscope' }, 'DashScope(阿里云官方)'),
|
|
817
894
|
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'))),
|
|
818
895
|
React.createElement('div', { className: 'iris-wb-row' },
|
|
@@ -936,7 +1013,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
936
1013
|
body: JSON.stringify(vals)
|
|
937
1014
|
})
|
|
938
1015
|
.then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
|
|
939
|
-
.then(function (res) {
|
|
1016
|
+
.then(function (res) {
|
|
1017
|
+
setRun(res.ok ? { ok: true, text: res.d.text || '', img: res.d.imageUrl || res.d.imageDataUrl || null } : { ok: false, text: res.d.error || '执行失败' });
|
|
1018
|
+
if (res.ok && res.d && res.d.storage === 'core') window.dispatchEvent(new CustomEvent('iris-core-artifacts-changed'));
|
|
1019
|
+
})
|
|
940
1020
|
.catch(function () { setRun({ ok: false, text: '网络错误' }); });
|
|
941
1021
|
}
|
|
942
1022
|
var cardHealth = capability ? capabilityHealth(irisState, capability) : { status: 'verified' };
|
|
@@ -1165,14 +1245,16 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1165
1245
|
/* ---- 历史浏览器(设置页:按今天/昨天/更早分组折叠) ---- */
|
|
1166
1246
|
function HistoryBrowser(props) {
|
|
1167
1247
|
var recent = props.recent || [];
|
|
1248
|
+
var coreHistory = props.coreHistory || [];
|
|
1168
1249
|
var selected = props.selected;
|
|
1169
1250
|
var onSelect = props.onSelect;
|
|
1170
1251
|
var collapsedPair = React.useState({});
|
|
1171
1252
|
var collapsed = collapsedPair[0];
|
|
1172
1253
|
var setCollapsed = collapsedPair[1];
|
|
1173
|
-
if (!recent.length) return React.createElement('div', { className: 'iris-wb-empty' }, '尚无任务记录');
|
|
1254
|
+
if (!recent.length && !coreHistory.length) return React.createElement('div', { className: 'iris-wb-empty' }, '尚无任务记录');
|
|
1174
1255
|
var buckets = { 今天: [], 昨天: [], 更早: [] };
|
|
1175
|
-
recent.forEach(function (t) { buckets[dayBucket(t.createdAt)].push(t); });
|
|
1256
|
+
recent.forEach(function (t) { buckets[dayBucket(t.createdAt)].push({ legacy: t }); });
|
|
1257
|
+
coreHistory.forEach(function (row) { buckets[dayBucket(row.updatedAt)].push({ core: row }); });
|
|
1176
1258
|
var order = ['今天', '昨天', '更早'];
|
|
1177
1259
|
return React.createElement('div', {}, order.map(function (name) {
|
|
1178
1260
|
var rows = buckets[name];
|
|
@@ -1184,7 +1266,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1184
1266
|
onClick: function () { var n = {}; Object.keys(collapsed).forEach(function (k) { n[k] = collapsed[k]; }); n[name] = !isCollapsed; setCollapsed(n); }
|
|
1185
1267
|
}, React.createElement('span', {}, isCollapsed ? '▶' : '▼'), name + ' · ' + rows.length),
|
|
1186
1268
|
isCollapsed ? null : React.createElement('div', { className: 'iris-wb-box' },
|
|
1187
|
-
rows.map(function (
|
|
1269
|
+
rows.map(function (entry) {
|
|
1270
|
+
return entry.core
|
|
1271
|
+
? coreTaskCard(entry.core)
|
|
1272
|
+
: taskRow(entry.legacy, false, selected === entry.legacy.id, onSelect);
|
|
1273
|
+
})));
|
|
1188
1274
|
}));
|
|
1189
1275
|
}
|
|
1190
1276
|
|
|
@@ -1214,7 +1300,20 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1214
1300
|
var mime = String(item.mime || '');
|
|
1215
1301
|
if (mime.indexOf('image/') === 0) {
|
|
1216
1302
|
return React.createElement('a', { className: 'iris-gallery-preview', href: item.url, target: '_blank', rel: 'noreferrer', title: '打开原图' },
|
|
1217
|
-
React.createElement('img', {
|
|
1303
|
+
React.createElement('img', {
|
|
1304
|
+
src: item.url, alt: 'Iris 作品 ' + item.file, loading: 'lazy',
|
|
1305
|
+
// 媒体文件丢失(404/损坏)时局部降级为占位提示,作品区其余项不受影响
|
|
1306
|
+
onError: function (event) {
|
|
1307
|
+
var img = event && event.target;
|
|
1308
|
+
var box = img && img.parentNode;
|
|
1309
|
+
if (!box || box.querySelector('span')) return;
|
|
1310
|
+
img.style.display = 'none';
|
|
1311
|
+
var note = document.createElement('span');
|
|
1312
|
+
note.className = 'iris-gallery-meta';
|
|
1313
|
+
note.textContent = '作品文件暂时不可用';
|
|
1314
|
+
box.appendChild(note);
|
|
1315
|
+
}
|
|
1316
|
+
}));
|
|
1218
1317
|
}
|
|
1219
1318
|
if (mime.indexOf('video/') === 0) {
|
|
1220
1319
|
return React.createElement('div', { className: 'iris-gallery-preview' },
|
|
@@ -1227,6 +1326,44 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1227
1326
|
return React.createElement('a', { className: 'iris-gallery-preview iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开文件');
|
|
1228
1327
|
}
|
|
1229
1328
|
|
|
1329
|
+
function coreGalleryItems(data, limit) {
|
|
1330
|
+
return (((data && data.artifacts && data.artifacts.recent) || []).filter(function (artifact) {
|
|
1331
|
+
return artifact.kind !== 'host-input' && String(artifact.mediaType || '').indexOf('image/') === 0;
|
|
1332
|
+
}).map(function (artifact) {
|
|
1333
|
+
return {
|
|
1334
|
+
id: artifact.id, file: artifact.id, mime: artifact.mediaType,
|
|
1335
|
+
size: artifact.size, createdAt: artifact.createdAt || '', source: 'core',
|
|
1336
|
+
kind: artifact.kind,
|
|
1337
|
+
digest: artifact.digest && artifact.digest.value ? artifact.digest.value.slice(0, 12) : '',
|
|
1338
|
+
url: '/iris/api/core/artifact/' + encodeURIComponent(artifact.id) + '/media'
|
|
1339
|
+
};
|
|
1340
|
+
})).slice(0, limit || 200);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
function useCoreWorks(limit) {
|
|
1344
|
+
var pair = React.useState([]);
|
|
1345
|
+
var setItems = pair[1];
|
|
1346
|
+
React.useEffect(function () {
|
|
1347
|
+
var alive = true;
|
|
1348
|
+
function load() {
|
|
1349
|
+
fetch('/iris/api/core/snapshot?limit=' + Math.max(1, Math.min(200, Number(limit) || 6)))
|
|
1350
|
+
.then(function (res) { return res.ok ? res.json() : null; })
|
|
1351
|
+
.then(function (data) { if (alive && data) setItems(coreGalleryItems(data, limit)); })
|
|
1352
|
+
.catch(function () { /* Core 不可用不影响旧作品 */ });
|
|
1353
|
+
}
|
|
1354
|
+
function changed() { load(); }
|
|
1355
|
+
load();
|
|
1356
|
+
window.addEventListener('iris-core-artifacts-changed', changed);
|
|
1357
|
+
window.addEventListener('iris-core-refresh-tick', changed);
|
|
1358
|
+
return function () {
|
|
1359
|
+
alive = false;
|
|
1360
|
+
window.removeEventListener('iris-core-artifacts-changed', changed);
|
|
1361
|
+
window.removeEventListener('iris-core-refresh-tick', changed);
|
|
1362
|
+
};
|
|
1363
|
+
}, [limit]);
|
|
1364
|
+
return pair[0];
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1230
1367
|
function ArtifactGallery(props) {
|
|
1231
1368
|
var itemsPair = React.useState([]);
|
|
1232
1369
|
var items = itemsPair[0];
|
|
@@ -1234,6 +1371,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1234
1371
|
var totalPair = React.useState(Number(props.total || 0));
|
|
1235
1372
|
var total = totalPair[0];
|
|
1236
1373
|
var setTotal = totalPair[1];
|
|
1374
|
+
var corePair = React.useState([]);
|
|
1375
|
+
var coreItems = corePair[0];
|
|
1376
|
+
var setCoreItems = corePair[1];
|
|
1237
1377
|
var busyPair = React.useState(false);
|
|
1238
1378
|
var busy = busyPair[0];
|
|
1239
1379
|
var setBusy = busyPair[1];
|
|
@@ -1245,11 +1385,25 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1245
1385
|
if (busy) return;
|
|
1246
1386
|
setBusy(true);
|
|
1247
1387
|
var offset = append ? items.length : 0;
|
|
1248
|
-
fetch('/iris/api/artifacts?offset=' + offset + '&limit=24')
|
|
1249
|
-
.then(function (res) { return res.ok ? res.json() : Promise.reject(new Error('
|
|
1250
|
-
|
|
1388
|
+
var legacyRequest = fetch('/iris/api/artifacts?offset=' + offset + '&limit=24')
|
|
1389
|
+
.then(function (res) { return res.ok ? res.json() : Promise.reject(new Error('旧版作品读取失败')); });
|
|
1390
|
+
var coreRequest = append ? Promise.resolve(null) : fetch('/iris/api/core/snapshot?limit=200')
|
|
1391
|
+
.then(function (res) {
|
|
1392
|
+
if (res.status === 404) return { available: false, backendReloadRequired: true, artifacts: { recent: [] } };
|
|
1393
|
+
return res.ok ? res.json() : Promise.reject(new Error('Core 作品读取失败(HTTP ' + res.status + ')'));
|
|
1394
|
+
}).catch(function (error) { return { available: false, error: error.message, artifacts: { recent: [] } }; });
|
|
1395
|
+
Promise.all([legacyRequest, coreRequest])
|
|
1396
|
+
.then(function (values) {
|
|
1397
|
+
var data = values[0];
|
|
1398
|
+
var core = values[1];
|
|
1251
1399
|
setItems(append ? items.concat(data.items || []) : (data.items || []));
|
|
1252
1400
|
setTotal(Number(data.total || 0));
|
|
1401
|
+
if (core) {
|
|
1402
|
+
var nextCore = coreGalleryItems(core, 200);
|
|
1403
|
+
setCoreItems(nextCore);
|
|
1404
|
+
if (core.error) setNote(core.error);
|
|
1405
|
+
else if (core.backendReloadRequired) setNote('DSH 后端尚未加载 Core 路由;重启后新作品会出现在这里。');
|
|
1406
|
+
}
|
|
1253
1407
|
})
|
|
1254
1408
|
.catch(function (error) { setNote('作品库读取失败:' + error.message); })
|
|
1255
1409
|
.finally(function () { setBusy(false); });
|
|
@@ -1266,27 +1420,535 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1266
1420
|
}).catch(function (error) { setBusy(false); setNote('失败:' + error.message); });
|
|
1267
1421
|
}
|
|
1268
1422
|
|
|
1269
|
-
React.useEffect(function () {
|
|
1423
|
+
React.useEffect(function () {
|
|
1424
|
+
function changed() { load(false); }
|
|
1425
|
+
window.addEventListener('iris-core-artifacts-changed', changed);
|
|
1426
|
+
window.addEventListener('iris-core-refresh-tick', changed);
|
|
1427
|
+
load(false);
|
|
1428
|
+
return function () {
|
|
1429
|
+
window.removeEventListener('iris-core-artifacts-changed', changed);
|
|
1430
|
+
window.removeEventListener('iris-core-refresh-tick', changed);
|
|
1431
|
+
};
|
|
1432
|
+
}, [props.total]);
|
|
1433
|
+
var galleryItems = coreItems.concat(items).sort(function (a, b) {
|
|
1434
|
+
return String(b.createdAt || '').localeCompare(String(a.createdAt || ''));
|
|
1435
|
+
});
|
|
1436
|
+
var combinedTotal = coreItems.length + total;
|
|
1270
1437
|
|
|
1271
1438
|
return React.createElement('div', {},
|
|
1272
1439
|
React.createElement('div', { className: 'iris-gallery-tools' },
|
|
1273
|
-
React.createElement('span', { className: 'iris-clean-note' },
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1440
|
+
React.createElement('span', { className: 'iris-clean-note' },
|
|
1441
|
+
'共 ' + combinedTotal + ' 个作品 · Core ' + coreItems.length + ' · 旧版 ' + total + ' · 清任务历史不会删除'),
|
|
1442
|
+
React.createElement('button', { className: 'iris-clean-btn', disabled: busy, onClick: function () { load(false); } }, '刷新'),
|
|
1443
|
+
React.createElement('button', { className: 'iris-clean-btn', disabled: busy, onClick: function () { run('artifacts_reindex'); } }, '扫描旧版 outputs'),
|
|
1444
|
+
total ? React.createElement('button', { className: 'iris-clean-btn danger', disabled: busy, onClick: function () { run('artifacts_clear', {}, '永久删除旧版 outputs 作品?Core 作品不会删除;任务历史会保留。此操作不可逆。'); } }, '清空旧版作品') : null),
|
|
1445
|
+
galleryItems.length ? React.createElement('div', { className: 'iris-gallery-grid' }, galleryItems.map(function (item) {
|
|
1446
|
+
var isCore = item.source === 'core';
|
|
1447
|
+
return React.createElement('div', { key: (isCore ? 'core-' : 'legacy-') + item.id, className: 'iris-gallery-card' },
|
|
1278
1448
|
artifactPreview(item),
|
|
1279
1449
|
React.createElement('div', { className: 'iris-gallery-info' },
|
|
1280
1450
|
React.createElement('div', { className: 'iris-gallery-file', title: item.file }, item.file),
|
|
1281
|
-
React.createElement('div', { className: 'iris-gallery-meta' },
|
|
1451
|
+
React.createElement('div', { className: 'iris-gallery-meta' },
|
|
1452
|
+
(isCore ? 'Core · ' + (item.kind || 'artifact') + (item.digest ? ' · sha256 ' + item.digest : '') : fmtTime(item.createdAt)) + ' · ' + fmtBytes(item.size)),
|
|
1282
1453
|
React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1283
1454
|
React.createElement('a', { className: 'iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开'),
|
|
1284
|
-
React.createElement('
|
|
1285
|
-
|
|
1286
|
-
|
|
1455
|
+
isCore ? React.createElement('span', { className: 'iris-wb-muted' }, '内容哈希保护')
|
|
1456
|
+
: React.createElement('button', { className: 'iris-pm-btn danger', disabled: busy, onClick: function () { run('artifacts_delete', { artifact_id: item.id }, '永久删除作品“' + item.file + '”?任务历史会保留,但媒体将无法打开。'); } }, '删除'))));
|
|
1457
|
+
})) : React.createElement('div', { className: 'iris-wb-empty' }, busy ? '正在读取作品…' : '尚无作品;旧 outputs 文件可点“扫描旧版 outputs”找回。'),
|
|
1458
|
+
items.length < total ? React.createElement('button', { className: 'iris-clean-btn', disabled: busy, onClick: function () { load(true); } }, busy ? '加载中…' : '加载更多旧版作品') : null,
|
|
1287
1459
|
note ? React.createElement('div', { className: 'iris-clean-note', style: { marginTop: '7px' } }, note) : null);
|
|
1288
1460
|
}
|
|
1289
1461
|
|
|
1462
|
+
function coreCapabilityLabel(value) {
|
|
1463
|
+
return ({ image: '图片生成', video: '视频生成', tts: '语音合成', transcribe: '音频转写' })[value] || String(value || '媒体任务');
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
/* 高级诊断与用户任务区共用五类用户状态投影;与服务端 lib/core-user-projection.js 保持同一映射,
|
|
1467
|
+
文案必须区分“远端可能还在运行”和“已经明确失败”。 */
|
|
1468
|
+
function coreTaskState(task) {
|
|
1469
|
+
if (task.outcome === 'succeeded' && task.deliveryState === 'ready') return { tone: 'ok', state: 'succeeded', label: '已完成,作品可用' };
|
|
1470
|
+
if (task.outcome === 'succeeded' && task.deliveryState === 'failed') return { tone: 'error', state: 'delivery_failed', label: '已生成,作品取回失败' };
|
|
1471
|
+
if (task.outcome === 'succeeded') return { tone: 'warn', state: 'running', label: '已生成,正在保存作品' };
|
|
1472
|
+
if (task.outcome === 'failed') return { tone: 'error', state: 'attention', label: '已失败' };
|
|
1473
|
+
if (task.outcome === 'canceled') return { tone: 'idle', state: 'attention', label: '已取消' };
|
|
1474
|
+
if (task.outcome === 'unknown') return { tone: 'warn', state: 'attention', label: '任务结果未知,需要检查' };
|
|
1475
|
+
if (task.acceptance === 'unknown') return { tone: 'warn', state: 'attention', label: '提交结果未知,请检查后处理' };
|
|
1476
|
+
if (task.deliveryState === 'failed') return { tone: 'error', state: 'attention', label: '结果状态异常,需要检查' };
|
|
1477
|
+
if (task.watchState === 'suspended' || task.watchState === 'exhausted') return { tone: 'warn', state: 'observation_paused', label: '观察已暂停,远端可能仍在运行' };
|
|
1478
|
+
return { tone: 'idle', state: 'running', label: '运行中' };
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
function coreAttemptState(attempt) {
|
|
1482
|
+
if (attempt.resultKind === 'completed') return '已完成';
|
|
1483
|
+
if (attempt.acceptance === 'not_accepted') return '未受理';
|
|
1484
|
+
if (attempt.acceptance === 'unknown') return '是否受理未知';
|
|
1485
|
+
if (attempt.acceptance === 'accepted') return '已受理';
|
|
1486
|
+
return '正在提交';
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
function coreModelLabel(value) {
|
|
1490
|
+
var model = String(value || '');
|
|
1491
|
+
var separator = model.indexOf('::');
|
|
1492
|
+
return separator >= 0 ? model.slice(separator + 2) : (model || '未记录');
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
/* 诊断层允许更多事实:取消三态如实区分,不把"已请求/无法确认"显示成"已取消"。 */
|
|
1496
|
+
function coreCancelLabel(value) {
|
|
1497
|
+
if (value === 'remote_confirmed') return '供应商已明确确认取消';
|
|
1498
|
+
if (value === 'local_confirmed') return '本地已确认取消(远端未确认)';
|
|
1499
|
+
if (value === 'requested') return '取消请求进行中';
|
|
1500
|
+
if (value === 'unknown') return '已请求取消,远端结果未确认;可显式重新观察';
|
|
1501
|
+
return '无取消请求';
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
/* ---- Core 用户侧只读投影:任务区消费 /iris/api/core/snapshot 的 userTasks 安全 DTO ---- */
|
|
1505
|
+
var EMPTY_CORE_USER = Object.freeze({ rows: [], available: false, degraded: false, droppedTasks: 0 });
|
|
1506
|
+
|
|
1507
|
+
function useCoreUserTasks() {
|
|
1508
|
+
var pair = React.useState(EMPTY_CORE_USER);
|
|
1509
|
+
var setData = pair[1];
|
|
1510
|
+
React.useEffect(function () {
|
|
1511
|
+
var alive = true;
|
|
1512
|
+
function load() {
|
|
1513
|
+
fetch('/iris/api/core/snapshot?limit=200')
|
|
1514
|
+
.then(function (res) { return res.ok ? res.json() : null; })
|
|
1515
|
+
.then(function (data) {
|
|
1516
|
+
if (!alive || !data || !Array.isArray(data.userTasks)) return;
|
|
1517
|
+
setData({
|
|
1518
|
+
rows: data.userTasks,
|
|
1519
|
+
available: !!data.available,
|
|
1520
|
+
degraded: !!data.degraded,
|
|
1521
|
+
droppedTasks: Number(data.droppedTasks || 0)
|
|
1522
|
+
});
|
|
1523
|
+
})
|
|
1524
|
+
.catch(function () { /* Core 不可用不阻塞 legacy 任务区 */ });
|
|
1525
|
+
}
|
|
1526
|
+
function changed() { load(); }
|
|
1527
|
+
load();
|
|
1528
|
+
window.addEventListener('iris-core-artifacts-changed', changed);
|
|
1529
|
+
window.addEventListener('iris-core-refresh-tick', changed);
|
|
1530
|
+
return function () {
|
|
1531
|
+
alive = false;
|
|
1532
|
+
window.removeEventListener('iris-core-artifacts-changed', changed);
|
|
1533
|
+
window.removeEventListener('iris-core-refresh-tick', changed);
|
|
1534
|
+
};
|
|
1535
|
+
}, []);
|
|
1536
|
+
return pair[0];
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
/* 五类用户状态 → 既有 badge 色调;不新增第三套卡片样式,复用 iris-wb/iris-task-mini。 */
|
|
1540
|
+
function coreUserStateClass(state) {
|
|
1541
|
+
if (state === 'succeeded') return 'ok';
|
|
1542
|
+
if (state === 'delivery_failed' || state === 'attention') return 'err';
|
|
1543
|
+
if (state === 'observation_paused') return 'warn';
|
|
1544
|
+
return '';
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
function coreTaskBadge(row) {
|
|
1548
|
+
var tone = row.historical === true ? '' : coreUserStateClass(row.userState);
|
|
1549
|
+
return React.createElement('span', { className: 'iris-wb-badge' + (tone ? ' ' + tone : '') }, row.label);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
/* 完成提示只出现一次:会话内 Map 记录 core:id@revision,刷新页面后从事实重新计算基线。 */
|
|
1553
|
+
var coreCompletionSeen = null; // null = 尚未建立基线(首屏把既有完成项记为已见)
|
|
1554
|
+
function coreRowKey(row) { return 'core:' + row.id + '@' + Number(row.revision || 0); }
|
|
1555
|
+
function noteCoreCompletions(rows) {
|
|
1556
|
+
var current = {};
|
|
1557
|
+
for (var i = 0; i < rows.length; i++) {
|
|
1558
|
+
if (rows[i].userState === 'succeeded') current[coreRowKey(rows[i])] = true;
|
|
1559
|
+
}
|
|
1560
|
+
if (coreCompletionSeen === null) { coreCompletionSeen = current; return []; }
|
|
1561
|
+
var added = [];
|
|
1562
|
+
for (var j = 0; j < rows.length; j++) {
|
|
1563
|
+
var row = rows[j];
|
|
1564
|
+
if (row.userState === 'succeeded' && !coreCompletionSeen[coreRowKey(row)]) added.push(row);
|
|
1565
|
+
}
|
|
1566
|
+
for (var key in current) coreCompletionSeen[key] = true;
|
|
1567
|
+
return added;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
/* D1/D2 人工动作按钮:投影门满足才出现;每次点击只调用一次显式单步 API,绝不
|
|
1571
|
+
重新提交、也不间接重新生成;点击期间按钮禁用防重复,完成后复用
|
|
1572
|
+
iris-core-refresh-tick 让任务区/高级诊断重拉同一快照。服务端 Command 门仍
|
|
1573
|
+
是最终裁决(binding/能力校验失败返回稳定错误,行内提示,不改任务事实)。 */
|
|
1574
|
+
function CoreTaskManualButton(props) {
|
|
1575
|
+
var busyPair = React.useState(false);
|
|
1576
|
+
var busy = busyPair[0];
|
|
1577
|
+
var setBusy = busyPair[1];
|
|
1578
|
+
var notePair = React.useState('');
|
|
1579
|
+
var note = notePair[0];
|
|
1580
|
+
var setNote = notePair[1];
|
|
1581
|
+
function act() {
|
|
1582
|
+
if (busy) return; // 防重复触发:请求未结束前后端各拦一层
|
|
1583
|
+
var body = props.buildBody ? props.buildBody() : {};
|
|
1584
|
+
if (body === null) return; // 用户取消输入(如重试时放弃重新输入 prompt)
|
|
1585
|
+
if (props.confirm && !window.confirm(props.confirm)) return; // 改变远端事实/计费的动作需要二次确认
|
|
1586
|
+
setBusy(true);
|
|
1587
|
+
setNote('');
|
|
1588
|
+
fetch('/iris/api/core/task/' + encodeURIComponent(props.id) + '/' + props.action, {
|
|
1589
|
+
method: 'POST',
|
|
1590
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1591
|
+
body: JSON.stringify(body)
|
|
1592
|
+
}).then(function (res) {
|
|
1593
|
+
return res.json().then(function (data) { return { status: res.status, data: data }; })
|
|
1594
|
+
.catch(function () { return { status: res.status, data: null }; });
|
|
1595
|
+
}).then(function (result) {
|
|
1596
|
+
var detail = result.data && result.data.error;
|
|
1597
|
+
if (result.status !== 200) {
|
|
1598
|
+
setNote(props.failPrefix + '被拒绝:' + (detail && detail.message || '状态 ' + result.status));
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
var created = result.data && result.data.task;
|
|
1602
|
+
if (props.createdPrefix && created && created.id && created.id !== props.id) {
|
|
1603
|
+
setNote(props.createdPrefix + created.id);
|
|
1604
|
+
}
|
|
1605
|
+
window.dispatchEvent(new CustomEvent('iris-core-refresh-tick'));
|
|
1606
|
+
}).catch(function () {
|
|
1607
|
+
setNote(props.failPrefix + '请求失败;任务事实未改动');
|
|
1608
|
+
}).finally(function () { setBusy(false); });
|
|
1609
|
+
}
|
|
1610
|
+
return React.createElement('span', { className: 'iris-wb-row', style: { alignItems: 'center' } },
|
|
1611
|
+
React.createElement('button', {
|
|
1612
|
+
type: 'button', className: 'iris-pm-btn', disabled: busy,
|
|
1613
|
+
title: props.title,
|
|
1614
|
+
onClick: act
|
|
1615
|
+
}, busy ? props.busyLabel : props.idleLabel),
|
|
1616
|
+
note ? React.createElement('span', { className: 'iris-wb-err' }, note) : null);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
/* D1 reobserve:显式单步观察入口,投影标记 observable 才出现。 */
|
|
1620
|
+
function CoreReobserveButton(props) {
|
|
1621
|
+
if (!props.row || props.row.observable !== true) return null;
|
|
1622
|
+
return React.createElement(CoreTaskManualButton, {
|
|
1623
|
+
id: props.row.id, action: 'reobserve',
|
|
1624
|
+
idleLabel: '重新观察', busyLabel: '观察中…', failPrefix: '重新观察',
|
|
1625
|
+
title: '只向供应商查询一次该任务的最新状态,绝不重新提交'
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
/* D2 redeliver:delivery_failed 的唯一恢复入口;重新下载已生成的作品,不重新生成。 */
|
|
1630
|
+
function CoreRedeliverButton(props) {
|
|
1631
|
+
if (!props.row || props.row.redeliverable !== true) return null;
|
|
1632
|
+
return React.createElement(CoreTaskManualButton, {
|
|
1633
|
+
id: props.row.id, action: 'redeliver',
|
|
1634
|
+
idleLabel: '重新取回作品', busyLabel: '取回中…', failPrefix: '重新取回作品',
|
|
1635
|
+
title: '重新下载已生成的作品,不会再次生成,也不会增加生成费用'
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
/* D3 cancel:改变远端事实,需二次确认。只有供应商明确确认才会显示「已取消」;
|
|
1640
|
+
不支持或无法确认时保持真实状态(观察暂停/结果未知),绝不伪造已取消。 */
|
|
1641
|
+
function CoreCancelButton(props) {
|
|
1642
|
+
if (!props.row || props.row.cancelable !== true) return null;
|
|
1643
|
+
return React.createElement(CoreTaskManualButton, {
|
|
1644
|
+
id: props.row.id, action: 'cancel',
|
|
1645
|
+
idleLabel: '取消任务', busyLabel: '取消中…', failPrefix: '取消任务',
|
|
1646
|
+
title: '请求远端取消该任务;只有供应商明确确认才会记为已取消',
|
|
1647
|
+
confirm: '请求远端取消该任务?\n只有供应商明确确认后才会记为「已取消」;不支持或无法确认时保持真实状态,绝不伪造已取消。'
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
/* D4 retry as new task:产生新的真实计费。Core 记录不持久化生成指令,所以必须
|
|
1652
|
+
重新输入 prompt(绝不从旧任务"恢复");二次确认文案明确重复计费。 */
|
|
1653
|
+
function CoreRetryButton(props) {
|
|
1654
|
+
if (!props.row || props.row.retryable !== true) return null;
|
|
1655
|
+
return React.createElement(CoreTaskManualButton, {
|
|
1656
|
+
id: props.row.id, action: 'retry',
|
|
1657
|
+
idleLabel: '重试为新任务', busyLabel: '创建中…', failPrefix: '重试',
|
|
1658
|
+
title: '创建一个新任务重新生成,可能产生重复费用;生成指令需要重新输入',
|
|
1659
|
+
confirm: '将创建一个新任务并可能产生重复生成费用,确定继续?',
|
|
1660
|
+
createdPrefix: '已创建新任务:',
|
|
1661
|
+
buildBody: function () {
|
|
1662
|
+
var isTts = props.row.capability === 'tts';
|
|
1663
|
+
var isTranscribe = props.row.capability === 'transcribe';
|
|
1664
|
+
var draft = window.prompt(isTts
|
|
1665
|
+
? 'Core 记录不保存合成文本。请重新输入新任务要合成的文本:'
|
|
1666
|
+
: isTranscribe
|
|
1667
|
+
? 'Core 记录不保存音频地址。请重新输入新任务要转写音频的公网或 oss:// 地址:'
|
|
1668
|
+
: 'Core 记录不保存生成指令。请重新输入新任务使用的生成指令:');
|
|
1669
|
+
if (draft === null) return null;
|
|
1670
|
+
draft = String(draft).trim();
|
|
1671
|
+
if (!draft) return null;
|
|
1672
|
+
return isTts ? { text: draft, confirmBilling: true }
|
|
1673
|
+
: isTranscribe ? { audio_url: draft, confirmBilling: true }
|
|
1674
|
+
: { prompt: draft, confirmBilling: true };
|
|
1675
|
+
}
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
/* 注意力处置(Host 偏好,零 Core 写入):不再提醒/恢复提醒/移除/恢复显示。
|
|
1680
|
+
文案明确"仅在本机隐藏提醒,任务与产物记录保留",不使用"消除记录"类措辞。 */
|
|
1681
|
+
function CoreDispositionButtons(props) {
|
|
1682
|
+
var row = props.row;
|
|
1683
|
+
if (!row || row.suppressed === true || row.historical === true) return null;
|
|
1684
|
+
if (row.disposition === 'hidden') {
|
|
1685
|
+
return React.createElement(CoreTaskManualButton, {
|
|
1686
|
+
id: row.id, action: 'unhide',
|
|
1687
|
+
idleLabel: '恢复显示', busyLabel: '恢复中…', failPrefix: '恢复显示',
|
|
1688
|
+
title: '在本机恢复显示该任务;Core 记录一直在,只是被隐藏'
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
var attentionish = row.userState === 'attention' || row.userState === 'delivery_failed';
|
|
1692
|
+
var children = [];
|
|
1693
|
+
if (attentionish && row.disposition !== 'acknowledged') {
|
|
1694
|
+
children.push(React.createElement(CoreTaskManualButton, {
|
|
1695
|
+
key: 'ack', id: row.id, action: 'acknowledge',
|
|
1696
|
+
idleLabel: '不再提醒', busyLabel: '处理中…', failPrefix: '不再提醒',
|
|
1697
|
+
title: '仅在本机不再提醒该任务;任务与产物记录保留在 Core'
|
|
1698
|
+
}));
|
|
1699
|
+
children.push(React.createElement(CoreTaskManualButton, {
|
|
1700
|
+
key: 'hide', id: row.id, action: 'hide',
|
|
1701
|
+
idleLabel: '移除', busyLabel: '移除中…', failPrefix: '移除',
|
|
1702
|
+
title: '仅在本机隐藏提醒,任务与产物记录保留在 Core',
|
|
1703
|
+
confirm: '仅在本机隐藏此任务的提醒;任务与产物记录保留在 Core,可随时在高级诊断恢复显示。确定移除?'
|
|
1704
|
+
}));
|
|
1705
|
+
}
|
|
1706
|
+
if (attentionish && row.disposition === 'acknowledged') {
|
|
1707
|
+
children.push(React.createElement(CoreTaskManualButton, {
|
|
1708
|
+
key: 'restore', id: row.id, action: 'restore',
|
|
1709
|
+
idleLabel: '恢复提醒', busyLabel: '恢复中…', failPrefix: '恢复提醒',
|
|
1710
|
+
title: '取消"不再提醒",该任务重新出现在需要处理分区'
|
|
1711
|
+
}));
|
|
1712
|
+
}
|
|
1713
|
+
return children.length ? React.createElement('span', {}, children) : null;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
/* 只读卡片:显示稳定 Task ID、模型、更新时间和关联作品;不渲染供应商身份、错误原文或路径,无展开/删除入口。
|
|
1717
|
+
「重新观察」/「重新取回作品」/「取消任务」/「重试为新任务」是仅有的动作(子组件,卡片本体仍不 fetch),
|
|
1718
|
+
且仅当投影标记对应门时出现。 */
|
|
1719
|
+
function coreTaskCard(row) {
|
|
1720
|
+
var artifactIds = Array.isArray(row.artifactIds) ? row.artifactIds : [];
|
|
1721
|
+
var links = row.mediaReady && artifactIds.length
|
|
1722
|
+
? React.createElement('div', { className: 'iris-wb-row' }, artifactIds.map(function (id, index) {
|
|
1723
|
+
return React.createElement('a', {
|
|
1724
|
+
key: id, className: 'iris-wb-link', target: '_blank', rel: 'noreferrer',
|
|
1725
|
+
href: '/iris/api/core/artifact/' + encodeURIComponent(id) + '/media'
|
|
1726
|
+
}, '▶ 作品 ' + (index + 1));
|
|
1727
|
+
}))
|
|
1728
|
+
: null;
|
|
1729
|
+
return React.createElement('div', {
|
|
1730
|
+
key: 'core:' + row.id,
|
|
1731
|
+
className: 'iris-wb-card iris-core-user-row',
|
|
1732
|
+
title: '只读投影:更多事实见下方「高级诊断 · Core 任务事实」'
|
|
1733
|
+
},
|
|
1734
|
+
React.createElement('div', { className: 'iris-core-user-head' },
|
|
1735
|
+
React.createElement('span', {}, capIcon(row.capability)),
|
|
1736
|
+
coreTaskBadge(row),
|
|
1737
|
+
React.createElement('span', { className: 'iris-wb-kv iris-core-user-model' }, row.model || '未记录'),
|
|
1738
|
+
React.createElement('span', { className: 'iris-wb-muted' }, '更新 ' + fmtTime(row.updatedAt))),
|
|
1739
|
+
React.createElement('div', { className: 'iris-wb-row' },
|
|
1740
|
+
React.createElement('span', { className: 'iris-core-id' }, row.id)),
|
|
1741
|
+
React.createElement(CoreReobserveButton, { row: row }),
|
|
1742
|
+
React.createElement(CoreRedeliverButton, { row: row }),
|
|
1743
|
+
React.createElement(CoreCancelButton, { row: row }),
|
|
1744
|
+
React.createElement(CoreRetryButton, { row: row }),
|
|
1745
|
+
React.createElement(CoreDispositionButtons, { row: row }),
|
|
1746
|
+
row.userState === 'succeeded' && !row.mediaReady
|
|
1747
|
+
? React.createElement('div', { className: 'iris-wb-row' },
|
|
1748
|
+
React.createElement('span', { className: 'iris-wb-badge warn', title: '关联作品记录不在当前快照中,请稍后刷新' }, '作品文件暂时不可用'))
|
|
1749
|
+
: null,
|
|
1750
|
+
links);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
function coreTaskMini(row) {
|
|
1754
|
+
return React.createElement('div', {
|
|
1755
|
+
key: 'core:' + row.id, className: 'iris-task-mini',
|
|
1756
|
+
title: row.label + ' · ' + row.id
|
|
1757
|
+
},
|
|
1758
|
+
React.createElement('span', {}, capIcon(row.capability)),
|
|
1759
|
+
coreTaskBadge(row),
|
|
1760
|
+
React.createElement('span', { className: 'iris-task-mini-prompt' }, row.model || row.id),
|
|
1761
|
+
React.createElement('span', { className: 'iris-wb-muted' }, fmtTime(row.updatedAt)));
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
/* hidden 与 suppressed(重试成功自动静默)从用户任务区整体消失;canceled
|
|
1765
|
+
用 historical 进入历史且不计 attention;acknowledged 异常行也进入历史但可恢复。 */
|
|
1766
|
+
function splitCoreUserRows(rows) {
|
|
1767
|
+
var live = [], attention = [], done = [], acknowledged = [];
|
|
1768
|
+
for (var i = 0; i < rows.length; i++) {
|
|
1769
|
+
var row = rows[i];
|
|
1770
|
+
if (!row || row.suppressed === true || row.disposition === 'hidden') continue;
|
|
1771
|
+
if (row.historical === true) done.push(row);
|
|
1772
|
+
else if (row.userState === 'running' || row.userState === 'observation_paused') live.push(row);
|
|
1773
|
+
else if (row.userState === 'attention' || row.userState === 'delivery_failed') {
|
|
1774
|
+
if (row.disposition === 'acknowledged') acknowledged.push(row);
|
|
1775
|
+
else attention.push(row);
|
|
1776
|
+
}
|
|
1777
|
+
else if (row.userState === 'succeeded') done.push(row);
|
|
1778
|
+
}
|
|
1779
|
+
return { live: live, attention: attention, done: done, acknowledged: acknowledged };
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
function CoreRuntimePanel() {
|
|
1783
|
+
var dataPair = React.useState(null);
|
|
1784
|
+
var data = dataPair[0];
|
|
1785
|
+
var setData = dataPair[1];
|
|
1786
|
+
var busyPair = React.useState(false);
|
|
1787
|
+
var busy = busyPair[0];
|
|
1788
|
+
var setBusy = busyPair[1];
|
|
1789
|
+
var errorPair = React.useState('');
|
|
1790
|
+
var error = errorPair[0];
|
|
1791
|
+
var setError = errorPair[1];
|
|
1792
|
+
var openPair = React.useState(false);
|
|
1793
|
+
var open = openPair[0];
|
|
1794
|
+
var setOpen = openPair[1];
|
|
1795
|
+
var selectedPair = React.useState('');
|
|
1796
|
+
var selectedCoreTask = selectedPair[0];
|
|
1797
|
+
var setSelectedCoreTask = selectedPair[1];
|
|
1798
|
+
var copyPair = React.useState('');
|
|
1799
|
+
var copyNote = copyPair[0];
|
|
1800
|
+
var setCopyNote = copyPair[1];
|
|
1801
|
+
function load() {
|
|
1802
|
+
if (busy) return;
|
|
1803
|
+
setBusy(true);
|
|
1804
|
+
setError('');
|
|
1805
|
+
fetch('/iris/api/core/snapshot?limit=12')
|
|
1806
|
+
.then(function (res) {
|
|
1807
|
+
if (res.status === 404) return { available: false, backendReloadRequired: true, tasks: { total: 0, recent: [] }, artifacts: { total: 0, recent: [] } };
|
|
1808
|
+
return res.ok ? res.json() : Promise.reject(new Error('读取失败(HTTP ' + res.status + ')'));
|
|
1809
|
+
}).then(setData)
|
|
1810
|
+
.catch(function (caught) { setError('Core 运行事实读取失败:' + caught.message); })
|
|
1811
|
+
.finally(function () { setBusy(false); });
|
|
1812
|
+
}
|
|
1813
|
+
function copyCoreId(value, label) {
|
|
1814
|
+
setCopyNote('');
|
|
1815
|
+
var clipboard = window.navigator && window.navigator.clipboard;
|
|
1816
|
+
if (!clipboard || typeof clipboard.writeText !== 'function') {
|
|
1817
|
+
setCopyNote('浏览器不允许自动复制;请长按上方 ID 复制。');
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
clipboard.writeText(value)
|
|
1821
|
+
.then(function () { setCopyNote(label + ' 已复制'); })
|
|
1822
|
+
.catch(function () { setCopyNote('复制失败;请长按上方 ID 复制。'); });
|
|
1823
|
+
}
|
|
1824
|
+
React.useEffect(function () {
|
|
1825
|
+
function changed() { load(); }
|
|
1826
|
+
load();
|
|
1827
|
+
window.addEventListener('iris-core-artifacts-changed', changed);
|
|
1828
|
+
window.addEventListener('iris-core-refresh-tick', changed);
|
|
1829
|
+
return function () {
|
|
1830
|
+
window.removeEventListener('iris-core-artifacts-changed', changed);
|
|
1831
|
+
window.removeEventListener('iris-core-refresh-tick', changed);
|
|
1832
|
+
};
|
|
1833
|
+
}, []);
|
|
1834
|
+
var taskData = data && data.tasks || { total: 0, recent: [] };
|
|
1835
|
+
var artifactData = data && data.artifacts || { total: 0, recent: [] };
|
|
1836
|
+
/* 处置偏好投影:诊断层可见 hidden/acknowledged/suppressed,并可恢复显示。 */
|
|
1837
|
+
var dispositionMap = {};
|
|
1838
|
+
((data && data.userTasks) || []).forEach(function (row) { dispositionMap[row.id] = row; });
|
|
1839
|
+
function dispositionLabelOf(row) {
|
|
1840
|
+
if (!row) return '';
|
|
1841
|
+
if (row.suppressed) return '已由新任务成功交付自动静默';
|
|
1842
|
+
if (row.disposition === 'hidden') return '已在本机隐藏(记录保留,可恢复显示)';
|
|
1843
|
+
if (row.disposition === 'acknowledged') return '已设「不再提醒」(本机偏好,记录保留)';
|
|
1844
|
+
return '';
|
|
1845
|
+
}
|
|
1846
|
+
var selectedTask = (taskData.recent || []).find(function (task) { return task.id === selectedCoreTask; });
|
|
1847
|
+
var taskRows = (taskData.recent || []).slice(0, 6).map(function (task) {
|
|
1848
|
+
var state = coreTaskState(task);
|
|
1849
|
+
var disposed = dispositionMap[task.id];
|
|
1850
|
+
var hiddenMark = disposed && disposed.disposition === 'hidden' ? ' · 已隐藏' : '';
|
|
1851
|
+
var selected = selectedCoreTask === task.id;
|
|
1852
|
+
return React.createElement('button', {
|
|
1853
|
+
key: task.id,
|
|
1854
|
+
type: 'button',
|
|
1855
|
+
className: 'iris-core-task-row' + (selected ? ' selected' : ''),
|
|
1856
|
+
'aria-expanded': selected,
|
|
1857
|
+
onClick: function () { setSelectedCoreTask(selected ? '' : task.id); }
|
|
1858
|
+
},
|
|
1859
|
+
React.createElement('span', { className: 'iris-core-state-dot ' + state.tone }),
|
|
1860
|
+
React.createElement('span', { className: 'iris-core-task-main' },
|
|
1861
|
+
React.createElement('strong', {}, coreCapabilityLabel(task.capability) + ' · ' + state.label + hiddenMark),
|
|
1862
|
+
React.createElement('span', { className: 'iris-wb-muted' }, coreModelLabel(task.modelRef) + ' · ' + fmtTime(task.updatedAt))),
|
|
1863
|
+
React.createElement('span', { className: 'iris-core-task-arrow' }, selected ? '▼' : '›'));
|
|
1864
|
+
});
|
|
1865
|
+
var selectedArtifacts = selectedTask ? (selectedTask.artifactIds || []).map(function (id) {
|
|
1866
|
+
return (artifactData.recent || []).find(function (artifact) { return artifact.id === id; }) || { id: id };
|
|
1867
|
+
}) : [];
|
|
1868
|
+
var selectedDisposition = selectedTask ? dispositionMap[selectedTask.id] : null;
|
|
1869
|
+
var dispositionNote = dispositionLabelOf(selectedDisposition);
|
|
1870
|
+
var detail = selectedTask ? React.createElement('div', { className: 'iris-core-detail' },
|
|
1871
|
+
React.createElement('div', { className: 'iris-doctor-top' },
|
|
1872
|
+
React.createElement('strong', { className: 'iris-doctor-score' }, '任务事实'),
|
|
1873
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: function () { copyCoreId(selectedTask.id, 'Task ID'); } }, '复制 Task ID')),
|
|
1874
|
+
React.createElement('div', { className: 'iris-core-id' }, selectedTask.id),
|
|
1875
|
+
React.createElement('div', { className: 'iris-core-detail-grid' },
|
|
1876
|
+
React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '模型'), React.createElement('span', {}, coreModelLabel(selectedTask.modelRef))),
|
|
1877
|
+
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 || '未记录')),
|
|
1878
|
+
React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '取消状态'), React.createElement('span', {}, coreCancelLabel(selectedTask.cancelState))),
|
|
1879
|
+
dispositionNote ? React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '处置状态'), React.createElement('span', {}, dispositionNote)) : null,
|
|
1880
|
+
React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '重试来源'), React.createElement('span', { className: 'iris-core-id' }, selectedTask.retriedFrom || '无')),
|
|
1881
|
+
React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '创建时间'), React.createElement('span', {}, fmtTime(selectedTask.createdAt))),
|
|
1882
|
+
React.createElement('div', { className: 'iris-core-detail-cell' }, React.createElement('span', { className: 'iris-wb-muted' }, '最后更新'), React.createElement('span', {}, fmtTime(selectedTask.updatedAt)))),
|
|
1883
|
+
React.createElement('div', {},
|
|
1884
|
+
React.createElement('div', { className: 'iris-wb-muted', style: { marginBottom: '4px' } }, '尝试记录 · ' + (selectedTask.attempts || []).length),
|
|
1885
|
+
React.createElement('div', { className: 'iris-core-task-list' }, (selectedTask.attempts || []).map(function (attempt) {
|
|
1886
|
+
return React.createElement('div', { key: attempt.id, className: 'iris-core-attempt' },
|
|
1887
|
+
React.createElement('span', {}, '第 ' + attempt.ordinal + ' 次 · ' + coreAttemptState(attempt) + ' · ' + coreModelLabel(attempt.model)),
|
|
1888
|
+
React.createElement('span', { className: 'iris-wb-muted' }, fmtTime(attempt.finishedAt || attempt.startedAt)),
|
|
1889
|
+
attempt.error && attempt.error.safeMessage ? React.createElement('span', { className: 'iris-wb-err' }, attempt.error.safeMessage) : null);
|
|
1890
|
+
}))),
|
|
1891
|
+
React.createElement('div', {},
|
|
1892
|
+
React.createElement('div', { className: 'iris-wb-muted', style: { marginBottom: '4px' } }, '关联作品 · ' + selectedArtifacts.length),
|
|
1893
|
+
selectedArtifacts.length ? React.createElement('div', { className: 'iris-core-task-list' }, selectedArtifacts.map(function (artifact) {
|
|
1894
|
+
var canOpen = selectedTask.capability === 'image' && (!artifact.mediaType || String(artifact.mediaType).indexOf('image/') === 0);
|
|
1895
|
+
return React.createElement('div', { key: artifact.id, className: 'iris-core-artifact' },
|
|
1896
|
+
React.createElement('span', { className: 'iris-core-id', style: { flex: 1 } }, artifact.id),
|
|
1897
|
+
artifact.size ? React.createElement('span', { className: 'iris-wb-muted' }, fmtBytes(artifact.size)) : null,
|
|
1898
|
+
canOpen ? React.createElement('a', { className: 'iris-wb-link', href: '/iris/api/core/artifact/' + encodeURIComponent(artifact.id) + '/media', target: '_blank', rel: 'noreferrer' }, '打开作品') : null,
|
|
1899
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: function () { copyCoreId(artifact.id, 'Artifact ID'); } }, '复制 ID'));
|
|
1900
|
+
})) : React.createElement('div', { className: 'iris-wb-empty' }, '当前任务没有可用作品')),
|
|
1901
|
+
selectedTask.lastError && selectedTask.lastError.safeMessage ? React.createElement('div', { className: 'iris-wb-err' }, selectedTask.lastError.safeMessage) : null,
|
|
1902
|
+
React.createElement(CoreReobserveButton, {
|
|
1903
|
+
row: selectedTask && selectedTask.acceptance === 'accepted' && selectedTask.remoteTaskId
|
|
1904
|
+
&& ['none', 'unknown'].indexOf(selectedTask.outcome) >= 0 && selectedTask.phase !== 'terminal'
|
|
1905
|
+
? { id: selectedTask.id, observable: true } : null
|
|
1906
|
+
}),
|
|
1907
|
+
React.createElement(CoreRedeliverButton, {
|
|
1908
|
+
row: selectedTask && selectedTask.outcome === 'succeeded'
|
|
1909
|
+
&& selectedTask.deliveryState === 'failed' && selectedTask.remoteTaskId
|
|
1910
|
+
? { id: selectedTask.id, redeliverable: true } : null
|
|
1911
|
+
}),
|
|
1912
|
+
React.createElement(CoreCancelButton, {
|
|
1913
|
+
row: selectedTask && selectedTask.acceptance === 'accepted' && selectedTask.remoteTaskId
|
|
1914
|
+
&& ['none', 'unknown'].indexOf(selectedTask.outcome) >= 0 && selectedTask.phase !== 'terminal'
|
|
1915
|
+
&& (!selectedTask.cancelState || selectedTask.cancelState === 'none')
|
|
1916
|
+
? { id: selectedTask.id, cancelable: true } : null
|
|
1917
|
+
}),
|
|
1918
|
+
React.createElement(CoreRetryButton, {
|
|
1919
|
+
row: selectedTask && selectedTask.phase === 'terminal'
|
|
1920
|
+
&& !(selectedTask.outcome === 'succeeded' && selectedTask.deliveryState === 'ready')
|
|
1921
|
+
? { id: selectedTask.id, retryable: true } : null
|
|
1922
|
+
}),
|
|
1923
|
+
React.createElement(CoreDispositionButtons, {
|
|
1924
|
+
row: selectedDisposition
|
|
1925
|
+
? { id: selectedDisposition.id, userState: selectedDisposition.userState,
|
|
1926
|
+
disposition: selectedDisposition.disposition, suppressed: selectedDisposition.suppressed }
|
|
1927
|
+
: null
|
|
1928
|
+
}),
|
|
1929
|
+
React.createElement('div', { className: 'iris-clean-note' }, '事实面板:「重新观察」只查询远端最新状态、「重新取回作品」只重新下载已生成的产物、「取消任务」只有供应商明确确认才记为已取消、「重试为新任务」会创建新任务并可能产生重复费用;这里不会重新提交、删除或修改既有任务。')) : null;
|
|
1930
|
+
var countText = data && data.available
|
|
1931
|
+
? taskData.total + ' 个任务 · ' + artifactData.total + ' 个作品'
|
|
1932
|
+
: (busy ? '正在读取…' : '查看底层任务与作品关系');
|
|
1933
|
+
return React.createElement('div', { className: 'iris-wb-card iris-core-runtime' },
|
|
1934
|
+
React.createElement('div', { className: 'iris-core-runtime-head' },
|
|
1935
|
+
React.createElement('div', { className: 'iris-core-runtime-title' },
|
|
1936
|
+
React.createElement('strong', {}, 'Core 任务事实'),
|
|
1937
|
+
React.createElement('span', { className: 'iris-wb-muted' }, countText)),
|
|
1938
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', 'aria-expanded': open, onClick: function () { setOpen(!open); } }, open ? '收起' : '展开')),
|
|
1939
|
+
open ? React.createElement('div', { className: 'iris-core-runtime-body' },
|
|
1940
|
+
React.createElement('div', { className: 'iris-core-runtime-tools' },
|
|
1941
|
+
React.createElement('span', { className: 'iris-wb-muted', style: { flex: 1 } }, '只读开发诊断 · 点击任务查看尝试与作品'),
|
|
1942
|
+
React.createElement('button', { className: 'iris-pm-btn', type: 'button', disabled: busy, onClick: load }, busy ? '读取中…' : '刷新')),
|
|
1943
|
+
error ? React.createElement('div', { className: 'iris-doctor-row error' }, React.createElement('span', {}, '×'), React.createElement('span', {}, error)) : null,
|
|
1944
|
+
data && !data.available ? React.createElement('div', { className: 'iris-wb-empty' },
|
|
1945
|
+
data.backendReloadRequired ? 'DSH 后端尚未加载 Core 路由;请重启 DSH 后重试。' : '尚无 Core 运行事实;读取不会创建目录。') : null,
|
|
1946
|
+
data && data.available ? (taskRows.length ? React.createElement('div', { className: 'iris-core-task-list' }, taskRows)
|
|
1947
|
+
: React.createElement('div', { className: 'iris-wb-empty' }, '当前没有 Core Task')) : null,
|
|
1948
|
+
detail,
|
|
1949
|
+
copyNote ? React.createElement('div', { className: 'iris-clean-note' }, copyNote) : null) : null);
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1290
1952
|
/* ---- 泡泡常用卡片选择器(设置页:多项勾选 → 出现在悬浮窗「常用」标签) ---- */
|
|
1291
1953
|
function BubbleCardPicker() {
|
|
1292
1954
|
var pair = useBubbleCards();
|
|
@@ -1428,6 +2090,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1428
2090
|
var recent = (state && state.tasks && state.tasks.recent) || [];
|
|
1429
2091
|
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
1430
2092
|
var artifactTotal = (state && state.artifacts && state.artifacts.total) || 0;
|
|
2093
|
+
// 同一任务区合并 legacy 与 Core 只读投影;Core 行仅有的动作是「重新观察」「重新取回作品」与需确认的「取消任务」。
|
|
2094
|
+
var coreUser = useCoreUserTasks();
|
|
2095
|
+
var coreGroups = splitCoreUserRows(coreUser.rows || []);
|
|
1431
2096
|
var selPair = React.useState(null);
|
|
1432
2097
|
var selectedTask = selPair[0];
|
|
1433
2098
|
var setSelectedTask = selPair[1];
|
|
@@ -1451,17 +2116,25 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1451
2116
|
React.createElement(HostDoctorPanel, {}),
|
|
1452
2117
|
React.createElement('div', { className: 'iris-wb-sec' }, '运行中任务'),
|
|
1453
2118
|
React.createElement('div', { className: 'iris-wb-box' },
|
|
1454
|
-
running.length
|
|
2119
|
+
running.length || coreGroups.live.length ? [].concat(
|
|
2120
|
+
running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); }),
|
|
2121
|
+
coreGroups.live.map(coreTaskCard))
|
|
1455
2122
|
: React.createElement('div', { className: 'iris-wb-empty' }, '暂无进行中任务')),
|
|
1456
|
-
attention.length ? React.createElement('div', {},
|
|
2123
|
+
attention.length || coreGroups.attention.length || coreUser.degraded ? React.createElement('div', {},
|
|
1457
2124
|
React.createElement('div', { className: 'iris-wb-sec' }, '需要处理'),
|
|
1458
2125
|
React.createElement('div', { className: 'iris-wb-box' },
|
|
1459
|
-
|
|
2126
|
+
coreUser.degraded ? React.createElement('div', { className: 'iris-clean-note' },
|
|
2127
|
+
'部分 Core 记录损坏或媒体缺失,已跳过 ' + coreUser.droppedTasks + ' 条;其余事实照常显示。') : null,
|
|
2128
|
+
[].concat(
|
|
2129
|
+
attention.map(function (t) { return taskRow(t, false, selectedTask === t.id, onSelect); }),
|
|
2130
|
+
coreGroups.attention.map(coreTaskCard)))) : null,
|
|
1460
2131
|
React.createElement('div', { className: 'iris-wb-sec' }, '作品库'),
|
|
1461
2132
|
React.createElement(ArtifactGallery, { total: artifactTotal }),
|
|
2133
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '高级诊断'),
|
|
2134
|
+
React.createElement(CoreRuntimePanel, {}),
|
|
1462
2135
|
React.createElement('div', { className: 'iris-wb-sec' }, '历史任务'),
|
|
1463
2136
|
React.createElement(CleanupBar, {}),
|
|
1464
|
-
React.createElement(HistoryBrowser, { recent: recent, selected: selectedTask, onSelect: onSelect }),
|
|
2137
|
+
React.createElement(HistoryBrowser, { recent: recent, coreHistory: [].concat(coreGroups.done, coreGroups.acknowledged), selected: selectedTask, onSelect: onSelect }),
|
|
1465
2138
|
React.createElement('div', { className: 'iris-wb-sec' }, '操作'),
|
|
1466
2139
|
React.createElement(ActionGroups, {}));
|
|
1467
2140
|
}
|
|
@@ -1473,8 +2146,23 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1473
2146
|
var recent = (state && state.tasks && state.tasks.recent) || [];
|
|
1474
2147
|
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
1475
2148
|
var recentTotal = (state && state.tasks && state.tasks.recentTotal) || recent.length;
|
|
1476
|
-
var
|
|
1477
|
-
var
|
|
2149
|
+
var legacyWorks = (state && state.artifacts && state.artifacts.recent) || [];
|
|
2150
|
+
var coreWorks = useCoreWorks(6);
|
|
2151
|
+
// 同一任务/作品区合并 Core 只读投影;完成提示会话内只出现一次,刷新后从事实重算。
|
|
2152
|
+
var coreUser = useCoreUserTasks();
|
|
2153
|
+
var coreGroups = splitCoreUserRows(coreUser.rows || []);
|
|
2154
|
+
var arrivedPair = React.useState([]);
|
|
2155
|
+
var coreArrived = arrivedPair[0];
|
|
2156
|
+
var setCoreArrived = arrivedPair[1];
|
|
2157
|
+
var coreRowsKey = (coreUser.rows || []).map(function (row) { return row.id + '@' + row.revision; }).join(',');
|
|
2158
|
+
React.useEffect(function () {
|
|
2159
|
+
var added = noteCoreCompletions(coreUser.rows || []);
|
|
2160
|
+
if (added.length) setCoreArrived(added);
|
|
2161
|
+
}, [coreRowsKey]);
|
|
2162
|
+
var works = coreWorks.concat(legacyWorks).sort(function (a, b) {
|
|
2163
|
+
return String(b.createdAt || '').localeCompare(String(a.createdAt || ''));
|
|
2164
|
+
}).slice(0, 6);
|
|
2165
|
+
var artifactTotal = ((state && state.artifacts && state.artifacts.total) || legacyWorks.length) + coreWorks.length;
|
|
1478
2166
|
var cards = useBubbleCards()[0];
|
|
1479
2167
|
var tabPair = React.useState('tasks');
|
|
1480
2168
|
var tab = tabPair[0];
|
|
@@ -1487,12 +2175,16 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1487
2175
|
if (tab === 'tasks') {
|
|
1488
2176
|
// 快捷面板承载当前任务、需要处理的异常事实与独立作品库中的最近作品。
|
|
1489
2177
|
body = React.createElement('div', { className: 'iris-wb-box' },
|
|
1490
|
-
|
|
2178
|
+
coreArrived.length ? React.createElement('div', { className: 'iris-wb-badge ok', role: 'status' },
|
|
2179
|
+
'新完成:' + coreArrived.map(function (row) { return row.model || row.id; }).join('、')) : null,
|
|
2180
|
+
running.length || coreGroups.live.length ? React.createElement('div', {},
|
|
1491
2181
|
React.createElement('div', { className: 'iris-wb-sec' }, '运行中'),
|
|
1492
|
-
running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); })
|
|
1493
|
-
|
|
2182
|
+
running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); }),
|
|
2183
|
+
coreGroups.live.map(coreTaskMini)) : null,
|
|
2184
|
+
attention.length || coreGroups.attention.length ? React.createElement('div', {},
|
|
1494
2185
|
React.createElement('div', { className: 'iris-wb-sec' }, '需要处理'),
|
|
1495
|
-
attention.slice(0, 4).map(function (t) { return taskRowMini(t, selectedTask === t.id, onSelect); })
|
|
2186
|
+
attention.slice(0, 4).map(function (t) { return taskRowMini(t, selectedTask === t.id, onSelect); }),
|
|
2187
|
+
coreGroups.attention.slice(0, 4).map(coreTaskMini)) : null,
|
|
1496
2188
|
React.createElement('div', { className: 'iris-wb-sec' }, '最近作品'),
|
|
1497
2189
|
works.length ? works.map(artifactRowMini)
|
|
1498
2190
|
: React.createElement('div', { className: 'iris-wb-empty' }, '尚无作品'),
|
|
@@ -1754,6 +2446,29 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1754
2446
|
}));
|
|
1755
2447
|
}
|
|
1756
2448
|
|
|
2449
|
+
/* 曲线鸢尾花:矢量源见 docs/assets/logo/iris-bubble.svg。
|
|
2450
|
+
使用实色与无 ID 的路径,多个座位同时渲染也不会发生 SVG 引用冲突。 */
|
|
2451
|
+
var IRIS_MARK_PARTS = [
|
|
2452
|
+
{ fill: '#7db9a4', d: 'M30.5 39h3v18h-3z' },
|
|
2453
|
+
{ fill: '#7db9a4', d: 'M32 55C24 55 20 50 19 44C26 45 31 48 32 55Z' },
|
|
2454
|
+
{ fill: '#9cd1b7', d: 'M32 54C40 53 44 49 45 43C38 44 33 48 32 54Z' },
|
|
2455
|
+
{ fill: '#b5a0f3', d: 'M29 31C18 30 11 22 15 14C24 13 30 20 29 31Z' },
|
|
2456
|
+
{ fill: '#b5a0f3', d: 'M35 31C46 30 53 22 49 14C40 13 34 20 35 31Z' },
|
|
2457
|
+
{ fill: '#e0d4ff', d: 'M32 7C24 13 23 23 32 34C41 23 40 13 32 7Z' },
|
|
2458
|
+
{ fill: '#9b80df', d: 'M31 31C22 24 11 28 8 36C12 45 24 47 31 34Z' },
|
|
2459
|
+
{ fill: '#9b80df', d: 'M33 31C42 24 53 28 56 36C52 45 40 47 33 34Z' },
|
|
2460
|
+
{ fill: '#c4aff6', d: 'M32 29C24 35 23 44 32 50C41 44 40 35 32 29Z' },
|
|
2461
|
+
{ fill: '#f2ce83', d: 'M32 31C29 32 28 34 29 36L32 40L35 36C36 34 35 32 32 31Z' }
|
|
2462
|
+
];
|
|
2463
|
+
function IRIS_MARK_SVG() {
|
|
2464
|
+
return React.createElement('svg', {
|
|
2465
|
+
width: 26, height: 26, viewBox: '0 0 64 64',
|
|
2466
|
+
role: 'img', 'aria-label': 'Iris', focusable: 'false'
|
|
2467
|
+
}, IRIS_MARK_PARTS.map(function (part, i) {
|
|
2468
|
+
return React.createElement('path', { key: i, d: part.d, fill: part.fill });
|
|
2469
|
+
}));
|
|
2470
|
+
}
|
|
2471
|
+
|
|
1757
2472
|
function FloatingBubble() {
|
|
1758
2473
|
var state = useIrisState(5000);
|
|
1759
2474
|
var providers = (state && state.providers) || [];
|
|
@@ -1887,7 +2602,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1887
2602
|
onPointerCancel: onPointerCancel,
|
|
1888
2603
|
title: 'Iris ' + healthText({ status: overallHealth }) + ':点击打开快捷面板'
|
|
1889
2604
|
},
|
|
1890
|
-
React.createElement('div', { className: 'iris-bubble-btn' },
|
|
2605
|
+
React.createElement('div', { className: 'iris-bubble-btn' }, React.createElement(IRIS_MARK_SVG)),
|
|
1891
2606
|
badge,
|
|
1892
2607
|
panel);
|
|
1893
2608
|
}
|