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,529 @@
1
+ "use client";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ } from "@/components/ui/select";
11
+ import { cn } from "@/lib/utils";
12
+ import { CheckIcon, CopyIcon } from "lucide-react";
13
+ import {
14
+ type ComponentProps,
15
+ type CSSProperties,
16
+ createContext,
17
+ type HTMLAttributes,
18
+ memo,
19
+ useContext,
20
+ useEffect,
21
+ useMemo,
22
+ useRef,
23
+ useState,
24
+ } from "react";
25
+ import {
26
+ type BundledLanguage,
27
+ type BundledTheme,
28
+ createHighlighter,
29
+ type HighlighterGeneric,
30
+ type ThemedToken,
31
+ } from "shiki";
32
+
33
+ // Shiki uses bitflags for font styles: 1=italic, 2=bold, 4=underline
34
+ // biome-ignore lint/suspicious/noBitwiseOperators: shiki bitflag check
35
+ const isItalic = (fontStyle: number | undefined) => fontStyle && fontStyle & 1;
36
+ // biome-ignore lint/suspicious/noBitwiseOperators: shiki bitflag check
37
+ const isBold = (fontStyle: number | undefined) => fontStyle && fontStyle & 2;
38
+ const isUnderline = (fontStyle: number | undefined) =>
39
+ // biome-ignore lint/suspicious/noBitwiseOperators: shiki bitflag check
40
+ fontStyle && fontStyle & 4;
41
+
42
+ // Transform tokens to include pre-computed keys to avoid noArrayIndexKey lint
43
+ interface KeyedToken {
44
+ token: ThemedToken;
45
+ key: string;
46
+ }
47
+ interface KeyedLine {
48
+ tokens: KeyedToken[];
49
+ key: string;
50
+ }
51
+
52
+ const addKeysToTokens = (lines: ThemedToken[][]): KeyedLine[] =>
53
+ lines.map((line, lineIdx) => ({
54
+ key: `line-${lineIdx}`,
55
+ tokens: line.map((token, tokenIdx) => ({
56
+ token,
57
+ key: `line-${lineIdx}-${tokenIdx}`,
58
+ })),
59
+ }));
60
+
61
+ // Token rendering component
62
+ const TokenSpan = ({ token }: { token: ThemedToken }) => (
63
+ <span
64
+ className="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]"
65
+ style={
66
+ {
67
+ color: token.color,
68
+ backgroundColor: token.bgColor,
69
+ ...token.htmlStyle,
70
+ fontStyle: isItalic(token.fontStyle) ? "italic" : undefined,
71
+ fontWeight: isBold(token.fontStyle) ? "bold" : undefined,
72
+ textDecoration: isUnderline(token.fontStyle) ? "underline" : undefined,
73
+ } as CSSProperties
74
+ }
75
+ >
76
+ {token.content}
77
+ </span>
78
+ );
79
+
80
+ // Line rendering component
81
+ const LineSpan = ({
82
+ keyedLine,
83
+ showLineNumbers,
84
+ }: {
85
+ keyedLine: KeyedLine;
86
+ showLineNumbers: boolean;
87
+ }) => (
88
+ <span className={showLineNumbers ? LINE_NUMBER_CLASSES : "block"}>
89
+ {keyedLine.tokens.length === 0
90
+ ? "\n"
91
+ : keyedLine.tokens.map(({ token, key }) => (
92
+ <TokenSpan key={key} token={token} />
93
+ ))}
94
+ </span>
95
+ );
96
+
97
+ // Types
98
+ type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
99
+ code: string;
100
+ language: BundledLanguage;
101
+ showLineNumbers?: boolean;
102
+ };
103
+
104
+ interface TokenizedCode {
105
+ tokens: ThemedToken[][];
106
+ fg: string;
107
+ bg: string;
108
+ }
109
+
110
+ interface CodeBlockContextType {
111
+ code: string;
112
+ }
113
+
114
+ // Context
115
+ const CodeBlockContext = createContext<CodeBlockContextType>({
116
+ code: "",
117
+ });
118
+
119
+ // Highlighter cache (singleton per language)
120
+ const highlighterCache = new Map<
121
+ string,
122
+ Promise<HighlighterGeneric<BundledLanguage, BundledTheme>>
123
+ >();
124
+
125
+ // Token cache
126
+ const tokensCache = new Map<string, TokenizedCode>();
127
+
128
+ // Subscribers for async token updates
129
+ const subscribers = new Map<string, Set<(result: TokenizedCode) => void>>();
130
+
131
+ const getTokensCacheKey = (code: string, language: BundledLanguage) => {
132
+ const start = code.slice(0, 100);
133
+ const end = code.length > 100 ? code.slice(-100) : "";
134
+ return `${language}:${code.length}:${start}:${end}`;
135
+ };
136
+
137
+ const getHighlighter = (
138
+ language: BundledLanguage
139
+ ): Promise<HighlighterGeneric<BundledLanguage, BundledTheme>> => {
140
+ const cached = highlighterCache.get(language);
141
+ if (cached) {
142
+ return cached;
143
+ }
144
+
145
+ const highlighterPromise = createHighlighter({
146
+ themes: ["github-light", "github-dark"],
147
+ langs: [language],
148
+ });
149
+
150
+ highlighterCache.set(language, highlighterPromise);
151
+ return highlighterPromise;
152
+ };
153
+
154
+ // Create raw tokens for immediate display while highlighting loads
155
+ const createRawTokens = (code: string): TokenizedCode => ({
156
+ tokens: code.split("\n").map((line) =>
157
+ line === ""
158
+ ? []
159
+ : [
160
+ {
161
+ content: line,
162
+ color: "inherit",
163
+ } as ThemedToken,
164
+ ]
165
+ ),
166
+ fg: "inherit",
167
+ bg: "transparent",
168
+ });
169
+
170
+ // Synchronous highlight with callback for async results
171
+ export function highlightCode(
172
+ code: string,
173
+ language: BundledLanguage,
174
+ callback?: (result: TokenizedCode) => void
175
+ ): TokenizedCode | null {
176
+ const tokensCacheKey = getTokensCacheKey(code, language);
177
+
178
+ // Return cached result if available
179
+ const cached = tokensCache.get(tokensCacheKey);
180
+ if (cached) {
181
+ return cached;
182
+ }
183
+
184
+ // Subscribe callback if provided
185
+ if (callback) {
186
+ if (!subscribers.has(tokensCacheKey)) {
187
+ subscribers.set(tokensCacheKey, new Set());
188
+ }
189
+ subscribers.get(tokensCacheKey)?.add(callback);
190
+ }
191
+
192
+ // Start highlighting in background
193
+ getHighlighter(language)
194
+ .then((highlighter) => {
195
+ const availableLangs = highlighter.getLoadedLanguages();
196
+ const langToUse = availableLangs.includes(language) ? language : "text";
197
+
198
+ const result = highlighter.codeToTokens(code, {
199
+ lang: langToUse,
200
+ themes: {
201
+ light: "github-light",
202
+ dark: "github-dark",
203
+ },
204
+ });
205
+
206
+ const tokenized: TokenizedCode = {
207
+ tokens: result.tokens,
208
+ fg: result.fg ?? "",
209
+ bg: result.bg ?? "",
210
+ };
211
+
212
+ // Cache the result
213
+ tokensCache.set(tokensCacheKey, tokenized);
214
+
215
+ // Notify all subscribers
216
+ const subs = subscribers.get(tokensCacheKey);
217
+ if (subs) {
218
+ for (const sub of subs) {
219
+ sub(tokenized);
220
+ }
221
+ subscribers.delete(tokensCacheKey);
222
+ }
223
+ })
224
+ .catch((error) => {
225
+ console.error("Failed to highlight code:", error);
226
+ subscribers.delete(tokensCacheKey);
227
+ });
228
+
229
+ return null;
230
+ }
231
+
232
+ // Line number styles using CSS counters
233
+ const LINE_NUMBER_CLASSES = cn(
234
+ "block",
235
+ "before:content-[counter(line)]",
236
+ "before:inline-block",
237
+ "before:[counter-increment:line]",
238
+ "before:w-8",
239
+ "before:mr-4",
240
+ "before:text-right",
241
+ "before:text-muted-foreground/50",
242
+ "before:font-mono",
243
+ "before:select-none"
244
+ );
245
+
246
+ const CodeBlockBody = memo(
247
+ ({
248
+ tokenized,
249
+ showLineNumbers,
250
+ className,
251
+ }: {
252
+ tokenized: TokenizedCode;
253
+ showLineNumbers: boolean;
254
+ className?: string;
255
+ }) => {
256
+ const preStyle = useMemo(
257
+ () => ({
258
+ backgroundColor: tokenized.bg,
259
+ color: tokenized.fg,
260
+ }),
261
+ [tokenized.bg, tokenized.fg]
262
+ );
263
+
264
+ const keyedLines = useMemo(
265
+ () => addKeysToTokens(tokenized.tokens),
266
+ [tokenized.tokens]
267
+ );
268
+
269
+ return (
270
+ <pre
271
+ className={cn(
272
+ "dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)] m-0 p-4 text-sm",
273
+ className
274
+ )}
275
+ style={preStyle}
276
+ >
277
+ <code
278
+ className={cn(
279
+ "font-mono text-sm",
280
+ showLineNumbers && "[counter-increment:line_0] [counter-reset:line]"
281
+ )}
282
+ >
283
+ {keyedLines.map((keyedLine) => (
284
+ <LineSpan
285
+ key={keyedLine.key}
286
+ keyedLine={keyedLine}
287
+ showLineNumbers={showLineNumbers}
288
+ />
289
+ ))}
290
+ </code>
291
+ </pre>
292
+ );
293
+ },
294
+ (prevProps, nextProps) =>
295
+ prevProps.tokenized === nextProps.tokenized &&
296
+ prevProps.showLineNumbers === nextProps.showLineNumbers &&
297
+ prevProps.className === nextProps.className
298
+ );
299
+
300
+ export const CodeBlockContainer = ({
301
+ className,
302
+ language,
303
+ style,
304
+ ...props
305
+ }: HTMLAttributes<HTMLDivElement> & { language: string }) => (
306
+ <div
307
+ className={cn(
308
+ "group relative w-full overflow-hidden rounded-md border bg-background text-foreground",
309
+ className
310
+ )}
311
+ data-language={language}
312
+ style={{
313
+ contentVisibility: "auto",
314
+ containIntrinsicSize: "auto 200px",
315
+ ...style,
316
+ }}
317
+ {...props}
318
+ />
319
+ );
320
+
321
+ export const CodeBlockHeader = ({
322
+ children,
323
+ className,
324
+ ...props
325
+ }: HTMLAttributes<HTMLDivElement>) => (
326
+ <div
327
+ className={cn(
328
+ "flex items-center justify-between bg-muted/80 px-3 py-2 text-muted-foreground text-xs",
329
+ className
330
+ )}
331
+ {...props}
332
+ >
333
+ {children}
334
+ </div>
335
+ );
336
+
337
+ export const CodeBlockTitle = ({
338
+ children,
339
+ className,
340
+ ...props
341
+ }: HTMLAttributes<HTMLDivElement>) => (
342
+ <div className={cn("flex items-center gap-2", className)} {...props}>
343
+ {children}
344
+ </div>
345
+ );
346
+
347
+ export const CodeBlockFilename = ({
348
+ children,
349
+ className,
350
+ ...props
351
+ }: HTMLAttributes<HTMLSpanElement>) => (
352
+ <span className={cn("font-mono", className)} {...props}>
353
+ {children}
354
+ </span>
355
+ );
356
+
357
+ export const CodeBlockActions = ({
358
+ children,
359
+ className,
360
+ ...props
361
+ }: HTMLAttributes<HTMLDivElement>) => (
362
+ <div className={cn("flex items-center gap-2", className)} {...props}>
363
+ {children}
364
+ </div>
365
+ );
366
+
367
+ export const CodeBlockContent = ({
368
+ code,
369
+ language,
370
+ showLineNumbers = false,
371
+ }: {
372
+ code: string;
373
+ language: BundledLanguage;
374
+ showLineNumbers?: boolean;
375
+ }) => {
376
+ // Memoized raw tokens for immediate display
377
+ const rawTokens = useMemo(() => createRawTokens(code), [code]);
378
+
379
+ // Try to get cached result synchronously, otherwise use raw tokens
380
+ const [tokenized, setTokenized] = useState<TokenizedCode>(
381
+ () => highlightCode(code, language) ?? rawTokens
382
+ );
383
+
384
+ useEffect(() => {
385
+ // Reset to raw tokens when code changes (shows current code, not stale tokens)
386
+ setTokenized(highlightCode(code, language) ?? rawTokens);
387
+
388
+ // Subscribe to async highlighting result
389
+ highlightCode(code, language, setTokenized);
390
+ }, [code, language, rawTokens]);
391
+
392
+ return (
393
+ <div className="relative overflow-auto">
394
+ <CodeBlockBody showLineNumbers={showLineNumbers} tokenized={tokenized} />
395
+ </div>
396
+ );
397
+ };
398
+
399
+ export const CodeBlock = ({
400
+ code,
401
+ language,
402
+ showLineNumbers = false,
403
+ className,
404
+ children,
405
+ ...props
406
+ }: CodeBlockProps) => (
407
+ <CodeBlockContext.Provider value={{ code }}>
408
+ <CodeBlockContainer className={className} language={language} {...props}>
409
+ {children}
410
+ <CodeBlockContent
411
+ code={code}
412
+ language={language}
413
+ showLineNumbers={showLineNumbers}
414
+ />
415
+ </CodeBlockContainer>
416
+ </CodeBlockContext.Provider>
417
+ );
418
+
419
+ export type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {
420
+ onCopy?: () => void;
421
+ onError?: (error: Error) => void;
422
+ timeout?: number;
423
+ };
424
+
425
+ export const CodeBlockCopyButton = ({
426
+ onCopy,
427
+ onError,
428
+ timeout = 2000,
429
+ children,
430
+ className,
431
+ ...props
432
+ }: CodeBlockCopyButtonProps) => {
433
+ const [isCopied, setIsCopied] = useState(false);
434
+ const timeoutRef = useRef<number>(0);
435
+ const { code } = useContext(CodeBlockContext);
436
+
437
+ const copyToClipboard = async () => {
438
+ if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
439
+ onError?.(new Error("Clipboard API not available"));
440
+ return;
441
+ }
442
+
443
+ try {
444
+ if (!isCopied) {
445
+ await navigator.clipboard.writeText(code);
446
+ setIsCopied(true);
447
+ onCopy?.();
448
+ timeoutRef.current = window.setTimeout(
449
+ () => setIsCopied(false),
450
+ timeout
451
+ );
452
+ }
453
+ } catch (error) {
454
+ onError?.(error as Error);
455
+ }
456
+ };
457
+
458
+ useEffect(
459
+ () => () => {
460
+ window.clearTimeout(timeoutRef.current);
461
+ },
462
+ []
463
+ );
464
+
465
+ const Icon = isCopied ? CheckIcon : CopyIcon;
466
+
467
+ return (
468
+ <Button
469
+ className={cn("shrink-0", className)}
470
+ onClick={copyToClipboard}
471
+ size="icon"
472
+ variant="ghost"
473
+ {...props}
474
+ >
475
+ {children ?? <Icon size={14} />}
476
+ </Button>
477
+ );
478
+ };
479
+
480
+ export type CodeBlockLanguageSelectorProps = ComponentProps<typeof Select>;
481
+
482
+ export const CodeBlockLanguageSelector = (
483
+ props: CodeBlockLanguageSelectorProps
484
+ ) => <Select {...props} />;
485
+
486
+ export type CodeBlockLanguageSelectorTriggerProps = ComponentProps<
487
+ typeof SelectTrigger
488
+ >;
489
+
490
+ export const CodeBlockLanguageSelectorTrigger = ({
491
+ className,
492
+ ...props
493
+ }: CodeBlockLanguageSelectorTriggerProps) => (
494
+ <SelectTrigger
495
+ className={cn(
496
+ "h-7 border-none bg-transparent px-2 text-xs shadow-none",
497
+ className
498
+ )}
499
+ size="sm"
500
+ {...props}
501
+ />
502
+ );
503
+
504
+ export type CodeBlockLanguageSelectorValueProps = ComponentProps<
505
+ typeof SelectValue
506
+ >;
507
+
508
+ export const CodeBlockLanguageSelectorValue = (
509
+ props: CodeBlockLanguageSelectorValueProps
510
+ ) => <SelectValue {...props} />;
511
+
512
+ export type CodeBlockLanguageSelectorContentProps = ComponentProps<
513
+ typeof SelectContent
514
+ >;
515
+
516
+ export const CodeBlockLanguageSelectorContent = ({
517
+ align = "end",
518
+ ...props
519
+ }: CodeBlockLanguageSelectorContentProps) => (
520
+ <SelectContent align={align} {...props} />
521
+ );
522
+
523
+ export type CodeBlockLanguageSelectorItemProps = ComponentProps<
524
+ typeof SelectItem
525
+ >;
526
+
527
+ export const CodeBlockLanguageSelectorItem = (
528
+ props: CodeBlockLanguageSelectorItemProps
529
+ ) => <SelectItem {...props} />;