@wisdoverse/dsh-inline-media-viewer 1.0.7 → 1.0.9

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,9 +1,21 @@
1
1
  # Changelog
2
2
 
3
- ## 1.0.7
3
+ ## 1.0.9
4
+
5
+ - Collect media paths from tool-call arguments as well as the assistant's final
6
+ text, so every image read during a turn remains available in its preview.
7
+
8
+ ## 1.0.8 (deprecated)
9
+
10
+ - Preserve output-directory context across assistant steps in one turn, so a
11
+ later final answer containing only media filenames still renders previews.
12
+ - Superseded by 1.0.9 because tool-call-only media references were not retained.
13
+
14
+ ## 1.0.7 (deprecated)
4
15
 
5
16
  - Resolve backticked media filenames against a preceding backticked directory,
6
17
  matching the common "output directory plus file list" response format.
18
+ - Superseded by 1.0.9 because it only inspected assistant text.
7
19
 
8
20
  ## 1.0.6
9
21
 
package/client/client.js CHANGED
@@ -80,6 +80,11 @@ window.__ModuleLoader__.load({
80
80
  const found = [];
81
81
  const seen = new Set();
82
82
  let directory = "";
83
+ const ext = Array.from(MEDIA_EXTENSIONS).join("|");
84
+ const directories = new Set(Array.from(text.matchAll(
85
+ /[`'"]((?:\/|\.\.?\/|[\w.-]+\/)[^`'"\n]*[\/\\])[`'"]/g,
86
+ ), (match) => cleanCandidate(match[1])));
87
+ const soleDirectory = directories.size === 1 ? directories.values().next().value : "";
83
88
  const add = (raw) => {
84
89
  const source = cleanCandidate(raw);
85
90
  const kind = mediaKind(source);
@@ -95,7 +100,10 @@ window.__ModuleLoader__.load({
95
100
  plain = plain.replace(/`([^`\n]+)`/g, (match, source) => {
96
101
  const value = cleanCandidate(source);
97
102
  if (/[/\\]$/.test(value)) directory = value;
98
- else add(directory && !/[/\\]/.test(value) ? `${directory}${value}` : value);
103
+ else {
104
+ const base = directory || soleDirectory;
105
+ add(base && !/[/\\]/.test(value) ? `${base}${value}` : value);
106
+ }
99
107
  return " ".repeat(match.length);
100
108
  });
101
109
  plain = plain.replace(/https?:\/\/[^\s<>"'`]+/gi, (source) => {
@@ -103,7 +111,11 @@ window.__ModuleLoader__.load({
103
111
  return " ".repeat(source.length);
104
112
  });
105
113
 
106
- const ext = Array.from(MEDIA_EXTENSIONS).join("|");
114
+ if (soleDirectory) {
115
+ const filenamePattern = new RegExp(String.raw`[\x60'"]([^\x60'"/\\\n]+\.(?:${ext})(?:\?[^\x60'"\s]*)?)[\x60'"]`, "gi");
116
+ for (const match of text.matchAll(filenamePattern)) add(`${soleDirectory}${match[1]}`);
117
+ }
118
+
107
119
  const pathPattern = new RegExp(
108
120
  String.raw`(?:\/|\.\.?\/|[\w.-]+\/)[^\s<>"'\x60()\[\]{}]+?\.(?:${ext})(?:\?[^\s<>"'\x60]*)?`,
109
121
  "gi",
@@ -112,25 +124,56 @@ window.__ModuleLoader__.load({
112
124
  return found;
113
125
  }
114
126
 
127
+ function toolArgumentText(raw) {
128
+ try {
129
+ const text = [];
130
+ const visit = (value) => {
131
+ if (typeof value === "string") text.push(value);
132
+ else if (value && typeof value === "object") Object.values(value).forEach(visit);
133
+ };
134
+ visit(JSON.parse(raw));
135
+ return text.join("\n");
136
+ } catch (_error) {
137
+ return raw;
138
+ }
139
+ }
140
+
141
+ const mediaMentionsDefinition = {
142
+ kind: "inline-media-mentions",
143
+ match: (event) => {
144
+ if (event.type === "turn/start") return { id: String(event.data.turn), role: "start" };
145
+ if (event.type === "tool/call") return { id: String(event.data.turn), role: "update" };
146
+ return null;
147
+ },
148
+ start: (_context, match) => ({ turn: match.event.data.turn, texts: [] }),
149
+ update: (context, match) => typeof match.event.data.arguments !== "string" ? context.state : {
150
+ ...context.state,
151
+ texts: [...context.state.texts, { seq: match.event.seq, text: toolArgumentText(match.event.data.arguments) }],
152
+ },
153
+ buildLocationData: (context, scope) => scope !== "turn" || context.state === undefined ? null : {
154
+ kind: "turn",
155
+ turn: context.state.turn,
156
+ key: "inline-media-mentions",
157
+ value: { texts: context.state.texts },
158
+ },
159
+ };
160
+
115
161
  function selectMedia(owner) {
116
162
  const settings = readSettings();
117
163
  if (!settings.autoRender) return null;
118
- const candidates = [];
119
- const seen = new Set();
164
+ const text = [];
165
+ const mentions = owner.turn.data && owner.turn.data.get("inline-media-mentions");
166
+ if (mentions) {
167
+ text.push(...mentions.texts.filter((entry) => entry.seq <= owner.seq).map((entry) => entry.text));
168
+ }
120
169
  for (const step of owner.turn.steps) {
121
170
  const assistant = step.data.get("assistant-step");
122
171
  if (!assistant || !assistant.finalNode || assistant.finalNode.seq > owner.seq || !Array.isArray(assistant.blocks)) continue;
123
- const text = assistant.blocks
172
+ text.push(...assistant.blocks
124
173
  .filter((block) => block && block.kind === "text" && typeof block.text === "string")
125
- .map((block) => block.text)
126
- .join("\n");
127
- for (const candidate of extractCandidates(text)) {
128
- if (seen.has(candidate.source)) continue;
129
- seen.add(candidate.source);
130
- candidates.push(candidate);
131
- if (candidates.length === settings.displayCap) return candidates;
132
- }
174
+ .map((block) => block.text));
133
175
  }
176
+ const candidates = extractCandidates(text.join("\n")).slice(0, settings.displayCap);
134
177
  return candidates.length === 0 ? null : candidates;
135
178
  }
136
179
 
@@ -499,10 +542,11 @@ window.__ModuleLoader__.load({
499
542
  }
500
543
 
501
544
  const name = "dsh-inline-media-viewer";
502
- const inject = ["slots", "connection", "settingsScope", "locale"];
545
+ const inject = ["slots", "connection", "settingsScope", "locale", "conversationEvents"];
503
546
 
504
547
  function apply(ctx) {
505
548
  const connection = ctx.get("connection");
549
+ ctx.conversationEvents.register(mediaMentionsDefinition);
506
550
  ctx.effect(() => ctx.locale.register(SETTINGS_NS, SETTINGS_DICT), "inline-media: dictionaries");
507
551
  const t = ctx.locale.bind(SETTINGS_NS);
508
552
  const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE });
@@ -525,7 +569,7 @@ window.__ModuleLoader__.load({
525
569
  exports.apply = apply;
526
570
  exports.inject = inject;
527
571
  exports.name = name;
528
- exports.testing = Object.freeze({ extractCandidates, mediaTransport, selectMedia });
572
+ exports.testing = Object.freeze({ extractCandidates, mediaMentionsDefinition, mediaTransport, selectMedia, toolArgumentText });
529
573
  return module.exports;
530
574
  },
531
575
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wisdoverse/dsh-inline-media-viewer",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
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",