@tscircuit/fake-snippets 0.0.88 → 0.0.89
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 +187 -206
- package/dist/bundle.js +206 -100
- package/fake-snippets-api/routes/api/proxy.ts +128 -0
- package/package.json +56 -47
- package/renovate.json +2 -1
- package/src/App.tsx +22 -3
- package/src/ContextProviders.tsx +2 -0
- package/src/build-watcher.ts +52 -0
- 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 +17 -16
- 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 +16 -0
- package/src/components/package-port/CodeEditor.tsx +113 -11
- package/src/components/package-port/CodeEditorHeader.tsx +39 -4
- package/src/components/package-port/EditorNav.tsx +41 -15
- 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-current-package-release.ts +5 -1
- 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 +26 -8
- 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/main.tsx +7 -0
- package/src/pages/dashboard.tsx +8 -5
- package/src/pages/view-package.tsx +15 -7
- package/vite.config.ts +100 -1
|
@@ -184,7 +184,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
184
184
|
role="searchbox"
|
|
185
185
|
/>
|
|
186
186
|
{isLoading && (
|
|
187
|
-
<div className="absolute top-full w-lg left-0 right-0 mt-1 bg-white shadow-lg rounded-lg border w-80 grid place-items-center py-4 z-
|
|
187
|
+
<div className="absolute top-full w-lg left-0 right-0 mt-1 bg-white shadow-lg rounded-lg border w-80 grid place-items-center py-4 z-50 p-3">
|
|
188
188
|
<div className="flex items-center space-x-2">
|
|
189
189
|
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
|
|
190
190
|
<span className="text-gray-600 text-sm">Searching...</span>
|
|
@@ -195,7 +195,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
195
195
|
{showResults && searchResults && (
|
|
196
196
|
<div
|
|
197
197
|
ref={resultsRef}
|
|
198
|
-
className="absolute top-full md:left-0 right-0 mt-2 bg-white shadow-lg rounded-md z-
|
|
198
|
+
className="absolute top-full md:left-0 right-0 mt-2 bg-white shadow-lg rounded-md z-50 w-80 max-h-screen overflow-y-auto overflow-x-visible"
|
|
199
199
|
>
|
|
200
200
|
{searchResults.length > 0 ? (
|
|
201
201
|
<ul className="divide-y divide-gray-200">
|
|
@@ -9,8 +9,20 @@ export const SuspenseRunFrame = (
|
|
|
9
9
|
props: React.ComponentProps<typeof RunFrame>,
|
|
10
10
|
) => {
|
|
11
11
|
return (
|
|
12
|
-
<Suspense
|
|
13
|
-
|
|
12
|
+
<Suspense
|
|
13
|
+
fallback={
|
|
14
|
+
<div className="flex justify-center items-center h-full">
|
|
15
|
+
<div className="w-48">
|
|
16
|
+
<div className="loading">
|
|
17
|
+
<div className="loading-bar"></div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
}
|
|
22
|
+
>
|
|
23
|
+
<div className="h-[98vh]">
|
|
24
|
+
<RunFrame {...props} />
|
|
25
|
+
</div>
|
|
14
26
|
</Suspense>
|
|
15
27
|
)
|
|
16
28
|
}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from "react"
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Edit,
|
|
6
|
+
FileText,
|
|
7
|
+
Code,
|
|
8
|
+
Copy,
|
|
9
|
+
CopyCheck,
|
|
10
|
+
Loader2,
|
|
11
|
+
RefreshCcwIcon,
|
|
12
|
+
} from "lucide-react"
|
|
5
13
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
14
|
+
import { Button } from "@/components/ui/button"
|
|
6
15
|
import { usePackageFile, usePackageFileByPath } from "@/hooks/use-package-files"
|
|
7
16
|
import { ShikiCodeViewer } from "./ShikiCodeViewer"
|
|
8
17
|
import { SparklesIcon } from "lucide-react"
|
|
9
18
|
import MarkdownViewer from "./markdown-viewer"
|
|
19
|
+
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
10
20
|
|
|
11
21
|
interface PackageFile {
|
|
12
22
|
package_file_id: string
|
|
@@ -20,7 +30,7 @@ interface ImportantFilesViewProps {
|
|
|
20
30
|
importantFiles?: PackageFile[]
|
|
21
31
|
isLoading?: boolean
|
|
22
32
|
onEditClicked?: (file_path?: string | null) => void
|
|
23
|
-
|
|
33
|
+
packageAuthorOwner?: string | null
|
|
24
34
|
aiDescription?: string
|
|
25
35
|
aiUsageInstructions?: string
|
|
26
36
|
aiReviewText?: string | null
|
|
@@ -37,12 +47,20 @@ export default function ImportantFilesView({
|
|
|
37
47
|
onRequestAiReview,
|
|
38
48
|
isLoading = false,
|
|
39
49
|
onEditClicked,
|
|
50
|
+
packageAuthorOwner,
|
|
40
51
|
}: ImportantFilesViewProps) {
|
|
41
52
|
const [activeFilePath, setActiveFilePath] = useState<string | null>(null)
|
|
42
53
|
const [activeTab, setActiveTab] = useState<string | null>(null)
|
|
43
54
|
const [copyState, setCopyState] = useState<"copy" | "copied">("copy")
|
|
55
|
+
const { session: user } = useGlobalStore()
|
|
44
56
|
|
|
45
57
|
const handleCopy = () => {
|
|
58
|
+
if (activeTab === "ai-review" && aiReviewText) {
|
|
59
|
+
navigator.clipboard.writeText(aiReviewText || "")
|
|
60
|
+
setCopyState("copied")
|
|
61
|
+
setTimeout(() => setCopyState("copy"), 500)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
46
64
|
navigator.clipboard.writeText(activeFileContent)
|
|
47
65
|
setCopyState("copied")
|
|
48
66
|
setTimeout(() => setCopyState("copy"), 500)
|
|
@@ -131,20 +149,59 @@ export default function ImportantFilesView({
|
|
|
131
149
|
}
|
|
132
150
|
|
|
133
151
|
const renderAiReviewContent = () => {
|
|
152
|
+
const isOwner = user?.github_username === packageAuthorOwner
|
|
153
|
+
|
|
134
154
|
if (!aiReviewText && !aiReviewRequested) {
|
|
135
155
|
return (
|
|
136
|
-
<
|
|
137
|
-
className="text-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
156
|
+
<div className="flex flex-col items-center justify-center py-8 px-4">
|
|
157
|
+
<div className="text-center space-y-4 max-w-md">
|
|
158
|
+
<div className="flex justify-center">
|
|
159
|
+
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
|
|
160
|
+
<SparklesIcon className="h-6 w-6 text-blue-600" />
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
<div className="space-y-2">
|
|
164
|
+
<p className="text-sm text-gray-600">
|
|
165
|
+
Get detailed feedback and suggestions for improving your package
|
|
166
|
+
from our AI assistant.
|
|
167
|
+
</p>
|
|
168
|
+
</div>
|
|
169
|
+
{isOwner ? (
|
|
170
|
+
<Button
|
|
171
|
+
onClick={onRequestAiReview}
|
|
172
|
+
size="sm"
|
|
173
|
+
className="bg-blue-600 hover:bg-blue-700 text-white shadow-sm transition-all duration-200 hover:shadow-md"
|
|
174
|
+
>
|
|
175
|
+
<SparklesIcon className="h-4 w-4 mr-2" />
|
|
176
|
+
Request AI Review
|
|
177
|
+
</Button>
|
|
178
|
+
) : (
|
|
179
|
+
<p className="text-sm text-gray-500">
|
|
180
|
+
Only the package owner can generate an AI review
|
|
181
|
+
</p>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
142
185
|
)
|
|
143
186
|
}
|
|
144
187
|
|
|
145
188
|
if (!aiReviewText && aiReviewRequested) {
|
|
146
189
|
return (
|
|
147
|
-
<
|
|
190
|
+
<div className="flex flex-col items-center justify-center py-8 px-4">
|
|
191
|
+
<div className="text-center space-y-4 max-w-md">
|
|
192
|
+
<div className="flex justify-center">
|
|
193
|
+
<div className="w-12 h-12 bg-gray-200 rounded-full flex items-center justify-center">
|
|
194
|
+
<Loader2 className="h-6 w-6 text-gray-600 animate-spin" />
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
<div className="space-y-2">
|
|
198
|
+
<p className="text-sm text-gray-600">
|
|
199
|
+
Our AI is analyzing your package. This usually takes a few
|
|
200
|
+
minutes. Please check back shortly.
|
|
201
|
+
</p>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
148
205
|
)
|
|
149
206
|
}
|
|
150
207
|
|
|
@@ -162,6 +219,7 @@ export default function ImportantFilesView({
|
|
|
162
219
|
package_release_id: partialActiveFile.package_release_id,
|
|
163
220
|
}
|
|
164
221
|
: null,
|
|
222
|
+
{ keepPreviousData: true },
|
|
165
223
|
)
|
|
166
224
|
const activeFileContent = activeFileFull?.content_text || ""
|
|
167
225
|
|
|
@@ -210,6 +268,8 @@ export default function ImportantFilesView({
|
|
|
210
268
|
)
|
|
211
269
|
}
|
|
212
270
|
|
|
271
|
+
const isOwner = user?.github_username === packageAuthorOwner
|
|
272
|
+
|
|
213
273
|
return (
|
|
214
274
|
<div className="mt-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
|
|
215
275
|
<div className="flex items-center pl-2 pr-4 py-2 bg-gray-100 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d]">
|
|
@@ -268,7 +328,8 @@ export default function ImportantFilesView({
|
|
|
268
328
|
))}
|
|
269
329
|
</div>
|
|
270
330
|
<div className="ml-auto flex items-center">
|
|
271
|
-
{
|
|
331
|
+
{((activeTab === "file" && activeFileContent) ||
|
|
332
|
+
(activeTab === "ai-review" && aiReviewText)) && (
|
|
272
333
|
<button
|
|
273
334
|
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md transition-all duration-300"
|
|
274
335
|
onClick={handleCopy}
|
|
@@ -281,13 +342,25 @@ export default function ImportantFilesView({
|
|
|
281
342
|
<span className="sr-only">Copy</span>
|
|
282
343
|
</button>
|
|
283
344
|
)}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
345
|
+
{activeTab === "ai-review" && aiReviewText && isOwner && (
|
|
346
|
+
<button
|
|
347
|
+
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md ml-1"
|
|
348
|
+
onClick={onRequestAiReview}
|
|
349
|
+
title="Re-request AI Review"
|
|
350
|
+
>
|
|
351
|
+
<RefreshCcwIcon className="h-4 w-4" />
|
|
352
|
+
<span className="sr-only">Re-request AI Review</span>
|
|
353
|
+
</button>
|
|
354
|
+
)}
|
|
355
|
+
{activeTab === "file" && (
|
|
356
|
+
<button
|
|
357
|
+
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md"
|
|
358
|
+
onClick={() => onEditClicked?.(activeFilePath)}
|
|
359
|
+
>
|
|
360
|
+
<Edit className="h-4 w-4" />
|
|
361
|
+
<span className="sr-only">Edit</span>
|
|
362
|
+
</button>
|
|
363
|
+
)}
|
|
291
364
|
</div>
|
|
292
365
|
</div>
|
|
293
366
|
<div className="p-4 bg-white dark:bg-[#0d1117]">
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
Pencil,
|
|
13
13
|
GitForkIcon,
|
|
14
14
|
DownloadIcon,
|
|
15
|
+
Package2,
|
|
15
16
|
} from "lucide-react"
|
|
16
17
|
import MainContentViewSelector from "./main-content-view-selector"
|
|
17
18
|
import {
|
|
@@ -24,8 +25,11 @@ import {
|
|
|
24
25
|
import { DownloadButtonAndMenu } from "@/components/DownloadButtonAndMenu"
|
|
25
26
|
import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
|
|
26
27
|
import { useLocation } from "wouter"
|
|
27
|
-
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
28
|
+
import { Package, PackageFile } from "fake-snippets-api/lib/db/schema"
|
|
29
|
+
import { usePackageFiles } from "@/hooks/use-package-files"
|
|
30
|
+
import { useDownloadZip } from "@/hooks/use-download-zip"
|
|
28
31
|
interface MainContentHeaderProps {
|
|
32
|
+
packageFiles: PackageFile[]
|
|
29
33
|
activeView: string
|
|
30
34
|
onViewChange: (view: string) => void
|
|
31
35
|
onExportClicked?: (exportType: string) => void
|
|
@@ -33,6 +37,7 @@ interface MainContentHeaderProps {
|
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
export default function MainContentHeader({
|
|
40
|
+
packageFiles,
|
|
36
41
|
activeView,
|
|
37
42
|
onViewChange,
|
|
38
43
|
packageInfo,
|
|
@@ -59,6 +64,14 @@ export default function MainContentHeader({
|
|
|
59
64
|
setTimeout(() => setCopyCloneState("copy"), 2000)
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
const { downloadZip } = useDownloadZip()
|
|
68
|
+
|
|
69
|
+
const handleDownloadZip = () => {
|
|
70
|
+
if (packageInfo && packageFiles) {
|
|
71
|
+
downloadZip(packageInfo, packageFiles)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
const { circuitJson } = useCurrentPackageCircuitJson()
|
|
63
76
|
|
|
64
77
|
return (
|
|
@@ -70,7 +83,9 @@ export default function MainContentHeader({
|
|
|
70
83
|
|
|
71
84
|
<div className="flex space-x-2">
|
|
72
85
|
<DownloadButtonAndMenu
|
|
73
|
-
|
|
86
|
+
unscopedName={packageInfo?.unscoped_name}
|
|
87
|
+
desiredImageType={activeView}
|
|
88
|
+
author={packageInfo?.owner_github_username ?? undefined}
|
|
74
89
|
circuitJson={circuitJson}
|
|
75
90
|
/>
|
|
76
91
|
|
|
@@ -96,6 +111,15 @@ export default function MainContentHeader({
|
|
|
96
111
|
Edit Online
|
|
97
112
|
</a>
|
|
98
113
|
</DropdownMenuItem>
|
|
114
|
+
|
|
115
|
+
<DropdownMenuItem
|
|
116
|
+
disabled={!Boolean(packageInfo)}
|
|
117
|
+
onClick={handleDownloadZip}
|
|
118
|
+
className="cursor-pointer p-2 py-4"
|
|
119
|
+
>
|
|
120
|
+
<Package2 className="h-4 w-4 mx-3" />
|
|
121
|
+
Download ZIP
|
|
122
|
+
</DropdownMenuItem>
|
|
99
123
|
<DropdownMenuSeparator />
|
|
100
124
|
|
|
101
125
|
{/* Install Option */}
|
|
@@ -41,8 +41,8 @@ const MobileSidebar = ({
|
|
|
41
41
|
const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
|
|
42
42
|
const isOwner =
|
|
43
43
|
isLoggedIn &&
|
|
44
|
-
packageInfo?.
|
|
45
|
-
useGlobalStore((s) => s.session?.
|
|
44
|
+
packageInfo?.owner_github_username ===
|
|
45
|
+
useGlobalStore((s) => s.session?.github_username)
|
|
46
46
|
|
|
47
47
|
const {
|
|
48
48
|
Dialog: EditPackageDetailsDialog,
|
|
@@ -24,6 +24,9 @@ import { useLocation } from "wouter"
|
|
|
24
24
|
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
25
25
|
import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
|
|
26
26
|
import { useRequestAiReviewMutation } from "@/hooks/use-request-ai-review-mutation"
|
|
27
|
+
import { useAiReview } from "@/hooks/use-ai-review"
|
|
28
|
+
import { useQueryClient } from "react-query"
|
|
29
|
+
import SidebarReleasesSection from "./sidebar-releases-section"
|
|
27
30
|
|
|
28
31
|
interface PackageFile {
|
|
29
32
|
package_file_id: string
|
|
@@ -51,14 +54,38 @@ export default function RepoPageContent({
|
|
|
51
54
|
onEditClicked,
|
|
52
55
|
}: RepoPageContentProps) {
|
|
53
56
|
const [activeView, setActiveView] = useState<string>("files")
|
|
57
|
+
const [pendingAiReviewId, setPendingAiReviewId] = useState<string | null>(
|
|
58
|
+
null,
|
|
59
|
+
)
|
|
60
|
+
const queryClient = useQueryClient()
|
|
61
|
+
const { data: aiReview } = useAiReview(pendingAiReviewId, {
|
|
62
|
+
refetchInterval: (data) => (data && !data.ai_review_text ? 2000 : false),
|
|
63
|
+
})
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (aiReview?.ai_review_text) {
|
|
66
|
+
queryClient.invalidateQueries(["packageRelease"])
|
|
67
|
+
setPendingAiReviewId(null)
|
|
68
|
+
}
|
|
69
|
+
}, [aiReview?.ai_review_text, queryClient])
|
|
54
70
|
const session = useGlobalStore((s) => s.session)
|
|
55
71
|
const { circuitJson, isLoading: isCircuitJsonLoading } =
|
|
56
72
|
useCurrentPackageCircuitJson()
|
|
57
|
-
const { mutate: requestAiReview } =
|
|
73
|
+
const { mutate: requestAiReview, isLoading: isRequestingAiReview } =
|
|
74
|
+
useRequestAiReviewMutation({
|
|
75
|
+
onSuccess: (_packageRelease, aiReview) => {
|
|
76
|
+
setPendingAiReviewId(aiReview.ai_review_id)
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const aiReviewRequested =
|
|
81
|
+
Boolean(packageRelease?.ai_review_requested) ||
|
|
82
|
+
Boolean(pendingAiReviewId) ||
|
|
83
|
+
isRequestingAiReview
|
|
58
84
|
|
|
59
85
|
// Handle initial view selection and hash-based view changes
|
|
60
86
|
useEffect(() => {
|
|
61
87
|
if (isCircuitJsonLoading) return
|
|
88
|
+
if (!packageInfo) return
|
|
62
89
|
const hash = window.location.hash.slice(1)
|
|
63
90
|
const validViews = ["files", "3d", "pcb", "schematic", "bom"]
|
|
64
91
|
const circuitDependentViews = ["3d", "pcb", "schematic", "bom"]
|
|
@@ -105,34 +132,6 @@ export default function RepoPageContent({
|
|
|
105
132
|
.reverse()
|
|
106
133
|
}, [packageFiles, importantFilePaths])
|
|
107
134
|
|
|
108
|
-
// Generate package name with version for file lookups
|
|
109
|
-
const packageNameWithVersion = useMemo(() => {
|
|
110
|
-
if (!packageInfo) return ""
|
|
111
|
-
|
|
112
|
-
// Format: @scope/packageName@version or packageName@version
|
|
113
|
-
const name = packageInfo.name
|
|
114
|
-
|
|
115
|
-
// Extract the latest version from the files (assuming version information is available)
|
|
116
|
-
const versionFile = packageFiles?.find(
|
|
117
|
-
(file) => file.file_path === "package.json",
|
|
118
|
-
)
|
|
119
|
-
let version = "latest"
|
|
120
|
-
|
|
121
|
-
if (versionFile) {
|
|
122
|
-
try {
|
|
123
|
-
const content =
|
|
124
|
-
versionFile.file_content || versionFile.content_text || "{}"
|
|
125
|
-
const packageJson = JSON.parse(content)
|
|
126
|
-
if (packageJson.version) {
|
|
127
|
-
version = packageJson.version
|
|
128
|
-
}
|
|
129
|
-
} catch (e) {
|
|
130
|
-
// If package.json can't be parsed, use "latest"
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return `${name}@${version}`
|
|
135
|
-
}, [packageInfo, packageFiles])
|
|
136
135
|
// Render the appropriate content based on the active view
|
|
137
136
|
const renderContent = () => {
|
|
138
137
|
switch (activeView) {
|
|
@@ -194,6 +193,7 @@ export default function RepoPageContent({
|
|
|
194
193
|
{/* Main Content Header with Tabs */}
|
|
195
194
|
<MainContentHeader
|
|
196
195
|
activeView={activeView}
|
|
196
|
+
packageFiles={packageFiles ?? []}
|
|
197
197
|
onViewChange={(view) => {
|
|
198
198
|
setActiveView(view)
|
|
199
199
|
// Update URL hash when view changes
|
|
@@ -210,10 +210,11 @@ export default function RepoPageContent({
|
|
|
210
210
|
importantFiles={importantFiles}
|
|
211
211
|
isLoading={!packageFiles}
|
|
212
212
|
onEditClicked={onEditClicked}
|
|
213
|
+
packageAuthorOwner={packageInfo?.owner_github_username}
|
|
213
214
|
aiDescription={packageInfo?.ai_description ?? ""}
|
|
214
215
|
aiUsageInstructions={packageInfo?.ai_usage_instructions ?? ""}
|
|
215
216
|
aiReviewText={packageRelease?.ai_review_text ?? null}
|
|
216
|
-
aiReviewRequested={
|
|
217
|
+
aiReviewRequested={aiReviewRequested}
|
|
217
218
|
onRequestAiReview={() => {
|
|
218
219
|
if (packageRelease) {
|
|
219
220
|
requestAiReview({
|
|
@@ -236,6 +237,10 @@ export default function RepoPageContent({
|
|
|
236
237
|
}}
|
|
237
238
|
/>
|
|
238
239
|
</div>
|
|
240
|
+
{/* Releases section - Only visible on small screens */}
|
|
241
|
+
<div className="block md:hidden w-full px-5">
|
|
242
|
+
<SidebarReleasesSection />
|
|
243
|
+
</div>
|
|
239
244
|
</div>
|
|
240
245
|
</div>
|
|
241
246
|
<Footer />
|
|
@@ -39,8 +39,8 @@ export default function SidebarAboutSection() {
|
|
|
39
39
|
const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
|
|
40
40
|
const isOwner =
|
|
41
41
|
isLoggedIn &&
|
|
42
|
-
packageInfo?.
|
|
43
|
-
useGlobalStore((s) => s.session?.
|
|
42
|
+
packageInfo?.owner_github_username ===
|
|
43
|
+
useGlobalStore((s) => s.session?.github_username)
|
|
44
44
|
|
|
45
45
|
// Local state to store updated values before the query refetches
|
|
46
46
|
const [localDescription, setLocalDescription] = useState<string>("")
|
|
@@ -9,21 +9,29 @@ import type { PackageRelease } from "fake-snippets-api/lib/db/schema"
|
|
|
9
9
|
function getTranspilationStatus(
|
|
10
10
|
pr?: PackageRelease | null,
|
|
11
11
|
): BuildStep["status"] {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
switch (pr?.transpilation_display_status) {
|
|
13
|
+
case "complete":
|
|
14
|
+
return "success"
|
|
15
|
+
case "error":
|
|
16
|
+
return "error"
|
|
17
|
+
case "building":
|
|
18
|
+
return "running"
|
|
19
|
+
default:
|
|
20
|
+
return "pending"
|
|
21
|
+
}
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
function getCircuitJsonStatus(pr?: PackageRelease | null): BuildStep["status"] {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
switch (pr?.circuit_json_build_display_status) {
|
|
26
|
+
case "complete":
|
|
27
|
+
return "success"
|
|
28
|
+
case "error":
|
|
29
|
+
return "error"
|
|
30
|
+
case "building":
|
|
31
|
+
return "running"
|
|
32
|
+
default:
|
|
33
|
+
return "pending"
|
|
34
|
+
}
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
export default function SidebarReleasesSection() {
|
|
@@ -5,13 +5,6 @@ import { FileText, Folder } from "lucide-react"
|
|
|
5
5
|
import { useMemo, useState } from "react"
|
|
6
6
|
import { isHiddenFile } from "../../utils/is-hidden-file"
|
|
7
7
|
import { isWithinDirectory } from "../../utils/is-within-directory"
|
|
8
|
-
import {
|
|
9
|
-
DropdownMenu,
|
|
10
|
-
DropdownMenuContent,
|
|
11
|
-
DropdownMenuItem,
|
|
12
|
-
DropdownMenuTrigger,
|
|
13
|
-
} from "@/components/ui/dropdown-menu"
|
|
14
|
-
import { Settings } from "lucide-react"
|
|
15
8
|
import HiddenFilesDropdown from "@/components/HiddenFilesDropdown"
|
|
16
9
|
|
|
17
10
|
interface Directory {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export const fuzzyMatch = (
|
|
2
|
+
pattern: string,
|
|
3
|
+
text: string,
|
|
4
|
+
): { score: number; matches: number[] } => {
|
|
5
|
+
if (!pattern) return { score: 0, matches: [] }
|
|
6
|
+
|
|
7
|
+
const normalizePattern = (pat: string) => {
|
|
8
|
+
return pat.toLowerCase().split(" ").join("")
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const patternLower = normalizePattern(pattern)
|
|
12
|
+
const textLower = text.toLowerCase()
|
|
13
|
+
const matches: number[] = []
|
|
14
|
+
|
|
15
|
+
let patternIdx = 0
|
|
16
|
+
let score = 0
|
|
17
|
+
let consecutiveMatches = 0
|
|
18
|
+
let spaceBonus = 0
|
|
19
|
+
|
|
20
|
+
const spaceSegments = pattern.toLowerCase().trim().split(/\s+/)
|
|
21
|
+
const isSpaceSeparated = spaceSegments.length > 1
|
|
22
|
+
|
|
23
|
+
if (isSpaceSeparated) {
|
|
24
|
+
let segmentIdx = 0
|
|
25
|
+
let currentSegment = spaceSegments[0]
|
|
26
|
+
let segmentCharIdx = 0
|
|
27
|
+
|
|
28
|
+
for (
|
|
29
|
+
let i = 0;
|
|
30
|
+
i < textLower.length && segmentIdx < spaceSegments.length;
|
|
31
|
+
i++
|
|
32
|
+
) {
|
|
33
|
+
const char = textLower[i]
|
|
34
|
+
const targetChar = currentSegment[segmentCharIdx]
|
|
35
|
+
|
|
36
|
+
if (char === targetChar) {
|
|
37
|
+
matches.push(i)
|
|
38
|
+
segmentCharIdx++
|
|
39
|
+
consecutiveMatches++
|
|
40
|
+
score += 1 + consecutiveMatches * 0.5
|
|
41
|
+
|
|
42
|
+
if (i === 0 || /[/\-_.]/.test(text[i - 1])) {
|
|
43
|
+
score += 2
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (segmentCharIdx >= currentSegment.length) {
|
|
47
|
+
segmentIdx++
|
|
48
|
+
spaceBonus += 3
|
|
49
|
+
|
|
50
|
+
if (segmentIdx < spaceSegments.length) {
|
|
51
|
+
currentSegment = spaceSegments[segmentIdx]
|
|
52
|
+
segmentCharIdx = 0
|
|
53
|
+
consecutiveMatches = 0
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
if (
|
|
58
|
+
segmentCharIdx > 0 &&
|
|
59
|
+
/[/\-_.]/.test(char) &&
|
|
60
|
+
segmentIdx < spaceSegments.length - 1
|
|
61
|
+
) {
|
|
62
|
+
segmentIdx++
|
|
63
|
+
if (segmentIdx < spaceSegments.length) {
|
|
64
|
+
currentSegment = spaceSegments[segmentIdx]
|
|
65
|
+
segmentCharIdx = 0
|
|
66
|
+
consecutiveMatches = 0
|
|
67
|
+
if (char === currentSegment[0]) {
|
|
68
|
+
matches.push(i)
|
|
69
|
+
segmentCharIdx = 1
|
|
70
|
+
score += 2
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
consecutiveMatches = 0
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
segmentIdx < spaceSegments.length ||
|
|
81
|
+
(segmentIdx === spaceSegments.length - 1 &&
|
|
82
|
+
segmentCharIdx < currentSegment.length)
|
|
83
|
+
) {
|
|
84
|
+
return { score: -1, matches: [] }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
score += spaceBonus
|
|
88
|
+
} else {
|
|
89
|
+
for (
|
|
90
|
+
let i = 0;
|
|
91
|
+
i < textLower.length && patternIdx < patternLower.length;
|
|
92
|
+
i++
|
|
93
|
+
) {
|
|
94
|
+
if (textLower[i] === patternLower[patternIdx]) {
|
|
95
|
+
matches.push(i)
|
|
96
|
+
patternIdx++
|
|
97
|
+
consecutiveMatches++
|
|
98
|
+
|
|
99
|
+
score += 1 + consecutiveMatches * 0.5
|
|
100
|
+
|
|
101
|
+
if (i === 0 || /[/\-_.]/.test(text[i - 1])) {
|
|
102
|
+
score += 2
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
consecutiveMatches = 0
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (patternIdx !== patternLower.length) return { score: -1, matches: [] }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
score += Math.max(0, 100 - text.length) * 0.1
|
|
113
|
+
|
|
114
|
+
const fileName = text.split("/").pop() || text
|
|
115
|
+
const queryFileName = pattern.replace(/\s+/g, "")
|
|
116
|
+
if (fileName.toLowerCase().includes(queryFileName.toLowerCase())) {
|
|
117
|
+
score += 5
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { score, matches }
|
|
121
|
+
}
|
|
@@ -33,7 +33,7 @@ export const ConfirmDeletePackageDialog = ({
|
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
35
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
36
|
-
<DialogContent>
|
|
36
|
+
<DialogContent className="w-[90vw]">
|
|
37
37
|
<DialogHeader>
|
|
38
38
|
<DialogTitle>Confirm Delete Package</DialogTitle>
|
|
39
39
|
</DialogHeader>
|