@wisdoverse/dsh-inline-media-viewer 1.0.11 → 1.0.12
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 +9 -0
- package/README.md +9 -7
- package/README.zh-CN.md +6 -5
- package/SECURITY.md +4 -3
- package/client/client.js +71 -30
- package/cordis.patch.yml +1 -1
- package/index.js +35 -27
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.12
|
|
4
|
+
|
|
5
|
+
- Verify the current source on DSH v0.2.0-rc.2: Web settings load and persist, authenticated ComfyUI image reads succeed, and unauthenticated reads are rejected; the plugin-facing APIs used here are unchanged from v0.1.7-rc.2.
|
|
6
|
+
- Replace the removed `settingsScope` client service with live `Config` fields and `configForms`, fixing plugins stuck pending on DSH v0.1.7 and v0.2.0.
|
|
7
|
+
- Preserve remote read-only media preferences through the authenticated RPC channel.
|
|
8
|
+
- Register media endpoints on DSH's authenticated `/api` bridge; real v0.1.7 testing caught the old custom channel returning HTTP 405.
|
|
9
|
+
- Render the turn-tail media card from the slot's actual turn props; verified image display and enlargement in the v0.1.7-rc.2 Web UI.
|
|
10
|
+
- Align the profile entry id with the old `inline-media` settings section for migration.
|
|
11
|
+
|
|
3
12
|
## 1.0.11
|
|
4
13
|
|
|
5
14
|
- Register the authenticated RPC route inside a connection-injected context so
|
package/README.md
CHANGED
|
@@ -59,14 +59,14 @@ audio/sound.mp3 → <audio controls>
|
|
|
59
59
|
|
|
60
60
|
## Installation
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Version **1.0.12** supports DSH **v0.1.7-rc.2** and **v0.2.0-rc.2**. On v0.2.0-rc.2, the plugin loaded in the real Web UI, settings remained saved after a refresh, and the authenticated ComfyUI image route returned a decodable image; unauthenticated access returned HTTP 401. Local image reads and the inline image card were verified on v0.1.7-rc.2, but have not been retested on v0.2.0-rc.2. Video and audio playback remain unverified. Upgrade from v1.0.11 to remove its obsolete `settingsScope` dependency.
|
|
63
63
|
|
|
64
64
|
### From npm
|
|
65
65
|
|
|
66
66
|
Install and activate the bundle in your Web profile:
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
|
-
dsh plugin --profile web add @wisdoverse/dsh-inline-media-viewer@1.0.
|
|
69
|
+
dsh plugin --profile web add @wisdoverse/dsh-inline-media-viewer@1.0.12
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
Restart the Web profile after installation.
|
|
@@ -97,7 +97,7 @@ Restart the Web profile after installation.
|
|
|
97
97
|
|
|
98
98
|
3. Rebuild or restart the Web profile.
|
|
99
99
|
|
|
100
|
-
The plugin mounts its host
|
|
100
|
+
The plugin mounts its host API routes, client projection, and settings section
|
|
101
101
|
through [`cordis.patch.yml`](cordis.patch.yml).
|
|
102
102
|
|
|
103
103
|
## Configuration
|
|
@@ -114,9 +114,10 @@ switch live between English and Chinese.
|
|
|
114
114
|
| Maximum media height | `380 px` | `160`–`1200 px` |
|
|
115
115
|
| ComfyUI origin (optional) | Empty | `http(s)://host[:port]` |
|
|
116
116
|
|
|
117
|
-
Settings are stored in the DSH
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
Settings are stored in the active DSH profile configuration. DSH imports the
|
|
118
|
+
previous `inline-media` section from `settings.yaml` when upgrading. Writes are
|
|
119
|
+
available only on loopback connections; remote browsers can read these four
|
|
120
|
+
settings through the plugin's authenticated RPC channel but cannot change them.
|
|
120
121
|
|
|
121
122
|
### ComfyUI origin
|
|
122
123
|
|
|
@@ -156,6 +157,7 @@ limitations.
|
|
|
156
157
|
| Command | Purpose |
|
|
157
158
|
| --- | --- |
|
|
158
159
|
| `node test.mjs` | Run the dependency-free unit tests directly. |
|
|
160
|
+
| `node test.mjs --host` | Run the host registration check after installing peer dependencies. |
|
|
159
161
|
| `npm test` | Run the same unit test suite through npm. |
|
|
160
162
|
| `npm run lint` | Syntax-check the host modules and test file. |
|
|
161
163
|
|
|
@@ -163,7 +165,7 @@ limitations.
|
|
|
163
165
|
|
|
164
166
|
| Path | Responsibility |
|
|
165
167
|
| --- | --- |
|
|
166
|
-
| `index.js` | Host
|
|
168
|
+
| `index.js` | Host API routes, ComfyUI proxy, and settings registration. |
|
|
167
169
|
| `lib.js` | Pure, dependency-free helpers. |
|
|
168
170
|
| `client/client.js` | Turn projection, media renderer, and settings UI. |
|
|
169
171
|
| `cordis.patch.yml` | Bundle mounting and integration points. |
|
package/README.zh-CN.md
CHANGED
|
@@ -57,14 +57,14 @@ audio/sound.mp3 → <audio controls>
|
|
|
57
57
|
|
|
58
58
|
## 安装
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
**1.0.12** 兼容 DSH **v0.1.7-rc.2** 和 **v0.2.0-rc.2**。在 v0.2.0-rc.2 的真实 Web 页面中,插件正常加载,设置刷新后仍能读回,带认证的 ComfyUI 图片接口返回可解码图片,未认证访问返回 HTTP 401。本地图片读取和内联图片卡片已在 v0.1.7-rc.2 验证,尚未在 v0.2.0-rc.2 重新测试;视频和音频播放也尚未验证。请从 v1.0.11 升级,以移除已废弃的 `settingsScope` 依赖。
|
|
61
61
|
|
|
62
62
|
### 通过 npm 安装
|
|
63
63
|
|
|
64
64
|
在 Web profile 中安装并启用 Bundle:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
dsh plugin --profile web add @wisdoverse/dsh-inline-media-viewer@1.0.
|
|
67
|
+
dsh plugin --profile web add @wisdoverse/dsh-inline-media-viewer@1.0.12
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
安装后重启 Web profile。
|
|
@@ -95,7 +95,7 @@ dsh plugin --profile web add @wisdoverse/dsh-inline-media-viewer@1.0.11
|
|
|
95
95
|
|
|
96
96
|
3. 重新构建或重启 Web profile。
|
|
97
97
|
|
|
98
|
-
插件通过 [`cordis.patch.yml`](cordis.patch.yml) 挂载宿主
|
|
98
|
+
插件通过 [`cordis.patch.yml`](cordis.patch.yml) 挂载宿主 API 路由、客户端投影和设置区域。
|
|
99
99
|
|
|
100
100
|
## 配置
|
|
101
101
|
|
|
@@ -110,7 +110,7 @@ dsh plugin --profile web add @wisdoverse/dsh-inline-media-viewer@1.0.11
|
|
|
110
110
|
| 媒体最大高度 | `380 px` | `160`–`1200 px` |
|
|
111
111
|
| ComfyUI 源站(可选) | 留空 | `http(s)://host[:port]` |
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
设置值保存在当前 DSH profile 配置中。升级时,DSH 会导入旧 `settings.yaml` 中的 `inline-media` 设置。只有回环连接可以写入;远程浏览器可通过插件的认证 RPC 通道读取这四项设置,但不能修改。
|
|
114
114
|
|
|
115
115
|
### ComfyUI 源站
|
|
116
116
|
|
|
@@ -144,6 +144,7 @@ ComfyUI 来源。非空的无效地址会明确报错,而不会静默回退到
|
|
|
144
144
|
| 命令 | 用途 |
|
|
145
145
|
| --- | --- |
|
|
146
146
|
| `node test.mjs` | 直接运行无依赖的单元测试。 |
|
|
147
|
+
| `node test.mjs --host` | 安装 peer 依赖后运行宿主注册检查。 |
|
|
147
148
|
| `npm test` | 通过 npm 运行同一套单元测试。 |
|
|
148
149
|
| `npm run lint` | 对宿主模块和测试文件执行语法检查。 |
|
|
149
150
|
|
|
@@ -151,7 +152,7 @@ ComfyUI 来源。非空的无效地址会明确报错,而不会静默回退到
|
|
|
151
152
|
|
|
152
153
|
| 路径 | 职责 |
|
|
153
154
|
| --- | --- |
|
|
154
|
-
| `index.js` | 宿主
|
|
155
|
+
| `index.js` | 宿主 API 路由、ComfyUI 代理和设置注册。 |
|
|
155
156
|
| `lib.js` | 无依赖的纯辅助函数。 |
|
|
156
157
|
| `client/client.js` | 消息投影、媒体渲染器和设置界面。 |
|
|
157
158
|
| `cordis.patch.yml` | Bundle 挂载和集成点。 |
|
package/SECURITY.md
CHANGED
|
@@ -39,9 +39,8 @@ exfiltration, no arbitrary SSRF, no unbounded memory use.
|
|
|
39
39
|
|
|
40
40
|
## Transport
|
|
41
41
|
|
|
42
|
-
- The media
|
|
43
|
-
|
|
44
|
-
browser-trust fence and session gating as DSH's own RPC surface.
|
|
42
|
+
- The media route `/api/inline-media.read` uses DSH's authenticated `/api`
|
|
43
|
+
bridge and browser-trust fence.
|
|
45
44
|
- The client renderer never receives file system paths back — only `data:`
|
|
46
45
|
URLs — so a hostile session cannot use the channel as a file oracle for
|
|
47
46
|
paths the UI already knows.
|
|
@@ -53,6 +52,8 @@ exfiltration, no arbitrary SSRF, no unbounded memory use.
|
|
|
53
52
|
comfyUrl string ≤ 512 chars, parsed to a bare origin at use time).
|
|
54
53
|
- The settings service only writes back for **loopback** connections; remote
|
|
55
54
|
browsers are read-only, so a remote viewer cannot persist state.
|
|
55
|
+
- The plugin's authenticated `settings` RPC returns only its four non-secret
|
|
56
|
+
display and ComfyUI preferences to remote browsers.
|
|
56
57
|
|
|
57
58
|
## Known limitations
|
|
58
59
|
|
package/client/client.js
CHANGED
|
@@ -7,15 +7,16 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
|
|
8
8
|
const React = require("react");
|
|
9
9
|
const { createElement: h, useEffect, useMemo, useState } = React;
|
|
10
|
-
const CHANNEL = "/
|
|
11
|
-
const ENDPOINT = "read";
|
|
10
|
+
const CHANNEL = "/api";
|
|
11
|
+
const ENDPOINT = "inline-media.read";
|
|
12
12
|
const DISPLAY_CAP = 12;
|
|
13
13
|
const SETTINGS_NAMESPACE = "inline-media";
|
|
14
14
|
// Empty config = the host-side built-in default
|
|
15
15
|
// (http://127.0.0.1:8188, ComfyUI's standard local address).
|
|
16
16
|
const COMFY_DEFAULT_URL = "";
|
|
17
17
|
const DEFAULT_SETTINGS = Object.freeze({ autoRender: true, displayCap: DISPLAY_CAP, imageMaxPx: 380, comfyUrl: COMFY_DEFAULT_URL });
|
|
18
|
-
let
|
|
18
|
+
let settingsForm = null;
|
|
19
|
+
let remoteSettingsRequest = null;
|
|
19
20
|
|
|
20
21
|
function clampInt(raw, min, max, fallback) {
|
|
21
22
|
const n = Number.parseInt(String(raw), 10);
|
|
@@ -23,10 +24,8 @@ window.__ModuleLoader__.load({
|
|
|
23
24
|
return Math.min(max, Math.max(min, n));
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
function readSettings() {
|
|
27
|
-
|
|
28
|
-
const snapshot = settingsScope.getSnapshot();
|
|
29
|
-
const value = snapshot && snapshot.value;
|
|
27
|
+
function readSettings(remoteSettings) {
|
|
28
|
+
const value = settingsForm && settingsForm.getSnapshot().value || remoteSettings;
|
|
30
29
|
if (!value || typeof value !== "object") return DEFAULT_SETTINGS;
|
|
31
30
|
return {
|
|
32
31
|
autoRender: value.autoRender !== false,
|
|
@@ -37,6 +36,20 @@ window.__ModuleLoader__.load({
|
|
|
37
36
|
: DEFAULT_SETTINGS.comfyUrl,
|
|
38
37
|
};
|
|
39
38
|
}
|
|
39
|
+
|
|
40
|
+
function fetchRemoteSettings(connection) {
|
|
41
|
+
if (remoteSettingsRequest === null) {
|
|
42
|
+
remoteSettingsRequest = connection.rpc.call(CHANNEL, "inline-media.settings", {}).then((result) => {
|
|
43
|
+
if (result.ok) return result.value;
|
|
44
|
+
remoteSettingsRequest = null;
|
|
45
|
+
return null;
|
|
46
|
+
}).catch(() => {
|
|
47
|
+
remoteSettingsRequest = null;
|
|
48
|
+
return null;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return remoteSettingsRequest;
|
|
52
|
+
}
|
|
40
53
|
const MEDIA_EXTENSIONS = new Set([
|
|
41
54
|
"png", "jpg", "jpeg", "webp", "gif", "avif", "bmp",
|
|
42
55
|
"mp4", "webm", "mov", "m4v", "mkv", "avi", "ogv",
|
|
@@ -159,8 +172,6 @@ window.__ModuleLoader__.load({
|
|
|
159
172
|
};
|
|
160
173
|
|
|
161
174
|
function selectMedia(owner) {
|
|
162
|
-
const settings = readSettings();
|
|
163
|
-
if (!settings.autoRender) return null;
|
|
164
175
|
const text = [];
|
|
165
176
|
const mentions = owner.turn.data && owner.turn.data.get("inline-media-mentions");
|
|
166
177
|
if (mentions) {
|
|
@@ -173,7 +184,7 @@ window.__ModuleLoader__.load({
|
|
|
173
184
|
.filter((block) => block && block.kind === "text" && typeof block.text === "string")
|
|
174
185
|
.map((block) => block.text));
|
|
175
186
|
}
|
|
176
|
-
const candidates = extractCandidates(text.join("\n"))
|
|
187
|
+
const candidates = extractCandidates(text.join("\n"));
|
|
177
188
|
return candidates.length === 0 ? null : candidates;
|
|
178
189
|
}
|
|
179
190
|
|
|
@@ -363,9 +374,22 @@ window.__ModuleLoader__.load({
|
|
|
363
374
|
})) : null);
|
|
364
375
|
}
|
|
365
376
|
|
|
366
|
-
function
|
|
367
|
-
const
|
|
377
|
+
function MediaTailContent({ matched, connection, sessionId }) {
|
|
378
|
+
const [remoteSettings, setRemoteSettings] = useState(null);
|
|
379
|
+
const [settings, setSettings] = useState(() => readSettings());
|
|
380
|
+
useEffect(() => {
|
|
381
|
+
let active = true;
|
|
382
|
+
void fetchRemoteSettings(connection).then((value) => {
|
|
383
|
+
if (active) setRemoteSettings(value);
|
|
384
|
+
});
|
|
385
|
+
return () => { active = false; };
|
|
386
|
+
}, [connection]);
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
setSettings(readSettings(remoteSettings));
|
|
389
|
+
return settingsForm?.subscribe(() => setSettings(readSettings(remoteSettings)));
|
|
390
|
+
}, [remoteSettings]);
|
|
368
391
|
const candidates = useMemo(() => matched.slice(0, settings.displayCap), [matched, settings.displayCap]);
|
|
392
|
+
if (!settings.autoRender) return null;
|
|
369
393
|
return h("section", {
|
|
370
394
|
"aria-label": "媒体预览",
|
|
371
395
|
style: { marginTop: 14 },
|
|
@@ -393,11 +417,16 @@ window.__ModuleLoader__.load({
|
|
|
393
417
|
}))));
|
|
394
418
|
}
|
|
395
419
|
|
|
420
|
+
function MediaTail(props) {
|
|
421
|
+
const matched = selectMedia(props);
|
|
422
|
+
return matched === null ? null : h(MediaTailContent, { ...props, matched });
|
|
423
|
+
}
|
|
424
|
+
|
|
396
425
|
const SETTINGS_NS = "inlineMedia";
|
|
397
426
|
const SETTINGS_DICT = {
|
|
398
427
|
zh: {
|
|
399
428
|
nav: "媒体预览",
|
|
400
|
-
intro: "
|
|
429
|
+
intro: "调整聊天中内联媒体预览的行为。设置会保存到当前 profile 配置。",
|
|
401
430
|
autoRender: "自动渲染检测到的媒体",
|
|
402
431
|
autoRenderHint: "关闭后仅扫描不展示(仍会扫描消息)。",
|
|
403
432
|
displayCap: "每回合最多显示",
|
|
@@ -412,7 +441,7 @@ window.__ModuleLoader__.load({
|
|
|
412
441
|
},
|
|
413
442
|
en: {
|
|
414
443
|
nav: "Media preview",
|
|
415
|
-
intro: "Tune how inline media is previewed in conversations. Changes persist to the
|
|
444
|
+
intro: "Tune how inline media is previewed in conversations. Changes persist to the active profile configuration.",
|
|
416
445
|
autoRender: "Auto-render detected media",
|
|
417
446
|
autoRenderHint: "When off, mentions are still scanned but nothing is shown.",
|
|
418
447
|
displayCap: "Max items per turn",
|
|
@@ -427,10 +456,19 @@ window.__ModuleLoader__.load({
|
|
|
427
456
|
},
|
|
428
457
|
};
|
|
429
458
|
|
|
430
|
-
function MediaSettingsSection({
|
|
431
|
-
const [snap, setSnap] = useState(
|
|
432
|
-
|
|
433
|
-
|
|
459
|
+
function MediaSettingsSection({ form, connection, t }) {
|
|
460
|
+
const [snap, setSnap] = useState(form ? form.getSnapshot() : null);
|
|
461
|
+
const [remoteSettings, setRemoteSettings] = useState(null);
|
|
462
|
+
useEffect(() => (form ? form.subscribe(() => setSnap(form.getSnapshot())) : undefined), [form]);
|
|
463
|
+
useEffect(() => {
|
|
464
|
+
let active = true;
|
|
465
|
+
void fetchRemoteSettings(connection).then((value) => {
|
|
466
|
+
if (active) setRemoteSettings(value);
|
|
467
|
+
});
|
|
468
|
+
return () => { active = false; };
|
|
469
|
+
}, [connection]);
|
|
470
|
+
const stored = snap && snap.value && typeof snap.value === "object"
|
|
471
|
+
? snap.value : remoteSettings || DEFAULT_SETTINGS;
|
|
434
472
|
const writable = snap ? snap.writable === true : false;
|
|
435
473
|
const comfyUrlValue = String(typeof stored.comfyUrl === "string" ? stored.comfyUrl : DEFAULT_SETTINGS.comfyUrl);
|
|
436
474
|
const comfyUrlInvalid = comfyUrlValue.trim() !== "" && !normalizeComfyOrigin(comfyUrlValue);
|
|
@@ -438,15 +476,14 @@ window.__ModuleLoader__.load({
|
|
|
438
476
|
setSnap((prev) => (prev
|
|
439
477
|
? { ...prev, value: { ...(prev.value || DEFAULT_SETTINGS), [field]: next } }
|
|
440
478
|
: prev));
|
|
441
|
-
if (
|
|
479
|
+
if (form && writable) void form.set(field, next).catch(() => setSnap(form.getSnapshot()));
|
|
442
480
|
};
|
|
443
481
|
const reset = () => {
|
|
444
|
-
if (!
|
|
482
|
+
if (!form || !writable) return;
|
|
445
483
|
setSnap((prev) => (prev ? { ...prev, value: DEFAULT_SETTINGS } : prev));
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
scope.unset("comfyUrl");
|
|
484
|
+
void form.mutate(["autoRender", "displayCap", "imageMaxPx", "comfyUrl"]
|
|
485
|
+
.map((field) => ({ op: "unset", path: [field] })))
|
|
486
|
+
.catch(() => setSnap(form.getSnapshot()));
|
|
450
487
|
};
|
|
451
488
|
const field = (label, hint, node) => h("label", {
|
|
452
489
|
style: { display: "grid", gap: 4, margin: "14px 0 0" },
|
|
@@ -542,18 +579,22 @@ window.__ModuleLoader__.load({
|
|
|
542
579
|
}
|
|
543
580
|
|
|
544
581
|
const name = "dsh-inline-media-viewer";
|
|
545
|
-
const inject = ["slots", "connection", "
|
|
582
|
+
const inject = ["slots", "connection", "configForms", "locale", "uiConversation"];
|
|
546
583
|
|
|
547
584
|
function apply(ctx) {
|
|
548
585
|
const connection = ctx.get("connection");
|
|
549
586
|
ctx.uiConversation.events.register(mediaMentionsDefinition);
|
|
550
587
|
ctx.effect(() => ctx.locale.register(SETTINGS_NS, SETTINGS_DICT), "inline-media: dictionaries");
|
|
551
588
|
const t = ctx.locale.bind(SETTINGS_NS);
|
|
552
|
-
const
|
|
553
|
-
|
|
589
|
+
const form = ctx.configForms.get(SETTINGS_NAMESPACE);
|
|
590
|
+
settingsForm = form;
|
|
591
|
+
ctx.effect(() => () => {
|
|
592
|
+
settingsForm = null;
|
|
593
|
+
remoteSettingsRequest = null;
|
|
594
|
+
}, "inline-media: client settings");
|
|
554
595
|
ctx.slots.inject("conversation.chat.turnTail", () => ctx.slots.register({
|
|
555
596
|
name: "conversation.chat.turnTail",
|
|
556
|
-
|
|
597
|
+
id: "inline-media",
|
|
557
598
|
inject: () => ({ connection }),
|
|
558
599
|
}, MediaTail));
|
|
559
600
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
@@ -562,14 +603,14 @@ window.__ModuleLoader__.load({
|
|
|
562
603
|
order: 25,
|
|
563
604
|
label: () => t("nav"),
|
|
564
605
|
locale: SETTINGS_NS,
|
|
565
|
-
inject: () => ({
|
|
606
|
+
inject: () => ({ form, connection }),
|
|
566
607
|
}, MediaSettingsSection));
|
|
567
608
|
}
|
|
568
609
|
|
|
569
610
|
exports.apply = apply;
|
|
570
611
|
exports.inject = inject;
|
|
571
612
|
exports.name = name;
|
|
572
|
-
exports.testing = Object.freeze({ extractCandidates, mediaMentionsDefinition, mediaTransport, selectMedia, toolArgumentText });
|
|
613
|
+
exports.testing = Object.freeze({ extractCandidates, fetchRemoteSettings, mediaMentionsDefinition, mediaTransport, readSettings, selectMedia, toolArgumentText });
|
|
573
614
|
return module.exports;
|
|
574
615
|
},
|
|
575
616
|
});
|
package/cordis.patch.yml
CHANGED
package/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-inline-media-viewer — host half.
|
|
3
3
|
*
|
|
4
|
-
* Serves media bytes for the web client over
|
|
5
|
-
* (`/inline-media
|
|
4
|
+
* Serves media bytes for the web client over authenticated DSH API routes
|
|
5
|
+
* (`/api/inline-media.read`), reading workspace-local files under the calling
|
|
6
6
|
* session's workspace root and proxying ComfyUI media URLs onto the
|
|
7
7
|
* user-configured ComfyUI origin (default: `http://127.0.0.1:8188`,
|
|
8
|
-
* ComfyUI's standard local address).
|
|
9
|
-
*
|
|
10
|
-
* and the ComfyUI server address.
|
|
8
|
+
* ComfyUI's standard local address). Its live Config fields are edited
|
|
9
|
+
* through DSH's configuration forms.
|
|
11
10
|
*
|
|
12
11
|
* Security model (see SECURITY.md): local reads are confined to the session
|
|
13
12
|
* workspace (realpath + containment), remote reads always fetch from the
|
|
@@ -28,20 +27,11 @@ export { testing } from "./lib.js";
|
|
|
28
27
|
export const name = "dsh-inline-media-viewer";
|
|
29
28
|
export const inject = ["connection", "sessions", "sessionQuery", "settings"];
|
|
30
29
|
|
|
31
|
-
const CHANNEL = "/inline-media";
|
|
32
30
|
const ENDPOINT = "read";
|
|
33
31
|
|
|
34
32
|
/** Persistent user settings namespace for this plugin. */
|
|
35
33
|
export const MEDIA_SETTINGS_NAMESPACE = "inline-media";
|
|
36
34
|
|
|
37
|
-
/** Schema of the user settings section. */
|
|
38
|
-
export const MEDIA_SETTINGS_SCHEMA = z.object({
|
|
39
|
-
autoRender: z.boolean().required(),
|
|
40
|
-
displayCap: z.number().step(1).min(1).max(30).required(),
|
|
41
|
-
imageMaxPx: z.number().step(1).min(160).max(1200).required(),
|
|
42
|
-
comfyUrl: z.string().max(512).required(),
|
|
43
|
-
});
|
|
44
|
-
|
|
45
35
|
/** Composition defaults; the user layer overrides these. */
|
|
46
36
|
export const MEDIA_SETTINGS_DEFAULTS = Object.freeze({
|
|
47
37
|
autoRender: true,
|
|
@@ -52,6 +42,14 @@ export const MEDIA_SETTINGS_DEFAULTS = Object.freeze({
|
|
|
52
42
|
comfyUrl: "",
|
|
53
43
|
});
|
|
54
44
|
|
|
45
|
+
/** Live configuration projected into DSH's settings forms. */
|
|
46
|
+
export const Config = z.object({
|
|
47
|
+
autoRender: z.boolean().default(MEDIA_SETTINGS_DEFAULTS.autoRender).volatile(),
|
|
48
|
+
displayCap: z.number().step(1).min(1).max(30).default(MEDIA_SETTINGS_DEFAULTS.displayCap).volatile(),
|
|
49
|
+
imageMaxPx: z.number().step(1).min(160).max(1200).default(MEDIA_SETTINGS_DEFAULTS.imageMaxPx).volatile(),
|
|
50
|
+
comfyUrl: z.string().max(512).default(MEDIA_SETTINGS_DEFAULTS.comfyUrl).volatile(),
|
|
51
|
+
});
|
|
52
|
+
|
|
55
53
|
function success(value) {
|
|
56
54
|
return { ok: true, value };
|
|
57
55
|
}
|
|
@@ -136,6 +134,7 @@ async function readLocal(ctx, source, sessionId) {
|
|
|
136
134
|
}
|
|
137
135
|
|
|
138
136
|
async function handleRead(ctx, endpoint, payload, signal, resolveSettings) {
|
|
137
|
+
if (endpoint === "settings") return success(resolveSettings());
|
|
139
138
|
if (endpoint !== ENDPOINT) return failure("unknown inline-media endpoint");
|
|
140
139
|
if (!payload || typeof payload !== "object") return failure("invalid request");
|
|
141
140
|
const { source, sessionId } = payload;
|
|
@@ -155,21 +154,30 @@ async function handleRead(ctx, endpoint, payload, signal, resolveSettings) {
|
|
|
155
154
|
}
|
|
156
155
|
}
|
|
157
156
|
|
|
158
|
-
export function apply(ctx) {
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
export function apply(ctx, config) {
|
|
158
|
+
ctx.effect(() => ctx.settings.configure({ auto: false }, ctx.fiber), "inline-media: settings page");
|
|
159
|
+
const resolveSettings = () => ({
|
|
160
|
+
autoRender: config.autoRender.get(),
|
|
161
|
+
displayCap: config.displayCap.get(),
|
|
162
|
+
imageMaxPx: config.imageMaxPx.get(),
|
|
163
|
+
comfyUrl: config.comfyUrl.get(),
|
|
161
164
|
});
|
|
162
|
-
const resolveSettings = () => settings.get();
|
|
163
165
|
ctx.inject(["connection"], (connectionCtx) => {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
166
|
+
for (const endpoint of ["read", "settings"]) {
|
|
167
|
+
connectionCtx.effect(() => connectionCtx.connection.fetch.register({
|
|
168
|
+
path: `/api/inline-media.${endpoint}`,
|
|
169
|
+
methods: ["POST"],
|
|
170
|
+
requestBody: "buffered",
|
|
171
|
+
async fetch(request) {
|
|
172
|
+
let message;
|
|
173
|
+
try { message = await request.json(); } catch { return new Response("invalid request", { status: 400 }); }
|
|
174
|
+
if (message?.type !== "client-request" || typeof message.rpcId !== "string" ||
|
|
175
|
+
message.method !== `inline-media.${endpoint}`) return new Response("invalid request", { status: 400 });
|
|
176
|
+
const result = await handleRead(ctx, endpoint, message.payload, request.signal, resolveSettings);
|
|
177
|
+
return Response.json({ type: "server-response", rpcId: message.rpcId, result });
|
|
178
|
+
},
|
|
179
|
+
}), `inline-media: ${endpoint} route`);
|
|
180
|
+
}
|
|
173
181
|
});
|
|
174
182
|
}
|
|
175
183
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wisdoverse/dsh-inline-media-viewer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "Persistent inline image, video, and audio previews for DeepSeek Harness Web conversations, with workspace-confined local reads and an optional ComfyUI proxy.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"lint": "node --check index.js && node --check lib.js && node --check client/client.js && node --check test.mjs"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
57
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
56
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
57
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
58
58
|
},
|
|
59
59
|
"dsh": {
|
|
60
60
|
"bundle": {
|