@mokuyoaxis/dsh-iris 0.1.3 → 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/.dsh/skills/iris-compose-media/SKILL.md +23 -49
- package/.dsh/skills/iris-compose-media/references/task-v2-recovery.md +30 -0
- package/.dsh/skills/iris-compose-media/references/workflows.md +38 -0
- package/.dsh/skills/iris-verify-ui/SKILL.md +3 -25
- package/.dsh/skills/iris-verify-ui/agents/openai.yaml +4 -0
- package/.dsh/skills/iris-verify-ui/references/verdict-and-degradation.md +31 -0
- package/CHANGELOG.md +77 -2
- package/README.en.md +98 -19
- package/README.md +98 -19
- package/bin/dsh-iris.js +392 -13
- package/docs/AGENT_QUICKSTART.md +35 -0
- package/docs/ARCHITECTURE.md +82 -52
- package/docs/ARTIFACT_LIBRARY.md +24 -0
- 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 +105 -0
- package/docs/HOST_DOCTOR.md +49 -0
- package/docs/MODEL_PORT_CONTRACT.md +300 -0
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +138 -0
- package/docs/PROVIDER_HEALTH.md +39 -0
- package/docs/PROVIDER_RUNTIME_HARNESS.md +33 -0
- package/docs/PROVIDER_SUBMISSION_CONTRACT.md +2 -2
- package/docs/ROADMAP.md +42 -76
- package/docs/SECURITY.md +10 -5
- package/docs/TASK_SEMANTICS.md +3 -2
- package/docs/assets/diagrams/iris-architecture.png +0 -0
- package/docs/assets/diagrams/iris-architecture.svg +5 -5
- 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.1.4.md +59 -0
- package/docs/releases/0.2.0.md +48 -0
- package/lib/actions.js +673 -525
- package/lib/adapters.js +58 -21
- package/lib/api.js +182 -16
- package/lib/artifacts.js +239 -0
- package/lib/bundled-skills.js +5 -5
- package/lib/client.js +1116 -86
- package/lib/command-service.js +420 -0
- package/lib/config.js +245 -49
- 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 +260 -30
- package/lib/dsh-core-adapter.js +599 -0
- package/lib/dsh-host-adapter.js +240 -0
- package/lib/generation-input.js +95 -0
- package/lib/guard.js +3 -3
- package/lib/host-contract.js +131 -0
- package/lib/host-runtime.js +156 -0
- package/lib/index.js +255 -166
- package/lib/locate.js +1 -1
- package/lib/media.js +16 -6
- package/lib/model-invoker.js +218 -0
- package/lib/model-port-contract.js +333 -0
- package/lib/models.js +97 -11
- package/lib/prompt-optimizer.js +10 -20
- package/lib/provider-adapter.js +397 -0
- package/lib/provider-adapters.js +342 -0
- package/lib/provider-catalog.js +171 -0
- package/lib/provider-contract.js +72 -3
- package/lib/provider-health.js +59 -0
- package/lib/provider-protocol.js +51 -2
- package/lib/provider-task-runner.js +328 -0
- package/lib/render.js +43 -12
- package/lib/tasks.js +45 -9
- package/lib/vision.js +51 -56
- package/package.json +20 -14
- package/user_guide.md +67 -12
- package/docs/assets/diagrams/iris-architecture.drawio +0 -1
- package/docs/assets/diagrams/iris-task-lifecycle.drawio +0 -1
- package/docs/assets/diagrams/iris-workflow-compose.drawio +0 -1
- package/docs/releases/0.1.1.md +0 -51
- package/docs/releases/0.1.2.md +0 -26
- package/docs/releases/0.1.3.md +0 -30
package/lib/client.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* DSH web client bundle 标准形态(与 dsh-at-file / dsh-better-sidebar 同构):
|
|
5
5
|
* window.__ModuleLoader__.load({ id, factory }) → CJS 工厂,require 解析宿主依赖,
|
|
6
6
|
* `inject` 声明本 bundle 需要的 ctx 服务 key(这里是 slots),
|
|
7
|
-
* `apply(ctx)`
|
|
7
|
+
* `apply(ctx)` 通过本地 clientSlots 适配端口注册座位。
|
|
8
8
|
*
|
|
9
9
|
* 三个零替换风险的座位:
|
|
10
10
|
* - settings.section(id: 'iris-workbench'):Iris 工作台整页 ——
|
|
@@ -14,13 +14,49 @@
|
|
|
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.2.0';
|
|
25
|
+
var HOST_CLIENT_PROTOCOL_VERSION = 0;
|
|
26
|
+
var reportedSeats = [];
|
|
27
|
+
|
|
28
|
+
function reportClientSeat(seat) {
|
|
29
|
+
if (reportedSeats.indexOf(seat) < 0) reportedSeats.push(seat);
|
|
30
|
+
if (!window || typeof window.fetch !== 'function') return;
|
|
31
|
+
try {
|
|
32
|
+
Promise.resolve(window.fetch('/iris/api/host-client', {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: { 'content-type': 'application/json' },
|
|
35
|
+
credentials: 'same-origin',
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
pluginId: '@mokuyoaxis/dsh-iris',
|
|
38
|
+
version: IRIS_CLIENT_VERSION,
|
|
39
|
+
protocolVersion: HOST_CLIENT_PROTOCOL_VERSION,
|
|
40
|
+
seats: reportedSeats.slice()
|
|
41
|
+
})
|
|
42
|
+
})).catch(function () {});
|
|
43
|
+
} catch (_) { /* Doctor handshake 不影响 UI */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Client bundle 不能导入 server-side Host contract;只在此边界读取 DSH slots 服务。
|
|
47
|
+
function clientSlotsPort(ctx) {
|
|
48
|
+
var slots;
|
|
49
|
+
try { slots = ctx && ctx.slots; } catch (_) { slots = null; }
|
|
50
|
+
if (!slots || typeof slots.inject !== 'function' || typeof slots.register !== 'function') {
|
|
51
|
+
var error = new Error('Iris clientSlots 不可用:DSH slots 需要 inject/register');
|
|
52
|
+
error.code = slots ? 'IRIS_HOST_CAPABILITY_INCOMPATIBLE' : 'IRIS_HOST_CAPABILITY_UNAVAILABLE';
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
return Object.freeze({
|
|
56
|
+
inject: function (seat, callback) { return slots.inject(seat, callback); },
|
|
57
|
+
register: function (definition, component) { return slots.register(definition, component); }
|
|
58
|
+
});
|
|
59
|
+
}
|
|
24
60
|
|
|
25
61
|
var STYLE_ID = 'iris-wb-css';
|
|
26
62
|
var cssText = [
|
|
@@ -57,6 +93,44 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
57
93
|
'.iris-wb-drawer .iris-wb-prompt { white-space: pre-wrap; word-break: break-word; font-size: 12px; }',
|
|
58
94
|
'.iris-wb-drawer .iris-wb-err { color: var(--dsw-alias-state-error-primary, #e06c6c); white-space: pre-wrap; word-break: break-word; font-size: 12px; }',
|
|
59
95
|
'.iris-wb-loading { color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 12px; }',
|
|
96
|
+
'.iris-doctor-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }',
|
|
97
|
+
'.iris-doctor-score { font-weight: 600; flex: 1; }',
|
|
98
|
+
'.iris-doctor-list { display: grid; gap: 4px; margin-top: 7px; }',
|
|
99
|
+
'.iris-doctor-row { display: grid; grid-template-columns: 14px 1fr; gap: 6px; font-size: 11px; }',
|
|
100
|
+
'.iris-doctor-row.ok { color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
101
|
+
'.iris-doctor-row.warn { color: var(--dsw-alias-state-warn-primary, #d9a941); }',
|
|
102
|
+
'.iris-doctor-row.error { color: var(--dsw-alias-state-error-primary, #e06c6c); }',
|
|
103
|
+
'.iris-doctor-details { margin-top: 7px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
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; } }',
|
|
60
134
|
/* ---- 操作卡片组(阶段 5 GUI 直连) ---- */
|
|
61
135
|
'.iris-act-group { display: grid; gap: 8px; }',
|
|
62
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; }',
|
|
@@ -96,8 +170,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
96
170
|
'.iris-pm-field { display: grid; gap: 3px; }',
|
|
97
171
|
'.iris-pm-field input, .iris-pm-field textarea { 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: 4px 6px; font-size: 12px; width: 100%; box-sizing: border-box; }',
|
|
98
172
|
'.iris-pm-act { font-size: 11px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
99
|
-
'.iris-pm-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-label-
|
|
100
|
-
'.iris-pm-dot.
|
|
173
|
+
'.iris-pm-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #73737d); margin-right: 4px; }',
|
|
174
|
+
'.iris-pm-dot.configured { background: #6f9df6; box-shadow: 0 0 7px rgba(111,157,246,.55); }',
|
|
175
|
+
'.iris-pm-dot.verified { background: var(--dsw-alias-state-success-primary, #6fcf6f); box-shadow: 0 0 7px var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
176
|
+
'.iris-pm-dot.failed { background: #b9656d; box-shadow: 0 0 6px rgba(185,101,109,.38); opacity: .82; }',
|
|
177
|
+
'.iris-health-grid { display: grid; gap: 5px; }',
|
|
178
|
+
'.iris-health-row { display: grid; grid-template-columns: minmax(72px, auto) 1fr auto; align-items: center; gap: 7px; padding: 6px 9px; border-radius: 9px; background: var(--dsw-alias-bg-layer-1, #26262e); font-size: 11px; }',
|
|
179
|
+
'.iris-health-name { font-weight: 600; }',
|
|
180
|
+
'.iris-health-state { color: var(--dsw-alias-label-secondary, #9a9a9a); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
|
|
181
|
+
'.iris-health-row.failed .iris-health-state { color: #c87a81; }',
|
|
182
|
+
'.iris-health-row.configured .iris-health-state { color: #7da8fa; }',
|
|
183
|
+
'.iris-health-row.verified .iris-health-state { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
101
184
|
'.iris-cap-grid { display: grid; gap: 6px; }',
|
|
102
185
|
'.iris-cap-row { display: flex; align-items: center; flex-wrap: wrap; gap: 5px; background: var(--dsw-alias-bg-layer-1, #26262e); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 8px; padding: 6px 9px; font-size: 12px; }',
|
|
103
186
|
'.iris-cap-name { font-weight: 600; min-width: 64px; }',
|
|
@@ -109,10 +192,12 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
109
192
|
'.iris-bubble { position: fixed; z-index: 9999; cursor: grab; user-select: none; touch-action: none; }',
|
|
110
193
|
'.iris-bubble.dragging { cursor: grabbing; }',
|
|
111
194
|
'.iris-bubble-btn { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 20px; background: var(--dsw-alias-bg-layer-2, #32323c); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35); transition: box-shadow 0.2s, border-color 0.2s, opacity 0.2s; }',
|
|
112
|
-
'.iris-bubble.
|
|
113
|
-
'.iris-bubble.
|
|
195
|
+
'.iris-bubble.health-configured .iris-bubble-btn { border-color: #6f9df6; box-shadow: 0 0 14px rgba(111,157,246,.55); }',
|
|
196
|
+
'.iris-bubble.health-verified .iris-bubble-btn { border-color: var(--dsw-alias-state-success-primary, #6fcf6f); box-shadow: 0 0 14px var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
197
|
+
'.iris-bubble.health-failed .iris-bubble-btn { opacity: .72; border-color: #b9656d; box-shadow: 0 0 11px rgba(185,101,109,.34); }',
|
|
198
|
+
'.iris-bubble.health-unconfigured .iris-bubble-btn { opacity: 0.55; border-color: var(--dsw-alias-border-l1, #3a3a44); }',
|
|
114
199
|
'.iris-bubble-badge { position: absolute; top: -2px; right: -2px; min-width: 16px; height: 16px; border-radius: 8px; background: var(--dsw-alias-state-warn-primary, #d9a941); color: #1a1a1e; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; padding: 0 3px; }',
|
|
115
|
-
'.iris-bubble-panel { position: fixed; z-index: 9998; width: 360px; max-height: min(520px,
|
|
200
|
+
'.iris-bubble-panel { position: fixed; z-index: 9998; box-sizing: border-box; width: min(360px, calc(100vw - 16px)); max-height: min(520px, calc(100dvh - 16px)); overflow: auto; overscroll-behavior: contain; background: var(--dsw-alias-bg-layer-1, #26262e); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 12px; padding: 12px 14px; font-size: 12px; line-height: 1.45; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.45); }',
|
|
116
201
|
'.iris-bubble-panel-head { position: absolute; top: 6px; right: 6px; z-index: 2; }',
|
|
117
202
|
'.iris-bubble-close { border: none; background: transparent; color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 14px; cursor: pointer; padding: 4px 6px; }',
|
|
118
203
|
'.iris-bubble-close:hover { color: var(--dsw-alias-label-primary, #e6e6e6); }',
|
|
@@ -136,6 +221,22 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
136
221
|
'.iris-clean-btn:hover { border-color: var(--dsw-alias-brand-primary, #7aa2f7); }',
|
|
137
222
|
'.iris-clean-btn.danger:hover { color: var(--dsw-alias-state-error-primary, #e06c6c); border-color: var(--dsw-alias-state-error-primary, #e06c6c); }',
|
|
138
223
|
'.iris-clean-note { font-size: 11px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
224
|
+
/* 独立作品库 v0:任务清理后仍可浏览,窄屏自动收缩。 */
|
|
225
|
+
'.iris-gallery-tools { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }',
|
|
226
|
+
'.iris-gallery-tools .iris-clean-note { flex: 1; min-width: 100px; }',
|
|
227
|
+
'.iris-gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 10px; }',
|
|
228
|
+
'.iris-gallery-card { min-width: 0; overflow: hidden; border: 1px solid rgba(150,164,215,.16); border-radius: 13px; background: color-mix(in srgb, var(--dsw-alias-bg-layer-1, #26262e) 92%, transparent); box-shadow: 0 7px 22px rgba(0,0,0,.12); }',
|
|
229
|
+
'.iris-gallery-preview { display: flex; align-items: center; justify-content: center; width: 100%; aspect-ratio: 1 / 1; overflow: hidden; background: rgba(8,10,16,.28); }',
|
|
230
|
+
'.iris-gallery-preview img, .iris-gallery-preview video { width: 100%; height: 100%; object-fit: contain; }',
|
|
231
|
+
'.iris-gallery-preview audio { width: calc(100% - 16px); }',
|
|
232
|
+
'.iris-gallery-file { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; font-weight: 600; }',
|
|
233
|
+
'.iris-gallery-meta { color: var(--dsw-alias-label-tertiary, #7a7a85); font-size: 10px; }',
|
|
234
|
+
'.iris-gallery-info { display: grid; gap: 4px; padding: 8px 9px 9px; }',
|
|
235
|
+
'.iris-gallery-actions { display: flex; align-items: center; gap: 8px; }',
|
|
236
|
+
'.iris-artifact-mini { display: flex; align-items: center; gap: 7px; min-width: 0; padding: 4px 6px; border-radius: 7px; color: var(--dsw-alias-label-secondary, #9a9a9a); text-decoration: none; }',
|
|
237
|
+
'.iris-artifact-mini:hover { background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-primary, #e6e6e6); }',
|
|
238
|
+
'.iris-artifact-mini span:nth-child(2) { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
|
|
239
|
+
'@media (max-width: 360px) { .iris-gallery-grid { grid-template-columns: 1fr; } }',
|
|
139
240
|
/* 模型池(阶段 9 P4) */
|
|
140
241
|
'.iris-pm-pool { display: grid; gap: 4px; }',
|
|
141
242
|
'.iris-pm-mrows { display: grid; gap: 3px; max-height: 220px; overflow: auto; }',
|
|
@@ -144,8 +245,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
144
245
|
'.iris-pm-mtag { font-size: 9px; color: var(--dsw-alias-brand-primary, #7aa2f7); border: 1px solid var(--dsw-alias-brand-primary, #7aa2f7); border-radius: 3px; padding: 0 3px; margin-left: 4px; }',
|
|
145
246
|
'.iris-pm-mcaps { display: flex; gap: 3px; }',
|
|
146
247
|
'.iris-pm-ver { font-size: 10px; padding: 0 4px; border-radius: 3px; background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
147
|
-
'.iris-pm-ver.
|
|
148
|
-
'.iris-pm-ver.
|
|
248
|
+
'.iris-pm-ver.configured { color: #7da8fa; }',
|
|
249
|
+
'.iris-pm-ver.verified { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
250
|
+
'.iris-pm-ver.failed { color: #c87a81; border-color: rgba(185,101,109,.5); }',
|
|
251
|
+
'.iris-pm-ver.unconfigured { opacity: .55; }',
|
|
149
252
|
'.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; }',
|
|
150
253
|
/* 文件选择器(阶段 10) */
|
|
151
254
|
'.iris-ff { display: grid; gap: 4px; }',
|
|
@@ -222,15 +325,30 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
222
325
|
console.log('[iris] client apply start');
|
|
223
326
|
adoptStyles();
|
|
224
327
|
|
|
225
|
-
/* ----
|
|
328
|
+
/* ---- 当前主视图会话(旧 current / 0.2 mainView 持有者;不猜测列表首项) ---- */
|
|
226
329
|
var currentSessionId = null;
|
|
227
330
|
try {
|
|
228
|
-
var sessions = ctx && ctx.sessions;
|
|
331
|
+
var sessions = ctx && (typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions);
|
|
229
332
|
var listObs = sessions && sessions.list;
|
|
230
333
|
if (listObs && typeof listObs.getSnapshot === 'function') {
|
|
231
|
-
|
|
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();
|
|
232
346
|
if (typeof listObs.subscribe === 'function') {
|
|
233
|
-
|
|
347
|
+
// ObservableSnapshot 的变更通知不承诺携带快照,始终回读权威值。
|
|
348
|
+
var unsubscribeSession = listObs.subscribe(readCurrentSession);
|
|
349
|
+
if (typeof unsubscribeSession === 'function' && typeof ctx.effect === 'function') {
|
|
350
|
+
ctx.effect(function () { return unsubscribeSession; });
|
|
351
|
+
}
|
|
234
352
|
}
|
|
235
353
|
}
|
|
236
354
|
} catch (_) { /* 拿不到会话 → L1 退化为只列 iris 产物 */ }
|
|
@@ -264,7 +382,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
264
382
|
fetch('/iris/api/state')
|
|
265
383
|
.then(function (res) { return res.ok ? res.json() : null; })
|
|
266
384
|
.then(function (data) {
|
|
267
|
-
if (data)
|
|
385
|
+
if (data) {
|
|
386
|
+
apply(data);
|
|
387
|
+
window.dispatchEvent(new CustomEvent('iris-core-refresh-tick'));
|
|
388
|
+
}
|
|
268
389
|
})
|
|
269
390
|
.catch(function () { /* 网络抖动静默 */ });
|
|
270
391
|
}
|
|
@@ -294,17 +415,64 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
294
415
|
return sharedState;
|
|
295
416
|
}
|
|
296
417
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
418
|
+
var HEALTH_META = {
|
|
419
|
+
unconfigured: { label: '未配置', symbol: '○' },
|
|
420
|
+
configured: { label: '已配置,待验证', symbol: '◌' },
|
|
421
|
+
verified: { label: '近期验证成功', symbol: '●' },
|
|
422
|
+
failed: { label: '认证失败', symbol: '×' }
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
function capabilityHealth(state, capability) {
|
|
426
|
+
var health = state && state.health && state.health.capabilities
|
|
427
|
+
&& state.health.capabilities[capability];
|
|
428
|
+
if (health && HEALTH_META[health.status]) return health;
|
|
300
429
|
var providers = (state && state.providers) || [];
|
|
301
|
-
|
|
302
|
-
return p.enabled && p.apiKeyHint && Array.isArray(p.capabilities)
|
|
430
|
+
var configured = providers.some(function (p) {
|
|
431
|
+
return p.enabled && p.apiKeyHint && Array.isArray(p.capabilities)
|
|
432
|
+
&& p.capabilities.indexOf(capability) >= 0;
|
|
303
433
|
});
|
|
434
|
+
return { status: configured ? 'configured' : 'unconfigured', candidateCount: configured ? 1 : 0 };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function relativeHealthTime(value) {
|
|
438
|
+
var ms = Date.now() - Date.parse(String(value || ''));
|
|
439
|
+
if (!Number.isFinite(ms) || ms < 0) return '';
|
|
440
|
+
if (ms < 60000) return '刚刚';
|
|
441
|
+
if (ms < 3600000) return Math.floor(ms / 60000) + ' 分钟前';
|
|
442
|
+
if (ms < 86400000) return Math.floor(ms / 3600000) + ' 小时前';
|
|
443
|
+
return Math.floor(ms / 86400000) + ' 天前';
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function healthText(health) {
|
|
447
|
+
var status = health && HEALTH_META[health.status] ? health.status : 'unconfigured';
|
|
448
|
+
var text = HEALTH_META[status].label;
|
|
449
|
+
var age = relativeHealthTime(health && health.observedAt);
|
|
450
|
+
return text + (age ? ' · ' + age : '');
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function CapabilityHealthOverview() {
|
|
454
|
+
var state = useIrisState(5000);
|
|
455
|
+
var rows = [
|
|
456
|
+
['image-gen', '图片'],
|
|
457
|
+
['video-gen', '视频'],
|
|
458
|
+
['tts', '语音'],
|
|
459
|
+
['transcribe', '转写'],
|
|
460
|
+
['vision', '视觉']
|
|
461
|
+
];
|
|
462
|
+
return React.createElement('div', { className: 'iris-health-grid' }, rows.map(function (row) {
|
|
463
|
+
var health = capabilityHealth(state, row[0]);
|
|
464
|
+
var meta = HEALTH_META[health.status] || HEALTH_META.unconfigured;
|
|
465
|
+
return React.createElement('div', { key: row[0], className: 'iris-health-row ' + health.status },
|
|
466
|
+
React.createElement('span', { className: 'iris-health-name' },
|
|
467
|
+
React.createElement('span', { className: 'iris-pm-dot ' + health.status }, meta.symbol),
|
|
468
|
+
row[1]),
|
|
469
|
+
React.createElement('span', { className: 'iris-health-state', title: healthText(health) }, healthText(health)),
|
|
470
|
+
React.createElement('span', { className: 'iris-wb-muted' }, String(health.candidateCount || 0) + ' 路'));
|
|
471
|
+
}));
|
|
304
472
|
}
|
|
305
473
|
|
|
306
474
|
/* ---- 任务详情抽屉:按需拉取 /iris/api/task/:id ---- */
|
|
307
|
-
function useTaskDetail(taskId) {
|
|
475
|
+
function useTaskDetail(taskId, revision) {
|
|
308
476
|
var pair = React.useState(null);
|
|
309
477
|
var setDetail = pair[1];
|
|
310
478
|
React.useEffect(function () {
|
|
@@ -316,7 +484,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
316
484
|
.then(function (data) { if (alive && data) setDetail(data); })
|
|
317
485
|
.catch(function () { /* 静默 */ });
|
|
318
486
|
return function () { alive = false; };
|
|
319
|
-
}, [taskId]);
|
|
487
|
+
}, [taskId, revision]);
|
|
320
488
|
return pair[0];
|
|
321
489
|
}
|
|
322
490
|
|
|
@@ -392,7 +560,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
392
560
|
React.createElement('span', { className: 'iris-wb-muted' }, task.createdAt ? new Date(task.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : ''));
|
|
393
561
|
}
|
|
394
562
|
|
|
395
|
-
function TaskRecoveryActions({ detail }) {
|
|
563
|
+
function TaskRecoveryActions({ detail, onDeleted }) {
|
|
396
564
|
var pair = React.useState('');
|
|
397
565
|
var note = pair[0];
|
|
398
566
|
var setNote = pair[1];
|
|
@@ -409,13 +577,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
409
577
|
var canRedeliver = detail.schemaVersion === 2 && !acknowledged && state === 'artifact_unavailable' && detail.remoteTaskId;
|
|
410
578
|
var canRetry = detail.schemaVersion === 2 && !acknowledged && ['needs_attention', 'failed', 'canceled'].indexOf(state) >= 0
|
|
411
579
|
&& (detail.cap === 'image' || (detail.cap === 'video' && detail.mode === 't2v'));
|
|
412
|
-
|
|
580
|
+
var canDelete = detail.status !== 'running' || (detail.schemaVersion === 2 && attentionState && acknowledged);
|
|
581
|
+
if (!canAcknowledge && !canRestore && !canReobserve && !canRedeliver && !canRetry && !canDelete) return null;
|
|
413
582
|
function run(action, body, confirmMessage) {
|
|
414
583
|
if (confirmMessage && !window.confirm(confirmMessage)) return;
|
|
415
584
|
setBusy(true);
|
|
416
585
|
setNote('处理中…');
|
|
417
586
|
postAction(action, body).then(function (r) {
|
|
418
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();
|
|
419
589
|
}).catch(function (error) { setNote('失败:' + String((error && error.message) || error)); })
|
|
420
590
|
.finally(function () { setBusy(false); });
|
|
421
591
|
}
|
|
@@ -430,12 +600,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
430
600
|
onClick: function () { run('task_ack_attention', { task_id: detail.id }); } }, '标为已读') : null,
|
|
431
601
|
canRestore ? React.createElement('button', { className: 'iris-pm-btn', disabled: busy,
|
|
432
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,
|
|
433
606
|
note ? React.createElement('span', { className: 'iris-clean-note' }, note) : null);
|
|
434
607
|
}
|
|
435
608
|
|
|
436
609
|
/* ---- 任务详情抽屉:点击任务卡片行展开 ---- */
|
|
437
|
-
function TaskDetailDrawer({ taskId }) {
|
|
438
|
-
var detail = useTaskDetail(taskId);
|
|
610
|
+
function TaskDetailDrawer({ taskId, revision, onDeleted }) {
|
|
611
|
+
var detail = useTaskDetail(taskId, revision);
|
|
439
612
|
if (!detail) {
|
|
440
613
|
return React.createElement('div', { className: 'iris-wb-drawer iris-wb-loading' }, '加载任务详情…');
|
|
441
614
|
}
|
|
@@ -444,7 +617,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
444
617
|
el.push(React.createElement('div', { key: 'p', className: 'iris-wb-prompt' }, detail.prompt || '(空)'));
|
|
445
618
|
if (Number(detail.schemaVersion) === 1) {
|
|
446
619
|
el.push(React.createElement('div', { key: 'legacy', className: 'iris-clean-note' },
|
|
447
|
-
'旧版任务记录 ·
|
|
620
|
+
'旧版任务记录 · 仅支持查看恢复事实。由于缺少可靠的远端受理与交付事实,不提供重新观察、重新交付或知情重试;已终止记录仍可删除。'));
|
|
448
621
|
}
|
|
449
622
|
var dispositionLabel = attentionDispositionLabel(detail);
|
|
450
623
|
if (dispositionLabel) {
|
|
@@ -501,7 +674,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
501
674
|
return React.createElement('span', { key: a.attachmentId, className: 'iris-wb-kv' }, a.attachmentId + (a.file ? ' (' + a.file + ')' : ''));
|
|
502
675
|
})));
|
|
503
676
|
}
|
|
504
|
-
el.push(React.createElement(TaskRecoveryActions, { key: 'recovery', detail: detail }));
|
|
677
|
+
el.push(React.createElement(TaskRecoveryActions, { key: 'recovery', detail: detail, onDeleted: onDeleted }));
|
|
505
678
|
return React.createElement('div', { className: 'iris-wb-drawer' }, ...el);
|
|
506
679
|
}
|
|
507
680
|
|
|
@@ -547,7 +720,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
547
720
|
task.createdAt ? '发起 ' + fmtTime(task.createdAt) : '',
|
|
548
721
|
task.finishedAt ? ' · 完成 ' + fmtTime(task.finishedAt) : ''));
|
|
549
722
|
}
|
|
550
|
-
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); } }));
|
|
551
724
|
return React.createElement('div', {
|
|
552
725
|
key: task.id,
|
|
553
726
|
className: 'iris-wb-card' + (isRunning ? ' iris-running' : '') + (selected ? ' selected' : ''),
|
|
@@ -571,10 +744,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
571
744
|
var setNewId = addPair[1];
|
|
572
745
|
var models = p.models || [];
|
|
573
746
|
function verBadge(m, c) {
|
|
574
|
-
var
|
|
575
|
-
var
|
|
576
|
-
var
|
|
577
|
-
|
|
747
|
+
var health = m.health && m.health[c];
|
|
748
|
+
var status = health && HEALTH_META[health.status] ? health.status : 'configured';
|
|
749
|
+
var cls = 'iris-pm-ver ' + status;
|
|
750
|
+
var title = healthText(health || { status: status });
|
|
751
|
+
var symbol = status === 'verified' ? '✓' : (status === 'failed' ? '×' : (status === 'unconfigured' ? '○' : '?'));
|
|
752
|
+
return React.createElement('button', {
|
|
753
|
+
key: c,
|
|
754
|
+
className: cls,
|
|
755
|
+
title: title + ';点击单独验证此能力',
|
|
756
|
+
onClick: function () { props.testModel(p, m, c); }
|
|
757
|
+
}, c + symbol);
|
|
578
758
|
}
|
|
579
759
|
var rows = models.map(function (m) {
|
|
580
760
|
var caps = m.capabilities || [];
|
|
@@ -591,6 +771,26 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
591
771
|
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { props.addModel(p, newId.trim()); setNewId(''); } }, '+ 添加')));
|
|
592
772
|
}
|
|
593
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
|
+
|
|
594
794
|
function ProviderManager() {
|
|
595
795
|
var listPair = React.useState(null);
|
|
596
796
|
var list = listPair[0];
|
|
@@ -600,7 +800,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
600
800
|
var addingPair = React.useState(false);
|
|
601
801
|
var adding = addingPair[0];
|
|
602
802
|
var setAdding = addingPair[1];
|
|
603
|
-
var newForm = React.useState({ name: '', baseUrl: '', apiKey: '', type: 'openai', mediaProtocol: 'auto' });
|
|
803
|
+
var newForm = React.useState({ name: '', baseUrl: '', mediaBaseUrl: '', apiKey: '', type: 'openai', mediaProtocol: 'auto' });
|
|
604
804
|
var notePair = React.useState('');
|
|
605
805
|
var note = notePair[0];
|
|
606
806
|
var setNote = notePair[1];
|
|
@@ -661,7 +861,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
661
861
|
if (open) {
|
|
662
862
|
body = React.createElement('div', { className: 'iris-pm-body' },
|
|
663
863
|
React.createElement('div', { className: 'iris-pm-note' }, 'Key ' + (p.apiKeyHint || '未配置') + ' · ' + (p.type || '') + ' · ' + (p.mediaProtocol || '')),
|
|
664
|
-
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,
|
|
665
869
|
React.createElement('option', { value: 'dashscope' }, 'DashScope(仅阿里云官方 HTTPS)'),
|
|
666
870
|
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'))),
|
|
667
871
|
React.createElement('div', { className: 'iris-pm-note' }, '能力: ' + ((p.capabilities || []).join(' / ') || '无')),
|
|
@@ -682,9 +886,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
682
886
|
addBlock = React.createElement('div', { className: 'iris-pm-card' },
|
|
683
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 }); } })),
|
|
684
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 }); } })),
|
|
685
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 }); } })),
|
|
686
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 }); } },
|
|
687
|
-
React.createElement('option', { value: 'auto' }, '
|
|
892
|
+
React.createElement('option', { value: 'auto' }, '自动(按媒体 Base URL 安全判断)'),
|
|
688
893
|
React.createElement('option', { value: 'dashscope' }, 'DashScope(阿里云官方)'),
|
|
689
894
|
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'))),
|
|
690
895
|
React.createElement('div', { className: 'iris-wb-row' },
|
|
@@ -808,9 +1013,13 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
808
1013
|
body: JSON.stringify(vals)
|
|
809
1014
|
})
|
|
810
1015
|
.then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
|
|
811
|
-
.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
|
+
})
|
|
812
1020
|
.catch(function () { setRun({ ok: false, text: '网络错误' }); });
|
|
813
1021
|
}
|
|
1022
|
+
var cardHealth = capability ? capabilityHealth(irisState, capability) : { status: 'verified' };
|
|
814
1023
|
var body = null;
|
|
815
1024
|
if (open) {
|
|
816
1025
|
var inputs = fields.map(function (f) {
|
|
@@ -846,16 +1055,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
846
1055
|
result = React.createElement('div', { className: 'iris-act-result' + (run.ok ? '' : ' err') }, run.text);
|
|
847
1056
|
}
|
|
848
1057
|
}
|
|
849
|
-
//
|
|
1058
|
+
// 只读模型提示:当前能力解析到的模型与持久健康事实。
|
|
850
1059
|
var modelBar = null;
|
|
851
1060
|
if (capability) {
|
|
852
|
-
var
|
|
1061
|
+
var available = modelInfo && modelInfo.loaded && modelInfo.options.length > 0;
|
|
853
1062
|
var shown = (vals.model && vals.model.trim()) || (modelInfo && modelInfo.assigned) || '';
|
|
854
1063
|
modelBar = React.createElement('div', { className: 'iris-act-field' },
|
|
855
1064
|
React.createElement('label', {},
|
|
856
|
-
React.createElement('span', { className:
|
|
857
|
-
' 模型:' + (shown || '自动') + (
|
|
858
|
-
React.createElement('div', { className: 'iris-wb-muted' },
|
|
1065
|
+
React.createElement('span', { className: 'iris-pm-dot ' + cardHealth.status }, HEALTH_META[cardHealth.status].symbol),
|
|
1066
|
+
' 模型:' + (shown || '自动') + (available ? '' : '(无可用模型)')),
|
|
1067
|
+
React.createElement('div', { className: 'iris-wb-muted' },
|
|
1068
|
+
healthText(cardHealth) + ' · 改分配 → 上方「能力分配(failover 顺序)」'));
|
|
859
1069
|
}
|
|
860
1070
|
body = React.createElement('div', { className: 'iris-act-body' },
|
|
861
1071
|
modelBar,
|
|
@@ -864,13 +1074,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
864
1074
|
run === 'running' ? '执行中…' : '执行'),
|
|
865
1075
|
result);
|
|
866
1076
|
}
|
|
867
|
-
var
|
|
868
|
-
var
|
|
869
|
-
|
|
870
|
-
|
|
1077
|
+
var headStatus = capability ? cardHealth.status : 'verified';
|
|
1078
|
+
var headTitle = capability ? healthText(cardHealth) : '本地功能';
|
|
1079
|
+
return React.createElement('div', {
|
|
1080
|
+
className: 'iris-act-card' + (open ? ' open' : '') + (capability ? ' health-' + headStatus : '')
|
|
1081
|
+
},
|
|
871
1082
|
React.createElement('div', { className: 'iris-act-head', onClick: function () { setOpen(!open); } },
|
|
872
1083
|
React.createElement('span', { className: 'iris-act-title' }, title),
|
|
873
|
-
capability ? React.createElement('span', {
|
|
1084
|
+
capability ? React.createElement('span', {
|
|
1085
|
+
className: 'iris-pm-dot ' + headStatus,
|
|
1086
|
+
title: headTitle
|
|
1087
|
+
}, HEALTH_META[headStatus].symbol) : null,
|
|
874
1088
|
React.createElement('span', { className: 'iris-act-arrow' }, '▶')),
|
|
875
1089
|
body);
|
|
876
1090
|
}
|
|
@@ -1031,14 +1245,16 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1031
1245
|
/* ---- 历史浏览器(设置页:按今天/昨天/更早分组折叠) ---- */
|
|
1032
1246
|
function HistoryBrowser(props) {
|
|
1033
1247
|
var recent = props.recent || [];
|
|
1248
|
+
var coreHistory = props.coreHistory || [];
|
|
1034
1249
|
var selected = props.selected;
|
|
1035
1250
|
var onSelect = props.onSelect;
|
|
1036
1251
|
var collapsedPair = React.useState({});
|
|
1037
1252
|
var collapsed = collapsedPair[0];
|
|
1038
1253
|
var setCollapsed = collapsedPair[1];
|
|
1039
|
-
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' }, '尚无任务记录');
|
|
1040
1255
|
var buckets = { 今天: [], 昨天: [], 更早: [] };
|
|
1041
|
-
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 }); });
|
|
1042
1258
|
var order = ['今天', '昨天', '更早'];
|
|
1043
1259
|
return React.createElement('div', {}, order.map(function (name) {
|
|
1044
1260
|
var rows = buckets[name];
|
|
@@ -1050,10 +1266,689 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1050
1266
|
onClick: function () { var n = {}; Object.keys(collapsed).forEach(function (k) { n[k] = collapsed[k]; }); n[name] = !isCollapsed; setCollapsed(n); }
|
|
1051
1267
|
}, React.createElement('span', {}, isCollapsed ? '▶' : '▼'), name + ' · ' + rows.length),
|
|
1052
1268
|
isCollapsed ? null : React.createElement('div', { className: 'iris-wb-box' },
|
|
1053
|
-
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
|
+
})));
|
|
1054
1274
|
}));
|
|
1055
1275
|
}
|
|
1056
1276
|
|
|
1277
|
+
function fmtBytes(bytes) {
|
|
1278
|
+
var value = Number(bytes || 0);
|
|
1279
|
+
if (value < 1024) return value + 'B';
|
|
1280
|
+
if (value < 1048576) return (value / 1024).toFixed(0) + 'KB';
|
|
1281
|
+
return (value / 1048576).toFixed(1) + 'MB';
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
function artifactIcon(item) {
|
|
1285
|
+
var mime = String(item && item.mime || '');
|
|
1286
|
+
if (mime.indexOf('image/') === 0) return '🖼';
|
|
1287
|
+
if (mime.indexOf('video/') === 0) return '🎬';
|
|
1288
|
+
if (mime.indexOf('audio/') === 0) return '🔊';
|
|
1289
|
+
return '📄';
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function artifactRowMini(item) {
|
|
1293
|
+
return React.createElement('a', { key: item.id, className: 'iris-artifact-mini', href: item.url, target: '_blank', rel: 'noreferrer', title: '打开作品' },
|
|
1294
|
+
React.createElement('span', {}, artifactIcon(item)),
|
|
1295
|
+
React.createElement('span', {}, item.file),
|
|
1296
|
+
React.createElement('span', { className: 'iris-gallery-meta' }, fmtBytes(item.size)));
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function artifactPreview(item) {
|
|
1300
|
+
var mime = String(item.mime || '');
|
|
1301
|
+
if (mime.indexOf('image/') === 0) {
|
|
1302
|
+
return React.createElement('a', { className: 'iris-gallery-preview', href: item.url, target: '_blank', rel: 'noreferrer', title: '打开原图' },
|
|
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
|
+
}));
|
|
1317
|
+
}
|
|
1318
|
+
if (mime.indexOf('video/') === 0) {
|
|
1319
|
+
return React.createElement('div', { className: 'iris-gallery-preview' },
|
|
1320
|
+
React.createElement('video', { src: item.url, controls: true, playsInline: true, preload: 'metadata' }));
|
|
1321
|
+
}
|
|
1322
|
+
if (mime.indexOf('audio/') === 0) {
|
|
1323
|
+
return React.createElement('div', { className: 'iris-gallery-preview' },
|
|
1324
|
+
React.createElement('audio', { src: item.url, controls: true, preload: 'metadata' }));
|
|
1325
|
+
}
|
|
1326
|
+
return React.createElement('a', { className: 'iris-gallery-preview iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开文件');
|
|
1327
|
+
}
|
|
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
|
+
|
|
1367
|
+
function ArtifactGallery(props) {
|
|
1368
|
+
var itemsPair = React.useState([]);
|
|
1369
|
+
var items = itemsPair[0];
|
|
1370
|
+
var setItems = itemsPair[1];
|
|
1371
|
+
var totalPair = React.useState(Number(props.total || 0));
|
|
1372
|
+
var total = totalPair[0];
|
|
1373
|
+
var setTotal = totalPair[1];
|
|
1374
|
+
var corePair = React.useState([]);
|
|
1375
|
+
var coreItems = corePair[0];
|
|
1376
|
+
var setCoreItems = corePair[1];
|
|
1377
|
+
var busyPair = React.useState(false);
|
|
1378
|
+
var busy = busyPair[0];
|
|
1379
|
+
var setBusy = busyPair[1];
|
|
1380
|
+
var notePair = React.useState('');
|
|
1381
|
+
var note = notePair[0];
|
|
1382
|
+
var setNote = notePair[1];
|
|
1383
|
+
|
|
1384
|
+
function load(append) {
|
|
1385
|
+
if (busy) return;
|
|
1386
|
+
setBusy(true);
|
|
1387
|
+
var offset = append ? items.length : 0;
|
|
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];
|
|
1399
|
+
setItems(append ? items.concat(data.items || []) : (data.items || []));
|
|
1400
|
+
setTotal(Number(data.total || 0));
|
|
1401
|
+
if (core) {
|
|
1402
|
+
var nextCore = coreGalleryItems(core, 200);
|
|
1403
|
+
setCoreItems(nextCore);
|
|
1404
|
+
if (core.error) setNote(core.error);
|
|
1405
|
+
else if (core.backendReloadRequired) setNote('DSH 后端尚未加载 Core 路由;重启后新作品会出现在这里。');
|
|
1406
|
+
}
|
|
1407
|
+
})
|
|
1408
|
+
.catch(function (error) { setNote('作品库读取失败:' + error.message); })
|
|
1409
|
+
.finally(function () { setBusy(false); });
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
function run(action, body, confirmText) {
|
|
1413
|
+
if (confirmText && !window.confirm(confirmText)) return;
|
|
1414
|
+
setBusy(true);
|
|
1415
|
+
postAction(action, body || {}).then(function (result) {
|
|
1416
|
+
var succeeded = result.ok && result.d && result.d.ok !== false;
|
|
1417
|
+
setNote(succeeded ? (result.d.text || '完成') : ('失败:' + ((result.d && (result.d.error || result.d.text)) || '未知错误')));
|
|
1418
|
+
if (succeeded) load(false);
|
|
1419
|
+
else setBusy(false);
|
|
1420
|
+
}).catch(function (error) { setBusy(false); setNote('失败:' + error.message); });
|
|
1421
|
+
}
|
|
1422
|
+
|
|
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;
|
|
1437
|
+
|
|
1438
|
+
return React.createElement('div', {},
|
|
1439
|
+
React.createElement('div', { className: 'iris-gallery-tools' },
|
|
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' },
|
|
1448
|
+
artifactPreview(item),
|
|
1449
|
+
React.createElement('div', { className: 'iris-gallery-info' },
|
|
1450
|
+
React.createElement('div', { className: 'iris-gallery-file', title: item.file }, item.file),
|
|
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)),
|
|
1453
|
+
React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1454
|
+
React.createElement('a', { className: 'iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开'),
|
|
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,
|
|
1459
|
+
note ? React.createElement('div', { className: 'iris-clean-note', style: { marginTop: '7px' } }, note) : null);
|
|
1460
|
+
}
|
|
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
|
+
|
|
1057
1952
|
/* ---- 泡泡常用卡片选择器(设置页:多项勾选 → 出现在悬浮窗「常用」标签) ---- */
|
|
1058
1953
|
function BubbleCardPicker() {
|
|
1059
1954
|
var pair = useBubbleCards();
|
|
@@ -1141,11 +2036,63 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1141
2036
|
note ? React.createElement('div', { className: 'iris-clean-note' }, note) : null);
|
|
1142
2037
|
}
|
|
1143
2038
|
|
|
2039
|
+
function HostDoctorPanel() {
|
|
2040
|
+
var pair = React.useState(null);
|
|
2041
|
+
var report = pair[0];
|
|
2042
|
+
var setReport = pair[1];
|
|
2043
|
+
var busyPair = React.useState(false);
|
|
2044
|
+
var busy = busyPair[0];
|
|
2045
|
+
var setBusy = busyPair[1];
|
|
2046
|
+
var errorPair = React.useState('');
|
|
2047
|
+
var error = errorPair[0];
|
|
2048
|
+
var setError = errorPair[1];
|
|
2049
|
+
|
|
2050
|
+
function load() {
|
|
2051
|
+
setBusy(true);
|
|
2052
|
+
setError('');
|
|
2053
|
+
fetch('/iris/api/doctor')
|
|
2054
|
+
.then(function (res) { return res.json().then(function (data) { if (!res.ok) throw new Error(data.error || '诊断失败'); return data; }); })
|
|
2055
|
+
.then(function (data) { setReport(data); })
|
|
2056
|
+
.catch(function (err) { setError(String(err && err.message || err)); })
|
|
2057
|
+
.finally(function () { setBusy(false); });
|
|
2058
|
+
}
|
|
2059
|
+
React.useEffect(function () { load(); }, []);
|
|
2060
|
+
|
|
2061
|
+
var checks = report && Array.isArray(report.checks) ? report.checks : [];
|
|
2062
|
+
var issues = checks.filter(function (item) { return item.status !== 'ok'; });
|
|
2063
|
+
var mark = { ok: '✓', warn: '!', error: '×' };
|
|
2064
|
+
function rows(list) {
|
|
2065
|
+
return React.createElement('div', { className: 'iris-doctor-list' }, list.map(function (item) {
|
|
2066
|
+
return React.createElement('div', { key: item.id, className: 'iris-doctor-row ' + item.status },
|
|
2067
|
+
React.createElement('span', {}, mark[item.status] || '·'),
|
|
2068
|
+
React.createElement('span', {}, item.summary));
|
|
2069
|
+
}));
|
|
2070
|
+
}
|
|
2071
|
+
var score = report
|
|
2072
|
+
? report.summary.errors + ' 错误 · ' + report.summary.warnings + ' 警告'
|
|
2073
|
+
: (busy ? '正在检查宿主…' : '尚未运行');
|
|
2074
|
+
return React.createElement('div', { className: 'iris-wb-card' },
|
|
2075
|
+
React.createElement('div', { className: 'iris-doctor-top' },
|
|
2076
|
+
React.createElement('span', { className: 'iris-doctor-score' }, score),
|
|
2077
|
+
report && report.host ? React.createElement('span', { className: 'iris-wb-muted' }, 'DSH ' + report.host.version) : null,
|
|
2078
|
+
React.createElement('button', { className: 'iris-pm-btn', disabled: busy, onClick: load }, busy ? '检查中…' : '刷新')),
|
|
2079
|
+
error ? React.createElement('div', { className: 'iris-doctor-row error' }, React.createElement('span', {}, '×'), React.createElement('span', {}, error)) : null,
|
|
2080
|
+
issues.length ? rows(issues) : (report ? React.createElement('div', { className: 'iris-wb-muted' }, '当前可观察项全部健康') : null),
|
|
2081
|
+
checks.length ? React.createElement('details', { className: 'iris-doctor-details' },
|
|
2082
|
+
React.createElement('summary', {}, '查看全部 ' + checks.length + ' 项'),
|
|
2083
|
+
rows(checks)) : null,
|
|
2084
|
+
React.createElement('div', { className: 'iris-wb-muted' }, '只读本机注册证据;不调用 Browser、模型或供应商。'));
|
|
2085
|
+
}
|
|
2086
|
+
|
|
1144
2087
|
function WorkbenchPanel() {
|
|
1145
2088
|
var state = useIrisState(5000);
|
|
1146
2089
|
var running = (state && state.tasks && state.tasks.running) || [];
|
|
1147
2090
|
var recent = (state && state.tasks && state.tasks.recent) || [];
|
|
1148
2091
|
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
2092
|
+
var artifactTotal = (state && state.artifacts && state.artifacts.total) || 0;
|
|
2093
|
+
// 同一任务区合并 legacy 与 Core 只读投影;Core 行仅有的动作是「重新观察」「重新取回作品」与需确认的「取消任务」。
|
|
2094
|
+
var coreUser = useCoreUserTasks();
|
|
2095
|
+
var coreGroups = splitCoreUserRows(coreUser.rows || []);
|
|
1149
2096
|
var selPair = React.useState(null);
|
|
1150
2097
|
var selectedTask = selPair[0];
|
|
1151
2098
|
var setSelectedTask = selPair[1];
|
|
@@ -1156,23 +2103,38 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1156
2103
|
React.createElement('span', { className: 'iris-wb-date' }, state ? '刷新于 ' + new Date().toLocaleTimeString() : '加载中…')),
|
|
1157
2104
|
React.createElement('div', { className: 'iris-wb-sec' }, '供应商'),
|
|
1158
2105
|
React.createElement(ProviderManager, {}),
|
|
2106
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '能力健康'),
|
|
2107
|
+
React.createElement(CapabilityHealthOverview, {}),
|
|
2108
|
+
React.createElement('div', { className: 'iris-wb-muted' }, '绿色有效 7 天;真实成功或显式实测会刷新。不会后台探测,也不会因 429、网络或供应商 5xx 变红。'),
|
|
1159
2109
|
React.createElement('div', { className: 'iris-wb-sec' }, '能力分配(failover 顺序)'),
|
|
1160
2110
|
React.createElement(CapabilityAssigner, {}),
|
|
1161
2111
|
React.createElement('div', { className: 'iris-wb-sec' }, 'Iris 泡泡快捷卡片(勾选后显示在「⚡ 常用」标签)'),
|
|
1162
2112
|
React.createElement(BubbleCardPicker, {}),
|
|
1163
2113
|
React.createElement('div', { className: 'iris-wb-sec' }, '对话框提示词优化'),
|
|
1164
2114
|
React.createElement(PromptOptimizerSettings, {}),
|
|
2115
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '宿主诊断'),
|
|
2116
|
+
React.createElement(HostDoctorPanel, {}),
|
|
1165
2117
|
React.createElement('div', { className: 'iris-wb-sec' }, '运行中任务'),
|
|
1166
2118
|
React.createElement('div', { className: 'iris-wb-box' },
|
|
1167
|
-
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))
|
|
1168
2122
|
: React.createElement('div', { className: 'iris-wb-empty' }, '暂无进行中任务')),
|
|
1169
|
-
attention.length ? React.createElement('div', {},
|
|
2123
|
+
attention.length || coreGroups.attention.length || coreUser.degraded ? React.createElement('div', {},
|
|
1170
2124
|
React.createElement('div', { className: 'iris-wb-sec' }, '需要处理'),
|
|
1171
2125
|
React.createElement('div', { className: 'iris-wb-box' },
|
|
1172
|
-
|
|
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,
|
|
2131
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '作品库'),
|
|
2132
|
+
React.createElement(ArtifactGallery, { total: artifactTotal }),
|
|
2133
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '高级诊断'),
|
|
2134
|
+
React.createElement(CoreRuntimePanel, {}),
|
|
1173
2135
|
React.createElement('div', { className: 'iris-wb-sec' }, '历史任务'),
|
|
1174
2136
|
React.createElement(CleanupBar, {}),
|
|
1175
|
-
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 }),
|
|
1176
2138
|
React.createElement('div', { className: 'iris-wb-sec' }, '操作'),
|
|
1177
2139
|
React.createElement(ActionGroups, {}));
|
|
1178
2140
|
}
|
|
@@ -1184,6 +2146,23 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1184
2146
|
var recent = (state && state.tasks && state.tasks.recent) || [];
|
|
1185
2147
|
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
1186
2148
|
var recentTotal = (state && state.tasks && state.tasks.recentTotal) || recent.length;
|
|
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;
|
|
1187
2166
|
var cards = useBubbleCards()[0];
|
|
1188
2167
|
var tabPair = React.useState('tasks');
|
|
1189
2168
|
var tab = tabPair[0];
|
|
@@ -1194,24 +2173,24 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1194
2173
|
function onSelect(id) { setSelectedTask(id); }
|
|
1195
2174
|
var body;
|
|
1196
2175
|
if (tab === 'tasks') {
|
|
1197
|
-
//
|
|
1198
|
-
var succeeded = recent.filter(function (t) {
|
|
1199
|
-
return t.status === 'succeeded' && Array.isArray(t.media) && t.media.length > 0;
|
|
1200
|
-
});
|
|
1201
|
-
var shown = succeeded.slice(0, 6);
|
|
2176
|
+
// 快捷面板承载当前任务、需要处理的异常事实与独立作品库中的最近作品。
|
|
1202
2177
|
body = React.createElement('div', { className: 'iris-wb-box' },
|
|
1203
|
-
|
|
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', {},
|
|
1204
2181
|
React.createElement('div', { className: 'iris-wb-sec' }, '运行中'),
|
|
1205
|
-
running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); })
|
|
1206
|
-
|
|
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', {},
|
|
1207
2185
|
React.createElement('div', { className: 'iris-wb-sec' }, '需要处理'),
|
|
1208
|
-
attention.slice(0, 4).map(function (t) { return taskRowMini(t, selectedTask === t.id, onSelect); })
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
2186
|
+
attention.slice(0, 4).map(function (t) { return taskRowMini(t, selectedTask === t.id, onSelect); }),
|
|
2187
|
+
coreGroups.attention.slice(0, 4).map(coreTaskMini)) : null,
|
|
2188
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '最近作品'),
|
|
2189
|
+
works.length ? works.map(artifactRowMini)
|
|
2190
|
+
: React.createElement('div', { className: 'iris-wb-empty' }, '尚无作品'),
|
|
2191
|
+
artifactTotal > works.length || recentTotal > 0
|
|
1213
2192
|
? React.createElement('div', { className: 'iris-bubble-more', style: { cursor: 'default' } },
|
|
1214
|
-
'
|
|
2193
|
+
'完整作品库、失败与取消历史请到 Iris 工作台查看') : null);
|
|
1215
2194
|
} else {
|
|
1216
2195
|
var defs = CARD_DEFS.filter(function (d) { return cards.indexOf(d.action) >= 0; });
|
|
1217
2196
|
body = defs.length
|
|
@@ -1467,10 +2446,35 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1467
2446
|
}));
|
|
1468
2447
|
}
|
|
1469
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
|
+
|
|
1470
2472
|
function FloatingBubble() {
|
|
1471
2473
|
var state = useIrisState(5000);
|
|
1472
2474
|
var providers = (state && state.providers) || [];
|
|
1473
|
-
var
|
|
2475
|
+
var fallbackConfigured = providers.some(function (p) { return p.enabled && p.apiKeyHint; });
|
|
2476
|
+
var overallHealth = state && state.health && HEALTH_META[state.health.overall]
|
|
2477
|
+
? state.health.overall : (fallbackConfigured ? 'configured' : 'unconfigured');
|
|
1474
2478
|
var running = (state && state.tasks && state.tasks.running) || [];
|
|
1475
2479
|
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
1476
2480
|
|
|
@@ -1538,17 +2542,30 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1538
2542
|
}
|
|
1539
2543
|
}
|
|
1540
2544
|
|
|
2545
|
+
function persistCurrentPosition() {
|
|
2546
|
+
setPos(function (current) {
|
|
2547
|
+
if (current && current.x != null && current.y != null) {
|
|
2548
|
+
try { localStorage.setItem('iris-bubble-pos', JSON.stringify(current)); } catch (_) {}
|
|
2549
|
+
}
|
|
2550
|
+
return current;
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
|
|
1541
2554
|
function onPointerUp(e) {
|
|
1542
2555
|
var drag = dragRef.current;
|
|
1543
2556
|
if (!drag) return;
|
|
1544
2557
|
try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) {}
|
|
1545
2558
|
var moved = drag.moved;
|
|
1546
2559
|
dragRef.current = null;
|
|
1547
|
-
if (moved)
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
2560
|
+
if (moved) persistCurrentPosition();
|
|
2561
|
+
else setOpen(!open);
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
function onPointerCancel(e) {
|
|
2565
|
+
if (!dragRef.current) return;
|
|
2566
|
+
try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) {}
|
|
2567
|
+
dragRef.current = null;
|
|
2568
|
+
persistCurrentPosition();
|
|
1552
2569
|
}
|
|
1553
2570
|
|
|
1554
2571
|
var style;
|
|
@@ -1566,8 +2583,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1566
2583
|
var panel = null;
|
|
1567
2584
|
if (open) {
|
|
1568
2585
|
var xy = bubbleXY();
|
|
1569
|
-
var
|
|
1570
|
-
var
|
|
2586
|
+
var panelWidth = Math.min(360, Math.max(0, window.innerWidth - 16));
|
|
2587
|
+
var panelHeight = Math.min(520, Math.max(0, window.innerHeight - 16));
|
|
2588
|
+
var panelLeft = Math.max(8, Math.min(xy.x - panelWidth + 30, window.innerWidth - panelWidth - 8));
|
|
2589
|
+
var panelTop = Math.max(8, Math.min(xy.y - 260, window.innerHeight - panelHeight - 8));
|
|
1571
2590
|
panel = React.createElement('div', { className: 'iris-bubble-panel', style: { left: panelLeft + 'px', top: panelTop + 'px' } },
|
|
1572
2591
|
React.createElement('div', { className: 'iris-bubble-panel-head' },
|
|
1573
2592
|
React.createElement('button', { className: 'iris-bubble-close', onClick: function () { setOpen(false); } }, '✕')),
|
|
@@ -1575,52 +2594,63 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1575
2594
|
}
|
|
1576
2595
|
|
|
1577
2596
|
return React.createElement('div', {
|
|
1578
|
-
className: 'iris-bubble' +
|
|
2597
|
+
className: 'iris-bubble health-' + overallHealth + (dragRef.current ? ' dragging' : ''),
|
|
1579
2598
|
style: style,
|
|
1580
2599
|
onPointerDown: onPointerDown,
|
|
1581
2600
|
onPointerMove: onPointerMove,
|
|
1582
2601
|
onPointerUp: onPointerUp,
|
|
1583
|
-
|
|
2602
|
+
onPointerCancel: onPointerCancel,
|
|
2603
|
+
title: 'Iris ' + healthText({ status: overallHealth }) + ':点击打开快捷面板'
|
|
1584
2604
|
},
|
|
1585
|
-
React.createElement('div', { className: 'iris-bubble-btn' },
|
|
2605
|
+
React.createElement('div', { className: 'iris-bubble-btn' }, React.createElement(IRIS_MARK_SVG)),
|
|
1586
2606
|
badge,
|
|
1587
2607
|
panel);
|
|
1588
2608
|
}
|
|
1589
2609
|
|
|
1590
|
-
|
|
1591
|
-
|
|
2610
|
+
var clientSlots = clientSlotsPort(ctx);
|
|
2611
|
+
|
|
2612
|
+
clientSlots.inject('settings.section', function () {
|
|
2613
|
+
var dispose = clientSlots.register({
|
|
1592
2614
|
name: 'settings.section',
|
|
1593
2615
|
id: 'iris-workbench',
|
|
1594
2616
|
order: 140,
|
|
1595
2617
|
label: function () { return 'Iris 工作台'; }
|
|
1596
2618
|
}, WorkbenchPanel);
|
|
2619
|
+
reportClientSeat('settings.section');
|
|
2620
|
+
return dispose;
|
|
1597
2621
|
});
|
|
1598
2622
|
|
|
1599
|
-
|
|
1600
|
-
|
|
2623
|
+
clientSlots.inject('conversation.input.right', function () {
|
|
2624
|
+
var dispose = clientSlots.register({
|
|
1601
2625
|
name: 'conversation.input.right',
|
|
1602
2626
|
id: 'iris-prompt-optimizer',
|
|
1603
2627
|
order: 40,
|
|
1604
2628
|
label: function () { return 'Iris 提示词优化'; }
|
|
1605
2629
|
}, PromptOptimizerControl);
|
|
2630
|
+
reportClientSeat('conversation.input.right');
|
|
2631
|
+
return dispose;
|
|
1606
2632
|
});
|
|
1607
2633
|
|
|
1608
|
-
|
|
1609
|
-
|
|
2634
|
+
clientSlots.inject('conversation.input.dock', function () {
|
|
2635
|
+
var dispose = clientSlots.register({
|
|
1610
2636
|
name: 'conversation.input.dock',
|
|
1611
2637
|
id: 'iris-progress',
|
|
1612
2638
|
order: 0,
|
|
1613
2639
|
label: function () { return 'Iris 任务进度'; }
|
|
1614
2640
|
}, ProgressDock);
|
|
2641
|
+
reportClientSeat('conversation.input.dock');
|
|
2642
|
+
return dispose;
|
|
1615
2643
|
});
|
|
1616
2644
|
|
|
1617
|
-
|
|
1618
|
-
|
|
2645
|
+
clientSlots.inject('shell.overlay', function () {
|
|
2646
|
+
var dispose = clientSlots.register({
|
|
1619
2647
|
name: 'shell.overlay',
|
|
1620
2648
|
id: 'iris-bubble',
|
|
1621
2649
|
order: 200,
|
|
1622
2650
|
label: function () { return 'Iris 泡泡'; }
|
|
1623
2651
|
}, FloatingBubble);
|
|
2652
|
+
reportClientSeat('shell.overlay');
|
|
2653
|
+
return dispose;
|
|
1624
2654
|
});
|
|
1625
2655
|
console.log('[iris] client slots registered');
|
|
1626
2656
|
} catch (err) {
|