@wavespeed/mcp 1.2.0 → 1.2.2

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.
@@ -18,8 +18,13 @@
18
18
  .prompt { color: var(--muted); margin: 0 0 10px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
19
19
  .grid { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
20
20
  .grid.one { grid-template-columns: 1fr; }
21
- .tile { background: var(--card); border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
22
- .tile img, .tile video { display: block; width: 100%; max-height: 520px; object-fit: contain; background: #000; }
21
+ .tile { position: relative; background: var(--card); border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
22
+ .tile img, .tile video { display: block; width: 100%; max-height: 520px; object-fit: contain; background: var(--card); }
23
+ .tile video { background: #000; }
24
+ .tile-spin { position: absolute; top: 40%; left: 50%; margin: -9px 0 0 -9px; pointer-events: none; }
25
+ .code { margin: 0; padding: 12px; max-height: 360px; overflow: auto; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; word-break: break-word; }
26
+ .text { padding: 12px; white-space: pre-wrap; word-break: break-word; max-height: 360px; overflow: auto; }
27
+ .broken { padding: 28px 12px; text-align: center; color: var(--muted); font-size: 13px; }
23
28
  .tile img { cursor: zoom-in; }
24
29
  .tile audio { display: block; width: 100%; padding: 10px; }
25
30
  .tile .meta { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 6px 8px 6px 12px; font-size: 12px; color: var(--muted); }
@@ -111,19 +116,21 @@
111
116
  resize();
112
117
  }
113
118
 
114
- function urlOf(o) {
115
- if (typeof o === "string") return o;
116
- if (o && typeof o === "object") return o.url || o.download_url || o.href || "";
117
- return "";
118
- }
119
+ // File types by extension, the same lists the WaveSpeed site uses to pick a preview.
120
+ var EXT = {
121
+ image: "jpg jpeg png gif webp avif bmp tiff tif svg ico",
122
+ video: "mp4 mov avi mkv webm flv wmv m4v",
123
+ audio: "mp3 wav m4a ogg aac flac wma aiff au mid midi rmi opus",
124
+ model: "glb gltf",
125
+ subtitle: "srt vtt",
126
+ };
119
127
  function kindOf(url) {
120
128
  var path = url.split("?")[0].split("#")[0].toLowerCase();
121
129
  var ext = path.slice(path.lastIndexOf(".") + 1);
122
- if (/^(png|jpe?g|webp|gif|avif|bmp)$/.test(ext)) return "image";
123
- if (/^(mp4|webm|mov|m4v)$/.test(ext)) return "video";
124
- if (/^(mp3|wav|m4a|ogg|flac|aac)$/.test(ext)) return "audio";
130
+ for (var k in EXT) if ((" " + EXT[k] + " ").indexOf(" " + ext + " ") >= 0) return k;
125
131
  return "file";
126
132
  }
133
+ var KIND_LABEL = { model: "3D model", subtitle: "Subtitles", file: "File" };
127
134
  function el(tag, cls, text) {
128
135
  var n = document.createElement(tag);
129
136
  if (cls) n.className = cls;
@@ -136,25 +143,74 @@
136
143
  return b;
137
144
  }
138
145
 
139
- function tile(url) {
140
- var kind = kindOf(url), t = el("div", "tile"), media;
141
- if (!/^https?:\/\//i.test(url)) return null;
146
+ // Like the site: one thing plays at a time.
147
+ function onlyOnePlays(media) {
148
+ media.addEventListener("play", function () {
149
+ document.querySelectorAll("video, audio").forEach(function (m) { if (m !== media) m.pause(); });
150
+ });
151
+ }
152
+ // A spinner stands in until the media has something to draw (the site does the same).
153
+ function withSpinner(t, media, readyEvent) {
154
+ var sp = el("div", "spin tile-spin");
155
+ t.appendChild(sp);
156
+ var done = function () { if (sp.parentNode) sp.parentNode.removeChild(sp); };
157
+ media.addEventListener(readyEvent, done);
158
+ media.addEventListener("error", done);
159
+ }
160
+
161
+ function brokenNote(t, media, what) {
162
+ // The host may not let the card load this address; say so and keep the Open button working.
163
+ media.addEventListener("error", function () {
164
+ if (media.parentNode !== t) return;
165
+ t.replaceChild(el("div", "broken", "This chat can't show the " + what + " here. Use Open / save."), media); resize();
166
+ });
167
+ }
168
+
169
+ // One tile per output. Outputs are links to files, plain text, or JSON.
170
+ function tile(o) {
171
+ var t = el("div", "tile");
172
+ if (o !== null && typeof o === "object") {
173
+ var text = JSON.stringify(o, null, 2);
174
+ t.appendChild(el("pre", "code", text));
175
+ var jm = el("div", "meta"); jm.appendChild(el("span", "", "JSON"));
176
+ var cb = el("button", "", "Copy");
177
+ cb.addEventListener("click", function () {
178
+ try { navigator.clipboard.writeText(text).then(function () { cb.textContent = "Copied"; }, function () { cb.textContent = "Copy failed"; }); }
179
+ catch (e) { cb.textContent = "Copy failed"; }
180
+ });
181
+ jm.appendChild(cb); t.appendChild(jm);
182
+ return t;
183
+ }
184
+ var url = typeof o === "string" ? o : String(o);
185
+ if (!/^https?:\/\//i.test(url)) { t.appendChild(el("div", "text", url)); return t; }
186
+ var kind = kindOf(url), media;
142
187
  if (kind === "image") {
143
188
  media = el("img"); media.src = url; media.alt = "Generated image"; media.loading = "lazy";
144
189
  media.addEventListener("click", function () { openLink(url); });
145
190
  media.addEventListener("load", resize);
191
+ brokenNote(t, media, "image");
146
192
  t.appendChild(media);
193
+ withSpinner(t, media, "load");
147
194
  } else if (kind === "video") {
148
- media = el("video"); media.src = url; media.controls = true; media.playsInline = true; media.preload = "metadata";
195
+ // #t=0.001 makes the browser draw the first frame; without it iOS shows a black box until play.
196
+ media = el("video"); media.src = url + (url.indexOf("#") < 0 ? "#t=0.001" : ""); media.controls = true; media.playsInline = true; media.preload = "metadata"; media.loop = true;
149
197
  media.addEventListener("loadedmetadata", resize);
198
+ onlyOnePlays(media);
199
+ brokenNote(t, media, "video");
150
200
  t.appendChild(media);
201
+ withSpinner(t, media, "loadeddata");
151
202
  } else if (kind === "audio") {
152
203
  media = el("audio"); media.src = url; media.controls = true; media.preload = "metadata";
204
+ onlyOnePlays(media);
205
+ brokenNote(t, media, "audio");
153
206
  t.appendChild(media);
207
+ } else {
208
+ // 3D models, subtitles, documents and other links cannot be drawn here: show the link.
209
+ t.appendChild(el("div", "text", url.split("?")[0].split("/").pop() || url));
154
210
  }
155
- var meta = el("div", "meta"), name = url.split("?")[0].split("/").pop() || "file";
156
- meta.appendChild(el("span", "", kind === "file" ? name : kind));
157
- meta.appendChild(openButton(url, kind === "file" ? "Open" : "Open / save"));
211
+ var meta = el("div", "meta");
212
+ meta.appendChild(el("span", "", KIND_LABEL[kind] || kind));
213
+ meta.appendChild(openButton(url, kind === "file" || kind === "model" || kind === "subtitle" ? "Open" : "Open / save"));
158
214
  t.appendChild(meta);
159
215
  return t;
160
216
  }
@@ -184,10 +240,10 @@
184
240
  body.appendChild(el("div", "err", String(d.error)));
185
241
  setState("failed");
186
242
  } else {
187
- var urls = (d.outputs || []).map(urlOf).filter(Boolean);
188
- if (urls.length) {
189
- var grid = el("div", "grid" + (urls.length === 1 ? " one" : ""));
190
- urls.forEach(function (u) { var t = tile(u); if (t) grid.appendChild(t); });
243
+ var outs = (d.outputs || []).filter(function (o) { return o !== null && o !== undefined && o !== ""; });
244
+ if (outs.length) {
245
+ var grid = el("div", "grid" + (outs.length === 1 ? " one" : ""));
246
+ outs.forEach(function (o) { grid.appendChild(tile(o)); });
191
247
  body.appendChild(grid);
192
248
  setState(d.status === "completed" || d.done ? "done" : (d.status || ""));
193
249
  } else if (d.status && !d.done) {
package/dist/lib/app.js CHANGED
@@ -15,8 +15,9 @@ export const UI_TOOLS = new Set([
15
15
  'edit_video',
16
16
  'extend_video',
17
17
  ]);
18
- // Outputs normally sit on our CDNs, but not always: any https source may load.
19
- export const VIEW_RESOURCE_DOMAINS = ['https:'];
18
+ // "https:" alone should allow every https source, but a host may only honour named hosts when it builds the
19
+ // card's CSP, so the usual media hosts are named too, as wildcards.
20
+ export const VIEW_RESOURCE_DOMAINS = ['https:', 'https://*.cloudfront.net', 'https://*.wavespeed.ai', 'https://*.amazonaws.com'];
20
21
  export function loadView() {
21
22
  return readFileSync(new URL('../../assets/result.html', import.meta.url), 'utf8');
22
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wavespeed/mcp",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "WaveSpeed MCP server — run any model on the live WaveSpeed catalog (image, video, audio, 3D) with schema introspection, local-file upload, and price quotes.",
5
5
  "type": "module",
6
6
  "bin": {