@tscircuit/fake-snippets 0.0.88 → 0.0.90
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/api/generated-index.js +96 -14
- package/bun-tests/fake-snippets-api/routes/proxy.test.ts +42 -0
- package/bun.lock +196 -215
- package/dist/bundle.js +596 -370
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +134 -0
- package/fake-snippets-api/routes/api/proxy.ts +128 -0
- package/package.json +59 -48
- package/renovate.json +2 -1
- package/src/App.tsx +67 -3
- package/src/ContextProviders.tsx +2 -0
- package/src/build-watcher.ts +52 -0
- package/src/components/CircuitJsonImportDialog.tsx +1 -1
- package/src/components/CmdKMenu.tsx +533 -197
- package/src/components/DownloadButtonAndMenu.tsx +104 -26
- package/src/components/FileSidebar.tsx +11 -1
- package/src/components/Header2.tsx +7 -2
- package/src/components/PackageBuildsPage/LogContent.tsx +25 -22
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +6 -6
- package/src/components/PackageBuildsPage/build-preview-content.tsx +5 -5
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +15 -13
- package/src/components/PackageBuildsPage/package-build-header.tsx +19 -28
- package/src/components/PackageCard.tsx +66 -16
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/SuspenseRunFrame.tsx +14 -2
- package/src/components/ViewPackagePage/components/important-files-view.tsx +90 -17
- package/src/components/ViewPackagePage/components/main-content-header.tsx +26 -2
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +2 -2
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +35 -30
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +2 -2
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +20 -12
- package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +0 -7
- package/src/components/ViewPackagePage/utils/fuzz-search.ts +121 -0
- package/src/components/ViewPackagePage/utils/is-hidden-file.ts +4 -0
- package/src/components/dialogs/confirm-delete-package-dialog.tsx +1 -1
- package/src/components/dialogs/confirm-discard-changes-dialog.tsx +73 -0
- package/src/components/dialogs/edit-package-details-dialog.tsx +2 -2
- package/src/components/dialogs/view-ts-files-dialog.tsx +478 -42
- package/src/components/package-port/CodeAndPreview.tsx +17 -16
- package/src/components/package-port/CodeEditor.tsx +138 -17
- package/src/components/package-port/CodeEditorHeader.tsx +44 -4
- package/src/components/package-port/EditorNav.tsx +42 -29
- package/src/components/package-port/GlobalFindReplace.tsx +681 -0
- package/src/components/package-port/QuickOpen.tsx +241 -0
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/tree-view.tsx +1 -1
- package/src/global.d.ts +3 -0
- package/src/hooks/use-ai-review.ts +31 -0
- package/src/hooks/use-code-completion-ai-api.ts +3 -3
- package/src/hooks/use-current-package-release.ts +5 -1
- package/src/hooks/use-delete-package.ts +6 -2
- package/src/hooks/use-download-zip.ts +50 -0
- package/src/hooks/use-hotkey.ts +116 -0
- package/src/hooks/use-package-by-package-id.ts +1 -0
- package/src/hooks/use-package-by-package-name.ts +1 -0
- package/src/hooks/use-package-files.ts +3 -0
- package/src/hooks/use-package-release.ts +5 -1
- package/src/hooks/use-package.ts +1 -0
- package/src/hooks/use-request-ai-review-mutation.ts +14 -6
- package/src/hooks/use-snippet.ts +1 -0
- package/src/hooks/useFileManagement.ts +28 -10
- package/src/hooks/usePackageFilesLoader.ts +3 -1
- package/src/index.css +11 -0
- package/src/lib/decodeUrlHashToFsMap.ts +17 -0
- package/src/lib/download-fns/download-circuit-png.ts +88 -0
- package/src/lib/download-fns/download-png-utils.ts +31 -0
- package/src/lib/encodeFsMapToUrlHash.ts +13 -0
- package/src/lib/populate-query-cache-with-ssr-data.ts +7 -0
- package/src/lib/ts-lib-cache.ts +47 -0
- package/src/lib/types.ts +2 -0
- package/src/lib/utils/findTargetFile.ts +1 -1
- package/src/lib/utils/package-utils.ts +10 -0
- package/src/main.tsx +7 -0
- package/src/pages/dashboard.tsx +18 -5
- package/src/pages/view-package.tsx +15 -7
- package/src/types/package.ts +4 -0
- package/vite.config.ts +100 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import "dotenv/config"
|
|
2
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
3
|
+
import { z } from "zod"
|
|
4
|
+
import OpenAI from "openai"
|
|
5
|
+
|
|
6
|
+
// Lazy-loaded client instance
|
|
7
|
+
let openai: OpenAI | null = null
|
|
8
|
+
let cachedReadme: string | null = null
|
|
9
|
+
|
|
10
|
+
function getOpenAIClient() {
|
|
11
|
+
const apiKey = process.env.VITE_OPENROUTER_API_KEY
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
throw new Error("Missing Api Key in env")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!openai) {
|
|
17
|
+
openai = new OpenAI({
|
|
18
|
+
apiKey,
|
|
19
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
20
|
+
defaultHeaders: {
|
|
21
|
+
"HTTP-Referer": "https://tscircuit.com",
|
|
22
|
+
"X-Title": "TSCircuit Editor",
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return openai
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Cache README
|
|
31
|
+
async function getCachedReadme(): Promise<string> {
|
|
32
|
+
if (cachedReadme !== null) return cachedReadme
|
|
33
|
+
const res = await fetch(
|
|
34
|
+
"https://raw.githubusercontent.com/tscircuit/props/main/README.md",
|
|
35
|
+
)
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
throw new Error(`Failed to fetch README: ${res.status}`)
|
|
38
|
+
}
|
|
39
|
+
cachedReadme = await res.text()
|
|
40
|
+
return cachedReadme
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function completion(
|
|
44
|
+
openai: OpenAI,
|
|
45
|
+
readmeContent: string,
|
|
46
|
+
prefix: string,
|
|
47
|
+
suffix: string,
|
|
48
|
+
model = "openai/gpt-4.1-mini",
|
|
49
|
+
language?: string,
|
|
50
|
+
) {
|
|
51
|
+
const systemMessage = `You are an expert ${language ? language + " " : ""}programmer working in a TSX (TypeScript + React JSX) environment.
|
|
52
|
+
|
|
53
|
+
Below is the README.md for the available components. You MUST use this to determine which components and props are valid.
|
|
54
|
+
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.
|
|
55
|
+
|
|
56
|
+
===== README.md START =====
|
|
57
|
+
${readmeContent}
|
|
58
|
+
===== README.md END =====
|
|
59
|
+
|
|
60
|
+
Special instruction for the <chip> component:
|
|
61
|
+
- Do NOT add chip as a prop (e.g., <chip chip="..."> is invalid).
|
|
62
|
+
- Always use this format:
|
|
63
|
+
<chip name="U<number>" footprint="<valid footprint>" pinLabels={{}} pcbX={0} pcbY={0} schX={0} schY={0} />
|
|
64
|
+
- Determine the next sequential name automatically: e.g. U1, U2, U3.
|
|
65
|
+
- Only use valid footprints and pinLabels from the README.
|
|
66
|
+
- Some components like <netlabel> do not have a 'name' prop — do not add it for those.
|
|
67
|
+
|
|
68
|
+
STRICT rules:
|
|
69
|
+
- If partial like "<capa", only append remaining "citor". Never repeat letters.
|
|
70
|
+
- If input is "<capacitor", add only props, never repeat tag.
|
|
71
|
+
- Always produce exactly one JSX component, starting with "<" if needed.
|
|
72
|
+
- If partial doesn’t match any valid component, output nothing.
|
|
73
|
+
- Never output two JSX elements. Always end with exactly one "/>".
|
|
74
|
+
- Never add duplicate closing "/>".
|
|
75
|
+
- Never output the component name as a prop.
|
|
76
|
+
- Never add whitespace before your completion.
|
|
77
|
+
- If the input is exactly "<", then start with the component name directly (like "resistor ... />") without adding another "<".
|
|
78
|
+
- So that the final result is "<resistor ... />", not "<<resistor ... />".
|
|
79
|
+
- Never produce a double "<".
|
|
80
|
+
|
|
81
|
+
Examples:
|
|
82
|
+
- Input: "<FILL_ME>"
|
|
83
|
+
Output: <resistor name="R1" footprint="0603" pcbX={5} pcbY={7} schX={1} schY={2} resistance={1000} />
|
|
84
|
+
- Input: "<ca<FILL_ME>"
|
|
85
|
+
Output: pacitor name="C1" footprint="0805" pcbX={10} pcbY={15} schX={3} schY={4} />
|
|
86
|
+
- Input: "<chip<FILL_ME>"
|
|
87
|
+
Output: name="U1" footprint="SOIC-8" pinLabels={{}} pcbX={0} pcbY={0} schX={0} schY={0} />
|
|
88
|
+
- Input: "<netl<FILL_ME>"
|
|
89
|
+
Output: abel name="N1" />
|
|
90
|
+
- NEVER output: <capacitor capacitor ... /> or <netnet ... />
|
|
91
|
+
- Input: "<"
|
|
92
|
+
Output: resistor name="R1" footprint="0603" pcbX={5} pcbY={7} schX={1} schY={2} resistance={1000} />
|
|
93
|
+
- Input: "<ca"
|
|
94
|
+
Output: pacitor name="C1" footprint="0805" pcbX={10} pcbY={15} schX={3} schY={4} />
|
|
95
|
+
- Input: "<capacitor"
|
|
96
|
+
Output: capacitance="1000pF" footprint="0805" name="C1" pcbX={10} pcbY={15} schX={3} schY={4} />`
|
|
97
|
+
|
|
98
|
+
const chatCompletion = await openai.chat.completions.create({
|
|
99
|
+
messages: [
|
|
100
|
+
{ role: "system", content: systemMessage },
|
|
101
|
+
{ role: "user", content: `${prefix}<FILL_ME>${suffix}` },
|
|
102
|
+
],
|
|
103
|
+
model,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
return chatCompletion.choices[0].message?.content ?? ""
|
|
107
|
+
}
|
|
108
|
+
export default withRouteSpec({
|
|
109
|
+
methods: ["POST"],
|
|
110
|
+
auth: "session", // ✅ Require user to be signed in
|
|
111
|
+
jsonBody: z.object({
|
|
112
|
+
prefix: z.string(),
|
|
113
|
+
suffix: z.string(),
|
|
114
|
+
model: z.string().optional(),
|
|
115
|
+
language: z.string().optional(),
|
|
116
|
+
}),
|
|
117
|
+
jsonResponse: z.object({
|
|
118
|
+
prediction: z.string(),
|
|
119
|
+
}),
|
|
120
|
+
})(async (req, ctx) => {
|
|
121
|
+
const openai = getOpenAIClient()
|
|
122
|
+
const { prefix, suffix, model, language } = req.jsonBody
|
|
123
|
+
|
|
124
|
+
const readmeContent = await getCachedReadme()
|
|
125
|
+
const predictionResult = await completion(
|
|
126
|
+
openai,
|
|
127
|
+
readmeContent,
|
|
128
|
+
prefix,
|
|
129
|
+
suffix,
|
|
130
|
+
model,
|
|
131
|
+
language,
|
|
132
|
+
)
|
|
133
|
+
return ctx.json({ prediction: predictionResult })
|
|
134
|
+
})
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
|
|
4
|
+
const PROXY_HEADERS = [
|
|
5
|
+
"X-Target-Url",
|
|
6
|
+
"X-Sender-Origin",
|
|
7
|
+
"X-Sender-Host",
|
|
8
|
+
"X-Sender-Referer",
|
|
9
|
+
"X-Sender-User-Agent",
|
|
10
|
+
"X-Sender-Cookie",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
const ALLOWED_DOMAINS: Array<{ domain: string; routes?: string[] }> = [
|
|
14
|
+
{ domain: "easyeda.com" },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
function isAllowedDomain(url: string) {
|
|
18
|
+
try {
|
|
19
|
+
const { hostname, pathname, port } = new URL(url)
|
|
20
|
+
const allowedDomain = ALLOWED_DOMAINS.find(
|
|
21
|
+
(domain) => domain.domain === hostname,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if (allowedDomain) {
|
|
25
|
+
if (
|
|
26
|
+
!allowedDomain.routes ||
|
|
27
|
+
allowedDomain.routes.some((route) => pathname.startsWith(route))
|
|
28
|
+
) {
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return hostname === "localhost" || (hostname === "127.0.0.1" && port)
|
|
34
|
+
} catch {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default withRouteSpec({
|
|
40
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
|
|
41
|
+
jsonResponse: z.any(),
|
|
42
|
+
auth: "session",
|
|
43
|
+
})(async (req, ctx) => {
|
|
44
|
+
if (req.headers.get("X-proxied")) {
|
|
45
|
+
return ctx.json(
|
|
46
|
+
{ error: "Recursive proxy calls are not allowed" },
|
|
47
|
+
{ status: 403 },
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const targetUrl = req.headers.get("X-Target-Url")
|
|
52
|
+
|
|
53
|
+
if (!targetUrl) {
|
|
54
|
+
return ctx.json(
|
|
55
|
+
{ error: "X-Target-Url header is required" },
|
|
56
|
+
{ status: 400 },
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!isAllowedDomain(targetUrl)) {
|
|
61
|
+
return ctx.json({ error: "Domain not allowed" }, { status: 403 })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let body: string | undefined
|
|
65
|
+
if (["POST", "PUT", "PATCH"].includes(req.method)) {
|
|
66
|
+
body = await req.clone().text()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const headers = new Headers(req.headers)
|
|
70
|
+
|
|
71
|
+
const senderOrigin = req.headers.get("X-Sender-Origin")
|
|
72
|
+
if (senderOrigin) {
|
|
73
|
+
headers.set("Origin", senderOrigin)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const senderHost = req.headers.get("X-Sender-Host")
|
|
77
|
+
if (senderHost) {
|
|
78
|
+
const hostValue = senderHost.replace(/^https?:\/\//, "")
|
|
79
|
+
headers.set("Host", hostValue)
|
|
80
|
+
headers.set("authority", hostValue)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const senderReferer = req.headers.get("X-Sender-Referer")
|
|
84
|
+
if (senderReferer) {
|
|
85
|
+
headers.set("Referer", senderReferer)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const senderUserAgent = req.headers.get("X-Sender-User-Agent")
|
|
89
|
+
if (senderUserAgent) {
|
|
90
|
+
headers.set("User-Agent", senderUserAgent)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const senderCookie = req.headers.get("X-Sender-Cookie")
|
|
94
|
+
if (senderCookie) {
|
|
95
|
+
headers.set("Cookie", senderCookie)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const header of PROXY_HEADERS) {
|
|
99
|
+
headers.delete(header)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
headers.delete("content-encoding")
|
|
103
|
+
headers.delete("accept-encoding")
|
|
104
|
+
headers.set("X-proxied", "true")
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const response = await fetch(targetUrl, {
|
|
108
|
+
method: req.method,
|
|
109
|
+
headers: headers,
|
|
110
|
+
body: ["GET", "HEAD"].includes(req.method) ? undefined : body,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const responseHeaders = new Headers(response.headers)
|
|
114
|
+
responseHeaders.delete("content-encoding")
|
|
115
|
+
|
|
116
|
+
return new Response(response.body, {
|
|
117
|
+
status: response.status,
|
|
118
|
+
statusText: response.statusText,
|
|
119
|
+
headers: responseHeaders,
|
|
120
|
+
})
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error("Proxy error:", error)
|
|
123
|
+
return ctx.json(
|
|
124
|
+
{ error: { message: "Failed to proxy request" } },
|
|
125
|
+
{ status: 502 },
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fake-snippets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.90",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,8 +33,11 @@
|
|
|
33
33
|
"generate-sitemap": "bun run scripts/generate-sitemap.ts"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
+
"@anthropic-ai/sdk": "^0.27.3",
|
|
36
37
|
"@babel/preset-react": "^7.25.9",
|
|
37
38
|
"@babel/preset-typescript": "^7.26.0",
|
|
39
|
+
"@babel/standalone": "^7.26.2",
|
|
40
|
+
"@biomejs/biome": "^1.9.2",
|
|
38
41
|
"@codemirror/autocomplete": "^6.18.1",
|
|
39
42
|
"@codemirror/lang-javascript": "^6.2.2",
|
|
40
43
|
"@codemirror/lang-json": "^6.0.1",
|
|
@@ -42,6 +45,7 @@
|
|
|
42
45
|
"@codemirror/state": "^6.4.1",
|
|
43
46
|
"@codemirror/view": "^6.34.1",
|
|
44
47
|
"@hookform/resolvers": "^3.9.0",
|
|
48
|
+
"@playwright/test": "^1.48.0",
|
|
45
49
|
"@radix-ui/react-accordion": "^1.2.4",
|
|
46
50
|
"@radix-ui/react-alert-dialog": "^1.1.1",
|
|
47
51
|
"@radix-ui/react-aspect-ratio": "^1.1.0",
|
|
@@ -70,36 +74,65 @@
|
|
|
70
74
|
"@radix-ui/react-toggle": "^1.1.0",
|
|
71
75
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
|
72
76
|
"@radix-ui/react-tooltip": "^1.1.2",
|
|
73
|
-
"@
|
|
77
|
+
"@tailwindcss/typography": "^0.5.16",
|
|
78
|
+
"@tscircuit/3d-viewer": "^0.0.279",
|
|
79
|
+
"@tscircuit/assembly-viewer": "^0.0.1",
|
|
80
|
+
"@tscircuit/core": "^0.0.536",
|
|
81
|
+
"@tscircuit/create-snippet-url": "^0.0.8",
|
|
82
|
+
"@tscircuit/eval": "^0.0.244",
|
|
83
|
+
"@tscircuit/footprinter": "^0.0.186",
|
|
74
84
|
"@tscircuit/layout": "^0.0.29",
|
|
75
85
|
"@tscircuit/math-utils": "^0.0.10",
|
|
76
86
|
"@tscircuit/mm": "^0.0.8",
|
|
77
|
-
"@tscircuit/
|
|
87
|
+
"@tscircuit/pcb-viewer": "^1.11.194",
|
|
88
|
+
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
89
|
+
"@tscircuit/props": "^0.0.246",
|
|
90
|
+
"@tscircuit/runframe": "^0.0.669",
|
|
91
|
+
"@tscircuit/schematic-viewer": "^2.0.21",
|
|
92
|
+
"@types/babel__standalone": "^7.1.7",
|
|
93
|
+
"@types/bun": "^1.1.10",
|
|
94
|
+
"@types/country-list": "^2.1.4",
|
|
78
95
|
"@types/file-saver": "^2.0.7",
|
|
96
|
+
"@types/md5": "^2.3.5",
|
|
79
97
|
"@types/ms": "^0.7.34",
|
|
98
|
+
"@types/node": "^22.13.0",
|
|
99
|
+
"@types/prismjs": "^1.26.4",
|
|
100
|
+
"@types/react": "^18.3.9",
|
|
101
|
+
"@types/react-dom": "^18.3.0",
|
|
102
|
+
"@types/react-helmet": "^6.1.11",
|
|
103
|
+
"@types/sharp": "^0.32.0",
|
|
80
104
|
"@typescript/ata": "^0.9.7",
|
|
81
|
-
"@
|
|
105
|
+
"@typescript/vfs": "^1.6.0",
|
|
82
106
|
"@valtown/codemirror-ts": "^2.2.0",
|
|
83
107
|
"@vercel/analytics": "^1.4.1",
|
|
108
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
109
|
+
"autoprefixer": "^10.4.20",
|
|
84
110
|
"change-case": "^5.4.4",
|
|
85
111
|
"circuit-json": "^0.0.190",
|
|
86
|
-
"circuit-json-to-bom-csv": "^0.0.
|
|
87
|
-
"circuit-json-to-gerber": "^0.0.
|
|
88
|
-
"circuit-json-to-pnp-csv": "^0.0.
|
|
112
|
+
"circuit-json-to-bom-csv": "^0.0.7",
|
|
113
|
+
"circuit-json-to-gerber": "^0.0.25",
|
|
114
|
+
"circuit-json-to-pnp-csv": "^0.0.7",
|
|
89
115
|
"circuit-json-to-readable-netlist": "^0.0.13",
|
|
90
116
|
"circuit-json-to-tscircuit": "^0.0.4",
|
|
117
|
+
"circuit-to-svg": "^0.0.163",
|
|
91
118
|
"class-variance-authority": "^0.7.1",
|
|
92
119
|
"clsx": "^2.1.1",
|
|
93
120
|
"cmdk": "^1.0.4",
|
|
94
121
|
"codemirror": "^6.0.1",
|
|
122
|
+
"codemirror-copilot": "^0.0.7",
|
|
95
123
|
"country-list": "^2.3.0",
|
|
96
124
|
"date-fns": "^4.1.0",
|
|
125
|
+
"dotenv": "^16.5.0",
|
|
97
126
|
"dsn-converter": "^0.0.60",
|
|
98
|
-
"easyeda": "^0.0.
|
|
127
|
+
"easyeda": "^0.0.195",
|
|
99
128
|
"embla-carousel-react": "^8.3.0",
|
|
100
129
|
"extract-codefence": "^0.0.4",
|
|
101
130
|
"fflate": "^0.8.2",
|
|
102
131
|
"file-saver": "^2.0.5",
|
|
132
|
+
"get-port": "^7.1.0",
|
|
133
|
+
"globals": "^15.9.0",
|
|
134
|
+
"he": "^1.2.0",
|
|
135
|
+
"idb-keyval": "^6.2.2",
|
|
103
136
|
"immer": "^10.1.1",
|
|
104
137
|
"input-otp": "^1.2.4",
|
|
105
138
|
"javascript-time-ago": "^2.5.11",
|
|
@@ -107,11 +140,18 @@
|
|
|
107
140
|
"jscad-electronics": "^0.0.25",
|
|
108
141
|
"jszip": "^3.10.1",
|
|
109
142
|
"kicad-converter": "^0.0.16",
|
|
143
|
+
"ky": "^1.7.5",
|
|
110
144
|
"lucide-react": "^0.488.0",
|
|
145
|
+
"lz-string": "^1.5.0",
|
|
111
146
|
"md5": "^2.3.0",
|
|
112
147
|
"ms": "^2.1.3",
|
|
113
148
|
"next-themes": "^0.3.0",
|
|
149
|
+
"openai": "^5.6.0",
|
|
150
|
+
"postcss": "^8.4.47",
|
|
114
151
|
"posthog-js": "^1.203.2",
|
|
152
|
+
"prismjs": "^1.29.0",
|
|
153
|
+
"prompts": "^2.4.2",
|
|
154
|
+
"react": "^18.3.1",
|
|
115
155
|
"react-cookie-consent": "^9.0.0",
|
|
116
156
|
"react-day-picker": "8.10.1",
|
|
117
157
|
"react-dom": "^18.3.1",
|
|
@@ -124,60 +164,31 @@
|
|
|
124
164
|
"react-query": "^3.39.3",
|
|
125
165
|
"react-resizable-panels": "^2.1.3",
|
|
126
166
|
"recharts": "^2.12.7",
|
|
167
|
+
"redaxios": "^0.5.1",
|
|
127
168
|
"remark-gfm": "^4.0.1",
|
|
128
169
|
"rollup-plugin-visualizer": "^5.12.0",
|
|
129
170
|
"schematic-symbols": "^0.0.155",
|
|
171
|
+
"sharp": "^0.33.5",
|
|
172
|
+
"shiki": "^3.2.1",
|
|
130
173
|
"sitemap": "^8.0.0",
|
|
131
174
|
"sonner": "^1.5.0",
|
|
132
175
|
"states-us": "^1.1.1",
|
|
133
176
|
"tailwind-merge": "^2.5.2",
|
|
134
|
-
"tailwindcss-animate": "^1.0.7",
|
|
135
|
-
"use-async-memo": "^1.2.5",
|
|
136
|
-
"use-mouse-matrix-transform": "^1.3.0",
|
|
137
|
-
"vaul": "^0.9.9",
|
|
138
|
-
"vite-plugin-vercel": "^9.0.4",
|
|
139
|
-
"wouter": "^3.3.5",
|
|
140
|
-
"@anthropic-ai/sdk": "^0.27.3",
|
|
141
|
-
"@babel/standalone": "^7.26.2",
|
|
142
|
-
"@biomejs/biome": "^1.9.2",
|
|
143
|
-
"@playwright/test": "^1.48.0",
|
|
144
|
-
"@tailwindcss/typography": "^0.5.16",
|
|
145
|
-
"@tscircuit/core": "^0.0.433",
|
|
146
|
-
"@tscircuit/eval": "^0.0.227",
|
|
147
|
-
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
148
|
-
"@tscircuit/runframe": "^0.0.582",
|
|
149
|
-
"@types/babel__standalone": "^7.1.7",
|
|
150
|
-
"@types/bun": "^1.1.10",
|
|
151
|
-
"@types/country-list": "^2.1.4",
|
|
152
|
-
"@types/md5": "^2.3.5",
|
|
153
|
-
"@types/node": "^22.13.0",
|
|
154
|
-
"@types/prismjs": "^1.26.4",
|
|
155
|
-
"@types/react": "^18.3.9",
|
|
156
|
-
"@types/react-dom": "^18.3.0",
|
|
157
|
-
"@types/react-helmet": "^6.1.11",
|
|
158
|
-
"@types/sharp": "^0.32.0",
|
|
159
|
-
"@typescript/vfs": "^1.6.0",
|
|
160
|
-
"@vitejs/plugin-react": "^4.3.1",
|
|
161
|
-
"autoprefixer": "^10.4.20",
|
|
162
|
-
"circuit-to-svg": "^0.0.152",
|
|
163
|
-
"get-port": "^7.1.0",
|
|
164
|
-
"globals": "^15.9.0",
|
|
165
|
-
"he": "^1.2.0",
|
|
166
|
-
"ky": "^1.7.5",
|
|
167
|
-
"postcss": "^8.4.47",
|
|
168
|
-
"prismjs": "^1.29.0",
|
|
169
|
-
"prompts": "^2.4.2",
|
|
170
|
-
"react": "^18.3.1",
|
|
171
|
-
"redaxios": "^0.5.1",
|
|
172
|
-
"sharp": "^0.33.5",
|
|
173
|
-
"shiki": "^3.2.1",
|
|
174
177
|
"tailwindcss": "^3.4.13",
|
|
178
|
+
"tailwindcss-animate": "^1.0.7",
|
|
175
179
|
"terser": "^5.27.0",
|
|
180
|
+
"three": "^0.177.0",
|
|
181
|
+
"three-stdlib": "^2.36.0",
|
|
176
182
|
"tsup": "^8.5.0",
|
|
177
183
|
"typescript": "^5.6.3",
|
|
184
|
+
"use-async-memo": "^1.2.5",
|
|
185
|
+
"use-mouse-matrix-transform": "^1.3.0",
|
|
186
|
+
"vaul": "^0.9.9",
|
|
178
187
|
"vite": "^6.3.4",
|
|
179
188
|
"vite-plugin-image-optimizer": "^1.1.8",
|
|
189
|
+
"vite-plugin-vercel": "^9.0.4",
|
|
180
190
|
"winterspec": "^0.0.107",
|
|
191
|
+
"wouter": "^3.3.5",
|
|
181
192
|
"zod": "^3.23.8",
|
|
182
193
|
"zustand": "^4.5.5",
|
|
183
194
|
"zustand-hoist": "^2.0.1"
|
package/renovate.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -78,18 +78,82 @@ const PackageEditorPage = lazyImport(async () => {
|
|
|
78
78
|
|
|
79
79
|
class ErrorBoundary extends React.Component<
|
|
80
80
|
{ children: React.ReactNode },
|
|
81
|
-
{ hasError: boolean }
|
|
81
|
+
{ hasError: boolean; reloading: boolean }
|
|
82
82
|
> {
|
|
83
|
+
private visibilityHandler?: () => void
|
|
84
|
+
private reloadTimeout?: number
|
|
85
|
+
|
|
83
86
|
constructor(props: { children: React.ReactNode }) {
|
|
84
87
|
super(props)
|
|
85
|
-
this.state = { hasError: false }
|
|
88
|
+
this.state = { hasError: false, reloading: false }
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
static getDerivedStateFromError() {
|
|
89
|
-
return { hasError: true }
|
|
92
|
+
return { hasError: true, reloading: false }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
componentDidCatch(error: Error) {
|
|
96
|
+
console.error("ErrorBoundary caught", error)
|
|
97
|
+
const message = error.message || ""
|
|
98
|
+
if (
|
|
99
|
+
/(Loading chunk|ChunkLoadError|dynamically imported module)/i.test(
|
|
100
|
+
message,
|
|
101
|
+
)
|
|
102
|
+
) {
|
|
103
|
+
const loadedAt = window.__APP_LOADED_AT || Date.now()
|
|
104
|
+
if (Date.now() - loadedAt >= 10_000) {
|
|
105
|
+
this.performReload()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
componentDidUpdate(_prevProps: any, prevState: any) {
|
|
111
|
+
if (!prevState.hasError && this.state.hasError && !this.state.reloading) {
|
|
112
|
+
this.setupIdleReload()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
componentWillUnmount() {
|
|
117
|
+
this.cleanup()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
cleanup = () => {
|
|
121
|
+
if (this.visibilityHandler) {
|
|
122
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler)
|
|
123
|
+
this.visibilityHandler = undefined
|
|
124
|
+
}
|
|
125
|
+
if (this.reloadTimeout) {
|
|
126
|
+
clearTimeout(this.reloadTimeout)
|
|
127
|
+
this.reloadTimeout = undefined
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
performReload = () => {
|
|
132
|
+
if (this.state.reloading) return // Prevent multiple reloads
|
|
133
|
+
|
|
134
|
+
this.cleanup() // Clean up listeners before reload
|
|
135
|
+
this.setState({ reloading: true })
|
|
136
|
+
this.reloadTimeout = window.setTimeout(() => {
|
|
137
|
+
window.location.reload()
|
|
138
|
+
}, 500)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setupIdleReload = () => {
|
|
142
|
+
this.cleanup() // Clean up any existing handlers
|
|
143
|
+
|
|
144
|
+
this.visibilityHandler = () => {
|
|
145
|
+
if (!document.hidden && this.state.hasError && !this.state.reloading) {
|
|
146
|
+
this.performReload()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
document.addEventListener("visibilitychange", this.visibilityHandler)
|
|
90
151
|
}
|
|
91
152
|
|
|
92
153
|
render() {
|
|
154
|
+
if (this.state.reloading) {
|
|
155
|
+
return <div>There was a problem loading this page. Reloading…</div>
|
|
156
|
+
}
|
|
93
157
|
if (this.state.hasError) {
|
|
94
158
|
return <div>Something went wrong loading the page.</div>
|
|
95
159
|
}
|
package/src/ContextProviders.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { useEffect } from "react"
|
|
|
4
4
|
import { useGlobalStore } from "./hooks/use-global-store"
|
|
5
5
|
import { posthog } from "./lib/posthog"
|
|
6
6
|
import { Toaster } from "react-hot-toast"
|
|
7
|
+
import { Toaster as SonnerToaster } from "@/components/ui/sonner"
|
|
7
8
|
import { populateQueryCacheWithSSRData } from "./lib/populate-query-cache-with-ssr-data"
|
|
8
9
|
|
|
9
10
|
const staffGithubUsernames = [
|
|
@@ -65,6 +66,7 @@ export const ContextProviders = ({ children }: any) => {
|
|
|
65
66
|
<PostHogIdentifier />
|
|
66
67
|
{children}
|
|
67
68
|
<Toaster position="bottom-right" />
|
|
69
|
+
<SonnerToaster position="bottom-left" />
|
|
68
70
|
</HelmetProvider>
|
|
69
71
|
</QueryClientProvider>
|
|
70
72
|
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { toast } from "sonner"
|
|
2
|
+
|
|
3
|
+
export function setupBuildWatcher() {
|
|
4
|
+
const meta = document.querySelector<HTMLMetaElement>(
|
|
5
|
+
'meta[name="tscircuit-build"]',
|
|
6
|
+
)
|
|
7
|
+
const currentId = meta?.content || ""
|
|
8
|
+
;(window as any).TSC_BUILD_ID = currentId
|
|
9
|
+
|
|
10
|
+
async function fetchBuildId(): Promise<string | null> {
|
|
11
|
+
try {
|
|
12
|
+
const res = await fetch("/api/generated-index", { cache: "no-store" })
|
|
13
|
+
const text = await res.text()
|
|
14
|
+
const match = text.match(
|
|
15
|
+
/<meta name="tscircuit-build" content="([^"]+)"/i,
|
|
16
|
+
)
|
|
17
|
+
return match ? match[1] : null
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.error("Failed to fetch build identifier", err)
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let updateToastShown = false
|
|
25
|
+
|
|
26
|
+
async function checkForUpdate() {
|
|
27
|
+
const serverId = await fetchBuildId()
|
|
28
|
+
if (
|
|
29
|
+
serverId &&
|
|
30
|
+
serverId !== (window as any).TSC_BUILD_ID &&
|
|
31
|
+
!updateToastShown
|
|
32
|
+
) {
|
|
33
|
+
updateToastShown = true
|
|
34
|
+
toast("A new version of tscircuit.com is available.", {
|
|
35
|
+
action: {
|
|
36
|
+
label: "Reload",
|
|
37
|
+
onClick: () => window.location.reload(),
|
|
38
|
+
},
|
|
39
|
+
duration: Infinity,
|
|
40
|
+
position: "bottom-left",
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
document.addEventListener("visibilitychange", () => {
|
|
46
|
+
if (document.visibilityState === "visible") {
|
|
47
|
+
checkForUpdate()
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
setInterval(checkForUpdate, 5 * 60 * 1000)
|
|
52
|
+
}
|
|
@@ -15,7 +15,7 @@ import { useLocation } from "wouter"
|
|
|
15
15
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
16
16
|
import { convertCircuitJsonToTscircuit } from "circuit-json-to-tscircuit"
|
|
17
17
|
import { useCreatePackageMutation } from "@/hooks/use-create-package-mutation"
|
|
18
|
-
import { generateRandomPackageName } from "
|
|
18
|
+
import { generateRandomPackageName } from "@/lib/utils/package-utils"
|
|
19
19
|
import { useCreatePackageReleaseMutation } from "@/hooks/use-create-package-release-mutation"
|
|
20
20
|
import { useCreatePackageFilesMutation } from "@/hooks/use-create-package-files-mutation"
|
|
21
21
|
|