@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.
- package/.github/workflows/bun-formatcheck.yml +2 -2
- package/.github/workflows/bun-pver-release.yml +3 -3
- package/.github/workflows/bun-test.yml +1 -1
- package/.github/workflows/bun-typecheck.yml +2 -2
- package/.github/workflows/update-snapshots.yml +1 -1
- package/README.md +4 -0
- package/api/generated-index.js +37 -3
- package/biome.json +2 -1
- package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +32 -3
- package/bun-tests/fake-snippets-api/fixtures/preload.ts +18 -0
- package/bun-tests/fake-snippets-api/routes/orgs/add_member.test.ts +26 -0
- package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +37 -0
- package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +52 -0
- package/bun-tests/fake-snippets-api/routes/orgs/list.test.ts +17 -0
- package/bun-tests/fake-snippets-api/routes/orgs/list_members.test.ts +23 -0
- package/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +81 -0
- package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +151 -0
- package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +1 -1
- package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +15 -13
- package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -24
- package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +9 -9
- package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +4 -4
- package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +38 -28
- package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +23 -15
- package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +33 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +4 -4
- package/bun-tests/fake-snippets-api/routes/package_releases/get_image_generation_fields.test.ts +38 -0
- package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +19 -0
- package/bun-tests/fake-snippets-api/routes/packages/fork.test.ts +3 -4
- package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +30 -0
- package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +4 -2
- package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +34 -0
- package/bun.lock +361 -453
- package/bunfig.toml +2 -1
- package/dist/bundle.js +1313 -639
- package/dist/index.d.ts +313 -6
- package/dist/index.js +328 -24
- package/dist/schema.d.ts +290 -1
- package/dist/schema.js +54 -1
- package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
- package/fake-snippets-api/lib/db/db-client.ts +219 -4
- package/fake-snippets-api/lib/db/schema.ts +63 -1
- package/fake-snippets-api/lib/db/seed.ts +100 -0
- package/fake-snippets-api/lib/middleware/with-session-auth.ts +60 -8
- package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +2 -2
- package/fake-snippets-api/lib/public-mapping/public-map-org.ts +33 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +10 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +17 -0
- package/fake-snippets-api/routes/api/orgs/add_member.ts +52 -0
- package/fake-snippets-api/routes/api/orgs/create.ts +48 -0
- package/fake-snippets-api/routes/api/orgs/get.ts +39 -0
- package/fake-snippets-api/routes/api/orgs/list.ts +31 -0
- package/fake-snippets-api/routes/api/orgs/list_members.ts +60 -0
- package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/update.ts +118 -0
- package/fake-snippets-api/routes/api/package_files/get.ts +3 -6
- package/fake-snippets-api/routes/api/package_files/list.ts +7 -4
- package/fake-snippets-api/routes/api/packages/create.ts +57 -10
- package/fake-snippets-api/routes/api/packages/get.ts +23 -0
- package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +13 -11
- package/fake-snippets-api/routes/api/packages/list.ts +29 -2
- package/fake-snippets-api/routes/api/packages/update_ai_description.ts +37 -0
- package/package.json +25 -19
- package/renovate.json +1 -1
- package/scripts/generate-sitemap.ts +1 -1
- package/src/App.tsx +27 -8
- package/src/ContextProviders.tsx +25 -2
- package/src/components/CircuitJsonImportDialog.tsx +1 -1
- package/src/components/CmdKMenu.tsx +281 -247
- package/src/components/DownloadButtonAndMenu.tsx +17 -5
- package/src/components/FileSidebar.tsx +11 -17
- package/src/components/Footer.tsx +8 -9
- package/src/components/Header.tsx +19 -32
- package/src/components/Header2.tsx +16 -32
- package/src/components/HeaderDropdown.tsx +13 -8
- package/src/components/HeaderLogin.tsx +43 -15
- package/src/components/NotFound.tsx +5 -5
- package/src/components/PackageBreadcrumb.tsx +6 -12
- package/src/components/PackageSearchResults.tsx +1 -1
- package/src/components/PrefetchPageLink.tsx +7 -1
- package/src/components/ProfileRouter.tsx +32 -0
- package/src/components/SearchComponent.tsx +12 -8
- package/src/components/SentryNotFoundReporter.tsx +44 -0
- package/src/components/UserCard.tsx +80 -0
- package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
- package/src/components/ViewPackagePage/components/important-files-view.tsx +105 -34
- package/src/components/ViewPackagePage/components/main-content-header.tsx +10 -6
- package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -1
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -19
- package/src/components/ViewPackagePage/components/package-header.tsx +25 -33
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -18
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +12 -5
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
- package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
- package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
- package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
- package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
- package/src/components/dialogs/import-component-dialog.tsx +16 -9
- package/src/components/dialogs/import-package-dialog.tsx +3 -2
- package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
- package/src/components/organization/OrganizationCard.tsx +206 -0
- package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
- package/src/components/organization/OrganizationHeader.tsx +154 -0
- package/src/components/organization/OrganizationMembers.tsx +146 -0
- package/src/components/package-port/CodeAndPreview.tsx +15 -12
- package/src/components/package-port/CodeEditor.tsx +4 -30
- package/src/components/package-port/CodeEditorHeader.tsx +123 -61
- package/src/components/package-port/EditorNav.tsx +32 -49
- package/src/components/preview/ConnectedPackagesList.tsx +8 -8
- package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
- package/src/components/preview/PackageReleasesDashboard.tsx +23 -11
- package/src/components/ui/tree-view.tsx +6 -3
- package/src/hooks/use-add-org-member-mutation.ts +51 -0
- package/src/hooks/use-create-org-mutation.ts +38 -0
- package/src/hooks/use-create-package-mutation.ts +3 -0
- package/src/hooks/use-current-package-release.ts +4 -3
- package/src/hooks/use-download-zip.ts +2 -2
- package/src/hooks/use-global-store.ts +6 -4
- package/src/hooks/use-hydration.ts +30 -0
- package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
- package/src/hooks/use-list-org-members.ts +27 -0
- package/src/hooks/use-list-user-orgs.ts +25 -0
- package/src/hooks/use-org-by-github-handle.ts +26 -0
- package/src/hooks/use-org.ts +24 -0
- package/src/hooks/use-organization.ts +42 -0
- package/src/hooks/use-package-as-snippet.ts +4 -2
- package/src/hooks/use-package-builds.ts +6 -2
- package/src/hooks/use-package-files.ts +5 -3
- package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
- package/src/hooks/use-package-release-images.ts +105 -0
- package/src/hooks/use-package-release.ts +2 -2
- package/src/hooks/use-package-stars.ts +80 -4
- package/src/hooks/use-preview-images.ts +6 -3
- package/src/hooks/use-remove-org-member-mutation.ts +32 -0
- package/src/hooks/use-update-ai-description-mutation.ts +42 -0
- package/src/hooks/use-update-org-mutation.ts +41 -0
- package/src/hooks/use-warn-user-on-page-change.ts +71 -4
- package/src/hooks/useFileManagement.ts +51 -22
- package/src/hooks/useOptimizedPackageFilesLoader.ts +11 -24
- package/src/hooks/usePackageFilesLoader.ts +2 -2
- package/src/hooks/useUpdatePackageFilesMutation.ts +13 -1
- package/src/lib/download-fns/download-gltf-from-circuit-json.ts +1 -1
- package/src/lib/download-fns/download-kicad-files.ts +22 -11
- package/src/lib/download-fns/download-step.ts +12 -0
- package/src/lib/normalize-svg-for-tile.ts +50 -0
- package/src/lib/posthog.ts +11 -9
- package/src/lib/react-query-api-failure-tracking.ts +148 -0
- package/src/lib/sentry.ts +14 -0
- package/src/lib/templates/blank-circuit-board-template.ts +0 -4
- package/src/lib/ts-lib-cache.ts +122 -7
- package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
- package/src/lib/utils/findTargetFile.ts +45 -10
- package/src/lib/utils/isComponentExported.ts +2 -1
- package/src/main.tsx +2 -1
- package/src/pages/create-organization.tsx +169 -0
- package/src/pages/dashboard.tsx +38 -6
- package/src/pages/datasheet.tsx +1 -1
- package/src/pages/datasheets.tsx +3 -3
- package/src/pages/editor.tsx +4 -6
- package/src/pages/landing.tsx +6 -6
- package/src/pages/latest.tsx +3 -0
- package/src/pages/organization-profile.tsx +199 -0
- package/src/pages/organization-settings.tsx +569 -0
- package/src/pages/package-editor.tsx +21 -21
- package/src/pages/preview-release.tsx +75 -145
- package/src/pages/quickstart.tsx +159 -123
- package/src/pages/release-detail.tsx +119 -31
- package/src/pages/search.tsx +197 -57
- package/src/pages/settings-redirect.tsx +44 -0
- package/src/pages/trending.tsx +29 -20
- package/src/pages/user-profile.tsx +58 -7
- package/src/pages/user-settings.tsx +161 -0
- package/src/pages/view-package.tsx +30 -16
- package/vite.config.ts +9 -0
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
- package/src/components/JLCPCBImportDialog.tsx +0 -280
- package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -113
- package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -56
- package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
- package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
- package/src/components/PageSearchComponent.tsx +0 -148
- 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 {
|
|
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
|
-
|
|
22
|
+
packageRelease,
|
|
23
23
|
error: packageReleaseError,
|
|
24
24
|
isLoading: isLoadingPackageRelease,
|
|
25
|
-
} =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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
|
|
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
|
-
}
|