@tscircuit/fake-snippets 0.0.82 → 0.0.84
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/README.md +5 -2
- package/bun-tests/fake-snippets-api/routes/ai_reviews/create.test.ts +12 -0
- package/bun-tests/fake-snippets-api/routes/ai_reviews/get.test.ts +16 -0
- package/bun-tests/fake-snippets-api/routes/ai_reviews/list.test.ts +14 -0
- package/bun-tests/fake-snippets-api/routes/ai_reviews/process_review.test.ts +16 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +3 -3
- package/bun.lock +26 -37
- package/dist/bundle.js +590 -427
- package/dist/index.d.ts +83 -11
- package/dist/index.js +50 -2
- package/dist/schema.d.ts +116 -15
- package/dist/schema.js +17 -2
- package/fake-snippets-api/lib/db/db-client.ts +40 -0
- package/fake-snippets-api/lib/db/schema.ts +17 -1
- package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +14 -1
- package/fake-snippets-api/routes/api/_fake/ai_reviews/process_review.ts +31 -0
- package/fake-snippets-api/routes/api/ai_reviews/create.ts +22 -0
- package/fake-snippets-api/routes/api/ai_reviews/get.ts +24 -0
- package/fake-snippets-api/routes/api/ai_reviews/list.ts +14 -0
- package/fake-snippets-api/routes/api/package_releases/get.ts +11 -3
- package/fake-snippets-api/routes/api/package_releases/list.ts +8 -1
- package/package.json +4 -3
- package/src/App.tsx +0 -2
- package/src/ContextProviders.tsx +1 -1
- package/src/components/Header2.tsx +8 -18
- package/src/components/PackageBuildsPage/package-build-header.tsx +14 -2
- package/src/components/SearchComponent.tsx +46 -8
- package/src/components/ViewPackagePage/hooks/use-toast.tsx +70 -0
- package/src/components/ViewSnippetHeader.tsx +9 -6
- package/src/components/dialogs/edit-package-details-dialog.tsx +5 -10
- package/src/components/dialogs/{import-snippet-dialog.tsx → import-package-dialog.tsx} +25 -24
- package/src/components/package-port/CodeEditorHeader.tsx +7 -6
- package/src/components/ui/toaster.tsx +1 -33
- package/src/hooks/use-current-package-release.ts +10 -1
- package/src/hooks/use-fork-package-mutation.ts +4 -3
- package/src/hooks/use-package-release.ts +15 -14
- package/src/hooks/use-sign-in.ts +10 -8
- package/src/hooks/use-toast.tsx +50 -169
- package/src/hooks/useFileManagement.ts +74 -12
- package/src/hooks/useForkPackageMutation.ts +2 -1
- package/src/hooks/useForkSnippetMutation.ts +2 -1
- package/src/pages/authorize.tsx +164 -8
- package/src/pages/view-package.tsx +1 -0
- package/src/components/ViewPackagePage/hooks/use-toast.ts +0 -191
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
type PackageFile,
|
|
17
17
|
type PackageRelease,
|
|
18
18
|
packageReleaseSchema,
|
|
19
|
+
type AiReview,
|
|
20
|
+
aiReviewSchema,
|
|
19
21
|
type Session,
|
|
20
22
|
type Snippet,
|
|
21
23
|
databaseSchema,
|
|
@@ -1338,4 +1340,42 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
|
|
|
1338
1340
|
),
|
|
1339
1341
|
}))
|
|
1340
1342
|
},
|
|
1343
|
+
|
|
1344
|
+
addAiReview: (review: Omit<AiReview, "ai_review_id">): AiReview => {
|
|
1345
|
+
const base = aiReviewSchema.omit({ ai_review_id: true }).parse(review)
|
|
1346
|
+
const newReview = {
|
|
1347
|
+
ai_review_id: crypto.randomUUID(),
|
|
1348
|
+
...base,
|
|
1349
|
+
}
|
|
1350
|
+
set((state) => ({
|
|
1351
|
+
aiReviews: [...state.aiReviews, newReview],
|
|
1352
|
+
idCounter: state.idCounter + 1,
|
|
1353
|
+
}))
|
|
1354
|
+
return newReview
|
|
1355
|
+
},
|
|
1356
|
+
updateAiReview: (
|
|
1357
|
+
aiReviewId: string,
|
|
1358
|
+
updates: Partial<AiReview>,
|
|
1359
|
+
): AiReview | undefined => {
|
|
1360
|
+
let updated: AiReview | undefined
|
|
1361
|
+
set((state) => {
|
|
1362
|
+
const index = state.aiReviews.findIndex(
|
|
1363
|
+
(ar) => ar.ai_review_id === aiReviewId,
|
|
1364
|
+
)
|
|
1365
|
+
if (index === -1) return state
|
|
1366
|
+
const aiReviews = [...state.aiReviews]
|
|
1367
|
+
aiReviews[index] = { ...aiReviews[index], ...updates }
|
|
1368
|
+
updated = aiReviews[index]
|
|
1369
|
+
return { ...state, aiReviews }
|
|
1370
|
+
})
|
|
1371
|
+
return updated
|
|
1372
|
+
},
|
|
1373
|
+
getAiReviewById: (aiReviewId: string): AiReview | undefined => {
|
|
1374
|
+
const state = get()
|
|
1375
|
+
return state.aiReviews.find((ar) => ar.ai_review_id === aiReviewId)
|
|
1376
|
+
},
|
|
1377
|
+
listAiReviews: (): AiReview[] => {
|
|
1378
|
+
const state = get()
|
|
1379
|
+
return state.aiReviews
|
|
1380
|
+
},
|
|
1341
1381
|
}))
|
|
@@ -138,6 +138,17 @@ export const orderQuoteSchema = z.object({
|
|
|
138
138
|
})
|
|
139
139
|
export type OrderQuote = z.infer<typeof orderQuoteSchema>
|
|
140
140
|
|
|
141
|
+
export const aiReviewSchema = z.object({
|
|
142
|
+
ai_review_id: z.string().uuid(),
|
|
143
|
+
ai_review_text: z.string().nullable(),
|
|
144
|
+
start_processing_at: z.string().datetime().nullable(),
|
|
145
|
+
finished_processing_at: z.string().datetime().nullable(),
|
|
146
|
+
processing_error: z.any().nullable(),
|
|
147
|
+
created_at: z.string().datetime(),
|
|
148
|
+
display_status: z.enum(["pending", "completed", "failed"]),
|
|
149
|
+
})
|
|
150
|
+
export type AiReview = z.infer<typeof aiReviewSchema>
|
|
151
|
+
|
|
141
152
|
// TODO: Remove this schema after migration to accountPackages is complete
|
|
142
153
|
export const accountSnippetSchema = z.object({
|
|
143
154
|
account_id: z.string(),
|
|
@@ -203,7 +214,11 @@ export const packageReleaseSchema = z.object({
|
|
|
203
214
|
circuit_json_build_is_stale: z.boolean().default(false),
|
|
204
215
|
|
|
205
216
|
// AI Review
|
|
206
|
-
ai_review_text: z.string().nullable().default(null),
|
|
217
|
+
ai_review_text: z.string().nullable().default(null).optional(),
|
|
218
|
+
ai_review_started_at: z.string().datetime().nullable().optional(),
|
|
219
|
+
ai_review_completed_at: z.string().datetime().nullable().optional(),
|
|
220
|
+
ai_review_error: z.any().optional().nullable(),
|
|
221
|
+
ai_review_logs: z.array(z.any()).optional().nullable(),
|
|
207
222
|
ai_review_requested: z.boolean().default(false),
|
|
208
223
|
})
|
|
209
224
|
export type PackageRelease = z.infer<typeof packageReleaseSchema>
|
|
@@ -310,5 +325,6 @@ export const databaseSchema = z.object({
|
|
|
310
325
|
jlcpcbOrderState: z.array(jlcpcbOrderStateSchema).default([]),
|
|
311
326
|
jlcpcbOrderStepRuns: z.array(jlcpcbOrderStepRunSchema).default([]),
|
|
312
327
|
orderQuotes: z.array(orderQuoteSchema).default([]),
|
|
328
|
+
aiReviews: z.array(aiReviewSchema).default([]),
|
|
313
329
|
})
|
|
314
330
|
export type DatabaseSchema = z.infer<typeof databaseSchema>
|
|
@@ -4,11 +4,13 @@ export const publicMapPackageRelease = (
|
|
|
4
4
|
internal_package_release: ZT.PackageRelease,
|
|
5
5
|
options: {
|
|
6
6
|
include_logs?: boolean
|
|
7
|
+
include_ai_review?: boolean
|
|
7
8
|
} = {
|
|
8
9
|
include_logs: false,
|
|
10
|
+
include_ai_review: false,
|
|
9
11
|
},
|
|
10
12
|
): ZT.PackageRelease => {
|
|
11
|
-
|
|
13
|
+
const result = {
|
|
12
14
|
...internal_package_release,
|
|
13
15
|
created_at: internal_package_release.created_at,
|
|
14
16
|
circuit_json_build_error_last_updated_at:
|
|
@@ -21,4 +23,15 @@ export const publicMapPackageRelease = (
|
|
|
21
23
|
? internal_package_release.circuit_json_build_logs
|
|
22
24
|
: [],
|
|
23
25
|
}
|
|
26
|
+
|
|
27
|
+
// Only include AI review fields when include_ai_review is true
|
|
28
|
+
if (!options.include_ai_review) {
|
|
29
|
+
delete result.ai_review_text
|
|
30
|
+
delete result.ai_review_started_at
|
|
31
|
+
delete result.ai_review_completed_at
|
|
32
|
+
delete result.ai_review_error
|
|
33
|
+
delete result.ai_review_logs
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result
|
|
24
37
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { aiReviewSchema } from "fake-snippets-api/lib/db/schema"
|
|
4
|
+
|
|
5
|
+
export default withRouteSpec({
|
|
6
|
+
methods: ["POST"],
|
|
7
|
+
auth: "session",
|
|
8
|
+
jsonBody: z.object({
|
|
9
|
+
ai_review_id: z.string(),
|
|
10
|
+
}),
|
|
11
|
+
jsonResponse: z.object({
|
|
12
|
+
ai_review: aiReviewSchema,
|
|
13
|
+
}),
|
|
14
|
+
})(async (req, ctx) => {
|
|
15
|
+
const { ai_review_id } = req.jsonBody
|
|
16
|
+
const existing = ctx.db.getAiReviewById(ai_review_id)
|
|
17
|
+
if (!existing) {
|
|
18
|
+
return ctx.error(404, {
|
|
19
|
+
error_code: "ai_review_not_found",
|
|
20
|
+
message: "AI review not found",
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
const now = new Date().toISOString()
|
|
24
|
+
const updated = ctx.db.updateAiReview(ai_review_id, {
|
|
25
|
+
ai_review_text: "Placeholder AI Review",
|
|
26
|
+
start_processing_at: existing.start_processing_at ?? now,
|
|
27
|
+
finished_processing_at: now,
|
|
28
|
+
display_status: "completed",
|
|
29
|
+
})!
|
|
30
|
+
return ctx.json({ ai_review: updated })
|
|
31
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { aiReviewSchema } from "fake-snippets-api/lib/db/schema"
|
|
4
|
+
|
|
5
|
+
export default withRouteSpec({
|
|
6
|
+
methods: ["POST"],
|
|
7
|
+
auth: "session",
|
|
8
|
+
jsonResponse: z.object({
|
|
9
|
+
ai_review: aiReviewSchema,
|
|
10
|
+
}),
|
|
11
|
+
})(async (req, ctx) => {
|
|
12
|
+
const ai_review = ctx.db.addAiReview({
|
|
13
|
+
ai_review_text: null,
|
|
14
|
+
start_processing_at: null,
|
|
15
|
+
finished_processing_at: null,
|
|
16
|
+
processing_error: null,
|
|
17
|
+
created_at: new Date().toISOString(),
|
|
18
|
+
display_status: "pending",
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
return ctx.json({ ai_review })
|
|
22
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { aiReviewSchema } from "fake-snippets-api/lib/db/schema"
|
|
4
|
+
|
|
5
|
+
export default withRouteSpec({
|
|
6
|
+
methods: ["GET"],
|
|
7
|
+
auth: "session",
|
|
8
|
+
queryParams: z.object({
|
|
9
|
+
ai_review_id: z.string(),
|
|
10
|
+
}),
|
|
11
|
+
jsonResponse: z.object({
|
|
12
|
+
ai_review: aiReviewSchema,
|
|
13
|
+
}),
|
|
14
|
+
})(async (req, ctx) => {
|
|
15
|
+
const { ai_review_id } = req.query
|
|
16
|
+
const ai_review = ctx.db.getAiReviewById(ai_review_id)
|
|
17
|
+
if (!ai_review) {
|
|
18
|
+
return ctx.error(404, {
|
|
19
|
+
error_code: "ai_review_not_found",
|
|
20
|
+
message: "AI review not found",
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
return ctx.json({ ai_review })
|
|
24
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { aiReviewSchema } from "fake-snippets-api/lib/db/schema"
|
|
4
|
+
|
|
5
|
+
export default withRouteSpec({
|
|
6
|
+
methods: ["GET"],
|
|
7
|
+
auth: "session",
|
|
8
|
+
jsonResponse: z.object({
|
|
9
|
+
ai_reviews: z.array(aiReviewSchema),
|
|
10
|
+
}),
|
|
11
|
+
})(async (req, ctx) => {
|
|
12
|
+
const ai_reviews = ctx.db.listAiReviews()
|
|
13
|
+
return ctx.json({ ai_reviews })
|
|
14
|
+
})
|
|
@@ -8,6 +8,7 @@ export default withRouteSpec({
|
|
|
8
8
|
auth: "none",
|
|
9
9
|
commonParams: z.object({
|
|
10
10
|
include_logs: z.boolean().optional().default(false),
|
|
11
|
+
include_ai_review: z.boolean().optional().default(false),
|
|
11
12
|
}),
|
|
12
13
|
jsonBody: z.object({
|
|
13
14
|
package_release_id: z.string().optional(),
|
|
@@ -58,7 +59,9 @@ export default withRouteSpec({
|
|
|
58
59
|
|
|
59
60
|
return ctx.json({
|
|
60
61
|
ok: true,
|
|
61
|
-
package_release: publicMapPackageRelease(packageReleases[0]
|
|
62
|
+
package_release: publicMapPackageRelease(packageReleases[0], {
|
|
63
|
+
include_ai_review: req.commonParams?.include_ai_review,
|
|
64
|
+
}),
|
|
62
65
|
})
|
|
63
66
|
}
|
|
64
67
|
|
|
@@ -82,7 +85,9 @@ export default withRouteSpec({
|
|
|
82
85
|
|
|
83
86
|
return ctx.json({
|
|
84
87
|
ok: true,
|
|
85
|
-
package_release: publicMapPackageRelease(packageReleases[0]
|
|
88
|
+
package_release: publicMapPackageRelease(packageReleases[0], {
|
|
89
|
+
include_ai_review: req.commonParams?.include_ai_review,
|
|
90
|
+
}),
|
|
86
91
|
})
|
|
87
92
|
}
|
|
88
93
|
|
|
@@ -102,7 +107,9 @@ export default withRouteSpec({
|
|
|
102
107
|
|
|
103
108
|
return ctx.json({
|
|
104
109
|
ok: true,
|
|
105
|
-
package_release: publicMapPackageRelease(pkgRelease
|
|
110
|
+
package_release: publicMapPackageRelease(pkgRelease, {
|
|
111
|
+
include_ai_review: req.commonParams?.include_ai_review,
|
|
112
|
+
}),
|
|
106
113
|
})
|
|
107
114
|
}
|
|
108
115
|
|
|
@@ -120,6 +127,7 @@ export default withRouteSpec({
|
|
|
120
127
|
ok: true,
|
|
121
128
|
package_release: publicMapPackageRelease(foundRelease, {
|
|
122
129
|
include_logs: req.commonParams?.include_logs,
|
|
130
|
+
include_ai_review: req.commonParams?.include_ai_review,
|
|
123
131
|
}),
|
|
124
132
|
})
|
|
125
133
|
})
|
|
@@ -6,6 +6,9 @@ import { z } from "zod"
|
|
|
6
6
|
export default withRouteSpec({
|
|
7
7
|
methods: ["POST"],
|
|
8
8
|
auth: "none",
|
|
9
|
+
commonParams: z.object({
|
|
10
|
+
include_ai_review: z.boolean().optional().default(false),
|
|
11
|
+
}),
|
|
9
12
|
jsonBody: z
|
|
10
13
|
.object({
|
|
11
14
|
package_id: z.string().optional(),
|
|
@@ -72,6 +75,10 @@ export default withRouteSpec({
|
|
|
72
75
|
|
|
73
76
|
return ctx.json({
|
|
74
77
|
ok: true,
|
|
75
|
-
package_releases: releases.map((pr) =>
|
|
78
|
+
package_releases: releases.map((pr) =>
|
|
79
|
+
publicMapPackageRelease(pr, {
|
|
80
|
+
include_ai_review: req.commonParams?.include_ai_review,
|
|
81
|
+
}),
|
|
82
|
+
),
|
|
76
83
|
})
|
|
77
84
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fake-snippets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.84",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"circuit-json-to-bom-csv": "^0.0.6",
|
|
87
87
|
"circuit-json-to-gerber": "^0.0.21",
|
|
88
88
|
"circuit-json-to-pnp-csv": "^0.0.6",
|
|
89
|
-
"circuit-json-to-readable-netlist": "^0.0.
|
|
89
|
+
"circuit-json-to-readable-netlist": "^0.0.13",
|
|
90
90
|
"circuit-json-to-tscircuit": "^0.0.4",
|
|
91
91
|
"class-variance-authority": "^0.7.1",
|
|
92
92
|
"clsx": "^2.1.1",
|
|
@@ -145,8 +145,9 @@
|
|
|
145
145
|
"@playwright/test": "^1.48.0",
|
|
146
146
|
"@tailwindcss/typography": "^0.5.16",
|
|
147
147
|
"@tscircuit/core": "^0.0.433",
|
|
148
|
+
"@tscircuit/eval": "^0.0.227",
|
|
148
149
|
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
149
|
-
"@tscircuit/runframe": "^0.0.
|
|
150
|
+
"@tscircuit/runframe": "^0.0.578",
|
|
150
151
|
"@types/babel__standalone": "^7.1.7",
|
|
151
152
|
"@types/bun": "^1.1.10",
|
|
152
153
|
"@types/country-list": "^2.1.4",
|
package/src/App.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ComponentType, Suspense, lazy } from "react"
|
|
2
|
-
import { Toaster } from "@/components/ui/toaster"
|
|
3
2
|
import { Route, Switch } from "wouter"
|
|
4
3
|
import "./components/CmdKMenu"
|
|
5
4
|
import { ContextProviders } from "./ContextProviders"
|
|
@@ -129,7 +128,6 @@ function App() {
|
|
|
129
128
|
<Route component={lazyImport(() => import("@/pages/404"))} />
|
|
130
129
|
</Switch>
|
|
131
130
|
</Suspense>
|
|
132
|
-
<Toaster />
|
|
133
131
|
</ErrorBoundary>
|
|
134
132
|
</ContextProviders>
|
|
135
133
|
)
|
package/src/ContextProviders.tsx
CHANGED
|
@@ -15,18 +15,15 @@ const SearchButtonComponent = () => {
|
|
|
15
15
|
return (
|
|
16
16
|
<div className="relative">
|
|
17
17
|
{isExpanded ? (
|
|
18
|
-
<div className="flex items-center gap-2">
|
|
19
|
-
<div className="
|
|
20
|
-
<SearchComponent
|
|
18
|
+
<div className="flex items-center gap-2 ml-8">
|
|
19
|
+
<div className="absolute -top-4 right-3 bg-white">
|
|
20
|
+
<SearchComponent
|
|
21
|
+
autofocus
|
|
22
|
+
closeOnClick={() => {
|
|
23
|
+
setIsExpanded(false)
|
|
24
|
+
}}
|
|
25
|
+
/>
|
|
21
26
|
</div>
|
|
22
|
-
{/* <Button
|
|
23
|
-
variant="ghost"
|
|
24
|
-
size="icon"
|
|
25
|
-
onClick={() => setIsExpanded(false)}
|
|
26
|
-
className="h-8 w-8"
|
|
27
|
-
>
|
|
28
|
-
<X className="h-4 w-4" />
|
|
29
|
-
</Button> */}
|
|
30
27
|
</div>
|
|
31
28
|
) : (
|
|
32
29
|
<>
|
|
@@ -58,13 +55,6 @@ export const Header2 = () => {
|
|
|
58
55
|
const isLoggedIn = useGlobalStore((state) => Boolean(state.session))
|
|
59
56
|
return (
|
|
60
57
|
<>
|
|
61
|
-
{/* <div className="absolute left-0 top-0 z-[9999999]">
|
|
62
|
-
<div className="hidden xl:block">xl</div>
|
|
63
|
-
<div className="hidden lg:block xl:hidden">lg</div>
|
|
64
|
-
<div className="hidden md:block lg:hidden">md</div>
|
|
65
|
-
<div className="hidden sm:block md:hidden">sm</div>
|
|
66
|
-
<div className="hidden xs:block sm:hidden">xs</div>
|
|
67
|
-
</div> */}
|
|
68
58
|
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
69
59
|
<div className="container mx-auto flex h-16 items-center justify-between px-2 md:px-6">
|
|
70
60
|
<div className="flex items-center gap-2">
|
|
@@ -1,13 +1,15 @@
|
|
|
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 } from "lucide-react"
|
|
4
|
+
import { Github, RefreshCw, RotateCcw } 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 } = useCurrentPackageRelease({
|
|
10
|
+
const { packageRelease, refetch, isFetching } = useCurrentPackageRelease({
|
|
11
|
+
include_logs: true,
|
|
12
|
+
})
|
|
11
13
|
const { mutate: rebuildPackage, isLoading } =
|
|
12
14
|
useRebuildPackageReleaseMutation()
|
|
13
15
|
|
|
@@ -54,6 +56,16 @@ export function PackageBuildHeader() {
|
|
|
54
56
|
<RefreshCw className="w-3 h-3 sm:w-4 sm:h-4 mr-1 sm:mr-2" />
|
|
55
57
|
{isLoading ? "Rebuilding..." : "Rebuild"}
|
|
56
58
|
</Button>
|
|
59
|
+
<Button
|
|
60
|
+
variant="outline"
|
|
61
|
+
size="icon"
|
|
62
|
+
aria-label="Reload logs"
|
|
63
|
+
className="border-gray-300 bg-white hover:bg-gray-50"
|
|
64
|
+
onClick={() => refetch()}
|
|
65
|
+
disabled={isFetching}
|
|
66
|
+
>
|
|
67
|
+
<RotateCcw className="w-3 h-3 sm:w-4 sm:h-4" />
|
|
68
|
+
</Button>
|
|
57
69
|
<DownloadButtonAndMenu
|
|
58
70
|
snippetUnscopedName={`${author}/${packageName}`}
|
|
59
71
|
/>
|
|
@@ -6,10 +6,12 @@ import { useQuery } from "react-query"
|
|
|
6
6
|
import { Alert } from "./ui/alert"
|
|
7
7
|
import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
|
|
8
8
|
import { PrefetchPageLink } from "./PrefetchPageLink"
|
|
9
|
+
import { CircuitBoard } from "lucide-react"
|
|
9
10
|
|
|
10
11
|
interface SearchComponentProps {
|
|
11
12
|
onResultsFetched?: (results: any[]) => void
|
|
12
13
|
autofocus?: boolean
|
|
14
|
+
closeOnClick?: () => void
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
const LinkWithNewTabHandling = ({
|
|
@@ -45,13 +47,14 @@ const LinkWithNewTabHandling = ({
|
|
|
45
47
|
const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
46
48
|
onResultsFetched,
|
|
47
49
|
autofocus = false,
|
|
50
|
+
closeOnClick,
|
|
48
51
|
}) => {
|
|
49
52
|
const [searchQuery, setSearchQuery] = useState("")
|
|
50
53
|
const [showResults, setShowResults] = useState(false)
|
|
51
54
|
const axios = useAxios()
|
|
52
55
|
const resultsRef = useRef<HTMLDivElement>(null)
|
|
53
56
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
54
|
-
const [location] = useLocation()
|
|
57
|
+
const [location, setLocation] = useLocation()
|
|
55
58
|
const snippetsBaseApiUrl = useSnippetsBaseApiUrl()
|
|
56
59
|
|
|
57
60
|
const { data: searchResults, isLoading } = useQuery(
|
|
@@ -71,7 +74,9 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
71
74
|
|
|
72
75
|
const handleSearch = (e: React.FormEvent) => {
|
|
73
76
|
e.preventDefault()
|
|
74
|
-
|
|
77
|
+
if (searchQuery.trim()) {
|
|
78
|
+
setLocation(`/search?q=${encodeURIComponent(searchQuery.trim())}`)
|
|
79
|
+
}
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
// Focus input on mount
|
|
@@ -91,18 +96,31 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
|
|
99
|
+
const handleEscapeKey = (event: KeyboardEvent) => {
|
|
100
|
+
if (event.key === "Escape") {
|
|
101
|
+
setShowResults(false)
|
|
102
|
+
if (closeOnClick) {
|
|
103
|
+
closeOnClick()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
94
108
|
document.addEventListener("mousedown", handleClickOutside)
|
|
109
|
+
document.addEventListener("keydown", handleEscapeKey)
|
|
95
110
|
return () => {
|
|
96
111
|
document.removeEventListener("mousedown", handleClickOutside)
|
|
112
|
+
document.removeEventListener("keydown", handleEscapeKey)
|
|
97
113
|
}
|
|
98
|
-
}, [])
|
|
114
|
+
}, [closeOnClick])
|
|
99
115
|
|
|
100
116
|
const shouldOpenInNewTab = location === "/editor" || location === "/ai"
|
|
101
117
|
const shouldOpenInEditor = location === "/editor" || location === "/ai"
|
|
102
118
|
|
|
103
119
|
return (
|
|
104
|
-
<form onSubmit={handleSearch} className="relative">
|
|
120
|
+
<form onSubmit={handleSearch} autoComplete="off" className="relative w-44">
|
|
105
121
|
<Input
|
|
122
|
+
autoComplete="off"
|
|
123
|
+
spellCheck={false}
|
|
106
124
|
ref={inputRef}
|
|
107
125
|
type="search"
|
|
108
126
|
placeholder="Search"
|
|
@@ -112,12 +130,20 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
112
130
|
setSearchQuery(e.target.value)
|
|
113
131
|
setShowResults(!!e.target.value)
|
|
114
132
|
}}
|
|
133
|
+
onKeyDown={(e) => {
|
|
134
|
+
if (e.key === "Backspace" && !searchQuery && closeOnClick) {
|
|
135
|
+
closeOnClick()
|
|
136
|
+
}
|
|
137
|
+
}}
|
|
115
138
|
aria-label="Search packages"
|
|
116
139
|
role="searchbox"
|
|
117
140
|
/>
|
|
118
141
|
{isLoading && (
|
|
119
|
-
<div className="absolute top-full left-0 right-0 mt-
|
|
120
|
-
<
|
|
142
|
+
<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-10 p-3">
|
|
143
|
+
<div className="flex items-center space-x-2">
|
|
144
|
+
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
|
|
145
|
+
<span className="text-gray-600 text-sm">Searching...</span>
|
|
146
|
+
</div>
|
|
121
147
|
</div>
|
|
122
148
|
)}
|
|
123
149
|
|
|
@@ -139,12 +165,24 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
139
165
|
shouldOpenInNewTab={shouldOpenInNewTab}
|
|
140
166
|
className="flex"
|
|
141
167
|
>
|
|
142
|
-
<div className="w-12 h-12 overflow-hidden mr-2 flex-shrink-0 rounded-sm">
|
|
168
|
+
<div className="w-12 h-12 overflow-hidden mr-2 flex-shrink-0 rounded-sm bg-gray-50 border flex items-center justify-center">
|
|
143
169
|
<img
|
|
144
170
|
src={`${snippetsBaseApiUrl}/snippets/images/${pkg.name}/pcb.svg`}
|
|
145
171
|
alt={`PCB preview for ${pkg.name}`}
|
|
146
172
|
className="w-12 h-12 object-contain p-1 scale-[4] rotate-45"
|
|
173
|
+
onError={(e) => {
|
|
174
|
+
e.currentTarget.style.display = "none"
|
|
175
|
+
e.currentTarget.nextElementSibling?.classList.remove(
|
|
176
|
+
"hidden",
|
|
177
|
+
)
|
|
178
|
+
e.currentTarget.nextElementSibling?.classList.add(
|
|
179
|
+
"flex",
|
|
180
|
+
)
|
|
181
|
+
}}
|
|
147
182
|
/>
|
|
183
|
+
<div className="w-12 h-12 hidden items-center justify-center">
|
|
184
|
+
<CircuitBoard className="w-6 h-6 text-gray-300" />
|
|
185
|
+
</div>
|
|
148
186
|
</div>
|
|
149
187
|
<div className="flex-grow">
|
|
150
188
|
<div className="font-medium text-blue-600 break-words text-xs">
|
|
@@ -161,7 +199,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
161
199
|
))}
|
|
162
200
|
</ul>
|
|
163
201
|
) : (
|
|
164
|
-
<Alert variant="default" className="p-4">
|
|
202
|
+
<Alert variant="default" className="p-4 text-center">
|
|
165
203
|
No results found for "{searchQuery}"
|
|
166
204
|
</Alert>
|
|
167
205
|
)}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import toastLibrary, { Toaster, type Toast } from "react-hot-toast"
|
|
3
|
+
import React from "react"
|
|
4
|
+
|
|
5
|
+
export interface ToasterToast {
|
|
6
|
+
title?: React.ReactNode
|
|
7
|
+
description?: React.ReactNode
|
|
8
|
+
variant?: "default" | "destructive"
|
|
9
|
+
duration?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function ToastContent({
|
|
13
|
+
title,
|
|
14
|
+
description,
|
|
15
|
+
variant,
|
|
16
|
+
t,
|
|
17
|
+
}: ToasterToast & { t: Toast }) {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
className={`rounded-md border p-4 shadow-lg transition-all ${
|
|
21
|
+
t.visible
|
|
22
|
+
? "animate-in fade-in slide-in-from-top-full"
|
|
23
|
+
: "animate-out fade-out slide-out-to-right-full"
|
|
24
|
+
} ${
|
|
25
|
+
variant === "destructive"
|
|
26
|
+
? "border-red-500 bg-red-500 text-slate-50"
|
|
27
|
+
: "border-slate-200 bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50"
|
|
28
|
+
}`}
|
|
29
|
+
>
|
|
30
|
+
{title && <div className="text-sm font-semibold">{title}</div>}
|
|
31
|
+
{description && <div className="text-sm opacity-90">{description}</div>}
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const toast = ({
|
|
37
|
+
duration,
|
|
38
|
+
description,
|
|
39
|
+
variant = "default",
|
|
40
|
+
title,
|
|
41
|
+
}: ToasterToast) => {
|
|
42
|
+
if (description) {
|
|
43
|
+
return toastLibrary.custom(
|
|
44
|
+
(t) => (
|
|
45
|
+
<ToastContent
|
|
46
|
+
title={title}
|
|
47
|
+
description={description}
|
|
48
|
+
variant={variant}
|
|
49
|
+
t={t}
|
|
50
|
+
/>
|
|
51
|
+
),
|
|
52
|
+
{ duration },
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (variant === "destructive") {
|
|
57
|
+
return toastLibrary.error(<>{title}</>, { duration })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return toastLibrary(<>{title}</>, { duration })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function useToast() {
|
|
64
|
+
return {
|
|
65
|
+
toast,
|
|
66
|
+
dismiss: toastLibrary.dismiss,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { useToast, toast, Toaster }
|
|
@@ -63,18 +63,21 @@ export default function ViewSnippetHeader() {
|
|
|
63
63
|
onSuccess?.(forkedSnippet)
|
|
64
64
|
},
|
|
65
65
|
onError: (error: any) => {
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
const message =
|
|
67
|
+
error?.data?.error?.message ||
|
|
68
|
+
error.message ||
|
|
69
|
+
"Failed to fork snippet. Please try again."
|
|
70
|
+
if (message.includes("already forked")) {
|
|
68
71
|
toast({
|
|
69
72
|
title: "Snippet already exists",
|
|
70
|
-
description:
|
|
71
|
-
variant: "destructive",
|
|
73
|
+
description: message,
|
|
74
|
+
variant: "destructive",
|
|
72
75
|
})
|
|
73
76
|
} else {
|
|
74
77
|
toast({
|
|
75
78
|
title: "Error",
|
|
76
|
-
description:
|
|
77
|
-
variant: "destructive",
|
|
79
|
+
description: message,
|
|
80
|
+
variant: "destructive",
|
|
78
81
|
})
|
|
79
82
|
}
|
|
80
83
|
console.error("Error forking snippet:", error)
|