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

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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.6
4
+
5
+ - Resolve workspace media from persisted session metadata when a historical
6
+ conversation is no longer attached to a live host session.
7
+
8
+ ## 1.0.5
9
+
10
+ - Register the browser bundle under the scoped package name expected by the
11
+ DSH module loader.
12
+
13
+ ## 1.0.4
14
+
15
+ - Read media candidates from the standard assistant-step projection so the
16
+ first inline preview renders reliably.
17
+ - Avoid extracting duplicate paths from Markdown links, code spans, and URLs.
18
+
3
19
  ## 1.0.3
4
20
 
5
21
  - 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/index.js CHANGED
@@ -22,12 +22,12 @@ import { isAbsolute, resolve } from "node:path";
22
22
  import z from "@deepseek-ai/schemastery";
23
23
  import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
24
24
 
25
- import { COMFY_DEFAULT_ORIGIN, MAX_BYTES, comfyUrl, isInside, mimeOf, normalizeComfyOrigin, testing } from "./lib.js";
25
+ import { COMFY_DEFAULT_ORIGIN, MAX_BYTES, comfyUrl, isInside, mimeOf, normalizeComfyOrigin, resolveSessionCwd, testing } from "./lib.js";
26
26
 
27
27
  export { testing } from "./lib.js";
28
28
 
29
29
  export const name = "dsh-inline-media-viewer";
30
- export const inject = ["connection", "sessions", "settings"];
30
+ export const inject = ["connection", "sessions", "sessionQuery", "settings"];
31
31
 
32
32
  const CHANNEL = "/inline-media";
33
33
  const ENDPOINT = "read";
@@ -119,8 +119,7 @@ async function readRemote(source, signal, settingsValue) {
119
119
  }
120
120
 
121
121
  async function readLocal(ctx, source, sessionId) {
122
- const session = ctx.sessions.get(sessionId);
123
- const cwd = session?.header?.cwd;
122
+ const cwd = await resolveSessionCwd(ctx.sessions, ctx.sessionQuery, sessionId);
124
123
  if (!cwd) throw new Error("session working directory is unavailable");
125
124
  const mime = mimeOf(source);
126
125
  if (!mime) throw new Error("unsupported media extension");
package/lib.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * dsh-inline-media-viewer — pure helpers.
2
+ * dsh-inline-media-viewer — dependency-free helpers.
3
3
  *
4
4
  * This module has NO runtime dependencies: unit tests and reviewers can import
5
5
  * it anywhere. Integration concerns (RPC channel, settings registration) live
@@ -79,6 +79,11 @@ export function isInside(root, target) {
79
79
  return rel === "" || (!isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`));
80
80
  }
81
81
 
82
+ export async function resolveSessionCwd(sessions, sessionQuery, sessionId) {
83
+ const live = sessions.get(sessionId);
84
+ return live ? live.header.cwd : (await sessionQuery.readSession(sessionId)).session.cwd;
85
+ }
86
+
82
87
  /**
83
88
  * Parse a user-configured ComfyUI address into a canonical origin URL.
84
89
  *
@@ -175,4 +180,5 @@ export const testing = Object.freeze({
175
180
  isInside,
176
181
  mimeOf,
177
182
  normalizeComfyOrigin,
183
+ resolveSessionCwd,
178
184
  });
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.6",
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",