@tscircuit/fake-snippets 0.0.89 → 0.0.91
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/bun.lock +84 -108
- package/dist/bundle.js +493 -372
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +135 -0
- package/package.json +8 -10
- package/src/App.tsx +47 -2
- package/src/ContextProviders.tsx +0 -2
- package/src/components/CircuitJsonImportDialog.tsx +1 -1
- package/src/components/PackageBuildsPage/package-build-header.tsx +2 -12
- package/src/components/PageSearchComponent.tsx +2 -2
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/TrendingPackagesCarousel.tsx +2 -2
- package/src/components/dialogs/import-component-dialog.tsx +25 -0
- package/src/components/package-port/CodeAndPreview.tsx +1 -16
- package/src/components/package-port/CodeEditor.tsx +34 -8
- package/src/components/package-port/CodeEditorHeader.tsx +75 -13
- package/src/components/package-port/EditorNav.tsx +58 -65
- package/src/components/package-port/GlobalFindReplace.tsx +1 -1
- package/src/components/package-port/QuickOpen.tsx +1 -1
- package/src/hooks/use-axios.ts +2 -2
- package/src/hooks/use-code-completion-ai-api.ts +3 -3
- package/src/hooks/use-delete-package.ts +6 -2
- package/src/hooks/use-packages-base-api-url.ts +3 -0
- package/src/hooks/use-request-ai-review-mutation.ts +1 -1
- package/src/hooks/use-sign-in.ts +2 -2
- package/src/hooks/use-toast.tsx +1 -0
- package/src/hooks/useFileManagement.ts +10 -5
- package/src/lib/utils/findTargetFile.ts +1 -1
- package/src/lib/utils/package-utils.ts +10 -0
- package/src/main.tsx +0 -3
- package/src/pages/dashboard.tsx +12 -2
- package/src/pages/dev-login.tsx +2 -2
- package/src/pages/latest.tsx +2 -2
- package/src/pages/search.tsx +2 -2
- package/src/pages/trending.tsx +2 -2
- package/src/pages/user-profile.tsx +2 -2
- package/src/types/package.ts +4 -0
- package/vite.config.ts +0 -19
- package/src/build-watcher.ts +0 -52
- package/src/global.d.ts +0 -3
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
return ctx.json({ prediction: "mock" })
|
|
122
|
+
const openai = getOpenAIClient()
|
|
123
|
+
const { prefix, suffix, model, language } = req.jsonBody
|
|
124
|
+
|
|
125
|
+
const readmeContent = await getCachedReadme()
|
|
126
|
+
const predictionResult = await completion(
|
|
127
|
+
openai,
|
|
128
|
+
readmeContent,
|
|
129
|
+
prefix,
|
|
130
|
+
suffix,
|
|
131
|
+
model,
|
|
132
|
+
language,
|
|
133
|
+
)
|
|
134
|
+
return ctx.json({ prediction: predictionResult })
|
|
135
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fake-snippets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.91",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -77,17 +77,13 @@
|
|
|
77
77
|
"@tailwindcss/typography": "^0.5.16",
|
|
78
78
|
"@tscircuit/3d-viewer": "^0.0.279",
|
|
79
79
|
"@tscircuit/assembly-viewer": "^0.0.1",
|
|
80
|
-
"@tscircuit/core": "^0.0.536",
|
|
81
80
|
"@tscircuit/create-snippet-url": "^0.0.8",
|
|
82
81
|
"@tscircuit/eval": "^0.0.244",
|
|
83
|
-
"@tscircuit/footprinter": "^0.0.186",
|
|
84
82
|
"@tscircuit/layout": "^0.0.29",
|
|
85
|
-
"@tscircuit/math-utils": "^0.0.10",
|
|
86
83
|
"@tscircuit/mm": "^0.0.8",
|
|
87
84
|
"@tscircuit/pcb-viewer": "^1.11.194",
|
|
88
85
|
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
89
|
-
"@tscircuit/
|
|
90
|
-
"@tscircuit/runframe": "^0.0.653",
|
|
86
|
+
"@tscircuit/runframe": "^0.0.669",
|
|
91
87
|
"@tscircuit/schematic-viewer": "^2.0.21",
|
|
92
88
|
"@types/babel__standalone": "^7.1.7",
|
|
93
89
|
"@types/bun": "^1.1.10",
|
|
@@ -103,13 +99,11 @@
|
|
|
103
99
|
"@types/sharp": "^0.32.0",
|
|
104
100
|
"@typescript/ata": "^0.9.7",
|
|
105
101
|
"@typescript/vfs": "^1.6.0",
|
|
106
|
-
"@valtown/codemirror-codeium": "^1.1.1",
|
|
107
102
|
"@valtown/codemirror-ts": "^2.2.0",
|
|
108
103
|
"@vercel/analytics": "^1.4.1",
|
|
109
104
|
"@vitejs/plugin-react": "^4.3.1",
|
|
110
105
|
"autoprefixer": "^10.4.20",
|
|
111
106
|
"change-case": "^5.4.4",
|
|
112
|
-
"circuit-json": "^0.0.190",
|
|
113
107
|
"circuit-json-to-bom-csv": "^0.0.7",
|
|
114
108
|
"circuit-json-to-gerber": "^0.0.25",
|
|
115
109
|
"circuit-json-to-pnp-csv": "^0.0.7",
|
|
@@ -120,10 +114,12 @@
|
|
|
120
114
|
"clsx": "^2.1.1",
|
|
121
115
|
"cmdk": "^1.0.4",
|
|
122
116
|
"codemirror": "^6.0.1",
|
|
117
|
+
"codemirror-copilot": "^0.0.7",
|
|
123
118
|
"country-list": "^2.3.0",
|
|
124
119
|
"date-fns": "^4.1.0",
|
|
120
|
+
"dotenv": "^16.5.0",
|
|
125
121
|
"dsn-converter": "^0.0.60",
|
|
126
|
-
"easyeda": "^0.0.
|
|
122
|
+
"easyeda": "^0.0.203",
|
|
127
123
|
"embla-carousel-react": "^8.3.0",
|
|
128
124
|
"extract-codefence": "^0.0.4",
|
|
129
125
|
"fflate": "^0.8.2",
|
|
@@ -138,13 +134,14 @@
|
|
|
138
134
|
"jose": "^5.9.3",
|
|
139
135
|
"jscad-electronics": "^0.0.25",
|
|
140
136
|
"jszip": "^3.10.1",
|
|
141
|
-
"kicad-converter": "^0.0.
|
|
137
|
+
"kicad-converter": "^0.0.17",
|
|
142
138
|
"ky": "^1.7.5",
|
|
143
139
|
"lucide-react": "^0.488.0",
|
|
144
140
|
"lz-string": "^1.5.0",
|
|
145
141
|
"md5": "^2.3.0",
|
|
146
142
|
"ms": "^2.1.3",
|
|
147
143
|
"next-themes": "^0.3.0",
|
|
144
|
+
"openai": "^5.6.0",
|
|
148
145
|
"postcss": "^8.4.47",
|
|
149
146
|
"posthog-js": "^1.203.2",
|
|
150
147
|
"prismjs": "^1.29.0",
|
|
@@ -177,6 +174,7 @@
|
|
|
177
174
|
"terser": "^5.27.0",
|
|
178
175
|
"three": "^0.177.0",
|
|
179
176
|
"three-stdlib": "^2.36.0",
|
|
177
|
+
"tscircuit": "^0.0.522",
|
|
180
178
|
"tsup": "^8.5.0",
|
|
181
179
|
"typescript": "^5.6.3",
|
|
182
180
|
"use-async-memo": "^1.2.5",
|
package/src/App.tsx
CHANGED
|
@@ -80,6 +80,9 @@ class ErrorBoundary extends React.Component<
|
|
|
80
80
|
{ children: React.ReactNode },
|
|
81
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
88
|
this.state = { hasError: false, reloading: false }
|
|
@@ -99,12 +102,54 @@ class ErrorBoundary extends React.Component<
|
|
|
99
102
|
) {
|
|
100
103
|
const loadedAt = window.__APP_LOADED_AT || Date.now()
|
|
101
104
|
if (Date.now() - loadedAt >= 10_000) {
|
|
102
|
-
this.
|
|
103
|
-
window.location.reload()
|
|
105
|
+
this.performReload()
|
|
104
106
|
}
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
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)
|
|
151
|
+
}
|
|
152
|
+
|
|
108
153
|
render() {
|
|
109
154
|
if (this.state.reloading) {
|
|
110
155
|
return <div>There was a problem loading this page. Reloading…</div>
|
package/src/ContextProviders.tsx
CHANGED
|
@@ -4,7 +4,6 @@ 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"
|
|
8
7
|
import { populateQueryCacheWithSSRData } from "./lib/populate-query-cache-with-ssr-data"
|
|
9
8
|
|
|
10
9
|
const staffGithubUsernames = [
|
|
@@ -66,7 +65,6 @@ export const ContextProviders = ({ children }: any) => {
|
|
|
66
65
|
<PostHogIdentifier />
|
|
67
66
|
{children}
|
|
68
67
|
<Toaster position="bottom-right" />
|
|
69
|
-
<SonnerToaster position="bottom-left" />
|
|
70
68
|
</HelmetProvider>
|
|
71
69
|
</QueryClientProvider>
|
|
72
70
|
)
|
|
@@ -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
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Button } from "@/components/ui/button"
|
|
2
2
|
import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
|
|
3
3
|
import { useRebuildPackageReleaseMutation } from "@/hooks/use-rebuild-package-release-mutation"
|
|
4
|
-
import { Github, RefreshCw
|
|
4
|
+
import { Github, RefreshCw } from "lucide-react"
|
|
5
5
|
import { useParams } from "wouter"
|
|
6
6
|
import { DownloadButtonAndMenu } from "../DownloadButtonAndMenu"
|
|
7
7
|
|
|
8
8
|
export function PackageBuildHeader() {
|
|
9
9
|
const { author, packageName } = useParams()
|
|
10
|
-
const { packageRelease
|
|
10
|
+
const { packageRelease } = useCurrentPackageRelease({
|
|
11
11
|
include_logs: true,
|
|
12
12
|
})
|
|
13
13
|
const { mutate: rebuildPackage, isLoading } =
|
|
@@ -59,16 +59,6 @@ export function PackageBuildHeader() {
|
|
|
59
59
|
<RefreshCw className="w-3 h-3 sm:w-4 sm:h-4 mr-1 sm:mr-2" />
|
|
60
60
|
{isLoading ? "Rebuilding..." : "Rebuild"}
|
|
61
61
|
</Button>
|
|
62
|
-
<Button
|
|
63
|
-
variant="outline"
|
|
64
|
-
size="icon"
|
|
65
|
-
aria-label="Reload logs"
|
|
66
|
-
className="border-gray-300 bg-white hover:bg-gray-50"
|
|
67
|
-
onClick={() => refetch()}
|
|
68
|
-
disabled={isFetching}
|
|
69
|
-
>
|
|
70
|
-
<RotateCcw className="w-3 h-3 sm:w-4 sm:h-4" />
|
|
71
|
-
</Button>
|
|
72
62
|
<DownloadButtonAndMenu unscopedName={packageName} author={author} />
|
|
73
63
|
</div>
|
|
74
64
|
</div>
|
|
@@ -3,7 +3,7 @@ import { useAxios } from "@/hooks/use-axios"
|
|
|
3
3
|
import { useLocation } from "wouter"
|
|
4
4
|
import React, { useState } from "react"
|
|
5
5
|
import { useQuery } from "react-query"
|
|
6
|
-
import {
|
|
6
|
+
import { usePackagesBaseApiUrl } from "@/hooks/use-packages-base-api-url"
|
|
7
7
|
import { Search } from "lucide-react"
|
|
8
8
|
import { Button } from "./ui/button"
|
|
9
9
|
import { PackageCardSkeleton } from "./PackageCardSkeleton"
|
|
@@ -20,7 +20,7 @@ const PageSearchComponent: React.FC<PageSearchComponentProps> = ({
|
|
|
20
20
|
}) => {
|
|
21
21
|
const [location, setLocation] = useLocation()
|
|
22
22
|
const axios = useAxios()
|
|
23
|
-
const snippetsBaseApiUrl =
|
|
23
|
+
const snippetsBaseApiUrl = usePackagesBaseApiUrl()
|
|
24
24
|
|
|
25
25
|
// Initialize search query directly from URL
|
|
26
26
|
const [searchQuery, setSearchQuery] = useState(
|
|
@@ -4,7 +4,7 @@ import { useLocation } from "wouter"
|
|
|
4
4
|
import React, { useEffect, useRef, useState } from "react"
|
|
5
5
|
import { useQuery } from "react-query"
|
|
6
6
|
import { Alert } from "./ui/alert"
|
|
7
|
-
import {
|
|
7
|
+
import { usePackagesBaseApiUrl } from "@/hooks/use-packages-base-api-url"
|
|
8
8
|
import { PrefetchPageLink } from "./PrefetchPageLink"
|
|
9
9
|
import { CircuitBoard } from "lucide-react"
|
|
10
10
|
import { cn } from "@/lib/utils"
|
|
@@ -60,7 +60,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
60
60
|
const resultsRef = useRef<HTMLDivElement>(null)
|
|
61
61
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
62
62
|
const [location, setLocation] = useLocation()
|
|
63
|
-
const snippetsBaseApiUrl =
|
|
63
|
+
const snippetsBaseApiUrl = usePackagesBaseApiUrl()
|
|
64
64
|
|
|
65
65
|
const { data: searchResults, isLoading } = useQuery(
|
|
66
66
|
["packageSearch", searchQuery],
|
|
@@ -4,7 +4,7 @@ import { StarFilledIcon } from "@radix-ui/react-icons"
|
|
|
4
4
|
import { Link } from "wouter"
|
|
5
5
|
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
6
6
|
import { useRef, useState } from "react"
|
|
7
|
-
import {
|
|
7
|
+
import { usePackagesBaseApiUrl } from "@/hooks/use-packages-base-api-url"
|
|
8
8
|
|
|
9
9
|
const CarouselItem = ({
|
|
10
10
|
pkg,
|
|
@@ -36,7 +36,7 @@ export const TrendingPackagesCarousel = () => {
|
|
|
36
36
|
const axios = useAxios()
|
|
37
37
|
const scrollRef = useRef<HTMLDivElement>(null)
|
|
38
38
|
const [isHovered, setIsHovered] = useState(false)
|
|
39
|
-
const apiBaseUrl =
|
|
39
|
+
const apiBaseUrl = usePackagesBaseApiUrl()
|
|
40
40
|
|
|
41
41
|
const { data: trendingPackages } = useQuery<Package[]>(
|
|
42
42
|
"trendingPackages",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createUseDialog } from "./create-use-dialog"
|
|
2
|
+
import {
|
|
3
|
+
ComponentSearchResult,
|
|
4
|
+
ImportComponentDialog as RunframeImportComponentDialog,
|
|
5
|
+
} from "@tscircuit/runframe/runner"
|
|
6
|
+
|
|
7
|
+
export const ImportComponentDialog = ({
|
|
8
|
+
open,
|
|
9
|
+
onOpenChange,
|
|
10
|
+
onComponentSelected,
|
|
11
|
+
}: {
|
|
12
|
+
open: boolean
|
|
13
|
+
onOpenChange: (open: boolean) => any
|
|
14
|
+
onComponentSelected: (pkg: ComponentSearchResult) => any
|
|
15
|
+
}) => {
|
|
16
|
+
return (
|
|
17
|
+
<RunframeImportComponentDialog
|
|
18
|
+
isOpen={open}
|
|
19
|
+
onClose={() => onOpenChange(false)}
|
|
20
|
+
onImport={(data) => onComponentSelected(data)}
|
|
21
|
+
/>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const useImportComponentDialog = createUseDialog(ImportComponentDialog)
|
|
@@ -15,6 +15,7 @@ import { applyEditEventsToManualEditsFile } from "@tscircuit/core"
|
|
|
15
15
|
import { toastManualEditConflicts } from "@/lib/utils/toastManualEditConflicts"
|
|
16
16
|
import { ManualEditEvent } from "@tscircuit/props"
|
|
17
17
|
import { useFileManagement } from "@/hooks/useFileManagement"
|
|
18
|
+
import { DEFAULT_CODE } from "@/lib/utils/package-utils"
|
|
18
19
|
|
|
19
20
|
interface Props {
|
|
20
21
|
pkg?: Package
|
|
@@ -25,11 +26,6 @@ interface Props {
|
|
|
25
26
|
projectUrl?: string
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export interface PackageFile {
|
|
29
|
-
path: string
|
|
30
|
-
content: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
29
|
export interface CodeAndPreviewState {
|
|
34
30
|
showPreview: boolean
|
|
35
31
|
fullScreen: boolean
|
|
@@ -40,17 +36,6 @@ export interface CodeAndPreviewState {
|
|
|
40
36
|
defaultComponentFile?: string
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
export const DEFAULT_CODE = `
|
|
44
|
-
export default () => (
|
|
45
|
-
<board width="10mm" height="10mm">
|
|
46
|
-
{/* write your code here! */}
|
|
47
|
-
</board>
|
|
48
|
-
)
|
|
49
|
-
`.trim()
|
|
50
|
-
|
|
51
|
-
export const generateRandomPackageName = () =>
|
|
52
|
-
`untitled-package-${Math.floor(Math.random() * 900) + 100}`
|
|
53
|
-
|
|
54
39
|
export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
55
40
|
const { toast } = useToast()
|
|
56
41
|
const urlParams = useUrlParams()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { usePackagesBaseApiUrl } from "@/hooks/use-packages-base-api-url"
|
|
2
2
|
import { useHotkeyCombo } from "@/hooks/use-hotkey"
|
|
3
3
|
import { basicSetup } from "@/lib/codemirror/basic-setup"
|
|
4
4
|
import {
|
|
@@ -32,7 +32,7 @@ import CodeEditorHeader, {
|
|
|
32
32
|
import { useCodeCompletionApi } from "@/hooks/use-code-completion-ai-api"
|
|
33
33
|
import FileSidebar from "../FileSidebar"
|
|
34
34
|
import { findTargetFile } from "@/lib/utils/findTargetFile"
|
|
35
|
-
import type { PackageFile } from "
|
|
35
|
+
import type { PackageFile } from "@/types/package"
|
|
36
36
|
import { useShikiHighlighter } from "@/hooks/use-shiki-highlighter"
|
|
37
37
|
import QuickOpen from "./QuickOpen"
|
|
38
38
|
import GlobalFindReplace from "./GlobalFindReplace"
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
IDeleteFileResult,
|
|
44
44
|
} from "@/hooks/useFileManagement"
|
|
45
45
|
import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
|
|
46
|
+
import { inlineCopilot } from "codemirror-copilot"
|
|
46
47
|
|
|
47
48
|
const defaultImports = `
|
|
48
49
|
import React from "@types/react/jsx-runtime"
|
|
@@ -81,7 +82,7 @@ export const CodeEditor = ({
|
|
|
81
82
|
const viewRef = useRef<EditorView | null>(null)
|
|
82
83
|
const ataRef = useRef<ReturnType<typeof setupTypeAcquisition> | null>(null)
|
|
83
84
|
const lastReceivedTsFileTimeRef = useRef<number>(0)
|
|
84
|
-
const apiUrl =
|
|
85
|
+
const apiUrl = usePackagesBaseApiUrl()
|
|
85
86
|
const codeCompletionApi = useCodeCompletionApi()
|
|
86
87
|
const [cursorPosition, setCursorPosition] = useState<number | null>(null)
|
|
87
88
|
const [code, setCode] = useState(files[0]?.content || "")
|
|
@@ -94,6 +95,7 @@ export const CodeEditor = ({
|
|
|
94
95
|
// Get URL search params for file_path
|
|
95
96
|
const urlParams = new URLSearchParams(window.location.search)
|
|
96
97
|
const filePathFromUrl = urlParams.get("file_path")
|
|
98
|
+
const [aiAutocompleteEnabled, setAiAutocompleteEnabled] = useState(false)
|
|
97
99
|
|
|
98
100
|
const entryPointFileName = useMemo(() => {
|
|
99
101
|
const entryPointFile = findTargetFile(files, null)
|
|
@@ -324,12 +326,27 @@ export const CodeEditor = ({
|
|
|
324
326
|
},
|
|
325
327
|
}),
|
|
326
328
|
]
|
|
327
|
-
if (
|
|
329
|
+
if (aiAutocompleteEnabled) {
|
|
328
330
|
baseExtensions.push(
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
inlineCopilot(async (prefix, suffix) => {
|
|
332
|
+
const res = await fetch(
|
|
333
|
+
`${apiUrl}/autocomplete/create_autocomplete`,
|
|
334
|
+
{
|
|
335
|
+
method: "POST",
|
|
336
|
+
headers: {
|
|
337
|
+
"Content-Type": "application/json",
|
|
338
|
+
},
|
|
339
|
+
body: JSON.stringify({
|
|
340
|
+
prefix,
|
|
341
|
+
suffix,
|
|
342
|
+
language: "typescript",
|
|
343
|
+
}),
|
|
344
|
+
},
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
const { prediction } = await res.json()
|
|
348
|
+
return prediction
|
|
349
|
+
}),
|
|
333
350
|
EditorView.theme({
|
|
334
351
|
".cm-ghostText, .cm-ghostText *": {
|
|
335
352
|
opacity: "0.6",
|
|
@@ -537,6 +554,7 @@ export const CodeEditor = ({
|
|
|
537
554
|
Boolean(highlighter),
|
|
538
555
|
isSaving,
|
|
539
556
|
fontSize,
|
|
557
|
+
aiAutocompleteEnabled,
|
|
540
558
|
])
|
|
541
559
|
|
|
542
560
|
const updateCurrentEditorContent = (newContent: string) => {
|
|
@@ -646,6 +664,10 @@ export const CodeEditor = ({
|
|
|
646
664
|
{showImportAndFormatButtons && (
|
|
647
665
|
<CodeEditorHeader
|
|
648
666
|
entrypointFileName={entryPointFileName}
|
|
667
|
+
appendNewFile={(path: string, content: string) => {
|
|
668
|
+
onFileContentChanged?.(path, content)
|
|
669
|
+
}}
|
|
670
|
+
createFile={handleCreateFile}
|
|
649
671
|
fileSidebarState={
|
|
650
672
|
[sidebarOpen, setSidebarOpen] as ReturnType<
|
|
651
673
|
typeof useState<boolean>
|
|
@@ -655,6 +677,10 @@ export const CodeEditor = ({
|
|
|
655
677
|
files={Object.fromEntries(files.map((f) => [f.path, f.content]))}
|
|
656
678
|
updateFileContent={updateFileContent}
|
|
657
679
|
handleFileChange={handleFileChange}
|
|
680
|
+
aiAutocompleteState={[
|
|
681
|
+
aiAutocompleteEnabled,
|
|
682
|
+
setAiAutocompleteEnabled,
|
|
683
|
+
]}
|
|
658
684
|
/>
|
|
659
685
|
)}
|
|
660
686
|
<div
|