@wisdoverse/dsh-inline-media-viewer 1.0.3 → 1.0.5

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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.5
4
+
5
+ - Register the browser bundle under the scoped package name expected by the
6
+ DSH module loader.
7
+
8
+ ## 1.0.4
9
+
10
+ - Read media candidates from the standard assistant-step projection so the
11
+ first inline preview renders reliably.
12
+ - Avoid extracting duplicate paths from Markdown links, code spans, and URLs.
13
+
3
14
  ## 1.0.3
4
15
 
5
16
  - Clarify and enforce the transport split: workspace files use the DSH host,
package/client/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  window.__ModuleLoader__.load({
2
- id: "dsh-inline-media-viewer",
2
+ id: "@wisdoverse/dsh-inline-media-viewer",
3
3
  factory: (require) => {
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
@@ -9,7 +9,6 @@ window.__ModuleLoader__.load({
9
9
  const { createElement: h, useEffect, useMemo, useState } = React;
10
10
  const CHANNEL = "/inline-media";
11
11
  const ENDPOINT = "read";
12
- const DATA_KEY = "inlineMedia";
13
12
  const DISPLAY_CAP = 12;
14
13
  const SETTINGS_NAMESPACE = "inline-media";
15
14
  // Empty config = the host-side built-in default
@@ -88,79 +87,48 @@ window.__ModuleLoader__.load({
88
87
  found.push({ source, kind });
89
88
  };
90
89
 
91
- for (const match of text.matchAll(/!?\[[^\]]*\]\(([^)]+)\)/g)) add(match[1]);
92
- for (const match of text.matchAll(/`([^`\n]+)`/g)) add(match[1]);
93
- for (const match of text.matchAll(/https?:\/\/[^\s<>"'`]+/gi)) add(match[0]);
90
+ let plain = text.replace(/!?\[[^\]]*\]\(([^)]+)\)/g, (match, source) => {
91
+ add(source);
92
+ return " ".repeat(match.length);
93
+ });
94
+ plain = plain.replace(/`([^`\n]+)`/g, (match, source) => {
95
+ add(source);
96
+ return " ".repeat(match.length);
97
+ });
98
+ plain = plain.replace(/https?:\/\/[^\s<>"'`]+/gi, (source) => {
99
+ add(source);
100
+ return " ".repeat(source.length);
101
+ });
94
102
 
95
103
  const ext = Array.from(MEDIA_EXTENSIONS).join("|");
96
104
  const pathPattern = new RegExp(
97
105
  String.raw`(?:\/|\.\.?\/|[\w.-]+\/)[^\s<>"'\x60()\[\]{}]+?\.(?:${ext})(?:\?[^\s<>"'\x60]*)?`,
98
106
  "gi",
99
107
  );
100
- for (const match of text.matchAll(pathPattern)) add(match[0]);
108
+ for (const match of plain.matchAll(pathPattern)) add(match[0]);
101
109
  return found;
102
110
  }
103
111
 
104
- function assistantText(event) {
105
- if (event.type !== "assistant/message") return "";
106
- const content = event.data && event.data.message && event.data.message.content;
107
- if (!Array.isArray(content)) return "";
108
- return content
109
- .filter((block) => block && block.type === "text" && typeof block.text === "string")
110
- .map((block) => block.text)
111
- .join("\n");
112
- }
113
-
114
- const mediaDefinition = {
115
- kind: DATA_KEY,
116
- match(event) {
117
- if (event.type === "turn/start") {
118
- return { id: String(event.data.turn), role: "start" };
119
- }
120
- if (event.type === "assistant/message" && Number.isInteger(event.data && event.data.turn)) {
121
- return { id: String(event.data.turn), role: "update" };
122
- }
123
- return null;
124
- },
125
- start(_context, match) {
126
- return { turn: match.event.data.turn, candidates: [] };
127
- },
128
- update(context, match) {
129
- const additions = extractCandidates(assistantText(match.event)).map((candidate) => ({
130
- ...candidate,
131
- seq: match.event.seq,
132
- }));
133
- if (additions.length === 0) return context.state;
134
- const seen = new Set(context.state.candidates.map((candidate) => candidate.source));
135
- const fresh = additions.filter((candidate) => {
136
- if (seen.has(candidate.source)) return false;
137
- seen.add(candidate.source);
138
- return true;
139
- });
140
- if (fresh.length === 0) return context.state;
141
- return {
142
- ...context.state,
143
- candidates: [...context.state.candidates, ...fresh].slice(0, DISPLAY_CAP),
144
- };
145
- },
146
- buildLocationData(context, scope) {
147
- if (scope !== "turn" || context.state === undefined) return null;
148
- return {
149
- kind: "turn",
150
- turn: context.state.turn,
151
- key: DATA_KEY,
152
- value: { candidates: context.state.candidates },
153
- };
154
- },
155
- };
156
-
157
112
  function selectMedia(owner) {
158
113
  const settings = readSettings();
159
114
  if (!settings.autoRender) return null;
160
- const data = owner.turn.data.get(DATA_KEY);
161
- if (!data || !Array.isArray(data.candidates)) return null;
162
- const candidates = data.candidates.filter((candidate) => candidate.seq <= owner.seq);
163
- return candidates.length === 0 ? null : candidates.slice(0, settings.displayCap);
115
+ const candidates = [];
116
+ const seen = new Set();
117
+ for (const step of owner.turn.steps) {
118
+ const assistant = step.data.get("assistant-step");
119
+ if (!assistant || !assistant.finalNode || assistant.finalNode.seq > owner.seq || !Array.isArray(assistant.blocks)) continue;
120
+ const text = assistant.blocks
121
+ .filter((block) => block && block.kind === "text" && typeof block.text === "string")
122
+ .map((block) => block.text)
123
+ .join("\n");
124
+ for (const candidate of extractCandidates(text)) {
125
+ if (seen.has(candidate.source)) continue;
126
+ seen.add(candidate.source);
127
+ candidates.push(candidate);
128
+ if (candidates.length === settings.displayCap) return candidates;
129
+ }
130
+ }
131
+ return candidates.length === 0 ? null : candidates;
164
132
  }
165
133
 
166
134
  function normalizeComfyOrigin(input) {
@@ -528,7 +496,7 @@ window.__ModuleLoader__.load({
528
496
  }
529
497
 
530
498
  const name = "dsh-inline-media-viewer";
531
- const inject = ["slots", "conversationEvents", "connection", "settingsScope", "locale"];
499
+ const inject = ["slots", "connection", "settingsScope", "locale"];
532
500
 
533
501
  function apply(ctx) {
534
502
  const connection = ctx.get("connection");
@@ -536,7 +504,6 @@ window.__ModuleLoader__.load({
536
504
  const t = ctx.locale.bind(SETTINGS_NS);
537
505
  const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE });
538
506
  settingsScope = scope;
539
- ctx.conversationEvents.register(mediaDefinition);
540
507
  ctx.slots.inject("conversation.chat.turnTail", () => ctx.slots.register({
541
508
  name: "conversation.chat.turnTail",
542
509
  select: selectMedia,
@@ -555,7 +522,7 @@ window.__ModuleLoader__.load({
555
522
  exports.apply = apply;
556
523
  exports.inject = inject;
557
524
  exports.name = name;
558
- exports.testing = Object.freeze({ mediaTransport });
525
+ exports.testing = Object.freeze({ extractCandidates, mediaTransport, selectMedia });
559
526
  return module.exports;
560
527
  },
561
528
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wisdoverse/dsh-inline-media-viewer",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
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",