@mokuyoaxis/dsh-iris 0.1.2 → 0.1.4
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 +57 -2
- package/README.en.md +43 -7
- package/README.md +43 -7
- package/bin/dsh-iris.js +23 -0
- package/docs/ARCHITECTURE.md +95 -0
- package/docs/ARTIFACT_LIBRARY.md +24 -0
- package/docs/FAULT_INJECTION.md +26 -0
- package/docs/HOST_ADAPTER_CONTRACT.md +86 -0
- package/docs/HOST_DOCTOR.md +47 -0
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +97 -0
- package/docs/PROVIDER_HEALTH.md +39 -0
- package/docs/PROVIDER_SUBMISSION_CONTRACT.md +60 -0
- package/docs/ROADMAP.md +21 -11
- package/docs/SECURITY.md +57 -0
- package/docs/TASK_SEMANTICS.md +148 -0
- package/docs/assets/diagrams/iris-architecture.png +0 -0
- package/docs/assets/diagrams/iris-architecture.svg +5 -5
- package/docs/releases/0.1.4.md +59 -0
- package/lib/actions.js +860 -290
- package/lib/adapters.js +7 -7
- package/lib/api.js +110 -18
- package/lib/artifacts.js +239 -0
- package/lib/bundled-skills.js +5 -5
- package/lib/client.js +478 -68
- package/lib/config.js +192 -9
- package/lib/doctor.js +414 -0
- package/lib/dsh-host-adapter.js +189 -0
- package/lib/host-contract.js +131 -0
- package/lib/host-runtime.js +156 -0
- package/lib/index.js +142 -157
- package/lib/locate.js +1 -1
- package/lib/media.js +16 -6
- package/lib/prompt-optimizer.js +10 -20
- package/lib/provider-adapter.js +371 -0
- package/lib/provider-adapters.js +279 -0
- package/lib/provider-contract.js +234 -0
- package/lib/provider-health.js +59 -0
- package/lib/render.js +43 -12
- package/lib/task-semantics.js +209 -0
- package/lib/tasks.js +500 -32
- package/lib/vision.js +23 -49
- package/package.json +19 -8
- package/user_guide.md +62 -2
- 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/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 工作台整页 ——
|
|
@@ -21,6 +21,42 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
21
21
|
var exports = module.exports;
|
|
22
22
|
|
|
23
23
|
var React = require('react');
|
|
24
|
+
var IRIS_CLIENT_VERSION = '0.1.4';
|
|
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 = [
|
|
@@ -37,6 +73,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
37
73
|
'.iris-wb-badge { font-size: 11px; padding: 1px 6px; border-radius: 4px; background: var(--dsw-alias-bg-layer-2, #32323c); }',
|
|
38
74
|
'.iris-wb-badge.ok { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
39
75
|
'.iris-wb-badge.err { color: var(--dsw-alias-state-error-primary, #e06c6c); }',
|
|
76
|
+
'.iris-wb-badge.warn { color: var(--dsw-alias-state-warn-primary, #d9a941); }',
|
|
40
77
|
'.iris-wb-progress { margin-top: 6px; }',
|
|
41
78
|
'.iris-wb-bar { height: 5px; border-radius: 3px; background: var(--dsw-alias-bg-layer-2, #32323c); overflow: hidden; }',
|
|
42
79
|
'.iris-wb-bar i { display: block; height: 100%; background: var(--dsw-alias-brand-primary, #7aa2f7); }',
|
|
@@ -56,6 +93,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
56
93
|
'.iris-wb-drawer .iris-wb-prompt { white-space: pre-wrap; word-break: break-word; font-size: 12px; }',
|
|
57
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; }',
|
|
58
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; }',
|
|
59
105
|
/* ---- 操作卡片组(阶段 5 GUI 直连) ---- */
|
|
60
106
|
'.iris-act-group { display: grid; gap: 8px; }',
|
|
61
107
|
'.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; }',
|
|
@@ -95,8 +141,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
95
141
|
'.iris-pm-field { display: grid; gap: 3px; }',
|
|
96
142
|
'.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; }',
|
|
97
143
|
'.iris-pm-act { font-size: 11px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
98
|
-
'.iris-pm-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-label-
|
|
99
|
-
'.iris-pm-dot.
|
|
144
|
+
'.iris-pm-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #73737d); margin-right: 4px; }',
|
|
145
|
+
'.iris-pm-dot.configured { background: #6f9df6; box-shadow: 0 0 7px rgba(111,157,246,.55); }',
|
|
146
|
+
'.iris-pm-dot.verified { background: var(--dsw-alias-state-success-primary, #6fcf6f); box-shadow: 0 0 7px var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
147
|
+
'.iris-pm-dot.failed { background: #b9656d; box-shadow: 0 0 6px rgba(185,101,109,.38); opacity: .82; }',
|
|
148
|
+
'.iris-health-grid { display: grid; gap: 5px; }',
|
|
149
|
+
'.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; }',
|
|
150
|
+
'.iris-health-name { font-weight: 600; }',
|
|
151
|
+
'.iris-health-state { color: var(--dsw-alias-label-secondary, #9a9a9a); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
|
|
152
|
+
'.iris-health-row.failed .iris-health-state { color: #c87a81; }',
|
|
153
|
+
'.iris-health-row.configured .iris-health-state { color: #7da8fa; }',
|
|
154
|
+
'.iris-health-row.verified .iris-health-state { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
100
155
|
'.iris-cap-grid { display: grid; gap: 6px; }',
|
|
101
156
|
'.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; }',
|
|
102
157
|
'.iris-cap-name { font-weight: 600; min-width: 64px; }',
|
|
@@ -108,10 +163,12 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
108
163
|
'.iris-bubble { position: fixed; z-index: 9999; cursor: grab; user-select: none; touch-action: none; }',
|
|
109
164
|
'.iris-bubble.dragging { cursor: grabbing; }',
|
|
110
165
|
'.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; }',
|
|
111
|
-
'.iris-bubble.
|
|
112
|
-
'.iris-bubble.
|
|
166
|
+
'.iris-bubble.health-configured .iris-bubble-btn { border-color: #6f9df6; box-shadow: 0 0 14px rgba(111,157,246,.55); }',
|
|
167
|
+
'.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); }',
|
|
168
|
+
'.iris-bubble.health-failed .iris-bubble-btn { opacity: .72; border-color: #b9656d; box-shadow: 0 0 11px rgba(185,101,109,.34); }',
|
|
169
|
+
'.iris-bubble.health-unconfigured .iris-bubble-btn { opacity: 0.55; border-color: var(--dsw-alias-border-l1, #3a3a44); }',
|
|
113
170
|
'.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; }',
|
|
114
|
-
'.iris-bubble-panel { position: fixed; z-index: 9998; width: 360px; max-height: min(520px,
|
|
171
|
+
'.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); }',
|
|
115
172
|
'.iris-bubble-panel-head { position: absolute; top: 6px; right: 6px; z-index: 2; }',
|
|
116
173
|
'.iris-bubble-close { border: none; background: transparent; color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 14px; cursor: pointer; padding: 4px 6px; }',
|
|
117
174
|
'.iris-bubble-close:hover { color: var(--dsw-alias-label-primary, #e6e6e6); }',
|
|
@@ -135,6 +192,22 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
135
192
|
'.iris-clean-btn:hover { border-color: var(--dsw-alias-brand-primary, #7aa2f7); }',
|
|
136
193
|
'.iris-clean-btn.danger:hover { color: var(--dsw-alias-state-error-primary, #e06c6c); border-color: var(--dsw-alias-state-error-primary, #e06c6c); }',
|
|
137
194
|
'.iris-clean-note { font-size: 11px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
195
|
+
/* 独立作品库 v0:任务清理后仍可浏览,窄屏自动收缩。 */
|
|
196
|
+
'.iris-gallery-tools { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }',
|
|
197
|
+
'.iris-gallery-tools .iris-clean-note { flex: 1; min-width: 100px; }',
|
|
198
|
+
'.iris-gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 10px; }',
|
|
199
|
+
'.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); }',
|
|
200
|
+
'.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); }',
|
|
201
|
+
'.iris-gallery-preview img, .iris-gallery-preview video { width: 100%; height: 100%; object-fit: contain; }',
|
|
202
|
+
'.iris-gallery-preview audio { width: calc(100% - 16px); }',
|
|
203
|
+
'.iris-gallery-file { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; font-weight: 600; }',
|
|
204
|
+
'.iris-gallery-meta { color: var(--dsw-alias-label-tertiary, #7a7a85); font-size: 10px; }',
|
|
205
|
+
'.iris-gallery-info { display: grid; gap: 4px; padding: 8px 9px 9px; }',
|
|
206
|
+
'.iris-gallery-actions { display: flex; align-items: center; gap: 8px; }',
|
|
207
|
+
'.iris-artifact-mini { display: flex; align-items: center; gap: 7px; min-width: 0; padding: 4px 6px; border-radius: 7px; color: var(--dsw-alias-label-secondary, #9a9a9a); text-decoration: none; }',
|
|
208
|
+
'.iris-artifact-mini:hover { background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-primary, #e6e6e6); }',
|
|
209
|
+
'.iris-artifact-mini span:nth-child(2) { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
|
|
210
|
+
'@media (max-width: 360px) { .iris-gallery-grid { grid-template-columns: 1fr; } }',
|
|
138
211
|
/* 模型池(阶段 9 P4) */
|
|
139
212
|
'.iris-pm-pool { display: grid; gap: 4px; }',
|
|
140
213
|
'.iris-pm-mrows { display: grid; gap: 3px; max-height: 220px; overflow: auto; }',
|
|
@@ -143,8 +216,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
143
216
|
'.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; }',
|
|
144
217
|
'.iris-pm-mcaps { display: flex; gap: 3px; }',
|
|
145
218
|
'.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); }',
|
|
146
|
-
'.iris-pm-ver.
|
|
147
|
-
'.iris-pm-ver.
|
|
219
|
+
'.iris-pm-ver.configured { color: #7da8fa; }',
|
|
220
|
+
'.iris-pm-ver.verified { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
221
|
+
'.iris-pm-ver.failed { color: #c87a81; border-color: rgba(185,101,109,.5); }',
|
|
222
|
+
'.iris-pm-ver.unconfigured { opacity: .55; }',
|
|
148
223
|
'.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; }',
|
|
149
224
|
/* 文件选择器(阶段 10) */
|
|
150
225
|
'.iris-ff { display: grid; gap: 4px; }',
|
|
@@ -252,6 +327,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
252
327
|
if (sharedState) setState(sharedState);
|
|
253
328
|
if (!sharedTimer && !sharedSource) {
|
|
254
329
|
function apply(data) {
|
|
330
|
+
if (sharedState && data && sharedState.stateEpoch && data.stateEpoch === sharedState.stateEpoch
|
|
331
|
+
&& Number(data.stateRevision || 0) <= Number(sharedState.stateRevision || 0)) return;
|
|
255
332
|
sharedState = data;
|
|
256
333
|
for (var i = 0; i < sharedListeners.length; i++) {
|
|
257
334
|
sharedListeners[i](data);
|
|
@@ -291,13 +368,60 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
291
368
|
return sharedState;
|
|
292
369
|
}
|
|
293
370
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
371
|
+
var HEALTH_META = {
|
|
372
|
+
unconfigured: { label: '未配置', symbol: '○' },
|
|
373
|
+
configured: { label: '已配置,待验证', symbol: '◌' },
|
|
374
|
+
verified: { label: '近期验证成功', symbol: '●' },
|
|
375
|
+
failed: { label: '认证失败', symbol: '×' }
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
function capabilityHealth(state, capability) {
|
|
379
|
+
var health = state && state.health && state.health.capabilities
|
|
380
|
+
&& state.health.capabilities[capability];
|
|
381
|
+
if (health && HEALTH_META[health.status]) return health;
|
|
297
382
|
var providers = (state && state.providers) || [];
|
|
298
|
-
|
|
299
|
-
return p.enabled && p.apiKeyHint && Array.isArray(p.capabilities)
|
|
383
|
+
var configured = providers.some(function (p) {
|
|
384
|
+
return p.enabled && p.apiKeyHint && Array.isArray(p.capabilities)
|
|
385
|
+
&& p.capabilities.indexOf(capability) >= 0;
|
|
300
386
|
});
|
|
387
|
+
return { status: configured ? 'configured' : 'unconfigured', candidateCount: configured ? 1 : 0 };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function relativeHealthTime(value) {
|
|
391
|
+
var ms = Date.now() - Date.parse(String(value || ''));
|
|
392
|
+
if (!Number.isFinite(ms) || ms < 0) return '';
|
|
393
|
+
if (ms < 60000) return '刚刚';
|
|
394
|
+
if (ms < 3600000) return Math.floor(ms / 60000) + ' 分钟前';
|
|
395
|
+
if (ms < 86400000) return Math.floor(ms / 3600000) + ' 小时前';
|
|
396
|
+
return Math.floor(ms / 86400000) + ' 天前';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function healthText(health) {
|
|
400
|
+
var status = health && HEALTH_META[health.status] ? health.status : 'unconfigured';
|
|
401
|
+
var text = HEALTH_META[status].label;
|
|
402
|
+
var age = relativeHealthTime(health && health.observedAt);
|
|
403
|
+
return text + (age ? ' · ' + age : '');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function CapabilityHealthOverview() {
|
|
407
|
+
var state = useIrisState(5000);
|
|
408
|
+
var rows = [
|
|
409
|
+
['image-gen', '图片'],
|
|
410
|
+
['video-gen', '视频'],
|
|
411
|
+
['tts', '语音'],
|
|
412
|
+
['transcribe', '转写'],
|
|
413
|
+
['vision', '视觉']
|
|
414
|
+
];
|
|
415
|
+
return React.createElement('div', { className: 'iris-health-grid' }, rows.map(function (row) {
|
|
416
|
+
var health = capabilityHealth(state, row[0]);
|
|
417
|
+
var meta = HEALTH_META[health.status] || HEALTH_META.unconfigured;
|
|
418
|
+
return React.createElement('div', { key: row[0], className: 'iris-health-row ' + health.status },
|
|
419
|
+
React.createElement('span', { className: 'iris-health-name' },
|
|
420
|
+
React.createElement('span', { className: 'iris-pm-dot ' + health.status }, meta.symbol),
|
|
421
|
+
row[1]),
|
|
422
|
+
React.createElement('span', { className: 'iris-health-state', title: healthText(health) }, healthText(health)),
|
|
423
|
+
React.createElement('span', { className: 'iris-wb-muted' }, String(health.candidateCount || 0) + ' 路'));
|
|
424
|
+
}));
|
|
301
425
|
}
|
|
302
426
|
|
|
303
427
|
/* ---- 任务详情抽屉:按需拉取 /iris/api/task/:id ---- */
|
|
@@ -347,10 +471,38 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
347
471
|
return '更早';
|
|
348
472
|
}
|
|
349
473
|
|
|
474
|
+
var TASK_STATE_LABELS = {
|
|
475
|
+
queued: '排队中', running: '运行中', watching_paused: '观察暂停', needs_attention: '需要确认',
|
|
476
|
+
artifact_unavailable: '结果待取回', succeeded: '成功', failed: '失败', canceled: '已取消'
|
|
477
|
+
};
|
|
478
|
+
function taskState(task) {
|
|
479
|
+
if (task && task.userState) return task.userState;
|
|
480
|
+
if (task && task.status === 'succeeded') return 'succeeded';
|
|
481
|
+
if (task && task.status === 'failed') return 'failed';
|
|
482
|
+
if (task && task.status === 'canceled') return 'canceled';
|
|
483
|
+
return 'running';
|
|
484
|
+
}
|
|
485
|
+
function taskStateLabel(task) {
|
|
486
|
+
var state = taskState(task);
|
|
487
|
+
return TASK_STATE_LABELS[state] || state;
|
|
488
|
+
}
|
|
489
|
+
function taskStateClass(task) {
|
|
490
|
+
var state = taskState(task);
|
|
491
|
+
if (state === 'succeeded') return 'ok';
|
|
492
|
+
if (state === 'failed') return 'err';
|
|
493
|
+
if (state === 'watching_paused' || state === 'needs_attention' || state === 'artifact_unavailable') return 'warn';
|
|
494
|
+
return '';
|
|
495
|
+
}
|
|
496
|
+
function attentionDispositionLabel(task) {
|
|
497
|
+
var disposition = task && task.attentionDisposition;
|
|
498
|
+
if (!disposition || disposition.status !== 'acknowledged') return '';
|
|
499
|
+
return disposition.reason === 'retried' ? '已通过重试处理' : '提醒已读';
|
|
500
|
+
}
|
|
501
|
+
|
|
350
502
|
/* 紧凑任务行:泡泡「任务」标签用,单行摘要 */
|
|
351
503
|
function taskRowMini(task, selected, onSelect) {
|
|
352
|
-
var st = task
|
|
353
|
-
var label =
|
|
504
|
+
var st = taskStateClass(task);
|
|
505
|
+
var label = taskStateLabel(task);
|
|
354
506
|
return React.createElement('div', {
|
|
355
507
|
key: task.id, className: 'iris-task-mini' + (selected ? ' selected' : ''),
|
|
356
508
|
onClick: function () { onSelect(selected ? null : task.id); }, title: '点击展开详情'
|
|
@@ -361,6 +513,47 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
361
513
|
React.createElement('span', { className: 'iris-wb-muted' }, task.createdAt ? new Date(task.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : ''));
|
|
362
514
|
}
|
|
363
515
|
|
|
516
|
+
function TaskRecoveryActions({ detail }) {
|
|
517
|
+
var pair = React.useState('');
|
|
518
|
+
var note = pair[0];
|
|
519
|
+
var setNote = pair[1];
|
|
520
|
+
var busyPair = React.useState(false);
|
|
521
|
+
var busy = busyPair[0];
|
|
522
|
+
var setBusy = busyPair[1];
|
|
523
|
+
var state = taskState(detail);
|
|
524
|
+
var attentionState = ['watching_paused', 'needs_attention', 'artifact_unavailable'].indexOf(state) >= 0;
|
|
525
|
+
var acknowledged = detail.attentionDisposition && detail.attentionDisposition.status === 'acknowledged';
|
|
526
|
+
var canAcknowledge = detail.schemaVersion === 2 && attentionState && !acknowledged;
|
|
527
|
+
var canRestore = detail.schemaVersion === 2 && attentionState && acknowledged;
|
|
528
|
+
var canReobserve = detail.schemaVersion === 2 && !acknowledged && detail.acceptance === 'accepted' && detail.remoteTaskId
|
|
529
|
+
&& detail.outcome !== 'succeeded' && detail.outcome !== 'failed' && detail.outcome !== 'canceled';
|
|
530
|
+
var canRedeliver = detail.schemaVersion === 2 && !acknowledged && state === 'artifact_unavailable' && detail.remoteTaskId;
|
|
531
|
+
var canRetry = detail.schemaVersion === 2 && !acknowledged && ['needs_attention', 'failed', 'canceled'].indexOf(state) >= 0
|
|
532
|
+
&& (detail.cap === 'image' || (detail.cap === 'video' && detail.mode === 't2v'));
|
|
533
|
+
if (!canAcknowledge && !canRestore && !canReobserve && !canRedeliver && !canRetry) return null;
|
|
534
|
+
function run(action, body, confirmMessage) {
|
|
535
|
+
if (confirmMessage && !window.confirm(confirmMessage)) return;
|
|
536
|
+
setBusy(true);
|
|
537
|
+
setNote('处理中…');
|
|
538
|
+
postAction(action, body).then(function (r) {
|
|
539
|
+
setNote(r.ok ? ((r.d && r.d.text) || '操作已接受') : ('失败:' + ((r.d && r.d.error) || '未知错误')));
|
|
540
|
+
}).catch(function (error) { setNote('失败:' + String((error && error.message) || error)); })
|
|
541
|
+
.finally(function () { setBusy(false); });
|
|
542
|
+
}
|
|
543
|
+
return React.createElement('div', { className: 'iris-wb-row', onClick: function (e) { e.stopPropagation(); } },
|
|
544
|
+
canReobserve ? React.createElement('button', { className: 'iris-pm-btn', disabled: busy, onClick: function () { run('task_reobserve', { task_id: detail.id }); } }, '重新观察') : null,
|
|
545
|
+
canRedeliver ? React.createElement('button', { className: 'iris-pm-btn', disabled: busy, onClick: function () { run('task_redeliver', { task_id: detail.id }); } }, '重新交付') : null,
|
|
546
|
+
canRetry ? React.createElement('button', { className: 'iris-pm-btn danger', disabled: busy, onClick: function () {
|
|
547
|
+
run('task_manual_retry', { task_id: detail.id, confirm_duplicate_charge: true },
|
|
548
|
+
'远端任务可能已经产生,本次会创建新的生成任务,可能重复计费。确认知情重试?');
|
|
549
|
+
} }, '知情重试') : null,
|
|
550
|
+
canAcknowledge ? React.createElement('button', { className: 'iris-pm-btn', disabled: busy,
|
|
551
|
+
onClick: function () { run('task_ack_attention', { task_id: detail.id }); } }, '标为已读') : null,
|
|
552
|
+
canRestore ? React.createElement('button', { className: 'iris-pm-btn', disabled: busy,
|
|
553
|
+
onClick: function () { run('task_restore_attention', { task_id: detail.id }); } }, '恢复提醒') : null,
|
|
554
|
+
note ? React.createElement('span', { className: 'iris-clean-note' }, note) : null);
|
|
555
|
+
}
|
|
556
|
+
|
|
364
557
|
/* ---- 任务详情抽屉:点击任务卡片行展开 ---- */
|
|
365
558
|
function TaskDetailDrawer({ taskId }) {
|
|
366
559
|
var detail = useTaskDetail(taskId);
|
|
@@ -370,6 +563,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
370
563
|
var el = [];
|
|
371
564
|
el.push(React.createElement('div', { key: 'k', className: 'iris-wb-k' }, '提示词'));
|
|
372
565
|
el.push(React.createElement('div', { key: 'p', className: 'iris-wb-prompt' }, detail.prompt || '(空)'));
|
|
566
|
+
if (Number(detail.schemaVersion) === 1) {
|
|
567
|
+
el.push(React.createElement('div', { key: 'legacy', className: 'iris-clean-note' },
|
|
568
|
+
'旧版任务记录 · 仅支持查看。由于缺少可靠的远端受理与交付事实,不提供重新观察、重新交付或知情重试;0.1.3 新任务将使用可信任务语义。'));
|
|
569
|
+
}
|
|
570
|
+
var dispositionLabel = attentionDispositionLabel(detail);
|
|
571
|
+
if (dispositionLabel) {
|
|
572
|
+
var related = detail.attentionDisposition.relatedTaskId ? ' · 关联任务 ' + detail.attentionDisposition.relatedTaskId : '';
|
|
573
|
+
el.push(React.createElement('div', { key: 'attention-disposition', className: 'iris-clean-note' }, dispositionLabel + related));
|
|
574
|
+
}
|
|
373
575
|
if (detail.error) {
|
|
374
576
|
el.push(React.createElement('div', { key: 'ek', className: 'iris-wb-k' }, '错误'));
|
|
375
577
|
el.push(React.createElement('div', { key: 'e', className: 'iris-wb-err' }, detail.error));
|
|
@@ -377,11 +579,13 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
377
579
|
var kv = [
|
|
378
580
|
['ID', detail.id],
|
|
379
581
|
['能力', detail.cap],
|
|
380
|
-
['状态', detail
|
|
582
|
+
['状态', taskStateLabel(detail)],
|
|
381
583
|
['模型', detail.model],
|
|
382
584
|
['供应商', detail.providerName],
|
|
383
585
|
['模式', detail.mode || '—'],
|
|
384
586
|
['远端任务', detail.remoteTaskId || '—'],
|
|
587
|
+
['来源任务', detail.retryOf || '—'],
|
|
588
|
+
['后续重试', (detail.manualRetries || []).map(function (item) { return item.taskId; }).join('、') || '—'],
|
|
385
589
|
['发起', detail.createdAt ? fmtTime(detail.createdAt) : '—'],
|
|
386
590
|
['完成', detail.finishedAt ? fmtTime(detail.finishedAt) : '—'],
|
|
387
591
|
['耗时', detail.elapsedMs ? fmtElapsed(detail.elapsedMs) : '—']
|
|
@@ -418,24 +622,25 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
418
622
|
return React.createElement('span', { key: a.attachmentId, className: 'iris-wb-kv' }, a.attachmentId + (a.file ? ' (' + a.file + ')' : ''));
|
|
419
623
|
})));
|
|
420
624
|
}
|
|
625
|
+
el.push(React.createElement(TaskRecoveryActions, { key: 'recovery', detail: detail }));
|
|
421
626
|
return React.createElement('div', { className: 'iris-wb-drawer' }, ...el);
|
|
422
627
|
}
|
|
423
628
|
|
|
424
629
|
function taskRow(task, isRunning, selected, onSelect) {
|
|
425
|
-
var
|
|
426
|
-
|
|
427
|
-
badge = React.createElement('span', { className: 'iris-wb-badge ok' }, '成功');
|
|
428
|
-
} else if (task.status === 'failed') {
|
|
429
|
-
badge = React.createElement('span', { className: 'iris-wb-badge err' }, '失败');
|
|
430
|
-
} else {
|
|
431
|
-
badge = React.createElement('span', { className: 'iris-wb-badge' }, task.status);
|
|
432
|
-
}
|
|
630
|
+
var stateClass = taskStateClass(task);
|
|
631
|
+
var badge = React.createElement('span', { className: 'iris-wb-badge' + (stateClass ? ' ' + stateClass : '') }, taskStateLabel(task));
|
|
433
632
|
var cells = [
|
|
434
633
|
React.createElement('span', { className: 'iris-wb-kv' }, task.cap),
|
|
435
634
|
badge,
|
|
436
635
|
React.createElement('span', { className: 'iris-wb-kv' }, task.model || ''),
|
|
437
636
|
React.createElement('span', { className: 'iris-wb-muted' }, String(task.prompt || '').slice(0, 60))
|
|
438
637
|
];
|
|
638
|
+
if (Number(task.schemaVersion) === 1) {
|
|
639
|
+
cells.push(React.createElement('span', { className: 'iris-wb-badge', title: '旧版记录缺少可靠的远端受理与交付事实,仅支持查看' }, '旧任务 · 只读'));
|
|
640
|
+
}
|
|
641
|
+
var dispositionLabel = attentionDispositionLabel(task);
|
|
642
|
+
if (dispositionLabel) cells.push(React.createElement('span', { className: 'iris-wb-badge ok' }, dispositionLabel));
|
|
643
|
+
if (task.retryOf) cells.push(React.createElement('span', { className: 'iris-wb-badge' }, '重试任务'));
|
|
439
644
|
if (task.error) cells.push(React.createElement('span', { className: 'iris-wb-badge err' }, String(task.error).slice(0, 40)));
|
|
440
645
|
var links = (task.media || []).map(function (m) {
|
|
441
646
|
return React.createElement('a', {
|
|
@@ -487,10 +692,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
487
692
|
var setNewId = addPair[1];
|
|
488
693
|
var models = p.models || [];
|
|
489
694
|
function verBadge(m, c) {
|
|
490
|
-
var
|
|
491
|
-
var
|
|
492
|
-
var
|
|
493
|
-
|
|
695
|
+
var health = m.health && m.health[c];
|
|
696
|
+
var status = health && HEALTH_META[health.status] ? health.status : 'configured';
|
|
697
|
+
var cls = 'iris-pm-ver ' + status;
|
|
698
|
+
var title = healthText(health || { status: status });
|
|
699
|
+
var symbol = status === 'verified' ? '✓' : (status === 'failed' ? '×' : (status === 'unconfigured' ? '○' : '?'));
|
|
700
|
+
return React.createElement('button', {
|
|
701
|
+
key: c,
|
|
702
|
+
className: cls,
|
|
703
|
+
title: title + ';点击单独验证此能力',
|
|
704
|
+
onClick: function () { props.testModel(p, m, c); }
|
|
705
|
+
}, c + symbol);
|
|
494
706
|
}
|
|
495
707
|
var rows = models.map(function (m) {
|
|
496
708
|
var caps = m.capabilities || [];
|
|
@@ -727,6 +939,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
727
939
|
.then(function (res) { setRun(res.ok ? { ok: true, text: res.d.text || '', img: res.d.imageDataUrl || null } : { ok: false, text: res.d.error || '执行失败' }); })
|
|
728
940
|
.catch(function () { setRun({ ok: false, text: '网络错误' }); });
|
|
729
941
|
}
|
|
942
|
+
var cardHealth = capability ? capabilityHealth(irisState, capability) : { status: 'verified' };
|
|
730
943
|
var body = null;
|
|
731
944
|
if (open) {
|
|
732
945
|
var inputs = fields.map(function (f) {
|
|
@@ -762,16 +975,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
762
975
|
result = React.createElement('div', { className: 'iris-act-result' + (run.ok ? '' : ' err') }, run.text);
|
|
763
976
|
}
|
|
764
977
|
}
|
|
765
|
-
//
|
|
978
|
+
// 只读模型提示:当前能力解析到的模型与持久健康事实。
|
|
766
979
|
var modelBar = null;
|
|
767
980
|
if (capability) {
|
|
768
|
-
var
|
|
981
|
+
var available = modelInfo && modelInfo.loaded && modelInfo.options.length > 0;
|
|
769
982
|
var shown = (vals.model && vals.model.trim()) || (modelInfo && modelInfo.assigned) || '';
|
|
770
983
|
modelBar = React.createElement('div', { className: 'iris-act-field' },
|
|
771
984
|
React.createElement('label', {},
|
|
772
|
-
React.createElement('span', { className:
|
|
773
|
-
' 模型:' + (shown || '自动') + (
|
|
774
|
-
React.createElement('div', { className: 'iris-wb-muted' },
|
|
985
|
+
React.createElement('span', { className: 'iris-pm-dot ' + cardHealth.status }, HEALTH_META[cardHealth.status].symbol),
|
|
986
|
+
' 模型:' + (shown || '自动') + (available ? '' : '(无可用模型)')),
|
|
987
|
+
React.createElement('div', { className: 'iris-wb-muted' },
|
|
988
|
+
healthText(cardHealth) + ' · 改分配 → 上方「能力分配(failover 顺序)」'));
|
|
775
989
|
}
|
|
776
990
|
body = React.createElement('div', { className: 'iris-act-body' },
|
|
777
991
|
modelBar,
|
|
@@ -780,13 +994,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
780
994
|
run === 'running' ? '执行中…' : '执行'),
|
|
781
995
|
result);
|
|
782
996
|
}
|
|
783
|
-
var
|
|
784
|
-
var
|
|
785
|
-
|
|
786
|
-
|
|
997
|
+
var headStatus = capability ? cardHealth.status : 'verified';
|
|
998
|
+
var headTitle = capability ? healthText(cardHealth) : '本地功能';
|
|
999
|
+
return React.createElement('div', {
|
|
1000
|
+
className: 'iris-act-card' + (open ? ' open' : '') + (capability ? ' health-' + headStatus : '')
|
|
1001
|
+
},
|
|
787
1002
|
React.createElement('div', { className: 'iris-act-head', onClick: function () { setOpen(!open); } },
|
|
788
1003
|
React.createElement('span', { className: 'iris-act-title' }, title),
|
|
789
|
-
capability ? React.createElement('span', {
|
|
1004
|
+
capability ? React.createElement('span', {
|
|
1005
|
+
className: 'iris-pm-dot ' + headStatus,
|
|
1006
|
+
title: headTitle
|
|
1007
|
+
}, HEALTH_META[headStatus].symbol) : null,
|
|
790
1008
|
React.createElement('span', { className: 'iris-act-arrow' }, '▶')),
|
|
791
1009
|
body);
|
|
792
1010
|
}
|
|
@@ -970,6 +1188,105 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
970
1188
|
}));
|
|
971
1189
|
}
|
|
972
1190
|
|
|
1191
|
+
function fmtBytes(bytes) {
|
|
1192
|
+
var value = Number(bytes || 0);
|
|
1193
|
+
if (value < 1024) return value + 'B';
|
|
1194
|
+
if (value < 1048576) return (value / 1024).toFixed(0) + 'KB';
|
|
1195
|
+
return (value / 1048576).toFixed(1) + 'MB';
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function artifactIcon(item) {
|
|
1199
|
+
var mime = String(item && item.mime || '');
|
|
1200
|
+
if (mime.indexOf('image/') === 0) return '🖼';
|
|
1201
|
+
if (mime.indexOf('video/') === 0) return '🎬';
|
|
1202
|
+
if (mime.indexOf('audio/') === 0) return '🔊';
|
|
1203
|
+
return '📄';
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function artifactRowMini(item) {
|
|
1207
|
+
return React.createElement('a', { key: item.id, className: 'iris-artifact-mini', href: item.url, target: '_blank', rel: 'noreferrer', title: '打开作品' },
|
|
1208
|
+
React.createElement('span', {}, artifactIcon(item)),
|
|
1209
|
+
React.createElement('span', {}, item.file),
|
|
1210
|
+
React.createElement('span', { className: 'iris-gallery-meta' }, fmtBytes(item.size)));
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
function artifactPreview(item) {
|
|
1214
|
+
var mime = String(item.mime || '');
|
|
1215
|
+
if (mime.indexOf('image/') === 0) {
|
|
1216
|
+
return React.createElement('a', { className: 'iris-gallery-preview', href: item.url, target: '_blank', rel: 'noreferrer', title: '打开原图' },
|
|
1217
|
+
React.createElement('img', { src: item.url, alt: 'Iris 作品 ' + item.file, loading: 'lazy' }));
|
|
1218
|
+
}
|
|
1219
|
+
if (mime.indexOf('video/') === 0) {
|
|
1220
|
+
return React.createElement('div', { className: 'iris-gallery-preview' },
|
|
1221
|
+
React.createElement('video', { src: item.url, controls: true, playsInline: true, preload: 'metadata' }));
|
|
1222
|
+
}
|
|
1223
|
+
if (mime.indexOf('audio/') === 0) {
|
|
1224
|
+
return React.createElement('div', { className: 'iris-gallery-preview' },
|
|
1225
|
+
React.createElement('audio', { src: item.url, controls: true, preload: 'metadata' }));
|
|
1226
|
+
}
|
|
1227
|
+
return React.createElement('a', { className: 'iris-gallery-preview iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开文件');
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
function ArtifactGallery(props) {
|
|
1231
|
+
var itemsPair = React.useState([]);
|
|
1232
|
+
var items = itemsPair[0];
|
|
1233
|
+
var setItems = itemsPair[1];
|
|
1234
|
+
var totalPair = React.useState(Number(props.total || 0));
|
|
1235
|
+
var total = totalPair[0];
|
|
1236
|
+
var setTotal = totalPair[1];
|
|
1237
|
+
var busyPair = React.useState(false);
|
|
1238
|
+
var busy = busyPair[0];
|
|
1239
|
+
var setBusy = busyPair[1];
|
|
1240
|
+
var notePair = React.useState('');
|
|
1241
|
+
var note = notePair[0];
|
|
1242
|
+
var setNote = notePair[1];
|
|
1243
|
+
|
|
1244
|
+
function load(append) {
|
|
1245
|
+
if (busy) return;
|
|
1246
|
+
setBusy(true);
|
|
1247
|
+
var offset = append ? items.length : 0;
|
|
1248
|
+
fetch('/iris/api/artifacts?offset=' + offset + '&limit=24')
|
|
1249
|
+
.then(function (res) { return res.ok ? res.json() : Promise.reject(new Error('读取失败')); })
|
|
1250
|
+
.then(function (data) {
|
|
1251
|
+
setItems(append ? items.concat(data.items || []) : (data.items || []));
|
|
1252
|
+
setTotal(Number(data.total || 0));
|
|
1253
|
+
})
|
|
1254
|
+
.catch(function (error) { setNote('作品库读取失败:' + error.message); })
|
|
1255
|
+
.finally(function () { setBusy(false); });
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function run(action, body, confirmText) {
|
|
1259
|
+
if (confirmText && !window.confirm(confirmText)) return;
|
|
1260
|
+
setBusy(true);
|
|
1261
|
+
postAction(action, body || {}).then(function (result) {
|
|
1262
|
+
var succeeded = result.ok && result.d && result.d.ok !== false;
|
|
1263
|
+
setNote(succeeded ? (result.d.text || '完成') : ('失败:' + ((result.d && (result.d.error || result.d.text)) || '未知错误')));
|
|
1264
|
+
if (succeeded) load(false);
|
|
1265
|
+
else setBusy(false);
|
|
1266
|
+
}).catch(function (error) { setBusy(false); setNote('失败:' + error.message); });
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
React.useEffect(function () { load(false); }, [props.total]);
|
|
1270
|
+
|
|
1271
|
+
return React.createElement('div', {},
|
|
1272
|
+
React.createElement('div', { className: 'iris-gallery-tools' },
|
|
1273
|
+
React.createElement('span', { className: 'iris-clean-note' }, '共 ' + total + ' 个作品 · 清任务历史不会删除'),
|
|
1274
|
+
React.createElement('button', { className: 'iris-clean-btn', disabled: busy, onClick: function () { run('artifacts_reindex'); } }, '找回本地作品'),
|
|
1275
|
+
total ? React.createElement('button', { className: 'iris-clean-btn danger', disabled: busy, onClick: function () { run('artifacts_clear', {}, '永久删除作品库中的全部媒体文件?任务历史会保留,但相关媒体将无法打开。此操作不可逆。'); } }, '清空作品库') : null),
|
|
1276
|
+
items.length ? React.createElement('div', { className: 'iris-gallery-grid' }, items.map(function (item) {
|
|
1277
|
+
return React.createElement('div', { key: item.id, className: 'iris-gallery-card' },
|
|
1278
|
+
artifactPreview(item),
|
|
1279
|
+
React.createElement('div', { className: 'iris-gallery-info' },
|
|
1280
|
+
React.createElement('div', { className: 'iris-gallery-file', title: item.file }, item.file),
|
|
1281
|
+
React.createElement('div', { className: 'iris-gallery-meta' }, fmtTime(item.createdAt) + ' · ' + fmtBytes(item.size)),
|
|
1282
|
+
React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1283
|
+
React.createElement('a', { className: 'iris-wb-link', href: item.url, target: '_blank', rel: 'noreferrer' }, '打开'),
|
|
1284
|
+
React.createElement('button', { className: 'iris-pm-btn danger', disabled: busy, onClick: function () { run('artifacts_delete', { artifact_id: item.id }, '永久删除作品“' + item.file + '”?任务历史会保留,但媒体将无法打开。'); } }, '删除'))));
|
|
1285
|
+
})) : React.createElement('div', { className: 'iris-wb-empty' }, busy ? '正在读取作品…' : '尚无作品;如 outputs/ 中已有文件,可点“找回本地作品”。'),
|
|
1286
|
+
items.length < total ? React.createElement('button', { className: 'iris-clean-btn', disabled: busy, onClick: function () { load(true); } }, busy ? '加载中…' : '加载更多') : null,
|
|
1287
|
+
note ? React.createElement('div', { className: 'iris-clean-note', style: { marginTop: '7px' } }, note) : null);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
973
1290
|
/* ---- 泡泡常用卡片选择器(设置页:多项勾选 → 出现在悬浮窗「常用」标签) ---- */
|
|
974
1291
|
function BubbleCardPicker() {
|
|
975
1292
|
var pair = useBubbleCards();
|
|
@@ -1057,10 +1374,60 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1057
1374
|
note ? React.createElement('div', { className: 'iris-clean-note' }, note) : null);
|
|
1058
1375
|
}
|
|
1059
1376
|
|
|
1377
|
+
function HostDoctorPanel() {
|
|
1378
|
+
var pair = React.useState(null);
|
|
1379
|
+
var report = pair[0];
|
|
1380
|
+
var setReport = pair[1];
|
|
1381
|
+
var busyPair = React.useState(false);
|
|
1382
|
+
var busy = busyPair[0];
|
|
1383
|
+
var setBusy = busyPair[1];
|
|
1384
|
+
var errorPair = React.useState('');
|
|
1385
|
+
var error = errorPair[0];
|
|
1386
|
+
var setError = errorPair[1];
|
|
1387
|
+
|
|
1388
|
+
function load() {
|
|
1389
|
+
setBusy(true);
|
|
1390
|
+
setError('');
|
|
1391
|
+
fetch('/iris/api/doctor')
|
|
1392
|
+
.then(function (res) { return res.json().then(function (data) { if (!res.ok) throw new Error(data.error || '诊断失败'); return data; }); })
|
|
1393
|
+
.then(function (data) { setReport(data); })
|
|
1394
|
+
.catch(function (err) { setError(String(err && err.message || err)); })
|
|
1395
|
+
.finally(function () { setBusy(false); });
|
|
1396
|
+
}
|
|
1397
|
+
React.useEffect(function () { load(); }, []);
|
|
1398
|
+
|
|
1399
|
+
var checks = report && Array.isArray(report.checks) ? report.checks : [];
|
|
1400
|
+
var issues = checks.filter(function (item) { return item.status !== 'ok'; });
|
|
1401
|
+
var mark = { ok: '✓', warn: '!', error: '×' };
|
|
1402
|
+
function rows(list) {
|
|
1403
|
+
return React.createElement('div', { className: 'iris-doctor-list' }, list.map(function (item) {
|
|
1404
|
+
return React.createElement('div', { key: item.id, className: 'iris-doctor-row ' + item.status },
|
|
1405
|
+
React.createElement('span', {}, mark[item.status] || '·'),
|
|
1406
|
+
React.createElement('span', {}, item.summary));
|
|
1407
|
+
}));
|
|
1408
|
+
}
|
|
1409
|
+
var score = report
|
|
1410
|
+
? report.summary.errors + ' 错误 · ' + report.summary.warnings + ' 警告'
|
|
1411
|
+
: (busy ? '正在检查宿主…' : '尚未运行');
|
|
1412
|
+
return React.createElement('div', { className: 'iris-wb-card' },
|
|
1413
|
+
React.createElement('div', { className: 'iris-doctor-top' },
|
|
1414
|
+
React.createElement('span', { className: 'iris-doctor-score' }, score),
|
|
1415
|
+
report && report.host ? React.createElement('span', { className: 'iris-wb-muted' }, 'DSH ' + report.host.version) : null,
|
|
1416
|
+
React.createElement('button', { className: 'iris-pm-btn', disabled: busy, onClick: load }, busy ? '检查中…' : '刷新')),
|
|
1417
|
+
error ? React.createElement('div', { className: 'iris-doctor-row error' }, React.createElement('span', {}, '×'), React.createElement('span', {}, error)) : null,
|
|
1418
|
+
issues.length ? rows(issues) : (report ? React.createElement('div', { className: 'iris-wb-muted' }, '当前可观察项全部健康') : null),
|
|
1419
|
+
checks.length ? React.createElement('details', { className: 'iris-doctor-details' },
|
|
1420
|
+
React.createElement('summary', {}, '查看全部 ' + checks.length + ' 项'),
|
|
1421
|
+
rows(checks)) : null,
|
|
1422
|
+
React.createElement('div', { className: 'iris-wb-muted' }, '只读本机注册证据;不调用 Browser、模型或供应商。'));
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1060
1425
|
function WorkbenchPanel() {
|
|
1061
1426
|
var state = useIrisState(5000);
|
|
1062
1427
|
var running = (state && state.tasks && state.tasks.running) || [];
|
|
1063
1428
|
var recent = (state && state.tasks && state.tasks.recent) || [];
|
|
1429
|
+
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
1430
|
+
var artifactTotal = (state && state.artifacts && state.artifacts.total) || 0;
|
|
1064
1431
|
var selPair = React.useState(null);
|
|
1065
1432
|
var selectedTask = selPair[0];
|
|
1066
1433
|
var setSelectedTask = selPair[1];
|
|
@@ -1071,16 +1438,27 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1071
1438
|
React.createElement('span', { className: 'iris-wb-date' }, state ? '刷新于 ' + new Date().toLocaleTimeString() : '加载中…')),
|
|
1072
1439
|
React.createElement('div', { className: 'iris-wb-sec' }, '供应商'),
|
|
1073
1440
|
React.createElement(ProviderManager, {}),
|
|
1441
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '能力健康'),
|
|
1442
|
+
React.createElement(CapabilityHealthOverview, {}),
|
|
1443
|
+
React.createElement('div', { className: 'iris-wb-muted' }, '绿色有效 7 天;真实成功或显式实测会刷新。不会后台探测,也不会因 429、网络或供应商 5xx 变红。'),
|
|
1074
1444
|
React.createElement('div', { className: 'iris-wb-sec' }, '能力分配(failover 顺序)'),
|
|
1075
1445
|
React.createElement(CapabilityAssigner, {}),
|
|
1076
1446
|
React.createElement('div', { className: 'iris-wb-sec' }, 'Iris 泡泡快捷卡片(勾选后显示在「⚡ 常用」标签)'),
|
|
1077
1447
|
React.createElement(BubbleCardPicker, {}),
|
|
1078
1448
|
React.createElement('div', { className: 'iris-wb-sec' }, '对话框提示词优化'),
|
|
1079
1449
|
React.createElement(PromptOptimizerSettings, {}),
|
|
1450
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '宿主诊断'),
|
|
1451
|
+
React.createElement(HostDoctorPanel, {}),
|
|
1080
1452
|
React.createElement('div', { className: 'iris-wb-sec' }, '运行中任务'),
|
|
1081
1453
|
React.createElement('div', { className: 'iris-wb-box' },
|
|
1082
1454
|
running.length ? running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); })
|
|
1083
1455
|
: React.createElement('div', { className: 'iris-wb-empty' }, '暂无进行中任务')),
|
|
1456
|
+
attention.length ? React.createElement('div', {},
|
|
1457
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '需要处理'),
|
|
1458
|
+
React.createElement('div', { className: 'iris-wb-box' },
|
|
1459
|
+
attention.map(function (t) { return taskRow(t, false, selectedTask === t.id, onSelect); }))) : null,
|
|
1460
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '作品库'),
|
|
1461
|
+
React.createElement(ArtifactGallery, { total: artifactTotal }),
|
|
1084
1462
|
React.createElement('div', { className: 'iris-wb-sec' }, '历史任务'),
|
|
1085
1463
|
React.createElement(CleanupBar, {}),
|
|
1086
1464
|
React.createElement(HistoryBrowser, { recent: recent, selected: selectedTask, onSelect: onSelect }),
|
|
@@ -1093,7 +1471,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1093
1471
|
var state = useIrisState(5000);
|
|
1094
1472
|
var running = (state && state.tasks && state.tasks.running) || [];
|
|
1095
1473
|
var recent = (state && state.tasks && state.tasks.recent) || [];
|
|
1474
|
+
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
1096
1475
|
var recentTotal = (state && state.tasks && state.tasks.recentTotal) || recent.length;
|
|
1476
|
+
var works = (state && state.artifacts && state.artifacts.recent) || [];
|
|
1477
|
+
var artifactTotal = (state && state.artifacts && state.artifacts.total) || works.length;
|
|
1097
1478
|
var cards = useBubbleCards()[0];
|
|
1098
1479
|
var tabPair = React.useState('tasks');
|
|
1099
1480
|
var tab = tabPair[0];
|
|
@@ -1104,21 +1485,20 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1104
1485
|
function onSelect(id) { setSelectedTask(id); }
|
|
1105
1486
|
var body;
|
|
1106
1487
|
if (tab === 'tasks') {
|
|
1107
|
-
//
|
|
1108
|
-
var succeeded = recent.filter(function (t) {
|
|
1109
|
-
return t.status === 'succeeded' && Array.isArray(t.media) && t.media.length > 0;
|
|
1110
|
-
});
|
|
1111
|
-
var shown = succeeded.slice(0, 6);
|
|
1488
|
+
// 快捷面板承载当前任务、需要处理的异常事实与独立作品库中的最近作品。
|
|
1112
1489
|
body = React.createElement('div', { className: 'iris-wb-box' },
|
|
1113
1490
|
running.length ? React.createElement('div', {},
|
|
1114
1491
|
React.createElement('div', { className: 'iris-wb-sec' }, '运行中'),
|
|
1115
1492
|
running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); })) : null,
|
|
1116
|
-
React.createElement('div', {
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1493
|
+
attention.length ? React.createElement('div', {},
|
|
1494
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '需要处理'),
|
|
1495
|
+
attention.slice(0, 4).map(function (t) { return taskRowMini(t, selectedTask === t.id, onSelect); })) : null,
|
|
1496
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '最近作品'),
|
|
1497
|
+
works.length ? works.map(artifactRowMini)
|
|
1498
|
+
: React.createElement('div', { className: 'iris-wb-empty' }, '尚无作品'),
|
|
1499
|
+
artifactTotal > works.length || recentTotal > 0
|
|
1120
1500
|
? React.createElement('div', { className: 'iris-bubble-more', style: { cursor: 'default' } },
|
|
1121
|
-
'
|
|
1501
|
+
'完整作品库、失败与取消历史请到 Iris 工作台查看') : null);
|
|
1122
1502
|
} else {
|
|
1123
1503
|
var defs = CARD_DEFS.filter(function (d) { return cards.indexOf(d.action) >= 0; });
|
|
1124
1504
|
body = defs.length
|
|
@@ -1377,8 +1757,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1377
1757
|
function FloatingBubble() {
|
|
1378
1758
|
var state = useIrisState(5000);
|
|
1379
1759
|
var providers = (state && state.providers) || [];
|
|
1380
|
-
var
|
|
1760
|
+
var fallbackConfigured = providers.some(function (p) { return p.enabled && p.apiKeyHint; });
|
|
1761
|
+
var overallHealth = state && state.health && HEALTH_META[state.health.overall]
|
|
1762
|
+
? state.health.overall : (fallbackConfigured ? 'configured' : 'unconfigured');
|
|
1381
1763
|
var running = (state && state.tasks && state.tasks.running) || [];
|
|
1764
|
+
var attention = (state && state.tasks && state.tasks.attention) || [];
|
|
1382
1765
|
|
|
1383
1766
|
var posPair = React.useState(function () {
|
|
1384
1767
|
var saved = null;
|
|
@@ -1444,17 +1827,30 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1444
1827
|
}
|
|
1445
1828
|
}
|
|
1446
1829
|
|
|
1830
|
+
function persistCurrentPosition() {
|
|
1831
|
+
setPos(function (current) {
|
|
1832
|
+
if (current && current.x != null && current.y != null) {
|
|
1833
|
+
try { localStorage.setItem('iris-bubble-pos', JSON.stringify(current)); } catch (_) {}
|
|
1834
|
+
}
|
|
1835
|
+
return current;
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1447
1839
|
function onPointerUp(e) {
|
|
1448
1840
|
var drag = dragRef.current;
|
|
1449
1841
|
if (!drag) return;
|
|
1450
1842
|
try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) {}
|
|
1451
1843
|
var moved = drag.moved;
|
|
1452
1844
|
dragRef.current = null;
|
|
1453
|
-
if (moved)
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1845
|
+
if (moved) persistCurrentPosition();
|
|
1846
|
+
else setOpen(!open);
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
function onPointerCancel(e) {
|
|
1850
|
+
if (!dragRef.current) return;
|
|
1851
|
+
try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) {}
|
|
1852
|
+
dragRef.current = null;
|
|
1853
|
+
persistCurrentPosition();
|
|
1458
1854
|
}
|
|
1459
1855
|
|
|
1460
1856
|
var style;
|
|
@@ -1464,15 +1860,18 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1464
1860
|
style = { right: DEFAULT_X + 'px', bottom: DEFAULT_Y + 'px' };
|
|
1465
1861
|
}
|
|
1466
1862
|
|
|
1467
|
-
var
|
|
1468
|
-
|
|
1863
|
+
var badgeCount = running.length + attention.length;
|
|
1864
|
+
var badge = badgeCount
|
|
1865
|
+
? React.createElement('span', { className: 'iris-bubble-badge', title: attention.length ? attention.length + ' 个任务需要处理' : '' }, badgeCount)
|
|
1469
1866
|
: null;
|
|
1470
1867
|
|
|
1471
1868
|
var panel = null;
|
|
1472
1869
|
if (open) {
|
|
1473
1870
|
var xy = bubbleXY();
|
|
1474
|
-
var
|
|
1475
|
-
var
|
|
1871
|
+
var panelWidth = Math.min(360, Math.max(0, window.innerWidth - 16));
|
|
1872
|
+
var panelHeight = Math.min(520, Math.max(0, window.innerHeight - 16));
|
|
1873
|
+
var panelLeft = Math.max(8, Math.min(xy.x - panelWidth + 30, window.innerWidth - panelWidth - 8));
|
|
1874
|
+
var panelTop = Math.max(8, Math.min(xy.y - 260, window.innerHeight - panelHeight - 8));
|
|
1476
1875
|
panel = React.createElement('div', { className: 'iris-bubble-panel', style: { left: panelLeft + 'px', top: panelTop + 'px' } },
|
|
1477
1876
|
React.createElement('div', { className: 'iris-bubble-panel-head' },
|
|
1478
1877
|
React.createElement('button', { className: 'iris-bubble-close', onClick: function () { setOpen(false); } }, '✕')),
|
|
@@ -1480,52 +1879,63 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1480
1879
|
}
|
|
1481
1880
|
|
|
1482
1881
|
return React.createElement('div', {
|
|
1483
|
-
className: 'iris-bubble' +
|
|
1882
|
+
className: 'iris-bubble health-' + overallHealth + (dragRef.current ? ' dragging' : ''),
|
|
1484
1883
|
style: style,
|
|
1485
1884
|
onPointerDown: onPointerDown,
|
|
1486
1885
|
onPointerMove: onPointerMove,
|
|
1487
1886
|
onPointerUp: onPointerUp,
|
|
1488
|
-
|
|
1887
|
+
onPointerCancel: onPointerCancel,
|
|
1888
|
+
title: 'Iris ' + healthText({ status: overallHealth }) + ':点击打开快捷面板'
|
|
1489
1889
|
},
|
|
1490
1890
|
React.createElement('div', { className: 'iris-bubble-btn' }, '🫧'),
|
|
1491
1891
|
badge,
|
|
1492
1892
|
panel);
|
|
1493
1893
|
}
|
|
1494
1894
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1895
|
+
var clientSlots = clientSlotsPort(ctx);
|
|
1896
|
+
|
|
1897
|
+
clientSlots.inject('settings.section', function () {
|
|
1898
|
+
var dispose = clientSlots.register({
|
|
1497
1899
|
name: 'settings.section',
|
|
1498
1900
|
id: 'iris-workbench',
|
|
1499
1901
|
order: 140,
|
|
1500
1902
|
label: function () { return 'Iris 工作台'; }
|
|
1501
1903
|
}, WorkbenchPanel);
|
|
1904
|
+
reportClientSeat('settings.section');
|
|
1905
|
+
return dispose;
|
|
1502
1906
|
});
|
|
1503
1907
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1908
|
+
clientSlots.inject('conversation.input.right', function () {
|
|
1909
|
+
var dispose = clientSlots.register({
|
|
1506
1910
|
name: 'conversation.input.right',
|
|
1507
1911
|
id: 'iris-prompt-optimizer',
|
|
1508
1912
|
order: 40,
|
|
1509
1913
|
label: function () { return 'Iris 提示词优化'; }
|
|
1510
1914
|
}, PromptOptimizerControl);
|
|
1915
|
+
reportClientSeat('conversation.input.right');
|
|
1916
|
+
return dispose;
|
|
1511
1917
|
});
|
|
1512
1918
|
|
|
1513
|
-
|
|
1514
|
-
|
|
1919
|
+
clientSlots.inject('conversation.input.dock', function () {
|
|
1920
|
+
var dispose = clientSlots.register({
|
|
1515
1921
|
name: 'conversation.input.dock',
|
|
1516
1922
|
id: 'iris-progress',
|
|
1517
1923
|
order: 0,
|
|
1518
1924
|
label: function () { return 'Iris 任务进度'; }
|
|
1519
1925
|
}, ProgressDock);
|
|
1926
|
+
reportClientSeat('conversation.input.dock');
|
|
1927
|
+
return dispose;
|
|
1520
1928
|
});
|
|
1521
1929
|
|
|
1522
|
-
|
|
1523
|
-
|
|
1930
|
+
clientSlots.inject('shell.overlay', function () {
|
|
1931
|
+
var dispose = clientSlots.register({
|
|
1524
1932
|
name: 'shell.overlay',
|
|
1525
1933
|
id: 'iris-bubble',
|
|
1526
1934
|
order: 200,
|
|
1527
1935
|
label: function () { return 'Iris 泡泡'; }
|
|
1528
1936
|
}, FloatingBubble);
|
|
1937
|
+
reportClientSeat('shell.overlay');
|
|
1938
|
+
return dispose;
|
|
1529
1939
|
});
|
|
1530
1940
|
console.log('[iris] client slots registered');
|
|
1531
1941
|
} catch (err) {
|