Spaces:
Running
Running
Reupload OmniDev clean version
Browse files- components/new/scaffold.tsx +89 -25
- lib/hero-presets.ts +15 -0
- lib/new-stacks.ts +30 -0
components/new/scaffold.tsx
CHANGED
|
@@ -5,19 +5,54 @@ import { useAi } from "@/hooks/useAi";
|
|
| 5 |
import { useRouter } from "next/navigation";
|
| 6 |
import { Button } from "@/components/ui/button";
|
| 7 |
import { toast } from "sonner";
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
Requirements:
|
| 12 |
-
- Frontend under /frontend using React + Vite (
|
| 13 |
-
- Backend under /backend using Express (
|
| 14 |
-
-
|
|
|
|
| 15 |
- Provide package.json in both /frontend and /backend with scripts to start dev/prod.
|
| 16 |
-
- Provide /frontend/index.html and
|
| 17 |
-
- Provide /backend/server
|
| 18 |
- Use ports 5173 for frontend and 3000 for backend.
|
| 19 |
- Keep everything simple and runnable.
|
| 20 |
-
- Return JSON
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
export default function ScaffoldNew() {
|
| 23 |
const { user, openLoginWindow } = useUser();
|
|
@@ -26,6 +61,10 @@ export default function ScaffoldNew() {
|
|
| 26 |
const [loading, setLoading] = useState(false);
|
| 27 |
const [error, setError] = useState<string | null>(null);
|
| 28 |
const [logs, setLogs] = useState<string[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
async function runScaffold() {
|
| 31 |
if (!user) {
|
|
@@ -37,14 +76,15 @@ export default function ScaffoldNew() {
|
|
| 37 |
setLogs(["Starting scaffold via Augment..."]);
|
| 38 |
try {
|
| 39 |
// 1) Call augment to generate full-stack files
|
|
|
|
| 40 |
const aug = await fetch('/api/augment', {
|
| 41 |
method: 'POST',
|
| 42 |
headers: { 'Content-Type': 'application/json' },
|
| 43 |
body: JSON.stringify({
|
| 44 |
context: '/ (empty project)',
|
| 45 |
-
instruction
|
| 46 |
-
language: 'javascript',
|
| 47 |
-
framework:
|
| 48 |
response_type: 'file_updates',
|
| 49 |
model,
|
| 50 |
provider,
|
|
@@ -56,11 +96,10 @@ export default function ScaffoldNew() {
|
|
| 56 |
setLogs(prev => [...prev, `Augment produced ${aug.files.length} files`]);
|
| 57 |
|
| 58 |
// 2) Create space with initial files
|
| 59 |
-
const title = 'OmniDev Full-Stack Project';
|
| 60 |
const created = await fetch('/api/me/projects', {
|
| 61 |
method: 'POST',
|
| 62 |
headers: { 'Content-Type': 'application/json' },
|
| 63 |
-
body: JSON.stringify({ title, initialFiles: aug.files.map((f: any) => ({ path: f.path, content: f.content })) })
|
| 64 |
}).then(r => r.json());
|
| 65 |
if (!created?.space?.project?.space_id) {
|
| 66 |
throw new Error(created?.error || 'Failed to create project');
|
|
@@ -75,24 +114,49 @@ export default function ScaffoldNew() {
|
|
| 75 |
}
|
| 76 |
}
|
| 77 |
|
| 78 |
-
|
| 79 |
-
// Auto-run once when visiting /new
|
| 80 |
-
runScaffold();
|
| 81 |
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
| 82 |
-
}, []);
|
| 83 |
|
| 84 |
return (
|
| 85 |
-
<section className="max-w-
|
| 86 |
-
<h1 className="text-2xl font-semibold mb-
|
| 87 |
-
<p className="text-sm text-neutral-400 mb-4">
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
{logs.map((l, i) => (<li key={i}>• {l}</li>))}
|
| 91 |
</ul>
|
| 92 |
-
<div className="mt-
|
| 93 |
-
<Button size="sm" onClick={runScaffold} disabled={loading}>{loading ? 'Working...' : '
|
| 94 |
</div>
|
| 95 |
</section>
|
| 96 |
);
|
| 97 |
}
|
| 98 |
-
|
|
|
|
| 5 |
import { useRouter } from "next/navigation";
|
| 6 |
import { Button } from "@/components/ui/button";
|
| 7 |
import { toast } from "sonner";
|
| 8 |
+
import { NEW_STACKS, NewStackId } from "@/lib/new-stacks";
|
| 9 |
+
import { HERO_STYLES } from "@/lib/hero-presets";
|
| 10 |
|
| 11 |
+
function buildInstruction(stack: NewStackId, lang: "js" | "ts", hero: string, title?: string) {
|
| 12 |
+
const jsOrTs = lang === 'ts' ? 'TypeScript' : 'JavaScript';
|
| 13 |
+
const reactEntry = lang === 'ts' ? "/frontend/src/main.tsx and /frontend/src/App.tsx" : "/frontend/src/main.jsx and /frontend/src/App.jsx";
|
| 14 |
+
|
| 15 |
+
switch (stack) {
|
| 16 |
+
case 'express-react':
|
| 17 |
+
return `Initialize a complete full-stack web project with the following structure and runnable code.
|
| 18 |
+
|
| 19 |
+
Title: ${title || 'OmniDev Full-Stack App'}
|
| 20 |
|
| 21 |
Requirements:
|
| 22 |
+
- Frontend under /frontend using React + Vite (${jsOrTs}), TailwindCSS preconfigured.
|
| 23 |
+
- Backend under /backend using Express (${jsOrTs}, ESM), with basic routes (GET /, GET /health) and CORS enabled.
|
| 24 |
+
- Use a Hero section with style: ${hero}. Keep it performant and accessible.
|
| 25 |
+
- Add a minimal README.md at root with start instructions.
|
| 26 |
- Provide package.json in both /frontend and /backend with scripts to start dev/prod.
|
| 27 |
+
- Provide /frontend/index.html and ${reactEntry}.
|
| 28 |
+
- Provide /backend/server.${lang === 'ts' ? 'ts' : 'js'}.
|
| 29 |
- Use ports 5173 for frontend and 3000 for backend.
|
| 30 |
- Keep everything simple and runnable.
|
| 31 |
+
- Return STRICT JSON ONLY as file updates (no markdown), paths rooted from repo (e.g., /frontend/..., /backend/...).`;
|
| 32 |
+
case 'nextjs':
|
| 33 |
+
return `Scaffold a full-stack Next.js 15 App Router project.
|
| 34 |
+
|
| 35 |
+
Title: ${title || 'OmniDev Next App'}
|
| 36 |
+
|
| 37 |
+
Requirements:
|
| 38 |
+
- Next.js (${jsOrTs}), App Router, TailwindCSS.
|
| 39 |
+
- Implement a landing page with a Hero section style: ${hero}.
|
| 40 |
+
- Add /api/health route that returns { ok: true }.
|
| 41 |
+
- Provide package.json with dev/build scripts.
|
| 42 |
+
- Keep it simple and runnable with \'next dev\'.
|
| 43 |
+
- 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'}).`;
|
| 44 |
+
case 'nestjs-react':
|
| 45 |
+
return `Initialize a NestJS backend and React (Vite) frontend.
|
| 46 |
+
|
| 47 |
+
Title: ${title || 'OmniDev Nest + React'}
|
| 48 |
+
|
| 49 |
+
Requirements:
|
| 50 |
+
- Backend under /backend using NestJS (${jsOrTs}). Generate AppModule, AppController with GET / and GET /health. Enable CORS.
|
| 51 |
+
- Frontend under /frontend using React + Vite (${jsOrTs}), TailwindCSS with a modern Hero section style: ${hero}.
|
| 52 |
+
- Provide package.json in both apps with start/build scripts.
|
| 53 |
+
- Return STRICT JSON ONLY as file updates (no markdown), paths rooted from repo.`;
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
|
| 57 |
export default function ScaffoldNew() {
|
| 58 |
const { user, openLoginWindow } = useUser();
|
|
|
|
| 61 |
const [loading, setLoading] = useState(false);
|
| 62 |
const [error, setError] = useState<string | null>(null);
|
| 63 |
const [logs, setLogs] = useState<string[]>([]);
|
| 64 |
+
const [stack, setStack] = useState<NewStackId>("express-react");
|
| 65 |
+
const [lang, setLang] = useState<"js" | "ts">("js");
|
| 66 |
+
const [hero, setHero] = useState<string>(HERO_STYLES[0].id);
|
| 67 |
+
const [title, setTitle] = useState<string>("");
|
| 68 |
|
| 69 |
async function runScaffold() {
|
| 70 |
if (!user) {
|
|
|
|
| 76 |
setLogs(["Starting scaffold via Augment..."]);
|
| 77 |
try {
|
| 78 |
// 1) Call augment to generate full-stack files
|
| 79 |
+
const instruction = buildInstruction(stack, lang, hero, title);
|
| 80 |
const aug = await fetch('/api/augment', {
|
| 81 |
method: 'POST',
|
| 82 |
headers: { 'Content-Type': 'application/json' },
|
| 83 |
body: JSON.stringify({
|
| 84 |
context: '/ (empty project)',
|
| 85 |
+
instruction,
|
| 86 |
+
language: lang === 'ts' ? 'typescript' : 'javascript',
|
| 87 |
+
framework: stack,
|
| 88 |
response_type: 'file_updates',
|
| 89 |
model,
|
| 90 |
provider,
|
|
|
|
| 96 |
setLogs(prev => [...prev, `Augment produced ${aug.files.length} files`]);
|
| 97 |
|
| 98 |
// 2) Create space with initial files
|
|
|
|
| 99 |
const created = await fetch('/api/me/projects', {
|
| 100 |
method: 'POST',
|
| 101 |
headers: { 'Content-Type': 'application/json' },
|
| 102 |
+
body: JSON.stringify({ title: title || 'OmniDev Project', initialFiles: aug.files.map((f: any) => ({ path: f.path, content: f.content })) })
|
| 103 |
}).then(r => r.json());
|
| 104 |
if (!created?.space?.project?.space_id) {
|
| 105 |
throw new Error(created?.error || 'Failed to create project');
|
|
|
|
| 114 |
}
|
| 115 |
}
|
| 116 |
|
| 117 |
+
// Removed auto-run; wait for user selection
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
return (
|
| 120 |
+
<section className="max-w-3xl mx-auto p-6 text-neutral-200">
|
| 121 |
+
<h1 className="text-2xl font-semibold mb-1">Create New Project</h1>
|
| 122 |
+
<p className="text-sm text-neutral-400 mb-4">Choose your stack and hero style, then OmniDev will scaffold a complete project.</p>
|
| 123 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 124 |
+
<div>
|
| 125 |
+
<label className="text-sm block mb-1">Project Title</label>
|
| 126 |
+
<input className="w-full bg-neutral-800 rounded p-2 text-sm" placeholder="OmniDev Project" value={title} onChange={(e) => setTitle(e.target.value)} />
|
| 127 |
+
</div>
|
| 128 |
+
<div>
|
| 129 |
+
<label className="text-sm block mb-1">Language</label>
|
| 130 |
+
<select className="w-full bg-neutral-800 rounded p-2 text-sm" value={lang} onChange={(e) => setLang(e.target.value as any)}>
|
| 131 |
+
<option value="js">JavaScript</option>
|
| 132 |
+
<option value="ts">TypeScript</option>
|
| 133 |
+
</select>
|
| 134 |
+
</div>
|
| 135 |
+
<div>
|
| 136 |
+
<label className="text-sm block mb-1">Stack</label>
|
| 137 |
+
<select className="w-full bg-neutral-800 rounded p-2 text-sm" value={stack} onChange={(e) => setStack(e.target.value as NewStackId)}>
|
| 138 |
+
{NEW_STACKS.map(s => (
|
| 139 |
+
<option key={s.id} value={s.id}>{s.label}</option>
|
| 140 |
+
))}
|
| 141 |
+
</select>
|
| 142 |
+
<p className="text-xs text-neutral-500 mt-1">{NEW_STACKS.find(s => s.id === stack)?.description}</p>
|
| 143 |
+
</div>
|
| 144 |
+
<div>
|
| 145 |
+
<label className="text-sm block mb-1">Hero Style</label>
|
| 146 |
+
<select className="w-full bg-neutral-800 rounded p-2 text-sm" value={hero} onChange={(e) => setHero(e.target.value)}>
|
| 147 |
+
{HERO_STYLES.map(h => (
|
| 148 |
+
<option key={h.id} value={h.id}>{h.label}</option>
|
| 149 |
+
))}
|
| 150 |
+
</select>
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
{error && <p className="text-red-400 text-sm mt-3">{error}</p>}
|
| 154 |
+
<ul className="text-sm text-neutral-400 space-y-1 mt-3">
|
| 155 |
{logs.map((l, i) => (<li key={i}>• {l}</li>))}
|
| 156 |
</ul>
|
| 157 |
+
<div className="mt-5">
|
| 158 |
+
<Button size="sm" onClick={runScaffold} disabled={loading}>{loading ? 'Working...' : 'Create Project'}</Button>
|
| 159 |
</div>
|
| 160 |
</section>
|
| 161 |
);
|
| 162 |
}
|
|
|
lib/hero-presets.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const HERO_STYLES = [
|
| 2 |
+
{ id: "animated-gradient", label: "Animated Gradient" },
|
| 3 |
+
{ id: "particles", label: "Particles" },
|
| 4 |
+
{ id: "waves", label: "Waves" },
|
| 5 |
+
{ id: "noise", label: "Noise Overlay" },
|
| 6 |
+
{ id: "parallax", label: "Parallax Layers" },
|
| 7 |
+
{ id: "vanta-globe", label: "Vanta Globe" },
|
| 8 |
+
{ id: "morphing-blobs", label: "Morphing Blobs" },
|
| 9 |
+
{ id: "rays", label: "Rays / Aurora" },
|
| 10 |
+
{ id: "grid", label: "Animated Grid" },
|
| 11 |
+
{ id: "shapes", label: "Floating Shapes" },
|
| 12 |
+
{ id: "lines", label: "Moving Lines" },
|
| 13 |
+
{ id: "orbits", label: "Orbits" },
|
| 14 |
+
];
|
| 15 |
+
|
lib/new-stacks.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export type NewStackId = "express-react" | "nextjs" | "nestjs-react";
|
| 2 |
+
|
| 3 |
+
export interface NewStack {
|
| 4 |
+
id: NewStackId;
|
| 5 |
+
label: string;
|
| 6 |
+
description: string;
|
| 7 |
+
frameworkHint: string; // sent to /api/augment as framework
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
export const NEW_STACKS: NewStack[] = [
|
| 11 |
+
{
|
| 12 |
+
id: "express-react",
|
| 13 |
+
label: "Express + React (Vite)",
|
| 14 |
+
description: "React (Vite + Tailwind) for frontend and Express (ESM) for backend.",
|
| 15 |
+
frameworkHint: "express-react",
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
id: "nextjs",
|
| 19 |
+
label: "Next.js (App Router)",
|
| 20 |
+
description: "Full-stack Next.js 15 (App Router) with Tailwind and an /api route.",
|
| 21 |
+
frameworkHint: "nextjs",
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
id: "nestjs-react",
|
| 25 |
+
label: "NestJS + React",
|
| 26 |
+
description: "NestJS backend + React (Vite + Tailwind) frontend.",
|
| 27 |
+
frameworkHint: "nestjs-react",
|
| 28 |
+
},
|
| 29 |
+
];
|
| 30 |
+
|