Spaces:
Running
Running
File size: 7,067 Bytes
a51c5c9 b0e750c a51c5c9 e3c1646 b0e750c a51c5c9 b0e750c e3c1646 b0e750c a51c5c9 b0e750c a51c5c9 b0e750c e3c1646 b0e750c e3c1646 b0e750c a51c5c9 b0e750c a51c5c9 e3c1646 a51c5c9 b0e750c a51c5c9 e3c1646 a51c5c9 b0e750c a51c5c9 b0e750c a51c5c9 b0e750c c586a80 b0e750c e3c1646 b0e750c a51c5c9 b0e750c a51c5c9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
"use client";
import { useEffect, useState } from "react";
import { useUser } from "@/hooks/useUser";
import { useAi } from "@/hooks/useAi";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
import { NEW_STACKS, NewStackId } from "@/lib/new-stacks";
function buildInstruction(stack: NewStackId, lang: "js" | "ts", title?: string) {
const jsOrTs = lang === 'ts' ? 'TypeScript' : 'JavaScript';
const reactEntry = lang === 'ts' ? "/frontend/src/main.tsx and /frontend/src/App.tsx" : "/frontend/src/main.jsx and /frontend/src/App.jsx";
switch (stack) {
case 'express-react':
return `Initialize a complete full-stack web project with the following structure and runnable code.
Title: ${title || 'OmniDev Full-Stack App'}
Requirements:
- Frontend under /frontend using React + Vite (${jsOrTs}), TailwindCSS preconfigured.
- Backend under /backend using Express (${jsOrTs}, ESM), with basic routes (GET /, GET /health) and CORS enabled.
- Create a visually striking Hero section (animated) appropriate to the app, optimized and accessible.
- Add a minimal README.md at root with start instructions.
- Provide package.json in both /frontend and /backend with scripts to start dev/prod.
- Provide /frontend/index.html and ${reactEntry}.
- Provide /backend/server.${lang === 'ts' ? 'ts' : 'js'}.
- Use ports 5173 for frontend and 3000 for backend.
- Keep everything simple and runnable.
- Return STRICT JSON ONLY as file updates (no markdown), paths rooted from repo (e.g., /frontend/..., /backend/...).`;
case 'nextjs':
return `Scaffold a full-stack Next.js 15 App Router project.
Title: ${title || 'OmniDev Next App'}
Requirements:
- Next.js (${jsOrTs}), App Router, TailwindCSS.
- Implement a landing page with an animated Hero section suitable for the domain.
- Add /api/health route that returns { ok: true }.
- Provide package.json with dev/build scripts.
- Keep it simple and runnable with \'next dev\'.
- Return STRICT JSON ONLY as file updates (no markdown), paths rooted from repo (e.g., /app/page.${lang === 'ts' ? 'tsx' : 'jsx'}, /app/api/health/route.${lang === 'ts' ? 'ts' : 'js'}).`;
case 'nestjs-react':
return `Initialize a NestJS backend and React (Vite) frontend.
Title: ${title || 'OmniDev Nest + React'}
Requirements:
- Backend under /backend using NestJS (${jsOrTs}). Generate AppModule, AppController with GET / and GET /health. Enable CORS.
- Frontend under /frontend using React + Vite (${jsOrTs}), TailwindCSS with a modern animated Hero section appropriate to the app.
- Provide package.json in both apps with start/build scripts.
- Return STRICT JSON ONLY as file updates (no markdown), paths rooted from repo.`;
}
}
export default function ScaffoldNew() {
const { user, openLoginWindow } = useUser();
const { model, provider } = useAi();
const router = useRouter();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [logs, setLogs] = useState<string[]>([]);
const [stack, setStack] = useState<NewStackId>("express-react");
const [lang, setLang] = useState<"js" | "ts">("js");
const [title, setTitle] = useState<string>("");
async function runScaffold() {
if (!user) {
await openLoginWindow();
return;
}
setLoading(true);
setError(null);
setLogs(["Starting scaffold via Augment..."]);
try {
// 1) Call augment to generate full-stack files
const instruction = buildInstruction(stack, lang, title);
const aug = await fetch('/api/augment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
context: '/ (empty project)',
instruction,
language: lang === 'ts' ? 'typescript' : 'javascript',
framework: stack,
response_type: 'file_updates',
// Force Gemini for scaffold stage
model: 'gemini-2.5-flash',
provider: 'google',
}),
}).then(r => r.json());
if (!aug?.ok || !Array.isArray(aug.files) || aug.files.length === 0) {
throw new Error(aug?.message || 'Augment did not return files');
}
setLogs(prev => [...prev, `Augment produced ${aug.files.length} files`]);
// 2) Create space with initial files
const created = await fetch('/api/me/projects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: title || 'OmniDev Project', initialFiles: aug.files.map((f: any) => ({ path: f.path, content: f.content })) })
}).then(r => r.json());
if (!created?.space?.project?.space_id) {
throw new Error(created?.error || 'Failed to create project');
}
setLogs(prev => [...prev, 'Project created, redirecting...']);
router.push(`/${created.space.project.space_id}`);
} catch (e: any) {
setError(e?.message || 'Scaffold failed');
toast.error(e?.message || 'Scaffold failed');
} finally {
setLoading(false);
}
}
// Removed auto-run; wait for user selection
return (
<section className="max-w-3xl mx-auto p-6 text-neutral-200">
<h1 className="text-2xl font-semibold mb-1">Create New Project</h1>
<p className="text-sm text-neutral-400 mb-4">Choose your stack, then OmniDev will scaffold a complete project using AI.</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-sm block mb-1">Project Title</label>
<input className="w-full bg-neutral-800 rounded p-2 text-sm" placeholder="OmniDev Project" value={title} onChange={(e) => setTitle(e.target.value)} />
</div>
<div>
<label className="text-sm block mb-1">Language</label>
<select className="w-full bg-neutral-800 rounded p-2 text-sm" value={lang} onChange={(e) => setLang(e.target.value as any)}>
<option value="js">JavaScript</option>
<option value="ts">TypeScript</option>
</select>
</div>
<div>
<label className="text-sm block mb-1">Stack</label>
<select className="w-full bg-neutral-800 rounded p-2 text-sm" value={stack} onChange={(e) => setStack(e.target.value as NewStackId)}>
{NEW_STACKS.map(s => (
<option key={s.id} value={s.id}>{s.label}</option>
))}
</select>
<p className="text-xs text-neutral-500 mt-1">{NEW_STACKS.find(s => s.id === stack)?.description}</p>
</div>
{/* Hero selection removed: AI decides the best hero automatically */}
</div>
{error && <p className="text-red-400 text-sm mt-3">{error}</p>}
<ul className="text-sm text-neutral-400 space-y-1 mt-3">
{logs.map((l, i) => (<li key={i}>• {l}</li>))}
</ul>
<div className="mt-5">
<Button size="sm" onClick={runScaffold} disabled={loading}>{loading ? 'Working...' : 'Create Project'}</Button>
</div>
</section>
);
}
|