@seed-ship/mcp-ui-solid 6.19.1 → 6.21.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 +663 -0
- package/README.md +911 -4
- package/dist/adapters/connector.cjs +15 -4
- package/dist/adapters/connector.cjs.map +1 -1
- package/dist/adapters/connector.d.ts +38 -0
- package/dist/adapters/connector.d.ts.map +1 -1
- package/dist/adapters/connector.js +15 -4
- package/dist/adapters/connector.js.map +1 -1
- package/dist/adapters/index.d.ts +4 -3
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/macro-run.cjs +25 -13
- package/dist/adapters/macro-run.cjs.map +1 -1
- package/dist/adapters/macro-run.d.ts +37 -2
- package/dist/adapters/macro-run.d.ts.map +1 -1
- package/dist/adapters/macro-run.js +25 -13
- package/dist/adapters/macro-run.js.map +1 -1
- package/dist/adapters.cjs +2 -0
- package/dist/adapters.cjs.map +1 -1
- package/dist/adapters.js +4 -2
- package/dist/components/ActionGroupRenderer.cjs +3 -1
- package/dist/components/ActionGroupRenderer.cjs.map +1 -1
- package/dist/components/ActionGroupRenderer.d.ts.map +1 -1
- package/dist/components/ActionGroupRenderer.js +3 -1
- package/dist/components/ActionGroupRenderer.js.map +1 -1
- package/dist/components/AgentCard.cjs +11 -8
- package/dist/components/AgentCard.cjs.map +1 -1
- package/dist/components/AgentCard.d.ts.map +1 -1
- package/dist/components/AgentCard.js +11 -8
- package/dist/components/AgentCard.js.map +1 -1
- package/dist/components/AgentHandoff.cjs +6 -1
- package/dist/components/AgentHandoff.cjs.map +1 -1
- package/dist/components/AgentHandoff.d.ts.map +1 -1
- package/dist/components/AgentHandoff.js +6 -1
- package/dist/components/AgentHandoff.js.map +1 -1
- package/dist/components/ArtifactRenderer.cjs +25 -6
- package/dist/components/ArtifactRenderer.cjs.map +1 -1
- package/dist/components/ArtifactRenderer.d.ts.map +1 -1
- package/dist/components/ArtifactRenderer.js +25 -6
- package/dist/components/ArtifactRenderer.js.map +1 -1
- package/dist/components/AutocompleteDropdown.cjs +30 -24
- package/dist/components/AutocompleteDropdown.cjs.map +1 -1
- package/dist/components/AutocompleteDropdown.d.ts.map +1 -1
- package/dist/components/AutocompleteDropdown.js +30 -24
- package/dist/components/AutocompleteDropdown.js.map +1 -1
- package/dist/components/AutocompleteFormField.cjs +3 -1
- package/dist/components/AutocompleteFormField.cjs.map +1 -1
- package/dist/components/AutocompleteFormField.d.ts.map +1 -1
- package/dist/components/AutocompleteFormField.js +4 -2
- package/dist/components/AutocompleteFormField.js.map +1 -1
- package/dist/components/BriefingDiff.cjs +45 -39
- package/dist/components/BriefingDiff.cjs.map +1 -1
- package/dist/components/BriefingDiff.d.ts.map +1 -1
- package/dist/components/BriefingDiff.js +45 -39
- package/dist/components/BriefingDiff.js.map +1 -1
- package/dist/components/CarouselRenderer.cjs +8 -2
- package/dist/components/CarouselRenderer.cjs.map +1 -1
- package/dist/components/CarouselRenderer.d.ts.map +1 -1
- package/dist/components/CarouselRenderer.js +8 -2
- package/dist/components/CarouselRenderer.js.map +1 -1
- package/dist/components/ChartJSRenderer.cjs +60 -36
- package/dist/components/ChartJSRenderer.cjs.map +1 -1
- package/dist/components/ChartJSRenderer.d.ts.map +1 -1
- package/dist/components/ChartJSRenderer.js +61 -37
- package/dist/components/ChartJSRenderer.js.map +1 -1
- package/dist/components/ChatPrompt.cjs +15 -9
- package/dist/components/ChatPrompt.cjs.map +1 -1
- package/dist/components/ChatPrompt.d.ts.map +1 -1
- package/dist/components/ChatPrompt.js +15 -9
- package/dist/components/ChatPrompt.js.map +1 -1
- package/dist/components/CodeBlockRenderer.cjs +34 -16
- package/dist/components/CodeBlockRenderer.cjs.map +1 -1
- package/dist/components/CodeBlockRenderer.d.ts.map +1 -1
- package/dist/components/CodeBlockRenderer.js +34 -16
- package/dist/components/CodeBlockRenderer.js.map +1 -1
- package/dist/components/DataPreviewSection.cjs +91 -44
- package/dist/components/DataPreviewSection.cjs.map +1 -1
- package/dist/components/DataPreviewSection.d.ts.map +1 -1
- package/dist/components/DataPreviewSection.js +92 -45
- package/dist/components/DataPreviewSection.js.map +1 -1
- package/dist/components/DegradedFallback.cjs +20 -16
- package/dist/components/DegradedFallback.cjs.map +1 -1
- package/dist/components/DegradedFallback.d.ts.map +1 -1
- package/dist/components/DegradedFallback.js +20 -16
- package/dist/components/DegradedFallback.js.map +1 -1
- package/dist/components/FooterRenderer.cjs +21 -17
- package/dist/components/FooterRenderer.cjs.map +1 -1
- package/dist/components/FooterRenderer.d.ts.map +1 -1
- package/dist/components/FooterRenderer.js +21 -17
- package/dist/components/FooterRenderer.js.map +1 -1
- package/dist/components/FormFieldRenderer.cjs +116 -93
- package/dist/components/FormFieldRenderer.cjs.map +1 -1
- package/dist/components/FormFieldRenderer.d.ts.map +1 -1
- package/dist/components/FormFieldRenderer.js +116 -93
- package/dist/components/FormFieldRenderer.js.map +1 -1
- package/dist/components/FormRenderer.cjs +55 -31
- package/dist/components/FormRenderer.cjs.map +1 -1
- package/dist/components/FormRenderer.d.ts.map +1 -1
- package/dist/components/FormRenderer.js +55 -31
- package/dist/components/FormRenderer.js.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.cjs +20 -15
- package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.d.ts.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.js +20 -15
- package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
- package/dist/components/GraphRenderer.cjs +56 -33
- package/dist/components/GraphRenderer.cjs.map +1 -1
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js +56 -33
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/IframeFallbackLink.cjs +37 -0
- package/dist/components/IframeFallbackLink.cjs.map +1 -0
- package/dist/components/IframeFallbackLink.d.ts +23 -0
- package/dist/components/IframeFallbackLink.d.ts.map +1 -0
- package/dist/components/IframeFallbackLink.js +37 -0
- package/dist/components/IframeFallbackLink.js.map +1 -0
- package/dist/components/ImageGalleryRenderer.cjs +13 -4
- package/dist/components/ImageGalleryRenderer.cjs.map +1 -1
- package/dist/components/ImageGalleryRenderer.d.ts.map +1 -1
- package/dist/components/ImageGalleryRenderer.js +13 -4
- package/dist/components/ImageGalleryRenderer.js.map +1 -1
- package/dist/components/LightboxOverlay.cjs +16 -8
- package/dist/components/LightboxOverlay.cjs.map +1 -1
- package/dist/components/LightboxOverlay.d.ts.map +1 -1
- package/dist/components/LightboxOverlay.js +17 -9
- package/dist/components/LightboxOverlay.js.map +1 -1
- package/dist/components/MapRenderer.cjs +59 -29
- package/dist/components/MapRenderer.cjs.map +1 -1
- package/dist/components/MapRenderer.d.ts +10 -2
- package/dist/components/MapRenderer.d.ts.map +1 -1
- package/dist/components/MapRenderer.js +60 -30
- package/dist/components/MapRenderer.js.map +1 -1
- package/dist/components/ModalRenderer.cjs +4 -1
- package/dist/components/ModalRenderer.cjs.map +1 -1
- package/dist/components/ModalRenderer.d.ts.map +1 -1
- package/dist/components/ModalRenderer.js +5 -2
- package/dist/components/ModalRenderer.js.map +1 -1
- package/dist/components/ScratchpadPanel.cjs +578 -535
- package/dist/components/ScratchpadPanel.cjs.map +1 -1
- package/dist/components/ScratchpadPanel.d.ts.map +1 -1
- package/dist/components/ScratchpadPanel.js +578 -535
- package/dist/components/ScratchpadPanel.js.map +1 -1
- package/dist/components/StreamingUIRenderer.cjs +92 -28
- package/dist/components/StreamingUIRenderer.cjs.map +1 -1
- package/dist/components/StreamingUIRenderer.d.ts.map +1 -1
- package/dist/components/StreamingUIRenderer.js +92 -28
- package/dist/components/StreamingUIRenderer.js.map +1 -1
- package/dist/components/UIResourceRenderer.cjs +481 -365
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts +21 -0
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +484 -368
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/components/VerifiedText.cjs +39 -20
- package/dist/components/VerifiedText.cjs.map +1 -1
- package/dist/components/VerifiedText.d.ts +2 -1
- package/dist/components/VerifiedText.d.ts.map +1 -1
- package/dist/components/VerifiedText.js +39 -20
- package/dist/components/VerifiedText.js.map +1 -1
- package/dist/components/VideoRenderer.cjs +51 -20
- package/dist/components/VideoRenderer.cjs.map +1 -1
- package/dist/components/VideoRenderer.d.ts.map +1 -1
- package/dist/components/VideoRenderer.js +51 -20
- package/dist/components/VideoRenderer.js.map +1 -1
- package/dist/components/chart-data-table.cjs +38 -17
- package/dist/components/chart-data-table.cjs.map +1 -1
- package/dist/components/chart-data-table.d.ts +30 -1
- package/dist/components/chart-data-table.d.ts.map +1 -1
- package/dist/components/chart-data-table.js +38 -17
- package/dist/components/chart-data-table.js.map +1 -1
- package/dist/context/MCPActionContext.cjs +3 -1
- package/dist/context/MCPActionContext.cjs.map +1 -1
- package/dist/context/MCPActionContext.d.ts.map +1 -1
- package/dist/context/MCPActionContext.js +3 -1
- package/dist/context/MCPActionContext.js.map +1 -1
- package/dist/context/MCPUIConfigContext.cjs +29 -0
- package/dist/context/MCPUIConfigContext.cjs.map +1 -0
- package/dist/context/MCPUIConfigContext.d.ts +124 -0
- package/dist/context/MCPUIConfigContext.d.ts.map +1 -0
- package/dist/context/MCPUIConfigContext.js +29 -0
- package/dist/context/MCPUIConfigContext.js.map +1 -0
- package/dist/context/MCPUIStringsContext.cjs +287 -2
- package/dist/context/MCPUIStringsContext.cjs.map +1 -1
- package/dist/context/MCPUIStringsContext.d.ts +607 -3
- package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
- package/dist/context/MCPUIStringsContext.js +287 -2
- package/dist/context/MCPUIStringsContext.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useStreamingUI.cjs +28 -10
- package/dist/hooks/useStreamingUI.cjs.map +1 -1
- package/dist/hooks/useStreamingUI.d.ts +37 -0
- package/dist/hooks/useStreamingUI.d.ts.map +1 -1
- package/dist/hooks/useStreamingUI.js +28 -10
- package/dist/hooks/useStreamingUI.js.map +1 -1
- package/dist/hooks.cjs +1 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.js +2 -1
- package/dist/index.cjs +29 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/validation.cjs +52 -26
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts +67 -2
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +52 -26
- package/dist/services/validation.js.map +1 -1
- package/dist/utils/degraded-projections.cjs +37 -12
- package/dist/utils/degraded-projections.cjs.map +1 -1
- package/dist/utils/degraded-projections.d.ts +39 -3
- package/dist/utils/degraded-projections.d.ts.map +1 -1
- package/dist/utils/degraded-projections.js +37 -12
- package/dist/utils/degraded-projections.js.map +1 -1
- package/dist/utils/format-string.cjs +10 -0
- package/dist/utils/format-string.cjs.map +1 -0
- package/dist/utils/format-string.d.ts +26 -0
- package/dist/utils/format-string.d.ts.map +1 -0
- package/dist/utils/format-string.js +10 -0
- package/dist/utils/format-string.js.map +1 -0
- package/dist/utils/iframe-coep.cjs +30 -0
- package/dist/utils/iframe-coep.cjs.map +1 -0
- package/dist/utils/iframe-coep.d.ts +46 -0
- package/dist/utils/iframe-coep.d.ts.map +1 -0
- package/dist/utils/iframe-coep.js +30 -0
- package/dist/utils/iframe-coep.js.map +1 -0
- package/dist/validation.cjs +5 -0
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.d.cts +2 -1
- package/dist/validation.d.ts +2 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +6 -1
- package/package.json +1 -1
- package/src/adapters/connector.ts +58 -4
- package/src/adapters/index.ts +12 -2
- package/src/adapters/macro-run.ts +71 -11
- package/src/components/ActionGroupRenderer.tsx +3 -1
- package/src/components/AgentCard.tsx +27 -9
- package/src/components/AgentHandoff.tsx +4 -1
- package/src/components/ArtifactRenderer.tsx +17 -5
- package/src/components/AutocompleteDropdown.tsx +8 -6
- package/src/components/AutocompleteFormField.tsx +5 -1
- package/src/components/BriefingDiff.tsx +11 -3
- package/src/components/CarouselRenderer.tsx +4 -2
- package/src/components/ChartJSRenderer.tsx +18 -9
- package/src/components/ChatPrompt.tsx +13 -8
- package/src/components/CodeBlockRenderer.tsx +12 -10
- package/src/components/DataPreviewSection.tsx +92 -26
- package/src/components/DegradedFallback.tsx +4 -2
- package/src/components/FooterRenderer.tsx +3 -1
- package/src/components/FormFieldRenderer.tsx +34 -19
- package/src/components/FormRenderer.tsx +38 -10
- package/src/components/GenerativeUIErrorBoundary.tsx +8 -3
- package/src/components/GraphRenderer.tsx +31 -16
- package/src/components/IframeFallbackLink.tsx +57 -0
- package/src/components/ImageGalleryRenderer.tsx +8 -4
- package/src/components/LightboxOverlay.tsx +6 -4
- package/src/components/MapRenderer.tsx +57 -20
- package/src/components/ModalRenderer.tsx +3 -1
- package/src/components/ScratchpadPanel.tsx +95 -44
- package/src/components/StreamingUIRenderer.tsx +41 -11
- package/src/components/UIResourceRenderer.tsx +176 -67
- package/src/components/VerifiedText.tsx +19 -9
- package/src/components/VideoRenderer.tsx +26 -4
- package/src/components/chart-data-table.ts +68 -14
- package/src/context/MCPActionContext.tsx +4 -1
- package/src/context/MCPUIConfigContext.tsx +151 -0
- package/src/context/MCPUIStringsContext.tsx +1014 -1
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useStreamingUI.ts +64 -10
- package/src/index.ts +38 -0
- package/src/services/index.ts +1 -0
- package/src/services/validation.ts +131 -32
- package/src/solid-jsx-attrs.d.ts +24 -0
- package/src/utils/degraded-projections.ts +75 -12
- package/src/utils/format-string.ts +33 -0
- package/src/utils/iframe-coep.ts +74 -0
- package/src/validation.ts +9 -0
|
@@ -10,7 +10,7 @@ import { escapeHtml } from '../utils/escape-html'
|
|
|
10
10
|
import { safeUrl } from '../utils/safe-url'
|
|
11
11
|
import { SafeHtml } from './SafeHtml'
|
|
12
12
|
import type { UIComponent, UILayout, RendererError, TableVirtualizeOptions } from '../types'
|
|
13
|
-
import { validateComponent, DEFAULT_RESOURCE_LIMITS, getIframeSandbox } from '../services/validation'
|
|
13
|
+
import { validateComponent, DEFAULT_RESOURCE_LIMITS, getIframeSandbox, isTrustedIframeDomain } from '../services/validation'
|
|
14
14
|
import { GenerativeUIErrorBoundary } from './GenerativeUIErrorBoundary'
|
|
15
15
|
import { markRenderStart, markRenderEnd, PERF_PREFIX } from '../utils/perf'
|
|
16
16
|
import { isDebugEnabled } from '../utils/logger'
|
|
@@ -22,7 +22,14 @@ import {
|
|
|
22
22
|
type DuplicateMountInfo,
|
|
23
23
|
} from '../utils/duplicate-mount-registry'
|
|
24
24
|
import { useTelemetry } from '../context/MCPUITelemetryContext'
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
DEFAULT_MCPUI_STRINGS,
|
|
27
|
+
formatMCPUIString,
|
|
28
|
+
useMCPUIStrings,
|
|
29
|
+
} from '../context/MCPUIStringsContext'
|
|
30
|
+
import { useMCPUIConfig } from '../context/MCPUIConfigContext'
|
|
31
|
+
import { shouldSetCredentialless } from '../utils/iframe-coep'
|
|
32
|
+
import { IframeFallbackLink } from './IframeFallbackLink'
|
|
26
33
|
|
|
27
34
|
/**
|
|
28
35
|
* How `<UIResourceRenderer>` reacts when `validateComponent()` rejects a
|
|
@@ -65,6 +72,7 @@ import { marked } from 'marked'
|
|
|
65
72
|
* Copy button component with visual feedback
|
|
66
73
|
*/
|
|
67
74
|
function CopyButton(props: { getText: () => string; title?: string; position?: 'top-right' | 'bottom-right' }) {
|
|
75
|
+
const strings = useMCPUIStrings()
|
|
68
76
|
const [copied, setCopied] = createSignal(false)
|
|
69
77
|
|
|
70
78
|
const handleCopy = async () => {
|
|
@@ -87,8 +95,8 @@ function CopyButton(props: { getText: () => string; title?: string; position?: '
|
|
|
87
95
|
<button
|
|
88
96
|
onClick={handleCopy}
|
|
89
97
|
class={`${positionClasses()} opacity-60 hover:opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm z-10`}
|
|
90
|
-
title={props.title ||
|
|
91
|
-
aria-label={props.title ||
|
|
98
|
+
title={props.title || strings.copy}
|
|
99
|
+
aria-label={props.title || strings.copy}
|
|
92
100
|
data-mcp-ui-action="copy"
|
|
93
101
|
type="button"
|
|
94
102
|
>
|
|
@@ -210,6 +218,7 @@ function ChartRenderer(props: {
|
|
|
210
218
|
/** Host opt-in for the external quickchart.io fallback (audit P1.7). */
|
|
211
219
|
allowQuickchartFallback?: boolean
|
|
212
220
|
}) {
|
|
221
|
+
const strings = useMCPUIStrings()
|
|
213
222
|
const [useNative, setUseNative] = createSignal(false)
|
|
214
223
|
const [iframeUrl, setIframeUrl] = createSignal<string>()
|
|
215
224
|
const [isLoading, setIsLoading] = createSignal(true)
|
|
@@ -223,6 +232,11 @@ function ChartRenderer(props: {
|
|
|
223
232
|
const params = () => props.component.params as any
|
|
224
233
|
const rendererPref = () => params()?.renderer || 'auto'
|
|
225
234
|
const allowQuickchart = () => props.allowQuickchartFallback === true
|
|
235
|
+
/** `alt` / `aria-label` of the quickchart image — localized chrome. */
|
|
236
|
+
const chartImageAlt = () =>
|
|
237
|
+
params()?.title
|
|
238
|
+
? formatMCPUIString(strings.chartWithTitleAlt, { title: String(params().title) })
|
|
239
|
+
: strings.chartVisualizationAlt
|
|
226
240
|
|
|
227
241
|
// Emit a clear, observable signal whenever we decline the external fallback.
|
|
228
242
|
const signalBlockedFallback = (reason: string) => {
|
|
@@ -242,7 +256,7 @@ function ChartRenderer(props: {
|
|
|
242
256
|
if (!params()?.data?.datasets) {
|
|
243
257
|
return (
|
|
244
258
|
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
|
245
|
-
<p class="text-red-500 dark:text-red-400 text-sm">
|
|
259
|
+
<p class="text-red-500 dark:text-red-400 text-sm">{strings.chartInvalidData}</p>
|
|
246
260
|
</div>
|
|
247
261
|
)
|
|
248
262
|
}
|
|
@@ -270,7 +284,7 @@ function ChartRenderer(props: {
|
|
|
270
284
|
setUseNative(true)
|
|
271
285
|
setIsLoading(false)
|
|
272
286
|
} else {
|
|
273
|
-
setError(
|
|
287
|
+
setError(strings.chartJsUnavailable)
|
|
274
288
|
setIsLoading(false)
|
|
275
289
|
}
|
|
276
290
|
} else {
|
|
@@ -327,9 +341,11 @@ function ChartRenderer(props: {
|
|
|
327
341
|
<Show when={degraded()}>
|
|
328
342
|
<div class="p-3">
|
|
329
343
|
<DegradedFallback
|
|
330
|
-
message=
|
|
331
|
-
caption=
|
|
332
|
-
{...chartToDegradedTable(params() ?? {}
|
|
344
|
+
message={strings.chartIframeUnavailable}
|
|
345
|
+
caption={strings.chartQuickchartCaption}
|
|
346
|
+
{...chartToDegradedTable(params() ?? {}, {
|
|
347
|
+
series: strings.degradedSeries,
|
|
348
|
+
})}
|
|
333
349
|
/>
|
|
334
350
|
</div>
|
|
335
351
|
</Show>
|
|
@@ -343,7 +359,7 @@ function ChartRenderer(props: {
|
|
|
343
359
|
<Show when={error()}>
|
|
344
360
|
<div class="absolute inset-0 flex items-center justify-center p-4">
|
|
345
361
|
<div class="text-center">
|
|
346
|
-
<p class="text-red-600 dark:text-red-400 text-sm font-medium">
|
|
362
|
+
<p class="text-red-600 dark:text-red-400 text-sm font-medium">{strings.chartError}</p>
|
|
347
363
|
<p class="text-gray-600 dark:text-gray-400 text-xs mt-1">{error()}</p>
|
|
348
364
|
</div>
|
|
349
365
|
</div>
|
|
@@ -356,13 +372,13 @@ function ChartRenderer(props: {
|
|
|
356
372
|
{params()?.title}
|
|
357
373
|
</h3>
|
|
358
374
|
</Show>
|
|
359
|
-
<div class="w-full h-full" role="img" aria-label={
|
|
375
|
+
<div class="w-full h-full" role="img" aria-label={chartImageAlt()}>
|
|
360
376
|
<img
|
|
361
377
|
src={iframeUrl()}
|
|
362
|
-
alt={
|
|
378
|
+
alt={chartImageAlt()}
|
|
363
379
|
class="w-full h-auto max-h-[300px] object-contain"
|
|
364
380
|
onError={() => {
|
|
365
|
-
setError(
|
|
381
|
+
setError(strings.chartLoadFailed)
|
|
366
382
|
props.onError?.({
|
|
367
383
|
type: 'render',
|
|
368
384
|
message: 'Chart rendering failed',
|
|
@@ -491,6 +507,27 @@ export interface CitationCtx {
|
|
|
491
507
|
id: number,
|
|
492
508
|
mapping: { page: number | string; file?: string; file_id?: number | string } | undefined
|
|
493
509
|
) => string
|
|
510
|
+
/**
|
|
511
|
+
* Template for the visible placeholder kept when `map` is EMPTY and a
|
|
512
|
+
* marker therefore resolves to nothing — `{id}` is the marker number.
|
|
513
|
+
*
|
|
514
|
+
* Optional: `transformCellCitations` falls back to
|
|
515
|
+
* `DEFAULT_MCPUI_STRINGS.citationUnresolved` (`'[ref. {id}]'`). The
|
|
516
|
+
* renderers populate it from `strings.citationUnresolved`, so a host with a
|
|
517
|
+
* `<MCPUIStringsProvider>` gets its own wording; a direct
|
|
518
|
+
* `renderCellValue(value, ctx)` call may pass it explicitly.
|
|
519
|
+
*
|
|
520
|
+
* @since v6.20.0 — the hardcoded placeholder used to be French.
|
|
521
|
+
*/
|
|
522
|
+
unresolvedLabel?: string
|
|
523
|
+
/**
|
|
524
|
+
* Tooltip template of the default citation chip button — `{label}` is the
|
|
525
|
+
* file name and page. Falls back to `DEFAULT_MCPUI_STRINGS.citationViewSource`;
|
|
526
|
+
* the renderers populate it from `strings.citationViewSource`.
|
|
527
|
+
*
|
|
528
|
+
* @since v6.20.0
|
|
529
|
+
*/
|
|
530
|
+
viewSourceLabel?: string
|
|
494
531
|
}
|
|
495
532
|
|
|
496
533
|
/**
|
|
@@ -502,7 +539,8 @@ export interface CitationCtx {
|
|
|
502
539
|
function defaultCitationChip(
|
|
503
540
|
pageNum: number | string,
|
|
504
541
|
fileName: string,
|
|
505
|
-
verified = true
|
|
542
|
+
verified = true,
|
|
543
|
+
viewSourceLabel: string = DEFAULT_MCPUI_STRINGS.citationViewSource
|
|
506
544
|
): string {
|
|
507
545
|
const safeDocName = encodeURIComponent(fileName || '')
|
|
508
546
|
const label = fileName ? `${fileName} - ${pageNum}` : `${pageNum}`
|
|
@@ -516,7 +554,7 @@ function defaultCitationChip(
|
|
|
516
554
|
` data-citation-page="${pageNum}"`,
|
|
517
555
|
` data-citation-doc="${safeDocName}"`,
|
|
518
556
|
' data-citation-verified="true"',
|
|
519
|
-
` title="
|
|
557
|
+
` title="${escapeHtml(formatMCPUIString(viewSourceLabel, { label }))}">`,
|
|
520
558
|
'<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>',
|
|
521
559
|
'</button>',
|
|
522
560
|
'</span>',
|
|
@@ -543,12 +581,16 @@ function transformCellCitations(text: string, ctx: CitationCtx): string {
|
|
|
543
581
|
const id = parseInt(idStr, 10)
|
|
544
582
|
const mapping = ctx.map[id] ?? ctx.map[String(id)]
|
|
545
583
|
if (ctx.render) return ctx.render(id, mapping)
|
|
546
|
-
if (mapping) return defaultCitationChip(mapping.page, mapping.file ?? '', true)
|
|
584
|
+
if (mapping) return defaultCitationChip(mapping.page, mapping.file ?? '', true, ctx.viewSourceLabel)
|
|
547
585
|
// Unresolved id: when the map is non-empty (consumer claims to know
|
|
548
586
|
// the citations), drop silently — it's likely an LLM hallucination.
|
|
549
587
|
// When the map is empty (consumer didn't supply one), preserve a
|
|
550
588
|
// human-visible placeholder so the marker isn't lost.
|
|
551
|
-
return Object.keys(ctx.map).length > 0
|
|
589
|
+
return Object.keys(ctx.map).length > 0
|
|
590
|
+
? ''
|
|
591
|
+
: formatMCPUIString(ctx.unresolvedLabel ?? DEFAULT_MCPUI_STRINGS.citationUnresolved, {
|
|
592
|
+
id,
|
|
593
|
+
})
|
|
552
594
|
}
|
|
553
595
|
)
|
|
554
596
|
}
|
|
@@ -670,7 +712,17 @@ function TableRenderer(props: {
|
|
|
670
712
|
// every `renderCellValue` call below. Absent → undefined → cells render
|
|
671
713
|
// as before (regression-safe).
|
|
672
714
|
const citationCtx: CitationCtx | undefined = tableParams.citationMap
|
|
673
|
-
? {
|
|
715
|
+
? {
|
|
716
|
+
map: tableParams.citationMap,
|
|
717
|
+
render: tableParams.citationRender,
|
|
718
|
+
// Getters, not snapshots: cells read the provider's current wording.
|
|
719
|
+
get unresolvedLabel() {
|
|
720
|
+
return strings.citationUnresolved
|
|
721
|
+
},
|
|
722
|
+
get viewSourceLabel() {
|
|
723
|
+
return strings.citationViewSource
|
|
724
|
+
},
|
|
725
|
+
}
|
|
674
726
|
: undefined
|
|
675
727
|
|
|
676
728
|
// ─── Client-side sorting (v4.0.5) ────────────────────────
|
|
@@ -706,7 +758,7 @@ function TableRenderer(props: {
|
|
|
706
758
|
if (isNum) {
|
|
707
759
|
cmp = (Number(va) || 0) - (Number(vb) || 0)
|
|
708
760
|
} else {
|
|
709
|
-
cmp = String(va).localeCompare(String(vb),
|
|
761
|
+
cmp = String(va).localeCompare(String(vb), strings.locale, { sensitivity: 'base' })
|
|
710
762
|
}
|
|
711
763
|
return dir === 'desc' ? -cmp : cmp
|
|
712
764
|
})
|
|
@@ -729,7 +781,7 @@ function TableRenderer(props: {
|
|
|
729
781
|
// context where users scan many tables across messages. Backward-compat
|
|
730
782
|
// for anyone who explicitly disabled.
|
|
731
783
|
const isSearchable = () => tableParams.searchable !== false
|
|
732
|
-
const searchPlaceholder = () => tableParams.searchPlaceholder ||
|
|
784
|
+
const searchPlaceholder = () => tableParams.searchPlaceholder || strings.tableSearchPlaceholder
|
|
733
785
|
|
|
734
786
|
const handleSearch = (value: string) => {
|
|
735
787
|
setSearchQuery(value)
|
|
@@ -800,7 +852,7 @@ function TableRenderer(props: {
|
|
|
800
852
|
for (const n of [10, 30, 60, 100]) {
|
|
801
853
|
if (n < total) opts.push({ value: n, label: String(n) })
|
|
802
854
|
}
|
|
803
|
-
opts.push({ value: 0, label:
|
|
855
|
+
opts.push({ value: 0, label: strings.paginationAllRows })
|
|
804
856
|
return opts
|
|
805
857
|
}
|
|
806
858
|
|
|
@@ -1006,18 +1058,18 @@ function TableRenderer(props: {
|
|
|
1006
1058
|
}
|
|
1007
1059
|
|
|
1008
1060
|
return (
|
|
1009
|
-
<ExpandableWrapper title={tableParams.title ||
|
|
1061
|
+
<ExpandableWrapper title={tableParams.title || strings.tableTitle} copyData={getTableCSV()} copyLabel={strings.tableCopyCsv} toolbarVariant={props.toolbarVariant} onExpandedChange={setSelfExpanded}>
|
|
1010
1062
|
<div class={`relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden group ${
|
|
1011
1063
|
isExpanded() ? 'flex-1 min-h-0 flex flex-col' : 'h-full'
|
|
1012
1064
|
} ${tableParams.className || ''}`}>
|
|
1013
|
-
<Show when={exportable} fallback={<CopyButton getText={getTableCSV} title=
|
|
1065
|
+
<Show when={exportable} fallback={<CopyButton getText={getTableCSV} title={strings.tableCopyCsv} position="top-right" />}>
|
|
1014
1066
|
<div class="absolute right-10 top-2 z-10">
|
|
1015
1067
|
<button
|
|
1016
1068
|
ref={exportTriggerRef}
|
|
1017
1069
|
onClick={() => setShowExportMenu(!showExportMenu())}
|
|
1018
1070
|
class="opacity-60 hover:opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"
|
|
1019
|
-
title=
|
|
1020
|
-
aria-label=
|
|
1071
|
+
title={strings.tableExport}
|
|
1072
|
+
aria-label={strings.tableExport}
|
|
1021
1073
|
aria-haspopup="menu"
|
|
1022
1074
|
aria-expanded={showExportMenu()}
|
|
1023
1075
|
>
|
|
@@ -1032,13 +1084,13 @@ function TableRenderer(props: {
|
|
|
1032
1084
|
width={144}
|
|
1033
1085
|
>
|
|
1034
1086
|
<Show when={(exportFormats as string[]).includes('tsv')}>
|
|
1035
|
-
<button onClick={() => handleExport('tsv')} class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">
|
|
1087
|
+
<button onClick={() => handleExport('tsv')} class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">{strings.tableCopyTsv}</button>
|
|
1036
1088
|
</Show>
|
|
1037
1089
|
<Show when={(exportFormats as string[]).includes('csv')}>
|
|
1038
|
-
<button onClick={() => handleExport('csv')} class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">
|
|
1090
|
+
<button onClick={() => handleExport('csv')} class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">{strings.tableDownloadCsv}</button>
|
|
1039
1091
|
</Show>
|
|
1040
1092
|
<Show when={(exportFormats as string[]).includes('json')}>
|
|
1041
|
-
<button onClick={() => handleExport('json')} class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">
|
|
1093
|
+
<button onClick={() => handleExport('json')} class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">{strings.tableDownloadJson}</button>
|
|
1042
1094
|
</Show>
|
|
1043
1095
|
</PortalDropdownMenu>
|
|
1044
1096
|
</div>
|
|
@@ -1048,7 +1100,9 @@ function TableRenderer(props: {
|
|
|
1048
1100
|
<h3 id={`${tableId}-title`} class="text-sm font-semibold text-gray-900 dark:text-white mb-3 flex-shrink-0">
|
|
1049
1101
|
{tableParams.title}
|
|
1050
1102
|
<Show when={isVirtualizing()}>
|
|
1051
|
-
<span class="ml-2 text-xs font-normal text-gray-400">
|
|
1103
|
+
<span class="ml-2 text-xs font-normal text-gray-400">
|
|
1104
|
+
{formatMCPUIString(strings.tableVirtualizedRows, { count: tableParams.rows?.length ?? 0 })}
|
|
1105
|
+
</span>
|
|
1052
1106
|
</Show>
|
|
1053
1107
|
</h3>
|
|
1054
1108
|
</Show>
|
|
@@ -1069,13 +1123,16 @@ function TableRenderer(props: {
|
|
|
1069
1123
|
type="button"
|
|
1070
1124
|
onClick={() => { handleSearch(''); setSearchQuery(''); setDebouncedQuery('') }}
|
|
1071
1125
|
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 text-sm"
|
|
1072
|
-
aria-label=
|
|
1126
|
+
aria-label={strings.tableClearSearch}
|
|
1073
1127
|
>×</button>
|
|
1074
1128
|
</Show>
|
|
1075
1129
|
</div>
|
|
1076
1130
|
<Show when={debouncedQuery() && filteredRows().length !== sortedRows().length}>
|
|
1077
1131
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
1078
|
-
{
|
|
1132
|
+
{formatMCPUIString(
|
|
1133
|
+
filteredRows().length === 1 ? strings.tableSearchResultsOne : strings.tableSearchResultsMany,
|
|
1134
|
+
{ count: filteredRows().length, total: sortedRows().length }
|
|
1135
|
+
)}
|
|
1079
1136
|
</p>
|
|
1080
1137
|
</Show>
|
|
1081
1138
|
</Show>
|
|
@@ -1106,7 +1163,7 @@ function TableRenderer(props: {
|
|
|
1106
1163
|
})()
|
|
1107
1164
|
}
|
|
1108
1165
|
role="region"
|
|
1109
|
-
aria-label={tableParams.title ||
|
|
1166
|
+
aria-label={tableParams.title || strings.tableAriaLabel}
|
|
1110
1167
|
tabindex="0"
|
|
1111
1168
|
>
|
|
1112
1169
|
<table
|
|
@@ -1122,7 +1179,7 @@ function TableRenderer(props: {
|
|
|
1122
1179
|
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider border-b border-gray-200 dark:border-gray-700 first:pl-6 last:pr-6 bg-gray-100 dark:bg-gray-900 cursor-pointer select-none hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors"
|
|
1123
1180
|
style={column.width ? { width: column.width } : {}}
|
|
1124
1181
|
on:click={() => handleSort(column.key)}
|
|
1125
|
-
title={
|
|
1182
|
+
title={formatMCPUIString(strings.sortBy, { column: column.label })}
|
|
1126
1183
|
>
|
|
1127
1184
|
<span class="inline-flex items-center gap-1">
|
|
1128
1185
|
{column.label}
|
|
@@ -1151,12 +1208,14 @@ function TableRenderer(props: {
|
|
|
1151
1208
|
<Show when={tableParams.pagination}>
|
|
1152
1209
|
<div class="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
|
|
1153
1210
|
<span>
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1211
|
+
{formatMCPUIString(strings.tableServerPageRange, {
|
|
1212
|
+
start: tableParams.pagination.currentPage * tableParams.pagination.pageSize + 1,
|
|
1213
|
+
end: Math.min(
|
|
1214
|
+
(tableParams.pagination.currentPage + 1) * tableParams.pagination.pageSize,
|
|
1215
|
+
tableParams.pagination.totalRows
|
|
1216
|
+
),
|
|
1217
|
+
total: tableParams.pagination.totalRows,
|
|
1218
|
+
})}
|
|
1160
1219
|
</span>
|
|
1161
1220
|
</div>
|
|
1162
1221
|
</Show>
|
|
@@ -1165,7 +1224,7 @@ function TableRenderer(props: {
|
|
|
1165
1224
|
<Show when={needsClientPagination()}>
|
|
1166
1225
|
<div class="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
|
|
1167
1226
|
<span>
|
|
1168
|
-
{clientRangeStart()}–{clientRangeEnd()} / {filteredRows().length.toLocaleString(
|
|
1227
|
+
{clientRangeStart()}–{clientRangeEnd()} / {filteredRows().length.toLocaleString(strings.locale)}
|
|
1169
1228
|
</span>
|
|
1170
1229
|
<div class="flex items-center gap-2">
|
|
1171
1230
|
<button
|
|
@@ -1202,7 +1261,7 @@ function TableRenderer(props: {
|
|
|
1202
1261
|
{(opt) => <option value={opt.value}>{opt.label}</option>}
|
|
1203
1262
|
</For>
|
|
1204
1263
|
</select>
|
|
1205
|
-
<span class="text-gray-400"
|
|
1264
|
+
<span class="text-gray-400">{strings.perPageSuffix}</span>
|
|
1206
1265
|
</Show>
|
|
1207
1266
|
</div>
|
|
1208
1267
|
</div>
|
|
@@ -1217,6 +1276,7 @@ function TableRenderer(props: {
|
|
|
1217
1276
|
* Render a metric card component
|
|
1218
1277
|
*/
|
|
1219
1278
|
function MetricRenderer(props: { component: UIComponent }) {
|
|
1279
|
+
const strings = useMCPUIStrings()
|
|
1220
1280
|
const metricParams = props.component.params as any
|
|
1221
1281
|
|
|
1222
1282
|
// Generate copyable text for metric
|
|
@@ -1229,7 +1289,7 @@ function MetricRenderer(props: { component: UIComponent }) {
|
|
|
1229
1289
|
|
|
1230
1290
|
return (
|
|
1231
1291
|
<div class="relative w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 group">
|
|
1232
|
-
<CopyButton getText={getMetricText} title=
|
|
1292
|
+
<CopyButton getText={getMetricText} title={strings.copyMetric} position="top-right" />
|
|
1233
1293
|
<div class="flex flex-col h-full justify-between">
|
|
1234
1294
|
<div>
|
|
1235
1295
|
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">
|
|
@@ -1277,14 +1337,17 @@ function MetricRenderer(props: { component: UIComponent }) {
|
|
|
1277
1337
|
* Extract image data from markdown image link format
|
|
1278
1338
|
* Pattern: [](link-url)\n*Photo by Author*
|
|
1279
1339
|
*/
|
|
1280
|
-
function extractImageFromMarkdown(
|
|
1340
|
+
function extractImageFromMarkdown(
|
|
1341
|
+
content: string,
|
|
1342
|
+
fallbackAlt: string
|
|
1343
|
+
): { alt: string; imageUrl: string; linkUrl: string; credit: string } | null {
|
|
1281
1344
|
// Pattern: [](link-url) followed by optional credit line
|
|
1282
1345
|
const imagePattern = /\[!\[([^\]]*)\]\(([^)]+)\)\]\(([^)]+)\)\s*\*([^*]+)\*/
|
|
1283
1346
|
const match = content.match(imagePattern)
|
|
1284
1347
|
|
|
1285
1348
|
if (match) {
|
|
1286
1349
|
return {
|
|
1287
|
-
alt: match[1] ||
|
|
1350
|
+
alt: match[1] || fallbackAlt,
|
|
1288
1351
|
imageUrl: match[2],
|
|
1289
1352
|
linkUrl: match[3],
|
|
1290
1353
|
credit: match[4].trim()
|
|
@@ -1298,6 +1361,7 @@ function extractImageFromMarkdown(content: string): { alt: string; imageUrl: str
|
|
|
1298
1361
|
* Render a text component (with optional markdown)
|
|
1299
1362
|
*/
|
|
1300
1363
|
function TextRenderer(props: { component: UIComponent }) {
|
|
1364
|
+
const strings = useMCPUIStrings()
|
|
1301
1365
|
const textParams = props.component.params as any
|
|
1302
1366
|
|
|
1303
1367
|
// Check if this is an image markdown that should be rendered as image
|
|
@@ -1308,7 +1372,7 @@ function TextRenderer(props: { component: UIComponent }) {
|
|
|
1308
1372
|
// markdown path; an unusable link URL only drops the anchor.
|
|
1309
1373
|
const imageData = createMemo(() => {
|
|
1310
1374
|
if (textParams.markdown && textParams.content) {
|
|
1311
|
-
const extracted = extractImageFromMarkdown(textParams.content)
|
|
1375
|
+
const extracted = extractImageFromMarkdown(textParams.content, strings.imageAlt)
|
|
1312
1376
|
if (!extracted) return null
|
|
1313
1377
|
const imageUrl = safeUrl(extracted.imageUrl, { allowDataImage: true })
|
|
1314
1378
|
if (!imageUrl) return null
|
|
@@ -1351,7 +1415,7 @@ function TextRenderer(props: { component: UIComponent }) {
|
|
|
1351
1415
|
when={imageData()}
|
|
1352
1416
|
fallback={
|
|
1353
1417
|
<div class="relative w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 group">
|
|
1354
|
-
<CopyButton getText={getTextContent} title=
|
|
1418
|
+
<CopyButton getText={getTextContent} title={strings.copyText} position="top-right" />
|
|
1355
1419
|
<SafeHtml
|
|
1356
1420
|
class={`prose prose-sm dark:prose-invert max-w-none ${textParams.className || ''}`}
|
|
1357
1421
|
html={htmlContent}
|
|
@@ -1396,7 +1460,13 @@ function TextRenderer(props: { component: UIComponent }) {
|
|
|
1396
1460
|
* Render an iframe component
|
|
1397
1461
|
*/
|
|
1398
1462
|
function IframeRenderer(props: { component: UIComponent }) {
|
|
1463
|
+
const strings = useMCPUIStrings()
|
|
1464
|
+
const config = useMCPUIConfig()
|
|
1399
1465
|
const params = props.component.params as any
|
|
1466
|
+
// v6.21.0 — the host's custom trusted hosts now reach BOTH the sandbox (they
|
|
1467
|
+
// used not to: `getIframeSandbox(params.url)` was called with no options) and
|
|
1468
|
+
// the `credentialless` decision.
|
|
1469
|
+
const trustOptions = () => ({ customTrustedDomains: config.customTrustedIframeDomains })
|
|
1400
1470
|
return (
|
|
1401
1471
|
<div class="w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col">
|
|
1402
1472
|
<Show when={params.title}>
|
|
@@ -1406,12 +1476,25 @@ function IframeRenderer(props: { component: UIComponent }) {
|
|
|
1406
1476
|
</Show>
|
|
1407
1477
|
<iframe
|
|
1408
1478
|
src={params.url}
|
|
1409
|
-
title={params.title ||
|
|
1479
|
+
title={params.title || strings.iframeTitle}
|
|
1410
1480
|
class="w-full border-0 flex-1"
|
|
1411
1481
|
style={`height: ${params.height || '400px'}; min-height: 300px;`}
|
|
1412
|
-
sandbox={getIframeSandbox(params.url)}
|
|
1482
|
+
sandbox={getIframeSandbox(params.url, trustOptions())}
|
|
1483
|
+
// `attr:` is required: `credentialless` is in dom-expressions'
|
|
1484
|
+
// `Properties` set, so a plain `credentialless={…}` compiles to a JS
|
|
1485
|
+
// property assignment and never reaches the DOM as an attribute.
|
|
1486
|
+
// `true | undefined` keeps it a BOOLEAN attribute — bare in SSR
|
|
1487
|
+
// markup, removed (not `="false"`) when it does not apply.
|
|
1488
|
+
attr:credentialless={
|
|
1489
|
+
shouldSetCredentialless(params.url, config, {
|
|
1490
|
+
// This iframe's sandbox never carries `allow-same-origin` for an
|
|
1491
|
+
// untrusted host, so it already runs without cookies.
|
|
1492
|
+
sandboxedWithoutSameOrigin: !isTrustedIframeDomain(params.url, trustOptions()),
|
|
1493
|
+
}) || undefined
|
|
1494
|
+
}
|
|
1413
1495
|
loading="lazy"
|
|
1414
1496
|
/>
|
|
1497
|
+
<IframeFallbackLink url={params.url} class="border-t border-gray-200 dark:border-gray-700" />
|
|
1415
1498
|
</div>
|
|
1416
1499
|
)
|
|
1417
1500
|
}
|
|
@@ -1420,6 +1503,7 @@ function IframeRenderer(props: { component: UIComponent }) {
|
|
|
1420
1503
|
* Render an image component
|
|
1421
1504
|
*/
|
|
1422
1505
|
function ImageRenderer(props: { component: UIComponent }) {
|
|
1506
|
+
const strings = useMCPUIStrings()
|
|
1423
1507
|
const params = props.component.params as any
|
|
1424
1508
|
|
|
1425
1509
|
return (
|
|
@@ -1430,11 +1514,13 @@ function ImageRenderer(props: { component: UIComponent }) {
|
|
|
1430
1514
|
target="_blank"
|
|
1431
1515
|
rel="noopener noreferrer"
|
|
1432
1516
|
class="cursor-zoom-in focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 rounded"
|
|
1433
|
-
aria-label={
|
|
1517
|
+
aria-label={formatMCPUIString(strings.imageViewFullSize, {
|
|
1518
|
+
alt: params.alt || strings.imageAltFallback,
|
|
1519
|
+
})}
|
|
1434
1520
|
>
|
|
1435
1521
|
<img
|
|
1436
1522
|
src={safeUrl(params.url, { allowDataImage: true })}
|
|
1437
|
-
alt={params.alt ||
|
|
1523
|
+
alt={params.alt || strings.imageAlt}
|
|
1438
1524
|
class="max-w-full max-h-[500px] object-contain rounded shadow-sm hover:opacity-95 transition-opacity"
|
|
1439
1525
|
loading="lazy"
|
|
1440
1526
|
/>
|
|
@@ -1453,6 +1539,7 @@ function ImageRenderer(props: { component: UIComponent }) {
|
|
|
1453
1539
|
* Render a link component
|
|
1454
1540
|
*/
|
|
1455
1541
|
function LinkRenderer(props: { component: UIComponent }) {
|
|
1542
|
+
const strings = useMCPUIStrings()
|
|
1456
1543
|
const params = props.component.params as any
|
|
1457
1544
|
|
|
1458
1545
|
return (
|
|
@@ -1460,7 +1547,10 @@ function LinkRenderer(props: { component: UIComponent }) {
|
|
|
1460
1547
|
href={safeUrl(params.url)}
|
|
1461
1548
|
target="_blank"
|
|
1462
1549
|
rel="noopener noreferrer"
|
|
1463
|
-
aria-label={
|
|
1550
|
+
aria-label={formatMCPUIString(strings.linkOpensInNewTab, {
|
|
1551
|
+
label: params.label || strings.linkLabel,
|
|
1552
|
+
description: params.description || params.url,
|
|
1553
|
+
})}
|
|
1464
1554
|
class={`flex items-center gap-3 p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors group h-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${params.className || ''}`}
|
|
1465
1555
|
onClick={(e) => e.stopPropagation()}
|
|
1466
1556
|
>
|
|
@@ -1525,6 +1615,8 @@ function ComponentRenderer(props: {
|
|
|
1525
1615
|
// mounted above; null-checked at every dispatch site so apps that don't
|
|
1526
1616
|
// opt in see zero behavior change.
|
|
1527
1617
|
const telemetry = useTelemetry()
|
|
1618
|
+
const strings = useMCPUIStrings()
|
|
1619
|
+
const componentConfig = useMCPUIConfig()
|
|
1528
1620
|
|
|
1529
1621
|
onMount(() => {
|
|
1530
1622
|
markRenderEnd(props.component.id)
|
|
@@ -1566,8 +1658,13 @@ function ComponentRenderer(props: {
|
|
|
1566
1658
|
}
|
|
1567
1659
|
})
|
|
1568
1660
|
|
|
1569
|
-
// Validate component before rendering
|
|
1570
|
-
|
|
1661
|
+
// Validate component before rendering. The host's allow-list extension
|
|
1662
|
+
// (v6.21.0) has to reach validation too: an unlisted host is replaced by the
|
|
1663
|
+
// validation card before any renderer runs.
|
|
1664
|
+
const validation = validateComponent(props.component, {
|
|
1665
|
+
iframePolicy: componentConfig.iframePolicy,
|
|
1666
|
+
customIframeDomains: componentConfig.customIframeDomains,
|
|
1667
|
+
})
|
|
1571
1668
|
if (!validation.valid) {
|
|
1572
1669
|
props.onError?.({
|
|
1573
1670
|
type: 'validation',
|
|
@@ -1590,7 +1687,7 @@ function ComponentRenderer(props: {
|
|
|
1590
1687
|
}
|
|
1591
1688
|
|
|
1592
1689
|
const mode: ValidationErrorMode = props.errorMode ?? 'block'
|
|
1593
|
-
const firstError = validation.errors?.[0]?.message ||
|
|
1690
|
+
const firstError = validation.errors?.[0]?.message || strings.validationUnknownError
|
|
1594
1691
|
|
|
1595
1692
|
// P1.6 — an UNKNOWN component type must never produce a silent blank,
|
|
1596
1693
|
// whatever the errorMode. The renderer has no branch for it, so even
|
|
@@ -1609,7 +1706,7 @@ function ComponentRenderer(props: {
|
|
|
1609
1706
|
<div
|
|
1610
1707
|
class="inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 text-xs text-yellow-800 dark:text-yellow-200"
|
|
1611
1708
|
role="alert"
|
|
1612
|
-
aria-label=
|
|
1709
|
+
aria-label={strings.validationWarning}
|
|
1613
1710
|
title={firstError}
|
|
1614
1711
|
>
|
|
1615
1712
|
<svg
|
|
@@ -1627,7 +1724,7 @@ function ComponentRenderer(props: {
|
|
|
1627
1724
|
<line x1="12" y1="9" x2="12" y2="13" />
|
|
1628
1725
|
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
1629
1726
|
</svg>
|
|
1630
|
-
<span>
|
|
1727
|
+
<span>{formatMCPUIString(strings.invalidComponent, { type: props.component.type })}</span>
|
|
1631
1728
|
</div>
|
|
1632
1729
|
)
|
|
1633
1730
|
}
|
|
@@ -1635,7 +1732,7 @@ function ComponentRenderer(props: {
|
|
|
1635
1732
|
// mode === 'block' (default, pre-v5.4.0 behavior)
|
|
1636
1733
|
return (
|
|
1637
1734
|
<div class="w-full h-full bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
|
|
1638
|
-
<p class="text-sm font-medium text-red-900 dark:text-red-100">
|
|
1735
|
+
<p class="text-sm font-medium text-red-900 dark:text-red-100">{strings.validationError}</p>
|
|
1639
1736
|
<p class="text-xs text-red-700 dark:text-red-300 mt-1">
|
|
1640
1737
|
{firstError}
|
|
1641
1738
|
</p>
|
|
@@ -1737,12 +1834,13 @@ function ComponentRenderer(props: {
|
|
|
1737
1834
|
* 'UNKNOWN_COMPONENT_TYPE'`), so this component stays purely presentational.
|
|
1738
1835
|
*/
|
|
1739
1836
|
function UnsupportedComponentFallback(props: { component: UIComponent }) {
|
|
1837
|
+
const strings = useMCPUIStrings()
|
|
1740
1838
|
return (
|
|
1741
1839
|
<div
|
|
1742
1840
|
role="alert"
|
|
1743
1841
|
class="w-full rounded-lg border border-amber-300 bg-amber-50 px-3 py-2 text-sm text-amber-800 dark:border-amber-700/50 dark:bg-amber-900/20 dark:text-amber-200"
|
|
1744
1842
|
>
|
|
1745
|
-
|
|
1843
|
+
{strings.unsupportedComponentType} <code class="font-mono">{props.component.type}</code>
|
|
1746
1844
|
</div>
|
|
1747
1845
|
)
|
|
1748
1846
|
}
|
|
@@ -1852,13 +1950,21 @@ function ActionRenderer(props: { component: UIComponent }) {
|
|
|
1852
1950
|
* Handles {error: true, message: "...", tool: "...", suggestions: [...]} format
|
|
1853
1951
|
*/
|
|
1854
1952
|
function ErrorCardRenderer(props: { error: any }) {
|
|
1953
|
+
const strings = useMCPUIStrings()
|
|
1954
|
+
// The timestamp depends on the viewer's time zone, which the server cannot
|
|
1955
|
+
// know. Render it only after mount so SSR and hydration produce the same markup.
|
|
1956
|
+
const [mounted, setMounted] = createSignal(false)
|
|
1957
|
+
onMount(() => setMounted(true))
|
|
1855
1958
|
const getErrorText = () => {
|
|
1856
|
-
return
|
|
1959
|
+
return formatMCPUIString(strings.errorCopyText, {
|
|
1960
|
+
tool: props.error.tool || strings.errorUnknownTool,
|
|
1961
|
+
message: props.error.message || strings.errorUnknown,
|
|
1962
|
+
})
|
|
1857
1963
|
}
|
|
1858
1964
|
|
|
1859
1965
|
return (
|
|
1860
1966
|
<div class="relative w-full bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 group">
|
|
1861
|
-
<CopyButton getText={getErrorText} title=
|
|
1967
|
+
<CopyButton getText={getErrorText} title={strings.copyErrorDetails} position="top-right" />
|
|
1862
1968
|
<div class="flex items-start gap-3">
|
|
1863
1969
|
<div class="p-2 bg-red-100 dark:bg-red-900/40 rounded-full shrink-0">
|
|
1864
1970
|
<svg class="w-5 h-5 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
@@ -1867,19 +1973,21 @@ function ErrorCardRenderer(props: { error: any }) {
|
|
|
1867
1973
|
</div>
|
|
1868
1974
|
<div class="flex-1 min-w-0">
|
|
1869
1975
|
<h4 class="text-sm font-semibold text-red-800 dark:text-red-200">
|
|
1870
|
-
|
|
1976
|
+
{formatMCPUIString(strings.toolErrorTitle, {
|
|
1977
|
+
tool: props.error.tool || strings.errorUnknownToolName,
|
|
1978
|
+
})}
|
|
1871
1979
|
</h4>
|
|
1872
1980
|
<p class="text-sm text-red-700 dark:text-red-300 mt-1">
|
|
1873
|
-
{props.error.message ||
|
|
1981
|
+
{props.error.message || strings.errorToolExecution}
|
|
1874
1982
|
</p>
|
|
1875
1983
|
<Show when={props.error.type}>
|
|
1876
1984
|
<p class="text-xs text-red-600 dark:text-red-400 mt-1">
|
|
1877
|
-
|
|
1985
|
+
{formatMCPUIString(strings.errorTypeLabel, { type: props.error.type })}
|
|
1878
1986
|
</p>
|
|
1879
1987
|
</Show>
|
|
1880
1988
|
<Show when={props.error.suggestions?.length}>
|
|
1881
1989
|
<div class="mt-3">
|
|
1882
|
-
<p class="text-xs font-medium text-red-700 dark:text-red-300">
|
|
1990
|
+
<p class="text-xs font-medium text-red-700 dark:text-red-300">{strings.errorSuggestions}</p>
|
|
1883
1991
|
<ul class="mt-1 text-xs text-red-600 dark:text-red-400 list-disc list-inside">
|
|
1884
1992
|
<For each={props.error.suggestions}>
|
|
1885
1993
|
{(suggestion: string) => <li>{suggestion}</li>}
|
|
@@ -1887,9 +1995,9 @@ function ErrorCardRenderer(props: { error: any }) {
|
|
|
1887
1995
|
</ul>
|
|
1888
1996
|
</div>
|
|
1889
1997
|
</Show>
|
|
1890
|
-
<Show when={props.error.timestamp}>
|
|
1998
|
+
<Show when={mounted() && props.error.timestamp}>
|
|
1891
1999
|
<p class="text-xs text-red-500 dark:text-red-500 mt-2">
|
|
1892
|
-
{new Date(props.error.timestamp).toLocaleString()}
|
|
2000
|
+
{new Date(props.error.timestamp).toLocaleString(strings.locale)}
|
|
1893
2001
|
</p>
|
|
1894
2002
|
</Show>
|
|
1895
2003
|
</div>
|
|
@@ -1922,6 +2030,7 @@ function isUIResource(content: any): boolean {
|
|
|
1922
2030
|
* Handles HTML resources returned by tools like ui_show_dashboard, ui_show_health
|
|
1923
2031
|
*/
|
|
1924
2032
|
function UIResourceHtmlRenderer(props: { resource: any }) {
|
|
2033
|
+
const strings = useMCPUIStrings()
|
|
1925
2034
|
const htmlContent = () => {
|
|
1926
2035
|
if (props.resource.content?.htmlString) {
|
|
1927
2036
|
return sanitizeHtml(String(props.resource.content.htmlString), SANITIZE_PROFILES.resource)
|
|
@@ -1930,7 +2039,7 @@ function UIResourceHtmlRenderer(props: { resource: any }) {
|
|
|
1930
2039
|
}
|
|
1931
2040
|
|
|
1932
2041
|
const resourceTitle = () => {
|
|
1933
|
-
return props.resource.metadata?.title || props.resource.uri?.replace('ui://deposium/', '') ||
|
|
2042
|
+
return props.resource.metadata?.title || props.resource.uri?.replace('ui://deposium/', '') || strings.resourceTitle
|
|
1934
2043
|
}
|
|
1935
2044
|
|
|
1936
2045
|
return (
|