sparkecoder 0.1.16 → 0.1.18

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 (216) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +15 -6
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +9 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/server/index.js +9 -2
  7. package/dist/server/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/web/.next/BUILD_ID +1 -1
  10. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  11. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  12. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  13. package/web/.next/standalone/web/.next/server/app/(main)/page.js +3 -3
  14. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  15. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  16. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js +3 -3
  17. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  18. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  19. package/web/.next/standalone/web/.next/server/app/_global-error/page.js +2 -2
  20. package/web/.next/standalone/web/.next/server/app/_global-error/page.js.nft.json +1 -1
  21. package/web/.next/standalone/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  22. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  23. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  25. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_not-found/page.js +3 -3
  30. package/web/.next/standalone/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +13 -12
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +13 -12
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +4 -3
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/api/config/route.js +1 -1
  41. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  42. package/web/.next/standalone/web/.next/server/app/api/health/route.js +1 -1
  43. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  44. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  45. package/web/.next/standalone/web/.next/server/app/index.rsc +20 -19
  46. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  47. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  48. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +20 -19
  49. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +4 -3
  51. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__36edac7c._.js +3 -0
  53. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__74ebc442._.js +3 -0
  54. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_1b669458._.js → 2374f_1d78db71._.js} +1 -1
  55. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_10a13bff._.js → 2374f_30f9df13._.js} +1 -1
  56. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_42ae2ff5._.js → 2374f_378282b1._.js} +1 -1
  57. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_21324e82._.js → 2374f_5de336d2._.js} +1 -1
  58. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ba57e34a._.js → 2374f_8825dcc9._.js} +1 -1
  59. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_47e888fe._.js → 2374f_9bf3c7f3._.js} +2 -2
  60. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_63823b79._.js → 2374f_bbc99511._.js} +1 -1
  61. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_9c15fcde._.js → 2374f_d94c2b70._.js} +1 -1
  62. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__0f6b5fa7._.js +3 -0
  63. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__e1f2996b._.js → [root-of-the-server]__513c6b45._.js} +2 -2
  64. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__da40fb08._.js → [root-of-the-server]__7f04455b._.js} +2 -2
  65. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__be5e2967._.js +3 -0
  66. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__10aab2ca._.js → [root-of-the-server]__c3a1e22c._.js} +2 -2
  67. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__d2ce4b79._.js +3 -0
  68. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__de58a952._.js +3 -0
  69. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__f18f92f4._.js +10 -0
  70. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_d3c9d897._.js → web_19b6934c._.js} +2 -2
  71. package/web/.next/standalone/web/.next/server/chunks/ssr/web_96bca05b._.js +1 -1
  72. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  73. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  74. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  75. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  76. package/web/.next/standalone/web/.next/static/chunks/25a97bfee12ea1f6.js +1 -0
  77. package/web/.next/standalone/web/.next/static/chunks/2f9f08f6c6276b0f.js +1 -0
  78. package/web/.next/{static/chunks/c7e9604ccdb5cb09.js → standalone/web/.next/static/chunks/5ec82ce8f3aabaf0.js} +3 -3
  79. package/web/.next/standalone/web/.next/static/static/chunks/25a97bfee12ea1f6.js +1 -0
  80. package/web/.next/standalone/web/.next/static/static/chunks/2f9f08f6c6276b0f.js +1 -0
  81. package/web/.next/standalone/web/.next/static/{chunks/c7e9604ccdb5cb09.js → static/chunks/5ec82ce8f3aabaf0.js} +3 -3
  82. package/web/.next/standalone/web/README.md +63 -0
  83. package/web/.next/standalone/web/components.json +22 -0
  84. package/web/.next/standalone/web/eslint.config.mjs +18 -0
  85. package/web/.next/standalone/web/next.config.ts +9 -0
  86. package/web/.next/standalone/web/package-lock.json +15061 -0
  87. package/web/.next/standalone/web/postcss.config.mjs +7 -0
  88. package/web/.next/standalone/web/runtime-config.json +3 -0
  89. package/web/.next/standalone/web/src/app/(main)/layout.tsx +22 -0
  90. package/web/.next/standalone/web/src/app/(main)/page.tsx +230 -0
  91. package/web/.next/standalone/web/src/app/(main)/session/[id]/page.tsx +64 -0
  92. package/web/.next/standalone/web/src/app/api/config/route.ts +106 -0
  93. package/web/.next/standalone/web/src/app/api/health/route.ts +63 -0
  94. package/web/.next/standalone/web/src/app/apple-icon.png +0 -0
  95. package/web/.next/standalone/web/src/app/favicon.ico +0 -0
  96. package/web/.next/standalone/web/src/app/globals.css +311 -0
  97. package/web/.next/standalone/web/src/app/icon.png +0 -0
  98. package/web/.next/standalone/web/src/app/layout.tsx +100 -0
  99. package/web/.next/standalone/web/src/app/opengraph-image.png +0 -0
  100. package/web/.next/standalone/web/src/app/twitter-image.png +0 -0
  101. package/web/.next/standalone/web/src/components/ai-elements/agent.tsx +141 -0
  102. package/web/.next/standalone/web/src/components/ai-elements/artifact.tsx +147 -0
  103. package/web/.next/standalone/web/src/components/ai-elements/attachments.tsx +421 -0
  104. package/web/.next/standalone/web/src/components/ai-elements/audio-player.tsx +231 -0
  105. package/web/.next/standalone/web/src/components/ai-elements/bash-tool.tsx +299 -0
  106. package/web/.next/standalone/web/src/components/ai-elements/canvas.tsx +22 -0
  107. package/web/.next/standalone/web/src/components/ai-elements/chain-of-thought.tsx +231 -0
  108. package/web/.next/standalone/web/src/components/ai-elements/checkpoint.tsx +71 -0
  109. package/web/.next/standalone/web/src/components/ai-elements/code-block.tsx +529 -0
  110. package/web/.next/standalone/web/src/components/ai-elements/commit.tsx +448 -0
  111. package/web/.next/standalone/web/src/components/ai-elements/confirmation.tsx +176 -0
  112. package/web/.next/standalone/web/src/components/ai-elements/connection.tsx +28 -0
  113. package/web/.next/standalone/web/src/components/ai-elements/context.tsx +408 -0
  114. package/web/.next/standalone/web/src/components/ai-elements/controls.tsx +18 -0
  115. package/web/.next/standalone/web/src/components/ai-elements/conversation.tsx +104 -0
  116. package/web/.next/standalone/web/src/components/ai-elements/edge.tsx +140 -0
  117. package/web/.next/standalone/web/src/components/ai-elements/environment-variables.tsx +295 -0
  118. package/web/.next/standalone/web/src/components/ai-elements/file-tree.tsx +258 -0
  119. package/web/.next/standalone/web/src/components/ai-elements/image.tsx +24 -0
  120. package/web/.next/standalone/web/src/components/ai-elements/inline-citation.tsx +287 -0
  121. package/web/.next/standalone/web/src/components/ai-elements/linter-tool.tsx +330 -0
  122. package/web/.next/standalone/web/src/components/ai-elements/load-skill-tool.tsx +215 -0
  123. package/web/.next/standalone/web/src/components/ai-elements/loader.tsx +96 -0
  124. package/web/.next/standalone/web/src/components/ai-elements/message.tsx +421 -0
  125. package/web/.next/standalone/web/src/components/ai-elements/mic-selector.tsx +370 -0
  126. package/web/.next/standalone/web/src/components/ai-elements/model-selector.tsx +211 -0
  127. package/web/.next/standalone/web/src/components/ai-elements/node.tsx +71 -0
  128. package/web/.next/standalone/web/src/components/ai-elements/open-in-chat.tsx +365 -0
  129. package/web/.next/standalone/web/src/components/ai-elements/package-info.tsx +233 -0
  130. package/web/.next/standalone/web/src/components/ai-elements/panel.tsx +15 -0
  131. package/web/.next/standalone/web/src/components/ai-elements/persona.tsx +270 -0
  132. package/web/.next/standalone/web/src/components/ai-elements/plan.tsx +142 -0
  133. package/web/.next/standalone/web/src/components/ai-elements/prompt-input.tsx +1263 -0
  134. package/web/.next/standalone/web/src/components/ai-elements/queue.tsx +274 -0
  135. package/web/.next/standalone/web/src/components/ai-elements/read-file-tool.tsx +251 -0
  136. package/web/.next/standalone/web/src/components/ai-elements/reasoning.tsx +198 -0
  137. package/web/.next/standalone/web/src/components/ai-elements/sandbox.tsx +131 -0
  138. package/web/.next/standalone/web/src/components/ai-elements/schema-display.tsx +458 -0
  139. package/web/.next/standalone/web/src/components/ai-elements/shimmer.tsx +64 -0
  140. package/web/.next/standalone/web/src/components/ai-elements/snippet.tsx +139 -0
  141. package/web/.next/standalone/web/src/components/ai-elements/sources.tsx +77 -0
  142. package/web/.next/standalone/web/src/components/ai-elements/speech-input.tsx +300 -0
  143. package/web/.next/standalone/web/src/components/ai-elements/stack-trace.tsx +482 -0
  144. package/web/.next/standalone/web/src/components/ai-elements/suggestion.tsx +60 -0
  145. package/web/.next/standalone/web/src/components/ai-elements/task.tsx +87 -0
  146. package/web/.next/standalone/web/src/components/ai-elements/terminal.tsx +261 -0
  147. package/web/.next/standalone/web/src/components/ai-elements/test-results.tsx +485 -0
  148. package/web/.next/standalone/web/src/components/ai-elements/todo-panel.tsx +178 -0
  149. package/web/.next/standalone/web/src/components/ai-elements/todo-tool.tsx +246 -0
  150. package/web/.next/standalone/web/src/components/ai-elements/tool.tsx +174 -0
  151. package/web/.next/standalone/web/src/components/ai-elements/toolbar.tsx +16 -0
  152. package/web/.next/standalone/web/src/components/ai-elements/transcription.tsx +124 -0
  153. package/web/.next/standalone/web/src/components/ai-elements/voice-selector.tsx +479 -0
  154. package/web/.next/standalone/web/src/components/ai-elements/web-preview.tsx +263 -0
  155. package/web/.next/standalone/web/src/components/ai-elements/write-file-tool.tsx +368 -0
  156. package/web/.next/standalone/web/src/components/api-init.tsx +21 -0
  157. package/web/.next/standalone/web/src/components/chat-interface.tsx +2074 -0
  158. package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +875 -0
  159. package/web/.next/standalone/web/src/components/ui/accordion.tsx +66 -0
  160. package/web/.next/standalone/web/src/components/ui/alert.tsx +66 -0
  161. package/web/.next/standalone/web/src/components/ui/avatar.tsx +109 -0
  162. package/web/.next/standalone/web/src/components/ui/badge.tsx +48 -0
  163. package/web/.next/standalone/web/src/components/ui/button-group.tsx +83 -0
  164. package/web/.next/standalone/web/src/components/ui/button.tsx +64 -0
  165. package/web/.next/standalone/web/src/components/ui/card.tsx +92 -0
  166. package/web/.next/standalone/web/src/components/ui/carousel.tsx +241 -0
  167. package/web/.next/standalone/web/src/components/ui/collapsible.tsx +33 -0
  168. package/web/.next/standalone/web/src/components/ui/command.tsx +184 -0
  169. package/web/.next/standalone/web/src/components/ui/dialog.tsx +158 -0
  170. package/web/.next/standalone/web/src/components/ui/dropdown-menu.tsx +257 -0
  171. package/web/.next/standalone/web/src/components/ui/hover-card.tsx +44 -0
  172. package/web/.next/standalone/web/src/components/ui/input-group.tsx +170 -0
  173. package/web/.next/standalone/web/src/components/ui/input.tsx +22 -0
  174. package/web/.next/standalone/web/src/components/ui/label.tsx +24 -0
  175. package/web/.next/standalone/web/src/components/ui/popover.tsx +89 -0
  176. package/web/.next/standalone/web/src/components/ui/progress.tsx +31 -0
  177. package/web/.next/standalone/web/src/components/ui/scroll-area.tsx +58 -0
  178. package/web/.next/standalone/web/src/components/ui/select.tsx +190 -0
  179. package/web/.next/standalone/web/src/components/ui/separator.tsx +28 -0
  180. package/web/.next/standalone/web/src/components/ui/sheet.tsx +143 -0
  181. package/web/.next/standalone/web/src/components/ui/sidebar.tsx +726 -0
  182. package/web/.next/standalone/web/src/components/ui/skeleton.tsx +13 -0
  183. package/web/.next/standalone/web/src/components/ui/switch.tsx +35 -0
  184. package/web/.next/standalone/web/src/components/ui/tabs.tsx +91 -0
  185. package/web/.next/standalone/web/src/components/ui/textarea.tsx +18 -0
  186. package/web/.next/standalone/web/src/components/ui/tooltip.tsx +61 -0
  187. package/web/.next/standalone/web/src/hooks/use-mobile.ts +19 -0
  188. package/web/.next/standalone/web/src/hooks/use-sessions.ts +28 -0
  189. package/web/.next/standalone/web/src/lib/api.ts +568 -0
  190. package/web/.next/standalone/web/src/lib/config.ts +178 -0
  191. package/web/.next/standalone/web/src/lib/utils.ts +6 -0
  192. package/web/.next/standalone/web/src/test/api.test.ts +125 -0
  193. package/web/.next/standalone/web/src/test/setup.ts +1 -0
  194. package/web/.next/standalone/web/tsconfig.json +43 -0
  195. package/web/.next/standalone/web/vitest.config.ts +17 -0
  196. package/web/.next/static/chunks/25a97bfee12ea1f6.js +1 -0
  197. package/web/.next/static/chunks/2f9f08f6c6276b0f.js +1 -0
  198. package/web/.next/{standalone/web/.next/static/static/chunks/c7e9604ccdb5cb09.js → static/chunks/5ec82ce8f3aabaf0.js} +3 -3
  199. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__7ba4776a._.js +0 -3
  200. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__d907af4e._.js +0 -3
  201. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__3a7ef2b7._.js +0 -3
  202. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__5369f47d._.js +0 -3
  203. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__c1f0d54f._.js +0 -3
  204. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__fd3d4d25._.js +0 -10
  205. package/web/.next/standalone/web/.next/static/chunks/85dcb2949e032468.js +0 -1
  206. package/web/.next/standalone/web/.next/static/static/chunks/85dcb2949e032468.js +0 -1
  207. package/web/.next/static/chunks/85dcb2949e032468.js +0 -1
  208. /package/web/.next/standalone/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_buildManifest.js +0 -0
  209. /package/web/.next/standalone/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_clientMiddlewareManifest.json +0 -0
  210. /package/web/.next/standalone/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_ssgManifest.js +0 -0
  211. /package/web/.next/standalone/web/.next/static/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_buildManifest.js +0 -0
  212. /package/web/.next/standalone/web/.next/static/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_clientMiddlewareManifest.json +0 -0
  213. /package/web/.next/standalone/web/.next/static/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_ssgManifest.js +0 -0
  214. /package/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_buildManifest.js +0 -0
  215. /package/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_clientMiddlewareManifest.json +0 -0
  216. /package/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_ssgManifest.js +0 -0
@@ -0,0 +1,421 @@
1
+ "use client";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import {
5
+ ButtonGroup,
6
+ ButtonGroupText,
7
+ } from "@/components/ui/button-group";
8
+ import {
9
+ Tooltip,
10
+ TooltipContent,
11
+ TooltipProvider,
12
+ TooltipTrigger,
13
+ } from "@/components/ui/tooltip";
14
+ import { cn } from "@/lib/utils";
15
+ import { cjk } from "@streamdown/cjk";
16
+ import { code } from "@streamdown/code";
17
+ import { math } from "@streamdown/math";
18
+ import { mermaid } from "@streamdown/mermaid";
19
+ import type { UIMessage } from "ai";
20
+ import { ChevronLeftIcon, ChevronRightIcon, ExternalLinkIcon, XIcon } from "lucide-react";
21
+ import type { ComponentProps, HTMLAttributes, ReactElement } from "react";
22
+ import { createContext, memo, useContext, useEffect, useState } from "react";
23
+ import { createPortal } from "react-dom";
24
+ import { Streamdown, type LinkSafetyModalProps } from "streamdown";
25
+
26
+ export type MessageProps = HTMLAttributes<HTMLDivElement> & {
27
+ from: UIMessage["role"];
28
+ };
29
+
30
+ export const Message = ({ className, from, ...props }: MessageProps) => (
31
+ <div
32
+ className={cn(
33
+ "group flex w-full max-w-[95%] flex-col gap-2 transition-all duration-200",
34
+ from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
35
+ className
36
+ )}
37
+ {...props}
38
+ />
39
+ );
40
+
41
+ export type MessageContentProps = HTMLAttributes<HTMLDivElement>;
42
+
43
+ export const MessageContent = ({
44
+ children,
45
+ className,
46
+ ...props
47
+ }: MessageContentProps) => (
48
+ <div
49
+ className={cn(
50
+ "is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
51
+ "group-[.is-user]:ml-auto group-[.is-user]:rounded-2xl group-[.is-user]:rounded-br-md group-[.is-user]:bg-primary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-primary-foreground group-[.is-user]:shadow-sm",
52
+ "group-[.is-assistant]:text-foreground",
53
+ className
54
+ )}
55
+ {...props}
56
+ >
57
+ {children}
58
+ </div>
59
+ );
60
+
61
+ export type MessageActionsProps = ComponentProps<"div">;
62
+
63
+ export const MessageActions = ({
64
+ className,
65
+ children,
66
+ ...props
67
+ }: MessageActionsProps) => (
68
+ <div className={cn("flex items-center gap-1", className)} {...props}>
69
+ {children}
70
+ </div>
71
+ );
72
+
73
+ export type MessageActionProps = ComponentProps<typeof Button> & {
74
+ tooltip?: string;
75
+ label?: string;
76
+ };
77
+
78
+ export const MessageAction = ({
79
+ tooltip,
80
+ children,
81
+ label,
82
+ variant = "ghost",
83
+ size = "icon-sm",
84
+ ...props
85
+ }: MessageActionProps) => {
86
+ const button = (
87
+ <Button size={size} type="button" variant={variant} {...props}>
88
+ {children}
89
+ <span className="sr-only">{label || tooltip}</span>
90
+ </Button>
91
+ );
92
+
93
+ if (tooltip) {
94
+ return (
95
+ <TooltipProvider>
96
+ <Tooltip>
97
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
98
+ <TooltipContent>
99
+ <p>{tooltip}</p>
100
+ </TooltipContent>
101
+ </Tooltip>
102
+ </TooltipProvider>
103
+ );
104
+ }
105
+
106
+ return button;
107
+ };
108
+
109
+ interface MessageBranchContextType {
110
+ currentBranch: number;
111
+ totalBranches: number;
112
+ goToPrevious: () => void;
113
+ goToNext: () => void;
114
+ branches: ReactElement[];
115
+ setBranches: (branches: ReactElement[]) => void;
116
+ }
117
+
118
+ const MessageBranchContext = createContext<MessageBranchContextType | null>(
119
+ null
120
+ );
121
+
122
+ const useMessageBranch = () => {
123
+ const context = useContext(MessageBranchContext);
124
+
125
+ if (!context) {
126
+ throw new Error(
127
+ "MessageBranch components must be used within MessageBranch"
128
+ );
129
+ }
130
+
131
+ return context;
132
+ };
133
+
134
+ export type MessageBranchProps = HTMLAttributes<HTMLDivElement> & {
135
+ defaultBranch?: number;
136
+ onBranchChange?: (branchIndex: number) => void;
137
+ };
138
+
139
+ export const MessageBranch = ({
140
+ defaultBranch = 0,
141
+ onBranchChange,
142
+ className,
143
+ ...props
144
+ }: MessageBranchProps) => {
145
+ const [currentBranch, setCurrentBranch] = useState(defaultBranch);
146
+ const [branches, setBranches] = useState<ReactElement[]>([]);
147
+
148
+ const handleBranchChange = (newBranch: number) => {
149
+ setCurrentBranch(newBranch);
150
+ onBranchChange?.(newBranch);
151
+ };
152
+
153
+ const goToPrevious = () => {
154
+ const newBranch =
155
+ currentBranch > 0 ? currentBranch - 1 : branches.length - 1;
156
+ handleBranchChange(newBranch);
157
+ };
158
+
159
+ const goToNext = () => {
160
+ const newBranch =
161
+ currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
162
+ handleBranchChange(newBranch);
163
+ };
164
+
165
+ const contextValue: MessageBranchContextType = {
166
+ currentBranch,
167
+ totalBranches: branches.length,
168
+ goToPrevious,
169
+ goToNext,
170
+ branches,
171
+ setBranches,
172
+ };
173
+
174
+ return (
175
+ <MessageBranchContext.Provider value={contextValue}>
176
+ <div
177
+ className={cn("grid w-full gap-2 [&>div]:pb-0", className)}
178
+ {...props}
179
+ />
180
+ </MessageBranchContext.Provider>
181
+ );
182
+ };
183
+
184
+ export type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;
185
+
186
+ export const MessageBranchContent = ({
187
+ children,
188
+ ...props
189
+ }: MessageBranchContentProps) => {
190
+ const { currentBranch, setBranches, branches } = useMessageBranch();
191
+ const childrenArray = Array.isArray(children) ? children : [children];
192
+
193
+ // Use useEffect to update branches when they change
194
+ useEffect(() => {
195
+ if (branches.length !== childrenArray.length) {
196
+ setBranches(childrenArray);
197
+ }
198
+ }, [childrenArray, branches, setBranches]);
199
+
200
+ return childrenArray.map((branch, index) => (
201
+ <div
202
+ className={cn(
203
+ "grid gap-2 overflow-hidden [&>div]:pb-0",
204
+ index === currentBranch ? "block" : "hidden"
205
+ )}
206
+ key={branch.key}
207
+ {...props}
208
+ >
209
+ {branch}
210
+ </div>
211
+ ));
212
+ };
213
+
214
+ export type MessageBranchSelectorProps = HTMLAttributes<HTMLDivElement> & {
215
+ from: UIMessage["role"];
216
+ };
217
+
218
+ export const MessageBranchSelector = ({
219
+ className,
220
+ from,
221
+ ...props
222
+ }: MessageBranchSelectorProps) => {
223
+ const { totalBranches } = useMessageBranch();
224
+
225
+ // Don't render if there's only one branch
226
+ if (totalBranches <= 1) {
227
+ return null;
228
+ }
229
+
230
+ return (
231
+ <ButtonGroup
232
+ className="[&>*:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md"
233
+ orientation="horizontal"
234
+ {...props}
235
+ />
236
+ );
237
+ };
238
+
239
+ export type MessageBranchPreviousProps = ComponentProps<typeof Button>;
240
+
241
+ export const MessageBranchPrevious = ({
242
+ children,
243
+ ...props
244
+ }: MessageBranchPreviousProps) => {
245
+ const { goToPrevious, totalBranches } = useMessageBranch();
246
+
247
+ return (
248
+ <Button
249
+ aria-label="Previous branch"
250
+ disabled={totalBranches <= 1}
251
+ onClick={goToPrevious}
252
+ size="icon-sm"
253
+ type="button"
254
+ variant="ghost"
255
+ {...props}
256
+ >
257
+ {children ?? <ChevronLeftIcon size={14} />}
258
+ </Button>
259
+ );
260
+ };
261
+
262
+ export type MessageBranchNextProps = ComponentProps<typeof Button>;
263
+
264
+ export const MessageBranchNext = ({
265
+ children,
266
+ className,
267
+ ...props
268
+ }: MessageBranchNextProps) => {
269
+ const { goToNext, totalBranches } = useMessageBranch();
270
+
271
+ return (
272
+ <Button
273
+ aria-label="Next branch"
274
+ disabled={totalBranches <= 1}
275
+ onClick={goToNext}
276
+ size="icon-sm"
277
+ type="button"
278
+ variant="ghost"
279
+ {...props}
280
+ >
281
+ {children ?? <ChevronRightIcon size={14} />}
282
+ </Button>
283
+ );
284
+ };
285
+
286
+ export type MessageBranchPageProps = HTMLAttributes<HTMLSpanElement>;
287
+
288
+ export const MessageBranchPage = ({
289
+ className,
290
+ ...props
291
+ }: MessageBranchPageProps) => {
292
+ const { currentBranch, totalBranches } = useMessageBranch();
293
+
294
+ return (
295
+ <ButtonGroupText
296
+ className={cn(
297
+ "border-none bg-transparent text-muted-foreground shadow-none",
298
+ className
299
+ )}
300
+ {...props}
301
+ >
302
+ {currentBranch + 1} of {totalBranches}
303
+ </ButtonGroupText>
304
+ );
305
+ };
306
+
307
+ export type MessageResponseProps = ComponentProps<typeof Streamdown>;
308
+
309
+ /**
310
+ * Portal-based link safety modal to avoid hydration errors.
311
+ * The default Streamdown modal renders divs inside paragraphs which is invalid HTML.
312
+ * Using a portal renders the modal at the document body level instead.
313
+ */
314
+ export const LinkSafetyModal = ({
315
+ url,
316
+ isOpen,
317
+ onClose,
318
+ onConfirm,
319
+ }: LinkSafetyModalProps) => {
320
+ const [mounted, setMounted] = useState(false);
321
+
322
+ useEffect(() => {
323
+ setMounted(true);
324
+ }, []);
325
+
326
+ if (!mounted || !isOpen) return null;
327
+
328
+ const hostname = (() => {
329
+ try {
330
+ return new URL(url).hostname;
331
+ } catch {
332
+ return url;
333
+ }
334
+ })();
335
+
336
+ return createPortal(
337
+ <div
338
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
339
+ onClick={onClose}
340
+ onKeyDown={(e) => e.key === "Escape" && onClose()}
341
+ >
342
+ <div
343
+ className="relative mx-4 max-w-md rounded-lg bg-background p-6 shadow-lg"
344
+ onClick={(e) => e.stopPropagation()}
345
+ >
346
+ <button
347
+ className="absolute right-3 top-3 rounded-sm p-1 text-muted-foreground hover:bg-muted hover:text-foreground"
348
+ onClick={onClose}
349
+ type="button"
350
+ >
351
+ <XIcon className="h-4 w-4" />
352
+ <span className="sr-only">Close</span>
353
+ </button>
354
+
355
+ <div className="flex flex-col gap-4">
356
+ <div className="flex items-center gap-2 text-lg font-semibold">
357
+ <ExternalLinkIcon className="h-5 w-5" />
358
+ <span>External Link</span>
359
+ </div>
360
+
361
+ <p className="text-sm text-muted-foreground">
362
+ You are about to visit an external website:
363
+ </p>
364
+
365
+ <p className="break-all rounded bg-muted px-3 py-2 text-sm font-mono">
366
+ {hostname}
367
+ </p>
368
+
369
+ <div className="flex justify-end gap-2">
370
+ <Button variant="outline" onClick={onClose}>
371
+ Cancel
372
+ </Button>
373
+ <Button onClick={onConfirm}>
374
+ Continue
375
+ </Button>
376
+ </div>
377
+ </div>
378
+ </div>
379
+ </div>,
380
+ document.body
381
+ );
382
+ };
383
+
384
+ export const MessageResponse = memo(
385
+ ({ className, linkSafety, ...props }: MessageResponseProps) => (
386
+ <Streamdown
387
+ className={cn(
388
+ "size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
389
+ className
390
+ )}
391
+ plugins={{ code, mermaid, math, cjk }}
392
+ linkSafety={{
393
+ enabled: linkSafety?.enabled ?? true,
394
+ onLinkCheck: linkSafety?.onLinkCheck,
395
+ renderModal: linkSafety?.renderModal ?? LinkSafetyModal,
396
+ }}
397
+ {...props}
398
+ />
399
+ ),
400
+ (prevProps, nextProps) => prevProps.children === nextProps.children
401
+ );
402
+
403
+ MessageResponse.displayName = "MessageResponse";
404
+
405
+ export type MessageToolbarProps = ComponentProps<"div">;
406
+
407
+ export const MessageToolbar = ({
408
+ className,
409
+ children,
410
+ ...props
411
+ }: MessageToolbarProps) => (
412
+ <div
413
+ className={cn(
414
+ "mt-4 flex w-full items-center justify-between gap-4",
415
+ className
416
+ )}
417
+ {...props}
418
+ >
419
+ {children}
420
+ </div>
421
+ );