@tscircuit/fake-snippets 0.0.1

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 (396) hide show
  1. package/.github/CODEOWNERS +1 -0
  2. package/.github/workflows/bun-formatcheck.yml +26 -0
  3. package/.github/workflows/bun-test.yml +28 -0
  4. package/.github/workflows/bun-typecheck.yml +26 -0
  5. package/.github/workflows/bundle-size-analysis.yml +79 -0
  6. package/.github/workflows/playwright-test.yml +37 -0
  7. package/.github/workflows/stale.yml +40 -0
  8. package/.github/workflows/update-snapshots.yml +49 -0
  9. package/CONTRIBUTING.md +59 -0
  10. package/LICENSE +21 -0
  11. package/README.md +113 -0
  12. package/biome.json +60 -0
  13. package/bun-tests/fake-snippets-api/fixtures/get-circuit-json.ts +148 -0
  14. package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +96 -0
  15. package/bun-tests/fake-snippets-api/fixtures/start-server.ts +44 -0
  16. package/bun-tests/fake-snippets-api/routes/accounts/get_account_balance.test.ts +18 -0
  17. package/bun-tests/fake-snippets-api/routes/health.test.ts +9 -0
  18. package/bun-tests/fake-snippets-api/routes/order_files/get.test.ts +48 -0
  19. package/bun-tests/fake-snippets-api/routes/order_files/upload.test.ts +77 -0
  20. package/bun-tests/fake-snippets-api/routes/orders/create.test.ts +19 -0
  21. package/bun-tests/fake-snippets-api/routes/orders/get.test.ts +38 -0
  22. package/bun-tests/fake-snippets-api/routes/orders/list.test.ts +30 -0
  23. package/bun-tests/fake-snippets-api/routes/orders/update.test.ts +46 -0
  24. package/bun-tests/fake-snippets-api/routes/snippets/add_star.test.ts +114 -0
  25. package/bun-tests/fake-snippets-api/routes/snippets/create.test.ts +28 -0
  26. package/bun-tests/fake-snippets-api/routes/snippets/delete.test.ts +106 -0
  27. package/bun-tests/fake-snippets-api/routes/snippets/download.test.ts +90 -0
  28. package/bun-tests/fake-snippets-api/routes/snippets/generate_from_jlcpcb.test.ts +25 -0
  29. package/bun-tests/fake-snippets-api/routes/snippets/get_image.test.ts +113 -0
  30. package/bun-tests/fake-snippets-api/routes/snippets/images.test.ts +112 -0
  31. package/bun-tests/fake-snippets-api/routes/snippets/list.test.ts +62 -0
  32. package/bun-tests/fake-snippets-api/routes/snippets/list_newest.test.ts +48 -0
  33. package/bun-tests/fake-snippets-api/routes/snippets/list_trending.test.ts +69 -0
  34. package/bun-tests/fake-snippets-api/routes/snippets/remove_star.test.ts +110 -0
  35. package/bun-tests/fake-snippets-api/routes/snippets/search.test.ts +75 -0
  36. package/bun-tests/fake-snippets-api/routes/snippets/star-count.test.ts +44 -0
  37. package/bun-tests/fake-snippets-api/routes/snippets/update.test.ts +116 -0
  38. package/bun-tests/parts-engine.test.ts +18 -0
  39. package/bun.lockb +0 -0
  40. package/bunfig.toml +2 -0
  41. package/components.json +20 -0
  42. package/dist/assets/editor_example_1-1000w.webp +0 -0
  43. package/dist/assets/editor_example_1-1200w.webp +0 -0
  44. package/dist/assets/editor_example_1-1600w.webp +0 -0
  45. package/dist/assets/editor_example_1-2000w.webp +0 -0
  46. package/dist/assets/editor_example_1-400w.webp +0 -0
  47. package/dist/assets/editor_example_1-600w.webp +0 -0
  48. package/dist/assets/editor_example_1-800w.webp +0 -0
  49. package/dist/assets/editor_example_1_more_square-1000w.webp +0 -0
  50. package/dist/assets/editor_example_1_more_square-1200w.webp +0 -0
  51. package/dist/assets/editor_example_1_more_square-1600w.webp +0 -0
  52. package/dist/assets/editor_example_1_more_square-2000w.webp +0 -0
  53. package/dist/assets/editor_example_1_more_square-400w.webp +0 -0
  54. package/dist/assets/editor_example_1_more_square-600w.webp +0 -0
  55. package/dist/assets/editor_example_1_more_square-800w.webp +0 -0
  56. package/dist/assets/editor_example_2-1000w.webp +0 -0
  57. package/dist/assets/editor_example_2-1200w.webp +0 -0
  58. package/dist/assets/editor_example_2-1600w.webp +0 -0
  59. package/dist/assets/editor_example_2-2000w.webp +0 -0
  60. package/dist/assets/editor_example_2-400w.webp +0 -0
  61. package/dist/assets/editor_example_2-600w.webp +0 -0
  62. package/dist/assets/editor_example_2-800w.webp +0 -0
  63. package/dist/assets/example_schematic-1000w.webp +0 -0
  64. package/dist/assets/example_schematic-1200w.webp +0 -0
  65. package/dist/assets/example_schematic-1600w.webp +0 -0
  66. package/dist/assets/example_schematic-2000w.webp +0 -0
  67. package/dist/assets/example_schematic-400w.webp +0 -0
  68. package/dist/assets/example_schematic-600w.webp +0 -0
  69. package/dist/assets/example_schematic-800w.webp +0 -0
  70. package/dist/bundle.js +3270 -0
  71. package/dist/robots.txt +9 -0
  72. package/dist/sitemap.xml +118 -0
  73. package/docs/CIRCUIT_JSON_SOURCE_COMPONENT_OVERVIEW.md +151 -0
  74. package/fake-snippets-api/README.md +6 -0
  75. package/fake-snippets-api/biome.json +47 -0
  76. package/fake-snippets-api/bun.lockb +0 -0
  77. package/fake-snippets-api/lib/db/autoload-dev-snippets.ts +84 -0
  78. package/fake-snippets-api/lib/db/autoload-snippets.json +24 -0
  79. package/fake-snippets-api/lib/db/db-client.ts +343 -0
  80. package/fake-snippets-api/lib/db/schema.ts +112 -0
  81. package/fake-snippets-api/lib/db/seed.ts +1608 -0
  82. package/fake-snippets-api/lib/middleware/with-ctx-error.ts +26 -0
  83. package/fake-snippets-api/lib/middleware/with-db.ts +15 -0
  84. package/fake-snippets-api/lib/middleware/with-error-handling.ts +24 -0
  85. package/fake-snippets-api/lib/middleware/with-optional-session-auth.ts +34 -0
  86. package/fake-snippets-api/lib/middleware/with-request-logging.ts +54 -0
  87. package/fake-snippets-api/lib/middleware/with-session-auth.ts +39 -0
  88. package/fake-snippets-api/lib/middleware/with-winter-spec.ts +24 -0
  89. package/fake-snippets-api/next-env.d.ts +5 -0
  90. package/fake-snippets-api/routes/api/accounts/get.ts +21 -0
  91. package/fake-snippets-api/routes/api/accounts/get_account_balance.ts +22 -0
  92. package/fake-snippets-api/routes/api/accounts/update.ts +32 -0
  93. package/fake-snippets-api/routes/api/ai/[...anyroute].ts +31 -0
  94. package/fake-snippets-api/routes/api/ai.ts +2 -0
  95. package/fake-snippets-api/routes/api/aistream/[...anyroute].ts +65 -0
  96. package/fake-snippets-api/routes/api/health.ts +9 -0
  97. package/fake-snippets-api/routes/api/internal/sessions/create_without_auth.ts +63 -0
  98. package/fake-snippets-api/routes/api/order_files/get.ts +28 -0
  99. package/fake-snippets-api/routes/api/order_files/upload.ts +43 -0
  100. package/fake-snippets-api/routes/api/orders/create.ts +41 -0
  101. package/fake-snippets-api/routes/api/orders/get.ts +28 -0
  102. package/fake-snippets-api/routes/api/orders/list.ts +15 -0
  103. package/fake-snippets-api/routes/api/orders/update.ts +50 -0
  104. package/fake-snippets-api/routes/api/snippets/add_star.ts +42 -0
  105. package/fake-snippets-api/routes/api/snippets/create.ts +55 -0
  106. package/fake-snippets-api/routes/api/snippets/delete.ts +41 -0
  107. package/fake-snippets-api/routes/api/snippets/download.ts +148 -0
  108. package/fake-snippets-api/routes/api/snippets/generate_from_jlcpcb.ts +55 -0
  109. package/fake-snippets-api/routes/api/snippets/get.ts +50 -0
  110. package/fake-snippets-api/routes/api/snippets/get_image.ts +65 -0
  111. package/fake-snippets-api/routes/api/snippets/images/[author]/[snippet_name]/[typeFormat].ts +74 -0
  112. package/fake-snippets-api/routes/api/snippets/list.ts +28 -0
  113. package/fake-snippets-api/routes/api/snippets/list_newest.ts +13 -0
  114. package/fake-snippets-api/routes/api/snippets/list_trending.ts +21 -0
  115. package/fake-snippets-api/routes/api/snippets/remove_star.ts +42 -0
  116. package/fake-snippets-api/routes/api/snippets/search.ts +18 -0
  117. package/fake-snippets-api/routes/api/snippets/update.ts +86 -0
  118. package/favicon.ico +0 -0
  119. package/index.html +23 -0
  120. package/landing.html +23 -0
  121. package/package.json +164 -0
  122. package/playwright-tests/ai-page.spec.ts +19 -0
  123. package/playwright-tests/cmd-click.spec.ts +43 -0
  124. package/playwright-tests/dashboard-page.spec.ts +10 -0
  125. package/playwright-tests/editor-page.spec.ts +15 -0
  126. package/playwright-tests/files-dialog.spec.ts +19 -0
  127. package/playwright-tests/footprint-dialog/footprint-dialog.spec.ts +27 -0
  128. package/playwright-tests/footprint-dialog/footprint-insertion.spec.ts +38 -0
  129. package/playwright-tests/footprint-dialog/footprint-preview.spec.ts +34 -0
  130. package/playwright-tests/footprint-dialog/footprint-selection.spec.ts +29 -0
  131. package/playwright-tests/handle-manual-edits.spec.ts +55 -0
  132. package/playwright-tests/home-page.spec.ts +10 -0
  133. package/playwright-tests/images.spec.ts +17 -0
  134. package/playwright-tests/manual-edits.spec.ts +89 -0
  135. package/playwright-tests/preview-page.spec.ts +14 -0
  136. package/playwright-tests/quickstart-page.spec.ts +10 -0
  137. package/playwright-tests/search-links.spec.ts +21 -0
  138. package/playwright-tests/search.spec.ts +27 -0
  139. package/playwright-tests/snapshots/ai-page.spec.ts-AI-Page-lg.png +0 -0
  140. package/playwright-tests/snapshots/ai-page.spec.ts-AI-Page-md.png +0 -0
  141. package/playwright-tests/snapshots/ai-page.spec.ts-AI-Page-xs.png +0 -0
  142. package/playwright-tests/snapshots/cmd-click.spec.ts-underlined-imports.png +0 -0
  143. package/playwright-tests/snapshots/dashboard-page.spec.ts-Dashboard-page-lg.png +0 -0
  144. package/playwright-tests/snapshots/dashboard-page.spec.ts-Dashboard-page-md.png +0 -0
  145. package/playwright-tests/snapshots/dashboard-page.spec.ts-Dashboard-page-xs.png +0 -0
  146. package/playwright-tests/snapshots/editor-page.spec.ts-editor-with-snippet.png +0 -0
  147. package/playwright-tests/snapshots/error-fallback.spec.ts-error-fallback-lg.png +0 -0
  148. package/playwright-tests/snapshots/error-fallback.spec.ts-error-fallback-md.png +0 -0
  149. package/playwright-tests/snapshots/error-fallback.spec.ts-error-fallback-xs.png +0 -0
  150. package/playwright-tests/snapshots/files-dialog.spec.ts-view-snippet-files.png +0 -0
  151. package/playwright-tests/snapshots/footprint-dialog/footprint-dialog.spec.ts-footprint-preview-lg.png +0 -0
  152. package/playwright-tests/snapshots/footprint-dialog/footprint-dialog.spec.ts-footprint-preview-md.png +0 -0
  153. package/playwright-tests/snapshots/footprint-dialog/footprint-dialog.spec.ts-footprint-preview-xs.png +0 -0
  154. package/playwright-tests/snapshots/footprint-dialog/footprint-insertion.spec.ts-footprint-insertion-lg.png +0 -0
  155. package/playwright-tests/snapshots/footprint-dialog/footprint-insertion.spec.ts-footprint-insertion-md.png +0 -0
  156. package/playwright-tests/snapshots/footprint-dialog/footprint-insertion.spec.ts-footprint-insertion-xs.png +0 -0
  157. package/playwright-tests/snapshots/footprint-dialog/footprint-preview.spec.ts-footprint-preview-lg.png +0 -0
  158. package/playwright-tests/snapshots/footprint-dialog/footprint-preview.spec.ts-footprint-preview-md.png +0 -0
  159. package/playwright-tests/snapshots/footprint-dialog/footprint-preview.spec.ts-footprint-preview-xs.png +0 -0
  160. package/playwright-tests/snapshots/footprint-dialog/footprint-selection.spec.ts-footprint-preview-lg.png +0 -0
  161. package/playwright-tests/snapshots/footprint-dialog/footprint-selection.spec.ts-footprint-preview-md.png +0 -0
  162. package/playwright-tests/snapshots/footprint-dialog/footprint-selection.spec.ts-footprint-preview-xs.png +0 -0
  163. package/playwright-tests/snapshots/handle-manual-edits.spec.ts-handle-manual-edits.png +0 -0
  164. package/playwright-tests/snapshots/home-page.spec.ts-Home-page-lg.png +0 -0
  165. package/playwright-tests/snapshots/home-page.spec.ts-Home-page-md.png +0 -0
  166. package/playwright-tests/snapshots/home-page.spec.ts-Home-page-xs.png +0 -0
  167. package/playwright-tests/snapshots/images.spec.ts-pcb-image.png +0 -0
  168. package/playwright-tests/snapshots/images.spec.ts-schematic-image.png +0 -0
  169. package/playwright-tests/snapshots/manual-edits.spec.ts-editor-manual-edits.png +0 -0
  170. package/playwright-tests/snapshots/manual-edits.spec.ts-manual-edits-view.png +0 -0
  171. package/playwright-tests/snapshots/preview-page.spec.ts-preview-snippet-pcb.png +0 -0
  172. package/playwright-tests/snapshots/preview-page.spec.ts-preview-snippet-schematic.png +0 -0
  173. package/playwright-tests/snapshots/quickstart-page.spec.ts-Quickstart-Pagelg.png +0 -0
  174. package/playwright-tests/snapshots/quickstart-page.spec.ts-Quickstart-Pagemd.png +0 -0
  175. package/playwright-tests/snapshots/quickstart-page.spec.ts-Quickstart-Pagexs.png +0 -0
  176. package/playwright-tests/snapshots/search-links.spec.ts-search-links.png +0 -0
  177. package/playwright-tests/snapshots/search.spec.ts-search-lg.png +0 -0
  178. package/playwright-tests/snapshots/search.spec.ts-search-md.png +0 -0
  179. package/playwright-tests/snapshots/search.spec.ts-search-xs.png +0 -0
  180. package/playwright-tests/snapshots/star.spec.ts-remove-star-button.png +0 -0
  181. package/playwright-tests/snapshots/star.spec.ts-star-button.png +0 -0
  182. package/playwright-tests/snapshots/update-description.spec.ts-update-description.png +0 -0
  183. package/playwright-tests/snapshots/view-snippet.spec.ts-view-snippet-after-lg.png +0 -0
  184. package/playwright-tests/snapshots/view-snippet.spec.ts-view-snippet-after-md.png +0 -0
  185. package/playwright-tests/snapshots/view-snippet.spec.ts-view-snippet-after-xs.png +0 -0
  186. package/playwright-tests/snapshots/view-snippet.spec.ts-view-snippet-before-lg.png +0 -0
  187. package/playwright-tests/snapshots/view-snippet.spec.ts-view-snippet-before-md.png +0 -0
  188. package/playwright-tests/snapshots/view-snippet.spec.ts-view-snippet-before-xs.png +0 -0
  189. package/playwright-tests/snapshots/view-snippet.spec.ts-view-snippet-files.png +0 -0
  190. package/playwright-tests/star.spec.ts +40 -0
  191. package/playwright-tests/update-description.spec.ts +18 -0
  192. package/playwright-tests/view-snippet.spec.ts +35 -0
  193. package/playwright-tests/viewports.ts +5 -0
  194. package/playwright.config.ts +27 -0
  195. package/postcss.config.js +6 -0
  196. package/public/robots.txt +9 -0
  197. package/renovate.json +24 -0
  198. package/scripts/generate-image-sizes.ts +58 -0
  199. package/scripts/generate-sitemap.ts +103 -0
  200. package/scripts/generate_bundle_stats.js +192 -0
  201. package/scripts/snapshot.ts +35 -0
  202. package/src/App.tsx +113 -0
  203. package/src/ContextProviders.tsx +9 -0
  204. package/src/assets/originals/editor_example_1.webp +0 -0
  205. package/src/assets/originals/editor_example_1_more_square.webp +0 -0
  206. package/src/assets/originals/editor_example_2.webp +0 -0
  207. package/src/assets/originals/example_schematic.webp +0 -0
  208. package/src/components/AiChatInterface.tsx +221 -0
  209. package/src/components/AiChatMessage.tsx +86 -0
  210. package/src/components/Analytics.tsx +30 -0
  211. package/src/components/BomTable.tsx +69 -0
  212. package/src/components/ChatInput.tsx +53 -0
  213. package/src/components/CircuitToSvgWithMouseControl.tsx +78 -0
  214. package/src/components/CmdKMenu.tsx +301 -0
  215. package/src/components/CodeAndPreview.tsx +258 -0
  216. package/src/components/CodeEditor.tsx +460 -0
  217. package/src/components/CodeEditorHeader.tsx +160 -0
  218. package/src/components/CreateNewSnippetWithAiHero.tsx +77 -0
  219. package/src/components/DownloadButtonAndMenu.tsx +212 -0
  220. package/src/components/EditorNav.tsx +428 -0
  221. package/src/components/ErrorFallback.tsx +25 -0
  222. package/src/components/ErrorTabContent.tsx +122 -0
  223. package/src/components/FAQ.tsx +113 -0
  224. package/src/components/Footer.tsx +134 -0
  225. package/src/components/Footer2.tsx +100 -0
  226. package/src/components/FootprintDialog.tsx +342 -0
  227. package/src/components/Header.tsx +187 -0
  228. package/src/components/Header2.tsx +135 -0
  229. package/src/components/HeaderDropdown.tsx +83 -0
  230. package/src/components/HeaderLogin.tsx +94 -0
  231. package/src/components/JLCPCBImportDialog.tsx +155 -0
  232. package/src/components/LandingHero.tsx +175 -0
  233. package/src/components/LatestSnippets.tsx +39 -0
  234. package/src/components/OptimizedImage.tsx +96 -0
  235. package/src/components/OrderPreviewContent.tsx +61 -0
  236. package/src/components/ParametersEditor.tsx +140 -0
  237. package/src/components/PcbViewerWithContainerHeight.tsx +47 -0
  238. package/src/components/PrefetchPageLink.tsx +45 -0
  239. package/src/components/PreviewContent.tsx +375 -0
  240. package/src/components/PreviewEmptyState.tsx +16 -0
  241. package/src/components/RunButton.tsx +27 -0
  242. package/src/components/SearchComponent.tsx +163 -0
  243. package/src/components/ShippingInformationForm.tsx +423 -0
  244. package/src/components/SnippetLink.tsx +37 -0
  245. package/src/components/StaticPreviewContent.tsx +89 -0
  246. package/src/components/StaticViewSnippetHeader.tsx +70 -0
  247. package/src/components/StaticViewSnippetSidebar.tsx +100 -0
  248. package/src/components/TableViewer/CircuitJsonTableViewer.tsx +316 -0
  249. package/src/components/TableViewer/ClickableText.tsx +21 -0
  250. package/src/components/TableViewer/HeaderCell.tsx +27 -0
  251. package/src/components/TableViewer/Modal.tsx +39 -0
  252. package/src/components/TrendingSnippetCarousel.tsx +77 -0
  253. package/src/components/TypeBadge.tsx +31 -0
  254. package/src/components/ViewSnippetHeader.tsx +144 -0
  255. package/src/components/ViewSnippetSidebar.tsx +162 -0
  256. package/src/components/dialogs/confirm-delete-snippet-dialog.tsx +80 -0
  257. package/src/components/dialogs/create-order-dialog.tsx +146 -0
  258. package/src/components/dialogs/create-use-dialog.tsx +34 -0
  259. package/src/components/dialogs/edit-description-dialog.tsx +96 -0
  260. package/src/components/dialogs/files-dialog.tsx +70 -0
  261. package/src/components/dialogs/import-snippet-dialog.tsx +84 -0
  262. package/src/components/dialogs/rename-snippet-dialog.tsx +81 -0
  263. package/src/components/dialogs/view-ts-files-dialog.tsx +89 -0
  264. package/src/components/ui/accordion.tsx +55 -0
  265. package/src/components/ui/alert-dialog.tsx +139 -0
  266. package/src/components/ui/alert.tsx +59 -0
  267. package/src/components/ui/aspect-ratio.tsx +5 -0
  268. package/src/components/ui/avatar.tsx +48 -0
  269. package/src/components/ui/badge.tsx +36 -0
  270. package/src/components/ui/breadcrumb.tsx +118 -0
  271. package/src/components/ui/button.tsx +58 -0
  272. package/src/components/ui/calendar.tsx +73 -0
  273. package/src/components/ui/card.tsx +76 -0
  274. package/src/components/ui/carousel.tsx +260 -0
  275. package/src/components/ui/chart.tsx +363 -0
  276. package/src/components/ui/checkbox.tsx +28 -0
  277. package/src/components/ui/collapsible.tsx +9 -0
  278. package/src/components/ui/combobox.tsx +178 -0
  279. package/src/components/ui/command.tsx +151 -0
  280. package/src/components/ui/context-menu.tsx +202 -0
  281. package/src/components/ui/dialog.tsx +120 -0
  282. package/src/components/ui/drawer.tsx +116 -0
  283. package/src/components/ui/dropdown-menu.tsx +203 -0
  284. package/src/components/ui/form.tsx +182 -0
  285. package/src/components/ui/hover-card.tsx +27 -0
  286. package/src/components/ui/input-otp.tsx +69 -0
  287. package/src/components/ui/input.tsx +25 -0
  288. package/src/components/ui/label.tsx +24 -0
  289. package/src/components/ui/menubar.tsx +238 -0
  290. package/src/components/ui/navigation-menu.tsx +129 -0
  291. package/src/components/ui/pagination.tsx +121 -0
  292. package/src/components/ui/popover.tsx +31 -0
  293. package/src/components/ui/progress.tsx +26 -0
  294. package/src/components/ui/radio-group.tsx +42 -0
  295. package/src/components/ui/resizable.tsx +43 -0
  296. package/src/components/ui/scroll-area.tsx +46 -0
  297. package/src/components/ui/searchable-select.tsx +94 -0
  298. package/src/components/ui/select.tsx +162 -0
  299. package/src/components/ui/separator.tsx +29 -0
  300. package/src/components/ui/sheet.tsx +141 -0
  301. package/src/components/ui/skeleton.tsx +18 -0
  302. package/src/components/ui/slider.tsx +26 -0
  303. package/src/components/ui/sonner.tsx +30 -0
  304. package/src/components/ui/switch.tsx +27 -0
  305. package/src/components/ui/table.tsx +120 -0
  306. package/src/components/ui/tabs.tsx +53 -0
  307. package/src/components/ui/textarea.tsx +24 -0
  308. package/src/components/ui/toast.tsx +128 -0
  309. package/src/components/ui/toaster.tsx +33 -0
  310. package/src/components/ui/toggle-group.tsx +59 -0
  311. package/src/components/ui/toggle.tsx +43 -0
  312. package/src/components/ui/tooltip.tsx +28 -0
  313. package/src/entry-server.tsx +12 -0
  314. package/src/hooks/use-account-balance.ts +24 -0
  315. package/src/hooks/use-ai-api.ts +35 -0
  316. package/src/hooks/use-axios.ts +20 -0
  317. package/src/hooks/use-code-completion-ai-api.ts +11 -0
  318. package/src/hooks/use-compiled-tsx.ts +37 -0
  319. package/src/hooks/use-copy-to-clipboard.ts +26 -0
  320. package/src/hooks/use-create-snippet-mutation.ts +62 -0
  321. package/src/hooks/use-current-snippet-id.ts +75 -0
  322. package/src/hooks/use-current-snippet.ts +24 -0
  323. package/src/hooks/use-global-store.ts +33 -0
  324. package/src/hooks/use-is-using-fake-api.ts +3 -0
  325. package/src/hooks/use-run-tsx/construct-circuit.tsx +64 -0
  326. package/src/hooks/use-run-tsx/eval-compiled-js.ts +9 -0
  327. package/src/hooks/use-run-tsx/index.tsx +251 -0
  328. package/src/hooks/use-save-snippet.ts +66 -0
  329. package/src/hooks/use-sign-in.ts +22 -0
  330. package/src/hooks/use-snippet-by-name.ts +25 -0
  331. package/src/hooks/use-snippet.ts +23 -0
  332. package/src/hooks/use-snippets-base-api-url.ts +3 -0
  333. package/src/hooks/use-toast.tsx +208 -0
  334. package/src/hooks/use-typecheck.ts +54 -0
  335. package/src/hooks/use-url-params.ts +31 -0
  336. package/src/hooks/use-warn-user-on-page-change.ts +23 -0
  337. package/src/hooks/useForkSnippetMutation.ts +52 -0
  338. package/src/index.css +21 -0
  339. package/src/lib/__tests__/constants.test.ts +66 -0
  340. package/src/lib/base64ToBytes.ts +5 -0
  341. package/src/lib/bytesToBase64.ts +4 -0
  342. package/src/lib/codemirror/basic-setup.ts +51 -0
  343. package/src/lib/constants.ts +12 -0
  344. package/src/lib/decodeUrlHashToText.ts +15 -0
  345. package/src/lib/defaultCodeForBlankCode.tsx +7 -0
  346. package/src/lib/download-fns/createBlobURL.ts +4 -0
  347. package/src/lib/download-fns/download-assembly-svg.ts +12 -0
  348. package/src/lib/download-fns/download-circuit-json-fn.ts +12 -0
  349. package/src/lib/download-fns/download-dsn-file-fn.ts +12 -0
  350. package/src/lib/download-fns/download-fabrication-files.ts +233 -0
  351. package/src/lib/download-fns/download-gltf.ts +49 -0
  352. package/src/lib/download-fns/download-kicad-files.ts +27 -0
  353. package/src/lib/download-fns/download-readable-netlist.ts +12 -0
  354. package/src/lib/download-fns/download-schematic-svg.ts +12 -0
  355. package/src/lib/download-fns/download-simple-route-json.ts +17 -0
  356. package/src/lib/encodeTextToUrlHash.ts +17 -0
  357. package/src/lib/get-snippet-template.ts +26 -0
  358. package/src/lib/handleManualEditsImport.tsx +65 -0
  359. package/src/lib/jlc-parts-engine.ts +69 -0
  360. package/src/lib/templates/blank-3d-model-template.ts +12 -0
  361. package/src/lib/templates/blank-circuit-board-template.ts +24 -0
  362. package/src/lib/templates/blank-footprint-template.ts +29 -0
  363. package/src/lib/templates/blank-package-template.ts +22 -0
  364. package/src/lib/templates/blinking-led-board-template.ts +63 -0
  365. package/src/lib/templates/manual-edits-template.ts +5 -0
  366. package/src/lib/templates/usb-c-led-flashlight-template.ts +22 -0
  367. package/src/lib/utils/checkIfManualEditsImported.ts +6 -0
  368. package/src/lib/utils/getSyntaxError.ts +13 -0
  369. package/src/lib/utils/index.ts +6 -0
  370. package/src/lib/utils/load-prettier.ts +18 -0
  371. package/src/lib/utils/parseFootprintParams.ts +52 -0
  372. package/src/lib/utils/parseJsonOrNull.ts +8 -0
  373. package/src/lib/utils/pcbManualEditEventHandler.ts +156 -0
  374. package/src/main.tsx +10 -0
  375. package/src/pages/ai.tsx +92 -0
  376. package/src/pages/authorize.tsx +54 -0
  377. package/src/pages/dashboard.tsx +165 -0
  378. package/src/pages/dev-login.tsx +68 -0
  379. package/src/pages/editor.tsx +28 -0
  380. package/src/pages/landing.tsx +236 -0
  381. package/src/pages/my-orders.tsx +54 -0
  382. package/src/pages/newest.tsx +16 -0
  383. package/src/pages/preview.tsx +70 -0
  384. package/src/pages/quickstart.tsx +198 -0
  385. package/src/pages/search.tsx +26 -0
  386. package/src/pages/settings.tsx +25 -0
  387. package/src/pages/user-profile.tsx +97 -0
  388. package/src/pages/view-order.tsx +123 -0
  389. package/src/pages/view-snippet.tsx +149 -0
  390. package/src/prettier.ts +9 -0
  391. package/src/vite-env.d.ts +1 -0
  392. package/tailwind.config.js +47 -0
  393. package/tsconfig.json +30 -0
  394. package/vercel.json +50 -0
  395. package/vite.config.ts +146 -0
  396. package/winterspec.config.ts +6 -0
@@ -0,0 +1,192 @@
1
+ import fs from "fs"
2
+ import path from "path"
3
+
4
+ function processData(data, dependencies) {
5
+ const { tree, nodeParts } = data
6
+ const depStats = {}
7
+ let totalSize = 0
8
+
9
+ function traverse(node) {
10
+ if (node.uid && nodeParts[node.uid]) {
11
+ const part = nodeParts[node.uid]
12
+ totalSize += part.renderedLength
13
+ const matchedDep = Object.keys(dependencies).find((dep) =>
14
+ node.name.includes(dep),
15
+ )
16
+ if (matchedDep) {
17
+ if (!depStats[matchedDep]) {
18
+ depStats[matchedDep] = { size: 0, files: 0 }
19
+ }
20
+ depStats[matchedDep].size += part.renderedLength
21
+ depStats[matchedDep].files += 1
22
+ }
23
+ }
24
+ if (node.children) {
25
+ node.children.forEach(traverse)
26
+ }
27
+ }
28
+
29
+ traverse(tree)
30
+
31
+ return { totalSize, depStats }
32
+ }
33
+
34
+ function getDependencies() {
35
+ const packageJson = JSON.parse(fs.readFileSync("package.json", "utf-8"))
36
+ return {
37
+ ...packageJson.dependencies,
38
+ ...packageJson.devDependencies,
39
+ }
40
+ }
41
+
42
+ function parseStatsHtml(filePath) {
43
+ console.log(`Attempting to parse file: ${filePath}`)
44
+ const html = fs.readFileSync(filePath, "utf-8")
45
+
46
+ const dataMatch = html.match(/const data = (\{.*?\});/s)
47
+ if (dataMatch && dataMatch[1]) {
48
+ try {
49
+ return JSON.parse(dataMatch[1])
50
+ } catch (error) {
51
+ console.error("Error parsing JSON:", error)
52
+ return null
53
+ }
54
+ }
55
+ console.error("Could not find data in the script content")
56
+ return null
57
+ }
58
+
59
+ function formatBytes(bytes) {
60
+ const units = ["B", "KB", "MB", "GB"]
61
+ let size = bytes
62
+ let unitIndex = 0
63
+ while (size >= 1024 && unitIndex < units.length - 1) {
64
+ size /= 1024
65
+ unitIndex++
66
+ }
67
+ return `${size.toFixed(2)} ${units[unitIndex]}`
68
+ }
69
+
70
+ function compareSizes(prData, mainData, dependencies) {
71
+ const prStats = processData(prData, dependencies)
72
+ const mainStats = processData(mainData, dependencies)
73
+
74
+ const diffStats = {}
75
+ const allDeps = new Set([
76
+ ...Object.keys(prStats.depStats),
77
+ ...Object.keys(mainStats.depStats),
78
+ ])
79
+
80
+ allDeps.forEach((dep) => {
81
+ const prSize = prStats.depStats[dep]?.size || 0
82
+ const mainSize = mainStats.depStats[dep]?.size || 0
83
+ const diff = prSize - mainSize
84
+
85
+ diffStats[dep] = {
86
+ before: mainSize,
87
+ after: prSize,
88
+ diff,
89
+ percentChange: (diff / mainSize) * 100 || 0,
90
+ }
91
+ })
92
+
93
+ return {
94
+ totalBefore: mainStats.totalSize,
95
+ totalAfter: prStats.totalSize,
96
+ totalDiff: prStats.totalSize - mainStats.totalSize,
97
+ totalPercentChange:
98
+ ((prStats.totalSize - mainStats.totalSize) / mainStats.totalSize) * 100,
99
+ diffStats,
100
+ }
101
+ }
102
+
103
+ function generateDiffMarkdown(prData, mainData, dependencies) {
104
+ const comparison = compareSizes(prData, mainData, dependencies)
105
+ let markdown = ""
106
+ const totalDiffSymbol = comparison.totalDiff > 0 ? "📈" : "📉"
107
+ markdown += `## Total Bundle Size\n\n`
108
+ markdown += `- Before: **${formatBytes(comparison.totalBefore)}**\n`
109
+ markdown += `- After: **${formatBytes(comparison.totalAfter)}**\n`
110
+ markdown += `- Change: ${totalDiffSymbol} **${formatBytes(Math.abs(comparison.totalDiff))}** (${comparison.totalPercentChange.toFixed(2)}%)\n\n`
111
+
112
+ markdown += `## Diff\n\n`
113
+
114
+ const sortedDiffs = Object.entries(comparison.diffStats).sort(
115
+ ([, a], [, b]) => Math.abs(b.diff) - Math.abs(a.diff),
116
+ )
117
+
118
+ const significantChanges = sortedDiffs.filter(
119
+ ([, stats]) =>
120
+ Math.abs(stats.percentChange) > 1 || Math.abs(stats.diff) > 1024,
121
+ )
122
+
123
+ if (significantChanges.length > 0) {
124
+ markdown += `| Package | Before | After | Diff | Change |\n`
125
+ markdown += `|---------|--------|-------|------|--------|\n`
126
+
127
+ for (const [name, stats] of significantChanges) {
128
+ const version = dependencies[name]
129
+ const symbol = stats.diff > 0 ? "📈" : "📉"
130
+ markdown += `| ${name}@${version} | ${formatBytes(stats.before)} | ${formatBytes(stats.after)} | ${symbol} ${formatBytes(Math.abs(stats.diff))} |
131
+ ${stats.percentChange.toFixed(2)}% |\n`
132
+ }
133
+ } else {
134
+ markdown += "No significant changes in bundle size.\n"
135
+ }
136
+
137
+ markdown += `\n<details>\n`
138
+ markdown += `<summary>View Dependencies</summary>\n\n`
139
+ markdown += `| Package | Size |\n`
140
+ markdown += `|---------|------|\n`
141
+
142
+ const prStats = processData(prData, dependencies)
143
+ const prDependencies = Object.entries(prStats.depStats).sort(
144
+ ([, a], [, b]) => b.size - a.size,
145
+ )
146
+
147
+ for (const [name, stats] of prDependencies) {
148
+ const version = dependencies[name]
149
+ markdown += `| ${name}@${version} | ${formatBytes(stats.size)} |\n`
150
+ }
151
+
152
+ markdown += `\n</details>\n`
153
+
154
+ return markdown
155
+ }
156
+
157
+ function main() {
158
+ const args = process.argv.slice(2)
159
+ if (args.length !== 2) {
160
+ console.error(
161
+ "Usage: node generate_bundle_stats.js <pr_stats.html> <main_stats.html>",
162
+ )
163
+ process.exit(1)
164
+ }
165
+
166
+ const [prStatsFile, mainStatsFile] = args
167
+
168
+ try {
169
+ console.log(`Processing PR stats: ${prStatsFile}`)
170
+ console.log(`Processing main stats: ${mainStatsFile}`)
171
+
172
+ const prData = parseStatsHtml(prStatsFile)
173
+ const mainData = parseStatsHtml(mainStatsFile)
174
+
175
+ if (!prData || !mainData) {
176
+ console.error("Failed to parse stats files")
177
+ process.exit(1)
178
+ }
179
+
180
+ const dependencies = getDependencies()
181
+ const markdown = generateDiffMarkdown(prData, mainData, dependencies)
182
+
183
+ const outputFile = path.join(path.dirname(prStatsFile), "bundle_stats.md")
184
+ fs.writeFileSync(outputFile, markdown)
185
+ console.log(`Bundle stats comparison markdown generated: ${outputFile}`)
186
+ } catch (error) {
187
+ console.error("Error:", error.message)
188
+ process.exit(1)
189
+ }
190
+ }
191
+
192
+ main()
@@ -0,0 +1,35 @@
1
+ /*
2
+ * This script is for selecting and updating a single snapshot.
3
+ * We use prompts to allow the user to select the *.spec.ts file to run.
4
+ * We then run "bunx playwright test --update-snapshots" for that file.
5
+ */
6
+
7
+ import prompts from "prompts"
8
+ import { execSync } from "child_process"
9
+ import { Glob } from "bun"
10
+ import path from "path"
11
+
12
+ const run = async () => {
13
+ const { file } = await prompts({
14
+ type: "autocomplete",
15
+ name: "file",
16
+ message: "Select a file to update",
17
+ choices: getTestFiles(),
18
+ })
19
+
20
+ if (!file) return
21
+
22
+ execSync(`bunx playwright test --update-snapshots ${file}`, {
23
+ stdio: "inherit",
24
+ })
25
+ }
26
+
27
+ const getTestFiles = () => {
28
+ const files = new Glob("playwright-tests/**/*.spec.ts").scanSync()
29
+ return Array.from(files).map((file) => ({
30
+ title: file.replace("playwright-tests/", ""),
31
+ value: file,
32
+ }))
33
+ }
34
+
35
+ run()
package/src/App.tsx ADDED
@@ -0,0 +1,113 @@
1
+ import { ComponentType, Suspense, lazy } from "react"
2
+ import { Toaster } from "@/components/ui/toaster"
3
+ import { Route, Switch } from "wouter"
4
+ import "./components/CmdKMenu"
5
+ import { ContextProviders } from "./ContextProviders"
6
+ import React from "react"
7
+
8
+ const lazyImport = (importFn: () => Promise<any>) =>
9
+ lazy<ComponentType<any>>(async () => {
10
+ try {
11
+ const module = await importFn()
12
+
13
+ if (module.default) {
14
+ return { default: module.default }
15
+ }
16
+
17
+ const pageExportNames = ["Page", "Component", "View"]
18
+ for (const suffix of pageExportNames) {
19
+ const keys = Object.keys(module).filter((key) => key.endsWith(suffix))
20
+ if (keys.length > 0) {
21
+ return { default: module[keys[0]] }
22
+ }
23
+ }
24
+
25
+ const componentExport = Object.values(module).find(
26
+ (exp) => typeof exp === "function" && exp.prototype?.isReactComponent,
27
+ )
28
+ if (componentExport) {
29
+ return { default: componentExport }
30
+ }
31
+
32
+ throw new Error(
33
+ `No valid React component found in module. Available exports: ${Object.keys(module).join(", ")}`,
34
+ )
35
+ } catch (error) {
36
+ console.error("Failed to load component:", error)
37
+ throw error
38
+ }
39
+ })
40
+
41
+ const AiPage = lazyImport(() => import("@/pages/ai"))
42
+ const AuthenticatePage = lazyImport(() => import("@/pages/authorize"))
43
+ const DashboardPage = lazyImport(() => import("@/pages/dashboard"))
44
+ const EditorPage = lazyImport(async () => {
45
+ const [editorModule] = await Promise.all([
46
+ import("@/pages/editor"),
47
+ import("@/lib/utils/load-prettier").then((m) => m.loadPrettier()),
48
+ ])
49
+ return editorModule
50
+ })
51
+ const LandingPage = lazyImport(() => import("@/pages/landing"))
52
+ const MyOrdersPage = lazyImport(() => import("@/pages/my-orders"))
53
+ const NewestPage = lazyImport(() => import("@/pages/newest"))
54
+ const PreviewPage = lazyImport(() => import("@/pages/preview"))
55
+ const QuickstartPage = lazyImport(() => import("@/pages/quickstart"))
56
+ const SearchPage = lazyImport(() => import("@/pages/search"))
57
+ const SettingsPage = lazyImport(() => import("@/pages/settings"))
58
+ const UserProfilePage = lazyImport(() => import("@/pages/user-profile"))
59
+ const ViewOrderPage = lazyImport(() => import("@/pages/view-order"))
60
+ const ViewSnippetPage = lazyImport(() => import("@/pages/view-snippet"))
61
+ const DevLoginPage = lazyImport(() => import("@/pages/dev-login"))
62
+
63
+ class ErrorBoundary extends React.Component<
64
+ { children: React.ReactNode },
65
+ { hasError: boolean }
66
+ > {
67
+ constructor(props: { children: React.ReactNode }) {
68
+ super(props)
69
+ this.state = { hasError: false }
70
+ }
71
+
72
+ static getDerivedStateFromError() {
73
+ return { hasError: true }
74
+ }
75
+
76
+ render() {
77
+ if (this.state.hasError) {
78
+ return <div>Something went wrong loading the page.</div>
79
+ }
80
+ return this.props.children
81
+ }
82
+ }
83
+
84
+ function App() {
85
+ return (
86
+ <ContextProviders>
87
+ <ErrorBoundary>
88
+ <Suspense fallback={<div>Loading...</div>}>
89
+ <Switch>
90
+ <Route path="/" component={LandingPage} />
91
+ <Route path="/editor" component={EditorPage} />
92
+ <Route path="/quickstart" component={QuickstartPage} />
93
+ <Route path="/dashboard" component={DashboardPage} />
94
+ <Route path="/ai" component={AiPage} />
95
+ <Route path="/newest" component={NewestPage} />
96
+ <Route path="/settings" component={SettingsPage} />
97
+ <Route path="/search" component={SearchPage} />
98
+ <Route path="/authorize" component={AuthenticatePage} />
99
+ <Route path="/my-orders" component={MyOrdersPage} />
100
+ <Route path="/orders/:orderId" component={ViewOrderPage} />
101
+ <Route path="/preview" component={PreviewPage} />
102
+ <Route path="/dev-login" component={DevLoginPage} />
103
+ <Route path="/:username" component={UserProfilePage} />
104
+ <Route path="/:author/:snippetName" component={ViewSnippetPage} />
105
+ </Switch>
106
+ </Suspense>
107
+ <Toaster />
108
+ </ErrorBoundary>
109
+ </ContextProviders>
110
+ )
111
+ }
112
+
113
+ export default App
@@ -0,0 +1,9 @@
1
+ import { QueryClient, QueryClientProvider } from "react-query"
2
+
3
+ const queryClient = new QueryClient()
4
+
5
+ export const ContextProviders = ({ children }: any) => {
6
+ return (
7
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
8
+ )
9
+ }
@@ -0,0 +1,221 @@
1
+ import { useState, useRef, useEffect } from "react"
2
+ import { Button } from "@/components/ui/button"
3
+ import ChatInput from "./ChatInput"
4
+ import { useAiApi } from "@/hooks/use-ai-api"
5
+ import { createCircuitBoard1Template } from "@tscircuit/prompt-benchmarks"
6
+ import { TextDelta } from "@anthropic-ai/sdk/resources/messages.mjs"
7
+ import { MagicWandIcon } from "@radix-ui/react-icons"
8
+ import { AiChatMessage } from "./AiChatMessage"
9
+ import { useLocation } from "wouter"
10
+ import { useSnippet } from "@/hooks/use-snippet"
11
+ import { Edit2 } from "lucide-react"
12
+ import { SnippetLink } from "./SnippetLink"
13
+ import { useGlobalStore } from "@/hooks/use-global-store"
14
+ import { useSignIn } from "@/hooks/use-sign-in"
15
+ import { extractCodefence } from "extract-codefence"
16
+ import { PrefetchPageLink } from "./PrefetchPageLink"
17
+
18
+ export default function AIChatInterface({
19
+ code,
20
+ hasUnsavedChanges,
21
+ snippetId,
22
+ onCodeChange,
23
+ onStartStreaming,
24
+ onStopStreaming,
25
+ errorMessage,
26
+ disabled,
27
+ }: {
28
+ code: string
29
+ disabled?: boolean
30
+ hasUnsavedChanges: boolean
31
+ snippetId?: string | null
32
+ onCodeChange: (code: string) => void
33
+ onStartStreaming: () => void
34
+ onStopStreaming: () => void
35
+ errorMessage: string | null
36
+ }) {
37
+ const [messages, setMessages] = useState<AiChatMessage[]>([])
38
+ const [isStreaming, setIsStreaming] = useState(false)
39
+ const anthropic = useAiApi()
40
+ const messagesEndRef = useRef<HTMLDivElement>(null)
41
+ const { data: snippet } = useSnippet(snippetId!)
42
+ const [currentCodeBlock, setCurrentCodeBlock] = useState<string | null>(null)
43
+ const [location, navigate] = useLocation()
44
+ const isStreamingRef = useRef(false)
45
+ const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
46
+ const signIn = useSignIn()
47
+
48
+ useEffect(() => {
49
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
50
+ }, [messages])
51
+
52
+ const addMessage = async (message: string) => {
53
+ const newMessages = messages.concat([
54
+ {
55
+ sender: "user",
56
+ content: message,
57
+ },
58
+ {
59
+ sender: "bot",
60
+ content: "",
61
+ codeVersion: messages.filter((m) => m.sender === "bot").length,
62
+ },
63
+ ])
64
+ setMessages(newMessages)
65
+ setIsStreaming(true)
66
+ onStartStreaming()
67
+
68
+ try {
69
+ const stream = await anthropic.messages.stream({
70
+ model: "claude-3-sonnet-20240229",
71
+ system: createCircuitBoard1Template({
72
+ currentCode: code,
73
+ }),
74
+ messages: [
75
+ // TODO: include previous messages
76
+ {
77
+ role: "user",
78
+ content: message,
79
+ },
80
+ ],
81
+ max_tokens: 1000,
82
+ })
83
+
84
+ let accumulatedContent = ""
85
+ let isInCodeBlock = false
86
+
87
+ for await (const chunk of stream) {
88
+ if (chunk.type === "content_block_delta") {
89
+ const chunkText = (chunk.delta as TextDelta).text
90
+ accumulatedContent += chunkText
91
+
92
+ if (chunkText.includes("```")) {
93
+ isInCodeBlock = !isInCodeBlock
94
+ if (isInCodeBlock) {
95
+ setCurrentCodeBlock("")
96
+ } else {
97
+ const codeContent = extractCodefence(accumulatedContent)
98
+ if (codeContent) {
99
+ onCodeChange(codeContent)
100
+ }
101
+ setCurrentCodeBlock(null)
102
+ }
103
+ } else if (isInCodeBlock) {
104
+ setCurrentCodeBlock((prev) => {
105
+ const updatedCode = (prev || "") + chunkText
106
+ onCodeChange(updatedCode)
107
+ return updatedCode
108
+ })
109
+ }
110
+
111
+ setMessages((prevMessages) => {
112
+ const updatedMessages = [...prevMessages]
113
+ updatedMessages[updatedMessages.length - 1].content =
114
+ accumulatedContent
115
+ return updatedMessages
116
+ })
117
+ }
118
+ }
119
+ } catch (error) {
120
+ console.error("Error streaming response:", error)
121
+ setMessages((prevMessages) => {
122
+ const updatedMessages = [...prevMessages]
123
+ updatedMessages[updatedMessages.length - 1].content =
124
+ "An error occurred while generating the response."
125
+ return updatedMessages
126
+ })
127
+ } finally {
128
+ setIsStreaming(false)
129
+ onStopStreaming()
130
+ }
131
+ }
132
+
133
+ useEffect(() => {
134
+ const searchParams = new URLSearchParams(
135
+ window.location.search.split("?")[1],
136
+ )
137
+ const initialPrompt = searchParams.get("initial_prompt")
138
+
139
+ if (initialPrompt && messages.length === 0 && !isStreamingRef.current) {
140
+ isStreamingRef.current = true
141
+ addMessage(initialPrompt)
142
+ }
143
+ }, [])
144
+
145
+ return (
146
+ <div className="flex flex-col h-[calc(100vh-60px)] max-w-2xl mx-auto p-4 bg-gray-100">
147
+ <div className="flex-1 overflow-y-auto space-y-4 mb-4">
148
+ {snippet && (
149
+ <div className="flex pl-4 p-2 rounded items-center bg-white border border-gray-200 text-sm mb-4 shadow-sm">
150
+ <SnippetLink snippet={snippet} />
151
+ <div className="flex-grow" />
152
+ <PrefetchPageLink href={`/editor?snippet_id=${snippet.snippet_id}`}>
153
+ <Button
154
+ size="sm"
155
+ className="text-xs"
156
+ variant="ghost"
157
+ disabled={hasUnsavedChanges}
158
+ >
159
+ Open in Editor
160
+ <Edit2 className="w-3 h-3 ml-2 opacity-60" />
161
+ </Button>
162
+ </PrefetchPageLink>
163
+ </div>
164
+ )}
165
+ {messages.length === 0 && isLoggedIn && (
166
+ <div className="text-gray-500 text-xl text-center pt-[30vh] flex flex-col items-center">
167
+ <div>Submit a prompt to {snippet ? "edit!" : "get started!"}</div>
168
+ <div className="text-6xl mt-4">↓</div>
169
+ </div>
170
+ )}
171
+ {!isLoggedIn && (
172
+ <div className="text-gray-500 text-xl text-center pt-[30vh] flex flex-col items-center">
173
+ <div>
174
+ Sign in use the AI chat or{" "}
175
+ <PrefetchPageLink
176
+ className="text-blue-500 underline"
177
+ href="/quickstart"
178
+ >
179
+ use the regular editor
180
+ </PrefetchPageLink>
181
+ </div>
182
+ <div className="mt-4 flex gap-2">
183
+ <Button onClick={() => signIn()}>Sign In</Button>
184
+ <Button onClick={() => signIn()} variant="outline">
185
+ Sign Up
186
+ </Button>
187
+ </div>
188
+ </div>
189
+ )}
190
+ {messages.map((message, index) => (
191
+ <AiChatMessage key={index} message={message} />
192
+ ))}
193
+ <div ref={messagesEndRef} />
194
+ </div>
195
+ {code && errorMessage && !isStreaming && (
196
+ <div className="flex justify-end mr-6">
197
+ <Button
198
+ onClick={() => {
199
+ addMessage(`Fix this error: ${errorMessage}`)
200
+ }}
201
+ disabled={!isLoggedIn}
202
+ className="mb-2 bg-green-50 hover:bg-green-100"
203
+ variant="outline"
204
+ >
205
+ <MagicWandIcon className="w-4 h-4 mr-2" />
206
+ <span className="font-bold">Fix Error with AI</span>
207
+ <span className="italic font-normal ml-2">
208
+ "{errorMessage.slice(0, 26)}..."
209
+ </span>
210
+ </Button>
211
+ </div>
212
+ )}
213
+ <ChatInput
214
+ onSubmit={async (message: string) => {
215
+ addMessage(message)
216
+ }}
217
+ disabled={isStreaming || !isLoggedIn}
218
+ />
219
+ </div>
220
+ )
221
+ }
@@ -0,0 +1,86 @@
1
+ import { BotIcon, ChevronDown, Eye, RotateCcw } from "lucide-react"
2
+ import { Button } from "@/components/ui/button"
3
+ import { Avatar } from "@/components/ui/avatar"
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuItem,
8
+ DropdownMenuTrigger,
9
+ } from "@/components/ui/dropdown-menu"
10
+
11
+ export interface AiChatMessage {
12
+ sender: "user" | "bot"
13
+ content: string
14
+ codeVersion?: number
15
+ }
16
+
17
+ export const AiChatMessage = ({ message }: { message: AiChatMessage }) => {
18
+ const renderContent = (content: string) => {
19
+ const parts = content.split(/(```[\s\S]*?(?:```|$))/g)
20
+ return parts.map((part, index) => {
21
+ if (part.startsWith("```")) {
22
+ const isComplete = part.endsWith("```")
23
+ return (
24
+ <div
25
+ key={index}
26
+ className="bg-gray-100 rounded-md p-2 my-2 text-sm font-mono"
27
+ >
28
+ {isComplete
29
+ ? `Code Version ${message.codeVersion ?? "??"}`
30
+ : "generating..."}
31
+ </div>
32
+ )
33
+ }
34
+ return (
35
+ <p key={index} className="text-xs font-mono whitespace-pre-wrap">
36
+ {part}
37
+ </p>
38
+ )
39
+ })
40
+ }
41
+
42
+ return (
43
+ <div
44
+ className={`flex ${
45
+ message.sender === "user" ? "justify-end" : "justify-start"
46
+ }`}
47
+ >
48
+ <div
49
+ className={`max-w-[80%] rounded-lg p-4 ${
50
+ message.sender === "user" ? "bg-blue-100" : "bg-white"
51
+ }`}
52
+ >
53
+ {message.sender === "bot" && (
54
+ <div className="flex items-center mb-2">
55
+ <Avatar className="w-7 h-7 mr-2 flex items-center justify-center bg-black">
56
+ <BotIcon className="text-white px-1" />
57
+ </Avatar>
58
+ <DropdownMenu>
59
+ <DropdownMenuTrigger>
60
+ <Button
61
+ variant="outline"
62
+ size="sm"
63
+ className="h-6 px-2 text-xs"
64
+ >
65
+ version {message.codeVersion}
66
+ <ChevronDown className="ml-1 h-3 w-3" />
67
+ </Button>
68
+ </DropdownMenuTrigger>
69
+ <DropdownMenuContent>
70
+ <DropdownMenuItem className="text-xs bg-white flex">
71
+ <RotateCcw className="mr-1 h-3 w-3" />
72
+ Revert to v{message.codeVersion}
73
+ </DropdownMenuItem>
74
+ <DropdownMenuItem className="text-xs bg-white flex">
75
+ <Eye className="mr-1 h-3 w-3" />
76
+ View v{message.codeVersion}
77
+ </DropdownMenuItem>
78
+ </DropdownMenuContent>
79
+ </DropdownMenu>
80
+ </div>
81
+ )}
82
+ {renderContent(message.content)}
83
+ </div>
84
+ </div>
85
+ )
86
+ }
@@ -0,0 +1,30 @@
1
+ import { Analytics as VercelAnalytics } from "@vercel/analytics/react"
2
+ import posthog from "posthog-js"
3
+ import CookieConsent from "react-cookie-consent"
4
+
5
+ posthog.init("phc_htd8AQjSfVEsFCLQMAiUooG4Q0DKBCjqYuQglc9V3Wo", {
6
+ api_host: "https://us.i.posthog.com",
7
+ person_profiles: "always",
8
+ })
9
+
10
+ export const Analytics = () => {
11
+ return (
12
+ <div className="absolute bottom-0 left-0 right-0">
13
+ <VercelAnalytics />
14
+ {/* <CookieConsent
15
+ buttonText="Accept"
16
+ cookieName="cookieConsent"
17
+ style={{ background: "#2B373B" }}
18
+ acceptOnScroll
19
+ buttonStyle={{
20
+ backgroundColor: "#111",
21
+ color: "#fff",
22
+ fontSize: "13px",
23
+ }}
24
+ expires={150}
25
+ >
26
+ This website uses cookies to enhance the user experience.
27
+ </CookieConsent> */}
28
+ </div>
29
+ )
30
+ }