@seed-ship/mcp-ui-solid 5.5.1 → 5.7.0
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 +86 -0
- package/dist/components/FormRenderer.cjs +13 -2
- package/dist/components/FormRenderer.cjs.map +1 -1
- package/dist/components/FormRenderer.d.ts.map +1 -1
- package/dist/components/FormRenderer.js +13 -2
- package/dist/components/FormRenderer.js.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.cjs +11 -0
- package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.d.ts.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.js +11 -0
- package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
- package/dist/components/StreamingUIRenderer.cjs +49 -3
- package/dist/components/StreamingUIRenderer.cjs.map +1 -1
- package/dist/components/StreamingUIRenderer.d.ts.map +1 -1
- package/dist/components/StreamingUIRenderer.js +51 -5
- package/dist/components/StreamingUIRenderer.js.map +1 -1
- package/dist/components/UIResourceRenderer.cjs +102 -14
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts +36 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +104 -16
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/context/MCPUITelemetryContext.cjs +25 -0
- package/dist/context/MCPUITelemetryContext.cjs.map +1 -0
- package/dist/context/MCPUITelemetryContext.d.ts +36 -0
- package/dist/context/MCPUITelemetryContext.d.ts.map +1 -0
- package/dist/context/MCPUITelemetryContext.js +25 -0
- package/dist/context/MCPUITelemetryContext.js.map +1 -0
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-ui-spec/dist/schemas.cjs +25 -6
- package/dist/mcp-ui-spec/dist/schemas.cjs.map +1 -1
- package/dist/mcp-ui-spec/dist/schemas.js +25 -6
- package/dist/mcp-ui-spec/dist/schemas.js.map +1 -1
- package/dist/services/telemetry.cjs +56 -0
- package/dist/services/telemetry.cjs.map +1 -0
- package/dist/services/telemetry.d.ts +87 -0
- package/dist/services/telemetry.d.ts.map +1 -0
- package/dist/services/telemetry.js +56 -0
- package/dist/services/telemetry.js.map +1 -0
- package/dist/services/validation.cjs +25 -24
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +26 -25
- package/dist/services/validation.js.map +1 -1
- package/dist/types/index.d.ts +26 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types.d.cts +26 -0
- package/dist/types.d.ts +26 -0
- package/docs/briefs/BRIEF-citations-in-table-cells.md +365 -0
- package/package.json +3 -3
- package/src/components/FormRenderer.tsx +14 -0
- package/src/components/GenerativeUIErrorBoundary.tsx +17 -1
- package/src/components/StreamingUIRenderer.tsx +55 -3
- package/src/components/TableRenderer.citation.test.tsx +157 -0
- package/src/components/UIResourceRenderer.tsx +212 -15
- package/src/context/MCPUITelemetryContext.test.tsx +119 -0
- package/src/context/MCPUITelemetryContext.tsx +71 -0
- package/src/index.ts +20 -0
- package/src/services/telemetry.test.ts +134 -0
- package/src/services/telemetry.ts +149 -0
- package/src/services/validation.ts +43 -41
- package/src/types/index.ts +30 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -57,7 +57,42 @@ export interface UIResourceRendererProps {
|
|
|
57
57
|
* v4.3.8
|
|
58
58
|
*/
|
|
59
59
|
export declare function highlightQuery(html: string, query: string): string;
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Citation context — opt-in input to `renderCellValue` (v5.7.0).
|
|
62
|
+
*
|
|
63
|
+
* When passed, `[N]`, `Citation [N]`, `[CITATION N]` and `[📄 CITATION N]`
|
|
64
|
+
* markers in the cell text are normalized then replaced with chip HTML.
|
|
65
|
+
* Chips carry `data-citation-page`, `data-citation-doc`, and
|
|
66
|
+
* `data-citation-verified` attributes (already in the DOMPurify whitelist
|
|
67
|
+
* since v5.6.0) so a host's `target.closest('[data-citation-page]')`
|
|
68
|
+
* delegated click handler routes the click to the source-doc panel.
|
|
69
|
+
*
|
|
70
|
+
* See `mcp-ui-solid/docs/briefs/BRIEF-citations-in-table-cells.md`.
|
|
71
|
+
*/
|
|
72
|
+
export interface CitationCtx {
|
|
73
|
+
/**
|
|
74
|
+
* `Record<id, mapping>` keyed by the citation marker number (string-keyed
|
|
75
|
+
* because JSON serialization always produces strings; the runtime call
|
|
76
|
+
* sites accept either number or string ids and normalize internally).
|
|
77
|
+
*/
|
|
78
|
+
map: Record<string | number, {
|
|
79
|
+
page: number | string;
|
|
80
|
+
file?: string;
|
|
81
|
+
file_id?: number | string;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Optional override returning sanitized chip HTML for one marker. Wins
|
|
85
|
+
* over the default `defaultCitationChip` shape. Function inputs are
|
|
86
|
+
* intentionally `any`-loose so consumers can swap shapes (e.g. web
|
|
87
|
+
* citations vs doc citations) without subtyping the entry shape here.
|
|
88
|
+
*/
|
|
89
|
+
render?: (id: number, mapping: {
|
|
90
|
+
page: number | string;
|
|
91
|
+
file?: string;
|
|
92
|
+
file_id?: number | string;
|
|
93
|
+
} | undefined) => string;
|
|
94
|
+
}
|
|
95
|
+
export declare function renderCellValue(value: any, citationCtx?: CitationCtx): string;
|
|
61
96
|
/**
|
|
62
97
|
* Main UIResourceRenderer component
|
|
63
98
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UIResourceRenderer.d.ts","sourceRoot":"","sources":["../../src/components/UIResourceRenderer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"UIResourceRenderer.d.ts","sourceRoot":"","sources":["../../src/components/UIResourceRenderer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,SAAS,EAAyE,MAAM,UAAU,CAAA;AAE3G,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAA0B,MAAM,UAAU,CAAA;AAM5F;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,aAAa,GAAG,QAAQ,CAAA;AA8DpE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,OAAO,EAAE,WAAW,GAAG,QAAQ,CAAA;IAE/B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAExC;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,mBAAmB,CAAA;CAChC;AAiJD;;GAEG;AACH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAYlE;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC,CAAA;IACjG;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CACP,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,SAAS,KACrF,MAAM,CAAA;CACZ;AA8DD,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,MAAM,CAkG7E;AA8nCD;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,CAAC,uBAAuB,CA6GjE,CAAA"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { delegateEvents, createComponent, getNextElement, template, getNextMarker, insert, effect, style, className, setProperty, setAttribute, runHydrationEvents, memo, isServer, use, addEventListener, classList, setStyleProperty } from "solid-js/web";
|
|
2
2
|
import purify from "../node_modules/.pnpm/dompurify@3.4.1/node_modules/dompurify/dist/purify.es.js";
|
|
3
|
-
import { createMemo, For, Show, onMount, createSignal, createEffect } from "solid-js";
|
|
3
|
+
import { createMemo, For, Show, onMount, onCleanup, createSignal, createEffect } from "solid-js";
|
|
4
4
|
import { validateComponent, getIframeSandbox, DEFAULT_RESOURCE_LIMITS } from "../services/validation.js";
|
|
5
5
|
import { GenerativeUIErrorBoundary } from "./GenerativeUIErrorBoundary.js";
|
|
6
|
-
import { markRenderStart, markRenderEnd } from "../utils/perf.js";
|
|
6
|
+
import { markRenderStart, markRenderEnd, PERF_PREFIX } from "../utils/perf.js";
|
|
7
|
+
import { useTelemetry } from "../context/MCPUITelemetryContext.js";
|
|
7
8
|
import { GridRenderer } from "./GridRenderer.js";
|
|
8
9
|
import { FooterRenderer } from "./FooterRenderer.js";
|
|
9
10
|
import { CarouselRenderer } from "./CarouselRenderer.js";
|
|
@@ -218,7 +219,27 @@ function highlightQuery(html, query) {
|
|
|
218
219
|
return text.replace(regex, '<mark class="bg-yellow-200 dark:bg-[#222F49] text-inherit rounded px-0.5">$1</mark>');
|
|
219
220
|
});
|
|
220
221
|
}
|
|
221
|
-
function
|
|
222
|
+
function defaultCitationChip(pageNum, fileName, verified = true) {
|
|
223
|
+
const safeDocName = encodeURIComponent(fileName || "");
|
|
224
|
+
const label = fileName ? `${fileName} - ${pageNum}` : `${pageNum}`;
|
|
225
|
+
if (!verified) {
|
|
226
|
+
return `<span class="citation-ref inline-flex items-center gap-0.5 align-middle opacity-60"><span class="text-gray-500 line-through">[${label}]</span></span>`;
|
|
227
|
+
}
|
|
228
|
+
return ['<span class="citation-ref inline-flex items-center gap-0.5 align-middle">', `<span class="text-gray-500">[${label}]</span>`, '<button class="inline-flex items-center ml-0.5 px-1 py-0.5 text-xs bg-gray-800 hover:bg-gray-700 border border-gray-600 hover:border-teal-500 rounded cursor-pointer transition-colors align-middle"', ` data-citation-page="${pageNum}"`, ` data-citation-doc="${safeDocName}"`, ' data-citation-verified="true"', ` title="View source - ${label}">`, '<svg class="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>', "</button>", "</span>"].join("");
|
|
229
|
+
}
|
|
230
|
+
function transformCellCitations(text, ctx) {
|
|
231
|
+
let out = text.replace(new RegExp("(?<![p.])\\[(\\d{1,2})\\](?!\\()", "g"), "[📄 CITATION $1]");
|
|
232
|
+
out = out.replace(/\bCitations?\s*\[(\d+)\]/gi, "[📄 CITATION $1]");
|
|
233
|
+
out = out.replace(/\[CITATION\s+(\d+)\]/gi, "[📄 CITATION $1]");
|
|
234
|
+
return out.replace(/[【[]\s*📄\s*CITATION\s*(\d+)\s*[】\]]/gi, (_m, idStr) => {
|
|
235
|
+
const id = parseInt(idStr, 10);
|
|
236
|
+
const mapping = ctx.map[id] ?? ctx.map[String(id)];
|
|
237
|
+
if (ctx.render) return ctx.render(id, mapping);
|
|
238
|
+
if (mapping) return defaultCitationChip(mapping.page, mapping.file ?? "", true);
|
|
239
|
+
return Object.keys(ctx.map).length > 0 ? "" : `[réf. ${id}]`;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function renderCellValue(value, citationCtx) {
|
|
222
243
|
if (value === null || value === void 0) {
|
|
223
244
|
return "-";
|
|
224
245
|
}
|
|
@@ -254,20 +275,25 @@ function renderCellValue(value) {
|
|
|
254
275
|
ADD_ATTR: ["target", "rel"]
|
|
255
276
|
});
|
|
256
277
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return purify.sanitize(strValue, {
|
|
260
|
-
ALLOWED_TAGS: ["a", "strong", "em", "b", "i", "code", "span", "br", "button", "svg", "path"],
|
|
261
|
-
ALLOWED_ATTR: ["href", "target", "rel", "class", "data-citation-page", "data-citation-source", "data-citation-doc", "data-citation-verified", "title", "fill", "stroke", "viewBox", "stroke-linecap", "stroke-linejoin", "stroke-width", "d"],
|
|
262
|
-
ADD_ATTR: ["target", "rel"]
|
|
263
|
-
});
|
|
278
|
+
if (citationCtx) {
|
|
279
|
+
strValue = transformCellCitations(strValue, citationCtx);
|
|
264
280
|
}
|
|
265
|
-
const hasMarkdown = /[*_
|
|
281
|
+
const hasMarkdown = /[*_`#]/.test(strValue);
|
|
266
282
|
if (hasMarkdown) {
|
|
267
283
|
const parsed = k.parse(strValue, {
|
|
268
284
|
async: false
|
|
269
285
|
});
|
|
270
286
|
return purify.sanitize(parsed, {
|
|
287
|
+
ALLOWED_TAGS: ["a", "strong", "em", "b", "i", "code", "span", "br", "button", "svg", "path", "p", "ul", "ol", "li", "pre", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6"],
|
|
288
|
+
ALLOWED_ATTR: ["href", "target", "rel", "class", "data-citation-page", "data-citation-source", "data-citation-doc", "data-citation-verified", "title", "fill", "stroke", "viewBox", "stroke-linecap", "stroke-linejoin", "stroke-width", "d"],
|
|
289
|
+
ADD_ATTR: ["target", "rel"]
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
const hasHtml = /<[a-z][\s\S]*>/i.test(strValue);
|
|
293
|
+
if (hasHtml) {
|
|
294
|
+
return purify.sanitize(strValue, {
|
|
295
|
+
ALLOWED_TAGS: ["a", "strong", "em", "b", "i", "code", "span", "br", "button", "svg", "path"],
|
|
296
|
+
ALLOWED_ATTR: ["href", "target", "rel", "class", "data-citation-page", "data-citation-source", "data-citation-doc", "data-citation-verified", "title", "fill", "stroke", "viewBox", "stroke-linecap", "stroke-linejoin", "stroke-width", "d"],
|
|
271
297
|
ADD_ATTR: ["target", "rel"]
|
|
272
298
|
});
|
|
273
299
|
}
|
|
@@ -276,6 +302,10 @@ function renderCellValue(value) {
|
|
|
276
302
|
function TableRenderer(props) {
|
|
277
303
|
const tableParams = props.component.params;
|
|
278
304
|
let scrollContainerRef;
|
|
305
|
+
const citationCtx = tableParams.citationMap ? {
|
|
306
|
+
map: tableParams.citationMap,
|
|
307
|
+
render: tableParams.citationRender
|
|
308
|
+
} : void 0;
|
|
279
309
|
const allRows = () => tableParams.rows || [];
|
|
280
310
|
const columns = () => tableParams.columns || [];
|
|
281
311
|
const [sortKey, setSortKey] = createSignal(null);
|
|
@@ -516,7 +546,7 @@ ${dataRows}`;
|
|
|
516
546
|
},
|
|
517
547
|
children: (column) => (() => {
|
|
518
548
|
var _el$23 = getNextElement(_tmpl$10), _el$24 = _el$23.firstChild;
|
|
519
|
-
effect(() => setProperty(_el$24, "innerHTML", highlightQuery(renderCellValue(row[column.key]), debouncedQuery())));
|
|
549
|
+
effect(() => setProperty(_el$24, "innerHTML", highlightQuery(renderCellValue(row[column.key], citationCtx), debouncedQuery())));
|
|
520
550
|
return _el$23;
|
|
521
551
|
})()
|
|
522
552
|
}));
|
|
@@ -549,7 +579,7 @@ ${dataRows}`;
|
|
|
549
579
|
},
|
|
550
580
|
children: (column) => (() => {
|
|
551
581
|
var _el$27 = getNextElement(_tmpl$10), _el$28 = _el$27.firstChild;
|
|
552
|
-
effect(() => setProperty(_el$28, "innerHTML", highlightQuery(renderCellValue(row[column.key]), debouncedQuery())));
|
|
582
|
+
effect(() => setProperty(_el$28, "innerHTML", highlightQuery(renderCellValue(row[column.key], citationCtx), debouncedQuery())));
|
|
553
583
|
return _el$27;
|
|
554
584
|
})()
|
|
555
585
|
}));
|
|
@@ -1053,9 +1083,44 @@ function LinkRenderer(props) {
|
|
|
1053
1083
|
})();
|
|
1054
1084
|
}
|
|
1055
1085
|
function ComponentRenderer(props) {
|
|
1056
|
-
var _a, _b, _c;
|
|
1086
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1057
1087
|
markRenderStart(props.component.id);
|
|
1058
|
-
|
|
1088
|
+
const telemetry = useTelemetry();
|
|
1089
|
+
onMount(() => {
|
|
1090
|
+
markRenderEnd(props.component.id);
|
|
1091
|
+
if (telemetry) {
|
|
1092
|
+
const ts = Date.now();
|
|
1093
|
+
telemetry.dispatch({
|
|
1094
|
+
type: "component:mounted",
|
|
1095
|
+
id: props.component.id,
|
|
1096
|
+
componentType: props.component.type,
|
|
1097
|
+
ts
|
|
1098
|
+
});
|
|
1099
|
+
if (typeof performance !== "undefined" && typeof performance.getEntriesByName === "function") {
|
|
1100
|
+
const entries = performance.getEntriesByName(`${PERF_PREFIX}${props.component.id}:render`, "measure");
|
|
1101
|
+
const last = entries[entries.length - 1];
|
|
1102
|
+
if (last) {
|
|
1103
|
+
telemetry.dispatch({
|
|
1104
|
+
type: "component:rendered",
|
|
1105
|
+
id: props.component.id,
|
|
1106
|
+
componentType: props.component.type,
|
|
1107
|
+
durationMs: last.duration,
|
|
1108
|
+
ts
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
onCleanup(() => {
|
|
1115
|
+
if (telemetry) {
|
|
1116
|
+
telemetry.dispatch({
|
|
1117
|
+
type: "component:unmounted",
|
|
1118
|
+
id: props.component.id,
|
|
1119
|
+
componentType: props.component.type,
|
|
1120
|
+
ts: Date.now()
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1059
1124
|
const validation = validateComponent(props.component);
|
|
1060
1125
|
if (!validation.valid) {
|
|
1061
1126
|
(_a = props.onError) == null ? void 0 : _a.call(props, {
|
|
@@ -1064,8 +1129,18 @@ function ComponentRenderer(props) {
|
|
|
1064
1129
|
componentId: props.component.id,
|
|
1065
1130
|
details: validation.errors
|
|
1066
1131
|
});
|
|
1132
|
+
if (telemetry) {
|
|
1133
|
+
telemetry.dispatch({
|
|
1134
|
+
type: "validation:failed",
|
|
1135
|
+
id: props.component.id,
|
|
1136
|
+
componentType: props.component.type,
|
|
1137
|
+
errorCount: ((_b = validation.errors) == null ? void 0 : _b.length) ?? 0,
|
|
1138
|
+
firstErrorCode: ((_d = (_c = validation.errors) == null ? void 0 : _c[0]) == null ? void 0 : _d.code) ?? null,
|
|
1139
|
+
ts: Date.now()
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1067
1142
|
const mode = props.errorMode ?? "block";
|
|
1068
|
-
const firstError = ((
|
|
1143
|
+
const firstError = ((_f = (_e = validation.errors) == null ? void 0 : _e[0]) == null ? void 0 : _f.message) || "Unknown validation error";
|
|
1069
1144
|
if (mode === "silent") {
|
|
1070
1145
|
return null;
|
|
1071
1146
|
}
|
|
@@ -1317,7 +1392,20 @@ function ActionRenderer(props) {
|
|
|
1317
1392
|
execute,
|
|
1318
1393
|
isExecuting
|
|
1319
1394
|
} = useAction();
|
|
1395
|
+
const telemetry = useTelemetry();
|
|
1396
|
+
function dispatchTelemetry() {
|
|
1397
|
+
if (!telemetry) return;
|
|
1398
|
+
const actionName = params.toolName ?? params.action ?? "unknown";
|
|
1399
|
+
telemetry.dispatch({
|
|
1400
|
+
type: "action:dispatched",
|
|
1401
|
+
id: props.component.id,
|
|
1402
|
+
componentType: "action",
|
|
1403
|
+
actionName,
|
|
1404
|
+
ts: Date.now()
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1320
1407
|
const handleClick = async (e) => {
|
|
1408
|
+
dispatchTelemetry();
|
|
1321
1409
|
if (params.action === "tool-call" && params.toolName) {
|
|
1322
1410
|
e.preventDefault();
|
|
1323
1411
|
await execute(params.toolName, params.params || {});
|