create-supyagent-app 0.1.6 → 0.1.8
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/dist/index.js +290 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/api-route/route.ts.tmpl +4 -4
- package/templates/base/gitignore +33 -0
- package/templates/base/postcss.config.js +1 -1
- package/templates/base/src/app/chat/[id]/page.tsx +1 -1
- package/templates/base/src/app/globals.css +83 -6
- package/templates/base/src/app/layout.tsx +1 -1
- package/templates/base/src/components/chat-input.tsx +34 -15
- package/templates/base/src/components/chat-message.tsx +63 -18
- package/templates/base/src/components/chat-sidebar.tsx +111 -27
- package/templates/base/src/components/chat.tsx +65 -10
- package/templates/package-json/package.json.tmpl +6 -4
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ var AI_PROVIDERS = {
|
|
|
23
23
|
package: "@ai-sdk/anthropic",
|
|
24
24
|
packageVersion: "^3.0.0",
|
|
25
25
|
import: `import { anthropic } from '@ai-sdk/anthropic'`,
|
|
26
|
-
model: `anthropic('claude-sonnet-4-
|
|
26
|
+
model: `anthropic('claude-sonnet-4-6-20250620')`,
|
|
27
27
|
envKey: "ANTHROPIC_API_KEY"
|
|
28
28
|
},
|
|
29
29
|
openai: {
|
|
@@ -39,7 +39,7 @@ var AI_PROVIDERS = {
|
|
|
39
39
|
package: "@openrouter/ai-sdk-provider",
|
|
40
40
|
packageVersion: "^2.2.3",
|
|
41
41
|
import: `import { createOpenRouter } from '@openrouter/ai-sdk-provider'`,
|
|
42
|
-
model: `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('anthropic/claude-sonnet-4
|
|
42
|
+
model: `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('anthropic/claude-sonnet-4.6')`,
|
|
43
43
|
envKey: "OPENROUTER_API_KEY"
|
|
44
44
|
}
|
|
45
45
|
};
|
|
@@ -55,9 +55,21 @@ var DB_CONFIGS = {
|
|
|
55
55
|
url: "postgresql://user:password@localhost:5432/mydb"
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
|
+
function buildModelExpression(provider, customModelId) {
|
|
59
|
+
const config = AI_PROVIDERS[provider];
|
|
60
|
+
if (!customModelId) return config.model;
|
|
61
|
+
switch (provider) {
|
|
62
|
+
case "anthropic":
|
|
63
|
+
return `anthropic('${customModelId}')`;
|
|
64
|
+
case "openai":
|
|
65
|
+
return `openai('${customModelId}')`;
|
|
66
|
+
case "openrouter":
|
|
67
|
+
return `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('${customModelId}')`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
58
70
|
|
|
59
71
|
// src/prompts.ts
|
|
60
|
-
async function runPrompts(argName) {
|
|
72
|
+
async function runPrompts(argName, options) {
|
|
61
73
|
p.intro(pc.bgCyan(pc.black(" Create Supyagent App ")));
|
|
62
74
|
const projectName = argName || await p.text({
|
|
63
75
|
message: "Project name",
|
|
@@ -79,28 +91,40 @@ async function runPrompts(argName) {
|
|
|
79
91
|
p.cancel(`Directory "${projectName}" already exists.`);
|
|
80
92
|
return null;
|
|
81
93
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
options
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
let aiProvider;
|
|
95
|
+
if (options?.aiProvider) {
|
|
96
|
+
aiProvider = options.aiProvider;
|
|
97
|
+
} else {
|
|
98
|
+
const selected = await p.select({
|
|
99
|
+
message: "AI provider",
|
|
100
|
+
options: [
|
|
101
|
+
{ value: "anthropic", label: AI_PROVIDERS.anthropic.label },
|
|
102
|
+
{ value: "openai", label: AI_PROVIDERS.openai.label },
|
|
103
|
+
{ value: "openrouter", label: AI_PROVIDERS.openrouter.label }
|
|
104
|
+
]
|
|
105
|
+
});
|
|
106
|
+
if (p.isCancel(selected)) {
|
|
107
|
+
p.cancel("Cancelled.");
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
aiProvider = selected;
|
|
93
111
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
options
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
let database;
|
|
113
|
+
if (options?.database || options?.skipDatabase) {
|
|
114
|
+
database = options?.database ?? "sqlite";
|
|
115
|
+
} else {
|
|
116
|
+
const selected = await p.select({
|
|
117
|
+
message: "Database",
|
|
118
|
+
options: [
|
|
119
|
+
{ value: "sqlite", label: DB_CONFIGS.sqlite.label },
|
|
120
|
+
{ value: "postgres", label: DB_CONFIGS.postgres.label }
|
|
121
|
+
]
|
|
122
|
+
});
|
|
123
|
+
if (p.isCancel(selected)) {
|
|
124
|
+
p.cancel("Cancelled.");
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
database = selected;
|
|
104
128
|
}
|
|
105
129
|
return { projectName, projectPath, aiProvider, database };
|
|
106
130
|
}
|
|
@@ -137,7 +161,7 @@ function scaffoldProject(config) {
|
|
|
137
161
|
aiProviderPackage: ai.package,
|
|
138
162
|
aiProviderVersion: ai.packageVersion,
|
|
139
163
|
aiProviderImport: ai.import,
|
|
140
|
-
aiModel:
|
|
164
|
+
aiModel: buildModelExpression(aiProvider, config.model),
|
|
141
165
|
aiProviderEnvKey: ai.envKey,
|
|
142
166
|
dbProvider: db.provider,
|
|
143
167
|
dbUrl: db.url
|
|
@@ -147,7 +171,7 @@ function scaffoldProject(config) {
|
|
|
147
171
|
writeProject(projectPath, "tsconfig.json", readTemplate("base/tsconfig.json"));
|
|
148
172
|
writeProject(projectPath, "tailwind.config.ts", readTemplate("base/tailwind.config.ts"));
|
|
149
173
|
writeProject(projectPath, "postcss.config.js", readTemplate("base/postcss.config.js"));
|
|
150
|
-
writeProject(projectPath, ".gitignore", readTemplate("base
|
|
174
|
+
writeProject(projectPath, ".gitignore", readTemplate("base/gitignore"));
|
|
151
175
|
writeProject(projectPath, ".npmrc", readTemplate("base/.npmrc"));
|
|
152
176
|
writeProject(projectPath, "README.md", applyTemplate(readTemplate("base/README.md.tmpl"), vars));
|
|
153
177
|
writeProject(projectPath, "src/app/layout.tsx", readTemplate("base/src/app/layout.tsx"));
|
|
@@ -195,6 +219,58 @@ async function installDeps(projectPath) {
|
|
|
195
219
|
});
|
|
196
220
|
}
|
|
197
221
|
|
|
222
|
+
// src/quickstart.ts
|
|
223
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
224
|
+
import { join as join2 } from "path";
|
|
225
|
+
import { execSync, spawn as nodeSpawn } from "child_process";
|
|
226
|
+
import { detectPackageManager as detectPackageManager2 } from "nypm";
|
|
227
|
+
function writeEnvLocal(config) {
|
|
228
|
+
const { projectPath, aiProvider, apiKeys } = config;
|
|
229
|
+
const ai = AI_PROVIDERS[aiProvider];
|
|
230
|
+
const lines = [
|
|
231
|
+
"# Supyagent \u2014 Get your API key at https://app.supyagent.com",
|
|
232
|
+
`SUPYAGENT_API_KEY=${apiKeys?.supyagent ?? ""}`,
|
|
233
|
+
"",
|
|
234
|
+
"# AI Provider",
|
|
235
|
+
`${ai.envKey}=${apiKeys?.provider ?? ""}`,
|
|
236
|
+
"",
|
|
237
|
+
"# Database",
|
|
238
|
+
`DATABASE_URL="file:./dev.db"`,
|
|
239
|
+
""
|
|
240
|
+
];
|
|
241
|
+
writeFileSync2(join2(projectPath, ".env.local"), lines.join("\n"), "utf-8");
|
|
242
|
+
}
|
|
243
|
+
async function runDbSetup(projectPath) {
|
|
244
|
+
const pm = await detectPackageManager2(projectPath);
|
|
245
|
+
const cmd = pm?.name ?? "pnpm";
|
|
246
|
+
execSync(`${cmd} run db:setup`, {
|
|
247
|
+
cwd: projectPath,
|
|
248
|
+
stdio: "inherit",
|
|
249
|
+
env: { ...process.env, DATABASE_URL: "file:./dev.db" }
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async function runDevServer(projectPath) {
|
|
253
|
+
const pm = await detectPackageManager2(projectPath);
|
|
254
|
+
const cmd = pm?.name ?? "pnpm";
|
|
255
|
+
const child = nodeSpawn(cmd, ["dev"], {
|
|
256
|
+
cwd: projectPath,
|
|
257
|
+
stdio: "inherit"
|
|
258
|
+
});
|
|
259
|
+
const forward = (signal) => {
|
|
260
|
+
child.kill(signal);
|
|
261
|
+
};
|
|
262
|
+
process.on("SIGINT", forward);
|
|
263
|
+
process.on("SIGTERM", forward);
|
|
264
|
+
return new Promise((_, reject) => {
|
|
265
|
+
child.on("close", (code) => {
|
|
266
|
+
process.off("SIGINT", forward);
|
|
267
|
+
process.off("SIGTERM", forward);
|
|
268
|
+
process.exit(code ?? 0);
|
|
269
|
+
});
|
|
270
|
+
child.on("error", reject);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
198
274
|
// src/index.ts
|
|
199
275
|
function parseArgs() {
|
|
200
276
|
const args = process.argv.slice(2);
|
|
@@ -202,64 +278,131 @@ function parseArgs() {
|
|
|
202
278
|
let projectName;
|
|
203
279
|
for (let i = 0; i < args.length; i++) {
|
|
204
280
|
const arg = args[i];
|
|
205
|
-
|
|
281
|
+
const next = args[i + 1];
|
|
282
|
+
const hasValue = next && !next.startsWith("-");
|
|
283
|
+
if (arg === "--provider" && hasValue) {
|
|
206
284
|
result.aiProvider = args[++i];
|
|
207
|
-
} else if (arg === "--db" &&
|
|
285
|
+
} else if (arg === "--db" && hasValue) {
|
|
208
286
|
result.database = args[++i];
|
|
287
|
+
} else if (arg === "--model" && hasValue) {
|
|
288
|
+
result.model = args[++i];
|
|
289
|
+
} else if (arg === "--quickstart") {
|
|
290
|
+
result.quickstart = true;
|
|
209
291
|
} else if (arg === "--skip-install") {
|
|
210
292
|
result.skipInstall = true;
|
|
293
|
+
} else if (arg === "--supyagent-api-key" && hasValue) {
|
|
294
|
+
result.supyagentApiKey = args[++i];
|
|
295
|
+
} else if (arg === "--anthropic-api-key" && hasValue) {
|
|
296
|
+
result.anthropicApiKey = args[++i];
|
|
297
|
+
} else if (arg === "--openai-api-key" && hasValue) {
|
|
298
|
+
result.openaiApiKey = args[++i];
|
|
299
|
+
} else if (arg === "--openrouter-api-key" && hasValue) {
|
|
300
|
+
result.openrouterApiKey = args[++i];
|
|
211
301
|
} else if (!arg.startsWith("-")) {
|
|
212
302
|
projectName = arg;
|
|
213
303
|
}
|
|
214
304
|
}
|
|
215
305
|
return {
|
|
306
|
+
...result,
|
|
216
307
|
projectName,
|
|
217
|
-
projectPath: projectName ? resolveProjectPath(projectName) : void 0
|
|
218
|
-
aiProvider: result.aiProvider,
|
|
219
|
-
database: result.database,
|
|
220
|
-
skipInstall: result.skipInstall
|
|
308
|
+
projectPath: projectName ? resolveProjectPath(projectName) : void 0
|
|
221
309
|
};
|
|
222
310
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
if (!config) {
|
|
311
|
+
var ENV_KEY_MAP = {
|
|
312
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
313
|
+
openai: "OPENAI_API_KEY",
|
|
314
|
+
openrouter: "OPENROUTER_API_KEY"
|
|
315
|
+
};
|
|
316
|
+
var CLI_KEY_MAP = {
|
|
317
|
+
anthropic: "anthropicApiKey",
|
|
318
|
+
openai: "openaiApiKey",
|
|
319
|
+
openrouter: "openrouterApiKey"
|
|
320
|
+
};
|
|
321
|
+
async function promptForKey(name) {
|
|
322
|
+
const value = await p2.password({ message: `Enter your ${name}` });
|
|
323
|
+
if (p2.isCancel(value)) {
|
|
324
|
+
p2.cancel("Cancelled.");
|
|
239
325
|
process.exit(1);
|
|
240
326
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
327
|
+
return value;
|
|
328
|
+
}
|
|
329
|
+
async function resolveApiKeys(provider, parsed) {
|
|
330
|
+
const envKey = ENV_KEY_MAP[provider];
|
|
331
|
+
const cliFlag = CLI_KEY_MAP[provider];
|
|
332
|
+
const supyagent = parsed.supyagentApiKey ?? process.env.SUPYAGENT_API_KEY ?? await promptForKey("SUPYAGENT_API_KEY");
|
|
333
|
+
const providerKey = parsed[cliFlag] ?? process.env[envKey] ?? await promptForKey(envKey);
|
|
334
|
+
return { supyagent, provider: providerKey };
|
|
335
|
+
}
|
|
336
|
+
async function main() {
|
|
337
|
+
const parsed = parseArgs();
|
|
338
|
+
if (parsed.quickstart) {
|
|
339
|
+
if (parsed.database === "postgres") {
|
|
340
|
+
p2.log.warn(
|
|
341
|
+
pc2.yellow("--quickstart requires SQLite \u2014 ignoring --db postgres")
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
if (parsed.skipInstall) {
|
|
345
|
+
p2.log.warn(
|
|
346
|
+
pc2.yellow(
|
|
347
|
+
"--quickstart needs dependencies installed \u2014 ignoring --skip-install"
|
|
348
|
+
)
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
p2.intro(pc2.bgCyan(pc2.black(" Create Supyagent App \u2014 Quickstart ")));
|
|
352
|
+
let projectName = parsed.projectName;
|
|
353
|
+
if (!projectName) {
|
|
354
|
+
const name = await p2.text({
|
|
355
|
+
message: "Project name",
|
|
356
|
+
placeholder: "my-supyagent-app",
|
|
357
|
+
defaultValue: "my-supyagent-app",
|
|
358
|
+
validate(value) {
|
|
359
|
+
if (!value) return "Project name is required";
|
|
360
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {
|
|
361
|
+
return "Invalid project name (lowercase, alphanumeric, hyphens, dots)";
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
if (p2.isCancel(name)) {
|
|
366
|
+
p2.cancel("Cancelled.");
|
|
367
|
+
process.exit(1);
|
|
251
368
|
}
|
|
369
|
+
projectName = name;
|
|
252
370
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
371
|
+
const projectPath = resolveProjectPath(projectName);
|
|
372
|
+
if (projectExists(projectPath)) {
|
|
373
|
+
p2.cancel(`Directory "${projectName}" already exists.`);
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
let aiProvider = parsed.aiProvider;
|
|
377
|
+
if (!aiProvider) {
|
|
378
|
+
const selected = await p2.select({
|
|
379
|
+
message: "AI provider",
|
|
380
|
+
options: [
|
|
381
|
+
{ value: "anthropic", label: AI_PROVIDERS.anthropic.label },
|
|
382
|
+
{ value: "openai", label: AI_PROVIDERS.openai.label },
|
|
383
|
+
{ value: "openrouter", label: AI_PROVIDERS.openrouter.label }
|
|
384
|
+
]
|
|
385
|
+
});
|
|
386
|
+
if (p2.isCancel(selected)) {
|
|
387
|
+
p2.cancel("Cancelled.");
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
aiProvider = selected;
|
|
391
|
+
}
|
|
392
|
+
const apiKeys = await resolveApiKeys(aiProvider, parsed);
|
|
393
|
+
const config = {
|
|
394
|
+
projectName,
|
|
395
|
+
projectPath,
|
|
396
|
+
aiProvider,
|
|
397
|
+
database: "sqlite",
|
|
398
|
+
model: parsed.model,
|
|
399
|
+
quickstart: true,
|
|
400
|
+
apiKeys
|
|
401
|
+
};
|
|
260
402
|
const s = p2.spinner();
|
|
261
403
|
s.start("Scaffolding project...");
|
|
262
404
|
scaffoldProject(config);
|
|
405
|
+
writeEnvLocal(config);
|
|
263
406
|
s.stop("Scaffolded project");
|
|
264
407
|
s.start("Installing dependencies...");
|
|
265
408
|
try {
|
|
@@ -267,17 +410,89 @@ Next steps:
|
|
|
267
410
|
s.stop("Installed dependencies");
|
|
268
411
|
} catch {
|
|
269
412
|
s.stop("Failed to install dependencies \u2014 run install manually");
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
s.start("Setting up database...");
|
|
416
|
+
try {
|
|
417
|
+
await runDbSetup(config.projectPath);
|
|
418
|
+
s.stop("Database ready");
|
|
419
|
+
} catch (err) {
|
|
420
|
+
s.stop("Database setup failed");
|
|
421
|
+
p2.log.warn(
|
|
422
|
+
`Run ${pc2.cyan(`cd ${projectName} && pnpm db:setup`)} manually`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
p2.log.info(pc2.green("Starting dev server..."));
|
|
426
|
+
await runDevServer(config.projectPath);
|
|
427
|
+
} else {
|
|
428
|
+
const isNonInteractive = parsed.projectName && parsed.aiProvider && parsed.database;
|
|
429
|
+
let config;
|
|
430
|
+
if (isNonInteractive) {
|
|
431
|
+
config = {
|
|
432
|
+
projectName: parsed.projectName,
|
|
433
|
+
projectPath: parsed.projectPath,
|
|
434
|
+
aiProvider: parsed.aiProvider,
|
|
435
|
+
database: parsed.database,
|
|
436
|
+
model: parsed.model
|
|
437
|
+
};
|
|
438
|
+
console.log(`Creating ${config.projectName}...`);
|
|
439
|
+
} else {
|
|
440
|
+
config = await runPrompts(parsed.projectName, {
|
|
441
|
+
aiProvider: parsed.aiProvider,
|
|
442
|
+
database: parsed.database
|
|
443
|
+
});
|
|
444
|
+
if (config && parsed.model) {
|
|
445
|
+
config.model = parsed.model;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (!config) {
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
451
|
+
if (isNonInteractive) {
|
|
452
|
+
scaffoldProject(config);
|
|
453
|
+
console.log("Scaffolded project");
|
|
454
|
+
if (!parsed.skipInstall) {
|
|
455
|
+
console.log("Installing dependencies...");
|
|
456
|
+
try {
|
|
457
|
+
await installDeps(config.projectPath);
|
|
458
|
+
console.log("Installed dependencies");
|
|
459
|
+
} catch {
|
|
460
|
+
console.log(
|
|
461
|
+
"Failed to install dependencies \u2014 run install manually"
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
console.log(
|
|
466
|
+
`
|
|
467
|
+
Next steps:
|
|
468
|
+
cd ${config.projectName}
|
|
469
|
+
cp .env.example .env.local
|
|
470
|
+
pnpm db:setup
|
|
471
|
+
pnpm dev`
|
|
472
|
+
);
|
|
473
|
+
} else {
|
|
474
|
+
const s = p2.spinner();
|
|
475
|
+
s.start("Scaffolding project...");
|
|
476
|
+
scaffoldProject(config);
|
|
477
|
+
s.stop("Scaffolded project");
|
|
478
|
+
s.start("Installing dependencies...");
|
|
479
|
+
try {
|
|
480
|
+
await installDeps(config.projectPath);
|
|
481
|
+
s.stop("Installed dependencies");
|
|
482
|
+
} catch {
|
|
483
|
+
s.stop("Failed to install dependencies \u2014 run install manually");
|
|
484
|
+
}
|
|
485
|
+
p2.note(
|
|
486
|
+
[
|
|
487
|
+
`cd ${config.projectName}`,
|
|
488
|
+
`cp .env.example .env.local ${pc2.dim("# Add your API keys")}`,
|
|
489
|
+
`pnpm db:setup ${pc2.dim("# Initialize database")}`,
|
|
490
|
+
`pnpm dev ${pc2.dim("# Start development server")}`
|
|
491
|
+
].join("\n"),
|
|
492
|
+
"Next steps"
|
|
493
|
+
);
|
|
494
|
+
p2.outro(pc2.green("Done!"));
|
|
270
495
|
}
|
|
271
|
-
p2.note(
|
|
272
|
-
[
|
|
273
|
-
`cd ${config.projectName}`,
|
|
274
|
-
`cp .env.example .env.local ${pc2.dim("# Add your API keys")}`,
|
|
275
|
-
`pnpm db:setup ${pc2.dim("# Initialize database")}`,
|
|
276
|
-
`pnpm dev ${pc2.dim("# Start development server")}`
|
|
277
|
-
].join("\n"),
|
|
278
|
-
"Next steps"
|
|
279
|
-
);
|
|
280
|
-
p2.outro(pc2.green("Done!"));
|
|
281
496
|
}
|
|
282
497
|
}
|
|
283
498
|
main().catch(console.error);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/prompts.ts","../src/utils.ts","../src/scaffold.ts","../src/template.ts","../src/post-install.ts"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { runPrompts } from \"./prompts.js\";\nimport { scaffoldProject } from \"./scaffold.js\";\nimport { installDeps } from \"./post-install.js\";\nimport { resolveProjectPath } from \"./utils.js\";\nimport type { ProjectConfig } from \"./utils.js\";\n\nfunction parseArgs(): Partial<ProjectConfig> & { skipInstall?: boolean } {\n const args = process.argv.slice(2);\n const result: Record<string, string | boolean> = {};\n let projectName: string | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === \"--provider\" && args[i + 1]) {\n result.aiProvider = args[++i];\n } else if (arg === \"--db\" && args[i + 1]) {\n result.database = args[++i];\n } else if (arg === \"--skip-install\") {\n result.skipInstall = true;\n } else if (!arg.startsWith(\"-\")) {\n projectName = arg;\n }\n }\n\n return {\n projectName,\n projectPath: projectName ? resolveProjectPath(projectName) : undefined,\n aiProvider: result.aiProvider as ProjectConfig[\"aiProvider\"],\n database: result.database as ProjectConfig[\"database\"],\n skipInstall: result.skipInstall as boolean | undefined,\n };\n}\n\nasync function main() {\n const parsed = parseArgs();\n\n // If all required args are provided, skip interactive prompts\n const isNonInteractive =\n parsed.projectName && parsed.aiProvider && parsed.database;\n\n let config: ProjectConfig | null;\n\n if (isNonInteractive) {\n config = {\n projectName: parsed.projectName!,\n projectPath: parsed.projectPath!,\n aiProvider: parsed.aiProvider!,\n database: parsed.database!,\n };\n console.log(`Creating ${config.projectName}...`);\n } else {\n config = await runPrompts(parsed.projectName);\n }\n\n if (!config) {\n process.exit(1);\n }\n\n if (isNonInteractive) {\n scaffoldProject(config);\n console.log(\"Scaffolded project\");\n\n if (!parsed.skipInstall) {\n console.log(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n console.log(\"Installed dependencies\");\n } catch {\n console.log(\"Failed to install dependencies — run install manually\");\n }\n }\n\n console.log(`\\nNext steps:\\n cd ${config.projectName}\\n cp .env.example .env.local\\n pnpm db:setup\\n pnpm dev`);\n } else {\n const s = p.spinner();\n\n s.start(\"Scaffolding project...\");\n scaffoldProject(config);\n s.stop(\"Scaffolded project\");\n\n s.start(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n s.stop(\"Installed dependencies\");\n } catch {\n s.stop(\"Failed to install dependencies — run install manually\");\n }\n\n p.note(\n [\n `cd ${config.projectName}`,\n `cp .env.example .env.local ${pc.dim(\"# Add your API keys\")}`,\n `pnpm db:setup ${pc.dim(\"# Initialize database\")}`,\n `pnpm dev ${pc.dim(\"# Start development server\")}`,\n ].join(\"\\n\"),\n \"Next steps\"\n );\n\n p.outro(pc.green(\"Done!\"));\n }\n}\n\nmain().catch(console.error);\n","import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport type { ProjectConfig } from \"./utils.js\";\nimport { AI_PROVIDERS, DB_CONFIGS, resolveProjectPath, projectExists } from \"./utils.js\";\n\nexport async function runPrompts(argName?: string): Promise<ProjectConfig | null> {\n p.intro(pc.bgCyan(pc.black(\" Create Supyagent App \")));\n\n const projectName = argName || await p.text({\n message: \"Project name\",\n placeholder: \"my-supyagent-app\",\n defaultValue: \"my-supyagent-app\",\n validate(value) {\n if (!value) return \"Project name is required\";\n if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {\n return \"Invalid project name (lowercase, alphanumeric, hyphens, dots)\";\n }\n },\n }) as string;\n\n if (p.isCancel(projectName)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n\n const projectPath = resolveProjectPath(projectName);\n\n if (projectExists(projectPath)) {\n p.cancel(`Directory \"${projectName}\" already exists.`);\n return null;\n }\n\n const aiProvider = await p.select({\n message: \"AI provider\",\n options: [\n { value: \"anthropic\", label: AI_PROVIDERS.anthropic.label },\n { value: \"openai\", label: AI_PROVIDERS.openai.label },\n { value: \"openrouter\", label: AI_PROVIDERS.openrouter.label },\n ],\n }) as \"anthropic\" | \"openai\" | \"openrouter\";\n\n if (p.isCancel(aiProvider)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n\n const database = await p.select({\n message: \"Database\",\n options: [\n { value: \"sqlite\", label: DB_CONFIGS.sqlite.label },\n { value: \"postgres\", label: DB_CONFIGS.postgres.label },\n ],\n }) as \"sqlite\" | \"postgres\";\n\n if (p.isCancel(database)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n\n return { projectName, projectPath, aiProvider, database };\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport function resolveProjectPath(name: string): string {\n return resolve(process.cwd(), name);\n}\n\nexport function projectExists(path: string): boolean {\n return existsSync(path);\n}\n\nexport interface ProjectConfig {\n projectName: string;\n projectPath: string;\n aiProvider: \"anthropic\" | \"openai\" | \"openrouter\";\n database: \"sqlite\" | \"postgres\";\n}\n\nexport const AI_PROVIDERS = {\n anthropic: {\n label: \"Anthropic (Claude)\",\n package: \"@ai-sdk/anthropic\",\n packageVersion: \"^3.0.0\",\n import: `import { anthropic } from '@ai-sdk/anthropic'`,\n model: `anthropic('claude-sonnet-4-20250514')`,\n envKey: \"ANTHROPIC_API_KEY\",\n },\n openai: {\n label: \"OpenAI (GPT)\",\n package: \"@ai-sdk/openai\",\n packageVersion: \"^3.0.0\",\n import: `import { openai } from '@ai-sdk/openai'`,\n model: `openai('gpt-4o')`,\n envKey: \"OPENAI_API_KEY\",\n },\n openrouter: {\n label: \"OpenRouter (any model)\",\n package: \"@openrouter/ai-sdk-provider\",\n packageVersion: \"^2.2.3\",\n import: `import { createOpenRouter } from '@openrouter/ai-sdk-provider'`,\n model: `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('anthropic/claude-sonnet-4-20250514')`,\n envKey: \"OPENROUTER_API_KEY\",\n },\n} as const;\n\nexport const DB_CONFIGS = {\n sqlite: {\n label: \"SQLite (local dev)\",\n provider: \"sqlite\",\n url: \"file:./dev.db\",\n },\n postgres: {\n label: \"PostgreSQL (production)\",\n provider: \"postgresql\",\n url: \"postgresql://user:password@localhost:5432/mydb\",\n },\n} as const;\n","import { mkdirSync, writeFileSync, readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { applyTemplate } from \"./template.js\";\nimport { AI_PROVIDERS, DB_CONFIGS, type ProjectConfig } from \"./utils.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst TEMPLATES_DIR = join(__dirname, \"..\", \"templates\");\n\nfunction readTemplate(relativePath: string): string {\n return readFileSync(join(TEMPLATES_DIR, relativePath), \"utf-8\");\n}\n\nfunction writeProject(projectPath: string, relativePath: string, content: string): void {\n const fullPath = join(projectPath, relativePath);\n mkdirSync(dirname(fullPath), { recursive: true });\n writeFileSync(fullPath, content, \"utf-8\");\n}\n\nexport function scaffoldProject(config: ProjectConfig): void {\n const { projectPath, projectName, aiProvider, database } = config;\n const ai = AI_PROVIDERS[aiProvider];\n const db = DB_CONFIGS[database];\n\n const vars: Record<string, string> = {\n projectName,\n aiProviderPackage: ai.package,\n aiProviderVersion: ai.packageVersion,\n aiProviderImport: ai.import,\n aiModel: ai.model,\n aiProviderEnvKey: ai.envKey,\n dbProvider: db.provider,\n dbUrl: db.url,\n };\n\n mkdirSync(projectPath, { recursive: true });\n\n // ── Base files ──\n writeProject(projectPath, \"next.config.ts\", readTemplate(\"base/next.config.ts\"));\n writeProject(projectPath, \"tsconfig.json\", readTemplate(\"base/tsconfig.json\"));\n writeProject(projectPath, \"tailwind.config.ts\", readTemplate(\"base/tailwind.config.ts\"));\n writeProject(projectPath, \"postcss.config.js\", readTemplate(\"base/postcss.config.js\"));\n writeProject(projectPath, \".gitignore\", readTemplate(\"base/.gitignore\"));\n writeProject(projectPath, \".npmrc\", readTemplate(\"base/.npmrc\"));\n writeProject(projectPath, \"README.md\", applyTemplate(readTemplate(\"base/README.md.tmpl\"), vars));\n\n // ── Source files ──\n writeProject(projectPath, \"src/app/layout.tsx\", readTemplate(\"base/src/app/layout.tsx\"));\n writeProject(projectPath, \"src/app/page.tsx\", readTemplate(\"base/src/app/page.tsx\"));\n writeProject(projectPath, \"src/app/globals.css\", readTemplate(\"base/src/app/globals.css\"));\n writeProject(projectPath, \"src/app/chat/page.tsx\", readTemplate(\"base/src/app/chat/page.tsx\"));\n writeProject(projectPath, \"src/app/chat/[id]/page.tsx\", readTemplate(\"base/src/app/chat/[id]/page.tsx\"));\n\n // API routes\n writeProject(\n projectPath,\n \"src/app/api/chat/route.ts\",\n applyTemplate(readTemplate(\"api-route/route.ts.tmpl\"), vars)\n );\n writeProject(projectPath, \"src/app/api/chats/route.ts\", readTemplate(\"base/src/app/api/chats/route.ts\"));\n writeProject(projectPath, \"src/app/api/chats/[id]/route.ts\", readTemplate(\"base/src/app/api/chats/[id]/route.ts\"));\n\n // Components\n writeProject(projectPath, \"src/components/chat.tsx\", readTemplate(\"base/src/components/chat.tsx\"));\n writeProject(projectPath, \"src/components/chat-sidebar.tsx\", readTemplate(\"base/src/components/chat-sidebar.tsx\"));\n writeProject(projectPath, \"src/components/chat-message.tsx\", readTemplate(\"base/src/components/chat-message.tsx\"));\n writeProject(projectPath, \"src/components/chat-input.tsx\", readTemplate(\"base/src/components/chat-input.tsx\"));\n\n // Lib\n writeProject(projectPath, \"src/lib/utils.ts\", readTemplate(\"base/src/lib/utils.ts\"));\n writeProject(projectPath, \"src/lib/prisma.ts\", readTemplate(\"base/src/lib/prisma.ts\"));\n\n // ── Prisma schema ──\n writeProject(\n projectPath,\n \"prisma/schema.prisma\",\n applyTemplate(readTemplate(\"prisma/schema.prisma.tmpl\"), vars)\n );\n\n // ── Env example ──\n writeProject(\n projectPath,\n \".env.example\",\n applyTemplate(readTemplate(\"env/.env.example.tmpl\"), vars)\n );\n\n // ── package.json ──\n writeProject(\n projectPath,\n \"package.json\",\n applyTemplate(readTemplate(\"package-json/package.json.tmpl\"), vars)\n );\n}\n","/**\n * Replace {{variable}} placeholders in template content.\n */\nexport function applyTemplate(\n content: string,\n variables: Record<string, string>\n): string {\n return content.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key) => {\n return key in variables ? variables[key] : match;\n });\n}\n","import { detectPackageManager, installDependencies } from \"nypm\";\n\nexport async function installDeps(projectPath: string): Promise<void> {\n const pm = await detectPackageManager(projectPath);\n await installDependencies({\n cwd: projectPath,\n packageManager: pm?.name,\n });\n}\n"],"mappings":"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","names":["p","pc","pc"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/prompts.ts","../src/utils.ts","../src/scaffold.ts","../src/template.ts","../src/post-install.ts","../src/quickstart.ts"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { runPrompts } from \"./prompts.js\";\nimport { scaffoldProject } from \"./scaffold.js\";\nimport { installDeps } from \"./post-install.js\";\nimport { writeEnvLocal, runDbSetup, runDevServer } from \"./quickstart.js\";\nimport { AI_PROVIDERS, resolveProjectPath, projectExists } from \"./utils.js\";\nimport type { ProjectConfig, ApiKeys } from \"./utils.js\";\n\ninterface ParsedArgs extends Partial<ProjectConfig> {\n skipInstall?: boolean;\n supyagentApiKey?: string;\n anthropicApiKey?: string;\n openaiApiKey?: string;\n openrouterApiKey?: string;\n}\n\nfunction parseArgs(): ParsedArgs {\n const args = process.argv.slice(2);\n const result: ParsedArgs = {};\n let projectName: string | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n const next = args[i + 1];\n const hasValue = next && !next.startsWith(\"-\");\n\n if (arg === \"--provider\" && hasValue) {\n result.aiProvider = args[++i] as ProjectConfig[\"aiProvider\"];\n } else if (arg === \"--db\" && hasValue) {\n result.database = args[++i] as ProjectConfig[\"database\"];\n } else if (arg === \"--model\" && hasValue) {\n result.model = args[++i];\n } else if (arg === \"--quickstart\") {\n result.quickstart = true;\n } else if (arg === \"--skip-install\") {\n result.skipInstall = true;\n } else if (arg === \"--supyagent-api-key\" && hasValue) {\n result.supyagentApiKey = args[++i];\n } else if (arg === \"--anthropic-api-key\" && hasValue) {\n result.anthropicApiKey = args[++i];\n } else if (arg === \"--openai-api-key\" && hasValue) {\n result.openaiApiKey = args[++i];\n } else if (arg === \"--openrouter-api-key\" && hasValue) {\n result.openrouterApiKey = args[++i];\n } else if (!arg.startsWith(\"-\")) {\n projectName = arg;\n }\n }\n\n return {\n ...result,\n projectName,\n projectPath: projectName ? resolveProjectPath(projectName) : undefined,\n };\n}\n\nconst ENV_KEY_MAP: Record<ProjectConfig[\"aiProvider\"], string> = {\n anthropic: \"ANTHROPIC_API_KEY\",\n openai: \"OPENAI_API_KEY\",\n openrouter: \"OPENROUTER_API_KEY\",\n};\n\nconst CLI_KEY_MAP: Record<ProjectConfig[\"aiProvider\"], keyof ParsedArgs> = {\n anthropic: \"anthropicApiKey\",\n openai: \"openaiApiKey\",\n openrouter: \"openrouterApiKey\",\n};\n\nasync function promptForKey(name: string): Promise<string> {\n const value = await p.password({ message: `Enter your ${name}` });\n if (p.isCancel(value)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n return value;\n}\n\nasync function resolveApiKeys(\n provider: ProjectConfig[\"aiProvider\"],\n parsed: ParsedArgs,\n): Promise<ApiKeys> {\n const envKey = ENV_KEY_MAP[provider];\n const cliFlag = CLI_KEY_MAP[provider];\n\n const supyagent =\n parsed.supyagentApiKey ??\n process.env.SUPYAGENT_API_KEY ??\n (await promptForKey(\"SUPYAGENT_API_KEY\"));\n\n const providerKey =\n (parsed[cliFlag] as string | undefined) ??\n process.env[envKey] ??\n (await promptForKey(envKey));\n\n return { supyagent, provider: providerKey };\n}\n\nasync function main() {\n const parsed = parseArgs();\n\n if (parsed.quickstart) {\n // ── Quickstart mode ──\n if (parsed.database === \"postgres\") {\n p.log.warn(\n pc.yellow(\"--quickstart requires SQLite — ignoring --db postgres\"),\n );\n }\n if (parsed.skipInstall) {\n p.log.warn(\n pc.yellow(\n \"--quickstart needs dependencies installed — ignoring --skip-install\",\n ),\n );\n }\n\n p.intro(pc.bgCyan(pc.black(\" Create Supyagent App — Quickstart \")));\n\n // Resolve project name\n let projectName = parsed.projectName;\n if (!projectName) {\n const name = await p.text({\n message: \"Project name\",\n placeholder: \"my-supyagent-app\",\n defaultValue: \"my-supyagent-app\",\n validate(value) {\n if (!value) return \"Project name is required\";\n if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {\n return \"Invalid project name (lowercase, alphanumeric, hyphens, dots)\";\n }\n },\n });\n if (p.isCancel(name)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n projectName = name as string;\n }\n\n const projectPath = resolveProjectPath(projectName);\n if (projectExists(projectPath)) {\n p.cancel(`Directory \"${projectName}\" already exists.`);\n process.exit(1);\n }\n\n // Resolve provider\n let aiProvider = parsed.aiProvider;\n if (!aiProvider) {\n const selected = await p.select({\n message: \"AI provider\",\n options: [\n { value: \"anthropic\", label: AI_PROVIDERS.anthropic.label },\n { value: \"openai\", label: AI_PROVIDERS.openai.label },\n { value: \"openrouter\", label: AI_PROVIDERS.openrouter.label },\n ],\n });\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n aiProvider = selected as ProjectConfig[\"aiProvider\"];\n }\n\n // Resolve API keys\n const apiKeys = await resolveApiKeys(aiProvider, parsed);\n\n const config: ProjectConfig = {\n projectName,\n projectPath,\n aiProvider,\n database: \"sqlite\",\n model: parsed.model,\n quickstart: true,\n apiKeys,\n };\n\n const s = p.spinner();\n\n // Scaffold\n s.start(\"Scaffolding project...\");\n scaffoldProject(config);\n writeEnvLocal(config);\n s.stop(\"Scaffolded project\");\n\n // Install deps\n s.start(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n s.stop(\"Installed dependencies\");\n } catch {\n s.stop(\"Failed to install dependencies — run install manually\");\n process.exit(1);\n }\n\n // Database setup\n s.start(\"Setting up database...\");\n try {\n await runDbSetup(config.projectPath);\n s.stop(\"Database ready\");\n } catch (err) {\n s.stop(\"Database setup failed\");\n p.log.warn(\n `Run ${pc.cyan(`cd ${projectName} && pnpm db:setup`)} manually`,\n );\n }\n\n // Dev server\n p.log.info(pc.green(\"Starting dev server...\"));\n await runDevServer(config.projectPath);\n } else {\n // ── Existing non-quickstart flow ──\n const isNonInteractive =\n parsed.projectName && parsed.aiProvider && parsed.database;\n\n let config: ProjectConfig | null;\n\n if (isNonInteractive) {\n config = {\n projectName: parsed.projectName!,\n projectPath: parsed.projectPath!,\n aiProvider: parsed.aiProvider!,\n database: parsed.database!,\n model: parsed.model,\n };\n console.log(`Creating ${config.projectName}...`);\n } else {\n config = await runPrompts(parsed.projectName, {\n aiProvider: parsed.aiProvider,\n database: parsed.database,\n });\n if (config && parsed.model) {\n config.model = parsed.model;\n }\n }\n\n if (!config) {\n process.exit(1);\n }\n\n if (isNonInteractive) {\n scaffoldProject(config);\n console.log(\"Scaffolded project\");\n\n if (!parsed.skipInstall) {\n console.log(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n console.log(\"Installed dependencies\");\n } catch {\n console.log(\n \"Failed to install dependencies — run install manually\",\n );\n }\n }\n\n console.log(\n `\\nNext steps:\\n cd ${config.projectName}\\n cp .env.example .env.local\\n pnpm db:setup\\n pnpm dev`,\n );\n } else {\n const s = p.spinner();\n\n s.start(\"Scaffolding project...\");\n scaffoldProject(config);\n s.stop(\"Scaffolded project\");\n\n s.start(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n s.stop(\"Installed dependencies\");\n } catch {\n s.stop(\"Failed to install dependencies — run install manually\");\n }\n\n p.note(\n [\n `cd ${config.projectName}`,\n `cp .env.example .env.local ${pc.dim(\"# Add your API keys\")}`,\n `pnpm db:setup ${pc.dim(\"# Initialize database\")}`,\n `pnpm dev ${pc.dim(\"# Start development server\")}`,\n ].join(\"\\n\"),\n \"Next steps\",\n );\n\n p.outro(pc.green(\"Done!\"));\n }\n }\n}\n\nmain().catch(console.error);\n","import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport type { ProjectConfig } from \"./utils.js\";\nimport { AI_PROVIDERS, DB_CONFIGS, resolveProjectPath, projectExists } from \"./utils.js\";\n\nexport interface PromptOptions {\n aiProvider?: ProjectConfig[\"aiProvider\"];\n database?: ProjectConfig[\"database\"];\n skipDatabase?: boolean;\n}\n\nexport async function runPrompts(\n argName?: string,\n options?: PromptOptions,\n): Promise<ProjectConfig | null> {\n p.intro(pc.bgCyan(pc.black(\" Create Supyagent App \")));\n\n const projectName = argName || await p.text({\n message: \"Project name\",\n placeholder: \"my-supyagent-app\",\n defaultValue: \"my-supyagent-app\",\n validate(value) {\n if (!value) return \"Project name is required\";\n if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {\n return \"Invalid project name (lowercase, alphanumeric, hyphens, dots)\";\n }\n },\n }) as string;\n\n if (p.isCancel(projectName)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n\n const projectPath = resolveProjectPath(projectName);\n\n if (projectExists(projectPath)) {\n p.cancel(`Directory \"${projectName}\" already exists.`);\n return null;\n }\n\n let aiProvider: ProjectConfig[\"aiProvider\"];\n if (options?.aiProvider) {\n aiProvider = options.aiProvider;\n } else {\n const selected = await p.select({\n message: \"AI provider\",\n options: [\n { value: \"anthropic\", label: AI_PROVIDERS.anthropic.label },\n { value: \"openai\", label: AI_PROVIDERS.openai.label },\n { value: \"openrouter\", label: AI_PROVIDERS.openrouter.label },\n ],\n }) as \"anthropic\" | \"openai\" | \"openrouter\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n aiProvider = selected;\n }\n\n let database: ProjectConfig[\"database\"];\n if (options?.database || options?.skipDatabase) {\n database = options?.database ?? \"sqlite\";\n } else {\n const selected = await p.select({\n message: \"Database\",\n options: [\n { value: \"sqlite\", label: DB_CONFIGS.sqlite.label },\n { value: \"postgres\", label: DB_CONFIGS.postgres.label },\n ],\n }) as \"sqlite\" | \"postgres\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n database = selected;\n }\n\n return { projectName, projectPath, aiProvider, database };\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport function resolveProjectPath(name: string): string {\n return resolve(process.cwd(), name);\n}\n\nexport function projectExists(path: string): boolean {\n return existsSync(path);\n}\n\nexport interface ApiKeys {\n supyagent?: string;\n provider?: string;\n}\n\nexport interface ProjectConfig {\n projectName: string;\n projectPath: string;\n aiProvider: \"anthropic\" | \"openai\" | \"openrouter\";\n database: \"sqlite\" | \"postgres\";\n model?: string;\n quickstart?: boolean;\n apiKeys?: ApiKeys;\n}\n\nexport const AI_PROVIDERS = {\n anthropic: {\n label: \"Anthropic (Claude)\",\n package: \"@ai-sdk/anthropic\",\n packageVersion: \"^3.0.0\",\n import: `import { anthropic } from '@ai-sdk/anthropic'`,\n model: `anthropic('claude-sonnet-4-6-20250620')`,\n envKey: \"ANTHROPIC_API_KEY\",\n },\n openai: {\n label: \"OpenAI (GPT)\",\n package: \"@ai-sdk/openai\",\n packageVersion: \"^3.0.0\",\n import: `import { openai } from '@ai-sdk/openai'`,\n model: `openai('gpt-4o')`,\n envKey: \"OPENAI_API_KEY\",\n },\n openrouter: {\n label: \"OpenRouter (any model)\",\n package: \"@openrouter/ai-sdk-provider\",\n packageVersion: \"^2.2.3\",\n import: `import { createOpenRouter } from '@openrouter/ai-sdk-provider'`,\n model: `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('anthropic/claude-sonnet-4.6')`,\n envKey: \"OPENROUTER_API_KEY\",\n },\n} as const;\n\nexport const DB_CONFIGS = {\n sqlite: {\n label: \"SQLite (local dev)\",\n provider: \"sqlite\",\n url: \"file:./dev.db\",\n },\n postgres: {\n label: \"PostgreSQL (production)\",\n provider: \"postgresql\",\n url: \"postgresql://user:password@localhost:5432/mydb\",\n },\n} as const;\n\nexport function buildModelExpression(\n provider: ProjectConfig[\"aiProvider\"],\n customModelId?: string,\n): string {\n const config = AI_PROVIDERS[provider];\n if (!customModelId) return config.model;\n switch (provider) {\n case \"anthropic\":\n return `anthropic('${customModelId}')`;\n case \"openai\":\n return `openai('${customModelId}')`;\n case \"openrouter\":\n return `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('${customModelId}')`;\n }\n}\n","import { mkdirSync, writeFileSync, readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { applyTemplate } from \"./template.js\";\nimport { AI_PROVIDERS, DB_CONFIGS, buildModelExpression, type ProjectConfig } from \"./utils.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst TEMPLATES_DIR = join(__dirname, \"..\", \"templates\");\n\nfunction readTemplate(relativePath: string): string {\n return readFileSync(join(TEMPLATES_DIR, relativePath), \"utf-8\");\n}\n\nfunction writeProject(projectPath: string, relativePath: string, content: string): void {\n const fullPath = join(projectPath, relativePath);\n mkdirSync(dirname(fullPath), { recursive: true });\n writeFileSync(fullPath, content, \"utf-8\");\n}\n\nexport function scaffoldProject(config: ProjectConfig): void {\n const { projectPath, projectName, aiProvider, database } = config;\n const ai = AI_PROVIDERS[aiProvider];\n const db = DB_CONFIGS[database];\n\n const vars: Record<string, string> = {\n projectName,\n aiProviderPackage: ai.package,\n aiProviderVersion: ai.packageVersion,\n aiProviderImport: ai.import,\n aiModel: buildModelExpression(aiProvider, config.model),\n aiProviderEnvKey: ai.envKey,\n dbProvider: db.provider,\n dbUrl: db.url,\n };\n\n mkdirSync(projectPath, { recursive: true });\n\n // ── Base files ──\n writeProject(projectPath, \"next.config.ts\", readTemplate(\"base/next.config.ts\"));\n writeProject(projectPath, \"tsconfig.json\", readTemplate(\"base/tsconfig.json\"));\n writeProject(projectPath, \"tailwind.config.ts\", readTemplate(\"base/tailwind.config.ts\"));\n writeProject(projectPath, \"postcss.config.js\", readTemplate(\"base/postcss.config.js\"));\n writeProject(projectPath, \".gitignore\", readTemplate(\"base/gitignore\"));\n writeProject(projectPath, \".npmrc\", readTemplate(\"base/.npmrc\"));\n writeProject(projectPath, \"README.md\", applyTemplate(readTemplate(\"base/README.md.tmpl\"), vars));\n\n // ── Source files ──\n writeProject(projectPath, \"src/app/layout.tsx\", readTemplate(\"base/src/app/layout.tsx\"));\n writeProject(projectPath, \"src/app/page.tsx\", readTemplate(\"base/src/app/page.tsx\"));\n writeProject(projectPath, \"src/app/globals.css\", readTemplate(\"base/src/app/globals.css\"));\n writeProject(projectPath, \"src/app/chat/page.tsx\", readTemplate(\"base/src/app/chat/page.tsx\"));\n writeProject(projectPath, \"src/app/chat/[id]/page.tsx\", readTemplate(\"base/src/app/chat/[id]/page.tsx\"));\n\n // API routes\n writeProject(\n projectPath,\n \"src/app/api/chat/route.ts\",\n applyTemplate(readTemplate(\"api-route/route.ts.tmpl\"), vars)\n );\n writeProject(projectPath, \"src/app/api/chats/route.ts\", readTemplate(\"base/src/app/api/chats/route.ts\"));\n writeProject(projectPath, \"src/app/api/chats/[id]/route.ts\", readTemplate(\"base/src/app/api/chats/[id]/route.ts\"));\n\n // Components\n writeProject(projectPath, \"src/components/chat.tsx\", readTemplate(\"base/src/components/chat.tsx\"));\n writeProject(projectPath, \"src/components/chat-sidebar.tsx\", readTemplate(\"base/src/components/chat-sidebar.tsx\"));\n writeProject(projectPath, \"src/components/chat-message.tsx\", readTemplate(\"base/src/components/chat-message.tsx\"));\n writeProject(projectPath, \"src/components/chat-input.tsx\", readTemplate(\"base/src/components/chat-input.tsx\"));\n\n // Lib\n writeProject(projectPath, \"src/lib/utils.ts\", readTemplate(\"base/src/lib/utils.ts\"));\n writeProject(projectPath, \"src/lib/prisma.ts\", readTemplate(\"base/src/lib/prisma.ts\"));\n\n // ── Prisma schema ──\n writeProject(\n projectPath,\n \"prisma/schema.prisma\",\n applyTemplate(readTemplate(\"prisma/schema.prisma.tmpl\"), vars)\n );\n\n // ── Env example ──\n writeProject(\n projectPath,\n \".env.example\",\n applyTemplate(readTemplate(\"env/.env.example.tmpl\"), vars)\n );\n\n // ── package.json ──\n writeProject(\n projectPath,\n \"package.json\",\n applyTemplate(readTemplate(\"package-json/package.json.tmpl\"), vars)\n );\n}\n","/**\n * Replace {{variable}} placeholders in template content.\n */\nexport function applyTemplate(\n content: string,\n variables: Record<string, string>\n): string {\n return content.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key) => {\n return key in variables ? variables[key] : match;\n });\n}\n","import { detectPackageManager, installDependencies } from \"nypm\";\n\nexport async function installDeps(projectPath: string): Promise<void> {\n const pm = await detectPackageManager(projectPath);\n await installDependencies({\n cwd: projectPath,\n packageManager: pm?.name,\n });\n}\n","import { writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { execSync, spawn as nodeSpawn } from \"node:child_process\";\nimport { detectPackageManager } from \"nypm\";\nimport { AI_PROVIDERS, type ProjectConfig } from \"./utils.js\";\n\nexport function writeEnvLocal(config: ProjectConfig): void {\n const { projectPath, aiProvider, apiKeys } = config;\n const ai = AI_PROVIDERS[aiProvider];\n\n const lines = [\n \"# Supyagent — Get your API key at https://app.supyagent.com\",\n `SUPYAGENT_API_KEY=${apiKeys?.supyagent ?? \"\"}`,\n \"\",\n \"# AI Provider\",\n `${ai.envKey}=${apiKeys?.provider ?? \"\"}`,\n \"\",\n \"# Database\",\n `DATABASE_URL=\"file:./dev.db\"`,\n \"\",\n ];\n\n writeFileSync(join(projectPath, \".env.local\"), lines.join(\"\\n\"), \"utf-8\");\n}\n\nexport async function runDbSetup(projectPath: string): Promise<void> {\n const pm = await detectPackageManager(projectPath);\n const cmd = pm?.name ?? \"pnpm\";\n execSync(`${cmd} run db:setup`, {\n cwd: projectPath,\n stdio: \"inherit\",\n env: { ...process.env, DATABASE_URL: \"file:./dev.db\" },\n });\n}\n\nexport async function runDevServer(projectPath: string): Promise<never> {\n const pm = await detectPackageManager(projectPath);\n const cmd = pm?.name ?? \"pnpm\";\n\n const child = nodeSpawn(cmd, [\"dev\"], {\n cwd: projectPath,\n stdio: \"inherit\",\n });\n\n const forward = (signal: NodeJS.Signals) => {\n child.kill(signal);\n };\n\n process.on(\"SIGINT\", forward);\n process.on(\"SIGTERM\", forward);\n\n return new Promise((_, reject) => {\n child.on(\"close\", (code) => {\n process.off(\"SIGINT\", forward);\n process.off(\"SIGTERM\", forward);\n process.exit(code ?? 0);\n });\n child.on(\"error\", reject);\n });\n}\n"],"mappings":"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","names":["p","pc","writeFileSync","join","detectPackageManager","writeFileSync","join","detectPackageManager","pc"]}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { convertToModelMessages, streamText, type UIMessage } from 'ai';
|
|
1
|
+
import { convertToModelMessages, streamText, stepCountIs, type UIMessage } from 'ai';
|
|
2
2
|
{{aiProviderImport}};
|
|
3
3
|
import { supyagent } from '@supyagent/sdk';
|
|
4
4
|
import { createPrismaAdapter } from '@supyagent/sdk/prisma';
|
|
@@ -10,7 +10,7 @@ const adapter = createPrismaAdapter(prisma);
|
|
|
10
10
|
export async function POST(req: Request) {
|
|
11
11
|
const { messages, chatId }: { messages: UIMessage[]; chatId: string } = await req.json();
|
|
12
12
|
|
|
13
|
-
await adapter.saveChat(chatId, messages);
|
|
13
|
+
await adapter.saveChat(chatId, messages as any);
|
|
14
14
|
|
|
15
15
|
const tools = await client.tools({ cache: 300 });
|
|
16
16
|
|
|
@@ -19,13 +19,13 @@ export async function POST(req: Request) {
|
|
|
19
19
|
system: 'You are a helpful assistant. Use your tools when asked to interact with connected services.',
|
|
20
20
|
messages: await convertToModelMessages(messages),
|
|
21
21
|
tools,
|
|
22
|
-
|
|
22
|
+
stopWhen: stepCountIs(5),
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
return result.toUIMessageStreamResponse({
|
|
26
26
|
originalMessages: messages,
|
|
27
27
|
onFinish: async ({ messages: updatedMessages }) => {
|
|
28
|
-
await adapter.saveChat(chatId, updatedMessages);
|
|
28
|
+
await adapter.saveChat(chatId, updatedMessages as any);
|
|
29
29
|
},
|
|
30
30
|
});
|
|
31
31
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# dependencies
|
|
2
|
+
/node_modules
|
|
3
|
+
/.pnp
|
|
4
|
+
.pnp.*
|
|
5
|
+
.yarn/install-state.gz
|
|
6
|
+
|
|
7
|
+
# next.js
|
|
8
|
+
/.next/
|
|
9
|
+
/out/
|
|
10
|
+
|
|
11
|
+
# production
|
|
12
|
+
/build
|
|
13
|
+
|
|
14
|
+
# misc
|
|
15
|
+
.DS_Store
|
|
16
|
+
*.pem
|
|
17
|
+
|
|
18
|
+
# debug
|
|
19
|
+
npm-debug.log*
|
|
20
|
+
yarn-debug.log*
|
|
21
|
+
yarn-error.log*
|
|
22
|
+
.pnpm-debug.log*
|
|
23
|
+
|
|
24
|
+
# env files
|
|
25
|
+
.env*.local
|
|
26
|
+
|
|
27
|
+
# typescript
|
|
28
|
+
*.tsbuildinfo
|
|
29
|
+
next-env.d.ts
|
|
30
|
+
|
|
31
|
+
# prisma
|
|
32
|
+
/prisma/dev.db
|
|
33
|
+
/prisma/dev.db-journal
|
|
@@ -1,10 +1,86 @@
|
|
|
1
|
-
@
|
|
2
|
-
@
|
|
3
|
-
@
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@source "../node_modules/@supyagent/sdk/dist/*.js";
|
|
3
|
+
@source "../node_modules/streamdown/dist/*.js";
|
|
4
4
|
|
|
5
5
|
@layer base {
|
|
6
|
+
:root {
|
|
7
|
+
--background: 0 0% 100%;
|
|
8
|
+
--foreground: 240 10% 3.9%;
|
|
9
|
+
--card: 0 0% 100%;
|
|
10
|
+
--card-foreground: 240 10% 3.9%;
|
|
11
|
+
--popover: 0 0% 100%;
|
|
12
|
+
--popover-foreground: 240 10% 3.9%;
|
|
13
|
+
--primary: 240 5.9% 10%;
|
|
14
|
+
--primary-foreground: 0 0% 98%;
|
|
15
|
+
--secondary: 240 4.8% 95.9%;
|
|
16
|
+
--secondary-foreground: 240 5.9% 10%;
|
|
17
|
+
--muted: 240 4.8% 95.9%;
|
|
18
|
+
--muted-foreground: 240 3.8% 46.1%;
|
|
19
|
+
--accent: 240 4.8% 95.9%;
|
|
20
|
+
--accent-foreground: 240 5.9% 10%;
|
|
21
|
+
--destructive: 0 84.2% 60.2%;
|
|
22
|
+
--destructive-foreground: 0 0% 98%;
|
|
23
|
+
--border: 240 5.9% 90%;
|
|
24
|
+
--input: 240 5.9% 90%;
|
|
25
|
+
--ring: 240 5.9% 10%;
|
|
26
|
+
--radius: 0.5rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dark {
|
|
30
|
+
--background: 240 10% 3.9%;
|
|
31
|
+
--foreground: 0 0% 98%;
|
|
32
|
+
--card: 240 10% 5.9%;
|
|
33
|
+
--card-foreground: 0 0% 98%;
|
|
34
|
+
--popover: 240 10% 5.9%;
|
|
35
|
+
--popover-foreground: 0 0% 98%;
|
|
36
|
+
--primary: 0 0% 98%;
|
|
37
|
+
--primary-foreground: 240 5.9% 10%;
|
|
38
|
+
--secondary: 240 3.7% 15.9%;
|
|
39
|
+
--secondary-foreground: 0 0% 98%;
|
|
40
|
+
--muted: 240 3.7% 15.9%;
|
|
41
|
+
--muted-foreground: 240 5% 64.9%;
|
|
42
|
+
--accent: 240 3.7% 15.9%;
|
|
43
|
+
--accent-foreground: 0 0% 98%;
|
|
44
|
+
--destructive: 0 62.8% 30.6%;
|
|
45
|
+
--destructive-foreground: 0 0% 98%;
|
|
46
|
+
--border: 240 3.7% 15.9%;
|
|
47
|
+
--input: 240 3.7% 15.9%;
|
|
48
|
+
--ring: 240 4.9% 83.9%;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@theme inline {
|
|
53
|
+
--color-background: hsl(var(--background));
|
|
54
|
+
--color-foreground: hsl(var(--foreground));
|
|
55
|
+
--color-card: hsl(var(--card));
|
|
56
|
+
--color-card-foreground: hsl(var(--card-foreground));
|
|
57
|
+
--color-popover: hsl(var(--popover));
|
|
58
|
+
--color-popover-foreground: hsl(var(--popover-foreground));
|
|
59
|
+
--color-primary: hsl(var(--primary));
|
|
60
|
+
--color-primary-foreground: hsl(var(--primary-foreground));
|
|
61
|
+
--color-secondary: hsl(var(--secondary));
|
|
62
|
+
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
|
63
|
+
--color-muted: hsl(var(--muted));
|
|
64
|
+
--color-muted-foreground: hsl(var(--muted-foreground));
|
|
65
|
+
--color-accent: hsl(var(--accent));
|
|
66
|
+
--color-accent-foreground: hsl(var(--accent-foreground));
|
|
67
|
+
--color-destructive: hsl(var(--destructive));
|
|
68
|
+
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
|
69
|
+
--color-border: hsl(var(--border));
|
|
70
|
+
--color-input: hsl(var(--input));
|
|
71
|
+
--color-ring: hsl(var(--ring));
|
|
72
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
73
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
74
|
+
--radius-lg: var(--radius);
|
|
75
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@layer base {
|
|
79
|
+
* {
|
|
80
|
+
@apply border-border;
|
|
81
|
+
}
|
|
6
82
|
body {
|
|
7
|
-
@apply bg-
|
|
83
|
+
@apply bg-background text-foreground;
|
|
8
84
|
}
|
|
9
85
|
}
|
|
10
86
|
|
|
@@ -18,10 +94,11 @@
|
|
|
18
94
|
}
|
|
19
95
|
|
|
20
96
|
::-webkit-scrollbar-thumb {
|
|
21
|
-
background:
|
|
97
|
+
background: hsl(var(--border));
|
|
22
98
|
border-radius: 3px;
|
|
23
99
|
}
|
|
24
100
|
|
|
25
101
|
::-webkit-scrollbar-thumb:hover {
|
|
26
|
-
background:
|
|
102
|
+
background: hsl(var(--muted-foreground) / 0.3);
|
|
27
103
|
}
|
|
104
|
+
|
|
@@ -16,7 +16,7 @@ export default function RootLayout({
|
|
|
16
16
|
}) {
|
|
17
17
|
return (
|
|
18
18
|
<html lang="en" className="dark">
|
|
19
|
-
<body className={`${inter.className} bg-
|
|
19
|
+
<body className={`${inter.className} bg-background text-foreground antialiased`}>
|
|
20
20
|
{children}
|
|
21
21
|
</body>
|
|
22
22
|
</html>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { ArrowUp, Square } from "lucide-react";
|
|
4
|
-
import { useState } from "react";
|
|
5
|
-
import type { FormEvent } from "react";
|
|
4
|
+
import { useState, useRef, useEffect, useCallback } from "react";
|
|
5
|
+
import type { FormEvent, KeyboardEvent } from "react";
|
|
6
6
|
|
|
7
7
|
interface ChatInputProps {
|
|
8
8
|
sendMessage: (message: { text: string }) => Promise<void>;
|
|
@@ -12,40 +12,59 @@ interface ChatInputProps {
|
|
|
12
12
|
|
|
13
13
|
export function ChatInput({ sendMessage, isLoading, stop }: ChatInputProps) {
|
|
14
14
|
const [input, setInput] = useState("");
|
|
15
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
16
|
+
|
|
17
|
+
const adjustHeight = useCallback(() => {
|
|
18
|
+
const textarea = textareaRef.current;
|
|
19
|
+
if (!textarea) return;
|
|
20
|
+
textarea.style.height = "auto";
|
|
21
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
adjustHeight();
|
|
26
|
+
}, [input, adjustHeight]);
|
|
15
27
|
|
|
16
28
|
const handleSubmit = (e: FormEvent) => {
|
|
17
29
|
e.preventDefault();
|
|
18
|
-
if (!input.trim()) return;
|
|
30
|
+
if (!input.trim() || isLoading) return;
|
|
19
31
|
sendMessage({ text: input });
|
|
20
32
|
setInput("");
|
|
33
|
+
// Reset height
|
|
34
|
+
if (textareaRef.current) {
|
|
35
|
+
textareaRef.current.style.height = "auto";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
40
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
if (input.trim() && !isLoading) {
|
|
43
|
+
handleSubmit(e as unknown as FormEvent);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
21
46
|
};
|
|
22
47
|
|
|
23
48
|
return (
|
|
24
49
|
<form
|
|
25
50
|
onSubmit={handleSubmit}
|
|
26
|
-
className="relative flex items-end rounded-xl border border-
|
|
51
|
+
className="relative flex items-end rounded-xl border border-border bg-card focus-within:border-ring focus-within:ring-1 focus-within:ring-ring transition-colors"
|
|
27
52
|
>
|
|
28
53
|
<textarea
|
|
54
|
+
ref={textareaRef}
|
|
29
55
|
value={input}
|
|
30
56
|
onChange={(e) => setInput(e.target.value)}
|
|
57
|
+
onKeyDown={handleKeyDown}
|
|
31
58
|
placeholder="Send a message..."
|
|
32
59
|
rows={1}
|
|
33
|
-
className="flex-1 resize-none bg-transparent px-4 py-3 text-sm text-
|
|
34
|
-
onKeyDown={(e) => {
|
|
35
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
36
|
-
e.preventDefault();
|
|
37
|
-
if (input.trim()) {
|
|
38
|
-
handleSubmit(e as unknown as FormEvent);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}}
|
|
60
|
+
className="flex-1 resize-none bg-transparent px-4 py-3 text-sm text-foreground placeholder-muted-foreground outline-none max-h-[200px]"
|
|
42
61
|
/>
|
|
43
62
|
<div className="p-2">
|
|
44
63
|
{isLoading ? (
|
|
45
64
|
<button
|
|
46
65
|
type="button"
|
|
47
66
|
onClick={stop}
|
|
48
|
-
className="flex h-8 w-8 items-center justify-center rounded-lg bg-
|
|
67
|
+
className="flex h-8 w-8 items-center justify-center rounded-lg bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
|
|
49
68
|
>
|
|
50
69
|
<Square className="h-3.5 w-3.5" />
|
|
51
70
|
</button>
|
|
@@ -53,7 +72,7 @@ export function ChatInput({ sendMessage, isLoading, stop }: ChatInputProps) {
|
|
|
53
72
|
<button
|
|
54
73
|
type="submit"
|
|
55
74
|
disabled={!input.trim()}
|
|
56
|
-
className="flex h-8 w-8 items-center justify-center rounded-lg bg-
|
|
75
|
+
className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground disabled:opacity-30 disabled:cursor-not-allowed hover:bg-primary/90 transition-colors"
|
|
57
76
|
>
|
|
58
77
|
<ArrowUp className="h-4 w-4" />
|
|
59
78
|
</button>
|
|
@@ -1,44 +1,89 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { UIMessage } from "ai";
|
|
4
|
-
import {
|
|
4
|
+
import { isToolUIPart } from "ai";
|
|
5
|
+
import { SupyagentToolAction } from "@supyagent/sdk/react";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { Streamdown } from "streamdown";
|
|
8
|
+
import { Copy, Check } from "lucide-react";
|
|
9
|
+
import { cn } from "@/lib/utils";
|
|
5
10
|
|
|
6
11
|
interface ChatMessageProps {
|
|
7
12
|
message: UIMessage;
|
|
8
13
|
}
|
|
9
14
|
|
|
15
|
+
function MarkdownContent({ text }: { text: string }) {
|
|
16
|
+
return (
|
|
17
|
+
<div className="text-sm text-foreground">
|
|
18
|
+
<Streamdown>{text}</Streamdown>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function CopyButton({ text }: { text: string }) {
|
|
24
|
+
const [copied, setCopied] = useState(false);
|
|
25
|
+
|
|
26
|
+
const handleCopy = () => {
|
|
27
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
28
|
+
setCopied(true);
|
|
29
|
+
setTimeout(() => setCopied(false), 2000);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
onClick={handleCopy}
|
|
37
|
+
className="rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
|
38
|
+
title="Copy message"
|
|
39
|
+
>
|
|
40
|
+
{copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
|
|
41
|
+
</button>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
10
45
|
export function ChatMessage({ message }: ChatMessageProps) {
|
|
11
46
|
const isUser = message.role === "user";
|
|
47
|
+
const textContent = message.parts
|
|
48
|
+
.filter((part) => part.type === "text")
|
|
49
|
+
.map((part) => (part as any).text)
|
|
50
|
+
.join("\n");
|
|
12
51
|
|
|
13
52
|
return (
|
|
14
|
-
<div className={
|
|
53
|
+
<div className={cn("group flex", isUser ? "justify-end" : "justify-start")}>
|
|
15
54
|
<div
|
|
16
|
-
className={
|
|
55
|
+
className={cn(
|
|
56
|
+
"max-w-[85%] space-y-2",
|
|
17
57
|
isUser
|
|
18
|
-
? "rounded-2xl rounded-br-md bg-
|
|
19
|
-
: ""
|
|
20
|
-
}
|
|
58
|
+
? "rounded-2xl rounded-br-md bg-secondary px-4 py-2.5"
|
|
59
|
+
: "w-full max-w-none"
|
|
60
|
+
)}
|
|
21
61
|
>
|
|
22
62
|
{message.parts.map((part, i) => {
|
|
23
63
|
if (part.type === "text") {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
64
|
+
if (isUser) {
|
|
65
|
+
return (
|
|
66
|
+
<p key={i} className="text-sm text-foreground whitespace-pre-wrap">
|
|
67
|
+
{(part as any).text}
|
|
68
|
+
</p>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return <MarkdownContent key={i} text={(part as any).text} />;
|
|
29
72
|
}
|
|
30
73
|
|
|
31
|
-
if (part
|
|
32
|
-
return
|
|
33
|
-
<div key={i} className="space-y-2">
|
|
34
|
-
<SupyagentToolCall part={part as any} />
|
|
35
|
-
<SupyagentToolResult part={part as any} />
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
74
|
+
if (isToolUIPart(part)) {
|
|
75
|
+
return <SupyagentToolAction key={i} part={part as any} />;
|
|
38
76
|
}
|
|
39
77
|
|
|
40
78
|
return null;
|
|
41
79
|
})}
|
|
80
|
+
|
|
81
|
+
{/* Message actions */}
|
|
82
|
+
{!isUser && textContent && (
|
|
83
|
+
<div className="opacity-0 group-hover:opacity-100 transition-opacity pt-1">
|
|
84
|
+
<CopyButton text={textContent} />
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
42
87
|
</div>
|
|
43
88
|
</div>
|
|
44
89
|
);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
3
|
+
import { useEffect, useState, useMemo } from "react";
|
|
4
4
|
import { useRouter } from "next/navigation";
|
|
5
|
-
import { MessageSquare, Plus, Trash2 } from "lucide-react";
|
|
5
|
+
import { MessageSquare, Plus, Trash2, Search, PanelLeftClose, PanelLeft } from "lucide-react";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
6
7
|
|
|
7
8
|
interface ChatSummary {
|
|
8
9
|
id: string;
|
|
@@ -15,9 +16,24 @@ interface ChatSidebarProps {
|
|
|
15
16
|
currentChatId: string;
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
function getDateGroup(dateStr: string): string {
|
|
20
|
+
const date = new Date(dateStr);
|
|
21
|
+
const now = new Date();
|
|
22
|
+
const diffMs = now.getTime() - date.getTime();
|
|
23
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
24
|
+
|
|
25
|
+
if (diffDays === 0) return "Today";
|
|
26
|
+
if (diffDays === 1) return "Yesterday";
|
|
27
|
+
if (diffDays < 7) return "Previous 7 days";
|
|
28
|
+
if (diffDays < 30) return "Previous 30 days";
|
|
29
|
+
return "Older";
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
export function ChatSidebar({ currentChatId }: ChatSidebarProps) {
|
|
19
33
|
const router = useRouter();
|
|
20
34
|
const [chats, setChats] = useState<ChatSummary[]>([]);
|
|
35
|
+
const [search, setSearch] = useState("");
|
|
36
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
21
37
|
|
|
22
38
|
useEffect(() => {
|
|
23
39
|
fetch("/api/chats")
|
|
@@ -34,40 +50,108 @@ export function ChatSidebar({ currentChatId }: ChatSidebarProps) {
|
|
|
34
50
|
}
|
|
35
51
|
};
|
|
36
52
|
|
|
53
|
+
const filteredChats = useMemo(() => {
|
|
54
|
+
if (!search.trim()) return chats;
|
|
55
|
+
const q = search.toLowerCase();
|
|
56
|
+
return chats.filter((c) => c.title.toLowerCase().includes(q));
|
|
57
|
+
}, [chats, search]);
|
|
58
|
+
|
|
59
|
+
const groupedChats = useMemo(() => {
|
|
60
|
+
const groups: Record<string, ChatSummary[]> = {};
|
|
61
|
+
const order = ["Today", "Yesterday", "Previous 7 days", "Previous 30 days", "Older"];
|
|
62
|
+
|
|
63
|
+
for (const chat of filteredChats) {
|
|
64
|
+
const group = getDateGroup(chat.updatedAt || chat.createdAt);
|
|
65
|
+
if (!groups[group]) groups[group] = [];
|
|
66
|
+
groups[group].push(chat);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return order
|
|
70
|
+
.filter((g) => groups[g]?.length)
|
|
71
|
+
.map((g) => ({ label: g, chats: groups[g] }));
|
|
72
|
+
}, [filteredChats]);
|
|
73
|
+
|
|
74
|
+
if (collapsed) {
|
|
75
|
+
return (
|
|
76
|
+
<div className="flex h-full w-12 flex-col items-center border-r border-border bg-card py-3 gap-2">
|
|
77
|
+
<button
|
|
78
|
+
onClick={() => setCollapsed(false)}
|
|
79
|
+
className="rounded-md p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
|
80
|
+
>
|
|
81
|
+
<PanelLeft className="h-4 w-4" />
|
|
82
|
+
</button>
|
|
83
|
+
<button
|
|
84
|
+
onClick={() => router.push("/chat")}
|
|
85
|
+
className="rounded-md p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
|
86
|
+
>
|
|
87
|
+
<Plus className="h-4 w-4" />
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
37
93
|
return (
|
|
38
|
-
<div className="flex h-full w-64 flex-col border-r border-
|
|
39
|
-
<div className="flex items-center justify-between p-
|
|
40
|
-
<
|
|
94
|
+
<div className="flex h-full w-64 flex-col border-r border-border bg-card">
|
|
95
|
+
<div className="flex items-center justify-between p-3">
|
|
96
|
+
<button
|
|
97
|
+
onClick={() => setCollapsed(true)}
|
|
98
|
+
className="rounded-md p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
|
99
|
+
>
|
|
100
|
+
<PanelLeftClose className="h-4 w-4" />
|
|
101
|
+
</button>
|
|
102
|
+
<span className="text-sm font-medium text-foreground">Chats</span>
|
|
41
103
|
<button
|
|
42
104
|
onClick={() => router.push("/chat")}
|
|
43
|
-
className="rounded-md p-1.5 text-
|
|
105
|
+
className="rounded-md p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
|
44
106
|
>
|
|
45
107
|
<Plus className="h-4 w-4" />
|
|
46
108
|
</button>
|
|
47
109
|
</div>
|
|
48
110
|
|
|
111
|
+
{/* Search */}
|
|
112
|
+
<div className="px-3 pb-2">
|
|
113
|
+
<div className="flex items-center gap-2 rounded-lg border border-border bg-background px-2.5 py-1.5">
|
|
114
|
+
<Search className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
|
115
|
+
<input
|
|
116
|
+
type="text"
|
|
117
|
+
value={search}
|
|
118
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
119
|
+
placeholder="Search chats..."
|
|
120
|
+
className="flex-1 bg-transparent text-xs text-foreground placeholder-muted-foreground outline-none"
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
49
125
|
<div className="flex-1 overflow-y-auto px-2">
|
|
50
|
-
{
|
|
51
|
-
<div
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
126
|
+
{groupedChats.map((group) => (
|
|
127
|
+
<div key={group.label} className="mb-3">
|
|
128
|
+
<p className="px-3 py-1 text-xs font-medium text-muted-foreground">
|
|
129
|
+
{group.label}
|
|
130
|
+
</p>
|
|
131
|
+
{group.chats.map((chat) => (
|
|
132
|
+
<div
|
|
133
|
+
key={chat.id}
|
|
134
|
+
className={cn(
|
|
135
|
+
"group mb-0.5 flex items-center rounded-lg px-3 py-2 cursor-pointer transition-colors",
|
|
136
|
+
chat.id === currentChatId
|
|
137
|
+
? "bg-muted text-foreground"
|
|
138
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
139
|
+
)}
|
|
140
|
+
onClick={() => router.push(`/chat/${chat.id}`)}
|
|
141
|
+
>
|
|
142
|
+
<MessageSquare className="mr-2 h-3.5 w-3.5 shrink-0" />
|
|
143
|
+
<span className="flex-1 truncate text-sm">{chat.title}</span>
|
|
144
|
+
<button
|
|
145
|
+
onClick={(e) => {
|
|
146
|
+
e.stopPropagation();
|
|
147
|
+
deleteChat(chat.id);
|
|
148
|
+
}}
|
|
149
|
+
className="ml-1 hidden rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground group-hover:block"
|
|
150
|
+
>
|
|
151
|
+
<Trash2 className="h-3 w-3" />
|
|
152
|
+
</button>
|
|
153
|
+
</div>
|
|
154
|
+
))}
|
|
71
155
|
</div>
|
|
72
156
|
))}
|
|
73
157
|
</div>
|
|
@@ -5,7 +5,8 @@ import { DefaultChatTransport, type UIMessage } from "ai";
|
|
|
5
5
|
import { ChatMessage } from "./chat-message";
|
|
6
6
|
import { ChatInput } from "./chat-input";
|
|
7
7
|
import { ChatSidebar } from "./chat-sidebar";
|
|
8
|
-
import { useRef, useEffect, useMemo } from "react";
|
|
8
|
+
import { useRef, useEffect, useMemo, useState, useCallback } from "react";
|
|
9
|
+
import { MessageSquare, ArrowDown } from "lucide-react";
|
|
9
10
|
|
|
10
11
|
interface ChatProps {
|
|
11
12
|
chatId: string;
|
|
@@ -25,23 +26,49 @@ export function Chat({ chatId, initialMessages }: ChatProps) {
|
|
|
25
26
|
const { messages, sendMessage, status, stop } = useChat({
|
|
26
27
|
id: chatId,
|
|
27
28
|
transport,
|
|
28
|
-
initialMessages,
|
|
29
|
+
messages: initialMessages,
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
const isLoading = status === "submitted" || status === "streaming";
|
|
32
33
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
34
|
+
const bottomRef = useRef<HTMLDivElement>(null);
|
|
35
|
+
const [isAtBottom, setIsAtBottom] = useState(true);
|
|
36
|
+
|
|
37
|
+
const scrollToBottom = useCallback(() => {
|
|
38
|
+
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
39
|
+
}, []);
|
|
33
40
|
|
|
34
41
|
useEffect(() => {
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
const el = scrollRef.current;
|
|
43
|
+
if (!el) return;
|
|
44
|
+
|
|
45
|
+
const handleScroll = () => {
|
|
46
|
+
const threshold = 100;
|
|
47
|
+
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
|
|
48
|
+
setIsAtBottom(atBottom);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
el.addEventListener("scroll", handleScroll, { passive: true });
|
|
52
|
+
return () => el.removeEventListener("scroll", handleScroll);
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (isAtBottom) {
|
|
57
|
+
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
37
58
|
}
|
|
38
|
-
}, [messages]);
|
|
59
|
+
}, [messages, isAtBottom]);
|
|
60
|
+
|
|
61
|
+
const suggestions = [
|
|
62
|
+
"What integrations are connected?",
|
|
63
|
+
"Search my recent emails",
|
|
64
|
+
"What's on my calendar today?",
|
|
65
|
+
];
|
|
39
66
|
|
|
40
67
|
return (
|
|
41
68
|
<div className="flex h-screen">
|
|
42
69
|
<ChatSidebar currentChatId={chatId} />
|
|
43
70
|
|
|
44
|
-
<div className="flex flex-1 flex-col">
|
|
71
|
+
<div className="flex flex-1 flex-col relative">
|
|
45
72
|
<div
|
|
46
73
|
ref={scrollRef}
|
|
47
74
|
className="flex-1 overflow-y-auto px-4 py-6"
|
|
@@ -49,23 +76,51 @@ export function Chat({ chatId, initialMessages }: ChatProps) {
|
|
|
49
76
|
<div className="mx-auto max-w-3xl space-y-6">
|
|
50
77
|
{messages.length === 0 && (
|
|
51
78
|
<div className="flex h-full min-h-[60vh] items-center justify-center">
|
|
52
|
-
<div className="text-center">
|
|
53
|
-
<
|
|
79
|
+
<div className="text-center max-w-md">
|
|
80
|
+
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-muted">
|
|
81
|
+
<MessageSquare className="h-6 w-6 text-muted-foreground" />
|
|
82
|
+
</div>
|
|
83
|
+
<h1 className="text-xl font-semibold text-foreground">
|
|
54
84
|
Supyagent Chat
|
|
55
85
|
</h1>
|
|
56
|
-
<p className="mt-2 text-sm text-
|
|
86
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
57
87
|
Ask me anything — I can use your connected integrations.
|
|
58
88
|
</p>
|
|
89
|
+
<div className="mt-6 flex flex-wrap justify-center gap-2">
|
|
90
|
+
{suggestions.map((suggestion) => (
|
|
91
|
+
<button
|
|
92
|
+
key={suggestion}
|
|
93
|
+
type="button"
|
|
94
|
+
onClick={() => sendMessage({ text: suggestion })}
|
|
95
|
+
className="rounded-full border border-border bg-card px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
|
96
|
+
>
|
|
97
|
+
{suggestion}
|
|
98
|
+
</button>
|
|
99
|
+
))}
|
|
100
|
+
</div>
|
|
59
101
|
</div>
|
|
60
102
|
</div>
|
|
61
103
|
)}
|
|
62
104
|
{messages.map((message) => (
|
|
63
105
|
<ChatMessage key={message.id} message={message} />
|
|
64
106
|
))}
|
|
107
|
+
<div ref={bottomRef} />
|
|
65
108
|
</div>
|
|
66
109
|
</div>
|
|
67
110
|
|
|
68
|
-
|
|
111
|
+
{/* Scroll to bottom button */}
|
|
112
|
+
{!isAtBottom && messages.length > 0 && (
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
onClick={scrollToBottom}
|
|
116
|
+
className="absolute bottom-24 left-1/2 -translate-x-1/2 flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-xs text-muted-foreground shadow-lg hover:bg-muted transition-colors"
|
|
117
|
+
>
|
|
118
|
+
<ArrowDown className="h-3 w-3" />
|
|
119
|
+
Scroll to bottom
|
|
120
|
+
</button>
|
|
121
|
+
)}
|
|
122
|
+
|
|
123
|
+
<div className="border-t border-border px-4 py-4">
|
|
69
124
|
<div className="mx-auto max-w-3xl">
|
|
70
125
|
<ChatInput
|
|
71
126
|
sendMessage={sendMessage}
|
|
@@ -16,22 +16,24 @@
|
|
|
16
16
|
"@ai-sdk/react": "^3.0.0",
|
|
17
17
|
"{{aiProviderPackage}}": "{{aiProviderVersion}}",
|
|
18
18
|
"@prisma/client": "^6.2.0",
|
|
19
|
-
"@supyagent/sdk": "^0.1.
|
|
19
|
+
"@supyagent/sdk": "^0.1.7",
|
|
20
20
|
"ai": "^6.0.0",
|
|
21
21
|
"clsx": "^2.1.0",
|
|
22
22
|
"lucide-react": "^0.468.0",
|
|
23
23
|
"next": "^15.1.0",
|
|
24
24
|
"react": "^19.0.0",
|
|
25
25
|
"react-dom": "^19.0.0",
|
|
26
|
-
"
|
|
26
|
+
"streamdown": "^2.0.0",
|
|
27
|
+
"tailwind-merge": "^2.6.0",
|
|
28
|
+
"use-stick-to-bottom": "^1.0.0"
|
|
27
29
|
},
|
|
28
30
|
"devDependencies": {
|
|
31
|
+
"@tailwindcss/postcss": "^4.0.0",
|
|
29
32
|
"@types/node": "^22.0.0",
|
|
30
33
|
"@types/react": "^19.0.0",
|
|
31
34
|
"@types/react-dom": "^19.0.0",
|
|
32
|
-
"postcss": "^8.4.0",
|
|
33
35
|
"prisma": "^6.2.0",
|
|
34
|
-
"tailwindcss": "^
|
|
36
|
+
"tailwindcss": "^4.0.0",
|
|
35
37
|
"typescript": "^5.7.0"
|
|
36
38
|
}
|
|
37
39
|
}
|