Spaces:
Running
Running
Reupload OmniDev clean version
Browse files- app/api/augment/route.ts +104 -0
- app/api/me/projects/[namespace]/[repoId]/apply/route.ts +59 -0
- app/console/page.tsx +10 -0
- app/layout.tsx +19 -14
- components/editor/index.tsx +1 -1
- components/iframe-detector/modal.tsx +1 -0
- components/omni-console/README.md +9 -0
- components/omni-console/index.tsx +76 -0
- lib/seo.ts +15 -18
- types/index.ts +28 -0
app/api/augment/route.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 7 |
+
const SYS = `You are Omni Engine, an autonomous code evolution system.
|
| 8 |
+
You receive:
|
| 9 |
+
- project context (tree + critical file excerpts)
|
| 10 |
+
- a user instruction
|
| 11 |
+
|
| 12 |
+
You MUST respond ONLY with strict JSON, no comments, no markdown fences.
|
| 13 |
+
JSON schema:
|
| 14 |
+
{
|
| 15 |
+
"ok": true,
|
| 16 |
+
"summary": string,
|
| 17 |
+
"logs": string[],
|
| 18 |
+
"files": [
|
| 19 |
+
{ "path": string, "action": "add"|"update"|"delete", "content"?: string, "note"?: string }
|
| 20 |
+
]
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
Constraints:
|
| 24 |
+
- Ensure paths are POSIX-like and rooted from repo (e.g., /frontend/src/App.tsx)
|
| 25 |
+
- For add/update, provide full file content (ready to run)
|
| 26 |
+
- Keep changes minimal and consistent
|
| 27 |
+
- Produce compilable code for the chosen language/framework
|
| 28 |
+
`;
|
| 29 |
+
|
| 30 |
+
function extractJson(text: string): any {
|
| 31 |
+
try {
|
| 32 |
+
return JSON.parse(text);
|
| 33 |
+
} catch {
|
| 34 |
+
// Try to strip code fences or extraneous text
|
| 35 |
+
const start = text.indexOf("{");
|
| 36 |
+
const end = text.lastIndexOf("}");
|
| 37 |
+
if (start >= 0 && end > start) {
|
| 38 |
+
const candidate = text.slice(start, end + 1);
|
| 39 |
+
return JSON.parse(candidate);
|
| 40 |
+
}
|
| 41 |
+
throw new Error("Model did not return valid JSON");
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
export async function POST(req: NextRequest) {
|
| 46 |
+
try {
|
| 47 |
+
const body = (await req.json()) as AugmentRequest;
|
| 48 |
+
const { context, instruction, language = "javascript", framework = "express-react", response_type = "file_updates", model, provider } = body || {} as AugmentRequest;
|
| 49 |
+
if (!context || !instruction) {
|
| 50 |
+
return NextResponse.json({ ok: false, message: "Missing context or instruction" } as AugmentResponse, { status: 400 });
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
const userPrompt = `Project Context:\n${context}\n\nInstruction:\n${instruction}\n\nTarget:\n- language: ${language}\n- framework: ${framework}\n- response_type: ${response_type}`;
|
| 54 |
+
|
| 55 |
+
let text = "";
|
| 56 |
+
if ((provider || "").toLowerCase() === "google" || (model || "").toLowerCase().startsWith("gemini-")) {
|
| 57 |
+
const apiKey = process.env.GEMINI_API_KEY;
|
| 58 |
+
if (!apiKey) return NextResponse.json({ ok: false, message: "Missing GEMINI_API_KEY" } as AugmentResponse, { status: 500 });
|
| 59 |
+
const ai = new GoogleGenAI({ apiKey });
|
| 60 |
+
const res = await ai.models.generateContent({
|
| 61 |
+
model: model || "gemini-2.5-flash",
|
| 62 |
+
contents: [
|
| 63 |
+
{ role: "user", parts: [{ text: SYS }] },
|
| 64 |
+
{ role: "user", parts: [{ text: userPrompt }] },
|
| 65 |
+
],
|
| 66 |
+
config: { maxOutputTokens: 4096 },
|
| 67 |
+
} as any);
|
| 68 |
+
text = (res as any)?.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
| 69 |
+
} else {
|
| 70 |
+
const token = process.env.HF_TOKEN || process.env.DEFAULT_HF_TOKEN;
|
| 71 |
+
const client = new InferenceClient(token);
|
| 72 |
+
const res = await client.chatCompletion({
|
| 73 |
+
model: model || "deepseek-ai/DeepSeek-V3.1",
|
| 74 |
+
provider: (provider as any) || undefined,
|
| 75 |
+
messages: [
|
| 76 |
+
{ role: "system", content: SYS },
|
| 77 |
+
{ role: "user", content: userPrompt },
|
| 78 |
+
],
|
| 79 |
+
});
|
| 80 |
+
text = res.choices?.[0]?.message?.content || "";
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
if (!text.trim()) {
|
| 84 |
+
return NextResponse.json({ ok: false, message: "Empty model response" } as AugmentResponse, { status: 500 });
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
let json: any;
|
| 88 |
+
try {
|
| 89 |
+
json = extractJson(text);
|
| 90 |
+
} catch (e: any) {
|
| 91 |
+
return NextResponse.json({ ok: false, message: e?.message || "Invalid JSON from model", raw: text } as any, { status: 500 });
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
// Minimal validation
|
| 95 |
+
if (json && json.ok && Array.isArray(json.files)) {
|
| 96 |
+
return NextResponse.json(json as AugmentResponse, { status: 200 });
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
return NextResponse.json({ ok: false, message: "Model returned unexpected shape", raw: json } as any, { status: 500 });
|
| 100 |
+
} catch (e: any) {
|
| 101 |
+
return NextResponse.json({ ok: false, message: e?.message || "Internal error" } as AugmentResponse, { status: 500 });
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
|
app/api/me/projects/[namespace]/[repoId]/apply/route.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
import { uploadFiles, deleteFiles } from "@huggingface/hub";
|
| 3 |
+
|
| 4 |
+
import { isAuthenticated } from "@/lib/auth";
|
| 5 |
+
import type { FileUpdate } from "@/types";
|
| 6 |
+
|
| 7 |
+
export async function POST(
|
| 8 |
+
req: NextRequest,
|
| 9 |
+
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
| 10 |
+
) {
|
| 11 |
+
const user = await isAuthenticated();
|
| 12 |
+
if (user instanceof NextResponse || !user) {
|
| 13 |
+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
const { files, commitTitle = "OmniDev: Apply changes" } = await req.json();
|
| 17 |
+
if (!Array.isArray(files) || files.length === 0) {
|
| 18 |
+
return NextResponse.json(
|
| 19 |
+
{ ok: false, error: "files array is required" },
|
| 20 |
+
{ status: 400 }
|
| 21 |
+
);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
const { namespace, repoId } = await params;
|
| 25 |
+
const repo = { type: "space" as const, name: `${namespace}/${repoId}` };
|
| 26 |
+
|
| 27 |
+
try {
|
| 28 |
+
const toDelete: string[] = [];
|
| 29 |
+
const toUpload: File[] = [];
|
| 30 |
+
|
| 31 |
+
(files as FileUpdate[]).forEach((f) => {
|
| 32 |
+
const path = (f.path || '').replace(/^\/+/, '');
|
| 33 |
+
if (f.action === 'delete') {
|
| 34 |
+
toDelete.push(path);
|
| 35 |
+
} else if ((f.action === 'add' || f.action === 'update') && typeof f.content === 'string') {
|
| 36 |
+
const type = path.endsWith('.html') ? 'text/html' : 'text/plain';
|
| 37 |
+
toUpload.push(new File([f.content], path, { type }));
|
| 38 |
+
}
|
| 39 |
+
});
|
| 40 |
+
|
| 41 |
+
const results: any = { ok: true, commits: [] };
|
| 42 |
+
|
| 43 |
+
if (toDelete.length > 0) {
|
| 44 |
+
const delRes = await deleteFiles({ repo, accessToken: user.token as string, paths: toDelete });
|
| 45 |
+
results.commits.push({ type: 'delete', ...delRes.commit });
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
if (toUpload.length > 0) {
|
| 49 |
+
const upRes = await uploadFiles({ repo, files: toUpload, accessToken: user.token as string, commitTitle });
|
| 50 |
+
results.commits.push({ type: 'upload', ...upRes.commit, title: commitTitle });
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
return NextResponse.json(results);
|
| 54 |
+
} catch (error: any) {
|
| 55 |
+
console.error('Apply changes failed:', error);
|
| 56 |
+
return NextResponse.json({ ok: false, error: error.message || 'Failed to apply changes' }, { status: 500 });
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
|
app/console/page.tsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import OmniConsole from "@/components/omni-console";
|
| 2 |
+
|
| 3 |
+
export default function ConsolePage() {
|
| 4 |
+
return (
|
| 5 |
+
<main className="max-w-5xl mx-auto p-6">
|
| 6 |
+
<OmniConsole />
|
| 7 |
+
</main>
|
| 8 |
+
);
|
| 9 |
+
}
|
| 10 |
+
|
app/layout.tsx
CHANGED
|
@@ -28,14 +28,14 @@ const ptSans = PT_Sans({
|
|
| 28 |
|
| 29 |
export const metadata: Metadata = {
|
| 30 |
...generateSEO({
|
| 31 |
-
title: "
|
| 32 |
description:
|
| 33 |
-
"
|
| 34 |
path: "/",
|
| 35 |
}),
|
| 36 |
appleWebApp: {
|
| 37 |
capable: true,
|
| 38 |
-
title: "
|
| 39 |
statusBarStyle: "black-translucent",
|
| 40 |
},
|
| 41 |
icons: {
|
|
@@ -77,16 +77,16 @@ export default async function RootLayout({
|
|
| 77 |
}>) {
|
| 78 |
const data = await getMe();
|
| 79 |
|
| 80 |
-
//
|
| 81 |
const structuredData = generateStructuredData("WebApplication", {
|
| 82 |
-
name: "
|
| 83 |
-
description: "
|
| 84 |
-
url: "https://
|
| 85 |
});
|
| 86 |
|
| 87 |
const organizationData = generateStructuredData("Organization", {
|
| 88 |
-
name: "
|
| 89 |
-
url: "https://
|
| 90 |
});
|
| 91 |
|
| 92 |
return (
|
|
@@ -105,11 +105,15 @@ export default async function RootLayout({
|
|
| 105 |
}}
|
| 106 |
/>
|
| 107 |
</head>
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
<body
|
| 114 |
className={`${inter.variable} ${ptSans.variable} antialiased bg-black dark h-[100dvh] overflow-hidden`}
|
| 115 |
>
|
|
@@ -128,3 +132,4 @@ export default async function RootLayout({
|
|
| 128 |
</html>
|
| 129 |
);
|
| 130 |
}
|
|
|
|
|
|
| 28 |
|
| 29 |
export const metadata: Metadata = {
|
| 30 |
...generateSEO({
|
| 31 |
+
title: "OmniDev | AI Project Builder",
|
| 32 |
description:
|
| 33 |
+
"OmniDev is an AI engineering environment that builds complete web projects (frontend + backend) from natural language.",
|
| 34 |
path: "/",
|
| 35 |
}),
|
| 36 |
appleWebApp: {
|
| 37 |
capable: true,
|
| 38 |
+
title: "OmniDev",
|
| 39 |
statusBarStyle: "black-translucent",
|
| 40 |
},
|
| 41 |
icons: {
|
|
|
|
| 77 |
}>) {
|
| 78 |
const data = await getMe();
|
| 79 |
|
| 80 |
+
// Structured data with OmniDev
|
| 81 |
const structuredData = generateStructuredData("WebApplication", {
|
| 82 |
+
name: "OmniDev",
|
| 83 |
+
description: "AI-powered full-stack project builder",
|
| 84 |
+
url: process.env.PUBLIC_BASE_URL || "https://omnidev.hf.co",
|
| 85 |
});
|
| 86 |
|
| 87 |
const organizationData = generateStructuredData("Organization", {
|
| 88 |
+
name: "OmniDev",
|
| 89 |
+
url: process.env.PUBLIC_BASE_URL || "https://omnidev.hf.co",
|
| 90 |
});
|
| 91 |
|
| 92 |
return (
|
|
|
|
| 105 |
}}
|
| 106 |
/>
|
| 107 |
</head>
|
| 108 |
+
{process.env.PUBLIC_BASE_URL && (
|
| 109 |
+
<Script
|
| 110 |
+
defer
|
| 111 |
+
data-domain={(process.env.PUBLIC_BASE_URL as string)
|
| 112 |
+
.replace("https://", "")
|
| 113 |
+
.replace("http://", "")}
|
| 114 |
+
src="https://plausible.io/js/script.js"
|
| 115 |
+
/>
|
| 116 |
+
)}
|
| 117 |
<body
|
| 118 |
className={`${inter.variable} ${ptSans.variable} antialiased bg-black dark h-[100dvh] overflow-hidden`}
|
| 119 |
>
|
|
|
|
| 132 |
</html>
|
| 133 |
);
|
| 134 |
}
|
| 135 |
+
|
components/editor/index.tsx
CHANGED
|
@@ -49,8 +49,8 @@ export const AppEditor = ({
|
|
| 49 |
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
| 50 |
|
| 51 |
useMount(() => {
|
|
|
|
| 52 |
if (isNew && pagesStorage) {
|
| 53 |
-
setPages(pagesStorage);
|
| 54 |
removePagesStorage();
|
| 55 |
}
|
| 56 |
});
|
|
|
|
| 49 |
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
| 50 |
|
| 51 |
useMount(() => {
|
| 52 |
+
// Always start clean for new projects; clear any leftover local storage pages
|
| 53 |
if (isNew && pagesStorage) {
|
|
|
|
| 54 |
removePagesStorage();
|
| 55 |
}
|
| 56 |
});
|
components/iframe-detector/modal.tsx
CHANGED
|
@@ -59,3 +59,4 @@ IframeWarningModalProps) {
|
|
| 59 |
</Dialog>
|
| 60 |
);
|
| 61 |
}
|
|
|
|
|
|
| 59 |
</Dialog>
|
| 60 |
);
|
| 61 |
}
|
| 62 |
+
|
components/omni-console/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Omni Console
|
| 2 |
+
|
| 3 |
+
This console demonstrates the Omni Engine augment flow:
|
| 4 |
+
|
| 5 |
+
- Use the Project Context to paste a tree and key file excerpts.
|
| 6 |
+
- Enter an Instruction (e.g., Add JWT auth with /auth/login route).
|
| 7 |
+
- Click Run to get an AugmentResponse including file updates.
|
| 8 |
+
- Provide the target Space "namespace/repoId" then click Apply Changes to commit to your Space.
|
| 9 |
+
|
components/omni-console/index.tsx
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import { useState } from "react";
|
| 3 |
+
import { Button } from "@/components/ui/button";
|
| 4 |
+
import { TextareaHTMLAttributes } from "react";
|
| 5 |
+
import { useAi } from "@/hooks/useAi";
|
| 6 |
+
|
| 7 |
+
export default function OmniConsole() {
|
| 8 |
+
const [instruction, setInstruction] = useState("");
|
| 9 |
+
const [context, setContext] = useState("/frontend\n /src\n /components\n/backend\n /routes\n /controllers\n");
|
| 10 |
+
const [out, setOut] = useState<any>(null);
|
| 11 |
+
const { model, provider } = useAi();
|
| 12 |
+
const [loading, setLoading] = useState(false);
|
| 13 |
+
const [space, setSpace] = useState("");
|
| 14 |
+
const [commitTitle, setCommitTitle] = useState("OmniDev: Apply changes");
|
| 15 |
+
|
| 16 |
+
const run = async () => {
|
| 17 |
+
setLoading(true);
|
| 18 |
+
setOut(null);
|
| 19 |
+
try {
|
| 20 |
+
const res = await fetch('/api/augment', {
|
| 21 |
+
method: 'POST',
|
| 22 |
+
headers: { 'Content-Type': 'application/json' },
|
| 23 |
+
body: JSON.stringify({ context, instruction, language: 'javascript', framework: 'express-react', response_type: 'file_updates', model, provider }),
|
| 24 |
+
});
|
| 25 |
+
const data = await res.json();
|
| 26 |
+
setOut(data);
|
| 27 |
+
} finally {
|
| 28 |
+
setLoading(false);
|
| 29 |
+
}
|
| 30 |
+
};
|
| 31 |
+
|
| 32 |
+
const apply = async () => {
|
| 33 |
+
if (!out?.files || !Array.isArray(out.files) || out.files.length === 0) return;
|
| 34 |
+
if (!space.includes('/')) {
|
| 35 |
+
alert('Specify target space as namespace/repoId');
|
| 36 |
+
return;
|
| 37 |
+
}
|
| 38 |
+
const [namespace, repoId] = space.split('/');
|
| 39 |
+
setLoading(true);
|
| 40 |
+
try {
|
| 41 |
+
const res = await fetch(`/api/me/projects/${namespace}/${repoId}/apply`, {
|
| 42 |
+
method: 'POST',
|
| 43 |
+
headers: { 'Content-Type': 'application/json' },
|
| 44 |
+
body: JSON.stringify({ files: out.files, commitTitle }),
|
| 45 |
+
});
|
| 46 |
+
const data = await res.json();
|
| 47 |
+
setOut((prev: any) => ({ ...prev, apply: data }));
|
| 48 |
+
} finally {
|
| 49 |
+
setLoading(false);
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
return (
|
| 54 |
+
<div className="p-4 border border-neutral-800 bg-neutral-900 rounded-xl text-neutral-200">
|
| 55 |
+
<h2 className="text-xl font-semibold mb-2">Omni Console</h2>
|
| 56 |
+
<p className="text-sm text-neutral-400 mb-4">Type engineering instructions; the engine returns structured file updates.</p>
|
| 57 |
+
<div className="grid gap-2">
|
| 58 |
+
<label className="text-sm">Project Context</label>
|
| 59 |
+
<textarea className="min-h-24 bg-neutral-800 rounded p-2 text-sm" value={context} onChange={(e) => setContext(e.target.value)} />
|
| 60 |
+
<label className="text-sm">Instruction</label>
|
| 61 |
+
<textarea className="min-h-24 bg-neutral-800 rounded p-2 text-sm" placeholder="e.g., Add JWT auth with /auth/login route" value={instruction} onChange={(e) => setInstruction(e.target.value)} />
|
| 62 |
+
<label className="text-sm">Target Space (namespace/repoId)</label>
|
| 63 |
+
<input className="bg-neutral-800 rounded p-2 text-sm" placeholder="yourname/your-space" value={space} onChange={(e) => setSpace(e.target.value)} />
|
| 64 |
+
<label className="text-sm">Commit Title</label>
|
| 65 |
+
<input className="bg-neutral-800 rounded p-2 text-sm" value={commitTitle} onChange={(e) => setCommitTitle(e.target.value)} />
|
| 66 |
+
</div>
|
| 67 |
+
<div className="flex items-center gap-2 mt-3">
|
| 68 |
+
<Button size="sm" onClick={run} disabled={loading || !instruction.trim()}>Run</Button>
|
| 69 |
+
<Button size="sm" variant="outline" onClick={apply} disabled={loading || !out?.files?.length}>Apply Changes</Button>
|
| 70 |
+
</div>
|
| 71 |
+
{out && (
|
| 72 |
+
<pre className="mt-4 text-xs bg-neutral-950 p-3 rounded overflow-x-auto max-h-80">{JSON.stringify(out, null, 2)}</pre>
|
| 73 |
+
)}
|
| 74 |
+
</div>
|
| 75 |
+
);
|
| 76 |
+
}
|
lib/seo.ts
CHANGED
|
@@ -10,14 +10,14 @@ interface SEOParams {
|
|
| 10 |
}
|
| 11 |
|
| 12 |
export function generateSEO({
|
| 13 |
-
title = "
|
| 14 |
-
description = "
|
| 15 |
path = "",
|
| 16 |
image = "/banner.png",
|
| 17 |
noIndex = false,
|
| 18 |
canonical,
|
| 19 |
}: SEOParams = {}): Metadata {
|
| 20 |
-
const baseUrl = process.env.PUBLIC_BASE_URL || "https://
|
| 21 |
const fullUrl = `${baseUrl}${path}`;
|
| 22 |
const canonicalUrl = canonical || fullUrl;
|
| 23 |
|
|
@@ -54,13 +54,13 @@ export function generateSEO({
|
|
| 54 |
title,
|
| 55 |
description,
|
| 56 |
url: canonicalUrl,
|
| 57 |
-
siteName: "
|
| 58 |
images: [
|
| 59 |
{
|
| 60 |
url: `${baseUrl}${image}`,
|
| 61 |
width: 1200,
|
| 62 |
height: 630,
|
| 63 |
-
alt: `${title} -
|
| 64 |
},
|
| 65 |
],
|
| 66 |
locale: "en_US",
|
|
@@ -71,14 +71,11 @@ export function generateSEO({
|
|
| 71 |
title,
|
| 72 |
description,
|
| 73 |
images: [`${baseUrl}${image}`],
|
| 74 |
-
creator: "@
|
| 75 |
},
|
| 76 |
other: {
|
| 77 |
-
// Prevent iframe embedding from unauthorized domains
|
| 78 |
'X-Frame-Options': 'SAMEORIGIN',
|
| 79 |
-
// Control how the page appears when shared
|
| 80 |
'og:image:secure_url': `${baseUrl}${image}`,
|
| 81 |
-
// Help search engines understand the primary URL
|
| 82 |
'rel': 'canonical',
|
| 83 |
},
|
| 84 |
};
|
|
@@ -94,9 +91,9 @@ export function generateStructuredData(type: 'WebApplication' | 'Organization' |
|
|
| 94 |
case 'WebApplication':
|
| 95 |
return {
|
| 96 |
...baseStructuredData,
|
| 97 |
-
name: '
|
| 98 |
-
description: '
|
| 99 |
-
url: process.env.PUBLIC_BASE_URL || 'https://
|
| 100 |
applicationCategory: 'DeveloperApplication',
|
| 101 |
operatingSystem: 'Web',
|
| 102 |
offers: {
|
|
@@ -106,8 +103,8 @@ export function generateStructuredData(type: 'WebApplication' | 'Organization' |
|
|
| 106 |
},
|
| 107 |
creator: {
|
| 108 |
'@type': 'Organization',
|
| 109 |
-
name: '
|
| 110 |
-
url: process.env.PUBLIC_BASE_URL || 'https://
|
| 111 |
},
|
| 112 |
...data,
|
| 113 |
};
|
|
@@ -115,12 +112,11 @@ export function generateStructuredData(type: 'WebApplication' | 'Organization' |
|
|
| 115 |
case 'Organization':
|
| 116 |
return {
|
| 117 |
...baseStructuredData,
|
| 118 |
-
name: '
|
| 119 |
-
url: process.env.PUBLIC_BASE_URL || 'https://
|
| 120 |
-
logo: `${process.env.PUBLIC_BASE_URL || 'https://
|
| 121 |
description: 'AI-powered web development platform',
|
| 122 |
sameAs: [
|
| 123 |
-
// Add social media links here if available
|
| 124 |
],
|
| 125 |
...data,
|
| 126 |
};
|
|
@@ -129,3 +125,4 @@ export function generateStructuredData(type: 'WebApplication' | 'Organization' |
|
|
| 129 |
return { ...baseStructuredData, ...data };
|
| 130 |
}
|
| 131 |
}
|
|
|
|
|
|
| 10 |
}
|
| 11 |
|
| 12 |
export function generateSEO({
|
| 13 |
+
title = "OmniDev | AI Project Builder",
|
| 14 |
+
description = "OmniDev is an AI engineering environment that builds complete web projects (frontend + backend) from natural language.",
|
| 15 |
path = "",
|
| 16 |
image = "/banner.png",
|
| 17 |
noIndex = false,
|
| 18 |
canonical,
|
| 19 |
}: SEOParams = {}): Metadata {
|
| 20 |
+
const baseUrl = process.env.PUBLIC_BASE_URL || "https://omnidev.hf.co";
|
| 21 |
const fullUrl = `${baseUrl}${path}`;
|
| 22 |
const canonicalUrl = canonical || fullUrl;
|
| 23 |
|
|
|
|
| 54 |
title,
|
| 55 |
description,
|
| 56 |
url: canonicalUrl,
|
| 57 |
+
siteName: "OmniDev",
|
| 58 |
images: [
|
| 59 |
{
|
| 60 |
url: `${baseUrl}${image}`,
|
| 61 |
width: 1200,
|
| 62 |
height: 630,
|
| 63 |
+
alt: `${title} - OmniDev`,
|
| 64 |
},
|
| 65 |
],
|
| 66 |
locale: "en_US",
|
|
|
|
| 71 |
title,
|
| 72 |
description,
|
| 73 |
images: [`${baseUrl}${image}`],
|
| 74 |
+
creator: "@omnidev",
|
| 75 |
},
|
| 76 |
other: {
|
|
|
|
| 77 |
'X-Frame-Options': 'SAMEORIGIN',
|
|
|
|
| 78 |
'og:image:secure_url': `${baseUrl}${image}`,
|
|
|
|
| 79 |
'rel': 'canonical',
|
| 80 |
},
|
| 81 |
};
|
|
|
|
| 91 |
case 'WebApplication':
|
| 92 |
return {
|
| 93 |
...baseStructuredData,
|
| 94 |
+
name: 'OmniDev',
|
| 95 |
+
description: 'AI-powered full-stack project builder',
|
| 96 |
+
url: process.env.PUBLIC_BASE_URL || 'https://omnidev.hf.co',
|
| 97 |
applicationCategory: 'DeveloperApplication',
|
| 98 |
operatingSystem: 'Web',
|
| 99 |
offers: {
|
|
|
|
| 103 |
},
|
| 104 |
creator: {
|
| 105 |
'@type': 'Organization',
|
| 106 |
+
name: 'OmniDev',
|
| 107 |
+
url: process.env.PUBLIC_BASE_URL || 'https://omnidev.hf.co',
|
| 108 |
},
|
| 109 |
...data,
|
| 110 |
};
|
|
|
|
| 112 |
case 'Organization':
|
| 113 |
return {
|
| 114 |
...baseStructuredData,
|
| 115 |
+
name: 'OmniDev',
|
| 116 |
+
url: process.env.PUBLIC_BASE_URL || 'https://omnidev.hf.co',
|
| 117 |
+
logo: `${process.env.PUBLIC_BASE_URL || 'https://omnidev.hf.co'}/logo.svg`,
|
| 118 |
description: 'AI-powered web development platform',
|
| 119 |
sameAs: [
|
|
|
|
| 120 |
],
|
| 121 |
...data,
|
| 122 |
};
|
|
|
|
| 125 |
return { ...baseStructuredData, ...data };
|
| 126 |
}
|
| 127 |
}
|
| 128 |
+
|
types/index.ts
CHANGED
|
@@ -55,3 +55,31 @@ export interface EnhancedSettings {
|
|
| 55 |
}
|
| 56 |
|
| 57 |
export type Theme = "light" | "dark" | undefined;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
}
|
| 56 |
|
| 57 |
export type Theme = "light" | "dark" | undefined;
|
| 58 |
+
|
| 59 |
+
// OmniDev Augmentation types
|
| 60 |
+
export type FileAction = "add" | "update" | "delete";
|
| 61 |
+
|
| 62 |
+
export interface FileUpdate {
|
| 63 |
+
path: string; // e.g., /frontend/src/App.tsx or /backend/routes/auth.ts
|
| 64 |
+
action: FileAction;
|
| 65 |
+
content?: string; // required for add/update; omit for delete
|
| 66 |
+
note?: string; // optional human-readable note
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
export interface AugmentRequest {
|
| 70 |
+
context: string; // project structure + important file snippets
|
| 71 |
+
instruction: string; // user instruction (e.g., add JWT auth)
|
| 72 |
+
language?: string; // javascript, typescript, python
|
| 73 |
+
framework?: string; // express-react, nextjs, nestjs, etc.
|
| 74 |
+
response_type?: "file_updates" | "explanation";
|
| 75 |
+
model?: string;
|
| 76 |
+
provider?: string; // 'google' to force Gemini
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
export interface AugmentResponse {
|
| 80 |
+
ok: boolean;
|
| 81 |
+
files?: FileUpdate[];
|
| 82 |
+
logs?: string[];
|
| 83 |
+
summary?: string;
|
| 84 |
+
message?: string;
|
| 85 |
+
}
|