Spaces:
Running
Running
Reupload OmniDev clean version
Browse files- app/api/augment/route.ts +40 -11
- components/new/scaffold.tsx +9 -17
- package-lock.json +1 -0
- package.json +1 -0
app/api/augment/route.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
import { NextRequest, NextResponse } from "next/server";
|
| 3 |
import { GoogleGenAI } from "@google/genai";
|
|
|
|
| 4 |
import { InferenceClient } from "@huggingface/inference";
|
| 5 |
import type { AugmentRequest, AugmentResponse } from "@/types";
|
| 6 |
|
|
@@ -27,18 +28,41 @@ Constraints:
|
|
| 27 |
- Produce compilable code for the chosen language/framework
|
| 28 |
`;
|
| 29 |
|
| 30 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
try {
|
| 32 |
-
return JSON.parse(
|
| 33 |
} catch {
|
| 34 |
-
//
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
}
|
| 41 |
-
throw new Error("Model did not return valid JSON");
|
| 42 |
}
|
| 43 |
}
|
| 44 |
|
|
@@ -93,13 +117,14 @@ export async function POST(req: NextRequest) {
|
|
| 93 |
text = res.choices?.[0]?.message?.content || "";
|
| 94 |
}
|
| 95 |
|
| 96 |
-
|
|
|
|
| 97 |
return NextResponse.json({ ok: false, message: "Empty model response" } as AugmentResponse, { status: 500 });
|
| 98 |
}
|
| 99 |
|
| 100 |
let json: any;
|
| 101 |
try {
|
| 102 |
-
json =
|
| 103 |
} catch (e: any) {
|
| 104 |
return NextResponse.json({ ok: false, message: e?.message || "Invalid JSON from model", raw: text } as any, { status: 500 });
|
| 105 |
}
|
|
@@ -108,6 +133,10 @@ export async function POST(req: NextRequest) {
|
|
| 108 |
if (json && json.ok && Array.isArray(json.files)) {
|
| 109 |
return NextResponse.json(json as AugmentResponse, { status: 200 });
|
| 110 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
return NextResponse.json({ ok: false, message: "Model returned unexpected shape", raw: json } as any, { status: 500 });
|
| 113 |
} catch (e: any) {
|
|
|
|
| 1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
import { NextRequest, NextResponse } from "next/server";
|
| 3 |
import { GoogleGenAI } from "@google/genai";
|
| 4 |
+
import JSON5 from "json5";
|
| 5 |
import { InferenceClient } from "@huggingface/inference";
|
| 6 |
import type { AugmentRequest, AugmentResponse } from "@/types";
|
| 7 |
|
|
|
|
| 28 |
- Produce compilable code for the chosen language/framework
|
| 29 |
`;
|
| 30 |
|
| 31 |
+
function cleanText(raw: string): string {
|
| 32 |
+
let t = raw || "";
|
| 33 |
+
t = t.replace(/```[a-zA-Z]*\n?/g, "");
|
| 34 |
+
t = t.replace(/```/g, "");
|
| 35 |
+
return t.trim();
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
function tryParseAny(jsonish: string): any {
|
| 39 |
+
// Try strict JSON first
|
| 40 |
try {
|
| 41 |
+
return JSON.parse(jsonish);
|
| 42 |
} catch {
|
| 43 |
+
// Relaxed JSON5 parse (handles trailing commas, etc.)
|
| 44 |
+
try {
|
| 45 |
+
// Prefer full string
|
| 46 |
+
return JSON5.parse(jsonish);
|
| 47 |
+
} catch {
|
| 48 |
+
// Try to extract object
|
| 49 |
+
const sObj = jsonish.indexOf("{");
|
| 50 |
+
const eObj = jsonish.lastIndexOf("}");
|
| 51 |
+
if (sObj >= 0 && eObj > sObj) {
|
| 52 |
+
const candidate = jsonish.slice(sObj, eObj + 1);
|
| 53 |
+
try { return JSON.parse(candidate); } catch {}
|
| 54 |
+
try { return JSON5.parse(candidate); } catch {}
|
| 55 |
+
}
|
| 56 |
+
// Try to extract array
|
| 57 |
+
const sArr = jsonish.indexOf("[");
|
| 58 |
+
const eArr = jsonish.lastIndexOf("]");
|
| 59 |
+
if (sArr >= 0 && eArr > sArr) {
|
| 60 |
+
const candidate = jsonish.slice(sArr, eArr + 1);
|
| 61 |
+
try { return JSON.parse(candidate); } catch {}
|
| 62 |
+
try { return JSON5.parse(candidate); } catch {}
|
| 63 |
+
}
|
| 64 |
+
throw new Error("Model did not return valid JSON");
|
| 65 |
}
|
|
|
|
| 66 |
}
|
| 67 |
}
|
| 68 |
|
|
|
|
| 117 |
text = res.choices?.[0]?.message?.content || "";
|
| 118 |
}
|
| 119 |
|
| 120 |
+
const cleaned = cleanText(text);
|
| 121 |
+
if (!cleaned) {
|
| 122 |
return NextResponse.json({ ok: false, message: "Empty model response" } as AugmentResponse, { status: 500 });
|
| 123 |
}
|
| 124 |
|
| 125 |
let json: any;
|
| 126 |
try {
|
| 127 |
+
json = tryParseAny(cleaned);
|
| 128 |
} catch (e: any) {
|
| 129 |
return NextResponse.json({ ok: false, message: e?.message || "Invalid JSON from model", raw: text } as any, { status: 500 });
|
| 130 |
}
|
|
|
|
| 133 |
if (json && json.ok && Array.isArray(json.files)) {
|
| 134 |
return NextResponse.json(json as AugmentResponse, { status: 200 });
|
| 135 |
}
|
| 136 |
+
// If model returned array at root, assume it's files
|
| 137 |
+
if (Array.isArray(json)) {
|
| 138 |
+
return NextResponse.json({ ok: true, files: json } as AugmentResponse, { status: 200 });
|
| 139 |
+
}
|
| 140 |
|
| 141 |
return NextResponse.json({ ok: false, message: "Model returned unexpected shape", raw: json } as any, { status: 500 });
|
| 142 |
} catch (e: any) {
|
components/new/scaffold.tsx
CHANGED
|
@@ -6,9 +6,8 @@ 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",
|
| 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 |
|
|
@@ -21,7 +20,7 @@ Title: ${title || 'OmniDev Full-Stack App'}
|
|
| 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 |
-
-
|
| 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}.
|
|
@@ -36,7 +35,7 @@ Title: ${title || 'OmniDev Next App'}
|
|
| 36 |
|
| 37 |
Requirements:
|
| 38 |
- Next.js (${jsOrTs}), App Router, TailwindCSS.
|
| 39 |
-
- Implement a landing page with
|
| 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\'.
|
|
@@ -48,7 +47,7 @@ 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
|
| 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 |
}
|
|
@@ -63,7 +62,6 @@ export default function ScaffoldNew() {
|
|
| 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() {
|
|
@@ -76,7 +74,7 @@ export default function ScaffoldNew() {
|
|
| 76 |
setLogs(["Starting scaffold via Augment..."]);
|
| 77 |
try {
|
| 78 |
// 1) Call augment to generate full-stack files
|
| 79 |
-
const instruction = buildInstruction(stack, lang,
|
| 80 |
const aug = await fetch('/api/augment', {
|
| 81 |
method: 'POST',
|
| 82 |
headers: { 'Content-Type': 'application/json' },
|
|
@@ -86,8 +84,9 @@ export default function ScaffoldNew() {
|
|
| 86 |
language: lang === 'ts' ? 'typescript' : 'javascript',
|
| 87 |
framework: stack,
|
| 88 |
response_type: 'file_updates',
|
| 89 |
-
|
| 90 |
-
|
|
|
|
| 91 |
}),
|
| 92 |
}).then(r => r.json());
|
| 93 |
if (!aug?.ok || !Array.isArray(aug.files) || aug.files.length === 0) {
|
|
@@ -141,14 +140,7 @@ export default function ScaffoldNew() {
|
|
| 141 |
</select>
|
| 142 |
<p className="text-xs text-neutral-500 mt-1">{NEW_STACKS.find(s => s.id === stack)?.description}</p>
|
| 143 |
</div>
|
| 144 |
-
|
| 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">
|
|
|
|
| 6 |
import { Button } from "@/components/ui/button";
|
| 7 |
import { toast } from "sonner";
|
| 8 |
import { NEW_STACKS, NewStackId } from "@/lib/new-stacks";
|
|
|
|
| 9 |
|
| 10 |
+
function buildInstruction(stack: NewStackId, lang: "js" | "ts", title?: string) {
|
| 11 |
const jsOrTs = lang === 'ts' ? 'TypeScript' : 'JavaScript';
|
| 12 |
const reactEntry = lang === 'ts' ? "/frontend/src/main.tsx and /frontend/src/App.tsx" : "/frontend/src/main.jsx and /frontend/src/App.jsx";
|
| 13 |
|
|
|
|
| 20 |
Requirements:
|
| 21 |
- Frontend under /frontend using React + Vite (${jsOrTs}), TailwindCSS preconfigured.
|
| 22 |
- Backend under /backend using Express (${jsOrTs}, ESM), with basic routes (GET /, GET /health) and CORS enabled.
|
| 23 |
+
- Create a visually striking Hero section (animated) appropriate to the app, optimized and accessible.
|
| 24 |
- Add a minimal README.md at root with start instructions.
|
| 25 |
- Provide package.json in both /frontend and /backend with scripts to start dev/prod.
|
| 26 |
- Provide /frontend/index.html and ${reactEntry}.
|
|
|
|
| 35 |
|
| 36 |
Requirements:
|
| 37 |
- Next.js (${jsOrTs}), App Router, TailwindCSS.
|
| 38 |
+
- Implement a landing page with an animated Hero section suitable for the domain.
|
| 39 |
- Add /api/health route that returns { ok: true }.
|
| 40 |
- Provide package.json with dev/build scripts.
|
| 41 |
- Keep it simple and runnable with \'next dev\'.
|
|
|
|
| 47 |
|
| 48 |
Requirements:
|
| 49 |
- Backend under /backend using NestJS (${jsOrTs}). Generate AppModule, AppController with GET / and GET /health. Enable CORS.
|
| 50 |
+
- Frontend under /frontend using React + Vite (${jsOrTs}), TailwindCSS with a modern animated Hero section appropriate to the app.
|
| 51 |
- Provide package.json in both apps with start/build scripts.
|
| 52 |
- Return STRICT JSON ONLY as file updates (no markdown), paths rooted from repo.`;
|
| 53 |
}
|
|
|
|
| 62 |
const [logs, setLogs] = useState<string[]>([]);
|
| 63 |
const [stack, setStack] = useState<NewStackId>("express-react");
|
| 64 |
const [lang, setLang] = useState<"js" | "ts">("js");
|
|
|
|
| 65 |
const [title, setTitle] = useState<string>("");
|
| 66 |
|
| 67 |
async function runScaffold() {
|
|
|
|
| 74 |
setLogs(["Starting scaffold via Augment..."]);
|
| 75 |
try {
|
| 76 |
// 1) Call augment to generate full-stack files
|
| 77 |
+
const instruction = buildInstruction(stack, lang, title);
|
| 78 |
const aug = await fetch('/api/augment', {
|
| 79 |
method: 'POST',
|
| 80 |
headers: { 'Content-Type': 'application/json' },
|
|
|
|
| 84 |
language: lang === 'ts' ? 'typescript' : 'javascript',
|
| 85 |
framework: stack,
|
| 86 |
response_type: 'file_updates',
|
| 87 |
+
// Force Gemini for scaffold stage
|
| 88 |
+
model: 'gemini-2.5-flash',
|
| 89 |
+
provider: 'google',
|
| 90 |
}),
|
| 91 |
}).then(r => r.json());
|
| 92 |
if (!aug?.ok || !Array.isArray(aug.files) || aug.files.length === 0) {
|
|
|
|
| 140 |
</select>
|
| 141 |
<p className="text-xs text-neutral-500 mt-1">{NEW_STACKS.find(s => s.id === stack)?.description}</p>
|
| 142 |
</div>
|
| 143 |
+
{/* Hero selection removed: AI decides the best hero automatically */}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
</div>
|
| 145 |
{error && <p className="text-red-400 text-sm mt-3">{error}</p>}
|
| 146 |
<ul className="text-sm text-neutral-400 space-y-1 mt-3">
|
package-lock.json
CHANGED
|
@@ -34,6 +34,7 @@
|
|
| 34 |
"clsx": "^2.1.1",
|
| 35 |
"date-fns": "^4.1.0",
|
| 36 |
"framer-motion": "^12.23.22",
|
|
|
|
| 37 |
"log4js": "^6.9.1",
|
| 38 |
"log4js-json-layout": "^2.2.3",
|
| 39 |
"lucide-react": "^0.542.0",
|
|
|
|
| 34 |
"clsx": "^2.1.1",
|
| 35 |
"date-fns": "^4.1.0",
|
| 36 |
"framer-motion": "^12.23.22",
|
| 37 |
+
"json5": "^2.2.3",
|
| 38 |
"log4js": "^6.9.1",
|
| 39 |
"log4js-json-layout": "^2.2.3",
|
| 40 |
"lucide-react": "^0.542.0",
|
package.json
CHANGED
|
@@ -8,6 +8,7 @@
|
|
| 8 |
"start": "next start"
|
| 9 |
},
|
| 10 |
"dependencies": {
|
|
|
|
| 11 |
"@google/genai": "^0.11.0",
|
| 12 |
"mime": "^4.0.4",
|
| 13 |
"@huggingface/hub": "^2.6.2",
|
|
|
|
| 8 |
"start": "next start"
|
| 9 |
},
|
| 10 |
"dependencies": {
|
| 11 |
+
"json5": "^2.2.3",
|
| 12 |
"@google/genai": "^0.11.0",
|
| 13 |
"mime": "^4.0.4",
|
| 14 |
"@huggingface/hub": "^2.6.2",
|