@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.
Files changed (39) hide show
  1. package/bun.lock +84 -108
  2. package/dist/bundle.js +493 -372
  3. package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +135 -0
  4. package/package.json +8 -10
  5. package/src/App.tsx +47 -2
  6. package/src/ContextProviders.tsx +0 -2
  7. package/src/components/CircuitJsonImportDialog.tsx +1 -1
  8. package/src/components/PackageBuildsPage/package-build-header.tsx +2 -12
  9. package/src/components/PageSearchComponent.tsx +2 -2
  10. package/src/components/SearchComponent.tsx +2 -2
  11. package/src/components/TrendingPackagesCarousel.tsx +2 -2
  12. package/src/components/dialogs/import-component-dialog.tsx +25 -0
  13. package/src/components/package-port/CodeAndPreview.tsx +1 -16
  14. package/src/components/package-port/CodeEditor.tsx +34 -8
  15. package/src/components/package-port/CodeEditorHeader.tsx +75 -13
  16. package/src/components/package-port/EditorNav.tsx +58 -65
  17. package/src/components/package-port/GlobalFindReplace.tsx +1 -1
  18. package/src/components/package-port/QuickOpen.tsx +1 -1
  19. package/src/hooks/use-axios.ts +2 -2
  20. package/src/hooks/use-code-completion-ai-api.ts +3 -3
  21. package/src/hooks/use-delete-package.ts +6 -2
  22. package/src/hooks/use-packages-base-api-url.ts +3 -0
  23. package/src/hooks/use-request-ai-review-mutation.ts +1 -1
  24. package/src/hooks/use-sign-in.ts +2 -2
  25. package/src/hooks/use-toast.tsx +1 -0
  26. package/src/hooks/useFileManagement.ts +10 -5
  27. package/src/lib/utils/findTargetFile.ts +1 -1
  28. package/src/lib/utils/package-utils.ts +10 -0
  29. package/src/main.tsx +0 -3
  30. package/src/pages/dashboard.tsx +12 -2
  31. package/src/pages/dev-login.tsx +2 -2
  32. package/src/pages/latest.tsx +2 -2
  33. package/src/pages/search.tsx +2 -2
  34. package/src/pages/trending.tsx +2 -2
  35. package/src/pages/user-profile.tsx +2 -2
  36. package/src/types/package.ts +4 -0
  37. package/vite.config.ts +0 -19
  38. package/src/build-watcher.ts +0 -52
  39. 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.89",
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/props": "^0.0.246",
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.195",
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.16",
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.setState({ reloading: true })
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>
@@ -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 "./package-port/CodeAndPreview"
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, RotateCcw } from "lucide-react"
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, refetch, isFetching } = useCurrentPackageRelease({
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 { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
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 = useSnippetsBaseApiUrl()
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 { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
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 = useSnippetsBaseApiUrl()
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 { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
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 = useSnippetsBaseApiUrl()
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 { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
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 "./CodeAndPreview"
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 = useSnippetsBaseApiUrl()
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 (codeCompletionApi?.apiKey) {
329
+ if (aiAutocompleteEnabled) {
328
330
  baseExtensions.push(
329
- // copilotPlugin({
330
- // apiKey: codeCompletionApi.apiKey,
331
- // language: Language.TYPESCRIPT,
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