@voltx/cli 0.3.4 → 0.3.5
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 +54 -22
- package/dist/build.d.mts +4 -3
- package/dist/build.d.ts +4 -3
- package/dist/build.js +56 -27
- package/dist/build.mjs +1 -2
- package/dist/chunk-2LHDOMF2.mjs +680 -0
- package/dist/chunk-5DVBIJXU.mjs +84 -0
- package/dist/chunk-65PVXS4D.mjs +894 -0
- package/dist/chunk-7JVIEGSA.mjs +141 -0
- package/dist/chunk-A4NA4AJN.mjs +786 -0
- package/dist/chunk-AAAHANST.mjs +839 -0
- package/dist/chunk-AD3WMFZF.mjs +205 -0
- package/dist/chunk-CSSHLVYS.mjs +83 -0
- package/dist/chunk-CWOSNV5O.mjs +150 -0
- package/dist/chunk-EI6XBYKB.mjs +84 -0
- package/dist/chunk-FI2W4L4S.mjs +205 -0
- package/dist/chunk-G2INQCCJ.mjs +907 -0
- package/dist/chunk-H2DTIOEO.mjs +150 -0
- package/dist/chunk-IS2WTE3C.mjs +138 -0
- package/dist/chunk-JECCDBYI.mjs +730 -0
- package/dist/chunk-KX2MRJUO.mjs +795 -0
- package/dist/chunk-LTGMHAZS.mjs +147 -0
- package/dist/chunk-OPO6RUFP.mjs +698 -0
- package/dist/chunk-PWQSKYAM.mjs +682 -0
- package/dist/chunk-Q5XCFN7L.mjs +1026 -0
- package/dist/chunk-QSU6FZC7.mjs +497 -0
- package/dist/chunk-RYWRFHEC.mjs +83 -0
- package/dist/chunk-SU4Q3PTH.mjs +201 -0
- package/dist/chunk-UXI3QSDN.mjs +121 -0
- package/dist/chunk-VD3CNPNP.mjs +123 -0
- package/dist/chunk-X6VOAPRJ.mjs +756 -0
- package/dist/cli.js +932 -307
- package/dist/cli.mjs +7 -6
- package/dist/create.d.mts +1 -0
- package/dist/create.d.ts +1 -0
- package/dist/create.js +723 -191
- package/dist/create.mjs +1 -2
- package/dist/dev.d.mts +6 -4
- package/dist/dev.d.ts +6 -4
- package/dist/dev.js +119 -46
- package/dist/dev.mjs +1 -2
- package/dist/generate.js +13 -13
- package/dist/generate.mjs +1 -2
- package/dist/index.js +919 -295
- package/dist/index.mjs +5 -6
- package/dist/start.js +7 -17
- package/dist/start.mjs +1 -2
- package/dist/welcome.mjs +0 -1
- package/package.json +11 -3
package/dist/create.js
CHANGED
|
@@ -142,15 +142,28 @@ function printWelcomeBanner(projectName) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// src/create.ts
|
|
145
|
-
var
|
|
145
|
+
var VV = {
|
|
146
|
+
"@voltx/core": "^0.3.1",
|
|
147
|
+
"@voltx/server": "^0.3.1",
|
|
148
|
+
"@voltx/cli": "^0.3.5",
|
|
149
|
+
"@voltx/ai": "^0.3.0",
|
|
150
|
+
"@voltx/agents": "^0.3.1",
|
|
151
|
+
"@voltx/memory": "^0.3.0",
|
|
152
|
+
"@voltx/db": "^0.3.0",
|
|
153
|
+
"@voltx/rag": "^0.3.1",
|
|
154
|
+
"@voltx/auth": "^0.3.0"
|
|
155
|
+
};
|
|
156
|
+
function v(pkg) {
|
|
157
|
+
return VV[pkg] ?? "^0.3.0";
|
|
158
|
+
}
|
|
146
159
|
var TEMPLATE_DEPS = {
|
|
147
|
-
blank: { "@voltx/core":
|
|
148
|
-
chatbot: { "@voltx/core":
|
|
149
|
-
"rag-app": { "@voltx/core":
|
|
150
|
-
"agent-app": { "@voltx/core":
|
|
160
|
+
blank: { "@voltx/core": v("@voltx/core"), "@voltx/server": v("@voltx/server") },
|
|
161
|
+
chatbot: { "@voltx/core": v("@voltx/core"), "@voltx/ai": v("@voltx/ai"), "@voltx/server": v("@voltx/server"), "@voltx/memory": v("@voltx/memory") },
|
|
162
|
+
"rag-app": { "@voltx/core": v("@voltx/core"), "@voltx/ai": v("@voltx/ai"), "@voltx/server": v("@voltx/server"), "@voltx/rag": v("@voltx/rag"), "@voltx/db": v("@voltx/db") },
|
|
163
|
+
"agent-app": { "@voltx/core": v("@voltx/core"), "@voltx/ai": v("@voltx/ai"), "@voltx/server": v("@voltx/server"), "@voltx/agents": v("@voltx/agents"), "@voltx/memory": v("@voltx/memory") }
|
|
151
164
|
};
|
|
152
165
|
async function createProject(options) {
|
|
153
|
-
const { name, template = "blank", auth = "none" } = options;
|
|
166
|
+
const { name, template = "blank", auth = "none", shadcn = false } = options;
|
|
154
167
|
const targetDir = path.resolve(process.cwd(), name);
|
|
155
168
|
if (fs.existsSync(targetDir)) {
|
|
156
169
|
console.error(`[voltx] Directory "${name}" already exists.`);
|
|
@@ -160,21 +173,39 @@ async function createProject(options) {
|
|
|
160
173
|
const provider = template === "rag-app" ? "openai" : "cerebras";
|
|
161
174
|
const model = template === "rag-app" ? "gpt-4o" : "llama3.1-8b";
|
|
162
175
|
const hasDb = template === "rag-app" || template === "agent-app" || auth === "better-auth";
|
|
163
|
-
const deps = { ...TEMPLATE_DEPS[template] ?? TEMPLATE_DEPS["blank"], "@voltx/cli":
|
|
176
|
+
const deps = { ...TEMPLATE_DEPS[template] ?? TEMPLATE_DEPS["blank"], "@voltx/cli": v("@voltx/cli") };
|
|
164
177
|
if (auth === "better-auth") {
|
|
165
|
-
deps["@voltx/auth"] =
|
|
178
|
+
deps["@voltx/auth"] = v("@voltx/auth");
|
|
166
179
|
deps["better-auth"] = "^1.5.0";
|
|
167
180
|
} else if (auth === "jwt") {
|
|
168
|
-
deps["@voltx/auth"] =
|
|
181
|
+
deps["@voltx/auth"] = v("@voltx/auth");
|
|
169
182
|
deps["jose"] = "^6.0.0";
|
|
170
183
|
}
|
|
184
|
+
const devDeps = { typescript: "^5.7.0", tsx: "^4.21.0", tsup: "^8.0.0", "@types/node": "^22.0.0" };
|
|
185
|
+
deps["hono"] = "^4.7.0";
|
|
186
|
+
deps["@hono/node-server"] = "^1.14.0";
|
|
187
|
+
deps["react"] = "^19.0.0";
|
|
188
|
+
deps["react-dom"] = "^19.0.0";
|
|
189
|
+
deps["tailwindcss"] = "^4.0.0";
|
|
190
|
+
devDeps["vite"] = "^6.0.0";
|
|
191
|
+
devDeps["@hono/vite-dev-server"] = "^0.7.0";
|
|
192
|
+
devDeps["@vitejs/plugin-react"] = "^4.3.0";
|
|
193
|
+
devDeps["@tailwindcss/vite"] = "^4.0.0";
|
|
194
|
+
devDeps["@types/react"] = "^19.0.0";
|
|
195
|
+
devDeps["@types/react-dom"] = "^19.0.0";
|
|
196
|
+
if (shadcn) {
|
|
197
|
+
deps["class-variance-authority"] = "^0.7.0";
|
|
198
|
+
deps["clsx"] = "^2.1.0";
|
|
199
|
+
deps["tailwind-merge"] = "^3.0.0";
|
|
200
|
+
deps["lucide-react"] = "^0.468.0";
|
|
201
|
+
}
|
|
171
202
|
fs.writeFileSync(path.join(targetDir, "package.json"), JSON.stringify({
|
|
172
203
|
name,
|
|
173
204
|
version: "0.1.0",
|
|
174
205
|
private: true,
|
|
175
206
|
scripts: { dev: "voltx dev", build: "voltx build", start: "voltx start" },
|
|
176
207
|
dependencies: deps,
|
|
177
|
-
devDependencies:
|
|
208
|
+
devDependencies: devDeps
|
|
178
209
|
}, null, 2));
|
|
179
210
|
let config = `import { defineConfig } from "@voltx/core";
|
|
180
211
|
|
|
@@ -195,31 +226,53 @@ export default defineConfig({
|
|
|
195
226
|
},`;
|
|
196
227
|
config += `
|
|
197
228
|
server: {
|
|
198
|
-
routesDir: "
|
|
229
|
+
routesDir: "api",
|
|
199
230
|
staticDir: "public",
|
|
200
231
|
cors: true,
|
|
201
232
|
},
|
|
202
233
|
});
|
|
203
234
|
`;
|
|
204
235
|
fs.writeFileSync(path.join(targetDir, "voltx.config.ts"), config);
|
|
205
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
236
|
+
fs.mkdirSync(path.join(targetDir, "api"), { recursive: true });
|
|
206
237
|
fs.mkdirSync(path.join(targetDir, "public"), { recursive: true });
|
|
207
|
-
fs.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
fs.writeFileSync(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
238
|
+
fs.mkdirSync(path.join(targetDir, "src"), { recursive: true });
|
|
239
|
+
fs.mkdirSync(path.join(targetDir, "src", "components"), { recursive: true });
|
|
240
|
+
fs.mkdirSync(path.join(targetDir, "src", "hooks"), { recursive: true });
|
|
241
|
+
fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
|
|
242
|
+
fs.writeFileSync(path.join(targetDir, "public", "favicon.svg"), generateFaviconSVG());
|
|
243
|
+
fs.writeFileSync(path.join(targetDir, "public", "robots.txt"), generateRobotsTxt());
|
|
244
|
+
fs.writeFileSync(path.join(targetDir, "public", "site.webmanifest"), generateWebManifest(name));
|
|
245
|
+
const tsconfig = {
|
|
246
|
+
compilerOptions: {
|
|
247
|
+
target: "ES2022",
|
|
248
|
+
module: "ESNext",
|
|
249
|
+
moduleResolution: "bundler",
|
|
250
|
+
strict: true,
|
|
251
|
+
esModuleInterop: true,
|
|
252
|
+
skipLibCheck: true,
|
|
253
|
+
outDir: "dist",
|
|
254
|
+
baseUrl: ".",
|
|
255
|
+
paths: { "@/*": ["./src/*"] },
|
|
256
|
+
jsx: "react-jsx"
|
|
257
|
+
},
|
|
258
|
+
include: ["src", "api", "server.ts", "voltx.config.ts"]
|
|
259
|
+
};
|
|
260
|
+
if (template === "agent-app") tsconfig.include.push("agents", "tools");
|
|
261
|
+
fs.writeFileSync(path.join(targetDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
|
|
262
|
+
fs.writeFileSync(path.join(targetDir, "server.ts"), generateServerEntry(name, template));
|
|
263
|
+
fs.writeFileSync(path.join(targetDir, "vite.config.ts"), generateViteConfigFile("server.ts"));
|
|
264
|
+
fs.writeFileSync(path.join(targetDir, "src", "entry-client.tsx"), generateEntryClient());
|
|
265
|
+
fs.writeFileSync(path.join(targetDir, "src", "entry-server.tsx"), generateEntryServer());
|
|
266
|
+
fs.writeFileSync(path.join(targetDir, "src", "layout.tsx"), generateLayoutComponent(name));
|
|
267
|
+
fs.writeFileSync(path.join(targetDir, "src", "globals.css"), generateGlobalCSS(shadcn));
|
|
268
|
+
if (shadcn) {
|
|
269
|
+
fs.writeFileSync(path.join(targetDir, "src", "lib", "utils.ts"), generateCnUtil());
|
|
270
|
+
fs.writeFileSync(path.join(targetDir, "components.json"), generateComponentsJson());
|
|
271
|
+
}
|
|
272
|
+
fs.writeFileSync(path.join(targetDir, "src", "app.tsx"), generateAppComponent(name, template));
|
|
220
273
|
fs.writeFileSync(
|
|
221
|
-
path.join(targetDir, "
|
|
222
|
-
`// GET / \u2014 Health check
|
|
274
|
+
path.join(targetDir, "api", "index.ts"),
|
|
275
|
+
`// GET /api \u2014 Health check
|
|
223
276
|
import type { Context } from "@voltx/server";
|
|
224
277
|
|
|
225
278
|
export function GET(c: Context) {
|
|
@@ -229,7 +282,7 @@ export function GET(c: Context) {
|
|
|
229
282
|
);
|
|
230
283
|
if (template === "chatbot" || template === "agent-app") {
|
|
231
284
|
fs.writeFileSync(
|
|
232
|
-
path.join(targetDir, "
|
|
285
|
+
path.join(targetDir, "api", "chat.ts"),
|
|
233
286
|
`// POST /api/chat \u2014 Streaming chat with conversation memory
|
|
234
287
|
import type { Context } from "@voltx/server";
|
|
235
288
|
import { streamText } from "@voltx/ai";
|
|
@@ -263,9 +316,9 @@ export async function POST(c: Context) {
|
|
|
263
316
|
);
|
|
264
317
|
}
|
|
265
318
|
if (template === "agent-app") {
|
|
266
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
267
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
268
|
-
fs.writeFileSync(path.join(targetDir, "
|
|
319
|
+
fs.mkdirSync(path.join(targetDir, "agents"), { recursive: true });
|
|
320
|
+
fs.mkdirSync(path.join(targetDir, "tools"), { recursive: true });
|
|
321
|
+
fs.writeFileSync(path.join(targetDir, "tools", "calculator.ts"), `// Calculator tool \u2014 evaluates math expressions (no API key needed)
|
|
269
322
|
import type { Tool } from "@voltx/agents";
|
|
270
323
|
|
|
271
324
|
export const calculatorTool: Tool = {
|
|
@@ -290,7 +343,7 @@ export const calculatorTool: Tool = {
|
|
|
290
343
|
},
|
|
291
344
|
};
|
|
292
345
|
`);
|
|
293
|
-
fs.writeFileSync(path.join(targetDir, "
|
|
346
|
+
fs.writeFileSync(path.join(targetDir, "tools", "datetime.ts"), `// Date & time tool \u2014 returns current date, time, timezone (no API key needed)
|
|
294
347
|
import type { Tool } from "@voltx/agents";
|
|
295
348
|
|
|
296
349
|
export const datetimeTool: Tool = {
|
|
@@ -311,7 +364,7 @@ export const datetimeTool: Tool = {
|
|
|
311
364
|
},
|
|
312
365
|
};
|
|
313
366
|
`);
|
|
314
|
-
fs.writeFileSync(path.join(targetDir, "
|
|
367
|
+
fs.writeFileSync(path.join(targetDir, "agents", "assistant.ts"), `// AI Agent \u2014 autonomous assistant with tools
|
|
315
368
|
import { createAgent } from "@voltx/agents";
|
|
316
369
|
import { calculatorTool } from "../tools/calculator";
|
|
317
370
|
import { datetimeTool } from "../tools/datetime";
|
|
@@ -325,10 +378,10 @@ export const assistant = createAgent({
|
|
|
325
378
|
});
|
|
326
379
|
`);
|
|
327
380
|
fs.writeFileSync(
|
|
328
|
-
path.join(targetDir, "
|
|
381
|
+
path.join(targetDir, "api", "agent.ts"),
|
|
329
382
|
`// POST /api/agent \u2014 Run the AI agent
|
|
330
383
|
import type { Context } from "@voltx/server";
|
|
331
|
-
import { assistant } from "
|
|
384
|
+
import { assistant } from "../agents/assistant";
|
|
332
385
|
|
|
333
386
|
export async function POST(c: Context) {
|
|
334
387
|
const { input } = await c.req.json();
|
|
@@ -341,9 +394,9 @@ export async function POST(c: Context) {
|
|
|
341
394
|
}
|
|
342
395
|
if (template === "rag-app") {
|
|
343
396
|
const embedModel = "openai:text-embedding-3-small";
|
|
344
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
397
|
+
fs.mkdirSync(path.join(targetDir, "api", "rag"), { recursive: true });
|
|
345
398
|
fs.writeFileSync(
|
|
346
|
-
path.join(targetDir, "
|
|
399
|
+
path.join(targetDir, "api", "rag", "query.ts"),
|
|
347
400
|
`// POST /api/rag/query \u2014 Query documents with RAG
|
|
348
401
|
import type { Context } from "@voltx/server";
|
|
349
402
|
import { streamText } from "@voltx/ai";
|
|
@@ -367,7 +420,7 @@ export async function POST(c: Context) {
|
|
|
367
420
|
`
|
|
368
421
|
);
|
|
369
422
|
fs.writeFileSync(
|
|
370
|
-
path.join(targetDir, "
|
|
423
|
+
path.join(targetDir, "api", "rag", "ingest.ts"),
|
|
371
424
|
`// POST /api/rag/ingest \u2014 Ingest documents into the vector store
|
|
372
425
|
import type { Context } from "@voltx/server";
|
|
373
426
|
import { createRAGPipeline, createEmbedder } from "@voltx/rag";
|
|
@@ -387,12 +440,12 @@ export async function POST(c: Context) {
|
|
|
387
440
|
);
|
|
388
441
|
}
|
|
389
442
|
if (auth === "better-auth") {
|
|
390
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
443
|
+
fs.mkdirSync(path.join(targetDir, "api", "auth"), { recursive: true });
|
|
391
444
|
fs.writeFileSync(
|
|
392
|
-
path.join(targetDir, "
|
|
445
|
+
path.join(targetDir, "api", "auth", "[...path].ts"),
|
|
393
446
|
`// ALL /api/auth/* \u2014 Better Auth handler
|
|
394
447
|
import type { Context } from "@voltx/server";
|
|
395
|
-
import { auth } from "
|
|
448
|
+
import { auth } from "../../src/lib/auth";
|
|
396
449
|
import { createAuthHandler } from "@voltx/auth";
|
|
397
450
|
|
|
398
451
|
const handler = createAuthHandler(auth);
|
|
@@ -401,7 +454,6 @@ export const GET = (c: Context) => handler(c);
|
|
|
401
454
|
export const POST = (c: Context) => handler(c);
|
|
402
455
|
`
|
|
403
456
|
);
|
|
404
|
-
fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
|
|
405
457
|
fs.writeFileSync(
|
|
406
458
|
path.join(targetDir, "src", "lib", "auth.ts"),
|
|
407
459
|
`import { createAuth, createAuthMiddleware } from "@voltx/auth";
|
|
@@ -418,7 +470,6 @@ export const authMiddleware = createAuthMiddleware({
|
|
|
418
470
|
`
|
|
419
471
|
);
|
|
420
472
|
} else if (auth === "jwt") {
|
|
421
|
-
fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
|
|
422
473
|
fs.writeFileSync(
|
|
423
474
|
path.join(targetDir, "src", "lib", "auth.ts"),
|
|
424
475
|
`import { createAuth, createAuthMiddleware } from "@voltx/auth";
|
|
@@ -435,9 +486,9 @@ export const authMiddleware = createAuthMiddleware({
|
|
|
435
486
|
`
|
|
436
487
|
);
|
|
437
488
|
fs.writeFileSync(
|
|
438
|
-
path.join(targetDir, "
|
|
489
|
+
path.join(targetDir, "api", "auth.ts"),
|
|
439
490
|
`import type { Context } from "@voltx/server";
|
|
440
|
-
import { jwt } from "
|
|
491
|
+
import { jwt } from "../src/lib/auth";
|
|
441
492
|
|
|
442
493
|
export async function POST(c: Context) {
|
|
443
494
|
const { email, password } = await c.req.json();
|
|
@@ -454,14 +505,6 @@ export async function POST(c: Context) {
|
|
|
454
505
|
envContent += "# \u2500\u2500\u2500 Database \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDATABASE_URL=\n\n";
|
|
455
506
|
} else if (template === "chatbot" || template === "agent-app") {
|
|
456
507
|
envContent += "# \u2500\u2500\u2500 LLM Provider \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCEREBRAS_API_KEY=csk-...\n\n";
|
|
457
|
-
if (template === "agent-app") {
|
|
458
|
-
envContent += "# \u2500\u2500\u2500 Database (optional) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDATABASE_URL=\n\n";
|
|
459
|
-
envContent += "# \u2500\u2500\u2500 Tool API Keys (add keys for tools you use) \u2500\u2500\n";
|
|
460
|
-
envContent += "# TAVILY_API_KEY=tvly-... (Web Search \u2014 https://tavily.com)\n";
|
|
461
|
-
envContent += "# SERPER_API_KEY= (Google Search \u2014 https://serper.dev)\n";
|
|
462
|
-
envContent += "# OPENWEATHER_API_KEY= (Weather \u2014 https://openweathermap.org/api)\n";
|
|
463
|
-
envContent += "# NEWS_API_KEY= (News \u2014 https://newsapi.org)\n\n";
|
|
464
|
-
}
|
|
465
508
|
} else {
|
|
466
509
|
envContent += "# \u2500\u2500\u2500 LLM Provider \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# OPENAI_API_KEY=sk-...\n# CEREBRAS_API_KEY=csk-...\n\n";
|
|
467
510
|
}
|
|
@@ -472,158 +515,647 @@ export async function POST(c: Context) {
|
|
|
472
515
|
}
|
|
473
516
|
envContent += "# \u2500\u2500\u2500 App \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPORT=3000\nNODE_ENV=development\n";
|
|
474
517
|
fs.writeFileSync(path.join(targetDir, ".env.example"), envContent);
|
|
475
|
-
|
|
476
|
-
fs.writeFileSync(path.join(targetDir, "public", "index.html"), generateFrontendHTML(name, template));
|
|
477
|
-
}
|
|
478
|
-
fs.writeFileSync(path.join(targetDir, ".gitignore"), "node_modules\ndist\n.env\n");
|
|
518
|
+
fs.writeFileSync(path.join(targetDir, ".gitignore"), "node_modules\ndist\n.env\n.env.local\n.env.*.local\nvite.config.voltx.ts\n");
|
|
479
519
|
printWelcomeBanner(name);
|
|
480
520
|
}
|
|
481
|
-
function
|
|
482
|
-
const
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
.
|
|
497
|
-
.
|
|
498
|
-
.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
521
|
+
function generateServerEntry(projectName, template) {
|
|
522
|
+
const imports = [];
|
|
523
|
+
const mounts = [];
|
|
524
|
+
imports.push('import { GET as healthGET } from "./api/index";');
|
|
525
|
+
mounts.push('app.get("/api", healthGET);');
|
|
526
|
+
if (template === "chatbot" || template === "agent-app") {
|
|
527
|
+
imports.push('import { POST as chatPOST } from "./api/chat";');
|
|
528
|
+
mounts.push('app.post("/api/chat", chatPOST);');
|
|
529
|
+
}
|
|
530
|
+
if (template === "agent-app") {
|
|
531
|
+
imports.push('import { POST as agentPOST } from "./api/agent";');
|
|
532
|
+
mounts.push('app.post("/api/agent", agentPOST);');
|
|
533
|
+
}
|
|
534
|
+
if (template === "rag-app") {
|
|
535
|
+
imports.push('import { POST as ragQueryPOST } from "./api/rag/query";');
|
|
536
|
+
imports.push('import { POST as ragIngestPOST } from "./api/rag/ingest";');
|
|
537
|
+
mounts.push('app.post("/api/rag/query", ragQueryPOST);');
|
|
538
|
+
mounts.push('app.post("/api/rag/ingest", ragIngestPOST);');
|
|
539
|
+
}
|
|
540
|
+
return `import { Hono } from "hono";
|
|
541
|
+
import { serve } from "@hono/node-server";
|
|
542
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
543
|
+
import { registerSSR } from "@voltx/server";
|
|
544
|
+
import { loadEnv } from "@voltx/core";
|
|
545
|
+
${imports.join("\n")}
|
|
546
|
+
|
|
547
|
+
loadEnv(process.env.NODE_ENV ?? "development");
|
|
548
|
+
|
|
549
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
550
|
+
const app = new Hono();
|
|
551
|
+
|
|
552
|
+
// \u2500\u2500 API Routes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
553
|
+
${mounts.join("\n")}
|
|
554
|
+
|
|
555
|
+
// \u2500\u2500 Static assets (production) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
556
|
+
if (isProd) {
|
|
557
|
+
app.use("/assets/*", serveStatic({ root: "./dist/client/" }));
|
|
558
|
+
app.use("/favicon.svg", serveStatic({ root: "./public/" }));
|
|
559
|
+
app.use("/robots.txt", serveStatic({ root: "./public/" }));
|
|
560
|
+
app.use("/site.webmanifest", serveStatic({ root: "./public/" }));
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// \u2500\u2500 SSR catch-all \u2014 renders React on the server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
564
|
+
registerSSR(app, null, {
|
|
565
|
+
title: "${projectName}",
|
|
566
|
+
entryServer: "src/entry-server.tsx",
|
|
567
|
+
entryClient: "src/entry-client.tsx",
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
export default app;
|
|
571
|
+
|
|
572
|
+
if (isProd) {
|
|
573
|
+
const port = Number(process.env.PORT) || 3000;
|
|
574
|
+
serve({ fetch: app.fetch, port }, (info) => {
|
|
575
|
+
console.log(\`\\n \u26A1 ${projectName} running at http://localhost:\${info.port}\\n\`);
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
`;
|
|
579
|
+
}
|
|
580
|
+
function generateViteConfigFile(entry) {
|
|
581
|
+
return `import { defineConfig } from "vite";
|
|
582
|
+
import devServer from "@hono/vite-dev-server";
|
|
583
|
+
import react from "@vitejs/plugin-react";
|
|
584
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
585
|
+
|
|
586
|
+
export default defineConfig({
|
|
587
|
+
resolve: {
|
|
588
|
+
alias: {
|
|
589
|
+
"@": "/src",
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
plugins: [
|
|
593
|
+
react(),
|
|
594
|
+
tailwindcss(),
|
|
595
|
+
devServer({
|
|
596
|
+
entry: "${entry}",
|
|
597
|
+
exclude: [
|
|
598
|
+
/.*\\.tsx?($|\\?)/,
|
|
599
|
+
/.*\\.(s?css|less)($|\\?)/,
|
|
600
|
+
/.*\\.(svg|png|jpg|jpeg|gif|webp|ico)($|\\?)/,
|
|
601
|
+
/^\\/@.+$/,
|
|
602
|
+
/^\\/favicon\\.svg$/,
|
|
603
|
+
/^\\/node_modules\\/.*/,
|
|
604
|
+
/^\\/src\\/.*/,
|
|
605
|
+
],
|
|
606
|
+
injectClientScript: false,
|
|
607
|
+
}),
|
|
608
|
+
],
|
|
609
|
+
});
|
|
610
|
+
`;
|
|
611
|
+
}
|
|
612
|
+
function generateEntryClient() {
|
|
613
|
+
return `import React from "react";
|
|
614
|
+
import { hydrateRoot } from "react-dom/client";
|
|
615
|
+
import Layout from "./layout";
|
|
616
|
+
import App from "./app";
|
|
617
|
+
import "./globals.css";
|
|
618
|
+
|
|
619
|
+
hydrateRoot(
|
|
620
|
+
document.getElementById("root")!,
|
|
621
|
+
<React.StrictMode>
|
|
622
|
+
<Layout>
|
|
623
|
+
<App />
|
|
624
|
+
</Layout>
|
|
625
|
+
</React.StrictMode>
|
|
626
|
+
);
|
|
627
|
+
`;
|
|
628
|
+
}
|
|
629
|
+
function generateEntryServer() {
|
|
630
|
+
return `import React from "react";
|
|
631
|
+
import { renderToReadableStream } from "react-dom/server";
|
|
632
|
+
import Layout from "./layout";
|
|
633
|
+
import App from "./app";
|
|
634
|
+
|
|
635
|
+
export async function render(_url: string): Promise<ReadableStream> {
|
|
636
|
+
const stream = await renderToReadableStream(
|
|
637
|
+
<React.StrictMode>
|
|
638
|
+
<Layout>
|
|
639
|
+
<App />
|
|
640
|
+
</Layout>
|
|
641
|
+
</React.StrictMode>,
|
|
642
|
+
{
|
|
643
|
+
onError(error: unknown) {
|
|
644
|
+
console.error("[voltx] SSR render error:", error);
|
|
645
|
+
},
|
|
646
|
+
}
|
|
647
|
+
);
|
|
648
|
+
return stream;
|
|
649
|
+
}
|
|
650
|
+
`;
|
|
651
|
+
}
|
|
652
|
+
function generateLayoutComponent(projectName) {
|
|
653
|
+
return `import React from "react";
|
|
654
|
+
|
|
655
|
+
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
656
|
+
return (
|
|
657
|
+
<div className="min-h-screen bg-background text-foreground font-sans">
|
|
658
|
+
<header className="border-b border-border px-6 py-3 flex items-center justify-between">
|
|
659
|
+
<div className="flex items-center gap-2">
|
|
660
|
+
<span className="text-xl">\u26A1</span>
|
|
661
|
+
<span className="font-semibold">${projectName}</span>
|
|
538
662
|
</div>
|
|
539
|
-
<
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
663
|
+
<a href="https://github.com/codewithshail/voltx" target="_blank" rel="noopener noreferrer" className="text-muted text-sm hover:text-foreground transition-colors">
|
|
664
|
+
Built with VoltX
|
|
665
|
+
</a>
|
|
666
|
+
</header>
|
|
667
|
+
<main>{children}</main>
|
|
668
|
+
</div>
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
`;
|
|
672
|
+
}
|
|
673
|
+
function generateGlobalCSS(useShadcn = false) {
|
|
674
|
+
if (useShadcn) {
|
|
675
|
+
return `@import "tailwindcss";
|
|
676
|
+
|
|
677
|
+
@theme inline {
|
|
678
|
+
--radius-sm: 0.25rem;
|
|
679
|
+
--radius-md: 0.375rem;
|
|
680
|
+
--radius-lg: 0.5rem;
|
|
681
|
+
--radius-xl: 0.75rem;
|
|
682
|
+
--color-background: hsl(var(--background));
|
|
683
|
+
--color-foreground: hsl(var(--foreground));
|
|
684
|
+
--color-card: hsl(var(--card));
|
|
685
|
+
--color-card-foreground: hsl(var(--card-foreground));
|
|
686
|
+
--color-popover: hsl(var(--popover));
|
|
687
|
+
--color-popover-foreground: hsl(var(--popover-foreground));
|
|
688
|
+
--color-primary: hsl(var(--primary));
|
|
689
|
+
--color-primary-foreground: hsl(var(--primary-foreground));
|
|
690
|
+
--color-secondary: hsl(var(--secondary));
|
|
691
|
+
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
|
692
|
+
--color-muted: hsl(var(--muted));
|
|
693
|
+
--color-muted-foreground: hsl(var(--muted-foreground));
|
|
694
|
+
--color-accent: hsl(var(--accent));
|
|
695
|
+
--color-accent-foreground: hsl(var(--accent-foreground));
|
|
696
|
+
--color-destructive: hsl(var(--destructive));
|
|
697
|
+
--color-border: hsl(var(--border));
|
|
698
|
+
--color-input: hsl(var(--input));
|
|
699
|
+
--color-ring: hsl(var(--ring));
|
|
700
|
+
--color-chart-1: hsl(var(--chart-1));
|
|
701
|
+
--color-chart-2: hsl(var(--chart-2));
|
|
702
|
+
--color-chart-3: hsl(var(--chart-3));
|
|
703
|
+
--color-chart-4: hsl(var(--chart-4));
|
|
704
|
+
--color-chart-5: hsl(var(--chart-5));
|
|
705
|
+
--color-sidebar: hsl(var(--sidebar));
|
|
706
|
+
--color-sidebar-foreground: hsl(var(--sidebar-foreground));
|
|
707
|
+
--color-sidebar-primary: hsl(var(--sidebar-primary));
|
|
708
|
+
--color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
|
|
709
|
+
--color-sidebar-accent: hsl(var(--sidebar-accent));
|
|
710
|
+
--color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
|
|
711
|
+
--color-sidebar-border: hsl(var(--sidebar-border));
|
|
712
|
+
--color-sidebar-ring: hsl(var(--sidebar-ring));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
:root {
|
|
716
|
+
--background: 0 0% 4%;
|
|
717
|
+
--foreground: 0 0% 93%;
|
|
718
|
+
--card: 0 0% 6%;
|
|
719
|
+
--card-foreground: 0 0% 93%;
|
|
720
|
+
--popover: 0 0% 6%;
|
|
721
|
+
--popover-foreground: 0 0% 93%;
|
|
722
|
+
--primary: 0 0% 93%;
|
|
723
|
+
--primary-foreground: 0 0% 6%;
|
|
724
|
+
--secondary: 0 0% 12%;
|
|
725
|
+
--secondary-foreground: 0 0% 93%;
|
|
726
|
+
--muted: 0 0% 12%;
|
|
727
|
+
--muted-foreground: 0 0% 55%;
|
|
728
|
+
--accent: 0 0% 12%;
|
|
729
|
+
--accent-foreground: 0 0% 93%;
|
|
730
|
+
--destructive: 0 62% 50%;
|
|
731
|
+
--border: 0 0% 14%;
|
|
732
|
+
--input: 0 0% 14%;
|
|
733
|
+
--ring: 0 0% 83%;
|
|
734
|
+
--chart-1: 220 70% 50%;
|
|
735
|
+
--chart-2: 160 60% 45%;
|
|
736
|
+
--chart-3: 30 80% 55%;
|
|
737
|
+
--chart-4: 280 65% 60%;
|
|
738
|
+
--chart-5: 340 75% 55%;
|
|
739
|
+
--sidebar: 0 0% 5%;
|
|
740
|
+
--sidebar-foreground: 0 0% 93%;
|
|
741
|
+
--sidebar-primary: 0 0% 93%;
|
|
742
|
+
--sidebar-primary-foreground: 0 0% 6%;
|
|
743
|
+
--sidebar-accent: 0 0% 12%;
|
|
744
|
+
--sidebar-accent-foreground: 0 0% 93%;
|
|
745
|
+
--sidebar-border: 0 0% 14%;
|
|
746
|
+
--sidebar-ring: 0 0% 83%;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
*,
|
|
750
|
+
*::before,
|
|
751
|
+
*::after {
|
|
752
|
+
box-sizing: border-box;
|
|
753
|
+
margin: 0;
|
|
754
|
+
padding: 0;
|
|
755
|
+
border-color: var(--color-border);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
html,
|
|
759
|
+
body {
|
|
760
|
+
height: 100%;
|
|
761
|
+
background: var(--color-background);
|
|
762
|
+
color: var(--color-foreground);
|
|
763
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
764
|
+
-webkit-font-smoothing: antialiased;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
#root {
|
|
768
|
+
height: 100%;
|
|
769
|
+
}
|
|
770
|
+
`;
|
|
771
|
+
}
|
|
772
|
+
return `@import "tailwindcss";
|
|
773
|
+
|
|
774
|
+
@theme {
|
|
775
|
+
--color-background: #0a0a0a;
|
|
776
|
+
--color-foreground: #ededed;
|
|
777
|
+
--color-muted: #888888;
|
|
778
|
+
--color-border: #222222;
|
|
779
|
+
--color-primary: #2563eb;
|
|
780
|
+
--color-accent: #a78bfa;
|
|
781
|
+
--font-sans: system-ui, -apple-system, sans-serif;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
*,
|
|
785
|
+
*::before,
|
|
786
|
+
*::after {
|
|
787
|
+
box-sizing: border-box;
|
|
788
|
+
margin: 0;
|
|
789
|
+
padding: 0;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
html,
|
|
793
|
+
body {
|
|
794
|
+
height: 100%;
|
|
795
|
+
background: var(--color-background);
|
|
796
|
+
color: var(--color-foreground);
|
|
797
|
+
font-family: var(--font-sans);
|
|
798
|
+
-webkit-font-smoothing: antialiased;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
#root {
|
|
802
|
+
height: 100%;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
a {
|
|
806
|
+
color: inherit;
|
|
807
|
+
text-decoration: none;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
a:hover {
|
|
811
|
+
text-decoration: underline;
|
|
812
|
+
}
|
|
813
|
+
`;
|
|
814
|
+
}
|
|
815
|
+
function generateCnUtil() {
|
|
816
|
+
return `import { type ClassValue, clsx } from "clsx";
|
|
817
|
+
import { twMerge } from "tailwind-merge";
|
|
818
|
+
|
|
819
|
+
export function cn(...inputs: ClassValue[]) {
|
|
820
|
+
return twMerge(clsx(inputs));
|
|
821
|
+
}
|
|
822
|
+
`;
|
|
823
|
+
}
|
|
824
|
+
function generateComponentsJson() {
|
|
825
|
+
return JSON.stringify({
|
|
826
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
827
|
+
style: "new-york",
|
|
828
|
+
rsc: false,
|
|
829
|
+
tsx: true,
|
|
830
|
+
tailwind: {
|
|
831
|
+
config: "",
|
|
832
|
+
css: "src/globals.css",
|
|
833
|
+
baseColor: "neutral",
|
|
834
|
+
cssVariables: true
|
|
835
|
+
},
|
|
836
|
+
aliases: {
|
|
837
|
+
components: "@/components",
|
|
838
|
+
utils: "@/lib/utils",
|
|
839
|
+
ui: "@/components/ui",
|
|
840
|
+
lib: "@/lib",
|
|
841
|
+
hooks: "@/hooks"
|
|
842
|
+
}
|
|
843
|
+
}, null, 2) + "\n";
|
|
844
|
+
}
|
|
845
|
+
function generateAppComponent(projectName, template) {
|
|
846
|
+
if (template === "blank") {
|
|
847
|
+
return `import React, { useState, useEffect } from "react";
|
|
848
|
+
|
|
849
|
+
export default function App() {
|
|
850
|
+
const [status, setStatus] = useState<string>("checking...");
|
|
851
|
+
|
|
852
|
+
useEffect(() => {
|
|
853
|
+
fetch("/api")
|
|
854
|
+
.then((res) => res.json())
|
|
855
|
+
.then((data) => setStatus(data.status || "ok"))
|
|
856
|
+
.catch(() => setStatus("error"));
|
|
857
|
+
}, []);
|
|
858
|
+
|
|
859
|
+
return (
|
|
860
|
+
<div className="flex flex-col items-center justify-center min-h-[calc(100vh-60px)] px-6 py-12">
|
|
861
|
+
<div className="text-center max-w-2xl w-full">
|
|
862
|
+
{/* Hero */}
|
|
863
|
+
<div className="relative mb-8">
|
|
864
|
+
<div className="absolute inset-0 blur-3xl opacity-20 bg-gradient-to-r from-blue-500 via-purple-500 to-blue-500 rounded-full" />
|
|
865
|
+
<div className="relative text-7xl mb-4">\u26A1</div>
|
|
544
866
|
</div>
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
<textarea id="ingestText" rows="6" placeholder="Paste text to ingest..." class="w-full bg-gray-900 border border-gray-700 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-emerald-500 resize-none"></textarea>
|
|
559
|
-
<button id="ingestBtn" onclick="ingestDoc()" class="w-full py-2.5 bg-emerald-600 hover:bg-emerald-500 disabled:bg-gray-700 disabled:text-gray-500 text-white text-sm font-medium rounded-lg transition-colors">Ingest</button>
|
|
560
|
-
<div id="ingestStatus" class="text-xs text-gray-500 hidden"></div>
|
|
561
|
-
<div class="mt-auto pt-3 border-t border-gray-800"><p class="text-xs text-gray-600">Documents are chunked, embedded, and stored for retrieval.</p></div>
|
|
867
|
+
<h1 className="text-5xl font-bold tracking-tight mb-3 bg-gradient-to-r from-white to-white/60 bg-clip-text text-transparent">
|
|
868
|
+
\${"\${projectName}"}
|
|
869
|
+
</h1>
|
|
870
|
+
<p className="text-muted text-lg mb-10">The AI-first full-stack framework</p>
|
|
871
|
+
|
|
872
|
+
{/* Status cards */}
|
|
873
|
+
<div className="flex gap-4 justify-center mb-10">
|
|
874
|
+
<div className="px-6 py-4 rounded-xl bg-white/5 border border-border backdrop-blur-sm">
|
|
875
|
+
<div className="text-xs text-muted mb-1 uppercase tracking-wider">Server</div>
|
|
876
|
+
<div className={\`text-sm font-medium \${status === "ok" ? "text-emerald-400" : "text-red-400"}\`}>
|
|
877
|
+
<span className={\`inline-block w-2 h-2 rounded-full mr-2 \${status === "ok" ? "bg-emerald-400 animate-pulse" : "bg-red-400"}\`} />
|
|
878
|
+
{status}
|
|
879
|
+
</div>
|
|
562
880
|
</div>
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
<h2 class="text-xl font-semibold text-white mb-2">Ask your documents</h2>
|
|
569
|
-
<p class="text-gray-500 text-sm">Ingest documents on the left, then ask questions here.</p>
|
|
881
|
+
<div className="px-6 py-4 rounded-xl bg-white/5 border border-border backdrop-blur-sm">
|
|
882
|
+
<div className="text-xs text-muted mb-1 uppercase tracking-wider">Frontend</div>
|
|
883
|
+
<div className="text-sm font-medium text-emerald-400">
|
|
884
|
+
<span className="inline-block w-2 h-2 rounded-full mr-2 bg-emerald-400 animate-pulse" />
|
|
885
|
+
React + Vite
|
|
570
886
|
</div>
|
|
571
887
|
</div>
|
|
572
|
-
<div
|
|
573
|
-
<
|
|
574
|
-
|
|
575
|
-
<button type="submit" id="ragSendBtn" class="px-5 py-3 bg-emerald-600 hover:bg-emerald-500 disabled:bg-gray-700 disabled:text-gray-500 text-white text-sm font-medium rounded-xl transition-colors">Ask</button>
|
|
576
|
-
</form>
|
|
888
|
+
<div className="px-6 py-4 rounded-xl bg-white/5 border border-border backdrop-blur-sm">
|
|
889
|
+
<div className="text-xs text-muted mb-1 uppercase tracking-wider">CSS</div>
|
|
890
|
+
<div className="text-sm font-medium text-sky-400">Tailwind v4</div>
|
|
577
891
|
</div>
|
|
578
892
|
</div>
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
893
|
+
|
|
894
|
+
{/* Get started */}
|
|
895
|
+
<div className="bg-white/[0.03] border border-border rounded-2xl p-8 text-left backdrop-blur-sm">
|
|
896
|
+
<h2 className="text-sm font-medium text-muted mb-6 uppercase tracking-wider">Get started</h2>
|
|
897
|
+
<div className="space-y-4">
|
|
898
|
+
<div className="flex items-start gap-4">
|
|
899
|
+
<div className="w-8 h-8 rounded-lg bg-purple-500/10 border border-purple-500/20 flex items-center justify-center text-purple-400 text-sm shrink-0">1</div>
|
|
900
|
+
<div>
|
|
901
|
+
<code className="text-purple-400 text-sm">src/app.tsx</code>
|
|
902
|
+
<p className="text-muted text-sm mt-1">Edit this file to build your UI</p>
|
|
903
|
+
</div>
|
|
904
|
+
</div>
|
|
905
|
+
<div className="flex items-start gap-4">
|
|
906
|
+
<div className="w-8 h-8 rounded-lg bg-blue-500/10 border border-blue-500/20 flex items-center justify-center text-blue-400 text-sm shrink-0">2</div>
|
|
907
|
+
<div>
|
|
908
|
+
<code className="text-blue-400 text-sm">api/</code>
|
|
909
|
+
<p className="text-muted text-sm mt-1">Add API routes here (file-based routing)</p>
|
|
910
|
+
</div>
|
|
911
|
+
</div>
|
|
912
|
+
<div className="flex items-start gap-4">
|
|
913
|
+
<div className="w-8 h-8 rounded-lg bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400 text-sm shrink-0">3</div>
|
|
914
|
+
<div>
|
|
915
|
+
<code className="text-emerald-400 text-sm">src/components/</code>
|
|
916
|
+
<p className="text-muted text-sm mt-1">Create React components with Tailwind CSS</p>
|
|
917
|
+
</div>
|
|
918
|
+
</div>
|
|
595
919
|
</div>
|
|
596
920
|
</div>
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
921
|
+
|
|
922
|
+
{/* Links */}
|
|
923
|
+
<div className="flex gap-6 justify-center mt-8">
|
|
924
|
+
<a href="https://github.com/codewithshail/voltx" target="_blank" rel="noopener noreferrer" className="text-sm text-muted hover:text-foreground transition-colors">
|
|
925
|
+
GitHub \u2192
|
|
926
|
+
</a>
|
|
927
|
+
<a href="https://voltx.co.in" target="_blank" rel="noopener noreferrer" className="text-sm text-muted hover:text-foreground transition-colors">
|
|
928
|
+
Docs \u2192
|
|
929
|
+
</a>
|
|
602
930
|
</div>
|
|
603
931
|
</div>
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
function removeThinking(){const t=document.getElementById("thinking");if(t)t.remove()}
|
|
609
|
-
document.getElementById("agentForm").addEventListener("submit",async e=>{e.preventDefault();const input=document.getElementById("agentInput"),text=input.value.trim();if(!text)return;addAgentMsg("user",text);input.value="";document.getElementById("agentSendBtn").disabled=true;addThinking();try{const res=await fetch("/api/agent",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:text})});removeThinking();if(!res.ok){const err=await res.json().catch(()=>({}));addAgentMsg("assistant","Error: "+(err.error||res.statusText));document.getElementById("agentSendBtn").disabled=false;return}const data=await res.json();if(data.steps&&data.steps.length>0){for(const s of data.steps){const name=s.tool||s.name||"tool";const inp=typeof s.input==="string"?s.input:JSON.stringify(s.input||{});const out=typeof s.output==="string"?s.output:JSON.stringify(s.output||{});addAgentMsg("assistant","\u{1F527} "+name+"("+inp+")\\n\u2192 "+out.slice(0,300),true)}}addAgentMsg("assistant",data.content||"No response.")}catch(err){removeThinking();addAgentMsg("assistant","Error: "+err.message)}document.getElementById("agentSendBtn").disabled=false;input.focus()});
|
|
610
|
-
document.getElementById("agentInput").focus();
|
|
611
|
-
</script>`;
|
|
612
|
-
}
|
|
613
|
-
if (typeof require !== "undefined" && require.main === module && process.argv[1]?.includes("create")) {
|
|
614
|
-
const projectName = process.argv[2];
|
|
615
|
-
if (!projectName) {
|
|
616
|
-
console.log("Usage: create-voltx-app <project-name> [--template chatbot] [--auth jwt]");
|
|
617
|
-
process.exit(1);
|
|
932
|
+
</div>
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
`;
|
|
618
936
|
}
|
|
619
|
-
const
|
|
620
|
-
const
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
937
|
+
const apiEndpoint = template === "agent-app" ? "/api/agent" : template === "rag-app" ? "/api/rag/query" : "/api/chat";
|
|
938
|
+
const isAgent = template === "agent-app";
|
|
939
|
+
const isRag = template === "rag-app";
|
|
940
|
+
let emptyStateTitle = "Start a conversation";
|
|
941
|
+
let emptyStateHint = "Type a message below to chat with AI";
|
|
942
|
+
let accentColor = "blue";
|
|
943
|
+
let inputPlaceholder = "Type a message...";
|
|
944
|
+
if (isAgent) {
|
|
945
|
+
emptyStateTitle = "Talk to your AI agent";
|
|
946
|
+
emptyStateHint = "The agent can use tools like Calculator and Date/Time";
|
|
947
|
+
accentColor = "purple";
|
|
948
|
+
inputPlaceholder = "Ask the agent anything...";
|
|
949
|
+
} else if (isRag) {
|
|
950
|
+
emptyStateTitle = "Ask your documents";
|
|
951
|
+
emptyStateHint = "Query your knowledge base \u2014 ingest docs via POST /api/rag/ingest";
|
|
952
|
+
accentColor = "emerald";
|
|
953
|
+
inputPlaceholder = "Ask a question about your documents...";
|
|
954
|
+
}
|
|
955
|
+
return `import React, { useState, useRef, useEffect, useCallback } from "react";
|
|
956
|
+
|
|
957
|
+
interface Message {
|
|
958
|
+
id: string;
|
|
959
|
+
role: "user" | "assistant";
|
|
960
|
+
content: string;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
export default function App() {
|
|
964
|
+
const [messages, setMessages] = useState<Message[]>([]);
|
|
965
|
+
const [input, setInput] = useState("");
|
|
966
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
967
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
968
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
969
|
+
|
|
970
|
+
useEffect(() => {
|
|
971
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
972
|
+
}, [messages]);
|
|
973
|
+
|
|
974
|
+
useEffect(() => {
|
|
975
|
+
inputRef.current?.focus();
|
|
976
|
+
}, []);
|
|
977
|
+
|
|
978
|
+
const sendMessage = useCallback(async () => {
|
|
979
|
+
const text = input.trim();
|
|
980
|
+
if (!text || isLoading) return;
|
|
981
|
+
|
|
982
|
+
const userMsg: Message = { id: crypto.randomUUID(), role: "user", content: text };
|
|
983
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
984
|
+
setInput("");
|
|
985
|
+
setIsLoading(true);
|
|
986
|
+
|
|
987
|
+
const assistantMsg: Message = { id: crypto.randomUUID(), role: "assistant", content: "" };
|
|
988
|
+
setMessages((prev) => [...prev, assistantMsg]);
|
|
989
|
+
|
|
990
|
+
try {${isAgent ? `
|
|
991
|
+
const res = await fetch("${apiEndpoint}", {
|
|
992
|
+
method: "POST",
|
|
993
|
+
headers: { "Content-Type": "application/json" },
|
|
994
|
+
body: JSON.stringify({ input: text }),
|
|
995
|
+
});
|
|
996
|
+
const data = await res.json();
|
|
997
|
+
setMessages((prev) =>
|
|
998
|
+
prev.map((m) => m.id === assistantMsg.id ? { ...m, content: data.content || "No response" } : m)
|
|
999
|
+
);` : `
|
|
1000
|
+
const res = await fetch("${apiEndpoint}", {
|
|
1001
|
+
method: "POST",
|
|
1002
|
+
headers: { "Content-Type": "application/json" },
|
|
1003
|
+
body: JSON.stringify({${isRag ? ` question: text ` : ` messages: [...messages, { role: "user", content: text }] `}}),
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
if (!res.body) throw new Error("No response body");
|
|
1007
|
+
|
|
1008
|
+
const reader = res.body.getReader();
|
|
1009
|
+
const decoder = new TextDecoder();
|
|
1010
|
+
let buffer = "";
|
|
1011
|
+
let fullContent = "";
|
|
1012
|
+
|
|
1013
|
+
while (true) {
|
|
1014
|
+
const { done, value } = await reader.read();
|
|
1015
|
+
if (done) break;
|
|
1016
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1017
|
+
const lines = buffer.split("\\n");
|
|
1018
|
+
buffer = lines.pop() ?? "";
|
|
1019
|
+
|
|
1020
|
+
for (const line of lines) {
|
|
1021
|
+
if (!line.startsWith("data: ")) continue;
|
|
1022
|
+
const data = line.slice(6);
|
|
1023
|
+
if (data === "[DONE]") break;
|
|
1024
|
+
try {
|
|
1025
|
+
const parsed = JSON.parse(data);
|
|
1026
|
+
const chunk = parsed.textDelta ?? parsed.content ?? parsed.choices?.[0]?.delta?.content ?? "";
|
|
1027
|
+
if (chunk) {
|
|
1028
|
+
fullContent += chunk;
|
|
1029
|
+
setMessages((prev) =>
|
|
1030
|
+
prev.map((m) => m.id === assistantMsg.id ? { ...m, content: fullContent } : m)
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
} catch {}
|
|
1034
|
+
}
|
|
1035
|
+
}`}
|
|
1036
|
+
} catch (err) {
|
|
1037
|
+
setMessages((prev) =>
|
|
1038
|
+
prev.map((m) => m.id === assistantMsg.id ? { ...m, content: "Error: " + (err instanceof Error ? err.message : String(err)) } : m)
|
|
1039
|
+
);
|
|
1040
|
+
} finally {
|
|
1041
|
+
setIsLoading(false);
|
|
1042
|
+
}
|
|
1043
|
+
}, [input, isLoading, messages]);
|
|
1044
|
+
|
|
1045
|
+
return (
|
|
1046
|
+
<div className="h-[calc(100vh-60px)] flex flex-col">
|
|
1047
|
+
<main className="flex-1 overflow-y-auto px-4 py-6">
|
|
1048
|
+
<div className="max-w-3xl mx-auto">
|
|
1049
|
+
{messages.length === 0 && (
|
|
1050
|
+
<div className="flex flex-col items-center justify-center h-[60vh] text-center">
|
|
1051
|
+
<div className="relative mb-6">
|
|
1052
|
+
<div className="absolute inset-0 blur-2xl opacity-20 bg-${accentColor}-500 rounded-full" />
|
|
1053
|
+
<div className="relative text-5xl">\u26A1</div>
|
|
1054
|
+
</div>
|
|
1055
|
+
<h2 className="text-2xl font-semibold mb-2">${emptyStateTitle}</h2>
|
|
1056
|
+
<p className="text-muted text-sm max-w-md">${emptyStateHint}</p>
|
|
1057
|
+
<div className="flex gap-2 mt-6">
|
|
1058
|
+
${isAgent ? `<button onClick={() => { setInput("What is 42 * 17?"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
|
|
1059
|
+
What is 42 \xD7 17?
|
|
1060
|
+
</button>
|
|
1061
|
+
<button onClick={() => { setInput("What day is it today?"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
|
|
1062
|
+
What day is it?
|
|
1063
|
+
</button>` : isRag ? `<button onClick={() => { setInput("Summarize the main topics"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
|
|
1064
|
+
Summarize main topics
|
|
1065
|
+
</button>
|
|
1066
|
+
<button onClick={() => { setInput("What are the key findings?"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
|
|
1067
|
+
Key findings
|
|
1068
|
+
</button>` : `<button onClick={() => { setInput("Hello! What can you do?"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
|
|
1069
|
+
Hello! What can you do?
|
|
1070
|
+
</button>
|
|
1071
|
+
<button onClick={() => { setInput("Tell me a fun fact"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
|
|
1072
|
+
Tell me a fun fact
|
|
1073
|
+
</button>`}
|
|
1074
|
+
</div>
|
|
1075
|
+
</div>
|
|
1076
|
+
)}
|
|
1077
|
+
{messages.map((msg) => (
|
|
1078
|
+
<div key={msg.id} className={\`mb-6 flex \${msg.role === "user" ? "justify-end" : "justify-start"}\`}>
|
|
1079
|
+
<div className={\`flex items-start gap-3 max-w-[80%] \${msg.role === "user" ? "flex-row-reverse" : ""}\`}>
|
|
1080
|
+
<div className={\`w-8 h-8 rounded-full flex items-center justify-center text-sm shrink-0 \${
|
|
1081
|
+
msg.role === "user" ? "bg-${accentColor}-500 text-white" : "bg-white/10 text-muted"
|
|
1082
|
+
}\`}>
|
|
1083
|
+
{msg.role === "user" ? "Y" : "\u26A1"}
|
|
1084
|
+
</div>
|
|
1085
|
+
<div className={\`px-4 py-3 rounded-2xl whitespace-pre-wrap leading-relaxed text-sm \${
|
|
1086
|
+
msg.role === "user"
|
|
1087
|
+
? "bg-${accentColor}-500 text-white rounded-br-md"
|
|
1088
|
+
: "bg-white/[0.05] border border-border rounded-bl-md"
|
|
1089
|
+
}\`}>
|
|
1090
|
+
{msg.content || (isLoading && msg.role === "assistant" ? (
|
|
1091
|
+
<span className="flex gap-1">
|
|
1092
|
+
<span className="w-2 h-2 bg-muted rounded-full animate-bounce [animation-delay:0ms]" />
|
|
1093
|
+
<span className="w-2 h-2 bg-muted rounded-full animate-bounce [animation-delay:150ms]" />
|
|
1094
|
+
<span className="w-2 h-2 bg-muted rounded-full animate-bounce [animation-delay:300ms]" />
|
|
1095
|
+
</span>
|
|
1096
|
+
) : "")}
|
|
1097
|
+
</div>
|
|
1098
|
+
</div>
|
|
1099
|
+
</div>
|
|
1100
|
+
))}
|
|
1101
|
+
<div ref={messagesEndRef} />
|
|
1102
|
+
</div>
|
|
1103
|
+
</main>
|
|
1104
|
+
<footer className="border-t border-border px-4 py-4">
|
|
1105
|
+
<form onSubmit={(e) => { e.preventDefault(); sendMessage(); }} className="max-w-3xl mx-auto flex gap-3">
|
|
1106
|
+
<input
|
|
1107
|
+
ref={inputRef}
|
|
1108
|
+
value={input}
|
|
1109
|
+
onChange={(e) => setInput(e.target.value)}
|
|
1110
|
+
placeholder="${inputPlaceholder}"
|
|
1111
|
+
disabled={isLoading}
|
|
1112
|
+
className="flex-1 px-4 py-3 rounded-xl bg-white/[0.05] border border-border text-foreground placeholder:text-muted/50 outline-none focus:border-${accentColor}-500/50 focus:ring-1 focus:ring-${accentColor}-500/25 transition-all disabled:opacity-50"
|
|
1113
|
+
/>
|
|
1114
|
+
<button
|
|
1115
|
+
type="submit"
|
|
1116
|
+
disabled={isLoading || !input.trim()}
|
|
1117
|
+
className="px-6 py-3 rounded-xl font-medium text-sm transition-all disabled:opacity-30 disabled:cursor-not-allowed bg-${accentColor}-500 hover:bg-${accentColor}-400 text-white cursor-pointer"
|
|
1118
|
+
>
|
|
1119
|
+
{isLoading ? (
|
|
1120
|
+
<svg className="w-5 h-5 animate-spin" viewBox="0 0 24 24" fill="none">
|
|
1121
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
1122
|
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
|
1123
|
+
</svg>
|
|
1124
|
+
) : "Send"}
|
|
1125
|
+
</button>
|
|
1126
|
+
</form>
|
|
1127
|
+
</footer>
|
|
1128
|
+
</div>
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
`;
|
|
1132
|
+
}
|
|
1133
|
+
function generateFaviconSVG() {
|
|
1134
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
1135
|
+
<rect width="32" height="32" rx="6" fill="#0a0a0a"/>
|
|
1136
|
+
<path d="M18.5 4L8 18h7l-1.5 10L24 14h-7l1.5-10z" fill="#facc15" stroke="#facc15" stroke-width="0.5" stroke-linejoin="round"/>
|
|
1137
|
+
</svg>
|
|
1138
|
+
`;
|
|
1139
|
+
}
|
|
1140
|
+
function generateRobotsTxt() {
|
|
1141
|
+
return `# https://www.robotstxt.org/robotstxt.html
|
|
1142
|
+
User-agent: *
|
|
1143
|
+
Allow: /
|
|
1144
|
+
|
|
1145
|
+
Sitemap: /sitemap.xml
|
|
1146
|
+
`;
|
|
1147
|
+
}
|
|
1148
|
+
function generateWebManifest(projectName) {
|
|
1149
|
+
return JSON.stringify({
|
|
1150
|
+
name: projectName,
|
|
1151
|
+
short_name: projectName,
|
|
1152
|
+
icons: [
|
|
1153
|
+
{ src: "/favicon.svg", sizes: "any", type: "image/svg+xml" }
|
|
1154
|
+
],
|
|
1155
|
+
theme_color: "#0a0a0a",
|
|
1156
|
+
background_color: "#0a0a0a",
|
|
1157
|
+
display: "standalone"
|
|
1158
|
+
}, null, 2) + "\n";
|
|
627
1159
|
}
|
|
628
1160
|
// Annotate the CommonJS export names for ESM import in node:
|
|
629
1161
|
0 && (module.exports = {
|