@tscircuit/fake-snippets 0.0.109 → 0.0.111

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 (185) hide show
  1. package/.github/workflows/bun-formatcheck.yml +2 -2
  2. package/.github/workflows/bun-pver-release.yml +3 -3
  3. package/.github/workflows/bun-test.yml +1 -1
  4. package/.github/workflows/bun-typecheck.yml +2 -2
  5. package/.github/workflows/update-snapshots.yml +1 -1
  6. package/README.md +4 -0
  7. package/api/generated-index.js +37 -3
  8. package/biome.json +2 -1
  9. package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +32 -3
  10. package/bun-tests/fake-snippets-api/fixtures/preload.ts +18 -0
  11. package/bun-tests/fake-snippets-api/routes/orgs/add_member.test.ts +26 -0
  12. package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +37 -0
  13. package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +52 -0
  14. package/bun-tests/fake-snippets-api/routes/orgs/list.test.ts +17 -0
  15. package/bun-tests/fake-snippets-api/routes/orgs/list_members.test.ts +23 -0
  16. package/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +81 -0
  17. package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +151 -0
  18. package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +1 -1
  19. package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +15 -13
  20. package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -24
  21. package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +9 -9
  22. package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +4 -4
  23. package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +38 -28
  24. package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +23 -15
  25. package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +33 -0
  26. package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +4 -4
  27. package/bun-tests/fake-snippets-api/routes/package_releases/get_image_generation_fields.test.ts +38 -0
  28. package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +19 -0
  29. package/bun-tests/fake-snippets-api/routes/packages/fork.test.ts +3 -4
  30. package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +30 -0
  31. package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +4 -2
  32. package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +34 -0
  33. package/bun.lock +361 -453
  34. package/bunfig.toml +2 -1
  35. package/dist/bundle.js +1313 -639
  36. package/dist/index.d.ts +313 -6
  37. package/dist/index.js +328 -24
  38. package/dist/schema.d.ts +290 -1
  39. package/dist/schema.js +54 -1
  40. package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
  41. package/fake-snippets-api/lib/db/db-client.ts +219 -4
  42. package/fake-snippets-api/lib/db/schema.ts +63 -1
  43. package/fake-snippets-api/lib/db/seed.ts +100 -0
  44. package/fake-snippets-api/lib/middleware/with-session-auth.ts +60 -8
  45. package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +2 -2
  46. package/fake-snippets-api/lib/public-mapping/public-map-org.ts +33 -0
  47. package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +10 -0
  48. package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +17 -0
  49. package/fake-snippets-api/routes/api/orgs/add_member.ts +52 -0
  50. package/fake-snippets-api/routes/api/orgs/create.ts +48 -0
  51. package/fake-snippets-api/routes/api/orgs/get.ts +39 -0
  52. package/fake-snippets-api/routes/api/orgs/list.ts +31 -0
  53. package/fake-snippets-api/routes/api/orgs/list_members.ts +60 -0
  54. package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
  55. package/fake-snippets-api/routes/api/orgs/update.ts +118 -0
  56. package/fake-snippets-api/routes/api/package_files/get.ts +3 -6
  57. package/fake-snippets-api/routes/api/package_files/list.ts +7 -4
  58. package/fake-snippets-api/routes/api/packages/create.ts +57 -10
  59. package/fake-snippets-api/routes/api/packages/get.ts +23 -0
  60. package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +13 -11
  61. package/fake-snippets-api/routes/api/packages/list.ts +29 -2
  62. package/fake-snippets-api/routes/api/packages/update_ai_description.ts +37 -0
  63. package/package.json +25 -19
  64. package/renovate.json +1 -1
  65. package/scripts/generate-sitemap.ts +1 -1
  66. package/src/App.tsx +27 -8
  67. package/src/ContextProviders.tsx +25 -2
  68. package/src/components/CircuitJsonImportDialog.tsx +1 -1
  69. package/src/components/CmdKMenu.tsx +281 -247
  70. package/src/components/DownloadButtonAndMenu.tsx +17 -5
  71. package/src/components/FileSidebar.tsx +11 -17
  72. package/src/components/Footer.tsx +8 -9
  73. package/src/components/Header.tsx +19 -32
  74. package/src/components/Header2.tsx +16 -32
  75. package/src/components/HeaderDropdown.tsx +13 -8
  76. package/src/components/HeaderLogin.tsx +43 -15
  77. package/src/components/NotFound.tsx +5 -5
  78. package/src/components/PackageBreadcrumb.tsx +6 -12
  79. package/src/components/PackageSearchResults.tsx +1 -1
  80. package/src/components/PrefetchPageLink.tsx +7 -1
  81. package/src/components/ProfileRouter.tsx +32 -0
  82. package/src/components/SearchComponent.tsx +12 -8
  83. package/src/components/SentryNotFoundReporter.tsx +44 -0
  84. package/src/components/UserCard.tsx +80 -0
  85. package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
  86. package/src/components/ViewPackagePage/components/important-files-view.tsx +105 -34
  87. package/src/components/ViewPackagePage/components/main-content-header.tsx +10 -6
  88. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -1
  89. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -19
  90. package/src/components/ViewPackagePage/components/package-header.tsx +25 -33
  91. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -18
  92. package/src/components/ViewPackagePage/components/repo-page-content.tsx +12 -5
  93. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
  94. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
  95. package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
  96. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
  97. package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
  98. package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
  99. package/src/components/dialogs/import-component-dialog.tsx +16 -9
  100. package/src/components/dialogs/import-package-dialog.tsx +3 -2
  101. package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
  102. package/src/components/organization/OrganizationCard.tsx +206 -0
  103. package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
  104. package/src/components/organization/OrganizationHeader.tsx +154 -0
  105. package/src/components/organization/OrganizationMembers.tsx +146 -0
  106. package/src/components/package-port/CodeAndPreview.tsx +15 -12
  107. package/src/components/package-port/CodeEditor.tsx +4 -30
  108. package/src/components/package-port/CodeEditorHeader.tsx +123 -61
  109. package/src/components/package-port/EditorNav.tsx +32 -49
  110. package/src/components/preview/ConnectedPackagesList.tsx +8 -8
  111. package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
  112. package/src/components/preview/PackageReleasesDashboard.tsx +23 -11
  113. package/src/components/ui/tree-view.tsx +6 -3
  114. package/src/hooks/use-add-org-member-mutation.ts +51 -0
  115. package/src/hooks/use-create-org-mutation.ts +38 -0
  116. package/src/hooks/use-create-package-mutation.ts +3 -0
  117. package/src/hooks/use-current-package-release.ts +4 -3
  118. package/src/hooks/use-download-zip.ts +2 -2
  119. package/src/hooks/use-global-store.ts +6 -4
  120. package/src/hooks/use-hydration.ts +30 -0
  121. package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
  122. package/src/hooks/use-list-org-members.ts +27 -0
  123. package/src/hooks/use-list-user-orgs.ts +25 -0
  124. package/src/hooks/use-org-by-github-handle.ts +26 -0
  125. package/src/hooks/use-org.ts +24 -0
  126. package/src/hooks/use-organization.ts +42 -0
  127. package/src/hooks/use-package-as-snippet.ts +4 -2
  128. package/src/hooks/use-package-builds.ts +6 -2
  129. package/src/hooks/use-package-files.ts +5 -3
  130. package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
  131. package/src/hooks/use-package-release-images.ts +105 -0
  132. package/src/hooks/use-package-release.ts +2 -2
  133. package/src/hooks/use-package-stars.ts +80 -4
  134. package/src/hooks/use-preview-images.ts +6 -3
  135. package/src/hooks/use-remove-org-member-mutation.ts +32 -0
  136. package/src/hooks/use-update-ai-description-mutation.ts +42 -0
  137. package/src/hooks/use-update-org-mutation.ts +41 -0
  138. package/src/hooks/use-warn-user-on-page-change.ts +71 -4
  139. package/src/hooks/useFileManagement.ts +51 -22
  140. package/src/hooks/useOptimizedPackageFilesLoader.ts +11 -24
  141. package/src/hooks/usePackageFilesLoader.ts +2 -2
  142. package/src/hooks/useUpdatePackageFilesMutation.ts +13 -1
  143. package/src/lib/download-fns/download-gltf-from-circuit-json.ts +1 -1
  144. package/src/lib/download-fns/download-kicad-files.ts +22 -11
  145. package/src/lib/download-fns/download-step.ts +12 -0
  146. package/src/lib/normalize-svg-for-tile.ts +50 -0
  147. package/src/lib/posthog.ts +11 -9
  148. package/src/lib/react-query-api-failure-tracking.ts +148 -0
  149. package/src/lib/sentry.ts +14 -0
  150. package/src/lib/templates/blank-circuit-board-template.ts +0 -4
  151. package/src/lib/ts-lib-cache.ts +122 -7
  152. package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
  153. package/src/lib/utils/findTargetFile.ts +45 -10
  154. package/src/lib/utils/isComponentExported.ts +2 -1
  155. package/src/main.tsx +2 -1
  156. package/src/pages/create-organization.tsx +169 -0
  157. package/src/pages/dashboard.tsx +38 -6
  158. package/src/pages/datasheet.tsx +1 -1
  159. package/src/pages/datasheets.tsx +3 -3
  160. package/src/pages/editor.tsx +4 -6
  161. package/src/pages/landing.tsx +6 -6
  162. package/src/pages/latest.tsx +3 -0
  163. package/src/pages/organization-profile.tsx +199 -0
  164. package/src/pages/organization-settings.tsx +569 -0
  165. package/src/pages/package-editor.tsx +21 -21
  166. package/src/pages/preview-release.tsx +75 -145
  167. package/src/pages/quickstart.tsx +159 -123
  168. package/src/pages/release-detail.tsx +119 -31
  169. package/src/pages/search.tsx +197 -57
  170. package/src/pages/settings-redirect.tsx +44 -0
  171. package/src/pages/trending.tsx +29 -20
  172. package/src/pages/user-profile.tsx +58 -7
  173. package/src/pages/user-settings.tsx +161 -0
  174. package/src/pages/view-package.tsx +30 -16
  175. package/vite.config.ts +9 -0
  176. package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
  177. package/src/components/JLCPCBImportDialog.tsx +0 -280
  178. package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
  179. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -113
  180. package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -56
  181. package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
  182. package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
  183. package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
  184. package/src/components/PageSearchComponent.tsx +0 -148
  185. package/src/pages/package-builds.tsx +0 -33
@@ -0,0 +1,161 @@
1
+ import { useState } from "react"
2
+ import { useLocation, Redirect } from "wouter"
3
+ import { Helmet } from "react-helmet-async"
4
+ import { Button } from "@/components/ui/button"
5
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
6
+ import {
7
+ AlertDialog,
8
+ AlertDialogAction,
9
+ AlertDialogCancel,
10
+ AlertDialogContent,
11
+ AlertDialogDescription,
12
+ AlertDialogFooter,
13
+ AlertDialogHeader,
14
+ AlertDialogTitle,
15
+ } from "@/components/ui/alert-dialog"
16
+ import { useToast } from "@/hooks/use-toast"
17
+ import { useGlobalStore } from "@/hooks/use-global-store"
18
+ import { useHydration } from "@/hooks/use-hydration"
19
+ import { AlertTriangle, Loader2, Trash2 } from "lucide-react"
20
+ import Header from "@/components/Header"
21
+ import Footer from "@/components/Footer"
22
+ import { FullPageLoader } from "@/App"
23
+
24
+ export default function UserSettingsPage() {
25
+ const [, navigate] = useLocation()
26
+ const { toast } = useToast()
27
+ const session = useGlobalStore((s) => s.session)
28
+ const hasHydrated = useHydration()
29
+
30
+ const [showDeleteAccountDialog, setShowDeleteAccountDialog] = useState(false)
31
+
32
+ if (!hasHydrated) {
33
+ return <FullPageLoader />
34
+ }
35
+
36
+ if (!session) {
37
+ return <Redirect to="/" />
38
+ }
39
+
40
+ const pageTitle = "User Settings - tscircuit"
41
+
42
+ const handleDeleteAccount = () => {
43
+ setShowDeleteAccountDialog(true)
44
+ }
45
+
46
+ const confirmDeleteAccount = () => {
47
+ // TODO: Implement delete account functionality
48
+ toast({
49
+ title: "Account deleted",
50
+ description: "Your account has been permanently deleted.",
51
+ })
52
+ navigate("/")
53
+ }
54
+
55
+ return (
56
+ <div className="min-h-screen bg-white">
57
+ <Helmet>
58
+ <title>{pageTitle}</title>
59
+ </Helmet>
60
+ <Header />
61
+
62
+ <section className="w-full px-4 sm:px-6 lg:px-8 xl:px-12 2xl:px-16">
63
+ <div className="max-w-7xl mx-auto py-8">
64
+ <div className="mb-8">
65
+ <div className="flex items-center gap-4 mb-6">
66
+ <Avatar className="h-16 w-16 border-2 border-gray-200 shadow-sm">
67
+ <AvatarImage
68
+ src={`https://github.com/${session.github_username}.png`}
69
+ alt={`${session.github_username} avatar`}
70
+ />
71
+ <AvatarFallback className="text-lg bg-gradient-to-br from-blue-100 to-indigo-100 text-blue-700 font-medium">
72
+ {(session.github_username || session.account_id || "")
73
+ .slice(0, 2)
74
+ .toUpperCase()}
75
+ </AvatarFallback>
76
+ </Avatar>
77
+ <div>
78
+ <h1 className="text-3xl font-bold text-gray-900">
79
+ Account Settings
80
+ </h1>
81
+ <p className="text-gray-600 mt-1">
82
+ Manage your account preferences and settings
83
+ </p>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <div className="space-y-8">
89
+ <div className="bg-white border border-red-200 rounded-xl shadow-sm">
90
+ <div className="px-6 py-5 border-b border-red-200 bg-red-50 rounded-t-xl">
91
+ <h2 className="text-xl font-semibold text-red-900">
92
+ Danger Zone
93
+ </h2>
94
+ <p className="text-sm text-red-600 mt-2">
95
+ Irreversible and destructive actions for your account.
96
+ </p>
97
+ </div>
98
+
99
+ <div className="p-6 lg:p-8">
100
+ <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6">
101
+ <div className="flex-1">
102
+ <h3 className="text-sm font-semibold text-gray-900 mb-2">
103
+ Delete Account
104
+ </h3>
105
+ <p className="text-sm text-gray-500 leading-relaxed">
106
+ Permanently delete your account and all associated data.
107
+ This action cannot be undone and will remove all your
108
+ packages, snippets, and account information.
109
+ </p>
110
+ </div>
111
+ <div className="flex-shrink-0">
112
+ <Button
113
+ variant="destructive"
114
+ onClick={handleDeleteAccount}
115
+ className="bg-red-600 hover:bg-red-700 text-white px-6 py-2.5 text-sm font-medium shadow-sm w-full lg:w-auto"
116
+ >
117
+ <Trash2 className="mr-2 h-4 w-4" />
118
+ Delete Account
119
+ </Button>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </section>
127
+
128
+ <Footer />
129
+
130
+ <AlertDialog
131
+ open={showDeleteAccountDialog}
132
+ onOpenChange={setShowDeleteAccountDialog}
133
+ >
134
+ <AlertDialogContent>
135
+ <AlertDialogHeader>
136
+ <AlertDialogTitle className="flex items-center gap-2 text-red-600">
137
+ <AlertTriangle className="h-5 w-5" />
138
+ Delete Account
139
+ </AlertDialogTitle>
140
+ <AlertDialogDescription>
141
+ Are you absolutely sure you want to delete your account? This
142
+ action is permanent and cannot be undone. All your packages,
143
+ snippets, and account data will be permanently removed.
144
+ </AlertDialogDescription>
145
+ </AlertDialogHeader>
146
+ <AlertDialogFooter>
147
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
148
+ <AlertDialogAction
149
+ onClick={confirmDeleteAccount}
150
+ disabled={true}
151
+ className="bg-red-600 hover:bg-red-700"
152
+ >
153
+ {false && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
154
+ Delete Account
155
+ </AlertDialogAction>
156
+ </AlertDialogFooter>
157
+ </AlertDialogContent>
158
+ </AlertDialog>
159
+ </div>
160
+ )
161
+ }
@@ -1,11 +1,11 @@
1
1
  import RepoPageContent from "@/components/ViewPackagePage/components/repo-page-content"
2
2
  import { usePackageFiles } from "@/hooks/use-package-files"
3
- import { usePackageRelease } from "@/hooks/use-package-release"
3
+ import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
4
4
  import { useLocation, useParams } from "wouter"
5
5
  import { Helmet } from "react-helmet-async"
6
- import { useEffect, useState } from "react"
7
6
  import NotFoundPage from "./404"
8
7
  import { usePackageByName } from "@/hooks/use-package-by-package-name"
8
+ import { SentryNotFoundReporter } from "@/components/SentryNotFoundReporter"
9
9
 
10
10
  export const ViewPackagePage = () => {
11
11
  const { author, packageName } = useParams()
@@ -19,30 +19,44 @@ export const ViewPackagePage = () => {
19
19
  isLoading: isLoadingPackage,
20
20
  } = usePackageByName(packageNameFull)
21
21
  const {
22
- data: packageRelease,
22
+ packageRelease,
23
23
  error: packageReleaseError,
24
24
  isLoading: isLoadingPackageRelease,
25
- } = usePackageRelease(
26
- {
27
- is_latest: true,
28
- package_name: `${author}/${packageName}`,
29
- include_ai_review: true,
30
- },
31
- {
32
- refetchInterval: (data) =>
33
- data?.ai_review_requested && !data.ai_review_text ? 2000 : false,
34
- },
35
- )
25
+ } = useCurrentPackageRelease({
26
+ include_ai_review: true,
27
+ refetchInterval: (data) =>
28
+ data?.ai_review_requested && !data.ai_review_text ? 2000 : false,
29
+ })
36
30
 
37
31
  const { data: packageFiles, isFetched: arePackageFilesFetched } =
38
32
  usePackageFiles(packageRelease?.package_release_id)
39
33
 
40
34
  if (!isLoadingPackage && packageError) {
41
- return <NotFoundPage heading="Package Not Found" />
35
+ return (
36
+ <>
37
+ <SentryNotFoundReporter
38
+ context="package"
39
+ slug={packageNameFull}
40
+ status={(packageError as any)?.status}
41
+ message={packageError.message}
42
+ />
43
+ <NotFoundPage heading="Package Not Found" />
44
+ </>
45
+ )
42
46
  }
43
47
 
44
48
  if (!isLoadingPackageRelease && packageReleaseError?.status == 404) {
45
- return <NotFoundPage heading="Package Not Found" />
49
+ return (
50
+ <>
51
+ <SentryNotFoundReporter
52
+ context="package_release"
53
+ slug={packageNameFull}
54
+ status={packageReleaseError.status}
55
+ message={packageReleaseError.message}
56
+ />
57
+ <NotFoundPage heading="Package Not Found" />
58
+ </>
59
+ )
46
60
  }
47
61
 
48
62
  return (
package/vite.config.ts CHANGED
@@ -183,11 +183,18 @@ export default defineConfig(async (): Promise<UserConfig> => {
183
183
  }
184
184
  }
185
185
 
186
+ if (process.env.SENTRY_DSN) {
187
+ process.env.VITE_SENTRY_DSN = process.env.SENTRY_DSN
188
+ }
189
+
186
190
  return {
187
191
  plugins,
188
192
  define: {
189
193
  global: {},
190
194
  },
195
+ optimizeDeps: {
196
+ exclude: ["@resvg/resvg-js", "@resvg/resvg-js-darwin-arm64"],
197
+ },
191
198
  server: {
192
199
  host: "127.0.0.1",
193
200
  proxy: proxyConfig,
@@ -211,11 +218,13 @@ export default defineConfig(async (): Promise<UserConfig> => {
211
218
  main: path.resolve(__dirname, "index.html"),
212
219
  landing: path.resolve(__dirname, "landing.html"),
213
220
  },
221
+ external: ["@resvg/resvg-js"],
214
222
  },
215
223
  },
216
224
  ssr: {
217
225
  noExternal: ["react-dom/client"],
218
226
  target: "node",
227
+ external: ["@resvg/resvg-js", "@resvg/resvg-js-darwin-arm64"],
219
228
  },
220
229
  resolve: {
221
230
  alias: {
@@ -1,133 +0,0 @@
1
- import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
- import { z } from "zod"
3
- import OpenAI from "openai"
4
-
5
- // Lazy-loaded client instance
6
- let openai: OpenAI | null = null
7
- let cachedReadme: string | null = null
8
-
9
- function getOpenAIClient() {
10
- const apiKey = process.env.VITE_OPENROUTER_API_KEY
11
- if (!apiKey) {
12
- throw new Error("Missing Api Key in env")
13
- }
14
-
15
- if (!openai) {
16
- openai = new OpenAI({
17
- apiKey,
18
- baseURL: "https://openrouter.ai/api/v1",
19
- defaultHeaders: {
20
- "HTTP-Referer": "https://tscircuit.com",
21
- "X-Title": "TSCircuit Editor",
22
- },
23
- })
24
- }
25
-
26
- return openai
27
- }
28
-
29
- // Cache README
30
- async function getCachedReadme(): Promise<string> {
31
- if (cachedReadme !== null) return cachedReadme
32
- const res = await fetch(
33
- "https://raw.githubusercontent.com/tscircuit/props/main/README.md",
34
- )
35
- if (!res.ok) {
36
- throw new Error(`Failed to fetch README: ${res.status}`)
37
- }
38
- cachedReadme = await res.text()
39
- return cachedReadme
40
- }
41
-
42
- async function completion(
43
- openai: OpenAI,
44
- readmeContent: string,
45
- prefix: string,
46
- suffix: string,
47
- model = "openai/gpt-4.1-mini",
48
- language?: string,
49
- ) {
50
- const systemMessage = `You are an expert ${language ? language + " " : ""}programmer working in a TSX (TypeScript + React JSX) environment.
51
-
52
- Below is the README.md for the available components. You MUST use this to determine which components and props are valid.
53
- Only use components explicitly documented under Available Components in the README. Never invent or guess new components. If the user partially types a component that does not exist in the README, do NOT try to complete it.
54
-
55
- ===== README.md START =====
56
- ${readmeContent}
57
- ===== README.md END =====
58
-
59
- Special instruction for the <chip> component:
60
- - Do NOT add chip as a prop (e.g., <chip chip="..."> is invalid).
61
- - Always use this format:
62
- <chip name="U<number>" footprint="<valid footprint>" pinLabels={{}} pcbX={0} pcbY={0} schX={0} schY={0} />
63
- - Determine the next sequential name automatically: e.g. U1, U2, U3.
64
- - Only use valid footprints and pinLabels from the README.
65
- - Some components like <netlabel> do not have a 'name' prop — do not add it for those.
66
-
67
- STRICT rules:
68
- - If partial like "<capa", only append remaining "citor". Never repeat letters.
69
- - If input is "<capacitor", add only props, never repeat tag.
70
- - Always produce exactly one JSX component, starting with "<" if needed.
71
- - If partial doesn’t match any valid component, output nothing.
72
- - Never output two JSX elements. Always end with exactly one "/>".
73
- - Never add duplicate closing "/>".
74
- - Never output the component name as a prop.
75
- - Never add whitespace before your completion.
76
- - If the input is exactly "<", then start with the component name directly (like "resistor ... />") without adding another "<".
77
- - So that the final result is "<resistor ... />", not "<<resistor ... />".
78
- - Never produce a double "<".
79
-
80
- Examples:
81
- - Input: "<FILL_ME>"
82
- Output: <resistor name="R1" footprint="0603" pcbX={5} pcbY={7} schX={1} schY={2} resistance={1000} />
83
- - Input: "<ca<FILL_ME>"
84
- Output: pacitor name="C1" footprint="0805" pcbX={10} pcbY={15} schX={3} schY={4} />
85
- - Input: "<chip<FILL_ME>"
86
- Output: name="U1" footprint="SOIC-8" pinLabels={{}} pcbX={0} pcbY={0} schX={0} schY={0} />
87
- - Input: "<netl<FILL_ME>"
88
- Output: abel name="N1" />
89
- - NEVER output: <capacitor capacitor ... /> or <netnet ... />
90
- - Input: "<"
91
- Output: resistor name="R1" footprint="0603" pcbX={5} pcbY={7} schX={1} schY={2} resistance={1000} />
92
- - Input: "<ca"
93
- Output: pacitor name="C1" footprint="0805" pcbX={10} pcbY={15} schX={3} schY={4} />
94
- - Input: "<capacitor"
95
- Output: capacitance="1000pF" footprint="0805" name="C1" pcbX={10} pcbY={15} schX={3} schY={4} />`
96
-
97
- const chatCompletion = await openai.chat.completions.create({
98
- messages: [
99
- { role: "system", content: systemMessage },
100
- { role: "user", content: `${prefix}<FILL_ME>${suffix}` },
101
- ],
102
- model,
103
- })
104
-
105
- return chatCompletion.choices[0].message?.content ?? ""
106
- }
107
- export default withRouteSpec({
108
- methods: ["POST"],
109
- auth: "session", // ✅ Require user to be signed in
110
- jsonBody: z.object({
111
- prefix: z.string(),
112
- suffix: z.string(),
113
- model: z.string().optional(),
114
- language: z.string().optional(),
115
- }),
116
- jsonResponse: z.object({
117
- prediction: z.string(),
118
- }),
119
- })(async (req, ctx) => {
120
- const openai = getOpenAIClient()
121
- const { prefix, suffix, model, language } = req.jsonBody
122
-
123
- const readmeContent = await getCachedReadme()
124
- const predictionResult = await completion(
125
- openai,
126
- readmeContent,
127
- prefix,
128
- suffix,
129
- model,
130
- language,
131
- )
132
- return ctx.json({ prediction: predictionResult })
133
- })
@@ -1,280 +0,0 @@
1
- import React, { useState } from "react"
2
- import {
3
- Dialog,
4
- DialogContent,
5
- DialogHeader,
6
- DialogTitle,
7
- DialogFooter,
8
- DialogDescription,
9
- } from "@/components/ui/dialog"
10
- import { Button } from "@/components/ui/button"
11
- import { Input } from "@/components/ui/input"
12
- import { useAxios } from "@/hooks/use-axios"
13
- import { useToast } from "@/hooks/use-toast"
14
- import { useLocation } from "wouter"
15
- import { useGlobalStore } from "@/hooks/use-global-store"
16
- import { PrefetchPageLink } from "./PrefetchPageLink"
17
-
18
- interface JLCPCBImportDialogProps {
19
- open: boolean
20
- onOpenChange: (open: boolean) => void
21
- }
22
-
23
- interface ImportState {
24
- isLoading: boolean
25
- error: string | null
26
- existingComponent: {
27
- partNumber: string
28
- username: string
29
- } | null
30
- }
31
-
32
- interface JLCPCBResponse {
33
- ok: boolean
34
- package: {
35
- package_id: string
36
- }
37
- }
38
-
39
- interface APIError {
40
- status: number
41
- data?: {
42
- message?: string
43
- existing_part_number?: string
44
- part_number?: string
45
- error?: {
46
- message?: string
47
- }
48
- }
49
- }
50
-
51
- const extractErrorMessage = (error: APIError): string => {
52
- return (
53
- error?.data?.message ||
54
- error?.data?.error?.message ||
55
- "An unexpected error occurred"
56
- )
57
- }
58
-
59
- const extractExistingPartNumber = (
60
- error: APIError,
61
- fallback: string,
62
- ): string => {
63
- return error?.data?.message || error?.data?.error?.message || fallback
64
- }
65
-
66
- const useJLCPCBImport = () => {
67
- const [state, setState] = useState<ImportState>({
68
- isLoading: false,
69
- error: null,
70
- existingComponent: null,
71
- })
72
-
73
- const axios = useAxios()
74
- const { toast } = useToast()
75
- const [, navigate] = useLocation()
76
- const session = useGlobalStore((s) => s.session)
77
-
78
- const resetState = () => {
79
- setState({
80
- isLoading: false,
81
- error: null,
82
- existingComponent: null,
83
- })
84
- }
85
-
86
- const importComponent = async (partNumber: string) => {
87
- if (!partNumber.startsWith("C") || partNumber.length < 2) {
88
- toast({
89
- title: "Invalid Part Number",
90
- description:
91
- "JLCPCB part numbers should start with 'C' and be at least 2 characters long.",
92
- variant: "destructive",
93
- })
94
- return { success: false }
95
- }
96
-
97
- setState((prev) => ({
98
- ...prev,
99
- isLoading: true,
100
- error: null,
101
- existingComponent: null,
102
- }))
103
-
104
- try {
105
- const response = await axios.post<JLCPCBResponse>(
106
- "/packages/generate_from_jlcpcb",
107
- {
108
- jlcpcb_part_number: partNumber,
109
- },
110
- )
111
-
112
- if (!response.data.ok) {
113
- setState((prev) => ({
114
- ...prev,
115
- isLoading: false,
116
- error: "Failed to generate package from JLCPCB part",
117
- }))
118
- return { success: false }
119
- }
120
-
121
- const { package: generatedPackage } = response.data
122
-
123
- toast({
124
- title: "Import Successful",
125
- description: "JLCPCB component has been imported successfully.",
126
- })
127
-
128
- navigate(`/editor?package_id=${generatedPackage.package_id}`)
129
- return { success: true }
130
- } catch (error: any) {
131
- const apiError = error as APIError
132
-
133
- if (apiError.status === 404) {
134
- setState((prev) => ({
135
- ...prev,
136
- isLoading: false,
137
- error: `Component with JLCPCB part number ${partNumber} not found`,
138
- }))
139
- } else if (apiError.status === 409) {
140
- const existingPartNumber = extractExistingPartNumber(
141
- apiError,
142
- partNumber,
143
- )
144
- setState((prev) => ({
145
- ...prev,
146
- isLoading: false,
147
- existingComponent: {
148
- partNumber: existingPartNumber,
149
- username: session?.github_username || "",
150
- },
151
- }))
152
- } else {
153
- const errorMessage = extractErrorMessage(apiError)
154
- setState((prev) => ({ ...prev, isLoading: false, error: errorMessage }))
155
-
156
- toast({
157
- title: "Import Failed",
158
- description: errorMessage,
159
- variant: "destructive",
160
- })
161
- }
162
-
163
- return { success: false }
164
- }
165
- }
166
-
167
- return {
168
- ...state,
169
- importComponent,
170
- resetState,
171
- }
172
- }
173
-
174
- export function JLCPCBImportDialog({
175
- open,
176
- onOpenChange,
177
- }: JLCPCBImportDialogProps) {
178
- const [partNumber, setPartNumber] = useState("")
179
- const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
180
-
181
- const { isLoading, error, existingComponent, importComponent, resetState } =
182
- useJLCPCBImport()
183
-
184
- const handleImport = async () => {
185
- const result = await importComponent(partNumber)
186
- if (result.success) {
187
- onOpenChange(false)
188
- }
189
- }
190
-
191
- const handleInputChange = (value: string) => {
192
- setPartNumber(value)
193
- resetState()
194
- }
195
-
196
- const createGitHubIssue = () => {
197
- const issueTitle = `[${partNumber}] Failed to import from JLCPCB`
198
- const issueBody = `I tried to import the part number ${partNumber} from JLCPCB, but it failed. Here's the error I got:\n\`\`\`\n${error}\n\`\`\`\n\nCould be an issue in \`fetchEasyEDAComponent\` or \`convertRawEasyEdaToTs\``
199
- const issueLabels = "snippets,good first issue"
200
- const url = `https://github.com/tscircuit/easyeda-converter/issues/new?title=${encodeURIComponent(
201
- issueTitle,
202
- )}&body=${encodeURIComponent(issueBody)}&labels=${encodeURIComponent(issueLabels)}`
203
- window.open(url, "_blank")
204
- }
205
-
206
- return (
207
- <Dialog open={open} onOpenChange={onOpenChange}>
208
- <DialogContent>
209
- <DialogHeader>
210
- <DialogTitle>Import from JLCPCB</DialogTitle>
211
- <DialogDescription>
212
- Enter the JLCPCB part number to import the component.
213
- </DialogDescription>
214
- </DialogHeader>
215
-
216
- <div className="py-4 text-center">
217
- <a
218
- href="https://yaqwsx.github.io/jlcparts/#/"
219
- target="_blank"
220
- rel="noopener noreferrer"
221
- className="text-blue-500 hover:underline opacity-80"
222
- >
223
- JLCPCB Part Search
224
- </a>
225
-
226
- <Input
227
- className="mt-3"
228
- placeholder="Enter JLCPCB part number (e.g., C46749)"
229
- value={partNumber}
230
- disabled={isLoading}
231
- onChange={(e) => handleInputChange(e.target.value)}
232
- onKeyDown={(e) => {
233
- if (
234
- e.key === "Enter" &&
235
- !isLoading &&
236
- isLoggedIn &&
237
- partNumber.trim()
238
- ) {
239
- handleImport()
240
- }
241
- }}
242
- />
243
-
244
- {error && !existingComponent && (
245
- <>
246
- <p className="bg-red-100 p-2 mt-2 pre-wrap">{error}</p>
247
- <div className="flex justify-end mt-2">
248
- <Button variant="default" onClick={createGitHubIssue}>
249
- File Issue on GitHub (prefilled)
250
- </Button>
251
- </div>
252
- </>
253
- )}
254
-
255
- {existingComponent && (
256
- <p className="p-2 mt-2 pre-wrap text-md text-green-600">
257
- This part number has already been imported to your profile.{" "}
258
- <PrefetchPageLink
259
- className="text-blue-500 hover:underline"
260
- href={`/${existingComponent.username}/${existingComponent.partNumber}`}
261
- >
262
- View it here
263
- </PrefetchPageLink>
264
- </p>
265
- )}
266
- </div>
267
-
268
- <DialogFooter>
269
- <Button onClick={handleImport} disabled={isLoading || !isLoggedIn}>
270
- {!isLoggedIn
271
- ? "You must be logged in to import from JLCPCB"
272
- : isLoading
273
- ? "Importing..."
274
- : "Import"}
275
- </Button>
276
- </DialogFooter>
277
- </DialogContent>
278
- </Dialog>
279
- )
280
- }