@mokuyoaxis/dsh-iris 0.3.0-alpha.1 → 0.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -1
- package/README.en.md +20 -13
- package/README.md +20 -13
- package/bin/dsh-iris.js +143 -51
- package/docs/AGENT_CORE_QUICKSTART.md +2 -2
- package/docs/AGENT_QUICKSTART.md +1 -1
- package/docs/ARCHITECTURE.md +5 -5
- package/docs/ARTIFACT_MANIFEST.md +4 -0
- package/docs/AUDIO_FLOWS.md +20 -4
- package/docs/BETA_SCOPE.md +42 -0
- package/docs/CONTACT_SHEET.md +1 -1
- package/docs/CORE_CONNECTIONS.md +3 -1
- package/docs/CORE_QUICKSTART.md +7 -5
- package/docs/CORE_RUNTIME_CONTRACT.md +3 -3
- package/docs/CORE_SERVICE.md +9 -5
- package/docs/CORE_SKILLS.md +2 -2
- package/docs/DSH_COMPATIBILITY.md +11 -1
- package/docs/DSH_CORE_MIGRATION.md +6 -0
- package/docs/DSH_RUNTIME.md +2 -2
- package/docs/HEADLESS_CLI.md +29 -4
- package/docs/HOST_ADAPTER_CONTRACT.md +5 -3
- package/docs/IMAGE_FLOW.md +2 -2
- package/docs/IMAGE_GENERATION.md +1 -1
- package/docs/LOCAL_API.md +2 -2
- package/docs/LOCAL_API_CONTRACT.md +17 -15
- package/docs/LOCAL_CREATION.md +2 -2
- package/docs/LOCAL_OPERATIONS.md +2 -2
- package/docs/LOCAL_SELECTION.md +1 -1
- package/docs/LOCAL_WEB.md +11 -5
- package/docs/MCP.md +30 -9
- package/docs/MEDIA_SCHEDULING.md +72 -0
- package/docs/NETWORK_RESOURCES.md +106 -0
- package/docs/NETWORK_ROUTING.md +103 -0
- package/docs/NETWORK_WORKBENCH.md +65 -0
- package/docs/PANEL_EXTENSIONS.md +1 -1
- package/docs/PLUGINS.md +9 -5
- package/docs/PLUGIN_DEVELOPMENT.md +39 -0
- package/docs/PLUGIN_STARTUP.md +15 -2
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +8 -2
- package/docs/PROVIDER_CREDENTIALS.md +67 -0
- package/docs/PROVIDER_EXTENSIONS.md +8 -4
- package/docs/PROVIDER_HEALTH.md +4 -2
- package/docs/PROVIDER_NETWORK.md +45 -0
- package/docs/PROVIDER_PLUGIN_CONTRACT.md +24 -0
- package/docs/PROVIDER_SETTINGS.md +13 -7
- package/docs/PROVIDER_SUBMISSION_CONTRACT.md +3 -1
- package/docs/ROADMAP.md +6 -6
- package/docs/SECURITY.md +1 -1
- package/docs/SYNC_DELIVERY_RECOVERY.md +5 -1
- package/docs/VIDEO_FLOWS.md +89 -0
- package/docs/VISION_MODEL.md +2 -0
- package/docs/WEB_PROTOTYPE.md +3 -3
- package/docs/WEB_RECOVERY.md +2 -2
- package/docs/WORKBENCH_ARTIFACTS.md +6 -0
- package/docs/WORKBENCH_LOGIN.md +2 -2
- package/docs/releases/0.3.0-beta.1.md +34 -0
- package/docs/screenshots.md +2 -2
- package/lib/actions.js +5 -4
- package/lib/adapters.js +58 -41
- package/lib/api.js +23 -13
- package/lib/application-runtime.js +44 -15
- package/lib/application-writer.js +193 -0
- package/lib/client.d.ts +3 -3
- package/lib/client.js +62 -25
- package/lib/comfyui-adapter.js +3 -2
- package/lib/comfyui-client.js +10 -1
- package/lib/command-service.js +18 -5
- package/lib/config.js +13 -9
- package/lib/configured-image-runtime.js +28 -9
- package/lib/connection-skill.js +9 -0
- package/lib/core-artifact-store.js +68 -26
- package/lib/core-bridge-tools.js +5 -3
- package/lib/core-capability-catalog.js +21 -11
- package/lib/core-delivery-cache.js +8 -3
- package/lib/core-service-client.js +6 -2
- package/lib/core-tasks.js +86 -11
- package/lib/creation-request-store.js +8 -5
- package/lib/doctor.js +2 -2
- package/lib/dsh-core-adapter.js +61 -9
- package/lib/dsh-provider-config.js +1 -1
- package/lib/dsh-service-bridge.js +1 -0
- package/lib/flow-store.js +64 -17
- package/lib/image-flow.js +39 -17
- package/lib/index.js +10 -5
- package/lib/local-api-projection.js +6 -2
- package/lib/local-api.js +137 -49
- package/lib/local-creation.js +12 -5
- package/lib/local-operations.js +2 -2
- package/lib/local-request-root.js +2 -2
- package/lib/local-web-assets.js +2 -0
- package/lib/mcp-http.js +82 -9
- package/lib/mcp-server.js +23 -1
- package/lib/media-scheduler.js +194 -0
- package/lib/model-directory-service.js +25 -8
- package/lib/music-transport.js +4 -2
- package/lib/network-config-service.js +123 -0
- package/lib/network-config.js +127 -0
- package/lib/network-doctor.js +94 -0
- package/lib/network-management.js +51 -0
- package/lib/network-policy.js +19 -0
- package/lib/network-route-evidence.js +38 -0
- package/lib/network-routing.js +283 -0
- package/lib/operation-runner.js +83 -38
- package/lib/operation-store.js +6 -6
- package/lib/plugins.d.ts +25 -6
- package/lib/plugins.js +2 -1
- package/lib/provider-adapter.js +2 -2
- package/lib/provider-adapters.js +37 -5
- package/lib/provider-catalog.js +33 -16
- package/lib/provider-config-format.js +51 -0
- package/lib/provider-config-service.js +52 -22
- package/lib/provider-context.d.ts +90 -0
- package/lib/provider-context.js +42 -0
- package/lib/provider-credentials.js +97 -0
- package/lib/provider-diagnostics.js +5 -5
- package/lib/provider-health.js +10 -5
- package/lib/provider-monitor.js +5 -3
- package/lib/provider-network.js +52 -0
- package/lib/provider-protocol.js +3 -2
- package/lib/provider-registry.js +15 -5
- package/lib/provider-response.js +10 -0
- package/lib/provider-settings.js +14 -12
- package/lib/provider-task-runner.js +113 -37
- package/lib/provider-transport.js +16 -0
- package/lib/render.js +11 -1
- package/lib/scheduling-policy.js +66 -0
- package/lib/tasks.js +3 -2
- package/lib/video-flow-input.js +16 -0
- package/lib/video-input.js +12 -5
- package/lib/vision-model-routing.js +2 -0
- package/lib/web/app.js +107 -46
- package/lib/web/connections.js +151 -12
- package/lib/web/creation.js +29 -25
- package/lib/web/flow-view.js +4 -3
- package/lib/web/gallery.js +17 -16
- package/lib/web/index.html +58 -5
- package/lib/web/media-client.js +18 -4
- package/lib/web/network.js +232 -0
- package/lib/web/plugin-panel.js +4 -2
- package/lib/web/plugin-status.js +140 -0
- package/lib/web/providers.js +34 -13
- package/lib/web/recovery.js +2 -2
- package/lib/web/styles.css +54 -1
- package/lib/web/transport.js +6 -1
- package/lib/workbench-artifacts.js +3 -2
- package/package.json +5 -2
- package/skills/iris-core/references/comfyui.md +1 -1
- package/templates/plugin-starter/LICENSE +21 -0
- package/templates/plugin-starter/README.md +74 -0
- package/templates/plugin-starter/assets/peach.png +0 -0
- package/templates/plugin-starter/assets/violet.png +0 -0
- package/templates/plugin-starter/index.mjs +27 -0
- package/templates/plugin-starter/package.json +18 -0
- package/templates/plugin-starter/scripts/core-cli.mjs +20 -0
- package/templates/plugin-starter/scripts/environment.mjs +65 -0
- package/templates/plugin-starter/scripts/mcp-probe.mjs +22 -0
- package/templates/plugin-starter/scripts/setup.mjs +42 -0
- package/templates/plugin-starter/scripts/smoke.mjs +76 -0
- package/templates/plugin-starter/web/board.css +27 -0
- package/templates/plugin-starter/web/board.mjs +160 -0
- package/templates/plugin-starter/web/png.mjs +23 -0
- package/templates/provider-starter/LICENSE +21 -0
- package/templates/provider-starter/README.md +50 -0
- package/templates/provider-starter/index.d.mts +5 -0
- package/templates/provider-starter/index.mjs +165 -0
- package/templates/provider-starter/package.json +13 -0
- package/templates/provider-starter/scripts/core-cli.mjs +22 -0
- package/templates/provider-starter/scripts/environment.mjs +122 -0
- package/templates/provider-starter/scripts/fixture.mjs +54 -0
- package/templates/provider-starter/scripts/mcp-probe.mjs +21 -0
- package/templates/provider-starter/scripts/protocol-smoke.mjs +48 -0
- package/templates/provider-starter/scripts/setup.mjs +44 -0
- package/templates/provider-starter/scripts/smoke.mjs +145 -0
- package/user_guide.md +3 -3
package/lib/web/app.js
CHANGED
|
@@ -6,46 +6,75 @@ import { createCreationPanel } from './creation.js';
|
|
|
6
6
|
import { createGallery } from './gallery.js';
|
|
7
7
|
import { createProviderPanel } from './providers.js';
|
|
8
8
|
import { createAppearance } from './appearance.js';
|
|
9
|
+
import { createNetworkPanel } from './network.js';
|
|
9
10
|
|
|
10
11
|
const $ = selector => document.querySelector(selector);
|
|
11
12
|
const urls = new Set();
|
|
12
13
|
let client = null, mediaClient = null, recoveryTicket = 0, epoch = 0, runTicket = 0, runController = null, current = null;
|
|
13
14
|
let runs = [], nextCursor = null, historyBusy = false, connecting = false, capabilities = [], page = 'studio', historyTotal = 0;
|
|
14
|
-
let canManageProviders = false, audioEnabled = false;
|
|
15
|
+
let canManageProviders = false, audioEnabled = false, videoEnabled = false;
|
|
16
|
+
let modeLabel = '只读预览';
|
|
15
17
|
let loginController = null;
|
|
16
18
|
let startupSequence = 0;
|
|
17
19
|
const passwordMode = document.documentElement.dataset.auth === 'password';
|
|
18
20
|
let passwordLogin, passwordLoginLoading;
|
|
19
21
|
let plugins = null;
|
|
22
|
+
let pluginStatus = null, pluginStatusSession = null;
|
|
20
23
|
const RUN = /^run_[a-f0-9]{24}$/;
|
|
21
24
|
const TOKEN = /^[A-Za-z0-9_-]{43}$/, SESSION_KEY = 'iris.workbench.session.v0';
|
|
22
25
|
const date = value => new Date(value).toLocaleString('zh-CN', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
23
26
|
const valid = context => context.epoch === epoch && context.client === client;
|
|
24
27
|
const context = () => ({ epoch, client });
|
|
28
|
+
const videoRun = view => view?.recipe === 'video-generate/v0';
|
|
25
29
|
const audioRun = view => ['speech/v0', 'transcribe/v0'].includes(view?.recipe);
|
|
26
|
-
const recipeLabel = recipe => ({ 'image-edit/v0': '图片编辑', 'image-generate/v0': '图片生成', 'speech/v0': '语音合成', 'transcribe/v0': '音频转写' })[recipe];
|
|
30
|
+
const recipeLabel = recipe => ({ 'image-edit/v0': '图片编辑', 'image-generate/v0': '图片生成', 'speech/v0': '语音合成', 'transcribe/v0': '音频转写', 'video-generate/v0': '视频生成' })[recipe];
|
|
27
31
|
const connectionPanel = createConnectionPanel({ describe });
|
|
28
32
|
const gallery = createGallery({ openRun: id => loadRun(id), describe });
|
|
29
|
-
const providerPanel = createProviderPanel({ describe, onSaved: () => loadModels(context()), onSettings: (value, error) => overview.setSettings(value, error),
|
|
30
|
-
onProbe: options => creation.prepareVerification(options) });
|
|
33
|
+
const providerPanel = createProviderPanel({ describe, onSaved: () => { void networkPanel.refresh(); return loadModels(context()); }, onSettings: (value, error) => overview.setSettings(value, error),
|
|
34
|
+
onProbe: options => creation.prepareVerification(options), onNetworkOpen: id => { tab('network'); networkPanel.focusProvider(id); } });
|
|
35
|
+
const networkPanel = createNetworkPanel({ describe, onChanged: value => providerPanel.setNetworkFacts(value) });
|
|
31
36
|
const creation = createCreationPanel({ openRun: loadRun, refreshHistory: () => loadHistory(context(), false, false),
|
|
32
37
|
showDraft, describe, recover: recoverActions, renderOperation });
|
|
33
38
|
const overview = createOverviewPanel();
|
|
34
39
|
function tab(next = 'studio') {
|
|
35
40
|
page = next;
|
|
36
41
|
document.body.dataset.page = page;
|
|
37
|
-
$('#overview-panel').hidden = page !== 'overview'; $('#workspace').hidden = page !== 'studio'; $('#connections-panel').hidden = page !== 'connections'; $('#gallery-panel').hidden = page !== 'gallery'; $('#providers-panel').hidden = page !== 'providers';
|
|
38
|
-
|
|
42
|
+
$('#overview-panel').hidden = page !== 'overview'; $('#workspace').hidden = page !== 'studio'; $('#connections-panel').hidden = page !== 'connections'; $('#gallery-panel').hidden = page !== 'gallery'; $('#providers-panel').hidden = page !== 'providers'; $('#plugins-panel').hidden = page !== 'plugins';
|
|
43
|
+
$('#network-panel').hidden = page !== 'network';
|
|
44
|
+
for (const [id, target] of [['overview-tab', 'overview'], ['studio-tab', 'studio'], ['media-tab', 'gallery'], ['connections-tab', 'connections'], ['providers-tab', 'providers'], ['network-tab', 'network'], ['plugins-tab', 'plugins']]) {
|
|
39
45
|
$('#' + id).setAttribute('aria-current', page === target ? 'page' : 'false');
|
|
40
46
|
}
|
|
41
|
-
$('#page-label').textContent = { overview: '总览', studio: '创作', gallery: '作品', connections: '客户端连接', providers: '模型服务' }[page];
|
|
47
|
+
$('#page-label').textContent = { overview: '总览', studio: '创作', gallery: '作品', connections: '客户端连接', providers: '模型服务', network: '网络出口', plugins: '插件' }[page];
|
|
48
|
+
$('#mode-badge').textContent = page === 'network' ? '出口管理' : modeLabel;
|
|
42
49
|
plugins?.show(page);
|
|
43
50
|
if (page === 'plugin-settings') $('#page-label').textContent = '扩展设置';
|
|
44
51
|
else if (page === 'plugin-history') $('#page-label').textContent = '业务记录';
|
|
45
52
|
else if (page.startsWith('plugin:')) $('#page-label').textContent = $('#plugin-page-title').textContent;
|
|
46
53
|
connectionPanel.show(page === 'connections'); gallery.show(page === 'gallery'); creation.show(page === 'studio');
|
|
47
54
|
providerPanel.show(page === 'providers');
|
|
48
|
-
|
|
55
|
+
networkPanel.show(page === 'network');
|
|
56
|
+
pluginStatus?.show(page === 'plugins');
|
|
57
|
+
if (page === 'plugins' && !pluginStatus) void openPluginStatus();
|
|
58
|
+
if (page !== 'studio') { closePreview(); for (const id of ['source-audio', 'output-audio', 'audio-input-preview', 'output-video']) $('#' + id).pause(); }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function openPluginStatus() {
|
|
62
|
+
const ctx = context();
|
|
63
|
+
if (!ctx.client) return;
|
|
64
|
+
$('#plugins-refresh').disabled = true;
|
|
65
|
+
$('#plugins-feedback').hidden = false; $('#plugins-feedback').classList.remove('error'); $('#plugins-feedback').textContent = '正在载入插件页面…';
|
|
66
|
+
try {
|
|
67
|
+
const { createPluginStatusPanel } = await import('./plugin-status.js');
|
|
68
|
+
if (!valid(ctx) || page !== 'plugins') return;
|
|
69
|
+
if (!pluginStatus) { pluginStatus = createPluginStatusPanel({ describe, navigate: tab }); pluginStatus.connect(ctx.client, pluginStatusSession); }
|
|
70
|
+
pluginStatus.show(true);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (valid(ctx) && page === 'plugins') {
|
|
73
|
+
$('#plugins-feedback').hidden = false;
|
|
74
|
+
$('#plugins-feedback').classList.add('error');
|
|
75
|
+
$('#plugins-feedback').textContent = '插件页面未能载入,请核对服务版本;源码更新后需正常重启 Core 并刷新页面。' + describe(error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
49
78
|
}
|
|
50
79
|
|
|
51
80
|
function feedback(message, error = false) {
|
|
@@ -118,11 +147,11 @@ function showDraft(preview, mode = 'edit') {
|
|
|
118
147
|
runTicket++; current = null; releaseMedia(); tab();
|
|
119
148
|
history.replaceState(null, '', location.pathname);
|
|
120
149
|
$('#viewer').setAttribute('aria-busy', 'false'); $('#viewer-empty').hidden = false; $('#run-view').hidden = true;
|
|
121
|
-
$('#viewer-empty h2').textContent = ({ generate: '从一句想法开始', edit: '从一张图片开始', tts: '让文字有声音', transcribe: '把声音留下来' })[mode];
|
|
150
|
+
$('#viewer-empty h2').textContent = ({ generate: '从一句想法开始', edit: '从一张图片开始', tts: '让文字有声音', transcribe: '把声音留下来', video: '让想法动起来' })[mode];
|
|
122
151
|
$('#viewer-empty p').textContent = ({ generate: '写下你想看见的画面,选一个模型。作品会保存在当前工作区。', edit: '选好原图,写下想法。作品会保存在当前工作区。',
|
|
123
|
-
tts: '写下要朗读的内容,选择语音模型。合成后可在这里试听、下载。', transcribe: '选择一段音频和转写模型。原音频与文字结果都会保存在当前工作区。' })[mode];
|
|
124
|
-
$('.canvas-label').textContent = ['tts', 'transcribe'].includes(mode) ? '声音工作台' : '创作画布';
|
|
125
|
-
$('#view-modes').hidden = mode !== 'edit'; $('#reuse-selected').hidden = ['tts', 'transcribe'].includes(mode);
|
|
152
|
+
video: '描述主体与动作,选择实际视频模型。原任务的结果可在这里播放、下载。', tts: '写下要朗读的内容,选择语音模型。合成后可在这里试听、下载。', transcribe: '选择一段音频和转写模型。原音频与文字结果都会保存在当前工作区。' })[mode];
|
|
153
|
+
$('.canvas-label').textContent = mode === 'video' ? '动态工作台' : ['tts', 'transcribe'].includes(mode) ? '声音工作台' : '创作画布';
|
|
154
|
+
$('#view-modes').hidden = mode !== 'edit'; $('#reuse-selected').hidden = ['tts', 'transcribe', 'video'].includes(mode);
|
|
126
155
|
$('#draft-preview').removeAttribute('src'); $('#draft-preview').hidden = !preview; if (preview) $('#draft-preview').src = preview;
|
|
127
156
|
$('#prompt').textContent = ''; $('#run-facts').replaceChildren(); $('#run-parameter-values').replaceChildren(); $('#run-parameters').hidden = true; $('#download').disabled = true;
|
|
128
157
|
$('#run-details').open = false; $('[data-view="overlay"]').disabled = true; setView('pair'); feedback(''); renderHistory();
|
|
@@ -134,18 +163,24 @@ function renderOperation(operation) {
|
|
|
134
163
|
$('#operation-note').textContent = active ? '只读取已接收操作的本地进度;停止仅结束本地等待。'
|
|
135
164
|
: operation.state === 'canceled' ? '已停止本地等待。远端取消未请求,不代表退款或未计费。'
|
|
136
165
|
: operation.state === 'interrupted' ? '原操作已中断,现有任务与结果仍保留;不会自动重提。'
|
|
166
|
+
: operation.state === 'failed' ? '本地操作未完成,原记录仍保留。请核对任务及 Core 错误记录后,再决定是否继续。'
|
|
137
167
|
: '本次操作已结束,作品进展以当前流程为准。';
|
|
138
168
|
if (active) {
|
|
139
|
-
|
|
140
|
-
|
|
169
|
+
const waiting = { remote_capacity: ['等待远端任务名额', '已有任务尚未确认结束,本次生成还没有提交。'],
|
|
170
|
+
cooldown: ['等待模型冷却', '达到可用时间后继续,不会重复提交原请求。'],
|
|
171
|
+
rate_limit: ['等待发送窗口', '请求按当前发送速率排队。'], http_queue: ['等待网络名额', '其他请求正在提交、查询或下载。'],
|
|
172
|
+
candidates: ['正在核对模型', '核对本次模型与原连接身份。'] }[operation.scheduling?.phase];
|
|
173
|
+
$('#status-title').textContent = waiting?.[0] || (operation.state === 'queued' ? '本次操作已接收' : '正在处理');
|
|
174
|
+
$('#status-message').textContent = waiting?.[1] || '可以离开此页,已接收的事实仍会保存。';
|
|
141
175
|
}
|
|
142
176
|
}
|
|
143
177
|
function releaseMedia() {
|
|
144
178
|
plugins?.clearPreview();
|
|
145
179
|
runController?.abort(); runController = null;
|
|
146
180
|
closePreview();
|
|
147
|
-
for (const id of ['source-audio', 'output-audio']) { const player = $('#' + id); player.pause(); player.removeAttribute('src'); player.load(); }
|
|
181
|
+
for (const id of ['source-audio', 'output-audio', 'output-video']) { const player = $('#' + id); player.pause(); player.removeAttribute('src'); player.load(); }
|
|
148
182
|
for (const id of ['audio-source-result', 'audio-output-result', 'transcript-result']) $('#' + id).hidden = true;
|
|
183
|
+
$('#output-video').hidden = true; $('#video-result').hidden = true; $('#video-result-note').textContent = '';
|
|
149
184
|
$('#transcript-result').textContent = ''; $('#audio-result-note').textContent = ''; $('#audio-result').hidden = true;
|
|
150
185
|
for (const kind of ['source', 'output']) $('#overlay-' + kind).removeAttribute('src');
|
|
151
186
|
for (const url of urls) URL.revokeObjectURL(url);
|
|
@@ -157,17 +192,20 @@ function releaseMedia() {
|
|
|
157
192
|
}
|
|
158
193
|
}
|
|
159
194
|
function disconnect(message = '', { forget = true } = {}) {
|
|
195
|
+
pluginStatus?.disconnect(); pluginStatusSession = null;
|
|
196
|
+
$('#plugins-panel').hidden = true; $('#plugins-feedback').textContent = ''; $('#plugins-feedback').hidden = true;
|
|
160
197
|
plugins?.disconnect();
|
|
161
198
|
loginController?.abort(); loginController = null;
|
|
162
199
|
if (forget) { if (passwordMode) passwordLogin?.forget(); else storedSession('removeItem'); }
|
|
163
|
-
providerPanel.disconnect(); overview.disconnect(); canManageProviders = audioEnabled = false; $('#providers-panel').hidden = true; $('#providers-tab').hidden = true;
|
|
200
|
+
providerPanel.disconnect(); overview.disconnect(); canManageProviders = audioEnabled = videoEnabled = false; $('#providers-panel').hidden = true; $('#providers-tab').hidden = true;
|
|
201
|
+
networkPanel.disconnect(); $('#network-panel').hidden = true; $('#network-tab').hidden = true;
|
|
164
202
|
creation.disconnect(); gallery.disconnect(); capabilities = []; document.body.classList.remove('connected');
|
|
165
203
|
epoch++; runTicket++; recoveryTicket++; mediaClient?.close(); mediaClient = null; client?.close(); client = null; current = null;
|
|
166
204
|
$('#recovery-panel').hidden = true; $('#recovery-panel').open = false;
|
|
167
205
|
$('#recovery-list').replaceChildren(); $('#recovery-note').textContent = ''; $('#recovery-summary').textContent = '';
|
|
168
206
|
$('#recovery-clear-confirm').hidden = true;
|
|
169
207
|
connectionPanel.disconnect(); $('#workspace-nav').hidden = true; $('#app-sidebar').hidden = true; $('#connections-panel').hidden = true; $('#gallery-panel').hidden = true;
|
|
170
|
-
$('#mode-badge').textContent =
|
|
208
|
+
modeLabel = '只读预览'; $('#mode-badge').textContent = modeLabel;
|
|
171
209
|
connecting = false; historyBusy = false; runs = []; nextCursor = null; historyTotal = 0;
|
|
172
210
|
releaseMedia(); $('#connect').disabled = false; $('#token').value = ''; $('#token-file').value = '';
|
|
173
211
|
$('#workspace').hidden = true; $('#connection').hidden = false; $('#disconnect').hidden = true;
|
|
@@ -198,19 +236,21 @@ async function connectToken(raw, { restoring = false } = {}) {
|
|
|
198
236
|
if (session.contractVersion !== 0 || !session.operations.includes('runs.inspect')) throw new TypeError('不支持的工作台会话');
|
|
199
237
|
if (!passwordMode) storedSession('setItem', token);
|
|
200
238
|
mediaClient = createMediaClient({ client: ctx.client, session, origin: location.origin });
|
|
201
|
-
capabilities = session.operations; audioEnabled = session.audio === true; overview.connect(session); creation.connect(ctx.client, mediaClient, session); gallery.connect(ctx.client, session);
|
|
239
|
+
capabilities = session.operations; audioEnabled = session.audio === true; videoEnabled = session.video === true; overview.connect(session); creation.connect(ctx.client, mediaClient, session); gallery.connect(ctx.client, session);
|
|
202
240
|
if (!plugins && (session.pluginExecution || session.panelContributions?.pages.length || session.panelContributions?.settings.length || session.panelContributions?.renderers.length)) {
|
|
203
241
|
const { createPluginPanel } = await import('./plugin-panel.js');
|
|
204
242
|
if (!valid(ctx)) return;
|
|
205
243
|
plugins = createPluginPanel({ navigate: tab, describe, onProvidersSaved: result => { overview.setSettings(result); void loadModels(context()); } });
|
|
206
244
|
}
|
|
207
245
|
plugins?.connect(ctx.client, session);
|
|
246
|
+
pluginStatusSession = session; pluginStatus?.connect(ctx.client, session);
|
|
208
247
|
canManageProviders = session.providerManagement === true; $('#providers-tab').hidden = !canManageProviders;
|
|
248
|
+
$('#network-tab').hidden = session.networkManagement !== true;
|
|
209
249
|
document.body.classList.add('connected'); $('#app-sidebar').hidden = false;
|
|
210
250
|
$('#connection').hidden = true; $('#workspace').hidden = false; $('#disconnect').hidden = false;
|
|
211
251
|
tab(); $('#workspace-nav').hidden = false; $('#connections-tab').hidden = !session.connectionManagement;
|
|
212
252
|
$('#recent-panel').open = !matchMedia('(max-width:850px)').matches;
|
|
213
|
-
|
|
253
|
+
modeLabel = session.operations.includes('runs.continue') ? '可创作' : session.connectionManagement ? 'Core 工作台' : session.readOnly ? '只读预览' : '可选择作品'; $('#mode-badge').textContent = modeLabel;
|
|
214
254
|
const overviewRequested = location.hash === '#overview';
|
|
215
255
|
if (!RUN.test(location.hash.slice(1))) creation.beginDraft();
|
|
216
256
|
if (overviewRequested) { history.replaceState(null, '', '#overview'); tab('overview'); }
|
|
@@ -218,7 +258,7 @@ async function connectToken(raw, { restoring = false } = {}) {
|
|
|
218
258
|
$('#main-content').focus({ preventScroll: true });
|
|
219
259
|
feedback('');
|
|
220
260
|
await Promise.all([loadModels(ctx), loadHistory(ctx), recoverActions(ctx), ...(session.connectionManagement ? [connectionPanel.connect(ctx.client)] : []),
|
|
221
|
-
...(canManageProviders ? [providerPanel.connect(ctx.client)] : [])]);
|
|
261
|
+
...(canManageProviders ? [providerPanel.connect(ctx.client)] : []), ...(session.networkManagement ? [networkPanel.connect(ctx.client)] : [])]);
|
|
222
262
|
} catch (error) {
|
|
223
263
|
if (valid(ctx)) disconnect(describe(error), { forget: !restoring || error.status === 401 || error.message.startsWith('不支持') });
|
|
224
264
|
} finally {
|
|
@@ -245,7 +285,7 @@ async function recoverActions(ctx = context()) {
|
|
|
245
285
|
? '当前只保留内存中的动作标识,刷新恢复受限。请先核对原结果。' : '这里只读取原请求与已保存结果,不会自动开始或继续生成。');
|
|
246
286
|
$('#recovery-check').hidden = saved.blocked || !saved.actions.length;
|
|
247
287
|
$('#recovery-clear').hidden = !saved.blocked && !saved.actions.length;
|
|
248
|
-
const labels = { 'image-import': '素材保存', 'flow-create': '改图流程创建', 'generation-create': '生图流程创建', 'audio-import': '音频保存',
|
|
288
|
+
const labels = { 'image-import': '素材保存', 'flow-create': '改图流程创建', 'generation-create': '生图流程创建', 'video-create': '视频流程创建', 'audio-import': '音频保存',
|
|
249
289
|
'speech-create': '语音流程创建', 'transcription-create': '转写流程创建', continue: '执行', selection: '作品选择', stop: '本地停止' };
|
|
250
290
|
const states = { confirmed: '已读取原结果', prepared: '本地意图已保存,仍需明确接回', current: '已读取当前选择',
|
|
251
291
|
active: '原操作仍在处理中', not_found: '尚未找到原请求,请保留原身份后明确处理',
|
|
@@ -281,7 +321,7 @@ async function recoverActions(ctx = context()) {
|
|
|
281
321
|
|
|
282
322
|
async function loadModels(ctx) {
|
|
283
323
|
try {
|
|
284
|
-
const results = await Promise.all([ctx.client.get('/api/v0/models'), ...(audioEnabled ? [ctx.client.get('/api/v0/audio-models')] : [])]);
|
|
324
|
+
const results = await Promise.all([ctx.client.get('/api/v0/models'), ...(audioEnabled ? [ctx.client.get('/api/v0/audio-models')] : []), ...(videoEnabled ? [ctx.client.get('/api/v0/video-models').then(value => ({ ...value, models: value.models.map(model => ({ ...model, capability: 'video-gen' })) }))] : [])]);
|
|
285
325
|
if (!valid(ctx)) return;
|
|
286
326
|
if (results.some(result => result.contractVersion !== 0)) throw new TypeError('不支持的模型列表版本');
|
|
287
327
|
const models = results.flatMap(result => result.models), configured = results[0].configured;
|
|
@@ -290,7 +330,7 @@ async function loadModels(ctx) {
|
|
|
290
330
|
$('#model-note').textContent = !configured ? '启动时可传入 --provider-config。读取已有作品不需要模型配置。'
|
|
291
331
|
: '以下是配置中的候选,尚不代表已实测。此页面不会探测或调用模型。';
|
|
292
332
|
$('#model-list').replaceChildren(...models.map(model => {
|
|
293
|
-
const item = document.createElement('li'); item.textContent = model.displayName + (model.capability ? model.capability === 'tts' ? ' · 语音合成' : ' · 音频转写' : model.supportsImageEdit ? ' · 支持改图协议' : ''); return item;
|
|
333
|
+
const item = document.createElement('li'); item.textContent = model.displayName + (model.capability ? model.capability === 'video-gen' ? ' · 视频生成' : model.capability === 'tts' ? ' · 语音合成' : ' · 音频转写' : model.supportsImageEdit ? ' · 支持改图协议' : ''); return item;
|
|
294
334
|
}));
|
|
295
335
|
} catch (error) {
|
|
296
336
|
if (valid(ctx)) { $('#model-summary').textContent = '配置不可用'; $('#model-note').textContent = describe(error); overview.setModels(null, describe(error)); }
|
|
@@ -303,9 +343,9 @@ function createOverviewPanel() {
|
|
|
303
343
|
{ id: 'image', cap: 'image-gen', title: '图片', subtitle: '生成、修改,或运行 ComfyUI 工作流。',
|
|
304
344
|
icon: 'M4 4h16v16H4z M4 16l5-5 4 4 3-3 4 4 M8 8h.01', modes: [['generate', '生成图片', 'generations.create'], ['edit', '修改图片', 'runs.create']],
|
|
305
345
|
detail: 'API 与 ComfyUI 共用图片作品。修改图片需所选协议支持带图编辑;ComfyUI 参数来自已保存工作流,真实服务仍需单独验收。', command: 'run image' },
|
|
306
|
-
{ id: 'video', cap: 'video-gen', title: '视频', subtitle: '
|
|
307
|
-
icon: 'M3 6h12v12H3z M15 10l6-3v10l-6-3', modes: [],
|
|
308
|
-
detail: '
|
|
346
|
+
{ id: 'video', cap: 'video-gen', title: '视频', subtitle: '从一段动态描述开始,接回原任务的视频。',
|
|
347
|
+
icon: 'M3 6h12v12H3z M15 10l6-3v10l-6-3', modes: [['video', '生成视频', 'videos.create']],
|
|
348
|
+
detail: '新 v4 工作区提供固定文生视频流程,支持可选尺寸与时长。明确开始后才调用模型;MP4/WebM 原件可播放下载。图生视频与 S2V 仍使用 CLI / DSH。', command: 'run video' },
|
|
309
349
|
{ id: 'tts', cap: 'tts', title: '语音', subtitle: '让一段文字成为可以试听的声音。',
|
|
310
350
|
icon: 'M4 10h4l5-4v12l-5-4H4z M16 8c3 2 3 6 0 8 M19 5c5 4 5 10 0 14', modes: [['tts', '合成语音', 'speeches.create']],
|
|
311
351
|
detail: '新 v3 音频工作区提供 Web / MCP 流程,当前内置合成接口为 DashScope Qwen-TTS 普通 HTTP;音色须与实际模型匹配。', command: 'run tts' },
|
|
@@ -328,24 +368,25 @@ function createOverviewPanel() {
|
|
|
328
368
|
const operations = () => info?.operations || [];
|
|
329
369
|
const executable = () => operations().includes('runs.continue');
|
|
330
370
|
const candidates = feature => catalog?.models.filter(model => feature.id === 'image' ? !model.capability : model.capability === feature.cap);
|
|
331
|
-
const supported = feature => feature.id === 'image' || feature.modes.length && info?.audio === true;
|
|
371
|
+
const supported = feature => feature.id === 'image' || feature.modes.length && (feature.id === 'video' ? info?.video === true : info?.audio === true);
|
|
332
372
|
function fact(list, title, value) { list.append(node('dt', title), node('dd', value)); }
|
|
333
373
|
function card(feature, index) {
|
|
334
374
|
const article = node('article', undefined, 'overview-capability card'); article.dataset.overviewCapability = feature.id; article.dataset.providerTone = String(index);
|
|
335
375
|
const heading = node('div', undefined, 'overview-capability-heading'), mark = node('span', undefined, 'overview-capability-icon');
|
|
336
376
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), path = document.createElementNS(svg.namespaceURI, 'path');
|
|
337
377
|
svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('aria-hidden', 'true'); path.setAttribute('d', feature.icon); svg.append(path); mark.append(svg);
|
|
338
|
-
heading.append(mark, node('h3', feature.title), node('span', supported(feature) ? '面板已接入' : feature.modes.length ? '需音频工作区' : 'CLI / DSH', 'overview-entry'));
|
|
378
|
+
heading.append(mark, node('h3', feature.title), node('span', supported(feature) ? '面板已接入' : feature.modes.length ? feature.id === 'video' ? '需视频工作区' : '需音频工作区' : 'CLI / DSH', 'overview-entry'));
|
|
339
379
|
article.append(heading, node('p', feature.subtitle, 'overview-capability-description'));
|
|
340
380
|
const declared = settings?.providers.flatMap(provider => provider.models.filter(model => model.capabilities.includes(feature.cap)));
|
|
341
381
|
const policy = settings?.assignments[feature.cap], values = candidates(feature);
|
|
382
|
+
const workspaceKind = feature.id === 'video' ? '视频' : '音频';
|
|
342
383
|
const config = errors.settings ? '配置读取失败,未作判断' : settings
|
|
343
384
|
? declared.length + ' 个已标注 · ' + ({ auto: '自动选择', manual: '手动排序', disabled: '功能已停用' })[policy.mode]
|
|
344
|
-
: info.providerManagement ? '读取已保存配置…' : !feature.modes.length ? '当前会话未提供配置详情' : !supported(feature) ? '
|
|
385
|
+
: info.providerManagement ? '读取已保存配置…' : !feature.modes.length ? '当前会话未提供配置详情' : !supported(feature) ? '当前工作区未提供' + workspaceKind + '候选'
|
|
345
386
|
: errors.models ? '候选读取失败' : !catalog ? '读取模型候选…' : !catalog.configured ? '未指定配置' : values.length + ' 个候选';
|
|
346
387
|
const facts = node('dl', undefined, 'overview-capability-facts'); fact(facts, '配置', config);
|
|
347
388
|
if (feature.modes.length && info.providerManagement) fact(facts, '候选', errors.models ? '读取失败,未作判断'
|
|
348
|
-
: !catalog ? '读取中…' : !supported(feature) ? '
|
|
389
|
+
: !catalog ? '读取中…' : !supported(feature) ? '此工作区未接入' + workspaceKind + '流程' : values.length + ' 个当前候选');
|
|
349
390
|
const records = declared?.map(model => model.verified[feature.cap]).filter(Boolean), passed = records?.filter(record => record.ok).length;
|
|
350
391
|
const evidence = errors.settings ? '记录读取失败' : !settings ? info.providerManagement ? '读取实测记录…' : '当前会话未提供记录'
|
|
351
392
|
: !records.length ? '尚无实测记录' : passed + ' 通过 · ' + (records.length - passed) + ' 未通过';
|
|
@@ -379,9 +420,9 @@ function createOverviewPanel() {
|
|
|
379
420
|
if (!info) return;
|
|
380
421
|
const stats = $('#overview-stats'); stats.replaceChildren();
|
|
381
422
|
const state = node('div'); fact(state, '当前服务', executable() ? '可创作' : '仅浏览');
|
|
382
|
-
state.append(node('span', info.audio ? '图片、语音与转写工作区' : '图片工作区', 'overview-stat-note')); stats.append(state);
|
|
423
|
+
state.append(node('span', info.video ? '图片、视频、语音与转写工作区' : info.audio ? '图片、语音与转写工作区' : '图片工作区', 'overview-stat-note')); stats.append(state);
|
|
383
424
|
const models = node('div'); fact(models, '创作候选', errors.models ? '读取失败' : !catalog ? '读取中…' : !catalog.configured ? '未指定配置' : new Set(catalog.models.map(model => model.modelRef)).size + ' 个');
|
|
384
|
-
models.append(node('span', info.audio ? '图片/语音/转写,按模型去重' : '图片候选,按模型去重', 'overview-stat-note')); stats.append(models);
|
|
425
|
+
models.append(node('span', info.video ? '图片/视频/语音/转写,按模型去重' : info.audio ? '图片/语音/转写,按模型去重' : '图片候选,按模型去重', 'overview-stat-note')); stats.append(models);
|
|
385
426
|
const history = node('div'); fact(history, '已保存流程', errors.history ? '读取失败' : total === null ? '读取中…' : total + ' 个');
|
|
386
427
|
history.append(node('span', '仅统计当前工作区', 'overview-stat-note')); stats.append(history);
|
|
387
428
|
const expanded = new Set([...document.querySelectorAll('#overview-capabilities details[open]')].map(details => details.closest('[data-overview-capability]').dataset.overviewCapability));
|
|
@@ -428,7 +469,7 @@ function renderHistory() {
|
|
|
428
469
|
$('#history-list').replaceChildren(...runs.map((run, index) => {
|
|
429
470
|
const button = document.createElement('button'); button.type = 'button'; button.className = 'history-item';
|
|
430
471
|
button.dataset.runId = run.id; button.setAttribute('aria-current', run.id === current?.id ? 'true' : 'false');
|
|
431
|
-
const title = document.createElement('strong'); title.textContent = (audioRun(run) ? recipeLabel(run.recipe) : '图片流程') + ' · ' + date(run.createdAt);
|
|
472
|
+
const title = document.createElement('strong'); title.textContent = (audioRun(run) || videoRun(run) ? recipeLabel(run.recipe) : '图片流程') + ' · ' + date(run.createdAt);
|
|
432
473
|
const label = document.createElement('span'); label.textContent = run.modelRef;
|
|
433
474
|
const number = document.createElement('small'); number.textContent = String(index + 1).padStart(2, '0');
|
|
434
475
|
button.append(number, title, label); button.addEventListener('click', () => loadRun(run.id)); return button;
|
|
@@ -496,6 +537,19 @@ async function loadAudioMedia(ctx, ticket, kind, artifactId, signal) {
|
|
|
496
537
|
if (valid(ctx) && ticket === runTicket && !signal.aborted) $('#audio-result-note').textContent += (text ? '文字' : '音频') + '读取失败:' + describe(error) + ' ';
|
|
497
538
|
}
|
|
498
539
|
}
|
|
540
|
+
async function loadVideoMedia(ctx, ticket, artifactId, signal) {
|
|
541
|
+
$('#video-result-note').textContent = artifactId ? '正在读取原视频…' : '明确执行后,原任务的视频会保存在这里。';
|
|
542
|
+
if (!artifactId) return;
|
|
543
|
+
try {
|
|
544
|
+
const blob = await ctx.client.get('/api/v0/artifacts/' + artifactId + '/video', { binary: true, signal });
|
|
545
|
+
if (!valid(ctx) || ticket !== runTicket || signal.aborted) return;
|
|
546
|
+
if (!['video/mp4', 'video/webm'].includes(blob.type) || blob.size > 32 * 1024 * 1024) throw new TypeError('不支持的视频格式或大小');
|
|
547
|
+
const url = URL.createObjectURL(blob); urls.add(url);
|
|
548
|
+
$('#output-video').src = url; $('#output-video').hidden = false;
|
|
549
|
+
$('#video-result-note').textContent = '原视频已载入。点击播放或下载;播放器支持取决于当前浏览器。';
|
|
550
|
+
return { artifactId, mediaType: blob.type };
|
|
551
|
+
} catch (error) { if (valid(ctx) && ticket === runTicket && !signal.aborted) $('#video-result-note').textContent = '视频读取失败:' + describe(error); }
|
|
552
|
+
}
|
|
499
553
|
async function loadRun(id, { fromAction = false } = {}) {
|
|
500
554
|
const ctx = context(); if (!ctx.client || !RUN.test(id)) return;
|
|
501
555
|
if (!fromAction) creation.cancelChain();
|
|
@@ -510,22 +564,22 @@ async function loadRun(id, { fromAction = false } = {}) {
|
|
|
510
564
|
try {
|
|
511
565
|
const view = await ctx.client.get('/api/v0/runs/' + id, { signal });
|
|
512
566
|
if (!valid(ctx) || ticket !== runTicket) return;
|
|
513
|
-
const presentation = presentFlow(view, capabilities), isAudio = audioRun(view); current = view;
|
|
567
|
+
const presentation = presentFlow(view, capabilities), isAudio = audioRun(view), isVideo = videoRun(view); current = view;
|
|
514
568
|
history.replaceState(null, '', '#' + view.id); renderHistory();
|
|
515
569
|
$('#status-title').textContent = presentation.title; $('#status-message').textContent = presentation.message;
|
|
516
570
|
$('#run-view').dataset.tone = presentation.tone;
|
|
517
571
|
$('#run-view').dataset.hasOutput = String(Boolean(view.output));
|
|
518
|
-
$('#run-view').dataset.recipe = view.recipe; $('#source-card').hidden = !view.source; $('#view-modes').hidden = isAudio || !view.source;
|
|
572
|
+
$('#run-view').dataset.recipe = view.recipe; $('#source-card').hidden = !view.source; $('#view-modes').hidden = isAudio || isVideo || !view.source;
|
|
519
573
|
$('#prompt-title').textContent = isAudio ? view.recipe === 'speech/v0' ? '朗读内容' : '转写来源' : view.source ? '修改要求' : '画面描述';
|
|
520
574
|
$('[data-view="overlay"]').disabled = !view.source || !view.output; if (!sameRun || !view.output || !view.source) setView('pair');
|
|
521
|
-
if (isAudio) { $('#image-pair').hidden = true; $('#overlay-view').hidden = true; }
|
|
522
|
-
$('#audio-result').hidden = !isAudio; $('#reuse-selected').hidden = isAudio;
|
|
523
|
-
$('.canvas-label').textContent = isAudio ? '声音工作台' : '创作画布';
|
|
575
|
+
if (isAudio || isVideo) { $('#image-pair').hidden = true; $('#overlay-view').hidden = true; }
|
|
576
|
+
$('#audio-result').hidden = !isAudio; $('#video-result').hidden = !isVideo; $('#reuse-selected').hidden = isAudio || isVideo;
|
|
577
|
+
$('.canvas-label').textContent = isVideo ? '动态工作台' : isAudio ? '声音工作台' : '创作画布';
|
|
524
578
|
$('#audio-result-kind').textContent = view.recipe === 'speech/v0' ? 'VOICE' : 'TRANSCRIPT';
|
|
525
579
|
$('#audio-result-title').textContent = view.recipe === 'speech/v0' ? '让文字有声音' : '声音里的文字';
|
|
526
580
|
$('#audio-result-note').textContent = view.output ? '作品已保存;点击播放或下载即可使用。' : '明确执行后,原任务的结果会保存在这里。';
|
|
527
581
|
$('#operation-note').hidden = true;
|
|
528
|
-
$('#selection-feedback').textContent = isAudio ? view.output ? view.recipe === 'speech/v0' ? '音频已保存,可以试听或下载。' : '转写已保存,可下载 UTF-8 文本。' : '结果尚未保存。'
|
|
582
|
+
$('#selection-feedback').textContent = isVideo ? view.output ? '视频已保存,可以播放或下载原件。' : '原任务结果尚未保存。' : isAudio ? view.output ? view.recipe === 'speech/v0' ? '音频已保存,可以试听或下载。' : '转写已保存,可下载 UTF-8 文本。' : '结果尚未保存。'
|
|
529
583
|
: view.selection ? '当前选择已记录,可以下载原始图片。' : capabilities.includes('runs.select')
|
|
530
584
|
? '选择喜欢的版本,再保存到设备。' : '当前未开启选择权限;已有选择仍可下载。';
|
|
531
585
|
$('#run-meta').textContent = view.input.modelRef + ' · ' + date(view.createdAt);
|
|
@@ -542,19 +596,24 @@ async function loadRun(id, { fromAction = false } = {}) {
|
|
|
542
596
|
$('#output-card').classList.toggle('is-selected', !$('#output-selected').hidden);
|
|
543
597
|
$('#select-source').setAttribute('aria-pressed', String(!$('#source-selected').hidden));
|
|
544
598
|
$('#select-output').setAttribute('aria-pressed', String(!$('#output-selected').hidden));
|
|
545
|
-
$('#download').disabled = isAudio ? !view.output : !view.selection;
|
|
546
|
-
$('#download').textContent = isAudio ? view.recipe === 'speech/v0' ? '下载音频' : '下载文字' : '下载已选作品';
|
|
547
|
-
$('#run-details .muted').hidden = isAudio;
|
|
599
|
+
$('#download').disabled = isAudio || isVideo ? !view.output : !view.selection;
|
|
600
|
+
$('#download').textContent = isVideo ? '下载视频' : isAudio ? view.recipe === 'speech/v0' ? '下载音频' : '下载文字' : '下载已选作品';
|
|
601
|
+
$('#run-details .muted').hidden = isAudio || isVideo;
|
|
548
602
|
$('#run-facts').replaceChildren(); if (!sameRun) $('#run-details').open = false;
|
|
549
603
|
creation.view(view, { sameRun });
|
|
550
604
|
addFact('流程', view.id); addFact('版本', String(view.revision)); addFact('保存时间', date(view.updatedAt));
|
|
551
605
|
addFact('类型', recipeLabel(view.recipe));
|
|
552
606
|
if (view.input.voice) addFact('音色', view.input.voice);
|
|
607
|
+
if (view.input.duration !== undefined) addFact('时长', String(view.input.duration) + ' 秒');
|
|
608
|
+
if (isVideo && view.input.size) addFact('尺寸', view.input.size);
|
|
553
609
|
if (view.source) addFact(isAudio ? '原音频' : '原图', view.source.artifactId); addFact('结果', view.output?.artifactId || '尚未保存');
|
|
554
610
|
addFact('原任务', view.task?.id || '尚未创建');
|
|
611
|
+
const route = view.task?.networkRoutes?.reduce((latest, value) => !latest || value.lastAt > latest.lastAt ? value : latest, null);
|
|
612
|
+
if (view.task) addFact('最近记录的出口', route ? [route.route === 'proxy' ? '出口池 ' + route.poolId + ' / ' + route.nodeId : route.route === 'direct' ? '明确直连' : '继承环境',
|
|
613
|
+
({ discover: '目录检查', prepareInput: '输入准备', submit: '提交', poll: '查询', download: '交付', cancel: '取消', vision: '视觉' })[route.operation], date(route.lastAt)].join(' · ') : '未记录');
|
|
555
614
|
if (view.metrics) addFact('像素差异', (view.metrics.ratio * 100).toFixed(2) + '%');
|
|
556
615
|
$('#viewer-empty').hidden = true; $('#run-view').hidden = false;
|
|
557
|
-
const previewArtifacts = await Promise.all(isAudio ? [loadAudioMedia(ctx, ticket, 'source', view.source?.artifactId, signal),
|
|
616
|
+
const previewArtifacts = await Promise.all(isVideo ? [loadVideoMedia(ctx, ticket, view.output?.artifactId, signal)] : isAudio ? [loadAudioMedia(ctx, ticket, 'source', view.source?.artifactId, signal),
|
|
558
617
|
loadAudioMedia(ctx, ticket, view.recipe === 'speech/v0' ? 'output' : 'transcript', view.output?.artifactId, signal)]
|
|
559
618
|
: [loadMedia(ctx, ticket, 'source', view.source?.artifactId, signal), loadMedia(ctx, ticket, 'output', view.output?.artifactId, signal)]);
|
|
560
619
|
if (valid(ctx) && ticket === runTicket && !signal.aborted) plugins?.previewArtifacts(previewArtifacts, view.selection?.artifactId || view.output?.artifactId);
|
|
@@ -565,7 +624,7 @@ async function loadRun(id, { fromAction = false } = {}) {
|
|
|
565
624
|
} finally { if (valid(ctx) && ticket === runTicket) $('#viewer').setAttribute('aria-busy', 'false'); }
|
|
566
625
|
}
|
|
567
626
|
async function download() {
|
|
568
|
-
if (!(audioRun(current) ? current?.output : current?.selection) || $('#download').disabled) return;
|
|
627
|
+
if (!(audioRun(current) || videoRun(current) ? current?.output : current?.selection) || $('#download').disabled) return;
|
|
569
628
|
const ctx = context(), id = current.id, ticket = runTicket;
|
|
570
629
|
$('#download').disabled = true; feedback('正在读取作品…');
|
|
571
630
|
try {
|
|
@@ -573,11 +632,11 @@ async function download() {
|
|
|
573
632
|
if (!valid(ctx) || ticket !== runTicket) return;
|
|
574
633
|
const url = URL.createObjectURL(blob); urls.add(url);
|
|
575
634
|
const link = document.createElement('a'); link.href = url;
|
|
576
|
-
const extension = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/webp': 'webp', 'audio/wav': 'wav', 'audio/mpeg': 'mp3', 'audio/ogg': 'ogg', 'audio/flac': 'flac', 'text/plain': 'txt' }[blob.type];
|
|
635
|
+
const extension = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/webp': 'webp', 'audio/wav': 'wav', 'audio/mpeg': 'mp3', 'audio/ogg': 'ogg', 'audio/flac': 'flac', 'text/plain': 'txt', 'video/mp4': 'mp4', 'video/webm': 'webm' }[blob.type];
|
|
577
636
|
if (!extension) throw new TypeError('不支持的下载格式');
|
|
578
637
|
// The CLI may have changed the selection since this view was loaded. Use the server's
|
|
579
638
|
// filename, which identifies the exact artifact returned by this download request.
|
|
580
|
-
if (!/^iris-artifact_[a-f0-9]{24}\.(?:png|jpg|webp|wav|mp3|ogg|flac|txt)$/.test(filename)) throw new TypeError('不支持的下载文件名');
|
|
639
|
+
if (!/^iris-artifact_[a-f0-9]{24}\.(?:png|jpg|webp|wav|mp3|ogg|flac|txt|mp4|webm)$/.test(filename)) throw new TypeError('不支持的下载文件名');
|
|
581
640
|
link.download = filename; link.click();
|
|
582
641
|
setTimeout(() => { URL.revokeObjectURL(url); urls.delete(url); }, 60000);
|
|
583
642
|
feedback('下载请求已交给浏览器,请在浏览器下载记录中查看。');
|
|
@@ -612,6 +671,7 @@ $('#overview-tab').addEventListener('click', () => tab('overview'));
|
|
|
612
671
|
$('#studio-tab').addEventListener('click', () => tab());
|
|
613
672
|
$('#media-tab').addEventListener('click', () => tab('gallery'));
|
|
614
673
|
$('#connections-tab').addEventListener('click', () => tab('connections'));
|
|
674
|
+
$('#plugins-tab').addEventListener('click', () => tab('plugins'));
|
|
615
675
|
for (const id of ['new-run', 'gallery-new']) $('#' + id).addEventListener('click', () => creation.beginDraft());
|
|
616
676
|
for (const button of document.querySelectorAll('#view-modes button')) button.addEventListener('click', () => { if (!button.disabled) setView(button.dataset.view); });
|
|
617
677
|
$('#compare-split').addEventListener('input', event => $('#overlay-output').style.setProperty('--split', event.target.value + '%'));
|
|
@@ -626,6 +686,7 @@ for (const details of document.querySelectorAll('details')) details.addEventList
|
|
|
626
686
|
});
|
|
627
687
|
createAppearance();
|
|
628
688
|
$('#providers-tab').addEventListener('click', () => { if (canManageProviders) tab('providers'); });
|
|
689
|
+
$('#network-tab').addEventListener('click', () => { if (!$('#network-tab').hidden) tab('network'); });
|
|
629
690
|
$('#manage-models').addEventListener('click', () => {
|
|
630
691
|
if (canManageProviders) tab('providers');
|
|
631
692
|
else feedback('当前服务未开启供应商管理。启动时添加 --allow-provider-management 并指定 --provider-config 后可配置。');
|