@seed-ship/mcp-ui-solid 6.19.1 → 6.20.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.
Files changed (257) hide show
  1. package/CHANGELOG.md +519 -0
  2. package/README.md +791 -1
  3. package/dist/adapters/connector.cjs +15 -4
  4. package/dist/adapters/connector.cjs.map +1 -1
  5. package/dist/adapters/connector.d.ts +38 -0
  6. package/dist/adapters/connector.d.ts.map +1 -1
  7. package/dist/adapters/connector.js +15 -4
  8. package/dist/adapters/connector.js.map +1 -1
  9. package/dist/adapters/index.d.ts +4 -3
  10. package/dist/adapters/index.d.ts.map +1 -1
  11. package/dist/adapters/macro-run.cjs +25 -13
  12. package/dist/adapters/macro-run.cjs.map +1 -1
  13. package/dist/adapters/macro-run.d.ts +37 -2
  14. package/dist/adapters/macro-run.d.ts.map +1 -1
  15. package/dist/adapters/macro-run.js +25 -13
  16. package/dist/adapters/macro-run.js.map +1 -1
  17. package/dist/adapters.cjs +2 -0
  18. package/dist/adapters.cjs.map +1 -1
  19. package/dist/adapters.js +4 -2
  20. package/dist/components/ActionGroupRenderer.cjs +3 -1
  21. package/dist/components/ActionGroupRenderer.cjs.map +1 -1
  22. package/dist/components/ActionGroupRenderer.d.ts.map +1 -1
  23. package/dist/components/ActionGroupRenderer.js +3 -1
  24. package/dist/components/ActionGroupRenderer.js.map +1 -1
  25. package/dist/components/AgentCard.cjs +11 -8
  26. package/dist/components/AgentCard.cjs.map +1 -1
  27. package/dist/components/AgentCard.d.ts.map +1 -1
  28. package/dist/components/AgentCard.js +11 -8
  29. package/dist/components/AgentCard.js.map +1 -1
  30. package/dist/components/AgentHandoff.cjs +6 -1
  31. package/dist/components/AgentHandoff.cjs.map +1 -1
  32. package/dist/components/AgentHandoff.d.ts.map +1 -1
  33. package/dist/components/AgentHandoff.js +6 -1
  34. package/dist/components/AgentHandoff.js.map +1 -1
  35. package/dist/components/ArtifactRenderer.cjs +25 -6
  36. package/dist/components/ArtifactRenderer.cjs.map +1 -1
  37. package/dist/components/ArtifactRenderer.d.ts.map +1 -1
  38. package/dist/components/ArtifactRenderer.js +25 -6
  39. package/dist/components/ArtifactRenderer.js.map +1 -1
  40. package/dist/components/AutocompleteDropdown.cjs +30 -24
  41. package/dist/components/AutocompleteDropdown.cjs.map +1 -1
  42. package/dist/components/AutocompleteDropdown.d.ts.map +1 -1
  43. package/dist/components/AutocompleteDropdown.js +30 -24
  44. package/dist/components/AutocompleteDropdown.js.map +1 -1
  45. package/dist/components/AutocompleteFormField.cjs +3 -1
  46. package/dist/components/AutocompleteFormField.cjs.map +1 -1
  47. package/dist/components/AutocompleteFormField.d.ts.map +1 -1
  48. package/dist/components/AutocompleteFormField.js +4 -2
  49. package/dist/components/AutocompleteFormField.js.map +1 -1
  50. package/dist/components/BriefingDiff.cjs +45 -39
  51. package/dist/components/BriefingDiff.cjs.map +1 -1
  52. package/dist/components/BriefingDiff.d.ts.map +1 -1
  53. package/dist/components/BriefingDiff.js +45 -39
  54. package/dist/components/BriefingDiff.js.map +1 -1
  55. package/dist/components/CarouselRenderer.cjs +8 -2
  56. package/dist/components/CarouselRenderer.cjs.map +1 -1
  57. package/dist/components/CarouselRenderer.d.ts.map +1 -1
  58. package/dist/components/CarouselRenderer.js +8 -2
  59. package/dist/components/CarouselRenderer.js.map +1 -1
  60. package/dist/components/ChartJSRenderer.cjs +60 -36
  61. package/dist/components/ChartJSRenderer.cjs.map +1 -1
  62. package/dist/components/ChartJSRenderer.d.ts.map +1 -1
  63. package/dist/components/ChartJSRenderer.js +61 -37
  64. package/dist/components/ChartJSRenderer.js.map +1 -1
  65. package/dist/components/ChatPrompt.cjs +15 -9
  66. package/dist/components/ChatPrompt.cjs.map +1 -1
  67. package/dist/components/ChatPrompt.d.ts.map +1 -1
  68. package/dist/components/ChatPrompt.js +15 -9
  69. package/dist/components/ChatPrompt.js.map +1 -1
  70. package/dist/components/CodeBlockRenderer.cjs +34 -16
  71. package/dist/components/CodeBlockRenderer.cjs.map +1 -1
  72. package/dist/components/CodeBlockRenderer.d.ts.map +1 -1
  73. package/dist/components/CodeBlockRenderer.js +34 -16
  74. package/dist/components/CodeBlockRenderer.js.map +1 -1
  75. package/dist/components/DataPreviewSection.cjs +91 -44
  76. package/dist/components/DataPreviewSection.cjs.map +1 -1
  77. package/dist/components/DataPreviewSection.d.ts.map +1 -1
  78. package/dist/components/DataPreviewSection.js +92 -45
  79. package/dist/components/DataPreviewSection.js.map +1 -1
  80. package/dist/components/DegradedFallback.cjs +20 -16
  81. package/dist/components/DegradedFallback.cjs.map +1 -1
  82. package/dist/components/DegradedFallback.d.ts.map +1 -1
  83. package/dist/components/DegradedFallback.js +20 -16
  84. package/dist/components/DegradedFallback.js.map +1 -1
  85. package/dist/components/FooterRenderer.cjs +21 -17
  86. package/dist/components/FooterRenderer.cjs.map +1 -1
  87. package/dist/components/FooterRenderer.d.ts.map +1 -1
  88. package/dist/components/FooterRenderer.js +21 -17
  89. package/dist/components/FooterRenderer.js.map +1 -1
  90. package/dist/components/FormFieldRenderer.cjs +116 -93
  91. package/dist/components/FormFieldRenderer.cjs.map +1 -1
  92. package/dist/components/FormFieldRenderer.d.ts.map +1 -1
  93. package/dist/components/FormFieldRenderer.js +116 -93
  94. package/dist/components/FormFieldRenderer.js.map +1 -1
  95. package/dist/components/FormRenderer.cjs +55 -31
  96. package/dist/components/FormRenderer.cjs.map +1 -1
  97. package/dist/components/FormRenderer.d.ts.map +1 -1
  98. package/dist/components/FormRenderer.js +55 -31
  99. package/dist/components/FormRenderer.js.map +1 -1
  100. package/dist/components/GenerativeUIErrorBoundary.cjs +20 -15
  101. package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
  102. package/dist/components/GenerativeUIErrorBoundary.d.ts.map +1 -1
  103. package/dist/components/GenerativeUIErrorBoundary.js +20 -15
  104. package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
  105. package/dist/components/GraphRenderer.cjs +56 -33
  106. package/dist/components/GraphRenderer.cjs.map +1 -1
  107. package/dist/components/GraphRenderer.d.ts.map +1 -1
  108. package/dist/components/GraphRenderer.js +56 -33
  109. package/dist/components/GraphRenderer.js.map +1 -1
  110. package/dist/components/ImageGalleryRenderer.cjs +13 -4
  111. package/dist/components/ImageGalleryRenderer.cjs.map +1 -1
  112. package/dist/components/ImageGalleryRenderer.d.ts.map +1 -1
  113. package/dist/components/ImageGalleryRenderer.js +13 -4
  114. package/dist/components/ImageGalleryRenderer.js.map +1 -1
  115. package/dist/components/LightboxOverlay.cjs +16 -8
  116. package/dist/components/LightboxOverlay.cjs.map +1 -1
  117. package/dist/components/LightboxOverlay.d.ts.map +1 -1
  118. package/dist/components/LightboxOverlay.js +17 -9
  119. package/dist/components/LightboxOverlay.js.map +1 -1
  120. package/dist/components/MapRenderer.cjs +59 -29
  121. package/dist/components/MapRenderer.cjs.map +1 -1
  122. package/dist/components/MapRenderer.d.ts +10 -2
  123. package/dist/components/MapRenderer.d.ts.map +1 -1
  124. package/dist/components/MapRenderer.js +60 -30
  125. package/dist/components/MapRenderer.js.map +1 -1
  126. package/dist/components/ModalRenderer.cjs +4 -1
  127. package/dist/components/ModalRenderer.cjs.map +1 -1
  128. package/dist/components/ModalRenderer.d.ts.map +1 -1
  129. package/dist/components/ModalRenderer.js +5 -2
  130. package/dist/components/ModalRenderer.js.map +1 -1
  131. package/dist/components/ScratchpadPanel.cjs +578 -535
  132. package/dist/components/ScratchpadPanel.cjs.map +1 -1
  133. package/dist/components/ScratchpadPanel.d.ts.map +1 -1
  134. package/dist/components/ScratchpadPanel.js +578 -535
  135. package/dist/components/ScratchpadPanel.js.map +1 -1
  136. package/dist/components/StreamingUIRenderer.cjs +92 -28
  137. package/dist/components/StreamingUIRenderer.cjs.map +1 -1
  138. package/dist/components/StreamingUIRenderer.d.ts.map +1 -1
  139. package/dist/components/StreamingUIRenderer.js +92 -28
  140. package/dist/components/StreamingUIRenderer.js.map +1 -1
  141. package/dist/components/UIResourceRenderer.cjs +456 -363
  142. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  143. package/dist/components/UIResourceRenderer.d.ts +21 -0
  144. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  145. package/dist/components/UIResourceRenderer.js +458 -365
  146. package/dist/components/UIResourceRenderer.js.map +1 -1
  147. package/dist/components/VerifiedText.cjs +39 -20
  148. package/dist/components/VerifiedText.cjs.map +1 -1
  149. package/dist/components/VerifiedText.d.ts +2 -1
  150. package/dist/components/VerifiedText.d.ts.map +1 -1
  151. package/dist/components/VerifiedText.js +39 -20
  152. package/dist/components/VerifiedText.js.map +1 -1
  153. package/dist/components/VideoRenderer.cjs +10 -5
  154. package/dist/components/VideoRenderer.cjs.map +1 -1
  155. package/dist/components/VideoRenderer.d.ts.map +1 -1
  156. package/dist/components/VideoRenderer.js +10 -5
  157. package/dist/components/VideoRenderer.js.map +1 -1
  158. package/dist/components/chart-data-table.cjs +38 -17
  159. package/dist/components/chart-data-table.cjs.map +1 -1
  160. package/dist/components/chart-data-table.d.ts +30 -1
  161. package/dist/components/chart-data-table.d.ts.map +1 -1
  162. package/dist/components/chart-data-table.js +38 -17
  163. package/dist/components/chart-data-table.js.map +1 -1
  164. package/dist/context/MCPActionContext.cjs +3 -1
  165. package/dist/context/MCPActionContext.cjs.map +1 -1
  166. package/dist/context/MCPActionContext.d.ts.map +1 -1
  167. package/dist/context/MCPActionContext.js +3 -1
  168. package/dist/context/MCPActionContext.js.map +1 -1
  169. package/dist/context/MCPUIStringsContext.cjs +285 -2
  170. package/dist/context/MCPUIStringsContext.cjs.map +1 -1
  171. package/dist/context/MCPUIStringsContext.d.ts +600 -3
  172. package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
  173. package/dist/context/MCPUIStringsContext.js +285 -2
  174. package/dist/context/MCPUIStringsContext.js.map +1 -1
  175. package/dist/hooks/index.d.ts +2 -2
  176. package/dist/hooks/index.d.ts.map +1 -1
  177. package/dist/hooks/useStreamingUI.cjs +28 -10
  178. package/dist/hooks/useStreamingUI.cjs.map +1 -1
  179. package/dist/hooks/useStreamingUI.d.ts +37 -0
  180. package/dist/hooks/useStreamingUI.d.ts.map +1 -1
  181. package/dist/hooks/useStreamingUI.js +28 -10
  182. package/dist/hooks/useStreamingUI.js.map +1 -1
  183. package/dist/hooks.cjs +1 -0
  184. package/dist/hooks.cjs.map +1 -1
  185. package/dist/hooks.js +2 -1
  186. package/dist/index.cjs +23 -8
  187. package/dist/index.cjs.map +1 -1
  188. package/dist/index.d.cts +9 -3
  189. package/dist/index.d.ts +9 -3
  190. package/dist/index.d.ts.map +1 -1
  191. package/dist/index.js +18 -3
  192. package/dist/index.js.map +1 -1
  193. package/dist/services/validation.cjs +41 -16
  194. package/dist/services/validation.cjs.map +1 -1
  195. package/dist/services/validation.d.ts +47 -2
  196. package/dist/services/validation.d.ts.map +1 -1
  197. package/dist/services/validation.js +41 -16
  198. package/dist/services/validation.js.map +1 -1
  199. package/dist/utils/degraded-projections.cjs +37 -12
  200. package/dist/utils/degraded-projections.cjs.map +1 -1
  201. package/dist/utils/degraded-projections.d.ts +39 -3
  202. package/dist/utils/degraded-projections.d.ts.map +1 -1
  203. package/dist/utils/degraded-projections.js +37 -12
  204. package/dist/utils/degraded-projections.js.map +1 -1
  205. package/dist/utils/format-string.cjs +10 -0
  206. package/dist/utils/format-string.cjs.map +1 -0
  207. package/dist/utils/format-string.d.ts +26 -0
  208. package/dist/utils/format-string.d.ts.map +1 -0
  209. package/dist/utils/format-string.js +10 -0
  210. package/dist/utils/format-string.js.map +1 -0
  211. package/dist/validation.cjs +3 -0
  212. package/dist/validation.cjs.map +1 -1
  213. package/dist/validation.d.cts +2 -1
  214. package/dist/validation.d.ts +2 -1
  215. package/dist/validation.d.ts.map +1 -1
  216. package/dist/validation.js +4 -1
  217. package/package.json +1 -1
  218. package/src/adapters/connector.ts +58 -4
  219. package/src/adapters/index.ts +12 -2
  220. package/src/adapters/macro-run.ts +71 -11
  221. package/src/components/ActionGroupRenderer.tsx +3 -1
  222. package/src/components/AgentCard.tsx +27 -9
  223. package/src/components/AgentHandoff.tsx +4 -1
  224. package/src/components/ArtifactRenderer.tsx +17 -5
  225. package/src/components/AutocompleteDropdown.tsx +8 -6
  226. package/src/components/AutocompleteFormField.tsx +5 -1
  227. package/src/components/BriefingDiff.tsx +11 -3
  228. package/src/components/CarouselRenderer.tsx +4 -2
  229. package/src/components/ChartJSRenderer.tsx +18 -9
  230. package/src/components/ChatPrompt.tsx +13 -8
  231. package/src/components/CodeBlockRenderer.tsx +12 -10
  232. package/src/components/DataPreviewSection.tsx +92 -26
  233. package/src/components/DegradedFallback.tsx +4 -2
  234. package/src/components/FooterRenderer.tsx +3 -1
  235. package/src/components/FormFieldRenderer.tsx +34 -19
  236. package/src/components/FormRenderer.tsx +38 -10
  237. package/src/components/GenerativeUIErrorBoundary.tsx +8 -3
  238. package/src/components/GraphRenderer.tsx +31 -16
  239. package/src/components/ImageGalleryRenderer.tsx +8 -4
  240. package/src/components/LightboxOverlay.tsx +6 -4
  241. package/src/components/MapRenderer.tsx +57 -20
  242. package/src/components/ModalRenderer.tsx +3 -1
  243. package/src/components/ScratchpadPanel.tsx +95 -44
  244. package/src/components/StreamingUIRenderer.tsx +41 -11
  245. package/src/components/UIResourceRenderer.tsx +145 -63
  246. package/src/components/VerifiedText.tsx +19 -9
  247. package/src/components/VideoRenderer.tsx +6 -4
  248. package/src/components/chart-data-table.ts +68 -14
  249. package/src/context/MCPActionContext.tsx +4 -1
  250. package/src/context/MCPUIStringsContext.tsx +1004 -1
  251. package/src/hooks/index.ts +2 -1
  252. package/src/hooks/useStreamingUI.ts +64 -10
  253. package/src/index.ts +23 -0
  254. package/src/services/validation.ts +93 -16
  255. package/src/utils/degraded-projections.ts +75 -12
  256. package/src/utils/format-string.ts +33 -0
  257. package/src/validation.ts +5 -0
@@ -17,6 +17,7 @@ import { AgentCard, AgentStatusBadge } from './AgentCard'
17
17
  import { SplitStepper } from './SplitStepper'
18
18
  import { AgentHandoff } from './AgentHandoff'
19
19
  import { BriefingDiff } from './BriefingDiff'
20
+ import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
20
21
 
21
22
  export interface ScratchpadPanelProps {
22
23
  state: ScratchpadState
@@ -44,16 +45,35 @@ export interface ScratchpadPanelProps {
44
45
  maxHeight?: string
45
46
  }
46
47
 
47
- const STATUS_BADGES: Record<ScratchpadState['status'], { label: string; class: string }> = {
48
- loading: { label: 'Loading...', class: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400' },
49
- ready: { label: 'Action available', class: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-400' },
50
- waiting_human: { label: 'Your turn', class: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 animate-pulse' },
51
- processing: { label: 'Processing...', class: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400' },
52
- complete: { label: 'Complete', class: 'bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400' },
53
- error: { label: 'Error', class: 'bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400' },
48
+ /**
49
+ * Status badges. The record stays module-level (it is static styling), but
50
+ * it carries a `labelKey` instead of English: the label is resolved from
51
+ * `useMCPUIStrings()` at render time, so a host provider localizes it.
52
+ * Same technique as `SOURCE_BADGES` in `FormFieldRenderer`.
53
+ */
54
+ type StatusLabelKey =
55
+ | 'statusLoading'
56
+ | 'statusActionAvailable'
57
+ | 'statusYourTurn'
58
+ | 'statusProcessing'
59
+ | 'statusComplete'
60
+ | 'scratchpadError'
61
+
62
+ const STATUS_BADGES: Record<
63
+ ScratchpadState['status'],
64
+ { labelKey: StatusLabelKey; class: string }
65
+ > = {
66
+ loading: { labelKey: 'statusLoading', class: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400' },
67
+ ready: { labelKey: 'statusActionAvailable', class: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-400' },
68
+ waiting_human: { labelKey: 'statusYourTurn', class: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 animate-pulse' },
69
+ processing: { labelKey: 'statusProcessing', class: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400' },
70
+ complete: { labelKey: 'statusComplete', class: 'bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400' },
71
+ // The error badge reuses the existing `scratchpadError` key ('Error').
72
+ error: { labelKey: 'scratchpadError', class: 'bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400' },
54
73
  }
55
74
 
56
75
  export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
76
+ const strings = useMCPUIStrings()
57
77
  const [collapsed, setCollapsed] = createSignal(false)
58
78
  const [localPreview, setLocalPreview] = createSignal<ScratchpadState['preview']>(undefined)
59
79
  const [loadingAction, setLoadingAction] = createSignal<string | null>(null)
@@ -198,9 +218,9 @@ export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
198
218
  </Show>
199
219
  </div>
200
220
  <div class="flex items-center gap-2">
201
- <span class={`px-2 py-0.5 text-xs font-medium rounded-full ${badge().class}`}>{badge().label}</span>
221
+ <span class={`px-2 py-0.5 text-xs font-medium rounded-full ${badge().class}`}>{strings[badge().labelKey]}</span>
202
222
  <Show when={isClosable() && props.onClose}>
203
- <button onClick={(e) => { e.stopPropagation(); props.onClose?.() }} class="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" aria-label="Close">
223
+ <button onClick={(e) => { e.stopPropagation(); props.onClose?.() }} class="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" aria-label={strings.scratchpadClose}>
204
224
  <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
205
225
  </button>
206
226
  </Show>
@@ -275,8 +295,8 @@ export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
275
295
  <Show when={preview()!.count === 0} fallback={
276
296
  <>
277
297
  <div class="flex items-center gap-2 mb-2">
278
- <span class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Preview</span>
279
- <span class="px-1.5 py-0.5 text-xs font-bold bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded">{preview()!.count.toLocaleString()}</span>
298
+ <span class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">{strings.scratchpadPreview}</span>
299
+ <span class="px-1.5 py-0.5 text-xs font-bold bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded">{preview()!.count.toLocaleString(strings.locale)}</span>
280
300
  </div>
281
301
  <p class="text-sm text-gray-700 dark:text-gray-300">{preview()!.summary}</p>
282
302
  <Show when={preview()!.rows && preview()!.rows!.length > 0}>
@@ -291,8 +311,8 @@ export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
291
311
  }>
292
312
  <div class="flex flex-col items-center gap-2 py-4 text-center">
293
313
  <span class="text-2xl">&#128269;</span>
294
- <p class="text-sm text-gray-500 dark:text-gray-400">No results for these filters</p>
295
- <button type="button" onClick={() => props.onAction?.('refine_filters')} class="px-3 py-1.5 text-xs font-medium text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors">Modify filters</button>
314
+ <p class="text-sm text-gray-500 dark:text-gray-400">{strings.scratchpadNoResults}</p>
315
+ <button type="button" onClick={() => props.onAction?.('refine_filters')} class="px-3 py-1.5 text-xs font-medium text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors">{strings.scratchpadModifyFilters}</button>
296
316
  </div>
297
317
  </Show>
298
318
  </div>
@@ -306,7 +326,9 @@ export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
306
326
  <div class="flex-1">
307
327
  <p class="font-medium">{props.state.error!.message}</p>
308
328
  <Show when={props.state.error!.code}>
309
- <p class="text-xs text-red-500 dark:text-red-500 mt-0.5">Code: {props.state.error!.code}</p>
329
+ <p class="text-xs text-red-500 dark:text-red-500 mt-0.5">
330
+ {formatMCPUIString(strings.scratchpadErrorCode, { code: props.state.error!.code! })}
331
+ </p>
310
332
  </Show>
311
333
  </div>
312
334
  </div>
@@ -314,13 +336,13 @@ export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
314
336
  <Show when={props.state.error!.retryable !== false}>
315
337
  <button type="button" onClick={() => props.onRetry?.()}
316
338
  class="px-3 py-1.5 text-sm font-medium rounded-lg bg-red-600 text-white hover:bg-red-700 transition-colors flex items-center gap-1">
317
- &#128260; Retry
339
+ &#128260; {strings.retry}
318
340
  </button>
319
341
  </Show>
320
342
  <Show when={props.onClose}>
321
343
  <button type="button" onClick={() => props.onClose?.()}
322
344
  class="px-3 py-1.5 text-sm font-medium rounded-lg border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
323
- Close
345
+ {strings.scratchpadClose}
324
346
  </button>
325
347
  </Show>
326
348
  </div>
@@ -336,7 +358,7 @@ export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
336
358
  class="w-full px-4 py-2.5 text-sm font-semibold rounded-lg text-white bg-blue-600 hover:bg-blue-700 transition-colors flex items-center justify-center gap-2"
337
359
  >
338
360
  <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
339
- Search
361
+ {strings.scratchpadSearch}
340
362
  </button>
341
363
  </div>
342
364
  </Show>
@@ -427,6 +449,7 @@ const InteractiveFilterSection: Component<{
427
449
  filters: Record<string, string | string[]>
428
450
  onFilterChange?: (filters: Record<string, string | string[]>) => void
429
451
  }> = (props) => {
452
+ const strings = useMCPUIStrings()
430
453
  const [editingKey, setEditingKey] = createSignal<string | null>(null)
431
454
  const [editValue, setEditValue] = createSignal('')
432
455
 
@@ -477,7 +500,7 @@ const InteractiveFilterSection: Component<{
477
500
  <span class="text-blue-500 dark:text-blue-400">{def()?.label || key}:</span> {Array.isArray(value()) ? (value() as string[]).join(', ') : String(value())}
478
501
  </button>
479
502
  <Show when={props.onFilterChange}>
480
- <button type="button" onClick={() => removeFilter(key)} class="ml-0.5 hover:text-blue-900 dark:hover:text-blue-100" aria-label={`Remove ${key}`}>&times;</button>
503
+ <button type="button" onClick={() => removeFilter(key)} class="ml-0.5 hover:text-blue-900 dark:hover:text-blue-100" aria-label={formatMCPUIString(strings.removeItem, { name: key })}>&times;</button>
481
504
  </Show>
482
505
  </span>
483
506
  </Show>
@@ -489,8 +512,8 @@ const InteractiveFilterSection: Component<{
489
512
  <form onSubmit={(e) => { e.preventDefault(); setFilter(key, editValue()) }} class="flex gap-1">
490
513
  <input type="text" value={editValue()} onInput={(e) => setEditValue(e.currentTarget.value)} placeholder={def()?.placeholder || key} autofocus
491
514
  class="flex-1 px-2 py-1 text-sm border border-gray-200 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 outline-none" />
492
- <button type="submit" class="px-2 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700">OK</button>
493
- <button type="button" onClick={() => setEditingKey(null)} class="px-2 py-1 text-xs text-gray-500 hover:text-gray-700">Cancel</button>
515
+ <button type="submit" class="px-2 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700">{strings.ok}</button>
516
+ <button type="button" onClick={() => setEditingKey(null)} class="px-2 py-1 text-xs text-gray-500 hover:text-gray-700">{strings.cancel}</button>
494
517
  </form>
495
518
  }>
496
519
  <div class="max-h-48 overflow-y-auto">
@@ -506,7 +529,7 @@ const InteractiveFilterSection: Component<{
506
529
  )}
507
530
  </For>
508
531
  </div>
509
- <button type="button" onClick={() => setEditingKey(null)} class="mt-1 w-full px-2 py-1 text-xs text-gray-500 hover:text-gray-700 text-center">Cancel</button>
532
+ <button type="button" onClick={() => setEditingKey(null)} class="mt-1 w-full px-2 py-1 text-xs text-gray-500 hover:text-gray-700 text-center">{strings.cancel}</button>
510
533
  </Show>
511
534
  </div>
512
535
  </Show>
@@ -515,7 +538,7 @@ const InteractiveFilterSection: Component<{
515
538
  }}
516
539
  </For>
517
540
  <Show when={allKeys().length === 0}>
518
- <p class="text-xs text-gray-400 italic">No filters</p>
541
+ <p class="text-xs text-gray-400 italic">{strings.scratchpadNoFilters}</p>
519
542
  </Show>
520
543
  </div>
521
544
  )
@@ -530,13 +553,14 @@ const EmbeddedFormSection: Component<{
530
553
  onSubmit?: (sectionId: string, values: Record<string, unknown>) => void
531
554
  debugTrace?: boolean
532
555
  }> = (props) => {
556
+ const strings = useMCPUIStrings()
533
557
  const [dynamicOptions, setDynamicOptions] = createSignal<Record<string, Array<{ label: string; value: string }>>>({})
534
558
 
535
559
  const config = () => {
536
560
  const c = props.content as any
537
561
  return {
538
562
  fields: c?.fields || [],
539
- submitLabel: c?.submitLabel || 'Submit',
563
+ submitLabel: c?.submitLabel || strings.submit,
540
564
  autoSubmitDelay: c?.autoSubmitDelay as number | undefined,
541
565
  }
542
566
  }
@@ -689,7 +713,7 @@ const EmbeddedFormSection: Component<{
689
713
  </span>
690
714
  <span class="text-blue-600 dark:text-blue-300">{countdown()}s...</span>
691
715
  <button type="button" onClick={() => { setExpanded(true); cancelCountdown(); setUserInteracted(true) }}
692
- class="text-blue-600 dark:text-blue-400 underline text-xs">Modifier</button>
716
+ class="text-blue-600 dark:text-blue-400 underline text-xs">{strings.scratchpadEdit}</button>
693
717
  <button type="button" onClick={() => { cancelCountdown(); setUserInteracted(true) }}
694
718
  class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">&times;</button>
695
719
  </div>
@@ -698,7 +722,10 @@ const EmbeddedFormSection: Component<{
698
722
  {/* Proposal 3: prefill summary */}
699
723
  <Show when={prefillSummary().prefilled > 0}>
700
724
  <p class="text-xs text-gray-500 dark:text-gray-400">
701
- {prefillSummary().prefilled} champ{prefillSummary().prefilled > 1 ? 's' : ''} pré-rempli{prefillSummary().prefilled > 1 ? 's' : ''} sur {prefillSummary().total}
725
+ {formatMCPUIString(
726
+ prefillSummary().prefilled === 1 ? strings.formPrefilledOne : strings.formPrefilledMany,
727
+ { count: prefillSummary().prefilled, total: prefillSummary().total }
728
+ )}
702
729
  </p>
703
730
  </Show>
704
731
  <For each={config().fields}>
@@ -714,14 +741,17 @@ const EmbeddedFormSection: Component<{
714
741
  <Show when={countdown() != null}>
715
742
  <div class="flex items-center gap-3 p-2 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md text-sm">
716
743
  <span class="text-blue-700 dark:text-blue-300">
717
- {config().submitLabel} in {countdown()}s...
744
+ {formatMCPUIString(strings.formSubmitCountdown, {
745
+ label: config().submitLabel,
746
+ seconds: countdown()!,
747
+ })}
718
748
  </span>
719
749
  <button
720
750
  type="button"
721
751
  onClick={() => { cancelCountdown(); setUserInteracted(true) }}
722
752
  class="text-blue-600 dark:text-blue-400 underline hover:text-blue-800 dark:hover:text-blue-200"
723
753
  >
724
- Cancel
754
+ {strings.cancel}
725
755
  </button>
726
756
  </div>
727
757
  </Show>
@@ -758,6 +788,7 @@ const FormDebugTrace: Component<{
758
788
  userInteracted: boolean
759
789
  rawContent: unknown
760
790
  }> = (props) => {
791
+ const strings = useMCPUIStrings()
761
792
  const [open, setOpen] = createSignal(false)
762
793
  const [showRaw, setShowRaw] = createSignal(false)
763
794
 
@@ -848,7 +879,7 @@ const FormDebugTrace: Component<{
848
879
 
849
880
  <div class="pt-1 border-t border-gray-200 dark:border-gray-600">
850
881
  <button type="button" onClick={() => setShowRaw(!showRaw())} class="text-blue-500 hover:text-blue-700 underline">
851
- {showRaw() ? 'Hide' : 'Show'} raw SSE payload
882
+ {showRaw() ? strings.scratchpadHideRaw : strings.scratchpadShowRaw}
852
883
  </button>
853
884
  <Show when={showRaw()}>
854
885
  <pre class="mt-1 p-2 bg-gray-50 dark:bg-gray-900 rounded max-h-48 overflow-auto text-[10px]">
@@ -869,6 +900,7 @@ const EnrichedStepsSection: Component<{
869
900
  onAction?: (action: string, data?: unknown) => void
870
901
  onFilterChange?: (filters: Record<string, string | string[]>) => void
871
902
  }> = (props) => {
903
+ const strings = useMCPUIStrings()
872
904
  const stepsData = () => {
873
905
  const c = props.content as any
874
906
  return { steps: c?.steps || [], currentStep: c?.currentStep ?? 0 }
@@ -911,7 +943,7 @@ const EnrichedStepsSection: Component<{
911
943
  <div class="flex justify-end">
912
944
  <button type="button" onClick={() => props.onAction?.('next_step', { step: stepsData().currentStep })}
913
945
  class="px-3 py-1.5 text-sm font-medium text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors flex items-center gap-1">
914
- Next <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
946
+ {strings.scratchpadNextStep} <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
915
947
  </button>
916
948
  </div>
917
949
  </Show>
@@ -925,6 +957,7 @@ const ActionSection: Component<{
925
957
  content: unknown
926
958
  onAction?: (action: string, data?: unknown) => void
927
959
  }> = (props) => {
960
+ const strings = useMCPUIStrings()
928
961
  const data = () => {
929
962
  const c = props.content as any
930
963
  if (Array.isArray(c)) return { actions: c, title: undefined, preview: undefined, validation: undefined }
@@ -942,7 +975,7 @@ const ActionSection: Component<{
942
975
  {(preview) => (
943
976
  <div class="mb-2 p-2 rounded bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-xs text-gray-600 dark:text-gray-400">
944
977
  <Show when={preview().count != null}>
945
- <span class="font-medium text-gray-800 dark:text-gray-200">{preview().count}</span> items
978
+ <span class="font-medium text-gray-800 dark:text-gray-200">{preview().count}</span> {strings.scratchpadItems}
946
979
  </Show>
947
980
  <Show when={preview().summary}>
948
981
  <span class="ml-1">&mdash; {preview().summary}</span>
@@ -957,10 +990,16 @@ const ActionSection: Component<{
957
990
  'text-amber-600 dark:text-amber-400': data().validation.confidence >= 0.5 && data().validation.confidence < 0.8,
958
991
  'text-red-600 dark:text-red-400': data().validation.confidence < 0.5,
959
992
  }}>
960
- {Math.round(data().validation.confidence * 100)}% verified
993
+ {formatMCPUIString(strings.verifiedConfidence, {
994
+ pct: Math.round(data().validation.confidence * 100),
995
+ })}
961
996
  </span>
962
997
  <Show when={data().validation.hallucinated?.length > 0}>
963
- <span class="text-amber-600">({data().validation.hallucinated.length} unverified)</span>
998
+ <span class="text-amber-600">
999
+ {formatMCPUIString(strings.verifiedUnverifiedCount, {
1000
+ count: data().validation.hallucinated.length,
1001
+ })}
1002
+ </span>
964
1003
  </Show>
965
1004
  </div>
966
1005
  </Show>
@@ -1039,20 +1078,24 @@ const FeedbackSection: Component<{
1039
1078
  content: unknown
1040
1079
  onAction?: (action: string, data?: unknown) => void
1041
1080
  }> = (props) => {
1081
+ const strings = useMCPUIStrings()
1042
1082
  const [comment, setComment] = createSignal('')
1043
1083
  const [showComment, setShowComment] = createSignal(false)
1044
1084
  const [submitted, setSubmitted] = createSignal<string | null>(null)
1085
+ // The badge shows the chosen option's LABEL (payload or localized default),
1086
+ // never its machine value ('approve' / 'reject' / 'comment').
1087
+ const [submittedLabel, setSubmittedLabel] = createSignal('')
1045
1088
  const data = () => {
1046
1089
  const c = props.content as any
1047
1090
  const options = c?.options || [
1048
- { value: c?.approve?.value || 'approve', label: c?.approve?.label || 'Yes', icon: '\uD83D\uDC4D', variant: 'primary' },
1049
- { value: c?.reject?.value || 'reject', label: c?.reject?.label || 'No', icon: '\uD83D\uDC4E' },
1091
+ { value: c?.approve?.value || 'approve', label: c?.approve?.label || strings.yes, icon: '\uD83D\uDC4D', variant: 'primary' },
1092
+ { value: c?.reject?.value || 'reject', label: c?.reject?.label || strings.no, icon: '\uD83D\uDC4E' },
1050
1093
  ]
1051
1094
  return {
1052
1095
  question: c?.question || '',
1053
1096
  options: options as Array<{ value: string; label: string; icon?: string; variant?: string; needsComment?: boolean }>,
1054
1097
  allowFreeText: c?.allowFreeText ?? c?.allowComment ?? false,
1055
- placeholder: c?.placeholder || c?.commentPlaceholder || 'Add a comment...',
1098
+ placeholder: c?.placeholder || c?.commentPlaceholder || strings.scratchpadCommentPlaceholder,
1056
1099
  // v4.1.0: per-step feedback
1057
1100
  agentId: c?.agentId as string | undefined,
1058
1101
  stepId: c?.stepId as string | undefined,
@@ -1065,6 +1108,7 @@ const FeedbackSection: Component<{
1065
1108
  return
1066
1109
  }
1067
1110
  setSubmitted(option.value)
1111
+ setSubmittedLabel(option.label ?? '')
1068
1112
  const payload = {
1069
1113
  option: option.value,
1070
1114
  comment: comment(),
@@ -1089,7 +1133,7 @@ const FeedbackSection: Component<{
1089
1133
  'text-red-600': submitted() === 'reject',
1090
1134
  'text-blue-600': submitted() !== 'approve' && submitted() !== 'reject',
1091
1135
  }}>
1092
- {submitted() === 'approve' ? '\u2705' : submitted() === 'reject' ? '\u274C' : '\uD83D\uDCAC'} {submitted()}
1136
+ {submitted() === 'approve' ? '\u2705' : submitted() === 'reject' ? '\u274C' : '\uD83D\uDCAC'} {submittedLabel()}
1093
1137
  </span>
1094
1138
  <Show when={comment()}>
1095
1139
  <span class="italic">&mdash; {comment()}</span>
@@ -1119,8 +1163,8 @@ const FeedbackSection: Component<{
1119
1163
  <input type="text" value={comment()} onInput={(e) => setComment(e.currentTarget.value)}
1120
1164
  placeholder={data().placeholder} autofocus={showComment()}
1121
1165
  class="flex-1 px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 outline-none" />
1122
- <button type="button" on:click={() => { setSubmitted('comment'); props.onAction?.('feedback', { option: 'comment', comment: comment(), agentId: data().agentId, stepId: data().stepId }) }}
1123
- class="px-3 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700">Send</button>
1166
+ <button type="button" on:click={() => { setSubmitted('comment'); setSubmittedLabel(''); props.onAction?.('feedback', { option: 'comment', comment: comment(), agentId: data().agentId, stepId: data().stepId }) }}
1167
+ class="px-3 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700">{strings.scratchpadSend}</button>
1124
1168
  </div>
1125
1169
  </Show>
1126
1170
  </Show>
@@ -1134,6 +1178,7 @@ const PromptSection: Component<{
1134
1178
  content: unknown
1135
1179
  onAction?: (action: string, data?: unknown) => void
1136
1180
  }> = (props) => {
1181
+ const strings = useMCPUIStrings()
1137
1182
  const data = () => {
1138
1183
  const c = props.content as any
1139
1184
  return {
@@ -1162,13 +1207,13 @@ const PromptSection: Component<{
1162
1207
  </div>
1163
1208
  <Show when={data().plan}>
1164
1209
  <div class="mt-2 px-3 py-2 bg-blue-50 dark:bg-blue-900/10 rounded-lg text-sm text-blue-700 dark:text-blue-300">
1165
- <span class="font-medium">Plan:</span> {data().plan}
1210
+ <span class="font-medium">{strings.scratchpadPlan}</span> {data().plan}
1166
1211
  </div>
1167
1212
  </Show>
1168
1213
  <Show when={data().editable}>
1169
1214
  <button type="button" onClick={() => props.onAction?.('edit_prompt', data())}
1170
1215
  class="text-xs text-blue-600 dark:text-blue-400 hover:underline flex items-center gap-1">
1171
- &#9998; Modify
1216
+ &#9998; {strings.scratchpadModify}
1172
1217
  </button>
1173
1218
  </Show>
1174
1219
  </div>
@@ -1251,10 +1296,11 @@ const ErrorSectionRenderer: Component<{
1251
1296
  content: unknown
1252
1297
  onAction?: (action: string, data?: unknown) => void
1253
1298
  }> = (props) => {
1299
+ const strings = useMCPUIStrings()
1254
1300
  const [showDetails, setShowDetails] = createSignal(false)
1255
1301
  const data = () => {
1256
1302
  const c = props.content as any
1257
- const d = { message: c?.message || 'Error', severity: c?.severity || 'error', retryAction: c?.retryAction, retryLabel: c?.retryLabel || 'Retry', details: c?.details, timestamp: c?.timestamp }
1303
+ const d = { message: c?.message || strings.scratchpadError, severity: c?.severity || 'error', retryAction: c?.retryAction, retryLabel: c?.retryLabel || strings.retry, details: c?.details, timestamp: c?.timestamp }
1258
1304
  // DX1 Etape 8
1259
1305
  console.info(`%c[MCP-UI] Error section rendered%c severity=${d.severity} retry=${!!d.retryAction}`, 'color: #ef4444; font-weight: bold', 'color: inherit')
1260
1306
  return d
@@ -1272,7 +1318,7 @@ const ErrorSectionRenderer: Component<{
1272
1318
  <button type="button" onClick={() => props.onAction?.(data().retryAction!)} class={`px-2 py-1 text-xs font-medium rounded ${isWarning() ? 'bg-amber-600 text-white hover:bg-amber-700' : 'bg-red-600 text-white hover:bg-red-700'} transition-colors`}>&#128260; {data().retryLabel}</button>
1273
1319
  </Show>
1274
1320
  <Show when={data().details}>
1275
- <button type="button" onClick={() => setShowDetails(!showDetails())} class="px-2 py-1 text-xs opacity-70 hover:opacity-100">&#9654; Details</button>
1321
+ <button type="button" onClick={() => setShowDetails(!showDetails())} class="px-2 py-1 text-xs opacity-70 hover:opacity-100">&#9654; {strings.scratchpadDetails}</button>
1276
1322
  </Show>
1277
1323
  </div>
1278
1324
  <Show when={showDetails() && data().details}>
@@ -1287,9 +1333,10 @@ const ErrorSectionRenderer: Component<{
1287
1333
  // ─── Source Card Section (F9) ────────────────────────────────
1288
1334
 
1289
1335
  const SourceCardSection: Component<{ content: unknown }> = (props) => {
1336
+ const strings = useMCPUIStrings()
1290
1337
  const data = () => {
1291
1338
  const c = props.content as any
1292
- return { name: c?.name || 'Source', status: c?.status || 'available', capabilities: c?.capabilities || [], latency_ms: c?.latency_ms, freshness: c?.freshness, row_count: c?.row_count }
1339
+ return { name: c?.name || strings.scratchpadSource, status: c?.status || 'available', capabilities: c?.capabilities || [], latency_ms: c?.latency_ms, freshness: c?.freshness, row_count: c?.row_count }
1293
1340
  }
1294
1341
  const statusIcon = () => ({ queried: '✅', available: '📦', error: '❌' } as Record<string, string>)[data().status] || '📦'
1295
1342
 
@@ -1298,7 +1345,11 @@ const SourceCardSection: Component<{ content: unknown }> = (props) => {
1298
1345
  <div class="flex items-center justify-between mb-1.5">
1299
1346
  <span class="text-sm font-medium text-gray-900 dark:text-white">{statusIcon()} {data().name}</span>
1300
1347
  <Show when={data().row_count !== undefined}>
1301
- <span class="text-xs font-bold text-blue-600 dark:text-blue-400">{data().row_count?.toLocaleString()} results</span>
1348
+ <span class="text-xs font-bold text-blue-600 dark:text-blue-400">
1349
+ {formatMCPUIString(strings.scratchpadResultCount, {
1350
+ count: data().row_count?.toLocaleString(strings.locale) ?? '',
1351
+ })}
1352
+ </span>
1302
1353
  </Show>
1303
1354
  </div>
1304
1355
  <div class="flex flex-wrap gap-1.5 mb-1">
@@ -36,7 +36,11 @@
36
36
  */
37
37
 
38
38
  import { Show, For, createSignal, onMount } from 'solid-js'
39
- import { useStreamingUI, type UseStreamingUIOptions } from '../hooks/useStreamingUI'
39
+ import {
40
+ useStreamingUI,
41
+ type StreamingUIMessages,
42
+ type UseStreamingUIOptions,
43
+ } from '../hooks/useStreamingUI'
40
44
  import type { UIComponent, RendererError } from '../types'
41
45
  import { UIResourceRenderer, type ValidationErrorMode } from './UIResourceRenderer'
42
46
  import { useMCPUIStrings } from '../context/MCPUIStringsContext'
@@ -73,6 +77,21 @@ function asFullWidth(component: UIComponent): UIComponent {
73
77
  }
74
78
 
75
79
  export function StreamingUIRenderer(props: StreamingUIRendererProps) {
80
+ const strings = useMCPUIStrings()
81
+ // Getters: each message is read from the context when the hook produces it.
82
+ // An explicit `props.messages` entry wins over the context.
83
+ const messages: StreamingUIMessages = {
84
+ get initializing() { return props.messages?.initializing ?? strings.streamInitializing },
85
+ get connecting() { return props.messages?.connecting ?? strings.streamConnecting },
86
+ get loadingComponent() { return props.messages?.loadingComponent ?? strings.streamLoadingComponent },
87
+ get dashboardLoaded() { return props.messages?.dashboardLoaded ?? strings.streamDashboardLoaded },
88
+ get errorProgress() { return props.messages?.errorProgress ?? strings.streamErrorProgress },
89
+ get connectionFailed() { return props.messages?.connectionFailed ?? strings.streamConnectionFailed },
90
+ get requestFailed() { return props.messages?.requestFailed ?? strings.streamRequestFailed },
91
+ get emptyResponse() { return props.messages?.emptyResponse ?? strings.streamEmptyResponse },
92
+ get serverSide() { return props.messages?.serverSide ?? strings.streamServerSide },
93
+ get unknownError() { return props.messages?.unknownError ?? strings.streamUnknownError },
94
+ }
76
95
  const { components, isLoading, isStreaming, error, progress, metadata, startStreaming } =
77
96
  useStreamingUI({
78
97
  query: props.query,
@@ -82,9 +101,9 @@ export function StreamingUIRenderer(props: StreamingUIRendererProps) {
82
101
  onComplete: props.onComplete,
83
102
  onError: props.onError,
84
103
  onComponentReceived: props.onComponentReceived,
104
+ messages,
85
105
  })
86
106
 
87
- const strings = useMCPUIStrings()
88
107
  const [animatingComponents, setAnimatingComponents] = createSignal<Set<string>>(new Set())
89
108
 
90
109
  // Track new components for animation
@@ -156,7 +175,9 @@ export function StreamingUIRenderer(props: StreamingUIRendererProps) {
156
175
  d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
157
176
  />
158
177
  </svg>
159
- <span class="font-medium text-error-primary">{error()?.error}</span>
178
+ <span class="font-medium text-error-primary">
179
+ {error()?.error === 'ssr' ? strings.streamServerSideTitle : error()?.error}
180
+ </span>
160
181
  </div>
161
182
  <p class="text-sm text-text-secondary">{error()?.message}</p>
162
183
 
@@ -217,31 +238,40 @@ export function StreamingUIRenderer(props: StreamingUIRendererProps) {
217
238
  <div class="mt-6 rounded-lg border border-border-subtle bg-surface-secondary p-4 text-sm text-text-secondary">
218
239
  <div class="grid grid-cols-2 gap-4 md:grid-cols-4">
219
240
  <div>
220
- <div class="font-medium text-text-primary">Provider</div>
241
+ <div class="font-medium text-text-primary">{strings.metaProvider}</div>
221
242
  <div>{metadata()?.provider}</div>
222
243
  </div>
223
244
  <div>
224
- <div class="font-medium text-text-primary">Model</div>
245
+ <div class="font-medium text-text-primary">{strings.metaModel}</div>
225
246
  <div>{metadata()?.model}</div>
226
247
  </div>
227
248
  <div>
228
- <div class="font-medium text-text-primary">Execution Time</div>
249
+ <div class="font-medium text-text-primary">{strings.metaExecutionTime}</div>
229
250
  <div>{metadata()?.executionTimeMs}ms</div>
230
251
  </div>
231
252
  <Show when={metadata()?.costUSD !== undefined}>
232
253
  <div>
233
- <div class="font-medium text-text-primary">Cost</div>
234
- <div>${metadata()?.costUSD?.toFixed(4)}</div>
254
+ <div class="font-medium text-text-primary">{strings.metaCost}</div>
255
+ <div>
256
+ {new Intl.NumberFormat(strings.locale, {
257
+ style: 'currency',
258
+ currency: 'USD',
259
+ currencyDisplay: 'narrowSymbol',
260
+ minimumFractionDigits: 4,
261
+ maximumFractionDigits: 4,
262
+ useGrouping: false,
263
+ }).format(metadata()?.costUSD ?? 0)}
264
+ </div>
235
265
  </div>
236
266
  </Show>
237
267
  <div>
238
- <div class="font-medium text-text-primary">TTFB</div>
268
+ <div class="font-medium text-text-primary">{strings.metaTtfb}</div>
239
269
  <div>{metadata()?.firstTokenMs}ms</div>
240
270
  </div>
241
271
  <Show when={metadata()?.cached}>
242
272
  <div>
243
- <div class="font-medium text-text-primary">Cached</div>
244
- <div class="text-success-primary">Yes</div>
273
+ <div class="font-medium text-text-primary">{strings.metaCached}</div>
274
+ <div class="text-success-primary">{strings.yes}</div>
245
275
  </div>
246
276
  </Show>
247
277
  </div>