@mokuyoaxis/dsh-iris 0.2.5 → 0.2.7
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-use/SKILL.md +2 -2
- package/.dsh/skills/iris-use/references/media-contract.md +2 -2
- package/CHANGELOG.md +49 -1
- package/README.en.md +39 -22
- package/README.md +40 -21
- package/bin/dsh-iris.js +166 -35
- package/docs/AGENT_QUICKSTART.md +7 -7
- package/docs/ARCHITECTURE.md +9 -3
- package/docs/CLI_MANAGEMENT.md +5 -1
- package/docs/CORE_RUNTIME_CONTRACT.md +36 -5
- package/docs/CORE_SKILLS.md +62 -0
- package/docs/DSH_COMPATIBILITY.md +8 -4
- package/docs/DSH_CORE_MIGRATION.md +4 -2
- package/docs/DSH_PROVIDER_CONNECTIONS.md +34 -0
- package/docs/DSH_RUNTIME.md +36 -0
- package/docs/FUNCTION_MODEL_ASSIGNMENTS.md +2 -0
- package/docs/FUNCTION_MODEL_POOLS.md +84 -0
- package/docs/HEADLESS_CLI.md +16 -8
- package/docs/HOST_ADAPTER_CONTRACT.md +7 -2
- package/docs/HOST_DOCTOR.md +14 -2
- package/docs/IMAGE_FLOW.md +71 -0
- package/docs/LOCAL_API.md +60 -0
- package/docs/LOCAL_API_CONTRACT.md +115 -0
- package/docs/LOCAL_CREATION.md +50 -0
- package/docs/MUSIC.md +2 -2
- package/docs/PROVIDER_HEALTH.md +26 -2
- package/docs/ROADMAP.md +18 -4
- package/docs/SECURITY.md +20 -2
- package/docs/SYNC_DELIVERY_RECOVERY.md +2 -0
- package/docs/VISION_CLI.md +2 -2
- package/docs/WEB_PROTOTYPE.md +47 -0
- package/docs/releases/0.2.6.md +50 -0
- package/docs/releases/0.2.7.md +62 -0
- package/lib/actions.js +208 -90
- package/lib/adapters.js +36 -9
- package/lib/api.js +40 -39
- package/lib/application-runtime.js +186 -0
- package/lib/client.js +805 -182
- package/lib/composite-vision-routing.js +6 -6
- package/lib/config.js +140 -29
- package/lib/core-artifact-store.js +5 -0
- package/lib/core-maintenance.js +25 -2
- package/lib/core-runtime.js +19 -0
- package/lib/core-tasks.js +62 -8
- package/lib/creation-request-store.js +159 -0
- package/lib/doctor.js +20 -2
- package/lib/dsh-core-adapter.js +669 -564
- package/lib/dsh-host-adapter.js +14 -1
- package/lib/dsh-provider-config.js +35 -0
- package/lib/dsh-provider-source.js +102 -0
- package/lib/flow-store.js +195 -0
- package/lib/headless-vision.js +5 -5
- package/lib/host-contract.js +1 -0
- package/lib/http-vision-model-adapter.js +5 -2
- package/lib/image-flow.js +188 -0
- package/lib/index.js +103 -78
- package/lib/local-api-projection.js +27 -0
- package/lib/local-api.js +381 -0
- package/lib/local-creation.js +85 -0
- package/lib/model-directory-service.js +53 -0
- package/lib/model-directory.js +103 -0
- package/lib/models.js +71 -6
- package/lib/ocr-model-routing.js +2 -2
- package/lib/private-storage.js +12 -0
- package/lib/provider-adapters.js +42 -9
- package/lib/provider-catalog.js +14 -9
- package/lib/provider-config-scope.js +16 -0
- package/lib/provider-config-service.js +82 -13
- package/lib/provider-health.js +33 -0
- package/lib/provider-protocol.js +30 -0
- package/lib/provider-task-runner.js +22 -2
- package/lib/render-address.js +35 -0
- package/lib/render.js +52 -13
- package/lib/skill-installation.js +199 -0
- package/lib/vision-model-routing.js +25 -12
- package/lib/workbench-artifacts.js +1 -2
- package/lib/workbench-management.js +8 -9
- package/package.json +4 -3
- package/skills/iris-core/SKILL.md +40 -0
- package/skills/iris-core/agents/openai.yaml +4 -0
- package/skills/iris-core/references/cli.md +95 -0
- package/skills/iris-core/references/tasks.md +58 -0
- package/user_guide.md +32 -20
package/lib/client.js
CHANGED
|
@@ -21,7 +21,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
21
21
|
var exports = module.exports;
|
|
22
22
|
|
|
23
23
|
var React = require('react');
|
|
24
|
-
var IRIS_CLIENT_VERSION = '0.2.
|
|
24
|
+
var IRIS_CLIENT_VERSION = '0.2.7';
|
|
25
25
|
var HOST_CLIENT_PROTOCOL_VERSION = 0;
|
|
26
26
|
var reportedSeats = [];
|
|
27
27
|
|
|
@@ -81,6 +81,49 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
81
81
|
'.iris-wb-link:hover { text-decoration: underline; }',
|
|
82
82
|
'.iris-wb-muted { color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 12px; }',
|
|
83
83
|
'.iris-wb-empty { color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 12px; padding: 6px 2px; }',
|
|
84
|
+
/* 工作台页面共用视觉规则;不改变对话泡泡和 composer 的布局。 */
|
|
85
|
+
'.iris-workbench-page { --iris-workbench-accent: var(--dsw-alias-brand-primary, #8c9eff); display: grid; gap: 10px; }',
|
|
86
|
+
'.iris-workbench-page *, .iris-workbench-page *::before { box-sizing: border-box; }',
|
|
87
|
+
'.iris-workbench-page [hidden], .iris-health [hidden] { display: none !important; }',
|
|
88
|
+
'.iris-workbench-page > .iris-wb-head { justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; margin: 2px 0 4px; }',
|
|
89
|
+
'.iris-workbench-page > .iris-wb-head .iris-wb-title { font-size: 20px; font-weight: 650; letter-spacing: -.03em; }',
|
|
90
|
+
'.iris-wb-section { min-width: 0; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 12px; background: var(--dsw-alias-bg-layer-1, #26262e); }',
|
|
91
|
+
'.iris-wb-section-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 14px; list-style: none; }',
|
|
92
|
+
'.iris-wb-section-head::-webkit-details-marker { display: none; }',
|
|
93
|
+
'.iris-wb-section-head h2 { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: -.01em; }',
|
|
94
|
+
'.iris-wb-section-head p { margin: 4px 0 0; font-size: 12px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
95
|
+
'details.iris-wb-section > summary { cursor: pointer; border-radius: inherit; }',
|
|
96
|
+
'.iris-wb-section-arrow { flex-shrink: 0; display: inline-flex; gap: 7px; align-items: center; font-size: 11px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
97
|
+
'.iris-wb-section-arrow svg { transition: transform .15s; }',
|
|
98
|
+
'details.iris-wb-section[open] > summary .iris-wb-section-arrow svg { transform: rotate(180deg); }',
|
|
99
|
+
'.iris-wb-section-body { min-width: 0; padding: 0 14px 14px; display: grid; gap: 8px; }',
|
|
100
|
+
'.iris-workbench-page .iris-wb-sec { margin: 4px 0 0; font-size: 13px; color: var(--dsw-alias-label-primary, #e6e6e6); }',
|
|
101
|
+
'.iris-workbench-page .iris-pm, .iris-workbench-page .iris-cap-grid, .iris-workbench-page .iris-act-group { gap: 8px; }',
|
|
102
|
+
'.iris-workbench-page .iris-pm-card, .iris-workbench-page .iris-wb-card, .iris-workbench-page .iris-act-card, .iris-workbench-page .iris-cap-row { border-radius: 10px; }',
|
|
103
|
+
'.iris-workbench-page .iris-pm-card, .iris-workbench-page .iris-cap-row { padding: 10px; }',
|
|
104
|
+
'.iris-workbench-page .iris-pm-card { gap: 8px; }',
|
|
105
|
+
'.iris-workbench-page .iris-pm-body { display: grid; gap: 8px; padding-top: 10px; border-top: 1px solid var(--dsw-alias-border-l1, #3a3a44); }',
|
|
106
|
+
'.iris-workbench-page .iris-pm-field { gap: 5px; font-size: 12px; }',
|
|
107
|
+
'.iris-workbench-page :is(.iris-pm-btn, .iris-pm-add, .iris-ff-btn, .iris-act-run, .iris-clean-btn) { min-height: 32px; padding: 5px 9px; border-radius: 8px; font-family: inherit; font-size: 12px; }',
|
|
108
|
+
'.iris-workbench-page :is(button, select, input):focus-visible, .iris-wb-section-head:focus-visible { outline: 2px solid var(--iris-workbench-accent, #8c9eff); outline-offset: 3px; }',
|
|
109
|
+
'.iris-workbench-page :is(input, textarea, select) { font-family: inherit; accent-color: var(--iris-workbench-accent); }',
|
|
110
|
+
'.iris-workbench-page input:not([type=checkbox]):not([type=radio]):not([type=file]), .iris-workbench-page textarea, .iris-workbench-page select { min-width: 0; max-width: 100%; min-height: 34px; padding: 5px 8px; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 8px; background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-primary, #e6e6e6); font-size: 12px; }',
|
|
111
|
+
'.iris-workbench-page .iris-pm-note { line-height: 1.65; }',
|
|
112
|
+
'.iris-workbench-page .iris-pm-mrows { max-height: 400px; gap: 8px; }',
|
|
113
|
+
'.iris-workbench-page .iris-pm-mrow { min-width: 0; padding: 10px; gap: 6px; flex-wrap: wrap; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 10px; }',
|
|
114
|
+
'.iris-workbench-page .iris-pm-mname { flex-basis: 100%; white-space: normal; overflow-wrap: anywhere; font-size: 13px; }',
|
|
115
|
+
'.iris-workbench-page .iris-pm-mrow > label { display: flex; align-items: center; flex: 1 1 230px; flex-wrap: wrap; gap: 6px; }',
|
|
116
|
+
'.iris-workbench-page .iris-pm-mrow > label select { flex: 1; }',
|
|
117
|
+
'.iris-workbench-page .iris-cap-row { gap: 7px; }',
|
|
118
|
+
'.iris-workbench-page .iris-cap-row > select:first-of-type { margin-left: auto; }',
|
|
119
|
+
'.iris-workbench-page .iris-cap-row > .iris-pm-note, .iris-workbench-page .iris-cap-row > .iris-cap-feedback { flex-basis: 100%; }',
|
|
120
|
+
'.iris-workbench-page .iris-cap-row > .iris-pm-model-sel { margin-left: 0; }',
|
|
121
|
+
'.iris-workbench-page .iris-cap-chip { padding: 3px 7px; border-radius: 8px; overflow-wrap: anywhere; }',
|
|
122
|
+
'.iris-workbench-page .iris-cap-confirm { padding: 12px; border-radius: 10px; }',
|
|
123
|
+
'.iris-workbench-page .iris-act-head { min-height: 40px; padding: 9px 12px; }',
|
|
124
|
+
'.iris-workbench-page .iris-act-body { padding: 10px 12px 12px; gap: 8px; }',
|
|
125
|
+
'.iris-workbench-page .iris-doctor-row { font-size: 12px; line-height: 1.6; }',
|
|
126
|
+
'@media (max-width: 640px) { .iris-wb-section-head { padding: 12px; } .iris-wb-section-body { padding: 0 12px 12px; } .iris-workbench-page :is(.iris-pm-btn, .iris-pm-add, .iris-ff-btn, .iris-act-run, .iris-clean-btn) { min-height: 40px; } .iris-workbench-page input:not([type=checkbox]):not([type=radio]):not([type=file]), .iris-workbench-page textarea, .iris-workbench-page select { min-height: 36px; } }',
|
|
84
127
|
'.iris-progress-dock { display: grid; gap: 6px; }',
|
|
85
128
|
'.iris-progress-row { font-size: 12px; display: flex; gap: 8px; align-items: center; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
86
129
|
'.iris-wb-card { cursor: pointer; }',
|
|
@@ -97,11 +140,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
97
140
|
'.iris-doctor-score { font-weight: 600; flex: 1; }',
|
|
98
141
|
'.iris-doctor-list { display: grid; gap: 4px; margin-top: 7px; }',
|
|
99
142
|
'.iris-doctor-row { display: grid; grid-template-columns: 14px 1fr; gap: 6px; font-size: 11px; }',
|
|
143
|
+
'.iris-doctor-row > span:last-child { min-width: 0; overflow-wrap: anywhere; }',
|
|
100
144
|
'.iris-doctor-row.ok { color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
101
145
|
'.iris-doctor-row.warn { color: var(--dsw-alias-state-warn-primary, #d9a941); }',
|
|
102
146
|
'.iris-doctor-row.error { color: var(--dsw-alias-state-error-primary, #e06c6c); }',
|
|
103
147
|
'.iris-doctor-details { margin-top: 7px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
104
148
|
'.iris-doctor-details summary { cursor: pointer; font-size: 11px; }',
|
|
149
|
+
'.iris-browser-check { border-top: 1px solid var(--dsw-alias-border-l1, #ffffff12); margin-top: 10px; padding-top: 9px; min-width: 0; }',
|
|
150
|
+
'.iris-browser-check .iris-doctor-top { margin-bottom: 5px; }',
|
|
151
|
+
'.iris-browser-check img { display: block; width: 100%; max-height: 200px; object-fit: contain; margin-top: 7px; border-radius: 7px; background: var(--dsw-alias-bg-layer-2, #ffffff05); }',
|
|
152
|
+
'.iris-browser-check-note { font-size: 12px; line-height: 1.6; overflow-wrap: anywhere; }',
|
|
153
|
+
'.iris-browser-check-note.error { color: var(--dsw-alias-state-error-primary, #e06c6c); }',
|
|
105
154
|
'.iris-core-runtime { cursor: default; padding: 0; overflow: hidden; }',
|
|
106
155
|
'.iris-core-runtime-head { display: flex; align-items: center; gap: 8px; padding: 9px 10px; }',
|
|
107
156
|
'.iris-core-runtime-title { min-width: 0; flex: 1; display: grid; gap: 1px; }',
|
|
@@ -157,7 +206,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
157
206
|
'.iris-pm-add:hover { border-color: var(--dsw-alias-brand-primary, #7aa2f7); }',
|
|
158
207
|
'.iris-pm-card { background: var(--dsw-alias-bg-layer-1, #26262e); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 8px; padding: 8px 10px; display: grid; gap: 6px; }',
|
|
159
208
|
'.iris-pm-card.open { border-color: var(--dsw-alias-brand-primary, #7aa2f7); }',
|
|
160
|
-
'.iris-pm-top { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; cursor: pointer; }',
|
|
209
|
+
'.iris-pm-top { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; width: 100%; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }',
|
|
161
210
|
'.iris-pm-name { font-weight: 600; font-size: 13px; flex: 1; }',
|
|
162
211
|
'.iris-pm-btn { background: transparent; color: var(--dsw-alias-label-secondary, #9a9a9a); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 4px; font-size: 11px; padding: 2px 7px; cursor: pointer; }',
|
|
163
212
|
'.iris-pm-btn:hover { color: var(--dsw-alias-label-primary, #e6e6e6); border-color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
@@ -168,25 +217,82 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
168
217
|
'.iris-pm-cap { color: var(--dsw-alias-brand-primary, #7aa2f7); font-size: 10px; }',
|
|
169
218
|
'.iris-pm-note { font-size: 11px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
170
219
|
'.iris-pm-field { display: grid; gap: 3px; }',
|
|
220
|
+
'details[data-provider-directory] { min-width: 0; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 8px; }',
|
|
221
|
+
'details[data-provider-directory] > summary { cursor: pointer; overflow-wrap: anywhere; }',
|
|
222
|
+
'details[data-provider-directory] p { margin: 6px 0; overflow-wrap: anywhere; }',
|
|
223
|
+
'details[data-provider-directory] > summary:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #6b78e8); outline-offset: 3px; }',
|
|
171
224
|
'.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; }',
|
|
172
225
|
'.iris-pm-act { font-size: 11px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
173
226
|
'.iris-pm-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #73737d); margin-right: 4px; }',
|
|
174
227
|
'.iris-pm-dot.configured { background: #6f9df6; box-shadow: 0 0 7px rgba(111,157,246,.55); }',
|
|
175
228
|
'.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
229
|
'.iris-pm-dot.failed { background: #b9656d; box-shadow: 0 0 6px rgba(185,101,109,.38); opacity: .82; }',
|
|
177
|
-
'.iris-health
|
|
178
|
-
'.iris-health
|
|
179
|
-
'.iris-health-
|
|
180
|
-
'.iris-health-
|
|
181
|
-
'.iris-health-
|
|
182
|
-
'.iris-health-
|
|
183
|
-
'.iris-health-
|
|
230
|
+
'.iris-health { --iris-health-accent: var(--dsw-alias-brand-primary, #8c9eff); container-type: inline-size; padding: 0; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 12px; background: var(--dsw-alias-bg-layer-1, #26262e); }',
|
|
231
|
+
'.iris-health *, .iris-health *::before { box-sizing: border-box; }',
|
|
232
|
+
'.iris-health-head, .iris-health-title-line, .iris-health-card-top, .iris-health-card-foot, .iris-health-detail-head, .iris-health-model-head, .iris-health-confirm-actions { display: flex; align-items: center; gap: 10px; }',
|
|
233
|
+
'.iris-health-head { justify-content: space-between; flex-wrap: wrap; padding: 10px 14px; }',
|
|
234
|
+
'.iris-health-content { padding: 0 14px 14px; }',
|
|
235
|
+
'.iris-health-toolbar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }',
|
|
236
|
+
'.iris-health-eyebrow { color: var(--iris-health-accent); font-size: 10px; font-weight: 600; letter-spacing: .14em; margin-bottom: 6px; }',
|
|
237
|
+
'.iris-health h2, .iris-health h3, .iris-health p { margin: 0; }',
|
|
238
|
+
'.iris-health h2 { font-size: 15px; font-weight: 600; letter-spacing: -.01em; }',
|
|
239
|
+
'.iris-health-subtitle, .iris-health-caption { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
240
|
+
'.iris-health-subtitle { margin-top: 5px !important; }',
|
|
241
|
+
'.iris-health-btn { display: inline-flex; align-items: center; justify-content: center; gap: 7px; min-height: 32px; padding: 5px 10px; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 8px; background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-primary, #e6e6e6); font: inherit; font-size: 12px; white-space: nowrap; cursor: pointer; }',
|
|
242
|
+
'.iris-health-btn:hover { border-color: var(--iris-health-accent); }',
|
|
243
|
+
'.iris-health-btn.primary { border-color: transparent; background: var(--iris-health-accent); color: var(--dsw-alias-label-primary-inverted, #1b2034); font-weight: 600; }',
|
|
244
|
+
'.iris-health-btn:disabled { opacity: .5; cursor: default; }',
|
|
245
|
+
'.iris-health button:focus-visible { outline: 2px solid var(--iris-health-accent); outline-offset: 3px; }',
|
|
246
|
+
'.iris-health-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }',
|
|
247
|
+
'.iris-health-row { --iris-health-tone: var(--dsw-alias-label-secondary, #9a9a9a); display: grid; gap: 10px; min-width: 0; padding: 12px; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 10px; background: var(--dsw-alias-bg-layer-1, #26262e); color: var(--dsw-alias-label-primary, #e6e6e6); font: inherit; text-align: left; cursor: pointer; transition: border-color .15s, transform .15s; }',
|
|
248
|
+
'.iris-health-row:hover:not(:disabled) { transform: translateY(-2px); border-color: var(--iris-health-accent); }',
|
|
249
|
+
'.iris-health-row:disabled { cursor: wait; opacity: .7; }',
|
|
250
|
+
'.iris-health-row.active { border-color: var(--iris-health-accent); box-shadow: inset 0 0 0 1px var(--iris-health-accent); }',
|
|
251
|
+
'.iris-health-row.configured { --iris-health-tone: var(--iris-health-accent); }',
|
|
252
|
+
'.iris-health-row.verified { --iris-health-tone: var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
253
|
+
'.iris-health-row.failed, .iris-health-row.rate-limited { --iris-health-tone: var(--dsw-alias-state-error-primary, #e06c6c); }',
|
|
254
|
+
'.iris-health-row.cooling { --iris-health-tone: #c68b23; }',
|
|
255
|
+
'.iris-health-icon { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 32px; height: 32px; border-radius: 9px; background: rgba(132,115,238,.1); color: var(--iris-health-accent); }',
|
|
256
|
+
'.iris-health-card-top { justify-content: space-between; }',
|
|
257
|
+
'.iris-health-name { font-size: 14px; font-weight: 600; }',
|
|
258
|
+
'.iris-health-state { display: inline-flex; align-items: center; gap: 5px; margin-top: 7px; font-size: 11px; color: var(--iris-health-tone); }',
|
|
259
|
+
'.iris-health-state::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }',
|
|
260
|
+
'.iris-health-card-foot { justify-content: space-between; min-width: 0; padding-top: 8px; border-top: 1px solid var(--dsw-alias-border-l1, #3a3a44); font-size: 11px; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
261
|
+
'.iris-health-link { color: var(--iris-health-accent); white-space: nowrap; }',
|
|
262
|
+
'.iris-health-footer { margin-top: 13px !important; font-size: 11px; line-height: 1.6; color: var(--dsw-alias-label-secondary, #9a9a9a); }',
|
|
263
|
+
'.iris-health-notice { margin-top: 12px; padding: 11px 13px; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 10px; font-size: 12px; line-height: 1.6; overflow-wrap: anywhere; }',
|
|
264
|
+
'.iris-health-notice.error { color: var(--dsw-alias-state-error-primary, #e06c6c); }',
|
|
265
|
+
'.iris-health-detail { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1, #3a3a44); }',
|
|
266
|
+
'.iris-health-detail-head { justify-content: space-between; align-items: flex-start; margin-bottom: 10px; }',
|
|
267
|
+
'.iris-health-detail-head > button { flex-shrink: 0; }',
|
|
268
|
+
'.iris-health h3 { font-size: 15px; font-weight: 600; margin-bottom: 4px; }',
|
|
269
|
+
'.iris-health-models { display: grid; gap: 8px; }',
|
|
270
|
+
'.iris-health-model { padding: 10px 12px; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 10px; background: var(--dsw-alias-bg-layer-1, #26262e); min-width: 0; }',
|
|
271
|
+
'.iris-health-model-head { justify-content: space-between; align-items: flex-start; }',
|
|
272
|
+
'.iris-health-model-title { min-width: 0; flex: 1; }',
|
|
273
|
+
'.iris-health-model-title strong { display: block; font-size: 13px; font-weight: 550; overflow-wrap: anywhere; }',
|
|
274
|
+
'.iris-health-result { display: flex; gap: 7px; align-items: flex-start; margin-top: 10px; font-size: 12px; line-height: 1.6; overflow-wrap: anywhere; }',
|
|
275
|
+
'.iris-health-result.passed { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
276
|
+
'.iris-health-result.failed, .iris-health-result.unknown { color: var(--dsw-alias-state-error-primary, #e06c6c); }',
|
|
277
|
+
'.iris-health-result.running { color: var(--iris-health-accent); }',
|
|
278
|
+
'.iris-health-spinner { display: inline-block; flex: 0 0 13px; width: 13px; height: 13px; margin-top: 3px; border: 1.5px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: iris-health-spin .8s linear infinite; }',
|
|
279
|
+
'.iris-health-confirm { margin-top: 12px; padding: 14px; border-radius: 11px; border: 1px solid var(--iris-health-accent); background: rgba(132,115,238,.06); font-size: 12px; line-height: 1.7; }',
|
|
280
|
+
'.iris-health-confirm-actions { margin-top: 12px; flex-wrap: wrap; }',
|
|
281
|
+
'@keyframes iris-health-spin { to { transform: rotate(360deg); } }',
|
|
282
|
+
'@container (max-width: 650px) { .iris-health-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }',
|
|
283
|
+
'@container (max-width: 420px) { .iris-health-grid { grid-template-columns: 1fr; } }',
|
|
284
|
+
'@media (max-width: 640px) { .iris-health-head { flex-wrap: nowrap; padding: 12px; } .iris-health-head > div { flex: 1; min-width: 0; } .iris-health-head > button { flex-shrink: 0; } .iris-health-content { padding: 0 12px 12px; } .iris-health-grid { grid-template-columns: 1fr; gap: 8px; } .iris-health-row { position: relative; grid-template-columns: 32px minmax(0, 1fr); gap: 5px 9px; padding: 10px; } .iris-health-card-top { display: contents; } .iris-health-card-top .iris-health-icon { grid-row: 1 / 3; grid-column: 1; } .iris-health-card-top .iris-health-caption { position: absolute; right: 10px; top: 10px; font-size: 10px; } .iris-health-card-main { grid-column: 2; min-width: 0; } .iris-health-name { padding-right: 58px; } .iris-health-state { display: flex; line-height: 1.6; margin-top: 5px; } .iris-health-state::before { flex-shrink: 0; } .iris-health-card-foot { grid-column: 2; border: 0; padding-top: 0; font-size: 10px; flex-wrap: wrap; gap: 3px; } .iris-health-link { margin-left: auto; } .iris-health-btn { min-height: 40px; } .iris-health-model-head { flex-wrap: wrap; } .iris-health-model-head > .iris-health-btn { margin-left: auto; } }',
|
|
285
|
+
'@media (prefers-reduced-motion: reduce) { .iris-health-row { transition: none; } .iris-health-row:hover { transform: none; } .iris-health-spinner { animation: none; } }',
|
|
184
286
|
'.iris-cap-grid { display: grid; gap: 6px; }',
|
|
185
287
|
'.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; }',
|
|
186
288
|
'.iris-cap-name { font-weight: 600; min-width: 64px; }',
|
|
187
289
|
'.iris-cap-chip { display: inline-flex; align-items: center; gap: 3px; background: var(--dsw-alias-bg-layer-2, #32323c); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 10px; padding: 1px 6px; font-size: 11px; }',
|
|
188
290
|
'.iris-cap-chip b { color: var(--dsw-alias-brand-primary, #7aa2f7); }',
|
|
189
291
|
'.iris-cap-auto { color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 11px; font-style: italic; }',
|
|
292
|
+
'.iris-cap-feedback { flex-basis: 100%; color: var(--dsw-alias-brand-primary, #7aa2f7); }',
|
|
293
|
+
'.iris-cap-feedback.saved { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
294
|
+
'.iris-cap-feedback.unknown, .iris-cap-compatibility { color: var(--dsw-alias-state-error-primary, #e06c6c); }',
|
|
295
|
+
'.iris-cap-confirm { flex-basis: 100%; padding: 8px; border: 1px solid var(--dsw-alias-brand-primary, #7aa2f7); border-radius: 6px; }',
|
|
190
296
|
'.iris-pm-model-sel { 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%; }',
|
|
191
297
|
'.iris-wb-drawer .iris-wb-loading { color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 12px; }',
|
|
192
298
|
'.iris-bubble { position: fixed; z-index: 9999; cursor: grab; user-select: none; touch-action: none; }',
|
|
@@ -268,7 +374,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
268
374
|
'.iris-pm-mname { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
|
|
269
375
|
'.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; }',
|
|
270
376
|
'.iris-pm-mcaps { display: flex; gap: 3px; }',
|
|
271
|
-
'.iris-pm-ver { font-size:
|
|
377
|
+
'.iris-pm-ver { font: inherit; font-size: 11px; padding: 3px 7px; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 7px; background: var(--dsw-alias-bg-layer-2, #32323c); color: var(--dsw-alias-label-secondary, #9a9a9a); cursor: pointer; }',
|
|
272
378
|
'.iris-pm-ver.configured { color: #7da8fa; }',
|
|
273
379
|
'.iris-pm-ver.verified { color: var(--dsw-alias-state-success-primary, #6fcf6f); }',
|
|
274
380
|
'.iris-pm-ver.failed { color: #c87a81; border-color: rgba(185,101,109,.5); }',
|
|
@@ -370,13 +476,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
370
476
|
].join('\n');
|
|
371
477
|
|
|
372
478
|
function adoptStyles() {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
479
|
+
var style = document.getElementById(STYLE_ID);
|
|
480
|
+
if (style === null) {
|
|
481
|
+
style = document.createElement('style');
|
|
482
|
+
style.id = STYLE_ID;
|
|
483
|
+
style.dataset.plugin = 'dsh-iris';
|
|
484
|
+
style.dataset.pluginCss = STYLE_ID;
|
|
485
|
+
document.head.appendChild(style);
|
|
486
|
+
}
|
|
487
|
+
if (style.textContent !== cssText) style.textContent = cssText;
|
|
380
488
|
}
|
|
381
489
|
|
|
382
490
|
var inject = ['slots'];
|
|
@@ -514,7 +622,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
514
622
|
|
|
515
623
|
function healthText(health) {
|
|
516
624
|
var status = health && HEALTH_META[health.status] ? health.status : 'unconfigured';
|
|
517
|
-
var text = health && health.rateLimited
|
|
625
|
+
var text = health && health.modelUnavailable ? '模型不存在,已暂停自动选用' : health && health.rateLimited
|
|
518
626
|
? rateLimitLabel(health) + (health.retryAt
|
|
519
627
|
? ',' + new Date(health.retryAt).toLocaleTimeString() + ' 恢复候选资格;可提前实测'
|
|
520
628
|
: ',实测成功后恢复')
|
|
@@ -524,25 +632,238 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
524
632
|
return text + (age ? ' · ' + age : '');
|
|
525
633
|
}
|
|
526
634
|
|
|
635
|
+
var HEALTH_ROWS = [
|
|
636
|
+
{ cap: 'image-gen', label: '图片', action: 'draw', sample: '生成一张红色圆形测试图,可能需要约一分钟。', icon: 'M4 4h16v16H4z M4 16l5-5 4 4 3-3 4 4 M8 8h.01' },
|
|
637
|
+
{ cap: 'video-gen', label: '视频', action: 'video', taskOnly: true, sample: '请到视频功能填写创作要求或素材,选择模型后提交验证。', icon: 'M3 6h12v12H3z M15 10l6-3v10l-6-3' },
|
|
638
|
+
{ cap: 'tts', label: '语音', action: 'tts', sample: '合成一句“你好”,以接口返回音频作为通过依据。', icon: 'M4 10h4l5-4v12l-5-4H4z M16 8c3 2 3 6 0 8 M19 5c5 4 5 10 0 14' },
|
|
639
|
+
{ cap: 'music-gen', label: '音乐', action: 'music', taskOnly: true, sample: '请到音乐功能填写实际创作要求,选择模型后提交验证。', icon: 'M9 17V5l11-2v12 M9 5v4l11-2 M9 17a3 3 0 1 1-3-3h3 M20 15a3 3 0 1 1-3-3h3' },
|
|
640
|
+
{ cap: 'transcribe', label: '转写', action: 'transcribe', taskOnly: true, sample: '请到转写功能提供真实音频,选择模型后提交验证。', icon: 'M9 5a3 3 0 0 1 6 0v7a3 3 0 0 1-6 0z M5 10v2a7 7 0 0 0 14 0v-2 M12 19v3 M8 22h8' },
|
|
641
|
+
{ cap: 'vision', label: '视觉', action: 'ocr', sample: '发送一张内置红色测试图,检查模型能否识别颜色。', icon: 'M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0' }
|
|
642
|
+
];
|
|
643
|
+
function capabilityIcon(row) {
|
|
644
|
+
return React.createElement('svg', { width: 20, height: 20, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor',
|
|
645
|
+
strokeWidth: 1.6, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true }, React.createElement('path', { d: row.icon }));
|
|
646
|
+
}
|
|
647
|
+
function inspectionSupported(snapshot) {
|
|
648
|
+
return !!(snapshot && snapshot.policies && snapshot.readiness && HEALTH_ROWS.every(function (row) {
|
|
649
|
+
return snapshot.policies[row.cap] && ['auto', 'manual', 'disabled'].includes(snapshot.policies[row.cap].mode) && snapshot.readiness[row.cap];
|
|
650
|
+
}));
|
|
651
|
+
}
|
|
652
|
+
function inspectionModels(snapshot, row, requested) {
|
|
653
|
+
if (!snapshot) return [];
|
|
654
|
+
var order = snapshot.order[row.cap] || [];
|
|
655
|
+
var policy = snapshot.policies && snapshot.policies[row.cap];
|
|
656
|
+
var options = (snapshot.poolByCapability[row.cap] || []).slice();
|
|
657
|
+
if (requested && requested.ref && !options.some(function (option) { return option.ref === requested.ref; })) {
|
|
658
|
+
options.push({ ...requested, removed: true });
|
|
659
|
+
}
|
|
660
|
+
return options.filter(function (option) {
|
|
661
|
+
return order.includes(option.ref) || (requested && option.providerId === requested.providerId && option.id === requested.modelId);
|
|
662
|
+
}).sort(function (a, b) { return order.indexOf(a.ref) - order.indexOf(b.ref); }).map(function (option) {
|
|
663
|
+
var provider = snapshot.providers.find(function (item) { return item.id === option.providerId; });
|
|
664
|
+
var model = provider && provider.models.find(function (item) { return item.id === option.id; });
|
|
665
|
+
var capability = snapshot.health && snapshot.health.capabilities && snapshot.health.capabilities[row.cap];
|
|
666
|
+
var evidence = capability && (capability.candidates || []).find(function (item) { return item.providerId === option.providerId && item.modelId === option.id; });
|
|
667
|
+
var health = evidence || model && model.health && model.health[row.cap] || { status: 'configured' };
|
|
668
|
+
var host = option.ref === 'host:current-vision';
|
|
669
|
+
var reason = !inspectionSupported(snapshot) ? '请先让 DSH 加载新版功能池接口,再刷新配置。'
|
|
670
|
+
: policy.mode === 'disabled' ? '此功能已停用,请先调整调用方式。'
|
|
671
|
+
: option.removed ? '此模型已从当前配置中移除。'
|
|
672
|
+
: !order.includes(option.ref) ? '此模型未纳入当前功能池,请先调整调用方式或所选模型。'
|
|
673
|
+
: option.pendingConfirmation ? '请先在供应商中确认模型用途。'
|
|
674
|
+
: !host && (!provider || provider.enabled === false) ? '供应商当前不可用,请先检查设置。'
|
|
675
|
+
: provider && provider.dshProvider && (!provider.connection || !provider.connection.available) ? 'DSH 连接尚未就绪,请先检查宿主供应商设置。'
|
|
676
|
+
: option.protocolSupported === false ? option.reason || '当前接口协议不支持此功能。'
|
|
677
|
+
: option.unavailable && !health.rateLimited && !health.modelUnavailable && !option.modelUnavailable ? option.reason || '连接尚未就绪,请先检查供应商设置。' : '';
|
|
678
|
+
return { ...option, providerName: provider ? provider.name : 'DSH 宿主', health: health,
|
|
679
|
+
taskOnly: row.taskOnly || host, reason: reason, canTest: !reason };
|
|
680
|
+
});
|
|
681
|
+
}
|
|
527
682
|
function CapabilityHealthOverview() {
|
|
528
683
|
var state = useIrisState(5000);
|
|
529
|
-
var
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
];
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
684
|
+
var expandedPair = React.useState(false), expanded = expandedPair[0];
|
|
685
|
+
var snapshotPair = React.useState(null), snapshot = snapshotPair[0];
|
|
686
|
+
var loadingPair = React.useState(false), loading = loadingPair[0];
|
|
687
|
+
var freshPair = React.useState(false), fresh = freshPair[0];
|
|
688
|
+
var errorPair = React.useState('');
|
|
689
|
+
var checkedPair = React.useState('');
|
|
690
|
+
var selectedPair = React.useState(null), selected = selectedPair[0];
|
|
691
|
+
var requestedPair = React.useState(null), requested = requestedPair[0];
|
|
692
|
+
var confirmPair = React.useState(null), confirmation = confirmPair[0];
|
|
693
|
+
var resultsPair = React.useState({}), results = resultsPair[0];
|
|
694
|
+
var busyPair = React.useState(null), busy = busyPair[0];
|
|
695
|
+
var running = React.useRef(false), readVersion = React.useRef(0);
|
|
696
|
+
function noteResult(key, value) { resultsPair[1](function (previous) { return { ...previous, [key]: value }; }); }
|
|
697
|
+
function load() {
|
|
698
|
+
var version = ++readVersion.current;
|
|
699
|
+
var controller = new AbortController();
|
|
700
|
+
var timer = setTimeout(function () { controller.abort(); }, 10000);
|
|
701
|
+
loadingPair[1](true); freshPair[1](false); errorPair[1]('');
|
|
702
|
+
return Promise.all([
|
|
703
|
+
postAction('assignments_get', {}, { signal: controller.signal }),
|
|
704
|
+
postAction('providers_list', {}, { signal: controller.signal }),
|
|
705
|
+
fetch('/iris/api/state', { signal: controller.signal }).then(function (r) {
|
|
706
|
+
if (!r.ok) throw new Error('健康状态读取失败(HTTP ' + r.status + ')');
|
|
707
|
+
return r.json();
|
|
708
|
+
})
|
|
709
|
+
]).then(function (values) {
|
|
710
|
+
if (version !== readVersion.current) return null;
|
|
711
|
+
if (!values[0].ok || values[0].d.ok !== true || !values[1].ok || values[1].d.ok !== true) throw new Error('配置读取失败,请重新检查');
|
|
712
|
+
var data = { ...values[0].d, providers: values[1].d.providers, health: values[2].health };
|
|
713
|
+
snapshotPair[1](data); freshPair[1](true); checkedPair[1](new Date().toLocaleTimeString()); return data;
|
|
714
|
+
}).catch(function (error) {
|
|
715
|
+
if (version === readVersion.current) errorPair[1](error.name === 'AbortError' ? '配置检查超时,请重试;未发起模型实测。' : error.message);
|
|
716
|
+
return null;
|
|
717
|
+
}).finally(function () { clearTimeout(timer); controller.abort(); if (version === readVersion.current) loadingPair[1](false); });
|
|
718
|
+
}
|
|
719
|
+
function inspect(row, target) {
|
|
720
|
+
if (running.current) return;
|
|
721
|
+
expandedPair[1](true);
|
|
722
|
+
selectedPair[1](row.cap); requestedPair[1](target || null); confirmPair[1](null);
|
|
723
|
+
}
|
|
724
|
+
React.useEffect(function () {
|
|
725
|
+
load();
|
|
726
|
+
function open(event) {
|
|
727
|
+
var detail = event.detail || {};
|
|
728
|
+
var row = HEALTH_ROWS.find(function (item) { return item.cap === detail.capability; });
|
|
729
|
+
if (row && !running.current) {
|
|
730
|
+
inspect(row, detail); load();
|
|
731
|
+
var section = document.getElementById('iris-capability-health');
|
|
732
|
+
if (section) section.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
window.addEventListener('iris-capability-inspect', open);
|
|
736
|
+
window.addEventListener('iris-function-configuration-changed', load);
|
|
737
|
+
return function () {
|
|
738
|
+
++readVersion.current;
|
|
739
|
+
window.removeEventListener('iris-capability-inspect', open);
|
|
740
|
+
window.removeEventListener('iris-function-configuration-changed', load);
|
|
741
|
+
};
|
|
742
|
+
}, []);
|
|
743
|
+
function configure(row) {
|
|
744
|
+
var field = document.querySelector('[data-function="' + row.cap + '"] > select');
|
|
745
|
+
if (field) { revealWorkbenchItem(field); field.scrollIntoView({ block: 'center', behavior: 'smooth' }); field.focus({ preventScroll: true }); }
|
|
746
|
+
}
|
|
747
|
+
function openTask(row) {
|
|
748
|
+
var card = document.querySelector('[data-iris-action="' + row.action + '"]');
|
|
749
|
+
if (!card) return;
|
|
750
|
+
revealWorkbenchItem(card);
|
|
751
|
+
if (!card.classList.contains('open')) card.querySelector('.iris-act-head').click();
|
|
752
|
+
card.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
753
|
+
}
|
|
754
|
+
function probe(row, option) {
|
|
755
|
+
if (running.current) return;
|
|
756
|
+
running.current = true; confirmPair[1](null);
|
|
757
|
+
requestedPair[1]({ ...option, modelId: option.id });
|
|
758
|
+
var key = row.cap + '|' + option.ref;
|
|
759
|
+
busyPair[1](key); noteResult(key, { phase: 'running', note: '正在核对当前模型范围…' });
|
|
760
|
+
load().then(function (current) {
|
|
761
|
+
var target = inspectionModels(current, row, option).find(function (item) { return item.ref === option.ref; });
|
|
762
|
+
if (!current || !target || !target.canTest || target.taskOnly) {
|
|
763
|
+
noteResult(key, { phase: 'skipped', note: target && target.reason || '配置未确认或调用范围已变化,未发起实测。' }); return;
|
|
764
|
+
}
|
|
765
|
+
noteResult(key, { phase: 'running', note: '正在实测…' + (row.cap === 'image-gen' ? '图片可能需要约一分钟,请勿重复提交。' : '请等待接口返回。') });
|
|
766
|
+
var controller = new AbortController();
|
|
767
|
+
var timer = setTimeout(function () { controller.abort(); }, 90000);
|
|
768
|
+
return postAction('providers_test_model', { id: target.providerId, model_id: target.id, capability: row.cap, confirm_paid: true },
|
|
769
|
+
{ signal: controller.signal }).then(function (r) {
|
|
770
|
+
if (!r.ok || !r.d || r.d.ok !== true || (r.d.skipped !== true && typeof r.d.passed !== 'boolean')) {
|
|
771
|
+
noteResult(key, { phase: 'unknown', note: r.d && r.d.error || '未收到有效检测结果;请先核对供应商记录,避免重复计费。' }); return;
|
|
772
|
+
}
|
|
773
|
+
noteResult(key, { phase: r.d.skipped ? 'skipped' : r.d.passed ? 'passed' : 'failed',
|
|
774
|
+
note: String(r.d.text || '').replace(/^\[iris\][^\n]*?:/, '') || (r.d.passed ? '接口实测通过' : '接口未通过实测') });
|
|
775
|
+
}).catch(function () {
|
|
776
|
+
noteResult(key, { phase: 'unknown', note: '结果未确认,请先核对供应商记录,避免重复计费。本页不会自动重试。' });
|
|
777
|
+
}).finally(function () { clearTimeout(timer); });
|
|
778
|
+
}).then(function () { return load(); }).finally(function () { running.current = false; busyPair[1](null); });
|
|
779
|
+
}
|
|
780
|
+
var currentHealth = snapshot && snapshot.health;
|
|
781
|
+
if (!currentHealth || (state && state.health && state.health.generatedAt > currentHealth.generatedAt)) currentHealth = state && state.health;
|
|
782
|
+
var viewSnapshot = snapshot && { ...snapshot, health: currentHealth };
|
|
783
|
+
var supported = inspectionSupported(snapshot);
|
|
784
|
+
var row = HEALTH_ROWS.find(function (item) { return item.cap === selected; });
|
|
785
|
+
var modes = { auto: '自动选择', manual: '所选模型', disabled: '已停用' };
|
|
786
|
+
var verifiedCount = HEALTH_ROWS.filter(function (item) { return capabilityHealth({ health: currentHealth }, item.cap).status === 'verified'; }).length;
|
|
787
|
+
var blockedCount = HEALTH_ROWS.filter(function (item) { return capabilityHealth({ health: currentHealth }, item.cap).status === 'failed'; }).length;
|
|
788
|
+
var summary = busy ? '正在检测,请等待本次结果。' : errorPair[0] ? '配置读取失败,展开查看详情。'
|
|
789
|
+
: snapshot && !supported ? '服务端需重新加载,展开查看说明。'
|
|
790
|
+
: currentHealth ? '6 项能力 · ' + verifiedCount + ' 项近期成功' + (blockedCount ? ' · ' + blockedCount + ' 项调用受阻' : '')
|
|
791
|
+
: '正在读取能力状态…';
|
|
792
|
+
return React.createElement('section', { className: 'iris-health', id: 'iris-capability-health', 'aria-label': '能力状态与检测' },
|
|
793
|
+
React.createElement('div', { className: 'iris-health-head' },
|
|
794
|
+
React.createElement('div', {}, React.createElement('h2', {}, '能力状态与检测'),
|
|
795
|
+
React.createElement('p', { className: 'iris-health-subtitle', role: 'status' }, summary)),
|
|
796
|
+
React.createElement('button', { className: 'iris-health-btn', 'aria-expanded': expanded, 'aria-controls': 'iris-health-content',
|
|
797
|
+
onClick: function () { expandedPair[1](!expanded); confirmPair[1](null); } }, expanded ? '收起检测' : '展开检测')),
|
|
798
|
+
React.createElement('div', { className: 'iris-health-content', id: 'iris-health-content', hidden: !expanded },
|
|
799
|
+
React.createElement('div', { className: 'iris-health-toolbar' },
|
|
800
|
+
React.createElement('span', { className: 'iris-health-caption' }, '配置检查只读;实测前单独确认。'),
|
|
801
|
+
React.createElement('button', { className: 'iris-health-btn', 'data-health-refresh': true, disabled: loading || !!busy, onClick: function () { load(); } },
|
|
802
|
+
loading ? '正在检查配置…' : '检查配置')),
|
|
803
|
+
errorPair[0] ? React.createElement('div', { className: 'iris-health-notice error', role: 'alert' }, errorPair[0]) : null,
|
|
804
|
+
snapshot && !supported ? React.createElement('div', { className: 'iris-health-notice', role: 'status' },
|
|
805
|
+
'服务端尚未加载新版功能池接口。请重启 DSH 后刷新页面;当前只展示历史状态,暂停模型实测。') : null,
|
|
806
|
+
React.createElement('div', { className: 'iris-health-grid' }, HEALTH_ROWS.map(function (item) {
|
|
807
|
+
var health = capabilityHealth({ health: currentHealth }, item.cap);
|
|
808
|
+
var candidates = inspectionModels(viewSnapshot, item);
|
|
809
|
+
var verified = candidates.filter(function (model) { return model.health.status === 'verified'; }).length;
|
|
810
|
+
var ready = snapshot && snapshot.readiness && snapshot.readiness[item.cap];
|
|
811
|
+
var policy = snapshot && snapshot.policies && snapshot.policies[item.cap];
|
|
812
|
+
return React.createElement('button', { key: item.cap, className: 'iris-health-row ' + healthClass(health) + (selected === item.cap ? ' active' : ''),
|
|
813
|
+
'data-health-capability': item.cap, 'aria-expanded': selected === item.cap, 'aria-controls': 'iris-health-detail', disabled: !!busy,
|
|
814
|
+
onClick: function () { inspect(item); } },
|
|
815
|
+
React.createElement('div', { className: 'iris-health-card-top' },
|
|
816
|
+
React.createElement('span', { className: 'iris-health-icon' }, capabilityIcon(item)),
|
|
817
|
+
React.createElement('span', { className: 'iris-health-caption' }, policy ? modes[policy.mode] : '读取配置')),
|
|
818
|
+
React.createElement('div', { className: 'iris-health-card-main' }, React.createElement('div', { className: 'iris-health-name' }, item.label),
|
|
819
|
+
React.createElement('div', { className: 'iris-health-state', title: healthText(health) }, snapshot || state ? healthText(health) : '正在读取状态…')),
|
|
820
|
+
React.createElement('div', { className: 'iris-health-card-foot' },
|
|
821
|
+
React.createElement('span', {}, ready ? candidates.length ? ready.candidateCount + ' 个可用 · ' + verified + '/' + candidates.length + ' 近期成功' : '暂无候选模型' : '等待配置检查'),
|
|
822
|
+
React.createElement('span', { className: 'iris-health-link' }, '查看检测 ›')));
|
|
823
|
+
})),
|
|
824
|
+
React.createElement('p', { className: 'iris-health-footer', role: 'status' },
|
|
825
|
+
(checkedPair[0] ? '配置检查于 ' + checkedPair[0] + ' · ' : '') + '配置检查只读,实测需单独确认。绿色有效 7 天;不会后台探测。'),
|
|
826
|
+
row ? React.createElement('div', { className: 'iris-health-detail', id: 'iris-health-detail' },
|
|
827
|
+
React.createElement('div', { className: 'iris-health-detail-head' },
|
|
828
|
+
React.createElement('div', {}, React.createElement('h3', {}, row.label + '能力检测'),
|
|
829
|
+
React.createElement('p', { className: 'iris-health-caption' }, row.sample)),
|
|
830
|
+
React.createElement('button', { className: 'iris-health-btn', disabled: !!busy, 'aria-label': '收起能力检测',
|
|
831
|
+
onClick: function () { selectedPair[1](null); confirmPair[1](null); } }, '收起')),
|
|
832
|
+
React.createElement('div', { className: 'iris-health-models' },
|
|
833
|
+
inspectionModels(viewSnapshot, row, requested).map(function (option) {
|
|
834
|
+
var key = row.cap + '|' + option.ref;
|
|
835
|
+
var result = results[key];
|
|
836
|
+
var pending = confirmation && confirmation.ref === option.ref && confirmation.cap === row.cap;
|
|
837
|
+
return React.createElement('div', { key: option.ref, className: 'iris-health-model', 'data-health-model': option.ref, 'aria-busy': busy === key },
|
|
838
|
+
React.createElement('div', { className: 'iris-health-model-head' },
|
|
839
|
+
React.createElement('div', { className: 'iris-health-model-title' }, React.createElement('strong', {}, option.id),
|
|
840
|
+
React.createElement('span', { className: 'iris-health-caption' }, option.providerName + ' · ' + healthText(option.health))),
|
|
841
|
+
React.createElement('button', { className: 'iris-health-btn' + (!option.taskOnly ? ' primary' : ''),
|
|
842
|
+
disabled: !fresh || !option.canTest || !!busy || !!(result && result.phase === 'unknown'),
|
|
843
|
+
onClick: function () { if (running.current) return; if (option.taskOnly) openTask(row); else confirmPair[1]({ cap: row.cap, ref: option.ref }); } },
|
|
844
|
+
option.taskOnly ? '前往功能验证 ↗' : result && result.phase === 'running' ? '正在实测…' : '实测此模型')),
|
|
845
|
+
option.reason ? React.createElement('p', { className: 'iris-health-caption' }, option.reason) : null,
|
|
846
|
+
option.ref === 'host:current-vision' ? React.createElement('p', { className: 'iris-health-caption' },
|
|
847
|
+
'宿主视觉通过真实图片任务验证,请在打开的表单中选择 DSH 当前默认模型。') : null,
|
|
848
|
+
option.health.rateLimited ? React.createElement('p', { className: 'iris-health-caption' }, '显式实测成功后,会解除此模型的冷却或额度停用。') : null,
|
|
849
|
+
option.modelUnavailable || option.health.modelUnavailable ? React.createElement('p', { className: 'iris-health-caption' },
|
|
850
|
+
'当前连接曾返回模型不存在,已暂停自动选用;同步目录不会代替实测。单独实测成功后恢复,可能产生费用。') : null,
|
|
851
|
+
result ? React.createElement('div', { className: 'iris-health-result ' + result.phase, role: result.phase === 'unknown' ? 'alert' : 'status', 'aria-live': 'polite' },
|
|
852
|
+
result.phase === 'running' ? React.createElement('span', { className: 'iris-health-spinner', 'aria-hidden': true }) : null,
|
|
853
|
+
React.createElement('span', {}, (result.phase === 'passed' ? '实测通过 · ' : result.phase === 'failed' ? '实测未通过 · '
|
|
854
|
+
: result.phase === 'skipped' ? '未发起实测 · ' : '') + result.note)) : null,
|
|
855
|
+
pending ? React.createElement('div', { className: 'iris-health-confirm', role: 'group', 'aria-label': '确认模型实测' },
|
|
856
|
+
React.createElement('strong', {}, '确认向此模型发起一次真实请求?'),
|
|
857
|
+
React.createElement('p', {}, row.sample + ' 可能产生费用。本次仅验证 ' + option.id + ',失败不会改用其他模型。'),
|
|
858
|
+
React.createElement('div', { className: 'iris-health-confirm-actions' },
|
|
859
|
+
React.createElement('button', { className: 'iris-health-btn primary', disabled: !!busy, onClick: function () { probe(row, option); } }, '确认并实测'),
|
|
860
|
+
React.createElement('button', { className: 'iris-health-btn', disabled: !!busy, onClick: function () { confirmPair[1](null); } }, '取消'))) : null);
|
|
861
|
+
}),
|
|
862
|
+
!inspectionModels(viewSnapshot, row, requested).length ? React.createElement('div', { className: 'iris-health-notice' },
|
|
863
|
+
snapshot && snapshot.readiness && snapshot.readiness[row.cap] && snapshot.readiness[row.cap].note || '当前没有纳入调用范围的模型,请先配置此功能。') : null),
|
|
864
|
+
React.createElement('div', { className: 'iris-health-confirm-actions' },
|
|
865
|
+
React.createElement('button', { className: 'iris-health-btn', disabled: !!busy, onClick: function () { configure(row); } }, '查看功能设置'),
|
|
866
|
+
React.createElement('span', { className: 'iris-health-caption' }, '只检测当前调用范围内的模型;历史成功不代表所有候选均可用。'))) : null));
|
|
546
867
|
}
|
|
547
868
|
|
|
548
869
|
/* ---- 任务详情抽屉:按需拉取 /iris/api/task/:id ---- */
|
|
@@ -804,8 +1125,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
804
1125
|
}
|
|
805
1126
|
|
|
806
1127
|
/* ---- 供应商管理(阶段 6:增删 key + 模型列表 + 测试) ---- */
|
|
807
|
-
function postAction(action, body) {
|
|
808
|
-
return fetch('/iris/api/actions/' + action, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {})
|
|
1128
|
+
function postAction(action, body, options) {
|
|
1129
|
+
return fetch('/iris/api/actions/' + action, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}),
|
|
1130
|
+
...(options && options.signal ? { signal: options.signal } : {}) })
|
|
809
1131
|
.then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); });
|
|
810
1132
|
}
|
|
811
1133
|
|
|
@@ -813,9 +1135,6 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
813
1135
|
var CAP_SHORT = { 'image-gen': '图', 'video-gen': '视', 'tts': '音', 'music-gen': '乐', 'transcribe': '转', 'vision': '视👁' };
|
|
814
1136
|
function ModelPool(props) {
|
|
815
1137
|
var p = props.provider;
|
|
816
|
-
var addPair = React.useState('');
|
|
817
|
-
var newId = addPair[0];
|
|
818
|
-
var setNewId = addPair[1];
|
|
819
1138
|
var models = p.models || [];
|
|
820
1139
|
function setImageProtocol(m, value) {
|
|
821
1140
|
postAction('providers_set_model_image_protocol', { id: p.id, model_id: m.id, imageProtocol: value })
|
|
@@ -835,18 +1154,23 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
835
1154
|
return React.createElement('button', {
|
|
836
1155
|
key: c,
|
|
837
1156
|
className: cls,
|
|
838
|
-
title: title + '
|
|
1157
|
+
title: title + ';打开能力检测',
|
|
1158
|
+
'aria-label': (HEALTH_ROWS.find(function (row) { return row.cap === c; }) || { label: c }).label + '检测 · ' + m.id,
|
|
839
1159
|
onClick: function () { props.testModel(p, m, c); }
|
|
840
|
-
}, c + symbol);
|
|
1160
|
+
}, (HEALTH_ROWS.find(function (row) { return row.cap === c; }) || { label: c }).label + ' ' + symbol);
|
|
841
1161
|
}
|
|
842
1162
|
var rows = models.map(function (m) {
|
|
843
1163
|
var caps = m.capabilities || [];
|
|
844
1164
|
var blocked = caps.map(function (c) { return m.health && m.health[c]; }).find(function (health) { return health && health.rateLimited; });
|
|
845
1165
|
return React.createElement('div', { key: m.id, className: 'iris-pm-mrow' },
|
|
846
|
-
React.createElement('span', { className: 'iris-pm-mname' }, m.id, m.source === '
|
|
847
|
-
|
|
1166
|
+
React.createElement('span', { className: 'iris-pm-mname' }, m.id, m.source === 'discovered'
|
|
1167
|
+
? React.createElement('span', { className: 'iris-pm-mtag' }, '待确认用途') : null,
|
|
1168
|
+
m.catalogStatus === 'unlisted' ? React.createElement('span', { className: 'iris-pm-mtag' }, '目录未列出') : null),
|
|
1169
|
+
React.createElement('span', { className: 'iris-pm-mcaps' }, caps.length ? caps.map(function (c) {
|
|
1170
|
+
return m.directoryOnly ? React.createElement('span', { key: c }, (HEALTH_ROWS.find(function (row) { return row.cap === c; }) || { label: c }).label) : verBadge(m, c);
|
|
1171
|
+
}) : '未标注功能'),
|
|
848
1172
|
React.createElement(ModelCapabilityEditor, { provider: p, model: m, onNote: props.onNote, onDone: props.onDone }),
|
|
849
|
-
caps.includes('image-gen') ? React.createElement('label', { className: 'iris-pm-note' }, '图片协议 ',
|
|
1173
|
+
!m.directoryOnly && caps.includes('image-gen') ? React.createElement('label', { className: 'iris-pm-note' }, '图片协议 ',
|
|
850
1174
|
React.createElement('select', { value: m.imageProtocol || 'auto', onChange: function (e) { setImageProtocol(m, e.target.value); } },
|
|
851
1175
|
React.createElement('option', { value: 'auto' }, '继承账号默认(' + (p.mediaProtocol || '自动') + ')'),
|
|
852
1176
|
React.createElement('option', { value: 'dashscope' }, 'DashScope'),
|
|
@@ -854,44 +1178,54 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
854
1178
|
React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
|
|
855
1179
|
React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图')),
|
|
856
1180
|
m.imageRouting && m.imageRouting.protocolInferred ? ' · 协议待确认' : null) : null,
|
|
857
|
-
caps.includes('music-gen') ? React.createElement('label', { className: 'iris-pm-note' }, '音乐协议 ',
|
|
1181
|
+
!m.directoryOnly && caps.includes('music-gen') ? React.createElement('label', { className: 'iris-pm-note' }, '音乐协议 ',
|
|
858
1182
|
React.createElement('select', { value: m.musicProtocol || '', onChange: function (e) { setMusicProtocol(m, e.target.value); } },
|
|
859
1183
|
React.createElement('option', { value: '' }, '请选择音乐协议'),
|
|
860
1184
|
React.createElement('option', { value: 'gemini-web-music' }, 'Chat Completions(内嵌音频)'))) : null,
|
|
861
|
-
caps.includes('vision') || (caps.includes('image-gen') && m.imageRouting && m.imageRouting.editSupported) ? React.createElement(VisionInputEditor, { key: 'vision-input', provider: p, model: m,
|
|
1185
|
+
!m.directoryOnly && (caps.includes('vision') || (caps.includes('image-gen') && m.imageRouting && m.imageRouting.editSupported)) ? React.createElement(VisionInputEditor, { key: 'vision-input', provider: p, model: m,
|
|
862
1186
|
onNote: props.onNote, onDone: props.onDone }) : null,
|
|
863
1187
|
blocked ? React.createElement('span', { className: 'iris-pm-ver failed ' + healthClass(blocked), title: healthText(blocked) }, rateLimitLabel(blocked)) : null,
|
|
864
|
-
React.createElement('button', { className: 'iris-pm-btn danger', title: '从池移除', onClick: function () { props.removeModel(p, m); } }, '✕'));
|
|
1188
|
+
!m.directoryOnly ? React.createElement('button', { className: 'iris-pm-btn danger', title: '从池移除', onClick: function () { props.removeModel(p, m); } }, '✕') : null);
|
|
865
1189
|
});
|
|
866
1190
|
return React.createElement('div', { className: 'iris-pm-pool' },
|
|
867
|
-
React.createElement('div', { className: 'iris-pm-note' }, '模型池(' + models.length + '
|
|
868
|
-
|
|
869
|
-
React.createElement('div', { className: 'iris-
|
|
870
|
-
|
|
871
|
-
|
|
1191
|
+
React.createElement('div', { className: 'iris-pm-note' }, '模型池(' + models.filter(function (m) { return !m.directoryOnly; }).length + ')'
|
|
1192
|
+
+ (models.some(function (m) { return m.directoryOnly; }) ? ' · 目录待确认 ' + models.filter(function (m) { return m.directoryOnly; }).length + ' 项' : '') + ':'),
|
|
1193
|
+
rows.length ? React.createElement('div', { className: 'iris-pm-mrows' }, rows) : React.createElement('div', { className: 'iris-pm-note' }, '暂无模型 — 展开「模型目录」同步,或在下方手动添加'),
|
|
1194
|
+
React.createElement(ModelUsageForm, { provider: p, setupSuggested: props.setupSuggested,
|
|
1195
|
+
onNote: props.onNote, onDone: props.onDone }));
|
|
872
1196
|
}
|
|
873
1197
|
|
|
874
|
-
function
|
|
875
|
-
var
|
|
876
|
-
var capsPair = React.useState([]);
|
|
1198
|
+
function ModelUsageForm(props) {
|
|
1199
|
+
var namePair = React.useState(props.model ? props.model.id : '');
|
|
1200
|
+
var capsPair = React.useState(props.model ? (props.model.capabilities || []).slice() : []);
|
|
1201
|
+
var autoPair = React.useState(!props.model && props.setupSuggested === true);
|
|
1202
|
+
var musicPair = React.useState(props.model && props.model.musicProtocol || '');
|
|
877
1203
|
var savingPair = React.useState(false);
|
|
878
1204
|
var selected = capsPair[0];
|
|
879
1205
|
var saving = savingPair[0];
|
|
880
1206
|
function save() {
|
|
881
1207
|
if (saving) return;
|
|
1208
|
+
if (!namePair[0].trim()) { props.onNote('请填写模型名称'); return; }
|
|
1209
|
+
if (selected.includes('music-gen') && !musicPair[0]) { props.onNote('请先选择该模型实际支持的音乐协议'); return; }
|
|
882
1210
|
savingPair[1](true);
|
|
883
|
-
postAction('
|
|
1211
|
+
postAction('providers_confirm_model', { id: props.provider.id, model_id: namePair[0].trim(), capabilities: selected,
|
|
1212
|
+
enableAutomatic: autoPair[0], ...(selected.includes('music-gen') ? { musicProtocol: musicPair[0] } : {}) })
|
|
884
1213
|
.then(function (r) {
|
|
885
1214
|
if (r.ok) {
|
|
886
|
-
|
|
887
|
-
props.onNote('
|
|
1215
|
+
if (!props.model) namePair[1]('');
|
|
1216
|
+
props.onNote(r.d.text || '模型用途已确认');
|
|
888
1217
|
props.onDone();
|
|
1218
|
+
window.dispatchEvent(new CustomEvent('iris-function-configuration-changed'));
|
|
889
1219
|
} else props.onNote(r.d.error || '保存失败');
|
|
1220
|
+
}).catch(function () { props.onNote('无法保存,请检查与宿主的连接');
|
|
890
1221
|
}).finally(function () { savingPair[1](false); });
|
|
891
1222
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
1223
|
+
return React.createElement('div', { className: 'iris-pm-card' },
|
|
1224
|
+
!props.model ? React.createElement('label', { className: 'iris-pm-field' }, '模型名称',
|
|
1225
|
+
React.createElement('input', { value: namePair[0], placeholder: '发现后选模型,或填写服务提供的模型名',
|
|
1226
|
+
onChange: function (e) { namePair[1](e.target.value); } })) : null,
|
|
1227
|
+
React.createElement('div', { className: 'iris-pm-note' }, '确认此模型用于哪些功能;名称和文字接口本身不证明媒体能力。'),
|
|
1228
|
+
React.createElement('div', { className: 'iris-wb-row' },
|
|
895
1229
|
CAP_ROWS.map(function (row) {
|
|
896
1230
|
return React.createElement('label', { key: row.cap, className: 'iris-pm-note' },
|
|
897
1231
|
React.createElement('input', { type: 'checkbox', value: row.cap, checked: selected.includes(row.cap), disabled: saving,
|
|
@@ -902,9 +1236,25 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
902
1236
|
: previous.filter(function (cap) { return cap !== row.cap; });
|
|
903
1237
|
});
|
|
904
1238
|
} }), row.label);
|
|
905
|
-
}),
|
|
906
|
-
React.createElement('
|
|
907
|
-
|
|
1239
|
+
})),
|
|
1240
|
+
selected.includes('music-gen') ? React.createElement('label', { className: 'iris-pm-note' }, '音乐协议 ',
|
|
1241
|
+
React.createElement('select', { value: musicPair[0], onChange: function (e) { musicPair[1](e.target.value); } },
|
|
1242
|
+
React.createElement('option', { value: '' }, '请选择音乐协议'),
|
|
1243
|
+
React.createElement('option', { value: 'gemini-web-music' }, 'Chat Completions(内嵌音频)'))) : null,
|
|
1244
|
+
React.createElement('label', { className: 'iris-pm-note' }, React.createElement('input', {
|
|
1245
|
+
type: 'checkbox', checked: autoPair[0], onChange: function (e) { autoPair[1](e.target.checked); }
|
|
1246
|
+
}), '同时启用所选功能的自动池(替换这些功能当前的调用方式,保留原手工名单)'),
|
|
1247
|
+
React.createElement('button', { className: 'iris-pm-btn', disabled: saving, onClick: save }, saving ? '保存中…' : '保存模型用途'));
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function ModelCapabilityEditor(props) {
|
|
1251
|
+
var openPair = React.useState(false);
|
|
1252
|
+
if (!openPair[0]) return React.createElement('button', { className: 'iris-pm-btn', title: '确认模型用途,加入功能池',
|
|
1253
|
+
onClick: function () { openPair[1](true); } }, '设置功能');
|
|
1254
|
+
return React.createElement('div', {},
|
|
1255
|
+
React.createElement(ModelUsageForm, { provider: props.provider, model: props.model, onNote: props.onNote,
|
|
1256
|
+
onDone: function () { openPair[1](false); props.onDone(); } }),
|
|
1257
|
+
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { openPair[1](false); } }, '取消'));
|
|
908
1258
|
}
|
|
909
1259
|
|
|
910
1260
|
function VisionInputEditor(props) {
|
|
@@ -940,6 +1290,42 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
940
1290
|
React.createElement('span', { className: 'iris-pm-note' }, '用于看图、OCR 每块输入、定位和改图的发送副本。作品原图保留,定位结果换算回原图。'));
|
|
941
1291
|
}
|
|
942
1292
|
|
|
1293
|
+
function ProviderDirectory(props) {
|
|
1294
|
+
var p = props.provider, directory = p.directory || { status: 'unknown' };
|
|
1295
|
+
var busyPair = React.useState(false), notePair = React.useState(''), running = React.useRef(false);
|
|
1296
|
+
function synchronize() {
|
|
1297
|
+
if (running.current || !p.directory) return;
|
|
1298
|
+
running.current = true; busyPair[1](true); notePair[1]('');
|
|
1299
|
+
var controller = new AbortController();
|
|
1300
|
+
var timer = setTimeout(function () { controller.abort(); }, 90000);
|
|
1301
|
+
postAction('providers_sync_directory', { id: p.id, force: true }, { signal: controller.signal }).then(function (r) {
|
|
1302
|
+
if (!r.ok || !r.d || r.d.ok !== true || typeof r.d.synced !== 'boolean') {
|
|
1303
|
+
notePair[1]('同步结果未确认,请刷新状态查看。' + (r.d && r.d.error ? ' ' + String(r.d.error) : '')); return;
|
|
1304
|
+
}
|
|
1305
|
+
notePair[1](r.d.text || (r.d.synced ? '模型目录已同步' : '目录同步失败,保留上次完整目录'));
|
|
1306
|
+
props.onDone();
|
|
1307
|
+
}).catch(function () { notePair[1]('同步结果未确认,请刷新状态查看;本次没有发起模型生成。'); })
|
|
1308
|
+
.finally(function () { clearTimeout(timer); running.current = false; busyPair[1](false); });
|
|
1309
|
+
}
|
|
1310
|
+
var summary = busyPair[0] ? '正在同步…' : directory.checkedAt
|
|
1311
|
+
? (directory.status === 'stale' ? '缓存待更新' : '已同步') + ' · ' + directory.modelCount + ' 个模型 · 目录未列出 ' + (directory.missing || []).length + ' 项'
|
|
1312
|
+
: '尚未同步';
|
|
1313
|
+
return React.createElement('details', { className: 'iris-pm-field', 'data-provider-directory': p.id, 'aria-busy': busyPair[0] },
|
|
1314
|
+
React.createElement('summary', { className: 'iris-pm-note' }, '模型目录 · ' + summary),
|
|
1315
|
+
React.createElement('div', { className: 'iris-wb-row' },
|
|
1316
|
+
React.createElement('span', { className: 'iris-pm-note' }, directory.checkedAt ? '上次同步:' + new Date(directory.checkedAt).toLocaleString() : '目录与模型用途分别保存'),
|
|
1317
|
+
React.createElement('button', { className: 'iris-pm-btn', disabled: busyPair[0] || !p.directory || !p.enabled,
|
|
1318
|
+
onClick: synchronize }, busyPair[0] ? '正在同步…' : '同步目录')),
|
|
1319
|
+
!p.directory ? React.createElement('p', { className: 'iris-pm-note' }, '请先让 DSH 加载新版 Iris,再同步目录。') : null,
|
|
1320
|
+
directory.error && !notePair[0] ? React.createElement('p', { className: 'iris-pm-note', role: 'status' }, '上次同步未完成,保留旧目录:' + directory.error) : null,
|
|
1321
|
+
notePair[0] ? React.createElement('p', { className: 'iris-pm-note', role: 'status' }, notePair[0]) : null,
|
|
1322
|
+
React.createElement('p', { className: 'iris-pm-note' }, '同步只读取供应商目录,新模型仍需确认用途。目录未列出不等于下线;实测与生成需单独发起。'),
|
|
1323
|
+
(directory.added || []).length ? React.createElement('p', { className: 'iris-pm-note' }, '本次新增:' + directory.added.slice(0, 5).join('、')
|
|
1324
|
+
+ (directory.added.length > 5 ? ' 等 ' + directory.added.length + ' 项,完整条目见下方列表' : '')) : null,
|
|
1325
|
+
(directory.missing || []).length ? React.createElement('p', { className: 'iris-pm-note' }, '目录未列出:' + directory.missing.slice(0, 5).join('、')
|
|
1326
|
+
+ (directory.missing.length > 5 ? ' 等 ' + directory.missing.length + ' 项,原配置继续保留' : '')) : null);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
943
1329
|
function ProviderEndpointEditor(props) {
|
|
944
1330
|
var p = props.provider;
|
|
945
1331
|
var valuePair = React.useState(p.mediaBaseUrl || '');
|
|
@@ -970,11 +1356,22 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
970
1356
|
var adding = addingPair[0];
|
|
971
1357
|
var setAdding = addingPair[1];
|
|
972
1358
|
var newForm = React.useState({ name: '', baseUrl: '', mediaBaseUrl: '', apiKey: '', type: 'openai', mediaProtocol: 'auto' });
|
|
1359
|
+
var hostConnections = React.useState([]);
|
|
1360
|
+
var hostChoice = React.useState('');
|
|
1361
|
+
var setupPair = React.useState(false);
|
|
973
1362
|
var notePair = React.useState('');
|
|
974
1363
|
var note = notePair[0];
|
|
975
1364
|
var setNote = notePair[1];
|
|
976
1365
|
function refresh() {
|
|
977
|
-
postAction('providers_list', {}).then(function (r) { if (r.ok && r.d.providers)
|
|
1366
|
+
postAction('providers_list', {}).then(function (r) { if (r.ok && r.d.providers) {
|
|
1367
|
+
setList(r.d.providers); setupPair[1](r.d.setupSuggested === true);
|
|
1368
|
+
} });
|
|
1369
|
+
}
|
|
1370
|
+
function loadHostConnections() {
|
|
1371
|
+
postAction('providers_host_connections', {}).then(function (r) {
|
|
1372
|
+
if (r.ok) { hostConnections[1](r.d.connections || []); if (r.d.note) setNote(r.d.note); }
|
|
1373
|
+
else setNote(r.d.error || '无法读取 DSH 连接');
|
|
1374
|
+
});
|
|
978
1375
|
}
|
|
979
1376
|
React.useEffect(function () {
|
|
980
1377
|
refresh();
|
|
@@ -983,9 +1380,20 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
983
1380
|
}, []);
|
|
984
1381
|
function addProvider() {
|
|
985
1382
|
var f = newForm[0];
|
|
1383
|
+
if (hostChoice[0]) {
|
|
1384
|
+
var connection = hostConnections[0].find(function (item) { return JSON.stringify([item.settingsNs, item.provider]) === hostChoice[0]; });
|
|
1385
|
+
if (!connection || !connection.available) { setNote('请重新选择可用的 DSH 连接'); return; }
|
|
1386
|
+
postAction('providers_upsert', { name: f.name || connection.name, type: 'openai', enabled: true,
|
|
1387
|
+
mediaProtocol: f.mediaProtocol, dshProvider: { provider: connection.provider, settingsNs: connection.settingsNs } })
|
|
1388
|
+
.then(function (r) {
|
|
1389
|
+
if (r.ok) { setAdding(false); openPair[1](r.d.providerId); setNote('已引用 DSH 连接,请在下方确认模型用途,即可启用自动池'); refresh(); }
|
|
1390
|
+
else setNote(r.d.error || '添加失败');
|
|
1391
|
+
});
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
986
1394
|
if (!f.baseUrl || !f.apiKey) { setNote('baseUrl 和 apiKey 必填'); return; }
|
|
987
1395
|
postAction('providers_upsert', f).then(function (r) {
|
|
988
|
-
if (r.ok) { setAdding(false); setNote('
|
|
1396
|
+
if (r.ok) { setAdding(false); openPair[1](r.d.providerId); setNote('已连接,请在下方确认模型用途'); refresh(); }
|
|
989
1397
|
else setNote(r.d.error || '添加失败');
|
|
990
1398
|
});
|
|
991
1399
|
}
|
|
@@ -999,21 +1407,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
999
1407
|
if (!window.confirm('删除供应商「' + (p.name || p.id) + '」?此操作不可撤销。')) return;
|
|
1000
1408
|
postAction('providers_remove', { id: p.id }).then(function (r) { if (r.ok) refresh(); else setNote(r.d.error || '删除失败'); });
|
|
1001
1409
|
}
|
|
1002
|
-
function testVision(p) {
|
|
1003
|
-
if (!window.confirm('视觉实测会向供应商发送一张内置测试图,可能产生费用。继续吗?')) return;
|
|
1004
|
-
setNote('测试「' + (p.name || p.id) + '」的视觉能力…');
|
|
1005
|
-
postAction('providers_test_vision', { id: p.id, confirm_paid: true }).then(function (r) { setNote(r.d.text || r.d.error || '测试完成'); refresh(); });
|
|
1006
|
-
}
|
|
1007
|
-
function discover(p) {
|
|
1008
|
-
setNote('正在发现「' + (p.name || p.id) + '」的模型…');
|
|
1009
|
-
postAction('providers_discover', { id: p.id }).then(function (r) { setNote(r.d.text || r.d.error || '发现完成'); refresh(); });
|
|
1010
|
-
}
|
|
1011
1410
|
function testModel(p, m, capability) {
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
postAction('providers_test_model', { id: p.id, model_id: m.id, capability: capability, confirm_paid: !noProbe })
|
|
1016
|
-
.then(function (r) { setNote(r.d.text || r.d.error || ''); refresh(); });
|
|
1411
|
+
window.dispatchEvent(new CustomEvent('iris-capability-inspect', {
|
|
1412
|
+
detail: { capability: capability, providerId: p.id, modelId: m.id }
|
|
1413
|
+
}));
|
|
1017
1414
|
}
|
|
1018
1415
|
function setProtocol(p, value) {
|
|
1019
1416
|
postAction('providers_upsert', { id: p.id, mediaProtocol: value }).then(function (r) {
|
|
@@ -1024,16 +1421,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1024
1421
|
if (!window.confirm('从池中移除模型「' + m.id + '」?')) return;
|
|
1025
1422
|
postAction('providers_remove_model', { id: p.id, model_id: m.id }).then(function (r) { setNote(r.d.text || r.d.error || ''); refresh(); });
|
|
1026
1423
|
}
|
|
1027
|
-
function addModel(p, modelId) {
|
|
1028
|
-
if (!modelId) { setNote('模型名不能为空'); return; }
|
|
1029
|
-
postAction('providers_add_model', { id: p.id, model_id: modelId }).then(function (r) { setNote(r.d.text || r.d.error || ''); refresh(); });
|
|
1030
|
-
}
|
|
1031
1424
|
var cards = (list || []).map(function (p) {
|
|
1032
1425
|
var open = openPair[0] === p.id;
|
|
1033
1426
|
var body = null;
|
|
1034
1427
|
if (open) {
|
|
1035
|
-
body = React.createElement('div', { className: 'iris-pm-body' },
|
|
1428
|
+
body = React.createElement('div', { className: 'iris-pm-body', id: 'iris-provider-' + p.id },
|
|
1036
1429
|
React.createElement('div', { className: 'iris-pm-note' }, 'Key ' + (p.apiKeyHint || '未配置') + ' · ' + (p.type || '') + ' · ' + (p.mediaProtocol || '')),
|
|
1430
|
+
p.dshProvider ? React.createElement('div', { className: 'iris-pm-note' }, '引用 DSH:' + p.dshProvider.provider
|
|
1431
|
+
+ ' · 地址和密钥由宿主管理;' + (p.connection && p.connection.available ? '连接可用' : ((p.connection && p.connection.reason) || '连接不可用'))) : null,
|
|
1037
1432
|
p.protocolInferred ? React.createElement('div', { className: 'iris-pm-note' }, '协议为推断值,请确认') : null,
|
|
1038
1433
|
React.createElement(ProviderEndpointEditor, { provider: p, onNote: setNote, onDone: refresh }),
|
|
1039
1434
|
React.createElement(VisionInputEditor, { provider: p, onNote: setNote, onDone: refresh }),
|
|
@@ -1045,14 +1440,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1045
1440
|
React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
|
|
1046
1441
|
React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图'))),
|
|
1047
1442
|
React.createElement('div', { className: 'iris-pm-note' }, '能力: ' + ((p.capabilities || []).join(' / ') || '无')),
|
|
1048
|
-
React.createElement(
|
|
1443
|
+
React.createElement(ProviderDirectory, { provider: p, onDone: refresh }),
|
|
1444
|
+
React.createElement(ModelPool, { provider: p, setupSuggested: setupPair[0], onNote: setNote, onDone: refresh, testModel: testModel, removeModel: removeModel }),
|
|
1049
1445
|
React.createElement('div', { className: 'iris-wb-row' },
|
|
1050
|
-
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { discover(p); } }, '发现模型'),
|
|
1051
1446
|
React.createElement('button', { className: 'iris-pm-btn', onClick: function () { toggle(p); } }, p.enabled ? '停用' : '启用'),
|
|
1052
1447
|
React.createElement('button', { className: 'iris-pm-btn danger', onClick: function () { remove(p); } }, '删除')));
|
|
1053
1448
|
}
|
|
1054
1449
|
return React.createElement('div', { key: p.id, className: 'iris-pm-card' + (open ? ' open' : '') },
|
|
1055
|
-
React.createElement('
|
|
1450
|
+
React.createElement('button', { type: 'button', className: 'iris-pm-top', 'aria-expanded': open, 'aria-controls': 'iris-provider-' + p.id,
|
|
1451
|
+
onClick: function () { setOpen(open ? null : p.id); } },
|
|
1056
1452
|
React.createElement('span', { className: 'iris-pm-name' }, (p.name || p.id) + (p.enabled ? '' : '(停用)')),
|
|
1057
1453
|
React.createElement('span', { className: 'iris-pm-btn' }, open ? '收起' : '管理')),
|
|
1058
1454
|
body);
|
|
@@ -1060,12 +1456,19 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1060
1456
|
var addBlock = null;
|
|
1061
1457
|
if (adding) {
|
|
1062
1458
|
addBlock = React.createElement('div', { className: 'iris-pm-card' },
|
|
1459
|
+
React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '连接来源'),
|
|
1460
|
+
React.createElement('select', { value: hostChoice[0], onChange: function (e) { hostChoice[1](e.target.value); } },
|
|
1461
|
+
React.createElement('option', { value: '' }, '手动填写供应商'),
|
|
1462
|
+
hostConnections[0].map(function (connection) { return React.createElement('option', {
|
|
1463
|
+
key: JSON.stringify([connection.settingsNs, connection.provider]), value: JSON.stringify([connection.settingsNs, connection.provider]), disabled: !connection.available
|
|
1464
|
+
}, 'DSH · ' + connection.name + (connection.available ? '' : '(' + connection.reason + ')')); }))),
|
|
1465
|
+
hostChoice[0] ? React.createElement('div', { className: 'iris-pm-note' }, '地址与密钥随 DSH 连接更新,不保存密钥副本。请选择该服务实际支持的媒体协议;模型能力仍需明确配置。') : null,
|
|
1063
1466
|
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 }); } })),
|
|
1064
|
-
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 }); } })),
|
|
1065
|
-
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 }); } })),
|
|
1066
|
-
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 }); } })),
|
|
1467
|
+
!hostChoice[0] ? 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 }); } })) : null,
|
|
1468
|
+
!hostChoice[0] ? 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 }); } })) : null,
|
|
1469
|
+
!hostChoice[0] ? 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 }); } })) : null,
|
|
1067
1470
|
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 }); } },
|
|
1068
|
-
React.createElement('option', { value: 'auto' }, '自动(按媒体 Base URL 安全判断)'),
|
|
1471
|
+
React.createElement('option', { value: 'auto' }, hostChoice[0] ? '请选择该服务支持的媒体协议' : '自动(按媒体 Base URL 安全判断)'),
|
|
1069
1472
|
React.createElement('option', { value: 'dashscope' }, 'DashScope(阿里云官方)'),
|
|
1070
1473
|
React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'),
|
|
1071
1474
|
React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
|
|
@@ -1077,7 +1480,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1077
1480
|
return React.createElement('div', { className: 'iris-pm' },
|
|
1078
1481
|
React.createElement('div', { className: 'iris-pm-head' },
|
|
1079
1482
|
React.createElement('span', { className: 'iris-wb-muted' }, (list ? list.length : 0) + ' 个供应商'),
|
|
1080
|
-
React.createElement('button', { className: 'iris-pm-add', onClick: function () { setAdding(!adding); } }, adding ? '收起' : '+ 添加供应商')),
|
|
1483
|
+
React.createElement('button', { className: 'iris-pm-add', onClick: function () { if (!adding) { hostChoice[1](''); loadHostConnections(); } setAdding(!adding); } }, adding ? '收起' : '+ 添加供应商')),
|
|
1081
1484
|
addBlock,
|
|
1082
1485
|
cards.length ? cards : React.createElement('div', { className: 'iris-wb-empty' }, '暂无供应商 — 点右上角「+ 添加供应商」'),
|
|
1083
1486
|
note ? React.createElement('div', { className: 'iris-pm-act' }, note) : null);
|
|
@@ -1169,9 +1572,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1169
1572
|
var setModelInfo = modelPair[1];
|
|
1170
1573
|
var irisState = useIrisState(5000);
|
|
1171
1574
|
function setVal(key, value) { var nv = {}; Object.keys(vals).forEach(function (k) { nv[k] = vals[k]; }); nv[key] = value; setVals(nv); }
|
|
1172
|
-
function loadModels() {
|
|
1575
|
+
function loadModels(event) {
|
|
1173
1576
|
if (!capability) return;
|
|
1174
|
-
|
|
1577
|
+
var snapshot = event && event.detail && event.detail.functionAssignments;
|
|
1578
|
+
(snapshot ? Promise.resolve({ ok: true, d: snapshot }) : postAction('assignments_get', {})).then(function (r) {
|
|
1175
1579
|
if (!r.ok || !r.d) return;
|
|
1176
1580
|
var opts = (r.d.poolByCapability && r.d.poolByCapability[capability]) || [];
|
|
1177
1581
|
var ord = (r.d.order && r.d.order[capability]) || [];
|
|
@@ -1179,10 +1583,17 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1179
1583
|
var assignedRef = ord[0] || '';
|
|
1180
1584
|
var assignedOpt = opts.find(function (o) { return o.ref === assignedRef; });
|
|
1181
1585
|
// 不再自动填 vals.model:留空 = 用能力分配的解析结果(避免绕过分配)
|
|
1182
|
-
setModelInfo({ assigned: assignedOpt ? assignedOpt.label : '', assignedRef: assignedRef, options: opts, loaded: true
|
|
1586
|
+
setModelInfo({ assigned: assignedOpt ? assignedOpt.label : '', assignedRef: assignedRef, options: opts, loaded: true,
|
|
1587
|
+
mode: r.d.policies && r.d.policies[capability] && r.d.policies[capability].mode,
|
|
1588
|
+
readiness: r.d.readiness && r.d.readiness[capability] });
|
|
1183
1589
|
});
|
|
1184
1590
|
}
|
|
1185
|
-
React.useEffect(function () {
|
|
1591
|
+
React.useEffect(function () {
|
|
1592
|
+
if (!open || !capability) return;
|
|
1593
|
+
loadModels();
|
|
1594
|
+
window.addEventListener('iris-function-configuration-changed', loadModels);
|
|
1595
|
+
return function () { window.removeEventListener('iris-function-configuration-changed', loadModels); };
|
|
1596
|
+
}, [open]);
|
|
1186
1597
|
function submit() {
|
|
1187
1598
|
if (run === 'running') return;
|
|
1188
1599
|
setRun('running');
|
|
@@ -1199,13 +1610,18 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1199
1610
|
.catch(function () { setRun({ ok: false, text: '网络错误' }); });
|
|
1200
1611
|
}
|
|
1201
1612
|
var cardHealth = capability ? capabilityHealth(irisState, capability) : { status: 'verified' };
|
|
1613
|
+
// Host references can be ready without raw endpoint/key fields in the state snapshot.
|
|
1614
|
+
if (modelInfo && modelInfo.readiness) {
|
|
1615
|
+
if (modelInfo.mode === 'disabled') cardHealth = { status: 'unconfigured' };
|
|
1616
|
+
else if (modelInfo.readiness.candidateCount > 0 && cardHealth.status === 'unconfigured') cardHealth = { status: 'configured' };
|
|
1617
|
+
}
|
|
1202
1618
|
var body = null;
|
|
1203
1619
|
if (open) {
|
|
1204
1620
|
var inputs = fields.map(function (f) {
|
|
1205
1621
|
var node;
|
|
1206
1622
|
if (f.key === 'model' && capability) {
|
|
1207
1623
|
node = React.createElement('select', { value: vals.model || '', onChange: function (e) { setVal('model', e.target.value); } },
|
|
1208
|
-
React.createElement('option', { value: '' }, '
|
|
1624
|
+
React.createElement('option', { value: '' }, modelInfo && modelInfo.mode === 'auto' ? '按功能池顺序自动选择' : '按所选模型顺序'),
|
|
1209
1625
|
((modelInfo && modelInfo.options) || []).map(function (option) {
|
|
1210
1626
|
return React.createElement('option', { key: option.ref, value: option.ref, disabled: option.unavailable }, option.label + (option.unavailable ? ' · 当前不可用' : ''));
|
|
1211
1627
|
}));
|
|
@@ -1249,14 +1665,15 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1249
1665
|
// 只读模型提示:当前能力解析到的模型与持久健康事实。
|
|
1250
1666
|
var modelBar = null;
|
|
1251
1667
|
if (capability) {
|
|
1252
|
-
var available = modelInfo && modelInfo.loaded && modelInfo.options.length > 0;
|
|
1668
|
+
var available = modelInfo && modelInfo.loaded && (modelInfo.readiness ? modelInfo.readiness.candidateCount > 0 : modelInfo.options.length > 0);
|
|
1253
1669
|
var shown = (vals.model && vals.model.trim()) || (modelInfo && modelInfo.assigned) || '';
|
|
1254
1670
|
modelBar = React.createElement('div', { className: 'iris-act-field' },
|
|
1255
1671
|
React.createElement('label', {},
|
|
1256
1672
|
React.createElement('span', { className: 'iris-pm-dot ' + healthClass(cardHealth) }, cardHealth.retryAt ? '◷' : HEALTH_META[cardHealth.status].symbol),
|
|
1257
1673
|
' 模型:' + (shown || '未分配') + (available ? '' : '(无可用模型)')),
|
|
1258
1674
|
React.createElement('div', { className: 'iris-wb-muted' },
|
|
1259
|
-
|
|
1675
|
+
(modelInfo && modelInfo.readiness ? modelInfo.readiness.candidateCount + ' 个候选 · ' + (modelInfo.readiness.note || '实际权限以调用结果为准') : healthText(cardHealth))
|
|
1676
|
+
+ ' · 调整方式 → 上方「功能池与调用方式」'));
|
|
1260
1677
|
}
|
|
1261
1678
|
body = React.createElement('div', { className: 'iris-act-body' },
|
|
1262
1679
|
modelBar,
|
|
@@ -1268,7 +1685,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1268
1685
|
var headStatus = capability ? cardHealth.status : 'verified';
|
|
1269
1686
|
var headTitle = capability ? healthText(cardHealth) : '本地功能';
|
|
1270
1687
|
return React.createElement('div', {
|
|
1271
|
-
className: 'iris-act-card' + (open ? ' open' : '') + (capability ? ' health-' + headStatus : '')
|
|
1688
|
+
className: 'iris-act-card' + (open ? ' open' : '') + (capability ? ' health-' + headStatus : ''), 'data-iris-action': action
|
|
1272
1689
|
},
|
|
1273
1690
|
React.createElement('div', { className: 'iris-act-head', onClick: function () { setOpen(!open); } },
|
|
1274
1691
|
React.createElement('span', { className: 'iris-act-title' }, title),
|
|
@@ -1360,59 +1777,168 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1360
1777
|
{ cap: 'vision', label: '👁 视觉' }
|
|
1361
1778
|
];
|
|
1362
1779
|
function CapabilityAssigner() {
|
|
1363
|
-
var dataPair = React.useState(null);
|
|
1364
|
-
var
|
|
1365
|
-
var
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1780
|
+
var dataPair = React.useState(null), data = dataPair[0], setData = dataPair[1];
|
|
1781
|
+
var notePair = React.useState('');
|
|
1782
|
+
var feedbackPair = React.useState({}), feedback = feedbackPair[0];
|
|
1783
|
+
var busyPair = React.useState(null), busy = busyPair[0];
|
|
1784
|
+
var loadingPair = React.useState(false), loading = loadingPair[0];
|
|
1785
|
+
var confirmPair = React.useState(null), confirmation = confirmPair[0];
|
|
1786
|
+
var saving = React.useRef(false), readVersion = React.useRef(0);
|
|
1787
|
+
var labels = { auto: '按功能自动选择', manual: '仅用所选模型', disabled: '停用' };
|
|
1788
|
+
function compatible(snapshot) {
|
|
1789
|
+
return !!(snapshot && snapshot.policies && snapshot.readiness && CAP_ROWS.every(function (row) {
|
|
1790
|
+
return snapshot.policies[row.cap] && ['auto', 'manual', 'disabled'].includes(snapshot.policies[row.cap].mode) && snapshot.readiness[row.cap];
|
|
1791
|
+
}));
|
|
1370
1792
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1793
|
+
function setFeedback(cap, value) {
|
|
1794
|
+
feedbackPair[1](function (previous) { return { ...previous, [cap]: value }; });
|
|
1795
|
+
}
|
|
1796
|
+
function request(action, body) {
|
|
1797
|
+
var controller = new AbortController();
|
|
1798
|
+
var timer = setTimeout(function () { controller.abort(); }, 10000);
|
|
1799
|
+
return postAction(action, body, { signal: controller.signal }).finally(function () { clearTimeout(timer); });
|
|
1800
|
+
}
|
|
1801
|
+
function load(event) {
|
|
1802
|
+
var version = ++readVersion.current;
|
|
1803
|
+
var shared = event && event.detail && event.detail.functionAssignments;
|
|
1804
|
+
if (shared) { setData(shared); loadingPair[1](false); return Promise.resolve(shared); }
|
|
1805
|
+
loadingPair[1](true);
|
|
1806
|
+
return request('assignments_get', {}).then(function (r) {
|
|
1807
|
+
if (version !== readVersion.current) return null;
|
|
1808
|
+
if (!r.ok || !r.d || r.d.ok !== true) throw new Error(r.d && r.d.error || '无法读取功能池');
|
|
1809
|
+
setData(r.d); notePair[1](''); return r.d;
|
|
1810
|
+
}).catch(function (error) {
|
|
1811
|
+
if (version === readVersion.current) notePair[1](error.name === 'AbortError' ? '读取状态超时,请重新检查' : '无法读取功能池,请检查与宿主的连接');
|
|
1812
|
+
return null;
|
|
1813
|
+
}).finally(function () { if (version === readVersion.current) loadingPair[1](false); });
|
|
1814
|
+
}
|
|
1815
|
+
React.useEffect(function () {
|
|
1816
|
+
load();
|
|
1817
|
+
window.addEventListener('iris-function-configuration-changed', load);
|
|
1818
|
+
return function () { window.removeEventListener('iris-function-configuration-changed', load); };
|
|
1819
|
+
}, []);
|
|
1820
|
+
function update(body) {
|
|
1821
|
+
if (saving.current || !compatible(data)) return;
|
|
1822
|
+
saving.current = true; busyPair[1](body.capability); confirmPair[1](null);
|
|
1823
|
+
++readVersion.current; // A read started before this write cannot overwrite its result.
|
|
1824
|
+
loadingPair[1](false);
|
|
1825
|
+
var desired = body.mode || (body.model_refs.length ? 'manual' : 'disabled');
|
|
1826
|
+
setFeedback(body.capability, { phase: 'saving', message: '正在保存…' });
|
|
1827
|
+
request('assignments_set', body).then(function (r) {
|
|
1828
|
+
if (!r.ok || !r.d || r.d.ok !== true) throw new Error(r.d && r.d.error || '未收到保存确认,请刷新状态核对');
|
|
1829
|
+
if ((body.mode && r.d.mode !== body.mode) || !Array.isArray(r.d.model_refs)) {
|
|
1830
|
+
setData(function (previous) { return { ...previous, policies: null, readiness: null }; });
|
|
1831
|
+
throw new Error('服务端未确认新策略,请重启 DSH 后刷新页面');
|
|
1832
|
+
}
|
|
1833
|
+
setData(function (previous) { return { ...previous,
|
|
1834
|
+
policies: { ...previous.policies, [body.capability]: { ...previous.policies[body.capability], mode: desired,
|
|
1835
|
+
...(body.hostVision !== undefined ? { hostVision: body.hostVision } : {}) } },
|
|
1836
|
+
order: { ...previous.order, [body.capability]: r.d.model_refs }
|
|
1837
|
+
}; });
|
|
1838
|
+
setFeedback(body.capability, { phase: 'refreshing', message: '已保存为“' + labels[desired] + '”,正在更新候选…' });
|
|
1839
|
+
return load().then(function (snapshot) {
|
|
1840
|
+
if (!snapshot) {
|
|
1841
|
+
setFeedback(body.capability, { phase: 'saved-stale', message: '已保存为“' + labels[desired] + '”,候选未能刷新,请核对状态' });
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
var policy = snapshot.policies && snapshot.policies[body.capability];
|
|
1845
|
+
var confirmed = compatible(snapshot) && policy.mode === desired
|
|
1846
|
+
&& (body.hostVision === undefined || policy.hostVision === body.hostVision);
|
|
1847
|
+
setFeedback(body.capability, confirmed
|
|
1848
|
+
? { phase: 'saved', message: '已保存:' + labels[desired] + ' · ' + snapshot.readiness[body.capability].candidateCount + ' 个可用候选' }
|
|
1849
|
+
: { phase: 'unknown', message: '读取的状态与本次选择不一致,请核对服务端状态' });
|
|
1850
|
+
window.dispatchEvent(new CustomEvent('iris-function-configuration-changed', { detail: { functionAssignments: snapshot } }));
|
|
1851
|
+
});
|
|
1852
|
+
}).catch(function (error) {
|
|
1853
|
+
setFeedback(body.capability, { phase: 'unknown', message: error.name === 'AbortError'
|
|
1854
|
+
? '未收到保存确认,请刷新状态核对,暂勿重复提交' : error.message || '保存结果未确认,请刷新状态核对' });
|
|
1855
|
+
}).finally(function () { saving.current = false; busyPair[1](null); });
|
|
1376
1856
|
}
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
var opts = poolBy[row.cap] || [];
|
|
1383
|
-
var available = opts.filter(function (o) { return list.indexOf(o.ref) < 0; });
|
|
1384
|
-
var chips = list.map(function (ref, i) {
|
|
1385
|
-
var opt = opts.find(function (o) { return o.ref === ref; });
|
|
1386
|
-
return React.createElement('span', { key: ref, className: 'iris-cap-chip' },
|
|
1387
|
-
React.createElement('b', {}, (i + 1) + '.'),
|
|
1388
|
-
opt ? opt.label + (opt.unavailable ? ' · 当前不可用' : '') : ref + ' · 配置已失效',
|
|
1389
|
-
i > 0 ? React.createElement('button', {
|
|
1390
|
-
className: 'iris-pm-btn', title: '上移',
|
|
1391
|
-
onClick: function () { var n = list.slice(); var t = n.splice(i, 1)[0]; n.splice(i - 1, 0, t); save(row.cap, n); }
|
|
1392
|
-
}, '↑') : null,
|
|
1393
|
-
i < list.length - 1 ? React.createElement('button', {
|
|
1394
|
-
className: 'iris-pm-btn', title: '下移',
|
|
1395
|
-
onClick: function () { var n = list.slice(); var t = n.splice(i, 1)[0]; n.splice(i + 1, 0, t); save(row.cap, n); }
|
|
1396
|
-
}, '↓') : null,
|
|
1397
|
-
React.createElement('button', {
|
|
1398
|
-
className: 'iris-pm-btn danger', title: '移出列表',
|
|
1399
|
-
onClick: function () { save(row.cap, list.filter(function (x) { return x !== ref; })); }
|
|
1400
|
-
}, '✕'));
|
|
1857
|
+
function check(cap) {
|
|
1858
|
+
if (saving.current) return;
|
|
1859
|
+
return load().then(function (snapshot) {
|
|
1860
|
+
if (compatible(snapshot)) setFeedback(cap, { phase: 'saved', message: '当前已保存:' + labels[snapshot.policies[cap].mode]
|
|
1861
|
+
+ ' · ' + snapshot.readiness[cap].candidateCount + ' 个可用候选' });
|
|
1401
1862
|
});
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
onClick: function () {
|
|
1414
|
-
|
|
1415
|
-
|
|
1863
|
+
}
|
|
1864
|
+
function save(cap, refs) { update({ capability: cap, model_refs: refs }); }
|
|
1865
|
+
if (!data) return React.createElement('div', { className: 'iris-wb-muted', role: 'status' },
|
|
1866
|
+
notePair[0] || '加载功能池…', notePair[0] ? React.createElement('button', { className: 'iris-pm-btn', disabled: loading,
|
|
1867
|
+
onClick: function () { load(); } }, loading ? '正在检查…' : '重新检查') : null);
|
|
1868
|
+
var supported = compatible(data);
|
|
1869
|
+
var order = data.order || {}, poolBy = data.poolByCapability || {};
|
|
1870
|
+
return React.createElement('div', { className: 'iris-cap-grid' },
|
|
1871
|
+
!supported ? React.createElement('div', { className: 'iris-cap-compatibility', role: 'alert' },
|
|
1872
|
+
'当前服务端尚未加载功能池策略,已暂停切换。请重启 DSH 后刷新页面;仅刷新页面无法更新服务端。',
|
|
1873
|
+
React.createElement('button', { className: 'iris-pm-btn', disabled: loading,
|
|
1874
|
+
onClick: function () { load(); } }, loading ? '正在检查…' : '重新检查服务端')) : null,
|
|
1875
|
+
CAP_ROWS.map(function (row) {
|
|
1876
|
+
var policy = data.policies && data.policies[row.cap] || { mode: (order[row.cap] || []).length ? 'manual' : 'disabled', models: order[row.cap] || [] };
|
|
1877
|
+
var mode = policy.mode;
|
|
1878
|
+
var list = order[row.cap] || [];
|
|
1879
|
+
var opts = poolBy[row.cap] || [];
|
|
1880
|
+
var status = data.readiness && data.readiness[row.cap];
|
|
1881
|
+
var available = opts.filter(function (o) { return list.indexOf(o.ref) < 0; });
|
|
1882
|
+
var message = feedback[row.cap];
|
|
1883
|
+
var blocked = !supported || !!busy || (message && message.phase === 'unknown');
|
|
1884
|
+
function setMode(value, hostVision) {
|
|
1885
|
+
if (blocked || saving.current) return;
|
|
1886
|
+
var body = { capability: row.cap, mode: value,
|
|
1887
|
+
...(row.cap === 'vision' ? { hostVision: hostVision === undefined ? policy.hostVision === true : hostVision } : {}) };
|
|
1888
|
+
if (value === 'auto' && mode !== 'auto') {
|
|
1889
|
+
var count = opts.filter(function (o) { return !o.unavailable && !o.pendingConfirmation
|
|
1890
|
+
&& (o.ref !== 'host:current-vision' || policy.hostVision === true); }).length;
|
|
1891
|
+
setFeedback(row.cap, null);
|
|
1892
|
+
confirmPair[1]({ cap: row.cap, body: body, count: count }); return;
|
|
1893
|
+
}
|
|
1894
|
+
update(body);
|
|
1895
|
+
}
|
|
1896
|
+
var chips = list.map(function (ref, i) {
|
|
1897
|
+
var opt = opts.find(function (o) { return o.ref === ref; });
|
|
1898
|
+
return React.createElement('span', { key: ref, className: 'iris-cap-chip' },
|
|
1899
|
+
React.createElement('b', {}, (i + 1) + '.'),
|
|
1900
|
+
opt ? opt.label + (opt.unavailable ? ' · ' + (opt.reason || '当前不可用') : '') : ref + ' · 配置已失效',
|
|
1901
|
+
mode === 'manual' && i > 0 ? React.createElement('button', { className: 'iris-pm-btn', title: '上移', disabled: blocked,
|
|
1902
|
+
onClick: function () { var n = list.slice(); var t = n.splice(i, 1)[0]; n.splice(i - 1, 0, t); save(row.cap, n); }
|
|
1903
|
+
}, '↑') : null,
|
|
1904
|
+
mode === 'manual' && i < list.length - 1 ? React.createElement('button', { className: 'iris-pm-btn', title: '下移', disabled: blocked,
|
|
1905
|
+
onClick: function () { var n = list.slice(); var t = n.splice(i, 1)[0]; n.splice(i + 1, 0, t); save(row.cap, n); }
|
|
1906
|
+
}, '↓') : null,
|
|
1907
|
+
mode === 'manual' ? React.createElement('button', { className: 'iris-pm-btn danger', title: '移出列表', disabled: blocked,
|
|
1908
|
+
onClick: function () { save(row.cap, list.filter(function (x) { return x !== ref; })); }
|
|
1909
|
+
}, '✕') : null);
|
|
1910
|
+
});
|
|
1911
|
+
return React.createElement('div', { key: row.cap, className: 'iris-cap-row', 'data-function': row.cap, 'aria-busy': busy === row.cap },
|
|
1912
|
+
React.createElement('span', { className: 'iris-cap-name' }, row.label),
|
|
1913
|
+
React.createElement('select', { 'aria-label': row.cap + ' 调用方式', value: confirmation && confirmation.cap === row.cap ? 'auto' : mode, disabled: blocked,
|
|
1914
|
+
onChange: function (e) { setMode(e.target.value); } },
|
|
1915
|
+
React.createElement('option', { value: 'auto' }, '按功能自动选择'),
|
|
1916
|
+
React.createElement('option', { value: 'manual' }, '仅用所选模型'),
|
|
1917
|
+
React.createElement('option', { value: 'disabled' }, '停用')),
|
|
1918
|
+
status && !(confirmation && confirmation.cap === row.cap) && !(message && ['saving', 'refreshing', 'saved-stale', 'unknown'].includes(message.phase)) ? React.createElement('span', { className: 'iris-pm-note', title: '候选来自配置和连接元数据;实际接口权限以调用结果为准' },
|
|
1919
|
+
status.candidateCount + ' 个可用候选' + (status.note ? ' · ' + status.note : '')) : null,
|
|
1920
|
+
confirmation && confirmation.cap === row.cap ? React.createElement('div', { className: 'iris-cap-confirm', role: 'group', 'aria-label': '确认自动选择' },
|
|
1921
|
+
React.createElement('div', {}, '尚未保存:将启用按功能自动选择,当前有 ' + confirmation.count + ' 个已确认候选。保留原手工名单;未确认的新发现模型不会自动调用。'),
|
|
1922
|
+
React.createElement('button', { className: 'iris-pm-btn', disabled: !!busy, onClick: function () { update(confirmation.body); } }, '确认启用自动池'),
|
|
1923
|
+
React.createElement('button', { className: 'iris-pm-btn', disabled: !!busy, onClick: function () { confirmPair[1](null); } }, '取消')) : null,
|
|
1924
|
+
message ? React.createElement('div', { className: 'iris-cap-feedback ' + message.phase, role: message.phase === 'unknown' ? 'alert' : 'status', 'aria-live': 'polite' },
|
|
1925
|
+
message.message, ['unknown', 'saved-stale'].includes(message.phase) ? React.createElement('button', { className: 'iris-pm-btn', disabled: !!busy || loading,
|
|
1926
|
+
onClick: function () { check(row.cap); } }, loading ? '正在核对…' : '刷新状态') : null) : null,
|
|
1927
|
+
mode === 'auto' ? React.createElement('span', { className: 'iris-pm-note' }, '按池顺序选择;确认模型用途后自动加入,无需再次分配。') : null,
|
|
1928
|
+
mode === 'auto' && row.cap === 'vision' ? React.createElement('label', { className: 'iris-pm-note' },
|
|
1929
|
+
React.createElement('input', { type: 'checkbox', checked: policy.hostVision === true, disabled: blocked,
|
|
1930
|
+
onChange: function (e) { setMode('auto', e.target.checked); } }),
|
|
1931
|
+
'使用DSH当前视觉模型(执行前核对图片能力)') : null,
|
|
1932
|
+
chips,
|
|
1933
|
+
mode === 'manual' && available.length ? React.createElement('select', { className: 'iris-pm-model-sel', value: '', disabled: blocked,
|
|
1934
|
+
onChange: function (e) { if (e.target.value) save(row.cap, list.concat([e.target.value])); } },
|
|
1935
|
+
React.createElement('option', { value: '' }, '+ 分配模型'),
|
|
1936
|
+
available.map(function (o) { return React.createElement('option', { key: o.ref, value: o.ref }, o.label); })) : null,
|
|
1937
|
+
mode === 'manual' && list.length ? React.createElement('button', { className: 'iris-pm-btn', title: '清空分配后,该功能不再发起新调用', disabled: blocked,
|
|
1938
|
+
onClick: function () { save(row.cap, []); }
|
|
1939
|
+
}, '清空分配') : null);
|
|
1940
|
+
}),
|
|
1941
|
+
notePair[0] ? React.createElement('div', { className: 'iris-pm-note' }, notePair[0]) : null);
|
|
1416
1942
|
}
|
|
1417
1943
|
|
|
1418
1944
|
/* ---- 泡泡常用卡片选择(localStorage + 跨座位响应) ---- */
|
|
@@ -1829,7 +2355,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1829
2355
|
React.createElement('label', {}, '改图模型', React.createElement('select', { 'aria-label': '改图模型', value: model, disabled: !!running,
|
|
1830
2356
|
onChange: function (e) { setModel(e.target.value); } }, React.createElement('option', { value: '' }, '按当前顺序选择可用改图模型'),
|
|
1831
2357
|
(models || []).map(function (entry) { return React.createElement('option', { key: entry.id, value: entry.id, disabled: entry.blocked }, entry.label + (entry.blocked ? ' · 冷却或额度耗尽' : '')); }))),
|
|
1832
|
-
models && !models.some(function (entry) { return !entry.blocked; }) ? React.createElement('div', { role: 'status' }, '
|
|
2358
|
+
models && !models.some(function (entry) { return !entry.blocked; }) ? React.createElement('div', { role: 'status' }, '暂无可用改图模型,请检查图片功能的调用方式,确认聊天生图或 DashScope Qwen 改图模型用途及连接。') : null,
|
|
1833
2359
|
React.createElement('div', { className: 'iris-gallery-actions' },
|
|
1834
2360
|
React.createElement('button', { className: 'iris-clean-btn', type: 'button', disabled: !!running || !prompt.trim() || !models || !models.some(function (entry) { return !entry.blocked; }), onClick: execute }, running ? '编辑中…' : '生成修改版'),
|
|
1835
2361
|
running ? React.createElement('button', { className: 'iris-pm-btn', type: 'button', onClick: cancel }, '停止等待') : null),
|
|
@@ -2616,6 +3142,80 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2616
3142
|
note ? React.createElement('div', { className: 'iris-clean-note' }, note) : null);
|
|
2617
3143
|
}
|
|
2618
3144
|
|
|
3145
|
+
function BrowserCheckPanel(props) {
|
|
3146
|
+
var statePair = React.useState({ status: 'idle', message: '尚未实测' });
|
|
3147
|
+
var state = statePair[0], setState = statePair[1];
|
|
3148
|
+
var request = React.useRef(null);
|
|
3149
|
+
React.useEffect(function () { return function () {
|
|
3150
|
+
var operation = request.current;
|
|
3151
|
+
request.current = null;
|
|
3152
|
+
if (operation) { clearTimeout(operation.timer); operation.controller.abort(); }
|
|
3153
|
+
}; }, []);
|
|
3154
|
+
|
|
3155
|
+
function stop(timedOut) {
|
|
3156
|
+
var operation = request.current;
|
|
3157
|
+
if (!operation) return;
|
|
3158
|
+
request.current = null;
|
|
3159
|
+
clearTimeout(operation.timer);
|
|
3160
|
+
operation.controller.abort();
|
|
3161
|
+
setState({ status: 'canceled', message: timedOut
|
|
3162
|
+
? '截图自检超过 60 秒,已停止等待。若恰好完成保存,可在作品库查看。'
|
|
3163
|
+
: '已取消本次自检等待。若恰好完成保存,可在作品库查看。' });
|
|
3164
|
+
}
|
|
3165
|
+
function start() {
|
|
3166
|
+
if (request.current || !props.available) return;
|
|
3167
|
+
var operation = { controller: new AbortController(), started: Date.now() };
|
|
3168
|
+
request.current = operation;
|
|
3169
|
+
setState({ status: 'running', message: '正在打开浏览器并生成测试截图…' });
|
|
3170
|
+
operation.timer = setTimeout(function () { if (request.current === operation) stop(true); }, 60000);
|
|
3171
|
+
fetch('/iris/api/actions/html', {
|
|
3172
|
+
method: 'POST', headers: { 'content-type': 'application/json' }, credentials: 'same-origin',
|
|
3173
|
+
signal: operation.controller.signal,
|
|
3174
|
+
body: JSON.stringify({ fullPage: false, width: 480, height: 240,
|
|
3175
|
+
html: '<style>body{margin:0;background:#edf3f1;color:#183e34;font-family:system-ui,sans-serif}.iris-test{padding:32px}h1{font-size:26px;margin:0 0 12px}p{font-size:16px;margin:8px 0}</style><div class="iris-test"><h1>Iris · 截图自检</h1><p>中文与 HTML 渲染测试</p><p>本页只使用内联样式,不调用模型。</p></div>' })
|
|
3176
|
+
}).then(function (res) { return res.json().then(function (data) {
|
|
3177
|
+
if (!res.ok || data.ok !== true) throw Object.assign(new Error(data.error || '截图自检失败'), { code: data.code });
|
|
3178
|
+
var id = data.artifactIds && data.artifactIds[0];
|
|
3179
|
+
if (data.storage !== 'core' || !/^artifact_[a-f0-9]{24}$/.test(id || '')) throw new Error('服务端未返回有效截图作品,请核对前后端版本。');
|
|
3180
|
+
return id;
|
|
3181
|
+
}); }).then(function (id) {
|
|
3182
|
+
if (request.current !== operation || operation.controller.signal.aborted) return;
|
|
3183
|
+
setState({ status: 'passed', artifactId: id, message: '本次截图已保存 · ' + ((Date.now() - operation.started) / 1000).toFixed(1) + ' 秒 · ' + new Date().toLocaleTimeString() });
|
|
3184
|
+
window.dispatchEvent(new CustomEvent('iris-core-artifacts-changed'));
|
|
3185
|
+
}).catch(function (error) {
|
|
3186
|
+
if (request.current !== operation || operation.controller.signal.aborted) return;
|
|
3187
|
+
var code = String(error && error.code || '');
|
|
3188
|
+
var detail = String(error && error.message || error);
|
|
3189
|
+
var message = '截图自检未通过。请查看错误详情。';
|
|
3190
|
+
if (code === 'IRIS_RENDER_ADDRESS_INVALID') message = '截图地址配置有误,请核对 DSH_WEB_BASE。';
|
|
3191
|
+
else if (code === 'IRIS_CORE_DATA_ROOT_BUSY') message = '作品库正在被其他操作写入,请稍后重试。';
|
|
3192
|
+
else if (/^IRIS_HOST_CAPABILITY_/.test(code)) message = '宿主浏览器接口不可用,请检查浏览器插件是否启用。';
|
|
3193
|
+
else if (/Missing X server|DISPLAY|BROWSER_HOST_/.test(detail + code)) message = '浏览器进程未能启动,请检查运行环境与显示服务。';
|
|
3194
|
+
else if (/BROWSER_NAVIGATION|ERR_CONNECTION_REFUSED|ERR_NAME_NOT_RESOLVED/.test(detail + code)) message = '浏览器未能打开渲染页面,请核对上方截图地址与宿主监听状态。';
|
|
3195
|
+
else if (/BROWSER_SCREENSHOT/.test(code)) message = '浏览器未能生成截图,请查看错误详情。';
|
|
3196
|
+
setState({ status: 'failed', message: message, detail: detail, code: code });
|
|
3197
|
+
}).finally(function () {
|
|
3198
|
+
clearTimeout(operation.timer);
|
|
3199
|
+
if (request.current === operation) request.current = null;
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
var busy = state.status === 'running';
|
|
3203
|
+
return React.createElement('div', { className: 'iris-browser-check', 'data-iris-browser-check': state.status },
|
|
3204
|
+
React.createElement('div', { className: 'iris-doctor-top' },
|
|
3205
|
+
React.createElement('strong', { className: 'iris-doctor-score' }, 'HTML 截图'),
|
|
3206
|
+
React.createElement('button', { className: 'iris-pm-btn', disabled: busy || !props.available, onClick: start }, busy ? '检测中…' : '截图自检'),
|
|
3207
|
+
busy ? React.createElement('button', { className: 'iris-pm-btn', onClick: function () { stop(false); } }, '取消') : null),
|
|
3208
|
+
React.createElement('div', { className: 'iris-browser-check-note' + (state.status === 'failed' ? ' error' : ''), role: 'status', 'aria-live': 'polite' }, state.message),
|
|
3209
|
+
props.address ? React.createElement('div', { className: 'iris-browser-check-note iris-wb-muted' }, props.address) : null,
|
|
3210
|
+
React.createElement('div', { className: 'iris-wb-muted' }, '点击后打开浏览器,生成一张本地测试作品;不调用模型。'),
|
|
3211
|
+
state.artifactId ? React.createElement('details', { className: 'iris-doctor-details' },
|
|
3212
|
+
React.createElement('summary', {}, '查看测试截图'),
|
|
3213
|
+
React.createElement('img', { src: '/iris/api/core/artifact/' + state.artifactId + '/media', alt: 'Iris HTML 截图自检结果', loading: 'lazy' })) : null,
|
|
3214
|
+
state.detail ? React.createElement('details', { className: 'iris-doctor-details' },
|
|
3215
|
+
React.createElement('summary', {}, '错误详情'),
|
|
3216
|
+
React.createElement('div', { className: 'iris-browser-check-note' }, (state.code ? state.code + ' · ' : '') + state.detail)) : null);
|
|
3217
|
+
}
|
|
3218
|
+
|
|
2619
3219
|
function HostDoctorPanel() {
|
|
2620
3220
|
var pair = React.useState(null);
|
|
2621
3221
|
var report = pair[0];
|
|
@@ -2640,12 +3240,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2640
3240
|
|
|
2641
3241
|
var checks = report && Array.isArray(report.checks) ? report.checks : [];
|
|
2642
3242
|
var issues = checks.filter(function (item) { return item.status !== 'ok'; });
|
|
3243
|
+
var renderAddress = checks.find(function (item) { return item.id === 'render-address'; });
|
|
2643
3244
|
var mark = { ok: '✓', warn: '!', error: '×' };
|
|
2644
3245
|
function rows(list) {
|
|
2645
3246
|
return React.createElement('div', { className: 'iris-doctor-list' }, list.map(function (item) {
|
|
2646
3247
|
return React.createElement('div', { key: item.id, className: 'iris-doctor-row ' + item.status },
|
|
2647
3248
|
React.createElement('span', {}, mark[item.status] || '·'),
|
|
2648
|
-
React.createElement('span', {}, item.summary
|
|
3249
|
+
React.createElement('span', {}, item.summary,
|
|
3250
|
+
item.status !== 'ok' && item.suggestion ? React.createElement('div', { className: 'iris-wb-muted' }, item.suggestion) : null));
|
|
2649
3251
|
}));
|
|
2650
3252
|
}
|
|
2651
3253
|
var score = report
|
|
@@ -2661,9 +3263,31 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2661
3263
|
checks.length ? React.createElement('details', { className: 'iris-doctor-details' },
|
|
2662
3264
|
React.createElement('summary', {}, '查看全部 ' + checks.length + ' 项'),
|
|
2663
3265
|
rows(checks)) : null,
|
|
2664
|
-
React.createElement('div', { className: 'iris-wb-muted' }, '
|
|
3266
|
+
React.createElement('div', { className: 'iris-wb-muted' }, '刷新只读装载证据,不启动浏览器;接口已提供不代表截图已通过。'),
|
|
3267
|
+
React.createElement(BrowserCheckPanel, {
|
|
3268
|
+
available: Boolean(report && report.capabilities && report.capabilities.browser && report.capabilities.browser.status === 'available'),
|
|
3269
|
+
address: renderAddress && renderAddress.status === 'ok' ? renderAddress.summary : ''
|
|
3270
|
+
}));
|
|
2665
3271
|
}
|
|
2666
3272
|
|
|
3273
|
+
function revealWorkbenchItem(node) {
|
|
3274
|
+
for (var parent = node.parentElement; parent; parent = parent.parentElement) {
|
|
3275
|
+
if (parent.tagName === 'DETAILS') parent.open = true;
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
function WorkbenchSection(props) {
|
|
3279
|
+
var expandedPair = React.useState(false);
|
|
3280
|
+
return React.createElement(props.collapsible ? 'details' : 'section', { className: 'iris-wb-section',
|
|
3281
|
+
'data-iris-section': props.name, 'aria-label': props.title,
|
|
3282
|
+
onToggle: props.collapsible ? function (event) { expandedPair[1](event.currentTarget.open); } : undefined },
|
|
3283
|
+
React.createElement(props.collapsible ? 'summary' : 'div', { className: 'iris-wb-section-head' },
|
|
3284
|
+
React.createElement('div', {}, React.createElement('h2', {}, props.title),
|
|
3285
|
+
props.description ? React.createElement('p', {}, props.description) : null),
|
|
3286
|
+
props.collapsible ? React.createElement('span', { className: 'iris-wb-section-arrow', 'aria-hidden': true }, expandedPair[0] ? '收起' : '展开',
|
|
3287
|
+
React.createElement('svg', { width: 15, height: 15, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8 },
|
|
3288
|
+
React.createElement('path', { d: 'm6 9 6 6 6-6' }))) : null),
|
|
3289
|
+
React.createElement('div', { className: 'iris-wb-section-body' }, props.children));
|
|
3290
|
+
}
|
|
2667
3291
|
function WorkbenchPanel() {
|
|
2668
3292
|
var state = useIrisState(5000);
|
|
2669
3293
|
var running = (state && state.tasks && state.tasks.running) || [];
|
|
@@ -2677,46 +3301,45 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2677
3301
|
var selectedTask = selPair[0];
|
|
2678
3302
|
var setSelectedTask = selPair[1];
|
|
2679
3303
|
function onSelect(id) { setSelectedTask(id); }
|
|
2680
|
-
return React.createElement('div', { className: 'iris-wb' },
|
|
3304
|
+
return React.createElement('div', { className: 'iris-wb iris-workbench-page' },
|
|
2681
3305
|
React.createElement('div', { className: 'iris-wb-head' },
|
|
2682
3306
|
React.createElement('span', { className: 'iris-wb-title' }, 'Iris 工作台'),
|
|
2683
3307
|
React.createElement('span', { className: 'iris-wb-date' }, state ? '刷新于 ' + new Date().toLocaleTimeString() : '加载中…')),
|
|
2684
|
-
React.createElement('div', { className: 'iris-wb-sec' }, '供应商'),
|
|
2685
|
-
React.createElement(ProviderManager, {}),
|
|
2686
|
-
React.createElement('div', { className: 'iris-wb-sec' }, '能力健康'),
|
|
2687
3308
|
React.createElement(CapabilityHealthOverview, {}),
|
|
2688
|
-
React.createElement(
|
|
2689
|
-
|
|
2690
|
-
React.createElement(
|
|
2691
|
-
|
|
2692
|
-
React.createElement(
|
|
2693
|
-
|
|
2694
|
-
React.createElement(PromptOptimizerSettings, {}),
|
|
2695
|
-
React.createElement('div', { className: 'iris-wb-sec' }, '宿主诊断'),
|
|
2696
|
-
React.createElement(HostDoctorPanel, {}),
|
|
2697
|
-
React.createElement('div', { className: 'iris-wb-sec' }, '运行中任务'),
|
|
2698
|
-
React.createElement('div', { className: 'iris-wb-box' },
|
|
3309
|
+
React.createElement(WorkbenchSection, { name: 'providers', title: '供应商与模型', description: '管理连接、模型用途与接口协议。' },
|
|
3310
|
+
React.createElement(ProviderManager, {})),
|
|
3311
|
+
React.createElement(WorkbenchSection, { name: 'pools', title: '功能池与调用方式', description: '选择自动池、所选模型或停用,查看每项功能的候选范围。', collapsible: true },
|
|
3312
|
+
React.createElement(CapabilityAssigner, {})),
|
|
3313
|
+
React.createElement(WorkbenchSection, { name: 'running', title: '运行中任务' },
|
|
3314
|
+
React.createElement('div', { className: 'iris-wb-box' },
|
|
2699
3315
|
running.length || coreGroups.live.length ? [].concat(
|
|
2700
3316
|
running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); }),
|
|
2701
3317
|
coreGroups.live.map(coreTaskCard))
|
|
2702
|
-
: React.createElement('div', { className: 'iris-wb-empty' }, '暂无进行中任务')),
|
|
2703
|
-
attention.length || coreGroups.attention.length || coreUser.degraded ? React.createElement('
|
|
2704
|
-
React.createElement('div', { className: 'iris-wb-sec' }, '需要处理'),
|
|
3318
|
+
: React.createElement('div', { className: 'iris-wb-empty' }, '暂无进行中任务'))),
|
|
3319
|
+
attention.length || coreGroups.attention.length || coreUser.degraded ? React.createElement(WorkbenchSection, { name: 'attention', title: '需要处理' },
|
|
2705
3320
|
React.createElement('div', { className: 'iris-wb-box' },
|
|
2706
3321
|
coreUser.degraded ? React.createElement('div', { className: 'iris-clean-note' },
|
|
2707
3322
|
'部分 Core 记录损坏或媒体缺失,已跳过 ' + coreUser.droppedTasks + ' 条;其余事实照常显示。') : null,
|
|
2708
3323
|
[].concat(
|
|
2709
3324
|
attention.map(function (t) { return taskRow(t, false, selectedTask === t.id, onSelect); }),
|
|
2710
3325
|
coreGroups.attention.map(coreTaskCard)))) : null,
|
|
2711
|
-
React.createElement(
|
|
2712
|
-
|
|
2713
|
-
React.createElement(
|
|
2714
|
-
|
|
2715
|
-
React.createElement(
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
3326
|
+
React.createElement(WorkbenchSection, { name: 'artifacts', title: '作品库', description: '查看、下载和继续处理已有作品。' },
|
|
3327
|
+
React.createElement(ArtifactGallery, { total: artifactTotal })),
|
|
3328
|
+
React.createElement(WorkbenchSection, { name: 'actions', title: '创作工具', description: '生成媒体、导入图片、识别与处理素材。', collapsible: true },
|
|
3329
|
+
React.createElement(ActionGroups, {})),
|
|
3330
|
+
React.createElement(WorkbenchSection, { name: 'preferences', title: '快捷入口与偏好', description: '设置泡泡快捷卡片与对话框提示词优化。', collapsible: true },
|
|
3331
|
+
React.createElement('div', { className: 'iris-wb-sec' }, 'Iris 泡泡快捷卡片(勾选后显示在「⚡ 常用」标签)'),
|
|
3332
|
+
React.createElement(BubbleCardPicker, {}),
|
|
3333
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '对话框提示词优化'),
|
|
3334
|
+
React.createElement(PromptOptimizerSettings, {})),
|
|
3335
|
+
React.createElement(WorkbenchSection, { name: 'diagnostics', title: '诊断与运行信息', description: '查看宿主装载与 Core 任务事实。', collapsible: true },
|
|
3336
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '宿主诊断'),
|
|
3337
|
+
React.createElement(HostDoctorPanel, {}),
|
|
3338
|
+
React.createElement('div', { className: 'iris-wb-sec' }, '高级诊断'),
|
|
3339
|
+
React.createElement(CoreRuntimePanel, {})),
|
|
3340
|
+
React.createElement(WorkbenchSection, { name: 'history', title: '历史任务', description: '查看历史记录与清理选项。', collapsible: true },
|
|
3341
|
+
React.createElement(CleanupBar, {}),
|
|
3342
|
+
React.createElement(HistoryBrowser, { recent: recent, coreHistory: [].concat(coreGroups.done, coreGroups.acknowledged), selected: selectedTask, onSelect: onSelect })));
|
|
2720
3343
|
}
|
|
2721
3344
|
|
|
2722
3345
|
/* ---- 泡泡浮层(阶段 4 续:两标签,尽量干净)---- */
|