@tscircuit/fake-snippets 0.0.80 → 0.0.82
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-tests/fake-snippets-api/routes/package_releases/create.test.ts +6 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/update.test.ts +3 -0
- package/bun.lock +24 -18
- package/dist/bundle.js +25 -9
- package/dist/index.d.ts +10 -0
- package/dist/index.js +4 -1
- package/dist/schema.d.ts +16 -0
- package/dist/schema.js +4 -1
- package/fake-snippets-api/lib/db/schema.ts +4 -0
- package/fake-snippets-api/routes/api/package_releases/update.ts +14 -1
- package/fake-snippets-api/routes/api/packages/generate_from_jlcpcb.ts +3 -3
- package/package.json +5 -5
- package/src/components/JLCPCBImportDialog.tsx +164 -62
- package/src/components/PackageBuildsPage/LogContent.tsx +12 -5
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +8 -7
- package/src/components/PackageBuildsPage/build-preview-content.tsx +1 -1
- package/src/components/PackageBuildsPage/collapsible-section.tsx +14 -46
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +28 -10
- package/src/components/PackageBuildsPage/package-build-header.tsx +14 -2
- package/src/components/ViewPackagePage/components/build-status.tsx +24 -85
- package/src/components/ViewPackagePage/components/important-files-view.tsx +65 -2
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +13 -0
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +28 -5
- package/src/components/package-port/CodeAndPreview.tsx +7 -1
- package/src/components/package-port/CodeEditor.tsx +9 -1
- package/src/hooks/use-current-package-release.ts +4 -2
- package/src/hooks/use-now.ts +12 -0
- package/src/hooks/use-package-release.ts +3 -2
- package/src/hooks/use-rebuild-package-release-mutation.ts +41 -0
- package/src/hooks/use-request-ai-review-mutation.ts +45 -0
- package/src/hooks/useFileManagement.ts +1 -2
- package/src/lib/codemirror/basic-setup.ts +17 -1
- package/src/pages/dashboard.tsx +3 -1
- package/src/pages/editor.tsx +5 -1
- package/src/pages/user-profile.tsx +9 -2
- package/src/pages/view-package.tsx +1 -0
- package/.github/workflows/formatbot.yml +0 -63
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type React from "react"
|
|
2
|
-
import { ChevronRight
|
|
3
|
-
import { Badge } from "@/components/ui/badge"
|
|
2
|
+
import { ChevronRight } from "lucide-react"
|
|
4
3
|
import {
|
|
5
4
|
Collapsible,
|
|
6
5
|
CollapsibleContent,
|
|
@@ -8,16 +7,9 @@ import {
|
|
|
8
7
|
} from "@/components/ui/collapsible"
|
|
9
8
|
import { getColorForDisplayStatus } from "./getColorForDisplayStatus"
|
|
10
9
|
import { PackageRelease } from "fake-snippets-api/lib/db/schema"
|
|
11
|
-
import { ErrorObjectOrString
|
|
10
|
+
import { ErrorObjectOrString } from "./ErrorObject"
|
|
12
11
|
import { capitalCase } from "./capitalCase"
|
|
13
12
|
|
|
14
|
-
type BadgeInfo = {
|
|
15
|
-
text: string
|
|
16
|
-
variant?: "default" | "secondary" | "destructive"
|
|
17
|
-
className?: string
|
|
18
|
-
icon?: React.ReactNode
|
|
19
|
-
}
|
|
20
|
-
|
|
21
13
|
interface CollapsibleSectionProps {
|
|
22
14
|
title: string
|
|
23
15
|
duration?: string
|
|
@@ -25,69 +17,45 @@ interface CollapsibleSectionProps {
|
|
|
25
17
|
displayStatus?: PackageRelease["display_status"]
|
|
26
18
|
isOpen: boolean
|
|
27
19
|
onToggle: () => void
|
|
28
|
-
badges?: Array<BadgeInfo>
|
|
29
20
|
children?: React.ReactNode
|
|
30
21
|
}
|
|
31
22
|
|
|
32
23
|
export function CollapsibleSection({
|
|
33
24
|
title,
|
|
34
25
|
duration,
|
|
35
|
-
error,
|
|
36
26
|
displayStatus,
|
|
37
27
|
isOpen,
|
|
38
28
|
onToggle,
|
|
39
|
-
badges = [],
|
|
40
29
|
children,
|
|
41
30
|
}: CollapsibleSectionProps) {
|
|
42
31
|
return (
|
|
43
32
|
<Collapsible open={isOpen} onOpenChange={onToggle}>
|
|
44
33
|
<CollapsibleTrigger asChild>
|
|
45
|
-
<div className="flex items-center justify-between p-4 bg-white border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-100">
|
|
46
|
-
<div className="flex items-center gap-2">
|
|
34
|
+
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 p-4 bg-white border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-100">
|
|
35
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
47
36
|
<ChevronRight
|
|
48
|
-
className={`w-4 h-4 transition-transform ${isOpen ? "rotate-90" : ""}`}
|
|
37
|
+
className={`w-4 h-4 flex-shrink-0 transition-transform ${isOpen ? "rotate-90" : ""}`}
|
|
49
38
|
/>
|
|
50
|
-
<span className="font-medium">{title}</span>
|
|
39
|
+
<span className="font-medium truncate">{title}</span>
|
|
51
40
|
</div>
|
|
52
|
-
<div className="flex items-center gap-2">
|
|
53
|
-
{[
|
|
54
|
-
...badges,
|
|
55
|
-
...(error
|
|
56
|
-
? [
|
|
57
|
-
{
|
|
58
|
-
text: getErrorText(error),
|
|
59
|
-
variant: "destructive",
|
|
60
|
-
} as BadgeInfo,
|
|
61
|
-
]
|
|
62
|
-
: []),
|
|
63
|
-
].map((badge, index) => (
|
|
64
|
-
<Badge
|
|
65
|
-
key={index}
|
|
66
|
-
variant={badge.variant || "secondary"}
|
|
67
|
-
className={
|
|
68
|
-
badge.className ||
|
|
69
|
-
"bg-gray-200 text-gray-700 flex items-center gap-1"
|
|
70
|
-
}
|
|
71
|
-
>
|
|
72
|
-
{badge.icon}
|
|
73
|
-
{badge.text}
|
|
74
|
-
</Badge>
|
|
75
|
-
))}
|
|
41
|
+
<div className="flex items-center gap-2 flex-shrink-0 ml-6 sm:ml-0">
|
|
76
42
|
{duration && (
|
|
77
|
-
<span className="text-sm text-gray-600">
|
|
43
|
+
<span className="text-sm text-gray-600 whitespace-nowrap">
|
|
44
|
+
{duration}
|
|
45
|
+
</span>
|
|
78
46
|
)}
|
|
79
47
|
<div
|
|
80
|
-
className={`w-2 h-2 rounded-lg ${getColorForDisplayStatus(displayStatus)}`}
|
|
48
|
+
className={`w-2 h-2 rounded-lg flex-shrink-0 ${getColorForDisplayStatus(displayStatus)}`}
|
|
81
49
|
/>
|
|
82
|
-
<div className="text-gray-600 text-xs font-medium">
|
|
50
|
+
<div className="text-gray-600 text-xs font-medium whitespace-nowrap">
|
|
83
51
|
{capitalCase(displayStatus) || "???"}
|
|
84
52
|
</div>
|
|
85
53
|
</div>
|
|
86
54
|
</div>
|
|
87
55
|
</CollapsibleTrigger>
|
|
88
56
|
<CollapsibleContent>
|
|
89
|
-
<div className="
|
|
90
|
-
{children}
|
|
57
|
+
<div className="bg-white border-x border-b border-gray-200 rounded-b-lg overflow-hidden">
|
|
58
|
+
<div className="p-4 overflow-x-auto max-w-full">{children}</div>
|
|
91
59
|
</div>
|
|
92
60
|
</CollapsibleContent>
|
|
93
61
|
</Collapsible>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Globe, GitBranch, GitCommit, Clock } from "lucide-react"
|
|
2
1
|
import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
|
|
3
|
-
import {
|
|
2
|
+
import { useNow } from "@/hooks/use-now"
|
|
4
3
|
import { timeAgo } from "@/lib/utils/timeAgo"
|
|
5
4
|
import { PackageRelease } from "fake-snippets-api/lib/db/schema"
|
|
5
|
+
import { Clock, GitBranch, GitCommit, Globe } from "lucide-react"
|
|
6
|
+
import { useParams } from "wouter"
|
|
6
7
|
|
|
7
8
|
const capitalCase = (str: string) => {
|
|
8
9
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
@@ -24,8 +25,9 @@ function getColorFromDisplayStatus(
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export function PackageBuildDetailsPanel() {
|
|
27
|
-
const { packageRelease } = useCurrentPackageRelease()
|
|
28
|
+
const { packageRelease } = useCurrentPackageRelease({ refetchInterval: 2000 })
|
|
28
29
|
const { author } = useParams() // TODO use packageRelease.author_account_id when it's added by backed
|
|
30
|
+
const now = useNow(1000)
|
|
29
31
|
|
|
30
32
|
if (!packageRelease) {
|
|
31
33
|
// TODO show skeleton instead
|
|
@@ -55,6 +57,24 @@ export function PackageBuildDetailsPanel() {
|
|
|
55
57
|
commit_sha,
|
|
56
58
|
} = packageRelease
|
|
57
59
|
|
|
60
|
+
const buildStartedAt = (() => {
|
|
61
|
+
if (transpilation_started_at && circuit_json_build_started_at) {
|
|
62
|
+
return new Date(transpilation_started_at) <
|
|
63
|
+
new Date(circuit_json_build_started_at)
|
|
64
|
+
? transpilation_started_at
|
|
65
|
+
: circuit_json_build_started_at
|
|
66
|
+
}
|
|
67
|
+
return transpilation_started_at || circuit_json_build_started_at || null
|
|
68
|
+
})()
|
|
69
|
+
|
|
70
|
+
const buildCompletedAt =
|
|
71
|
+
circuit_json_build_completed_at || transpilation_completed_at || null
|
|
72
|
+
|
|
73
|
+
const elapsedMs = buildStartedAt
|
|
74
|
+
? (buildCompletedAt ? new Date(buildCompletedAt).getTime() : now) -
|
|
75
|
+
new Date(buildStartedAt).getTime()
|
|
76
|
+
: null
|
|
77
|
+
|
|
58
78
|
return (
|
|
59
79
|
<div className="space-y-6 bg-white p-4 border border-gray-200 rounded-lg">
|
|
60
80
|
{/* Created */}
|
|
@@ -92,15 +112,13 @@ export function PackageBuildDetailsPanel() {
|
|
|
92
112
|
<h3 className="text-sm font-medium text-gray-600 mb-2">Build Time</h3>
|
|
93
113
|
<div className="flex items-center gap-2">
|
|
94
114
|
<Clock className="w-4 h-4 text-gray-500" />
|
|
95
|
-
{
|
|
96
|
-
<span className="text-sm">
|
|
97
|
-
{total_build_duration_ms
|
|
98
|
-
? `${Math.floor(total_build_duration_ms / 1000)}s`
|
|
99
|
-
: ""}
|
|
100
|
-
</span>
|
|
115
|
+
{elapsedMs !== null && (
|
|
116
|
+
<span className="text-sm">{Math.floor(elapsedMs / 1000)}s</span>
|
|
101
117
|
)}
|
|
102
118
|
<span className="text-sm text-gray-500">
|
|
103
|
-
{
|
|
119
|
+
{buildStartedAt
|
|
120
|
+
? `Started ${timeAgo(buildStartedAt)}`
|
|
121
|
+
: "waiting..."}
|
|
104
122
|
</span>
|
|
105
123
|
</div>
|
|
106
124
|
</div>
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { Github, RefreshCw } from "lucide-react"
|
|
2
1
|
import { Button } from "@/components/ui/button"
|
|
2
|
+
import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
|
|
3
|
+
import { useRebuildPackageReleaseMutation } from "@/hooks/use-rebuild-package-release-mutation"
|
|
4
|
+
import { Github, RefreshCw } from "lucide-react"
|
|
3
5
|
import { useParams } from "wouter"
|
|
4
6
|
import { DownloadButtonAndMenu } from "../DownloadButtonAndMenu"
|
|
5
7
|
|
|
6
8
|
export function PackageBuildHeader() {
|
|
7
9
|
const { author, packageName } = useParams()
|
|
10
|
+
const { packageRelease } = useCurrentPackageRelease({ refetchInterval: 2000 })
|
|
11
|
+
const { mutate: rebuildPackage, isLoading } =
|
|
12
|
+
useRebuildPackageReleaseMutation()
|
|
8
13
|
|
|
9
14
|
return (
|
|
10
15
|
<div className="border-b border-gray-200 bg-white px-6 py-4">
|
|
@@ -38,9 +43,16 @@ export function PackageBuildHeader() {
|
|
|
38
43
|
variant="outline"
|
|
39
44
|
size="sm"
|
|
40
45
|
className="border-gray-300 bg-white hover:bg-gray-50 text-xs sm:text-sm"
|
|
46
|
+
disabled={isLoading || !packageRelease}
|
|
47
|
+
onClick={() =>
|
|
48
|
+
packageRelease &&
|
|
49
|
+
rebuildPackage({
|
|
50
|
+
package_release_id: packageRelease.package_release_id,
|
|
51
|
+
})
|
|
52
|
+
}
|
|
41
53
|
>
|
|
42
54
|
<RefreshCw className="w-3 h-3 sm:w-4 sm:h-4 mr-1 sm:mr-2" />
|
|
43
|
-
Rebuild
|
|
55
|
+
{isLoading ? "Rebuilding..." : "Rebuild"}
|
|
44
56
|
</Button>
|
|
45
57
|
<DownloadButtonAndMenu
|
|
46
58
|
snippetUnscopedName={`${author}/${packageName}`}
|
|
@@ -1,99 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
Dialog,
|
|
4
|
-
DialogContent,
|
|
5
|
-
DialogHeader,
|
|
6
|
-
DialogTitle,
|
|
7
|
-
} from "@/components/ui/dialog"
|
|
8
|
-
import { CheckCircle, XCircle, Check, X } from "lucide-react"
|
|
9
|
-
import { cn } from "@/lib/utils"
|
|
1
|
+
import { CheckCircle, XCircle, Clock, Loader2 } from "lucide-react"
|
|
2
|
+
import { Link, useParams } from "wouter"
|
|
10
3
|
|
|
11
4
|
export interface BuildStep {
|
|
12
5
|
id: string
|
|
13
6
|
name: string
|
|
14
|
-
status: "success" | "
|
|
15
|
-
message?: string
|
|
7
|
+
status: "pending" | "running" | "success" | "error"
|
|
16
8
|
}
|
|
17
9
|
|
|
18
10
|
export interface BuildStatusProps {
|
|
19
11
|
step: BuildStep
|
|
12
|
+
packageReleaseId: string
|
|
20
13
|
}
|
|
21
14
|
|
|
22
|
-
export const BuildStatus = ({ step }: BuildStatusProps) => {
|
|
23
|
-
const
|
|
15
|
+
export const BuildStatus = ({ step, packageReleaseId }: BuildStatusProps) => {
|
|
16
|
+
const { author, packageName } = useParams()
|
|
17
|
+
const href = `/${author}/${packageName}/builds?package_release_id=${packageReleaseId}`
|
|
24
18
|
|
|
25
19
|
return (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<DialogHeader>
|
|
44
|
-
<DialogTitle className="flex items-center gap-2">
|
|
45
|
-
{step.status === "success" ? (
|
|
46
|
-
<>
|
|
47
|
-
<CheckCircle className="h-5 w-5 text-green-600" />
|
|
48
|
-
<span>Build Status: Passing</span>
|
|
49
|
-
</>
|
|
50
|
-
) : (
|
|
51
|
-
<>
|
|
52
|
-
<XCircle className="h-5 w-5 text-red-600" />
|
|
53
|
-
<span>Build Status: Failing</span>
|
|
54
|
-
</>
|
|
55
|
-
)}
|
|
56
|
-
</DialogTitle>
|
|
57
|
-
</DialogHeader>
|
|
58
|
-
|
|
59
|
-
<div className="space-y-4">
|
|
60
|
-
<div className="space-y-3">
|
|
61
|
-
<div
|
|
62
|
-
key={step.id}
|
|
63
|
-
className={cn(
|
|
64
|
-
"flex items-start gap-3 rounded-md border p-3",
|
|
65
|
-
step.status === "success"
|
|
66
|
-
? "bg-green-50 border-green-200"
|
|
67
|
-
: "bg-red-50 border-red-200",
|
|
68
|
-
)}
|
|
69
|
-
>
|
|
70
|
-
<div
|
|
71
|
-
className={cn(
|
|
72
|
-
"rounded-full p-1 mt-0.5",
|
|
73
|
-
step.status === "success"
|
|
74
|
-
? "bg-green-100 text-green-600"
|
|
75
|
-
: "bg-red-100 text-red-600",
|
|
76
|
-
)}
|
|
77
|
-
>
|
|
78
|
-
{step.status === "success" ? (
|
|
79
|
-
<Check className="h-4 w-4" />
|
|
80
|
-
) : (
|
|
81
|
-
<X className="h-4 w-4" />
|
|
82
|
-
)}
|
|
83
|
-
</div>
|
|
84
|
-
<div>
|
|
85
|
-
<div className="font-medium">{step.name}</div>
|
|
86
|
-
{step.message && (
|
|
87
|
-
<div className="text-sm text-muted-foreground mt-1">
|
|
88
|
-
{step.message}
|
|
89
|
-
</div>
|
|
90
|
-
)}
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
</DialogContent>
|
|
96
|
-
</Dialog>
|
|
97
|
-
</>
|
|
20
|
+
<Link href={href} className="flex items-center gap-2">
|
|
21
|
+
{step.status === "success" && (
|
|
22
|
+
<CheckCircle className="h-4 w-4 text-green-600 dark:text-[#8b949e]" />
|
|
23
|
+
)}
|
|
24
|
+
{step.status === "error" && (
|
|
25
|
+
<XCircle className="h-4 w-4 text-red-600 dark:text-[#8b949e]" />
|
|
26
|
+
)}
|
|
27
|
+
{step.status === "running" && (
|
|
28
|
+
<Loader2 className="h-4 w-4 text-blue-600 animate-spin dark:text-[#8b949e]" />
|
|
29
|
+
)}
|
|
30
|
+
{step.status === "pending" && (
|
|
31
|
+
<Clock className="h-4 w-4 text-yellow-600 dark:text-[#8b949e]" />
|
|
32
|
+
)}
|
|
33
|
+
<span className="text-sm text-gray-500 dark:text-[#8b949e]">
|
|
34
|
+
{step.name}
|
|
35
|
+
</span>
|
|
36
|
+
</Link>
|
|
98
37
|
)
|
|
99
38
|
}
|
|
@@ -23,12 +23,18 @@ interface ImportantFilesViewProps {
|
|
|
23
23
|
|
|
24
24
|
aiDescription?: string
|
|
25
25
|
aiUsageInstructions?: string
|
|
26
|
+
aiReviewText?: string | null
|
|
27
|
+
aiReviewRequested?: boolean
|
|
28
|
+
onRequestAiReview?: () => void
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export default function ImportantFilesView({
|
|
29
32
|
importantFiles = [],
|
|
30
33
|
aiDescription,
|
|
31
34
|
aiUsageInstructions,
|
|
35
|
+
aiReviewText,
|
|
36
|
+
aiReviewRequested,
|
|
37
|
+
onRequestAiReview,
|
|
32
38
|
isLoading = false,
|
|
33
39
|
onEditClicked,
|
|
34
40
|
}: ImportantFilesViewProps) {
|
|
@@ -43,9 +49,15 @@ export default function ImportantFilesView({
|
|
|
43
49
|
}
|
|
44
50
|
// Determine if we have AI content
|
|
45
51
|
const hasAiContent = Boolean(aiDescription || aiUsageInstructions)
|
|
52
|
+
const hasAiReview = Boolean(aiReviewText)
|
|
46
53
|
|
|
47
|
-
// Select the appropriate tab/file when content changes
|
|
54
|
+
// Select the appropriate tab/file when content changes. Once the user has
|
|
55
|
+
// interacted with the tabs we keep their selection and only run this logic
|
|
56
|
+
// if no tab has been chosen yet.
|
|
48
57
|
useEffect(() => {
|
|
58
|
+
if (activeTab !== null) return
|
|
59
|
+
if (isLoading) return
|
|
60
|
+
|
|
49
61
|
// First priority: README file if it exists
|
|
50
62
|
const readmeFile = importantFiles.find(
|
|
51
63
|
(file) =>
|
|
@@ -60,12 +72,24 @@ export default function ImportantFilesView({
|
|
|
60
72
|
// Second priority: AI content if available
|
|
61
73
|
setActiveTab("ai")
|
|
62
74
|
setActiveFilePath(null)
|
|
75
|
+
} else if (hasAiReview) {
|
|
76
|
+
setActiveTab("ai-review")
|
|
77
|
+
setActiveFilePath(null)
|
|
63
78
|
} else if (importantFiles.length > 0) {
|
|
64
79
|
// Third priority: First important file
|
|
65
80
|
setActiveFilePath(importantFiles[0].file_path)
|
|
66
81
|
setActiveTab("file")
|
|
67
82
|
}
|
|
68
|
-
}, [
|
|
83
|
+
}, [
|
|
84
|
+
aiDescription,
|
|
85
|
+
aiUsageInstructions,
|
|
86
|
+
aiReviewText,
|
|
87
|
+
hasAiContent,
|
|
88
|
+
hasAiReview,
|
|
89
|
+
importantFiles,
|
|
90
|
+
activeTab,
|
|
91
|
+
isLoading,
|
|
92
|
+
])
|
|
69
93
|
|
|
70
94
|
// Get file name from path
|
|
71
95
|
const getFileName = (path: string) => {
|
|
@@ -106,6 +130,27 @@ export default function ImportantFilesView({
|
|
|
106
130
|
)
|
|
107
131
|
}
|
|
108
132
|
|
|
133
|
+
const renderAiReviewContent = () => {
|
|
134
|
+
if (!aiReviewText && !aiReviewRequested) {
|
|
135
|
+
return (
|
|
136
|
+
<button
|
|
137
|
+
className="text-sm text-blue-600 dark:text-[#58a6ff] underline"
|
|
138
|
+
onClick={onRequestAiReview}
|
|
139
|
+
>
|
|
140
|
+
Request AI Review
|
|
141
|
+
</button>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!aiReviewText && aiReviewRequested) {
|
|
146
|
+
return (
|
|
147
|
+
<p className="text-sm">AI review requested. Please check back later.</p>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return <MarkdownViewer markdownContent={aiReviewText || ""} />
|
|
152
|
+
}
|
|
153
|
+
|
|
109
154
|
// Get active file content
|
|
110
155
|
const partialActiveFile = importantFiles.find(
|
|
111
156
|
(file) => file.file_path === activeFilePath,
|
|
@@ -187,6 +232,22 @@ export default function ImportantFilesView({
|
|
|
187
232
|
</button>
|
|
188
233
|
)}
|
|
189
234
|
|
|
235
|
+
{/* AI Review Tab */}
|
|
236
|
+
<button
|
|
237
|
+
className={`flex items-center px-3 py-1.5 rounded-md text-xs ${
|
|
238
|
+
activeTab === "ai-review"
|
|
239
|
+
? "bg-gray-200 dark:bg-[#30363d] font-medium"
|
|
240
|
+
: "text-gray-500 dark:text-[#8b949e] hover:bg-gray-200 dark:hover:bg-[#30363d]"
|
|
241
|
+
}`}
|
|
242
|
+
onClick={() => {
|
|
243
|
+
setActiveTab("ai-review")
|
|
244
|
+
setActiveFilePath(null)
|
|
245
|
+
}}
|
|
246
|
+
>
|
|
247
|
+
<SparklesIcon className="h-3.5 w-3.5 mr-1.5" />
|
|
248
|
+
<span>AI Review</span>
|
|
249
|
+
</button>
|
|
250
|
+
|
|
190
251
|
{/* File Tabs */}
|
|
191
252
|
{importantFiles.map((file) => (
|
|
192
253
|
<button
|
|
@@ -232,6 +293,8 @@ export default function ImportantFilesView({
|
|
|
232
293
|
<div className="p-4 bg-white dark:bg-[#0d1117]">
|
|
233
294
|
{activeTab === "ai" ? (
|
|
234
295
|
renderAiContent()
|
|
296
|
+
) : activeTab === "ai-review" ? (
|
|
297
|
+
renderAiReviewContent()
|
|
235
298
|
) : activeFilePath && activeFilePath.endsWith(".md") ? (
|
|
236
299
|
<MarkdownViewer markdownContent={activeFileContent} />
|
|
237
300
|
) : activeFilePath &&
|
|
@@ -20,6 +20,7 @@ import { useGlobalStore } from "@/hooks/use-global-store"
|
|
|
20
20
|
import { useLocation } from "wouter"
|
|
21
21
|
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
22
22
|
import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
|
|
23
|
+
import { useRequestAiReviewMutation } from "@/hooks/use-request-ai-review-mutation"
|
|
23
24
|
|
|
24
25
|
interface PackageFile {
|
|
25
26
|
package_file_id: string
|
|
@@ -34,6 +35,7 @@ interface RepoPageContentProps {
|
|
|
34
35
|
packageFiles?: PackageFile[]
|
|
35
36
|
importantFilePaths?: string[]
|
|
36
37
|
packageInfo?: Package
|
|
38
|
+
packageRelease?: import("fake-snippets-api/lib/db/schema").PackageRelease
|
|
37
39
|
onFileClicked?: (file: PackageFile) => void
|
|
38
40
|
onEditClicked?: () => void
|
|
39
41
|
}
|
|
@@ -41,6 +43,7 @@ interface RepoPageContentProps {
|
|
|
41
43
|
export default function RepoPageContent({
|
|
42
44
|
packageFiles,
|
|
43
45
|
packageInfo,
|
|
46
|
+
packageRelease,
|
|
44
47
|
onFileClicked,
|
|
45
48
|
onEditClicked,
|
|
46
49
|
}: RepoPageContentProps) {
|
|
@@ -48,6 +51,7 @@ export default function RepoPageContent({
|
|
|
48
51
|
const session = useGlobalStore((s) => s.session)
|
|
49
52
|
const { circuitJson, isLoading: isCircuitJsonLoading } =
|
|
50
53
|
useCurrentPackageCircuitJson()
|
|
54
|
+
const { mutate: requestAiReview } = useRequestAiReviewMutation()
|
|
51
55
|
|
|
52
56
|
// Handle initial view selection and hash-based view changes
|
|
53
57
|
useEffect(() => {
|
|
@@ -198,6 +202,15 @@ export default function RepoPageContent({
|
|
|
198
202
|
onEditClicked={onEditClicked}
|
|
199
203
|
aiDescription={packageInfo?.ai_description ?? ""}
|
|
200
204
|
aiUsageInstructions={packageInfo?.ai_usage_instructions ?? ""}
|
|
205
|
+
aiReviewText={packageRelease?.ai_review_text ?? null}
|
|
206
|
+
aiReviewRequested={packageRelease?.ai_review_requested ?? false}
|
|
207
|
+
onRequestAiReview={() => {
|
|
208
|
+
if (packageRelease) {
|
|
209
|
+
requestAiReview({
|
|
210
|
+
package_release_id: packageRelease.package_release_id,
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
}}
|
|
201
214
|
/>
|
|
202
215
|
</div>
|
|
203
216
|
|
|
@@ -4,6 +4,27 @@ import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
|
|
|
4
4
|
import { usePackageReleaseById } from "@/hooks/use-package-release"
|
|
5
5
|
import { timeAgo } from "@/lib/utils/timeAgo"
|
|
6
6
|
import { BuildStatus, BuildStep } from "./build-status"
|
|
7
|
+
import type { PackageRelease } from "fake-snippets-api/lib/db/schema"
|
|
8
|
+
|
|
9
|
+
function getTranspilationStatus(
|
|
10
|
+
pr?: PackageRelease | null,
|
|
11
|
+
): BuildStep["status"] {
|
|
12
|
+
if (!pr) return "pending"
|
|
13
|
+
if (pr.transpilation_error) return "error"
|
|
14
|
+
if (pr.transpilation_in_progress) return "running"
|
|
15
|
+
if (pr.transpilation_completed_at) return "success"
|
|
16
|
+
if (pr.transpilation_started_at) return "running"
|
|
17
|
+
return "pending"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getCircuitJsonStatus(pr?: PackageRelease | null): BuildStep["status"] {
|
|
21
|
+
if (!pr) return "pending"
|
|
22
|
+
if (pr.circuit_json_build_error) return "error"
|
|
23
|
+
if (pr.circuit_json_build_in_progress) return "running"
|
|
24
|
+
if (pr.circuit_json_build_completed_at) return "success"
|
|
25
|
+
if (pr.circuit_json_build_started_at) return "running"
|
|
26
|
+
return "pending"
|
|
27
|
+
}
|
|
7
28
|
|
|
8
29
|
export default function SidebarReleasesSection() {
|
|
9
30
|
const { packageInfo } = useCurrentPackageInfo()
|
|
@@ -15,14 +36,12 @@ export default function SidebarReleasesSection() {
|
|
|
15
36
|
{
|
|
16
37
|
id: "package_transpilation",
|
|
17
38
|
name: "Package Transpilation",
|
|
18
|
-
status: packageRelease
|
|
19
|
-
message: packageRelease?.transpilation_error || undefined,
|
|
39
|
+
status: getTranspilationStatus(packageRelease),
|
|
20
40
|
},
|
|
21
41
|
{
|
|
22
42
|
id: "circuit_json_build",
|
|
23
43
|
name: "Circuit JSON Build",
|
|
24
|
-
status: packageRelease
|
|
25
|
-
message: packageRelease?.circuit_json_build_error || undefined,
|
|
44
|
+
status: getCircuitJsonStatus(packageRelease),
|
|
26
45
|
},
|
|
27
46
|
]
|
|
28
47
|
|
|
@@ -56,7 +75,11 @@ export default function SidebarReleasesSection() {
|
|
|
56
75
|
</span>
|
|
57
76
|
</div>
|
|
58
77
|
{buildSteps.map((step) => (
|
|
59
|
-
<BuildStatus
|
|
78
|
+
<BuildStatus
|
|
79
|
+
key={step.id}
|
|
80
|
+
step={step}
|
|
81
|
+
packageReleaseId={packageRelease.package_release_id}
|
|
82
|
+
/>
|
|
60
83
|
))}
|
|
61
84
|
</div>
|
|
62
85
|
{/* <a href="#" className="text-blue-600 dark:text-[#58a6ff] hover:underline text-sm">
|
|
@@ -17,6 +17,11 @@ import { useFileManagement } from "@/hooks/useFileManagement"
|
|
|
17
17
|
|
|
18
18
|
interface Props {
|
|
19
19
|
pkg?: Package
|
|
20
|
+
/**
|
|
21
|
+
* Optional project URL whose pathname will be used when
|
|
22
|
+
* reporting autorouting bugs
|
|
23
|
+
*/
|
|
24
|
+
projectUrl?: string
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
export interface PackageFile {
|
|
@@ -45,7 +50,7 @@ export default () => (
|
|
|
45
50
|
export const generateRandomPackageName = () =>
|
|
46
51
|
`untitled-package-${Math.floor(Math.random() * 900) + 100}`
|
|
47
52
|
|
|
48
|
-
export function CodeAndPreview({ pkg }: Props) {
|
|
53
|
+
export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
49
54
|
const { toast } = useToast()
|
|
50
55
|
const urlParams = useUrlParams()
|
|
51
56
|
|
|
@@ -243,6 +248,7 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
243
248
|
handleEditEvent(event)
|
|
244
249
|
}}
|
|
245
250
|
fsMap={fsMap ?? {}}
|
|
251
|
+
projectUrl={projectUrl}
|
|
246
252
|
/>
|
|
247
253
|
</div>
|
|
248
254
|
)}
|
|
@@ -4,7 +4,7 @@ import { autocompletion } from "@codemirror/autocomplete"
|
|
|
4
4
|
import { indentWithTab } from "@codemirror/commands"
|
|
5
5
|
import { javascript } from "@codemirror/lang-javascript"
|
|
6
6
|
import { json } from "@codemirror/lang-json"
|
|
7
|
-
import { EditorState } from "@codemirror/state"
|
|
7
|
+
import { EditorState, Prec } from "@codemirror/state"
|
|
8
8
|
import { Decoration, hoverTooltip, keymap } from "@codemirror/view"
|
|
9
9
|
import { getImportsFromCode } from "@tscircuit/prompt-benchmarks/code-runner-utils"
|
|
10
10
|
import type { ATABootstrapConfig } from "@typescript/ata"
|
|
@@ -237,6 +237,14 @@ export const CodeEditor = ({
|
|
|
237
237
|
currentFile?.endsWith(".json")
|
|
238
238
|
? json()
|
|
239
239
|
: javascript({ typescript: true, jsx: true }),
|
|
240
|
+
Prec.high(
|
|
241
|
+
keymap.of([
|
|
242
|
+
{
|
|
243
|
+
key: "Mod-Enter",
|
|
244
|
+
run: () => true,
|
|
245
|
+
},
|
|
246
|
+
]),
|
|
247
|
+
),
|
|
240
248
|
keymap.of([indentWithTab]),
|
|
241
249
|
EditorState.readOnly.of(readOnly || isSaving),
|
|
242
250
|
EditorView.updateListener.of((update) => {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useParams } from "wouter"
|
|
2
2
|
import { useCurrentPackageId } from "./use-current-package-id"
|
|
3
|
-
import { useUrlParams } from "./use-url-params"
|
|
4
3
|
import { usePackageRelease } from "./use-package-release"
|
|
4
|
+
import { useUrlParams } from "./use-url-params"
|
|
5
5
|
|
|
6
6
|
export const useCurrentPackageRelease = (options?: {
|
|
7
|
-
include_logs
|
|
7
|
+
include_logs?: boolean
|
|
8
|
+
refetchInterval?: number
|
|
8
9
|
}) => {
|
|
9
10
|
const { packageId } = useCurrentPackageId()
|
|
10
11
|
const urlParams = useUrlParams()
|
|
@@ -27,6 +28,7 @@ export const useCurrentPackageRelease = (options?: {
|
|
|
27
28
|
|
|
28
29
|
const { data: packageRelease, ...rest } = usePackageRelease(query, {
|
|
29
30
|
include_logs: options?.include_logs ?? false,
|
|
31
|
+
refetchInterval: options?.refetchInterval,
|
|
30
32
|
})
|
|
31
33
|
return { packageRelease, ...rest }
|
|
32
34
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useEffect, useState } from "react"
|
|
2
|
+
|
|
3
|
+
export const useNow = (intervalMs: number = 1000) => {
|
|
4
|
+
const [now, setNow] = useState(Date.now())
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const id = setInterval(() => setNow(Date.now()), intervalMs)
|
|
8
|
+
return () => clearInterval(id)
|
|
9
|
+
}, [intervalMs])
|
|
10
|
+
|
|
11
|
+
return now
|
|
12
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PackageRelease } from "fake-snippets-api/lib/db/schema"
|
|
2
|
-
import { useQuery } from "react-query"
|
|
2
|
+
import { type UseQueryOptions, useQuery } from "react-query"
|
|
3
3
|
import { useAxios } from "./use-axios"
|
|
4
4
|
|
|
5
5
|
type PackageReleaseQuery =
|
|
@@ -20,7 +20,7 @@ type PackageReleaseQuery =
|
|
|
20
20
|
|
|
21
21
|
export const usePackageRelease = (
|
|
22
22
|
query: PackageReleaseQuery | null,
|
|
23
|
-
options?: { include_logs
|
|
23
|
+
options?: { include_logs?: boolean; refetchInterval?: number },
|
|
24
24
|
) => {
|
|
25
25
|
const axios = useAxios()
|
|
26
26
|
|
|
@@ -50,6 +50,7 @@ export const usePackageRelease = (
|
|
|
50
50
|
{
|
|
51
51
|
retry: false,
|
|
52
52
|
enabled: Boolean(query),
|
|
53
|
+
refetchInterval: options?.refetchInterval,
|
|
53
54
|
},
|
|
54
55
|
)
|
|
55
56
|
}
|