@tscircuit/fake-snippets 0.0.6 → 0.0.7
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_files/create.test.ts +375 -0
- package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +248 -0
- package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +220 -0
- package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +204 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +0 -1
- package/bun.lock +97 -73
- package/dist/bundle.js +584 -212
- package/fake-snippets-api/lib/db/db-client.ts +13 -0
- package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +168 -0
- package/fake-snippets-api/lib/package_release/find-package-release-id.ts +122 -0
- package/fake-snippets-api/routes/api/package_files/create.ts +132 -0
- package/fake-snippets-api/routes/api/package_files/download.ts +70 -153
- package/fake-snippets-api/routes/api/package_files/get.ts +24 -5
- package/fake-snippets-api/routes/api/package_files/list.ts +16 -28
- package/index.html +12 -1
- package/package.json +9 -9
- package/playwright-tests/profile-page.spec.ts +59 -0
- package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-before-delete.png +0 -0
- package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-delete-dialog.png +0 -0
- package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-dropdown-open.png +0 -0
- package/scripts/generate-image-sizes.ts +0 -1
- package/scripts/generate_bundle_stats.js +22 -6
- package/src/components/AiChatInterface.tsx +8 -0
- package/src/components/Analytics.tsx +1 -1
- package/src/components/CodeAndPreview.tsx +9 -3
- package/src/components/CreateNewSnippetWithAiHero.tsx +6 -2
- package/src/components/EditorNav.tsx +4 -0
- package/src/components/Footer.tsx +1 -1
- package/src/components/Header.tsx +7 -10
- package/src/components/HeaderLogin.tsx +1 -1
- package/src/components/PreviewContent.tsx +4 -1
- package/src/components/SnippetList.tsx +71 -0
- package/src/lib/templates/blinking-led-board-template.ts +2 -1
- package/src/pages/dashboard.tsx +19 -44
- package/src/pages/editor.tsx +1 -1
- package/src/pages/landing.tsx +8 -16
- package/src/pages/quickstart.tsx +9 -9
- package/src/pages/user-profile.tsx +50 -3
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { withRouteSpec } from "fake-snippets-api/lib/with-winter-spec"
|
|
2
2
|
import { z } from "zod"
|
|
3
3
|
import * as ZT from "fake-snippets-api/lib/db/schema"
|
|
4
|
-
import {
|
|
4
|
+
import { getPackageFileIdFromFileDescriptor } from "fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor"
|
|
5
5
|
|
|
6
6
|
const routeSpec = {
|
|
7
7
|
methods: ["POST"],
|
|
8
8
|
auth: "none",
|
|
9
9
|
jsonBody: z
|
|
10
10
|
.object({
|
|
11
|
-
package_file_id: z.string()
|
|
11
|
+
package_file_id: z.string(),
|
|
12
12
|
})
|
|
13
13
|
.or(
|
|
14
14
|
z.object({
|
|
15
|
-
package_release_id: z.string()
|
|
15
|
+
package_release_id: z.string(),
|
|
16
16
|
file_path: z.string(),
|
|
17
17
|
}),
|
|
18
18
|
)
|
|
19
19
|
.or(
|
|
20
20
|
z.object({
|
|
21
|
-
package_id: z.string()
|
|
21
|
+
package_id: z.string(),
|
|
22
22
|
version: z.string().optional(),
|
|
23
23
|
file_path: z.string(),
|
|
24
24
|
}),
|
|
@@ -45,8 +45,27 @@ const routeSpec = {
|
|
|
45
45
|
} as const
|
|
46
46
|
|
|
47
47
|
export default withRouteSpec(routeSpec)(async (req, ctx) => {
|
|
48
|
+
const packageFileId = await getPackageFileIdFromFileDescriptor(
|
|
49
|
+
req.jsonBody,
|
|
50
|
+
ctx,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const packageFile = ctx.db.packageFiles.find(
|
|
54
|
+
(pf: ZT.PackageFile) => pf.package_file_id === packageFileId,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if (!packageFile) {
|
|
58
|
+
return ctx.error(404, {
|
|
59
|
+
error_code: "package_file_not_found",
|
|
60
|
+
message: "Package file not found",
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
48
64
|
return ctx.json({
|
|
49
65
|
ok: true,
|
|
50
|
-
package_file:
|
|
66
|
+
package_file: {
|
|
67
|
+
...packageFile,
|
|
68
|
+
created_at: packageFile.created_at.toString(),
|
|
69
|
+
},
|
|
51
70
|
})
|
|
52
71
|
})
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { withRouteSpec } from "fake-snippets-api/lib/with-winter-spec"
|
|
2
2
|
import { z } from "zod"
|
|
3
3
|
import * as ZT from "fake-snippets-api/lib/db/schema"
|
|
4
|
+
import { findPackageReleaseId } from "fake-snippets-api/lib/package_release/find-package-release-id"
|
|
4
5
|
|
|
5
6
|
const routeSpec = {
|
|
6
7
|
methods: ["POST"],
|
|
7
8
|
auth: "none",
|
|
8
9
|
jsonBody: z
|
|
9
10
|
.object({
|
|
10
|
-
package_release_id: z.string()
|
|
11
|
+
package_release_id: z.string(),
|
|
11
12
|
})
|
|
12
13
|
.or(
|
|
13
14
|
z.object({
|
|
@@ -27,34 +28,21 @@ const routeSpec = {
|
|
|
27
28
|
} as const
|
|
28
29
|
|
|
29
30
|
export default withRouteSpec(routeSpec)(async (req, ctx) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// "file_path",
|
|
44
|
-
// "created_at",
|
|
45
|
-
// ])
|
|
46
|
-
// .where("package_release_id", "=", package_release_id)
|
|
47
|
-
// .where("file_path", "not like", ".tscircuit-internal/%")
|
|
48
|
-
// .execute()
|
|
49
|
-
// return ctx.json({
|
|
50
|
-
// ok: true,
|
|
51
|
-
// package_files: package_files.map((pf) => ({
|
|
52
|
-
// ...pf,
|
|
53
|
-
// created_at: pf.created_at.toISOString(),
|
|
54
|
-
// })),
|
|
55
|
-
// })
|
|
31
|
+
const packageReleaseId = await findPackageReleaseId(req.jsonBody, ctx)
|
|
32
|
+
|
|
33
|
+
if (!packageReleaseId) {
|
|
34
|
+
return ctx.error(404, {
|
|
35
|
+
error_code: "package_release_not_found",
|
|
36
|
+
message: "Package release not found",
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const packageFiles = ctx.db.packageFiles.filter(
|
|
41
|
+
(file) => file.package_release_id === packageReleaseId,
|
|
42
|
+
)
|
|
43
|
+
|
|
56
44
|
return ctx.json({
|
|
57
45
|
ok: true,
|
|
58
|
-
package_files:
|
|
46
|
+
package_files: packageFiles,
|
|
59
47
|
})
|
|
60
48
|
})
|
package/index.html
CHANGED
|
@@ -84,6 +84,17 @@
|
|
|
84
84
|
}, 300); // Match the opacity transition duration
|
|
85
85
|
};
|
|
86
86
|
</script>
|
|
87
|
+
|
|
88
|
+
<!-- Google tag (gtag.js) -->
|
|
89
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-16607843883">
|
|
90
|
+
</script>
|
|
91
|
+
<script>
|
|
92
|
+
window.dataLayer = window.dataLayer || [];
|
|
93
|
+
function gtag(){dataLayer.push(arguments);}
|
|
94
|
+
gtag('js', new Date());
|
|
95
|
+
|
|
96
|
+
gtag('config', 'AW-16607843883');
|
|
97
|
+
</script>
|
|
87
98
|
</head>
|
|
88
99
|
|
|
89
100
|
<body>
|
|
@@ -102,4 +113,4 @@
|
|
|
102
113
|
<script type="module" src="./src/main.tsx"></script>
|
|
103
114
|
</body>
|
|
104
115
|
|
|
105
|
-
</html>
|
|
116
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fake-snippets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
"@radix-ui/react-toggle": "^1.1.0",
|
|
62
62
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
|
63
63
|
"@radix-ui/react-tooltip": "^1.1.2",
|
|
64
|
-
"@tscircuit/3d-viewer": "^0.0.
|
|
65
|
-
"@tscircuit/footprinter": "^0.0.
|
|
64
|
+
"@tscircuit/3d-viewer": "^0.0.142",
|
|
65
|
+
"@tscircuit/footprinter": "^0.0.124",
|
|
66
66
|
"@tscircuit/layout": "^0.0.29",
|
|
67
67
|
"@tscircuit/math-utils": "^0.0.10",
|
|
68
68
|
"@tscircuit/mm": "^0.0.8",
|
|
69
69
|
"@tscircuit/pcb-viewer": "^1.11.12",
|
|
70
|
-
"@tscircuit/props": "^0.0.
|
|
70
|
+
"@tscircuit/props": "^0.0.143",
|
|
71
71
|
"@tscircuit/schematic-viewer": "^1.4.3",
|
|
72
72
|
"@types/file-saver": "^2.0.7",
|
|
73
73
|
"@types/ms": "^0.7.34",
|
|
@@ -76,12 +76,12 @@
|
|
|
76
76
|
"@valtown/codemirror-ts": "^2.2.0",
|
|
77
77
|
"@vercel/analytics": "^1.4.1",
|
|
78
78
|
"change-case": "^5.4.4",
|
|
79
|
-
"circuit-json": "^0.0.
|
|
79
|
+
"circuit-json": "^0.0.136",
|
|
80
80
|
"circuit-json-to-bom-csv": "^0.0.6",
|
|
81
81
|
"circuit-json-to-gerber": "^0.0.16",
|
|
82
82
|
"circuit-json-to-pnp-csv": "^0.0.6",
|
|
83
|
+
"circuit-json-to-readable-netlist": "^0.0.8",
|
|
83
84
|
"circuit-json-to-tscircuit": "^0.0.4",
|
|
84
|
-
"circuit-json-to-readable-netlist": "^0.0.7",
|
|
85
85
|
"class-variance-authority": "^0.7.0",
|
|
86
86
|
"clsx": "^2.1.1",
|
|
87
87
|
"cmdk": "^1.0.4",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"immer": "^10.1.1",
|
|
98
98
|
"input-otp": "^1.2.4",
|
|
99
99
|
"jose": "^5.9.3",
|
|
100
|
-
"jscad-electronics": "^0.0.
|
|
100
|
+
"jscad-electronics": "^0.0.25",
|
|
101
101
|
"jszip": "^3.10.1",
|
|
102
102
|
"kicad-converter": "^0.0.16",
|
|
103
103
|
"lucide-react": "^0.445.0",
|
|
@@ -130,9 +130,9 @@
|
|
|
130
130
|
"@babel/standalone": "^7.26.2",
|
|
131
131
|
"@biomejs/biome": "^1.9.2",
|
|
132
132
|
"@playwright/test": "^1.48.0",
|
|
133
|
-
"@tscircuit/core": "^0.0.
|
|
133
|
+
"@tscircuit/core": "^0.0.315",
|
|
134
134
|
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
135
|
-
"@tscircuit/runframe": "^0.0.
|
|
135
|
+
"@tscircuit/runframe": "^0.0.193",
|
|
136
136
|
"@types/babel__standalone": "^7.1.7",
|
|
137
137
|
"@types/bun": "^1.1.10",
|
|
138
138
|
"@types/country-list": "^2.1.4",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { test, expect } from "@playwright/test"
|
|
2
|
+
|
|
3
|
+
test("test delete functionality in profile", async ({ page }) => {
|
|
4
|
+
// Go to profile page
|
|
5
|
+
await page.goto("http://localhost:5177/testuser")
|
|
6
|
+
|
|
7
|
+
// Login
|
|
8
|
+
await page.getByRole("button", { name: "Log in" }).click()
|
|
9
|
+
|
|
10
|
+
// Wait for snippets grid to load
|
|
11
|
+
await page.waitForSelector(".grid")
|
|
12
|
+
await page.waitForLoadState("networkidle")
|
|
13
|
+
|
|
14
|
+
// Verify initial snippet exists
|
|
15
|
+
const snippetTitle = page.locator(".text-md.font-semibold").first()
|
|
16
|
+
const snippetName = await snippetTitle.textContent()
|
|
17
|
+
expect(await snippetTitle.isVisible()).toBe(true)
|
|
18
|
+
|
|
19
|
+
// Take screenshot of initial state
|
|
20
|
+
await expect(page).toHaveScreenshot("profile-page-before-delete.png")
|
|
21
|
+
|
|
22
|
+
// Open dropdown menu
|
|
23
|
+
await page.locator(".lucide-ellipsis-vertical").first().click()
|
|
24
|
+
await page.waitForTimeout(1000)
|
|
25
|
+
|
|
26
|
+
// Take screenshot with dropdown open
|
|
27
|
+
await expect(page).toHaveScreenshot("profile-page-dropdown-open.png")
|
|
28
|
+
|
|
29
|
+
// Click delete option
|
|
30
|
+
await page.getByRole("menuitem", { name: "Delete Snippet" }).click()
|
|
31
|
+
|
|
32
|
+
// Wait for and verify confirmation dialog
|
|
33
|
+
const dialog = page.locator('[role="dialog"]')
|
|
34
|
+
await expect(dialog).toBeVisible()
|
|
35
|
+
const dialogText = await dialog.textContent()
|
|
36
|
+
expect(dialogText).toContain(
|
|
37
|
+
`Are you sure you want to delete the snippet "${snippetName}"?`,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
// Take screenshot of delete dialog
|
|
41
|
+
await expect(page).toHaveScreenshot("profile-page-delete-dialog.png")
|
|
42
|
+
|
|
43
|
+
// Confirm delete
|
|
44
|
+
await page.getByRole("button", { name: "Delete" }).click()
|
|
45
|
+
|
|
46
|
+
// Verify success toast appears
|
|
47
|
+
await page.waitForSelector('div:has-text("Successfully deleted")', {
|
|
48
|
+
state: "visible",
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Wait for page to update
|
|
52
|
+
await page.waitForLoadState("networkidle")
|
|
53
|
+
|
|
54
|
+
// Verify snippet is removed
|
|
55
|
+
const remainingSnippets = await page
|
|
56
|
+
.locator(`.text-md.font-semibold:has-text("${snippetName}")`)
|
|
57
|
+
.count()
|
|
58
|
+
expect(remainingSnippets).toBe(0)
|
|
59
|
+
})
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -82,11 +82,22 @@ function compareSizes(prData, mainData, dependencies) {
|
|
|
82
82
|
const mainSize = mainStats.depStats[dep]?.size || 0
|
|
83
83
|
const diff = prSize - mainSize
|
|
84
84
|
|
|
85
|
+
let percentChange = "N/A"
|
|
86
|
+
if (prSize === 0 && mainSize > 0) {
|
|
87
|
+
percentChange = "Removed"
|
|
88
|
+
} else if (mainSize !== 0) {
|
|
89
|
+
percentChange = (diff / mainSize) * 100
|
|
90
|
+
} else if (prSize > 0 && mainSize === 0) {
|
|
91
|
+
percentChange = "Added"
|
|
92
|
+
} else {
|
|
93
|
+
percentChange = 0
|
|
94
|
+
}
|
|
95
|
+
|
|
85
96
|
diffStats[dep] = {
|
|
86
97
|
before: mainSize,
|
|
87
98
|
after: prSize,
|
|
88
99
|
diff,
|
|
89
|
-
percentChange
|
|
100
|
+
percentChange,
|
|
90
101
|
}
|
|
91
102
|
})
|
|
92
103
|
|
|
@@ -95,7 +106,10 @@ function compareSizes(prData, mainData, dependencies) {
|
|
|
95
106
|
totalAfter: prStats.totalSize,
|
|
96
107
|
totalDiff: prStats.totalSize - mainStats.totalSize,
|
|
97
108
|
totalPercentChange:
|
|
98
|
-
|
|
109
|
+
mainStats.totalSize !== 0
|
|
110
|
+
? ((prStats.totalSize - mainStats.totalSize) / mainStats.totalSize) *
|
|
111
|
+
100
|
|
112
|
+
: 0,
|
|
99
113
|
diffStats,
|
|
100
114
|
}
|
|
101
115
|
}
|
|
@@ -107,7 +121,7 @@ function generateDiffMarkdown(prData, mainData, dependencies) {
|
|
|
107
121
|
markdown += `## Total Bundle Size\n\n`
|
|
108
122
|
markdown += `- Before: **${formatBytes(comparison.totalBefore)}**\n`
|
|
109
123
|
markdown += `- After: **${formatBytes(comparison.totalAfter)}**\n`
|
|
110
|
-
markdown += `- Change: ${totalDiffSymbol} **${formatBytes(Math.abs(comparison.totalDiff))}** (${comparison.totalPercentChange.toFixed(2)}%)\n\n`
|
|
124
|
+
markdown += `- Change: ${totalDiffSymbol} **${formatBytes(Math.abs(comparison.totalDiff))}** (${isNaN(comparison.totalPercentChange) ? "N/A" : comparison.totalPercentChange.toFixed(2)}%)\n\n`
|
|
111
125
|
|
|
112
126
|
markdown += `## Diff\n\n`
|
|
113
127
|
|
|
@@ -117,7 +131,10 @@ function generateDiffMarkdown(prData, mainData, dependencies) {
|
|
|
117
131
|
|
|
118
132
|
const significantChanges = sortedDiffs.filter(
|
|
119
133
|
([, stats]) =>
|
|
120
|
-
|
|
134
|
+
(typeof stats.percentChange === "number" &&
|
|
135
|
+
(Math.abs(stats.percentChange) > 1 || Math.abs(stats.diff) > 1024)) ||
|
|
136
|
+
stats.percentChange === "Added" ||
|
|
137
|
+
stats.percentChange === "Removed",
|
|
121
138
|
)
|
|
122
139
|
|
|
123
140
|
if (significantChanges.length > 0) {
|
|
@@ -127,8 +144,7 @@ function generateDiffMarkdown(prData, mainData, dependencies) {
|
|
|
127
144
|
for (const [name, stats] of significantChanges) {
|
|
128
145
|
const version = dependencies[name]
|
|
129
146
|
const symbol = stats.diff > 0 ? "📈" : "📉"
|
|
130
|
-
markdown += `| ${name}@${version} | ${formatBytes(stats.before)} | ${formatBytes(stats.after)} | ${symbol} ${formatBytes(Math.abs(stats.diff))} |
|
|
131
|
-
${stats.percentChange.toFixed(2)}% |\n`
|
|
147
|
+
markdown += `| ${name}@${version} | ${formatBytes(stats.before)} | ${formatBytes(stats.after)} | ${symbol} ${formatBytes(Math.abs(stats.diff))} | ${typeof stats.percentChange === "number" ? stats.percentChange.toFixed(2) + "%" : stats.percentChange} |\n`
|
|
132
148
|
}
|
|
133
149
|
} else {
|
|
134
150
|
markdown += "No significant changes in bundle size.\n"
|
|
@@ -165,6 +165,14 @@ export default function AIChatInterface({
|
|
|
165
165
|
{messages.length === 0 && isLoggedIn && (
|
|
166
166
|
<div className="text-gray-500 text-xl text-center pt-[30vh] flex flex-col items-center">
|
|
167
167
|
<div>Submit a prompt to {snippet ? "edit!" : "get started!"}</div>
|
|
168
|
+
<div className="mt-2">
|
|
169
|
+
This is our legacy AI chat interface. For a better experience,
|
|
170
|
+
please use{" "}
|
|
171
|
+
<PrefetchPageLink href="https://chat.tscircuit.com">
|
|
172
|
+
chat.tscircuit.com
|
|
173
|
+
</PrefetchPageLink>
|
|
174
|
+
.
|
|
175
|
+
</div>
|
|
168
176
|
<div className="text-6xl mt-4">↓</div>
|
|
169
177
|
</div>
|
|
170
178
|
)}
|
|
@@ -3,7 +3,7 @@ import posthog from "posthog-js"
|
|
|
3
3
|
import CookieConsent from "react-cookie-consent"
|
|
4
4
|
|
|
5
5
|
posthog.init("phc_htd8AQjSfVEsFCLQMAiUooG4Q0DKBCjqYuQglc9V3Wo", {
|
|
6
|
-
api_host: "https://
|
|
6
|
+
api_host: "https://postpig.tscircuit.com",
|
|
7
7
|
person_profiles: "always",
|
|
8
8
|
})
|
|
9
9
|
|
|
@@ -166,6 +166,15 @@ export function CodeAndPreview({ snippet }: Props) {
|
|
|
166
166
|
const [lastSavedAt, setLastSavedAt] = useState(Date.now())
|
|
167
167
|
|
|
168
168
|
const handleSave = async () => {
|
|
169
|
+
if (hasUnrunChanges) {
|
|
170
|
+
toast({
|
|
171
|
+
title: "Warning",
|
|
172
|
+
description: "You must run the snippet before saving your changes.",
|
|
173
|
+
variant: "destructive",
|
|
174
|
+
})
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
169
178
|
setLastSavedAt(Date.now())
|
|
170
179
|
if (snippet) {
|
|
171
180
|
updateSnippetMutation.mutate()
|
|
@@ -197,8 +206,6 @@ export function CodeAndPreview({ snippet }: Props) {
|
|
|
197
206
|
...(code.match(/export const (\w+) ?=/)?.slice(1) ?? []),
|
|
198
207
|
]
|
|
199
208
|
|
|
200
|
-
console.log(possibleExportNames)
|
|
201
|
-
|
|
202
209
|
const exportName = possibleExportNames[0]
|
|
203
210
|
|
|
204
211
|
let entrypointContent: string
|
|
@@ -224,7 +231,6 @@ export function CodeAndPreview({ snippet }: Props) {
|
|
|
224
231
|
"main.tsx": entrypointContent,
|
|
225
232
|
}
|
|
226
233
|
}, [code, manualEditsFileContent])
|
|
227
|
-
console.log(fsMap)
|
|
228
234
|
|
|
229
235
|
if (!snippet && (urlParams.snippet_id || urlParams.should_create_snippet)) {
|
|
230
236
|
return (
|
|
@@ -12,12 +12,16 @@ export function CreateNewSnippetWithAiHero() {
|
|
|
12
12
|
const handleSubmit = (e: React.FormEvent) => {
|
|
13
13
|
e.preventDefault()
|
|
14
14
|
if (inputValue.trim()) {
|
|
15
|
-
navigate(
|
|
15
|
+
navigate(
|
|
16
|
+
`https://chat.tscircuit.com?initial_prompt=${encodeURIComponent(inputValue)}`,
|
|
17
|
+
)
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
const handleQuickPrompt = (prompt: string) => {
|
|
20
|
-
navigate(
|
|
22
|
+
navigate(
|
|
23
|
+
`https://chat.tscircuit.com?initial_prompt=${encodeURIComponent(prompt)}`,
|
|
24
|
+
)
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
return (
|
|
@@ -48,6 +48,7 @@ import { SnippetLink } from "./SnippetLink"
|
|
|
48
48
|
import { TypeBadge } from "./TypeBadge"
|
|
49
49
|
import { useUpdateDescriptionDialog } from "./dialogs/edit-description-dialog"
|
|
50
50
|
import { useForkSnippetMutation } from "@/hooks/useForkSnippetMutation"
|
|
51
|
+
import tscircuitCorePkg from "@tscircuit/core/package.json"
|
|
51
52
|
|
|
52
53
|
export default function EditorNav({
|
|
53
54
|
circuitJson,
|
|
@@ -367,6 +368,9 @@ export default function EditorNav({
|
|
|
367
368
|
<Trash2 className="mr-2 h-3 w-3" />
|
|
368
369
|
Delete Snippet
|
|
369
370
|
</DropdownMenuItem>
|
|
371
|
+
<DropdownMenuItem className="text-xs text-gray-500" disabled>
|
|
372
|
+
@tscircuit/core@{tscircuitCorePkg.version}
|
|
373
|
+
</DropdownMenuItem>
|
|
370
374
|
</DropdownMenuContent>
|
|
371
375
|
</DropdownMenu>
|
|
372
376
|
<Button
|
|
@@ -26,7 +26,7 @@ export default function Footer() {
|
|
|
26
26
|
{ name: "Home", href: "/" },
|
|
27
27
|
{ name: "Dashboard", href: "/dashboard" },
|
|
28
28
|
{ name: "Editor", href: "/editor" },
|
|
29
|
-
{ name: "Create with AI", href: "
|
|
29
|
+
{ name: "Create with AI", href: "https://chat.tscircuit.com" },
|
|
30
30
|
{
|
|
31
31
|
name: "My Profile",
|
|
32
32
|
href: `/${session?.github_username}`,
|
|
@@ -75,16 +75,15 @@ export default function Header() {
|
|
|
75
75
|
<HeaderButton href="/dashboard">Dashboard</HeaderButton>
|
|
76
76
|
</li>
|
|
77
77
|
)}
|
|
78
|
-
<li>
|
|
79
|
-
<HeaderButton href="/newest">Newest</HeaderButton>
|
|
80
|
-
</li>
|
|
81
78
|
<li>
|
|
82
79
|
<HeaderButton href="/quickstart" alsoHighlightForUrl="/editor">
|
|
83
80
|
Editor
|
|
84
81
|
</HeaderButton>
|
|
85
82
|
</li>
|
|
86
83
|
<li>
|
|
87
|
-
<
|
|
84
|
+
<a href="https://chat.tscircuit.com">
|
|
85
|
+
<Button variant="ghost">AI</Button>
|
|
86
|
+
</a>
|
|
88
87
|
</li>
|
|
89
88
|
<li>
|
|
90
89
|
<a href="https://docs.tscircuit.com">
|
|
@@ -144,11 +143,6 @@ export default function Header() {
|
|
|
144
143
|
</HeaderButton>
|
|
145
144
|
</li>
|
|
146
145
|
)}
|
|
147
|
-
<li>
|
|
148
|
-
<HeaderButton className="w-full justify-start" href="/newest">
|
|
149
|
-
Newest
|
|
150
|
-
</HeaderButton>
|
|
151
|
-
</li>
|
|
152
146
|
<li>
|
|
153
147
|
<HeaderButton
|
|
154
148
|
className="w-full justify-start"
|
|
@@ -159,7 +153,10 @@ export default function Header() {
|
|
|
159
153
|
</HeaderButton>
|
|
160
154
|
</li>
|
|
161
155
|
<li>
|
|
162
|
-
<HeaderButton
|
|
156
|
+
<HeaderButton
|
|
157
|
+
className="w-full justify-start"
|
|
158
|
+
href="https://chat.tscircuit.com"
|
|
159
|
+
>
|
|
163
160
|
AI
|
|
164
161
|
</HeaderButton>
|
|
165
162
|
</li>
|
|
@@ -41,7 +41,7 @@ export const HeaderLogin: React.FC<HeaderLoginProps> = () => {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
return (
|
|
44
|
-
<div className="flex items-center space-x-2 justify-end">
|
|
44
|
+
<div className="flex items-center md:space-x-2 justify-end">
|
|
45
45
|
<Button onClick={() => signIn()} variant="ghost">
|
|
46
46
|
Log in
|
|
47
47
|
</Button>
|
|
@@ -320,7 +320,10 @@ export const PreviewContent = ({
|
|
|
320
320
|
>
|
|
321
321
|
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
|
322
322
|
{circuitJson ? (
|
|
323
|
-
<CadViewer
|
|
323
|
+
<CadViewer
|
|
324
|
+
circuitJson={circuitJson as any}
|
|
325
|
+
ref={threeJsObjectRef}
|
|
326
|
+
/>
|
|
324
327
|
) : (
|
|
325
328
|
<PreviewEmptyState triggerRunTsx={triggerRunTsx} />
|
|
326
329
|
)}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Button } from "@/components/ui/button"
|
|
2
|
+
import { ChevronDown, ChevronUp, Star } from "lucide-react"
|
|
3
|
+
import { Link } from "wouter"
|
|
4
|
+
import { Snippet } from "fake-snippets-api/lib/db/schema"
|
|
5
|
+
|
|
6
|
+
interface SnippetListProps {
|
|
7
|
+
title: string
|
|
8
|
+
snippets?: Snippet[]
|
|
9
|
+
showAll?: boolean
|
|
10
|
+
onToggleShowAll?: () => void
|
|
11
|
+
maxItems?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const SnippetList = ({
|
|
15
|
+
title,
|
|
16
|
+
snippets = [],
|
|
17
|
+
showAll = false,
|
|
18
|
+
onToggleShowAll,
|
|
19
|
+
maxItems = 5,
|
|
20
|
+
}: SnippetListProps) => {
|
|
21
|
+
const displayedSnippets = showAll ? snippets : snippets.slice(0, maxItems)
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div>
|
|
25
|
+
<div className="flex items-center justify-between">
|
|
26
|
+
<h2 className="text-sm font-bold text-gray-700">{title}</h2>
|
|
27
|
+
{snippets.length > maxItems && onToggleShowAll && (
|
|
28
|
+
<Button
|
|
29
|
+
variant="ghost"
|
|
30
|
+
size="sm"
|
|
31
|
+
onClick={onToggleShowAll}
|
|
32
|
+
className="text-blue-600 hover:text-blue-700 hover:bg-transparent"
|
|
33
|
+
>
|
|
34
|
+
{showAll ? (
|
|
35
|
+
<>
|
|
36
|
+
Show less <ChevronUp className="w-3 h-3 ml-1" />
|
|
37
|
+
</>
|
|
38
|
+
) : (
|
|
39
|
+
<>
|
|
40
|
+
Show more <ChevronDown className="w-3 h-3 ml-1" />
|
|
41
|
+
</>
|
|
42
|
+
)}
|
|
43
|
+
</Button>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
<div className="border-b border-gray-200" />
|
|
47
|
+
{snippets && (
|
|
48
|
+
<ul className="space-y-1 mt-2">
|
|
49
|
+
{displayedSnippets.map((snippet) => (
|
|
50
|
+
<li key={snippet.snippet_id}>
|
|
51
|
+
<div className="flex items-center">
|
|
52
|
+
<Link
|
|
53
|
+
href={`/${snippet.owner_name}/${snippet.unscoped_name}`}
|
|
54
|
+
className="text-blue-600 hover:underline text-sm"
|
|
55
|
+
>
|
|
56
|
+
{snippet.owner_name}/{snippet.unscoped_name}
|
|
57
|
+
</Link>
|
|
58
|
+
{snippet.star_count > 0 && (
|
|
59
|
+
<span className="ml-2 text-gray-500 text-xs flex items-center">
|
|
60
|
+
<Star className="w-3 h-3 mr-1" />
|
|
61
|
+
{snippet.star_count}
|
|
62
|
+
</span>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
</li>
|
|
66
|
+
))}
|
|
67
|
+
</ul>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -3,11 +3,12 @@ export const blinkingLedBoardTemplate = {
|
|
|
3
3
|
code: `
|
|
4
4
|
import { useUsbC } from "@tsci/seveibar.smd-usb-c"
|
|
5
5
|
import { useRedLed } from "@tsci/seveibar.red-led"
|
|
6
|
-
import {
|
|
6
|
+
import { useNE555P } from "@tsci/seveibar.a555timer"
|
|
7
7
|
|
|
8
8
|
export const MyBlinkingLedCircuit = () => {
|
|
9
9
|
const USBC = useUsbC("USBC")
|
|
10
10
|
const Led = useRedLed("LED")
|
|
11
|
+
const A555Timer = useNE555P("B1")
|
|
11
12
|
|
|
12
13
|
return (
|
|
13
14
|
<board width="30mm" height="30mm" schAutoLayoutEnabled>
|