@tscircuit/fake-snippets 0.0.109 → 0.0.110
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 +31 -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 +99 -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 +349 -453
- package/bunfig.toml +2 -1
- package/dist/bundle.js +1253 -624
- package/dist/index.d.ts +291 -4
- package/dist/index.js +323 -23
- package/dist/schema.d.ts +274 -1
- package/dist/schema.js +52 -1
- package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
- package/fake-snippets-api/lib/db/db-client.ts +214 -3
- package/fake-snippets-api/lib/db/schema.ts +61 -0
- package/fake-snippets-api/lib/db/seed.ts +100 -0
- package/fake-snippets-api/lib/middleware/with-session-auth.ts +1 -1
- 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 +32 -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 +46 -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 +67 -0
- package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/update.ts +93 -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 +54 -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 +24 -20
- package/renovate.json +1 -1
- package/scripts/generate-sitemap.ts +1 -1
- package/src/App.tsx +29 -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 +3 -4
- 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/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 +204 -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-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 +12 -11
- 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 +168 -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 +566 -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 +192 -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/view-package.tsx +7 -13
- 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
|
@@ -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
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
type ErrorObject =
|
|
2
|
-
| {
|
|
3
|
-
message: string
|
|
4
|
-
}
|
|
5
|
-
| string
|
|
6
|
-
|
|
7
|
-
const getErrorText = (error: ErrorObject | string) => {
|
|
8
|
-
if (typeof error === "string") {
|
|
9
|
-
return error
|
|
10
|
-
}
|
|
11
|
-
return error.message
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const LogContent = ({
|
|
15
|
-
logs,
|
|
16
|
-
error,
|
|
17
|
-
}: {
|
|
18
|
-
logs: Array<{
|
|
19
|
-
type?: "info" | "success" | "error"
|
|
20
|
-
msg?: string
|
|
21
|
-
message?: string
|
|
22
|
-
timestamp?: string | number
|
|
23
|
-
[key: string]: unknown
|
|
24
|
-
}>
|
|
25
|
-
error?: ErrorObject | string | null
|
|
26
|
-
}) => {
|
|
27
|
-
return (
|
|
28
|
-
<div className="font-mono text-xs space-y-1 min-w-0">
|
|
29
|
-
{logs.map((log, i) => {
|
|
30
|
-
const { type, msg, message, timestamp, ...rest } = log
|
|
31
|
-
const text = msg ?? message
|
|
32
|
-
if (!text) return null
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div
|
|
36
|
-
key={i}
|
|
37
|
-
className={`break-words whitespace-pre-wrap ${
|
|
38
|
-
type === "error"
|
|
39
|
-
? "text-red-600"
|
|
40
|
-
: type === "success"
|
|
41
|
-
? "text-green-600"
|
|
42
|
-
: "text-gray-600"
|
|
43
|
-
}`}
|
|
44
|
-
>
|
|
45
|
-
{timestamp !== undefined && (
|
|
46
|
-
<span className="text-gray-500 whitespace-nowrap">
|
|
47
|
-
{new Date(Number(timestamp)).toLocaleTimeString()}
|
|
48
|
-
</span>
|
|
49
|
-
)}
|
|
50
|
-
{timestamp !== undefined && " "}
|
|
51
|
-
<span className="break-all">{text}</span>
|
|
52
|
-
{Object.keys(rest).filter((k) => k !== "package_release_id")
|
|
53
|
-
.length > 0 && (
|
|
54
|
-
<span className="text-gray-500">
|
|
55
|
-
{" "}
|
|
56
|
-
{Object.entries(rest)
|
|
57
|
-
.filter(([key]) => key !== "package_release_id")
|
|
58
|
-
.map(([key, value]) => `${key}: ${String(value)}`)
|
|
59
|
-
.join(" ")}
|
|
60
|
-
</span>
|
|
61
|
-
)}
|
|
62
|
-
</div>
|
|
63
|
-
)
|
|
64
|
-
})}
|
|
65
|
-
{error && (
|
|
66
|
-
<div className="text-red-600 break-words whitespace-pre-wrap">
|
|
67
|
-
{getErrorText(error)}
|
|
68
|
-
</div>
|
|
69
|
-
)}
|
|
70
|
-
</div>
|
|
71
|
-
)
|
|
72
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
|
|
2
|
-
import { PackageRelease } from "fake-snippets-api/lib/db/schema"
|
|
3
|
-
import { useState } from "react"
|
|
4
|
-
import { LogContent } from "./LogContent"
|
|
5
|
-
import { BuildPreviewContent } from "./build-preview-content"
|
|
6
|
-
import { CollapsibleSection } from "./collapsible-section"
|
|
7
|
-
import { PackageBuildDetailsPanel } from "./package-build-details-panel"
|
|
8
|
-
import { PackageBuildHeader } from "./package-build-header"
|
|
9
|
-
|
|
10
|
-
function computeDuration(
|
|
11
|
-
startedAt: string | null | undefined,
|
|
12
|
-
completedAt: string | null | undefined,
|
|
13
|
-
) {
|
|
14
|
-
if (!startedAt || !completedAt) return ""
|
|
15
|
-
return `${Math.floor((new Date(completedAt).getTime() - new Date(startedAt).getTime()) / 1000)}s`
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const PackageBuildDetailsPage = () => {
|
|
19
|
-
const { packageRelease } = useCurrentPackageRelease({
|
|
20
|
-
include_logs: true,
|
|
21
|
-
refetchInterval: 2000,
|
|
22
|
-
})
|
|
23
|
-
const [openSections, setOpenSections] = useState<Record<string, boolean>>({})
|
|
24
|
-
|
|
25
|
-
const {
|
|
26
|
-
circuit_json_build_logs,
|
|
27
|
-
circuit_json_build_completed_at,
|
|
28
|
-
circuit_json_build_in_progress,
|
|
29
|
-
circuit_json_build_is_stale,
|
|
30
|
-
circuit_json_build_started_at,
|
|
31
|
-
circuit_json_build_error,
|
|
32
|
-
circuit_json_build_error_last_updated_at,
|
|
33
|
-
transpilation_completed_at,
|
|
34
|
-
transpilation_in_progress,
|
|
35
|
-
transpilation_is_stale,
|
|
36
|
-
transpilation_logs,
|
|
37
|
-
transpilation_started_at,
|
|
38
|
-
circuit_json_build_display_status,
|
|
39
|
-
transpilation_display_status,
|
|
40
|
-
transpilation_error,
|
|
41
|
-
} = packageRelease ?? ({} as Partial<PackageRelease>)
|
|
42
|
-
|
|
43
|
-
const toggleSection = (section: string) => {
|
|
44
|
-
setOpenSections((prev) => ({
|
|
45
|
-
...prev,
|
|
46
|
-
[section]: !prev[section],
|
|
47
|
-
}))
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<div className="min-h-screen bg-gray-50 text-gray-900">
|
|
52
|
-
<PackageBuildHeader />
|
|
53
|
-
|
|
54
|
-
<div className="px-4 sm:px-6 py-4 sm:py-6 container mx-auto max-w-7xl">
|
|
55
|
-
{/* Main Content */}
|
|
56
|
-
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6 mb-6 sm:mb-8 items-start">
|
|
57
|
-
{/* Preview Section */}
|
|
58
|
-
<div className="lg:col-span-2">
|
|
59
|
-
<div className="bg-white border border-gray-200 rounded-lg p-4 flex items-center justify-center min-h-[280px] sm:min-h-[340px] lg:max-h-[420px]">
|
|
60
|
-
<BuildPreviewContent />
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
{/* Details Panel */}
|
|
65
|
-
<PackageBuildDetailsPanel />
|
|
66
|
-
</div>
|
|
67
|
-
|
|
68
|
-
{/* Collapsible Sections */}
|
|
69
|
-
<div className="space-y-4 mb-6 sm:mb-8">
|
|
70
|
-
<CollapsibleSection
|
|
71
|
-
title="Transpilation Logs"
|
|
72
|
-
duration={computeDuration(
|
|
73
|
-
transpilation_started_at,
|
|
74
|
-
transpilation_completed_at,
|
|
75
|
-
)}
|
|
76
|
-
displayStatus={transpilation_display_status}
|
|
77
|
-
isOpen={openSections.summary}
|
|
78
|
-
onToggle={() => toggleSection("summary")}
|
|
79
|
-
>
|
|
80
|
-
<LogContent
|
|
81
|
-
logs={
|
|
82
|
-
transpilation_logs ?? [
|
|
83
|
-
{ msg: "No transpilation logs available" },
|
|
84
|
-
]
|
|
85
|
-
}
|
|
86
|
-
error={transpilation_error}
|
|
87
|
-
/>
|
|
88
|
-
</CollapsibleSection>
|
|
89
|
-
|
|
90
|
-
<CollapsibleSection
|
|
91
|
-
title="Circuit JSON Build Logs"
|
|
92
|
-
duration={computeDuration(
|
|
93
|
-
circuit_json_build_started_at,
|
|
94
|
-
circuit_json_build_completed_at,
|
|
95
|
-
)}
|
|
96
|
-
displayStatus={circuit_json_build_display_status}
|
|
97
|
-
isOpen={openSections.logs}
|
|
98
|
-
onToggle={() => toggleSection("logs")}
|
|
99
|
-
>
|
|
100
|
-
<LogContent
|
|
101
|
-
logs={
|
|
102
|
-
circuit_json_build_logs ?? [
|
|
103
|
-
{ msg: "No Circuit JSON logs available" },
|
|
104
|
-
]
|
|
105
|
-
}
|
|
106
|
-
error={circuit_json_build_error!}
|
|
107
|
-
/>
|
|
108
|
-
</CollapsibleSection>
|
|
109
|
-
</div>
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
)
|
|
113
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
|
|
2
|
-
import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
|
|
3
|
-
import { useState } from "react"
|
|
4
|
-
import { CircuitBoard } from "lucide-react"
|
|
5
|
-
|
|
6
|
-
export function BuildPreviewContent() {
|
|
7
|
-
const { packageRelease } = useCurrentPackageRelease({ refetchInterval: 2000 })
|
|
8
|
-
const { packageInfo } = useCurrentPackageInfo()
|
|
9
|
-
const [imageError, setImageError] = useState(false)
|
|
10
|
-
const [imageLoading, setImageLoading] = useState(true)
|
|
11
|
-
|
|
12
|
-
if (!packageRelease) {
|
|
13
|
-
return (
|
|
14
|
-
<div className="flex items-center justify-center w-full h-full">
|
|
15
|
-
<div className="w-32 h-32 sm:w-48 sm:h-48 bg-gray-200 rounded animate-pulse-slow"></div>
|
|
16
|
-
</div>
|
|
17
|
-
)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<div className="flex items-center justify-center w-full h-full">
|
|
22
|
-
<div className="rounded overflow-hidden w-full max-w-full">
|
|
23
|
-
{imageError ? (
|
|
24
|
-
<div className="flex flex-col items-center justify-center bg-gray-50 border border-gray-300 rounded-lg p-8 sm:p-12 lg:p-16 min-h-[240px] sm:min-h-[300px] lg:min-h-[360px]">
|
|
25
|
-
<CircuitBoard className="w-12 h-12 sm:w-16 sm:h-16 lg:w-20 lg:h-20 text-gray-400 mb-4" />
|
|
26
|
-
<h3 className="text-lg sm:text-xl font-medium text-gray-600 mb-2">
|
|
27
|
-
Preview Not Available
|
|
28
|
-
</h3>
|
|
29
|
-
<p className="text-sm sm:text-base text-gray-500 text-center max-w-sm">
|
|
30
|
-
The build preview image could not be loaded. This may be because
|
|
31
|
-
the build is still processing or the image is not available.
|
|
32
|
-
</p>
|
|
33
|
-
</div>
|
|
34
|
-
) : (
|
|
35
|
-
<>
|
|
36
|
-
{imageLoading && (
|
|
37
|
-
<div className="flex items-center justify-center bg-gray-100 rounded-lg min-h-[240px] sm:min-h-[300px] lg:min-h-[360px]">
|
|
38
|
-
<div className="w-16 h-16 sm:w-20 sm:h-20 bg-gray-200 rounded animate-pulse"></div>
|
|
39
|
-
</div>
|
|
40
|
-
)}
|
|
41
|
-
<img
|
|
42
|
-
src={`https://api.tscircuit.com/packages/images/${packageInfo?.name}/pcb.png`}
|
|
43
|
-
alt="Package build preview"
|
|
44
|
-
className={`object-contain rounded w-full h-auto max-h-[240px] sm:max-h-[300px] lg:max-h-[360px] ${imageLoading ? "hidden" : "block"}`}
|
|
45
|
-
onLoad={() => setImageLoading(false)}
|
|
46
|
-
onError={() => {
|
|
47
|
-
setImageError(true)
|
|
48
|
-
setImageLoading(false)
|
|
49
|
-
}}
|
|
50
|
-
/>
|
|
51
|
-
</>
|
|
52
|
-
)}
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
)
|
|
56
|
-
}
|