Spaces:
Running
Running
Reupload OmniDev clean version
Browse files- app/actions/auth.ts +6 -7
- app/api/ask/route.ts +25 -12
- app/api/auth/login-url/route.ts +6 -14
- app/api/auth/route.ts +7 -10
- app/api/enhance/route.ts +61 -0
- app/new/page.tsx +2 -2
- components/animated-text/index.tsx +1 -1
- components/contexts/pro-context.tsx +4 -1
- components/editor/ask-ai/index.tsx +11 -8
- components/editor/ask-ai/prompt-enhancer.tsx +53 -0
- components/editor/index.tsx +1 -1
- components/iframe-detector/index.tsx +1 -2
- components/iframe-detector/modal.tsx +3 -3
- components/login-modal/index.tsx +2 -2
- components/my-projects/index.tsx +9 -11
- lib/api.ts +2 -2
- lib/prompts.ts +2 -2
- lib/seo.ts +5 -5
- lib/utils.ts +5 -3
- middleware.ts +2 -1
app/actions/auth.ts
CHANGED
|
@@ -5,13 +5,12 @@ import { headers } from "next/headers";
|
|
| 5 |
export async function getAuth() {
|
| 6 |
const authList = await headers();
|
| 7 |
const host = authList.get("host") ?? "localhost:3000";
|
| 8 |
-
const
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
const
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
"/auth/callback";
|
| 15 |
|
| 16 |
const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
|
| 17 |
return loginRedirectUrl;
|
|
|
|
| 5 |
export async function getAuth() {
|
| 6 |
const authList = await headers();
|
| 7 |
const host = authList.get("host") ?? "localhost:3000";
|
| 8 |
+
const baseFromEnv = process.env.PUBLIC_BASE_URL?.trim();
|
| 9 |
+
const isLocal = (baseFromEnv || host).includes("localhost");
|
| 10 |
+
const protocol = isLocal ? "http" : "https";
|
| 11 |
+
const baseUrl = baseFromEnv || `${protocol}://${host}`;
|
| 12 |
+
const redirectPath = process.env.AUTH_REDIRECT_PATH || "/auth/callback";
|
| 13 |
+
const redirect_uri = `${baseUrl}${redirectPath}`;
|
|
|
|
| 14 |
|
| 15 |
const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
|
| 16 |
return loginRedirectUrl;
|
app/api/ask/route.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { COLORS } from "@/lib/utils";
|
|
| 30 |
import { templates } from "@/lib/templates";
|
| 31 |
|
| 32 |
const ipAddresses = new Map();
|
|
|
|
| 33 |
|
| 34 |
export async function POST(request: NextRequest) {
|
| 35 |
const authHeaders = await headers();
|
|
@@ -74,8 +75,10 @@ export async function POST(request: NextRequest) {
|
|
| 74 |
: authHeaders.get("x-forwarded-for");
|
| 75 |
|
| 76 |
if (!token) {
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
| 79 |
return NextResponse.json(
|
| 80 |
{
|
| 81 |
ok: false,
|
|
@@ -124,18 +127,20 @@ export async function POST(request: NextRequest) {
|
|
| 124 |
}
|
| 125 |
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
| 126 |
const contents = [
|
| 127 |
-
{ role: "user", parts: [{ text: systemPrompt }] },
|
| 128 |
{ role: "user", parts: [{ text: userPrompt }] },
|
| 129 |
];
|
| 130 |
const estimatedInputTokens = estimateInputTokens(systemPrompt, userPrompt);
|
| 131 |
const dynamicMaxTokens = calculateMaxTokens(selectedProvider, estimatedInputTokens, true);
|
| 132 |
const config: any = {
|
| 133 |
-
|
| 134 |
};
|
| 135 |
const response = await ai.models.generateContentStream({
|
| 136 |
model: selectedModel.value,
|
| 137 |
config,
|
| 138 |
-
contents
|
|
|
|
|
|
|
|
|
|
| 139 |
});
|
| 140 |
|
| 141 |
// Newer SDKs return an async iterable of chunks with `.text`
|
|
@@ -177,7 +182,7 @@ export async function POST(request: NextRequest) {
|
|
| 177 |
// Explicitly close the writer after successful completion
|
| 178 |
await writer.close();
|
| 179 |
} catch (error: any) {
|
| 180 |
-
if (error.message?.includes("exceeded your monthly included credits")) {
|
| 181 |
await writer.write(
|
| 182 |
encoder.encode(
|
| 183 |
JSON.stringify({
|
|
@@ -283,8 +288,10 @@ export async function PUT(request: NextRequest) {
|
|
| 283 |
: authHeaders.get("x-forwarded-for");
|
| 284 |
|
| 285 |
if (!token) {
|
| 286 |
-
|
| 287 |
-
|
|
|
|
|
|
|
| 288 |
return NextResponse.json(
|
| 289 |
{
|
| 290 |
ok: false,
|
|
@@ -343,13 +350,19 @@ export async function PUT(request: NextRequest) {
|
|
| 343 |
}
|
| 344 |
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
| 345 |
const contents = [
|
| 346 |
-
{ role: "user", parts: [{ text: systemPrompt }] },
|
| 347 |
{ role: "user", parts: [{ text: userContext }] },
|
| 348 |
{ role: "model", parts: [{ text: assistantContext }] },
|
| 349 |
{ role: "user", parts: [{ text: prompt }] },
|
| 350 |
];
|
| 351 |
-
const config: any = {
|
| 352 |
-
const response = await ai.models.generateContentStream({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
const iterable: any = (response as any)?.stream ?? response;
|
| 354 |
for await (const c of iterable as AsyncIterable<any>) {
|
| 355 |
const text = (c && (c.text ?? c?.candidates?.[0]?.content?.parts?.[0]?.text)) || "";
|
|
@@ -624,7 +637,7 @@ This project was created with [DeepSite](https://deepsite.hf.co).
|
|
| 624 |
);
|
| 625 |
}
|
| 626 |
} catch (error: any) {
|
| 627 |
-
if (error.message?.includes("exceeded your monthly included credits")) {
|
| 628 |
return NextResponse.json(
|
| 629 |
{
|
| 630 |
ok: false,
|
|
|
|
| 30 |
import { templates } from "@/lib/templates";
|
| 31 |
|
| 32 |
const ipAddresses = new Map();
|
| 33 |
+
const IS_UNLIMITED_MODE = (process.env.UNLIMITED_MODE || '').toLowerCase() === 'true';
|
| 34 |
|
| 35 |
export async function POST(request: NextRequest) {
|
| 36 |
const authHeaders = await headers();
|
|
|
|
| 75 |
: authHeaders.get("x-forwarded-for");
|
| 76 |
|
| 77 |
if (!token) {
|
| 78 |
+
if (!IS_UNLIMITED_MODE) {
|
| 79 |
+
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
| 80 |
+
}
|
| 81 |
+
if (!IS_UNLIMITED_MODE && ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
| 82 |
return NextResponse.json(
|
| 83 |
{
|
| 84 |
ok: false,
|
|
|
|
| 127 |
}
|
| 128 |
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
| 129 |
const contents = [
|
|
|
|
| 130 |
{ role: "user", parts: [{ text: userPrompt }] },
|
| 131 |
];
|
| 132 |
const estimatedInputTokens = estimateInputTokens(systemPrompt, userPrompt);
|
| 133 |
const dynamicMaxTokens = calculateMaxTokens(selectedProvider, estimatedInputTokens, true);
|
| 134 |
const config: any = {
|
| 135 |
+
maxOutputTokens: dynamicMaxTokens,
|
| 136 |
};
|
| 137 |
const response = await ai.models.generateContentStream({
|
| 138 |
model: selectedModel.value,
|
| 139 |
config,
|
| 140 |
+
contents: [
|
| 141 |
+
{ role: "user", parts: [{ text: systemPrompt }] },
|
| 142 |
+
...contents,
|
| 143 |
+
],
|
| 144 |
});
|
| 145 |
|
| 146 |
// Newer SDKs return an async iterable of chunks with `.text`
|
|
|
|
| 182 |
// Explicitly close the writer after successful completion
|
| 183 |
await writer.close();
|
| 184 |
} catch (error: any) {
|
| 185 |
+
if (!IS_UNLIMITED_MODE && error.message?.includes("exceeded your monthly included credits")) {
|
| 186 |
await writer.write(
|
| 187 |
encoder.encode(
|
| 188 |
JSON.stringify({
|
|
|
|
| 288 |
: authHeaders.get("x-forwarded-for");
|
| 289 |
|
| 290 |
if (!token) {
|
| 291 |
+
if (!IS_UNLIMITED_MODE) {
|
| 292 |
+
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
| 293 |
+
}
|
| 294 |
+
if (!IS_UNLIMITED_MODE && ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
| 295 |
return NextResponse.json(
|
| 296 |
{
|
| 297 |
ok: false,
|
|
|
|
| 350 |
}
|
| 351 |
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
| 352 |
const contents = [
|
|
|
|
| 353 |
{ role: "user", parts: [{ text: userContext }] },
|
| 354 |
{ role: "model", parts: [{ text: assistantContext }] },
|
| 355 |
{ role: "user", parts: [{ text: prompt }] },
|
| 356 |
];
|
| 357 |
+
const config: any = { maxOutputTokens: dynamicMaxTokens };
|
| 358 |
+
const response = await ai.models.generateContentStream({
|
| 359 |
+
model: selectedModel.value,
|
| 360 |
+
config,
|
| 361 |
+
contents: [
|
| 362 |
+
{ role: "user", parts: [{ text: systemPrompt }] },
|
| 363 |
+
...contents,
|
| 364 |
+
],
|
| 365 |
+
});
|
| 366 |
const iterable: any = (response as any)?.stream ?? response;
|
| 367 |
for await (const c of iterable as AsyncIterable<any>) {
|
| 368 |
const text = (c && (c.text ?? c?.candidates?.[0]?.content?.parts?.[0]?.text)) || "";
|
|
|
|
| 637 |
);
|
| 638 |
}
|
| 639 |
} catch (error: any) {
|
| 640 |
+
if (!IS_UNLIMITED_MODE && error.message?.includes("exceeded your monthly included credits")) {
|
| 641 |
return NextResponse.json(
|
| 642 |
{
|
| 643 |
ok: false,
|
app/api/auth/login-url/route.ts
CHANGED
|
@@ -2,20 +2,12 @@ import { NextRequest, NextResponse } from "next/server";
|
|
| 2 |
|
| 3 |
export async function GET(req: NextRequest) {
|
| 4 |
const host = req.headers.get("host") ?? "localhost:3000";
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
} else {
|
| 12 |
-
url = "deepsite.hf.co";
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
const redirect_uri =
|
| 16 |
-
`${host.includes("localhost") ? "http://" : "https://"}` +
|
| 17 |
-
url +
|
| 18 |
-
"/auth/callback";
|
| 19 |
|
| 20 |
const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
|
| 21 |
|
|
|
|
| 2 |
|
| 3 |
export async function GET(req: NextRequest) {
|
| 4 |
const host = req.headers.get("host") ?? "localhost:3000";
|
| 5 |
+
const baseFromEnv = process.env.PUBLIC_BASE_URL?.trim();
|
| 6 |
+
const isLocal = (baseFromEnv || host).includes("localhost");
|
| 7 |
+
const protocol = isLocal ? "http" : "https";
|
| 8 |
+
const baseUrl = baseFromEnv || `${protocol}://${host}`;
|
| 9 |
+
const redirectPath = process.env.AUTH_REDIRECT_PATH || "/auth/callback";
|
| 10 |
+
const redirect_uri = `${baseUrl}${redirectPath}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
|
| 13 |
|
app/api/auth/route.ts
CHANGED
|
@@ -21,16 +21,13 @@ export async function POST(req: NextRequest) {
|
|
| 21 |
`${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`
|
| 22 |
).toString("base64")}`;
|
| 23 |
|
| 24 |
-
const host =
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
const
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
const redirect_uri =
|
| 31 |
-
`${host.includes("localhost") ? "http://" : "https://"}` +
|
| 32 |
-
url +
|
| 33 |
-
"/auth/callback";
|
| 34 |
const request_auth = await fetch("https://huggingface.co/oauth/token", {
|
| 35 |
method: "POST",
|
| 36 |
headers: {
|
|
|
|
| 21 |
`${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`
|
| 22 |
).toString("base64")}`;
|
| 23 |
|
| 24 |
+
const host = req.headers.get("host") ?? req.headers.get("origin") ?? "localhost:3000";
|
| 25 |
+
const baseFromEnv = process.env.PUBLIC_BASE_URL?.trim();
|
| 26 |
+
const isLocal = (baseFromEnv || host).includes("localhost");
|
| 27 |
+
const protocol = isLocal ? "http" : "https";
|
| 28 |
+
const baseUrl = baseFromEnv || `${protocol}://${host}`;
|
| 29 |
+
const redirectPath = process.env.AUTH_REDIRECT_PATH || "/auth/callback";
|
| 30 |
+
const redirect_uri = `${baseUrl}${redirectPath}`;
|
|
|
|
|
|
|
|
|
|
| 31 |
const request_auth = await fetch("https://huggingface.co/oauth/token", {
|
| 32 |
method: "POST",
|
| 33 |
headers: {
|
app/api/enhance/route.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
import { GoogleGenAI } from "@google/genai";
|
| 3 |
+
import { InferenceClient } from "@huggingface/inference";
|
| 4 |
+
|
| 5 |
+
export async function POST(req: NextRequest) {
|
| 6 |
+
try {
|
| 7 |
+
const { prompt, model, provider, enhancedSettings } = await req.json();
|
| 8 |
+
if (!prompt || typeof prompt !== 'string') {
|
| 9 |
+
return NextResponse.json({ ok: false, message: 'Missing prompt' }, { status: 400 });
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
const system = `You are a senior product designer and prompt engineer. Rewrite the user's prompt to make it clearer, more actionable and specific for an AI that builds complete web applications (frontend-first with modern, responsive, accessible UI; optionally include backend notes if requested). Always preserve the original intent but add concrete details, constraints, and acceptance criteria. If enhanced settings are provided (colors, theme, etc.), weave them naturally into the prompt. Return ONLY the rewritten prompt, nothing else.`;
|
| 13 |
+
|
| 14 |
+
const userParts: string[] = [
|
| 15 |
+
`Original prompt: ${prompt}`
|
| 16 |
+
];
|
| 17 |
+
if (enhancedSettings?.isActive) {
|
| 18 |
+
userParts.push(
|
| 19 |
+
`Enhanced settings: primary=${enhancedSettings?.primaryColor || 'auto'}, secondary=${enhancedSettings?.secondaryColor || 'auto'}, theme=${enhancedSettings?.theme || 'auto'}`
|
| 20 |
+
);
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
const selectedProvider = (provider || '').toLowerCase();
|
| 24 |
+
let text = '';
|
| 25 |
+
|
| 26 |
+
if (selectedProvider === 'google' || (model || '').toLowerCase().startsWith('gemini-')) {
|
| 27 |
+
const apiKey = process.env.GEMINI_API_KEY;
|
| 28 |
+
if (!apiKey) return NextResponse.json({ ok: false, message: 'Missing GEMINI_API_KEY' }, { status: 500 });
|
| 29 |
+
const ai = new GoogleGenAI({ apiKey });
|
| 30 |
+
const res = await ai.models.generateContent({
|
| 31 |
+
model: model || 'gemini-2.5-flash',
|
| 32 |
+
contents: [
|
| 33 |
+
{ role: 'user', parts: [{ text: system }] },
|
| 34 |
+
{ role: 'user', parts: [{ text: userParts.join('\n') }] },
|
| 35 |
+
],
|
| 36 |
+
config: { maxOutputTokens: 1024 },
|
| 37 |
+
} as any);
|
| 38 |
+
text = (res as any)?.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
| 39 |
+
} else {
|
| 40 |
+
const token = process.env.HF_TOKEN || process.env.DEFAULT_HF_TOKEN;
|
| 41 |
+
const client = new InferenceClient(token);
|
| 42 |
+
const res = await client.chatCompletion({
|
| 43 |
+
model: model,
|
| 44 |
+
provider: provider as any,
|
| 45 |
+
messages: [
|
| 46 |
+
{ role: 'system', content: system },
|
| 47 |
+
{ role: 'user', content: userParts.join('\n') },
|
| 48 |
+
],
|
| 49 |
+
});
|
| 50 |
+
text = res.choices?.[0]?.message?.content || '';
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
if (!text.trim()) {
|
| 54 |
+
return NextResponse.json({ ok: false, message: 'No content returned' }, { status: 500 });
|
| 55 |
+
}
|
| 56 |
+
return NextResponse.json({ ok: true, prompt: text });
|
| 57 |
+
} catch (e: any) {
|
| 58 |
+
return NextResponse.json({ ok: false, message: e?.message || 'Internal error' }, { status: 500 });
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
app/new/page.tsx
CHANGED
|
@@ -3,9 +3,9 @@ import { Metadata } from "next";
|
|
| 3 |
import { generateSEO } from "@/lib/seo";
|
| 4 |
|
| 5 |
export const metadata: Metadata = generateSEO({
|
| 6 |
-
title: "Create New Project -
|
| 7 |
description:
|
| 8 |
-
"Start building your next website with AI. Create a new project on
|
| 9 |
path: "/new",
|
| 10 |
});
|
| 11 |
|
|
|
|
| 3 |
import { generateSEO } from "@/lib/seo";
|
| 4 |
|
| 5 |
export const metadata: Metadata = generateSEO({
|
| 6 |
+
title: "Create New Project - OmniDev",
|
| 7 |
description:
|
| 8 |
+
"Start building your next website with AI. Create a new project on OmniDev and experience the power of AI-driven web development.",
|
| 9 |
path: "/new",
|
| 10 |
});
|
| 11 |
|
components/animated-text/index.tsx
CHANGED
|
@@ -100,7 +100,7 @@ export function AnimatedText({ className = "" }: AnimatedTextProps) {
|
|
| 100 |
|
| 101 |
return (
|
| 102 |
<p className={`font-mono ${className}`}>
|
| 103 |
-
Hey
|
| 104 |
{displayText.split("").map((char, index) => (
|
| 105 |
<span
|
| 106 |
key={`${currentSuggestionIndex}-${index}`}
|
|
|
|
| 100 |
|
| 101 |
return (
|
| 102 |
<p className={`font-mono ${className}`}>
|
| 103 |
+
Hey OmniDev,
|
| 104 |
{displayText.split("").map((char, index) => (
|
| 105 |
<span
|
| 106 |
key={`${currentSuggestionIndex}-${index}`}
|
components/contexts/pro-context.tsx
CHANGED
|
@@ -16,6 +16,7 @@ const ProContext = createContext<ProContextType | undefined>(undefined);
|
|
| 16 |
export function ProProvider({ children }: { children: ReactNode }) {
|
| 17 |
const [isOpen, setIsOpen] = useState(false);
|
| 18 |
const { pages } = useEditor();
|
|
|
|
| 19 |
|
| 20 |
const openProModal = () => {
|
| 21 |
setIsOpen(true);
|
|
@@ -34,7 +35,9 @@ export function ProProvider({ children }: { children: ReactNode }) {
|
|
| 34 |
return (
|
| 35 |
<ProContext.Provider value={value}>
|
| 36 |
{children}
|
| 37 |
-
|
|
|
|
|
|
|
| 38 |
</ProContext.Provider>
|
| 39 |
);
|
| 40 |
}
|
|
|
|
| 16 |
export function ProProvider({ children }: { children: ReactNode }) {
|
| 17 |
const [isOpen, setIsOpen] = useState(false);
|
| 18 |
const { pages } = useEditor();
|
| 19 |
+
const isUnlimited = (process.env.UNLIMITED_MODE || '').toLowerCase() === 'true';
|
| 20 |
|
| 21 |
const openProModal = () => {
|
| 22 |
setIsOpen(true);
|
|
|
|
| 35 |
return (
|
| 36 |
<ProContext.Provider value={value}>
|
| 37 |
{children}
|
| 38 |
+
{!isUnlimited && (
|
| 39 |
+
<ProModal open={isOpen} onClose={setIsOpen} pages={pages} />
|
| 40 |
+
)}
|
| 41 |
</ProContext.Provider>
|
| 42 |
);
|
| 43 |
}
|
components/editor/ask-ai/index.tsx
CHANGED
|
@@ -15,11 +15,12 @@ import { Uploader } from "@/components/editor/ask-ai/uploader";
|
|
| 15 |
import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
|
| 16 |
import { Selector } from "@/components/editor/ask-ai/selector";
|
| 17 |
import { PromptBuilder } from "@/components/editor/ask-ai/prompt-builder";
|
|
|
|
| 18 |
import { useUser } from "@/hooks/useUser";
|
| 19 |
import { useLoginModal } from "@/components/contexts/login-context";
|
| 20 |
import { Settings } from "./settings";
|
| 21 |
import { useProModal } from "@/components/contexts/pro-context";
|
| 22 |
-
import { MAX_FREE_PROJECTS } from "@/lib/utils";
|
| 23 |
import { PROMPTS_FOR_AI } from "@/lib/prompts";
|
| 24 |
|
| 25 |
export const AskAi = ({
|
|
@@ -80,7 +81,7 @@ export const AskAi = ({
|
|
| 80 |
|
| 81 |
const callAi = async (redesignMarkdown?: string) => {
|
| 82 |
removePromptStorage();
|
| 83 |
-
if (user && !user.isPro && projects.length >= MAX_FREE_PROJECTS)
|
| 84 |
return openProModal([]);
|
| 85 |
if (isAiWorking) return;
|
| 86 |
if (!redesignMarkdown && !prompt.trim()) return;
|
|
@@ -170,7 +171,7 @@ export const AskAi = ({
|
|
| 170 |
}}
|
| 171 |
>
|
| 172 |
<p className="text-sm font-medium text-neutral-300 group-hover:text-neutral-200 transition-colors duration-200">
|
| 173 |
-
{isThinking ? "
|
| 174 |
</p>
|
| 175 |
<ChevronDown
|
| 176 |
className={classNames(
|
|
@@ -224,8 +225,8 @@ export const AskAi = ({
|
|
| 224 |
: isUploading
|
| 225 |
? "Uploading images..."
|
| 226 |
: isAiWorking && !isSameHtml
|
| 227 |
-
? "
|
| 228 |
-
: "
|
| 229 |
}
|
| 230 |
/>
|
| 231 |
{isAiWorking && (
|
|
@@ -254,10 +255,10 @@ export const AskAi = ({
|
|
| 254 |
)}
|
| 255 |
placeholder={
|
| 256 |
selectedElement
|
| 257 |
-
? `Ask
|
| 258 |
: isFollowUp && (!isSameHtml || pages?.length > 1)
|
| 259 |
-
? "Ask
|
| 260 |
-
: "Ask
|
| 261 |
}
|
| 262 |
value={prompt}
|
| 263 |
onChange={(e) => setPrompt(e.target.value)}
|
|
@@ -288,6 +289,7 @@ export const AskAi = ({
|
|
| 288 |
enhancedSettings={enhancedSettings!}
|
| 289 |
setEnhancedSettings={setEnhancedSettings}
|
| 290 |
/>
|
|
|
|
| 291 |
<Settings
|
| 292 |
open={openProvider}
|
| 293 |
error={providerError}
|
|
@@ -320,3 +322,4 @@ export const AskAi = ({
|
|
| 320 |
</div>
|
| 321 |
);
|
| 322 |
};
|
|
|
|
|
|
| 15 |
import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
|
| 16 |
import { Selector } from "@/components/editor/ask-ai/selector";
|
| 17 |
import { PromptBuilder } from "@/components/editor/ask-ai/prompt-builder";
|
| 18 |
+
import { PromptEnhancer } from "@/components/editor/ask-ai/prompt-enhancer";
|
| 19 |
import { useUser } from "@/hooks/useUser";
|
| 20 |
import { useLoginModal } from "@/components/contexts/login-context";
|
| 21 |
import { Settings } from "./settings";
|
| 22 |
import { useProModal } from "@/components/contexts/pro-context";
|
| 23 |
+
import { IS_UNLIMITED, MAX_FREE_PROJECTS } from "@/lib/utils";
|
| 24 |
import { PROMPTS_FOR_AI } from "@/lib/prompts";
|
| 25 |
|
| 26 |
export const AskAi = ({
|
|
|
|
| 81 |
|
| 82 |
const callAi = async (redesignMarkdown?: string) => {
|
| 83 |
removePromptStorage();
|
| 84 |
+
if (!IS_UNLIMITED && user && !user.isPro && projects.length >= MAX_FREE_PROJECTS)
|
| 85 |
return openProModal([]);
|
| 86 |
if (isAiWorking) return;
|
| 87 |
if (!redesignMarkdown && !prompt.trim()) return;
|
|
|
|
| 171 |
}}
|
| 172 |
>
|
| 173 |
<p className="text-sm font-medium text-neutral-300 group-hover:text-neutral-200 transition-colors duration-200">
|
| 174 |
+
{isThinking ? "OmniDev is thinking..." : "OmniDev's plan"}
|
| 175 |
</p>
|
| 176 |
<ChevronDown
|
| 177 |
className={classNames(
|
|
|
|
| 225 |
: isUploading
|
| 226 |
? "Uploading images..."
|
| 227 |
: isAiWorking && !isSameHtml
|
| 228 |
+
? "OmniDev is working..."
|
| 229 |
+
: "OmniDev is thinking..."
|
| 230 |
}
|
| 231 |
/>
|
| 232 |
{isAiWorking && (
|
|
|
|
| 255 |
)}
|
| 256 |
placeholder={
|
| 257 |
selectedElement
|
| 258 |
+
? `Ask OmniDev about ${selectedElement.tagName.toLowerCase()}...`
|
| 259 |
: isFollowUp && (!isSameHtml || pages?.length > 1)
|
| 260 |
+
? "Ask OmniDev for edits"
|
| 261 |
+
: "Ask OmniDev anything..."
|
| 262 |
}
|
| 263 |
value={prompt}
|
| 264 |
onChange={(e) => setPrompt(e.target.value)}
|
|
|
|
| 289 |
enhancedSettings={enhancedSettings!}
|
| 290 |
setEnhancedSettings={setEnhancedSettings}
|
| 291 |
/>
|
| 292 |
+
<PromptEnhancer prompt={prompt} setPrompt={setPrompt} enhancedSettings={enhancedSettings!} />
|
| 293 |
<Settings
|
| 294 |
open={openProvider}
|
| 295 |
error={providerError}
|
|
|
|
| 322 |
</div>
|
| 323 |
);
|
| 324 |
};
|
| 325 |
+
|
components/editor/ask-ai/prompt-enhancer.tsx
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import { WandSparkles } from "lucide-react";
|
| 3 |
+
import { Button } from "@/components/ui/button";
|
| 4 |
+
import { useState } from "react";
|
| 5 |
+
import { useAi } from "@/hooks/useAi";
|
| 6 |
+
import { EnhancedSettings } from "@/types";
|
| 7 |
+
import { toast } from "sonner";
|
| 8 |
+
|
| 9 |
+
export function PromptEnhancer({
|
| 10 |
+
prompt,
|
| 11 |
+
setPrompt,
|
| 12 |
+
enhancedSettings,
|
| 13 |
+
}: {
|
| 14 |
+
prompt: string;
|
| 15 |
+
setPrompt: (p: string) => void;
|
| 16 |
+
enhancedSettings: EnhancedSettings;
|
| 17 |
+
}) {
|
| 18 |
+
const { model, provider, globalAiLoading } = useAi();
|
| 19 |
+
const [loading, setLoading] = useState(false);
|
| 20 |
+
|
| 21 |
+
const enhance = async () => {
|
| 22 |
+
if (!prompt.trim()) return;
|
| 23 |
+
try {
|
| 24 |
+
setLoading(true);
|
| 25 |
+
const res = await fetch('/api/enhance', {
|
| 26 |
+
method: 'POST',
|
| 27 |
+
headers: { 'Content-Type': 'application/json' },
|
| 28 |
+
body: JSON.stringify({ prompt, model, provider, enhancedSettings }),
|
| 29 |
+
});
|
| 30 |
+
const data = await res.json();
|
| 31 |
+
if (!res.ok || !data.ok) throw new Error(data.message || 'Failed to enhance prompt');
|
| 32 |
+
setPrompt(data.prompt);
|
| 33 |
+
toast.success('Prompt enhanced');
|
| 34 |
+
} catch (e: any) {
|
| 35 |
+
toast.error(e?.message || 'Failed to enhance');
|
| 36 |
+
} finally {
|
| 37 |
+
setLoading(false);
|
| 38 |
+
}
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
return (
|
| 42 |
+
<Button
|
| 43 |
+
size="xs"
|
| 44 |
+
variant="outline"
|
| 45 |
+
className="!rounded-md"
|
| 46 |
+
disabled={loading || globalAiLoading || !prompt.trim()}
|
| 47 |
+
onClick={enhance}
|
| 48 |
+
>
|
| 49 |
+
<WandSparkles className="size-3.5" /> Improve
|
| 50 |
+
</Button>
|
| 51 |
+
);
|
| 52 |
+
}
|
| 53 |
+
|
components/editor/index.tsx
CHANGED
|
@@ -103,7 +103,7 @@ export const AppEditor = ({
|
|
| 103 |
readOnlyMessage: {
|
| 104 |
value: currentCommit
|
| 105 |
? "You can't edit the code, as this is an old version of the project."
|
| 106 |
-
: "Wait for
|
| 107 |
isTrusted: true,
|
| 108 |
},
|
| 109 |
}}
|
|
|
|
| 103 |
readOnlyMessage: {
|
| 104 |
value: currentCommit
|
| 105 |
? "You can't edit the code, as this is an old version of the project."
|
| 106 |
+
: "Wait for OmniDev to finish working...",
|
| 107 |
isTrusted: true,
|
| 108 |
},
|
| 109 |
}}
|
components/iframe-detector/index.tsx
CHANGED
|
@@ -15,8 +15,7 @@ export default function IframeDetector() {
|
|
| 15 |
host.endsWith(".hf.co") ||
|
| 16 |
host === "huggingface.co" ||
|
| 17 |
host === "hf.co" ||
|
| 18 |
-
host === "
|
| 19 |
-
host === "deepsite.hf.co"
|
| 20 |
);
|
| 21 |
};
|
| 22 |
|
|
|
|
| 15 |
host.endsWith(".hf.co") ||
|
| 16 |
host === "huggingface.co" ||
|
| 17 |
host === "hf.co" ||
|
| 18 |
+
host === (process.env.NEXT_PUBLIC_ALLOWED_IFRAME_HOST || "omnidev.hf.co")
|
|
|
|
| 19 |
);
|
| 20 |
};
|
| 21 |
|
components/iframe-detector/modal.tsx
CHANGED
|
@@ -21,7 +21,7 @@ export default function IframeWarningModal({
|
|
| 21 |
}: // onOpenChange,
|
| 22 |
IframeWarningModalProps) {
|
| 23 |
const handleVisitSite = () => {
|
| 24 |
-
window.open("https://
|
| 25 |
};
|
| 26 |
|
| 27 |
return (
|
|
@@ -33,7 +33,7 @@ IframeWarningModalProps) {
|
|
| 33 |
<DialogTitle>Unauthorized Embedding</DialogTitle>
|
| 34 |
</div>
|
| 35 |
<DialogDescription className="text-left">
|
| 36 |
-
You're viewing
|
| 37 |
best experience and security, please visit the official website
|
| 38 |
directly.
|
| 39 |
</DialogDescription>
|
|
@@ -52,7 +52,7 @@ IframeWarningModalProps) {
|
|
| 52 |
<DialogFooter className="flex-col sm:flex-row gap-2">
|
| 53 |
<Button onClick={handleVisitSite} className="w-full sm:w-auto">
|
| 54 |
<ExternalLink className="mr-2 h-4 w-4" />
|
| 55 |
-
Visit
|
| 56 |
</Button>
|
| 57 |
</DialogFooter>
|
| 58 |
</DialogContent>
|
|
|
|
| 21 |
}: // onOpenChange,
|
| 22 |
IframeWarningModalProps) {
|
| 23 |
const handleVisitSite = () => {
|
| 24 |
+
window.open(process.env.NEXT_PUBLIC_HOME_URL || "https://omnidev.hf.co", "_blank");
|
| 25 |
};
|
| 26 |
|
| 27 |
return (
|
|
|
|
| 33 |
<DialogTitle>Unauthorized Embedding</DialogTitle>
|
| 34 |
</div>
|
| 35 |
<DialogDescription className="text-left">
|
| 36 |
+
You're viewing OmniDev through an unauthorized iframe. For the
|
| 37 |
best experience and security, please visit the official website
|
| 38 |
directly.
|
| 39 |
</DialogDescription>
|
|
|
|
| 52 |
<DialogFooter className="flex-col sm:flex-row gap-2">
|
| 53 |
<Button onClick={handleVisitSite} className="w-full sm:w-auto">
|
| 54 |
<ExternalLink className="mr-2 h-4 w-4" />
|
| 55 |
+
Visit OmniDev
|
| 56 |
</Button>
|
| 57 |
</DialogFooter>
|
| 58 |
</DialogContent>
|
components/login-modal/index.tsx
CHANGED
|
@@ -9,8 +9,8 @@ import { useEditor } from "@/hooks/useEditor";
|
|
| 9 |
export const LoginModal = ({
|
| 10 |
open,
|
| 11 |
onClose,
|
| 12 |
-
title = "Log In to use
|
| 13 |
-
description = "Log In through your Hugging Face account to continue using
|
| 14 |
prompt,
|
| 15 |
}: {
|
| 16 |
open: boolean;
|
|
|
|
| 9 |
export const LoginModal = ({
|
| 10 |
open,
|
| 11 |
onClose,
|
| 12 |
+
title = "Log In to use OmniDev",
|
| 13 |
+
description = "Log In through your Hugging Face account to continue using OmniDev.",
|
| 14 |
prompt,
|
| 15 |
}: {
|
| 16 |
open: boolean;
|
components/my-projects/index.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import { toast } from "sonner";
|
|
| 7 |
import { useUser } from "@/hooks/useUser";
|
| 8 |
import { ProjectType } from "@/types";
|
| 9 |
import { ProjectCard } from "./project-card";
|
| 10 |
-
import { MAX_FREE_PROJECTS } from "@/lib/utils";
|
| 11 |
import { ProTag } from "@/components/pro-modal";
|
| 12 |
import { Button } from "@/components/ui/button";
|
| 13 |
import { useProModal } from "@/components/contexts/pro-context";
|
|
@@ -39,28 +39,26 @@ export function MyProjects() {
|
|
| 39 |
<div className="text-left">
|
| 40 |
<h1 className="text-3xl font-bold text-white">
|
| 41 |
<span className="capitalize">{user?.fullname}</span>'s
|
| 42 |
-
|
| 43 |
-
{user?.isPro
|
| 44 |
-
""
|
| 45 |
-
) : (
|
| 46 |
<span className="text-neutral-400 text-2xl ml-2">
|
| 47 |
({projects.length}/{MAX_FREE_PROJECTS})
|
| 48 |
</span>
|
| 49 |
)}
|
| 50 |
</h1>
|
| 51 |
<p className="text-muted-foreground text-lg mt-1 max-w-xl">
|
| 52 |
-
{user?.isPro
|
| 53 |
-
"Create, manage, and explore your
|
| 54 |
-
|
| 55 |
<span>
|
| 56 |
Upgrade to{" "}
|
| 57 |
<ProTag className="mx-1" onClick={() => openProModal([])} />{" "}
|
| 58 |
-
to create
|
| 59 |
</span>
|
| 60 |
)}
|
| 61 |
</p>
|
| 62 |
</div>
|
| 63 |
-
{projects?.length >= MAX_FREE_PROJECTS && !user?.isPro ? (
|
| 64 |
<Button
|
| 65 |
size="default"
|
| 66 |
variant="default"
|
|
@@ -74,7 +72,7 @@ export function MyProjects() {
|
|
| 74 |
)}
|
| 75 |
</header>
|
| 76 |
<div className="mt-8 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
| 77 |
-
{projects.length < MAX_FREE_PROJECTS || user?.isPro ? (
|
| 78 |
<Link
|
| 79 |
href="/new"
|
| 80 |
className="bg-neutral-900 rounded-xl h-64 lg:h-44 flex items-center justify-center text-neutral-300 border border-neutral-800 hover:brightness-110 transition-all duration-200"
|
|
|
|
| 7 |
import { useUser } from "@/hooks/useUser";
|
| 8 |
import { ProjectType } from "@/types";
|
| 9 |
import { ProjectCard } from "./project-card";
|
| 10 |
+
import { IS_UNLIMITED, MAX_FREE_PROJECTS } from "@/lib/utils";
|
| 11 |
import { ProTag } from "@/components/pro-modal";
|
| 12 |
import { Button } from "@/components/ui/button";
|
| 13 |
import { useProModal } from "@/components/contexts/pro-context";
|
|
|
|
| 39 |
<div className="text-left">
|
| 40 |
<h1 className="text-3xl font-bold text-white">
|
| 41 |
<span className="capitalize">{user?.fullname}</span>'s
|
| 42 |
+
OmniDev Projects
|
| 43 |
+
{!IS_UNLIMITED && !user?.isPro && (
|
|
|
|
|
|
|
| 44 |
<span className="text-neutral-400 text-2xl ml-2">
|
| 45 |
({projects.length}/{MAX_FREE_PROJECTS})
|
| 46 |
</span>
|
| 47 |
)}
|
| 48 |
</h1>
|
| 49 |
<p className="text-muted-foreground text-lg mt-1 max-w-xl">
|
| 50 |
+
{IS_UNLIMITED || user?.isPro
|
| 51 |
+
? "Create, manage, and explore your OmniDev projects."
|
| 52 |
+
: (
|
| 53 |
<span>
|
| 54 |
Upgrade to{" "}
|
| 55 |
<ProTag className="mx-1" onClick={() => openProModal([])} />{" "}
|
| 56 |
+
to create more projects.
|
| 57 |
</span>
|
| 58 |
)}
|
| 59 |
</p>
|
| 60 |
</div>
|
| 61 |
+
{projects?.length >= MAX_FREE_PROJECTS && !user?.isPro && !IS_UNLIMITED ? (
|
| 62 |
<Button
|
| 63 |
size="default"
|
| 64 |
variant="default"
|
|
|
|
| 72 |
)}
|
| 73 |
</header>
|
| 74 |
<div className="mt-8 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
| 75 |
+
{IS_UNLIMITED || projects.length < MAX_FREE_PROJECTS || user?.isPro ? (
|
| 76 |
<Link
|
| 77 |
href="/new"
|
| 78 |
className="bg-neutral-900 rounded-xl h-64 lg:h-44 flex items-center justify-center text-neutral-300 border border-neutral-800 hover:brightness-110 transition-all duration-200"
|
lib/api.ts
CHANGED
|
@@ -2,14 +2,14 @@ import axios from "axios";
|
|
| 2 |
import MY_TOKEN_KEY from "./get-cookie-name";
|
| 3 |
|
| 4 |
export const api = axios.create({
|
| 5 |
-
baseURL: `/api`,
|
| 6 |
headers: {
|
| 7 |
cache: "no-store",
|
| 8 |
},
|
| 9 |
});
|
| 10 |
|
| 11 |
export const apiServer = axios.create({
|
| 12 |
-
baseURL: process.env.NEXT_APP_API_URL as string
|
| 13 |
headers: {
|
| 14 |
cache: "no-store",
|
| 15 |
},
|
|
|
|
| 2 |
import MY_TOKEN_KEY from "./get-cookie-name";
|
| 3 |
|
| 4 |
export const api = axios.create({
|
| 5 |
+
baseURL: (typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_APP_API_URL as string) : undefined) || `/api`,
|
| 6 |
headers: {
|
| 7 |
cache: "no-store",
|
| 8 |
},
|
| 9 |
});
|
| 10 |
|
| 11 |
export const apiServer = axios.create({
|
| 12 |
+
baseURL: (process.env.NEXT_APP_API_URL as string) || `http://localhost:3000/api`,
|
| 13 |
headers: {
|
| 14 |
cache: "no-store",
|
| 15 |
},
|
lib/prompts.ts
CHANGED
|
@@ -25,7 +25,7 @@ You create website in a way a designer would, using ONLY HTML, CSS and Javascrip
|
|
| 25 |
Try to create the best UI possible. Important: Make the website responsive by using TailwindCSS. Use it as much as you can, if you can't use it, use custom css (make sure to import tailwind with <script src="https://cdn.tailwindcss.com"></script> in the head).
|
| 26 |
Also try to elaborate as much as you can, to create something unique, with a great design.
|
| 27 |
If you want to use ICONS import Feather Icons (Make sure to add <script src="https://unpkg.com/feather-icons"></script> and <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> in the head., and <script>feather.replace();</script> in the body. Ex : <i data-feather="user"></i>).
|
| 28 |
-
For interactive animations you can use: Vanta.js (Make sure to add <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script> and <script>VANTA.GLOBE({...</script> in the body.).
|
| 29 |
Don't hesitate to use real public API for the datas, you can find good ones here https://github.com/public-apis/public-apis depending on what the user asks for.
|
| 30 |
You can create multiple pages website at once (following the format rules below) or a Single Page Application. But make sure to create multiple pages if the user asks for different pages.
|
| 31 |
${PROMPT_FOR_IMAGE_GENERATION}
|
|
@@ -167,4 +167,4 @@ export const PROMPTS_FOR_AI = [
|
|
| 167 |
"Create a Password Generator, with a password generator section, and a history section.",
|
| 168 |
"Create a Currency Converter, with a currency converter section, and a history section.",
|
| 169 |
"Create a Dictionary, with a dictionary section, and a history section.",
|
| 170 |
-
];
|
|
|
|
| 25 |
Try to create the best UI possible. Important: Make the website responsive by using TailwindCSS. Use it as much as you can, if you can't use it, use custom css (make sure to import tailwind with <script src="https://cdn.tailwindcss.com"></script> in the head).
|
| 26 |
Also try to elaborate as much as you can, to create something unique, with a great design.
|
| 27 |
If you want to use ICONS import Feather Icons (Make sure to add <script src="https://unpkg.com/feather-icons"></script> and <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> in the head., and <script>feather.replace();</script> in the body. Ex : <i data-feather="user"></i>).
|
| 28 |
+
For interactive animations you can use: Vanta.js (Make sure to add <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script> and <script>VANTA.GLOBE({...</script> in the body.). Prefer stunning hero sections with animated backgrounds (e.g., animated gradients, particles, waves, noise overlay, parallax) while keeping performance and accessibility in mind.
|
| 29 |
Don't hesitate to use real public API for the datas, you can find good ones here https://github.com/public-apis/public-apis depending on what the user asks for.
|
| 30 |
You can create multiple pages website at once (following the format rules below) or a Single Page Application. But make sure to create multiple pages if the user asks for different pages.
|
| 31 |
${PROMPT_FOR_IMAGE_GENERATION}
|
|
|
|
| 167 |
"Create a Password Generator, with a password generator section, and a history section.",
|
| 168 |
"Create a Currency Converter, with a currency converter section, and a history section.",
|
| 169 |
"Create a Dictionary, with a dictionary section, and a history section.",
|
| 170 |
+
];
|
lib/seo.ts
CHANGED
|
@@ -17,7 +17,7 @@ export function generateSEO({
|
|
| 17 |
noIndex = false,
|
| 18 |
canonical,
|
| 19 |
}: SEOParams = {}): Metadata {
|
| 20 |
-
const baseUrl = "https://deepsite.hf.co";
|
| 21 |
const fullUrl = `${baseUrl}${path}`;
|
| 22 |
const canonicalUrl = canonical || fullUrl;
|
| 23 |
|
|
@@ -96,7 +96,7 @@ export function generateStructuredData(type: 'WebApplication' | 'Organization' |
|
|
| 96 |
...baseStructuredData,
|
| 97 |
name: 'DeepSite',
|
| 98 |
description: 'Build websites with AI, no code required',
|
| 99 |
-
url: 'https://deepsite.hf.co',
|
| 100 |
applicationCategory: 'DeveloperApplication',
|
| 101 |
operatingSystem: 'Web',
|
| 102 |
offers: {
|
|
@@ -107,7 +107,7 @@ export function generateStructuredData(type: 'WebApplication' | 'Organization' |
|
|
| 107 |
creator: {
|
| 108 |
'@type': 'Organization',
|
| 109 |
name: 'DeepSite',
|
| 110 |
-
url: 'https://deepsite.hf.co',
|
| 111 |
},
|
| 112 |
...data,
|
| 113 |
};
|
|
@@ -116,8 +116,8 @@ export function generateStructuredData(type: 'WebApplication' | 'Organization' |
|
|
| 116 |
return {
|
| 117 |
...baseStructuredData,
|
| 118 |
name: 'DeepSite',
|
| 119 |
-
url: 'https://deepsite.hf.co',
|
| 120 |
-
logo: 'https://deepsite.hf.co/logo.svg
|
| 121 |
description: 'AI-powered web development platform',
|
| 122 |
sameAs: [
|
| 123 |
// Add social media links here if available
|
|
|
|
| 17 |
noIndex = false,
|
| 18 |
canonical,
|
| 19 |
}: SEOParams = {}): Metadata {
|
| 20 |
+
const baseUrl = process.env.PUBLIC_BASE_URL || "https://deepsite.hf.co";
|
| 21 |
const fullUrl = `${baseUrl}${path}`;
|
| 22 |
const canonicalUrl = canonical || fullUrl;
|
| 23 |
|
|
|
|
| 96 |
...baseStructuredData,
|
| 97 |
name: 'DeepSite',
|
| 98 |
description: 'Build websites with AI, no code required',
|
| 99 |
+
url: process.env.PUBLIC_BASE_URL || 'https://deepsite.hf.co',
|
| 100 |
applicationCategory: 'DeveloperApplication',
|
| 101 |
operatingSystem: 'Web',
|
| 102 |
offers: {
|
|
|
|
| 107 |
creator: {
|
| 108 |
'@type': 'Organization',
|
| 109 |
name: 'DeepSite',
|
| 110 |
+
url: process.env.PUBLIC_BASE_URL || 'https://deepsite.hf.co',
|
| 111 |
},
|
| 112 |
...data,
|
| 113 |
};
|
|
|
|
| 116 |
return {
|
| 117 |
...baseStructuredData,
|
| 118 |
name: 'DeepSite',
|
| 119 |
+
url: process.env.PUBLIC_BASE_URL || 'https://deepsite.hf.co',
|
| 120 |
+
logo: `${process.env.PUBLIC_BASE_URL || 'https://deepsite.hf.co'}/logo.svg`,
|
| 121 |
description: 'AI-powered web development platform',
|
| 122 |
sameAs: [
|
| 123 |
// Add social media links here if available
|
lib/utils.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { clsx, type ClassValue } from "clsx";
|
| 2 |
import { twMerge } from "tailwind-merge";
|
| 3 |
|
| 4 |
export function cn(...inputs: ClassValue[]) {
|
|
@@ -16,7 +16,9 @@ export const COLORS = [
|
|
| 16 |
];
|
| 17 |
|
| 18 |
export const getPTag = (repoId: string) => {
|
| 19 |
-
|
|
|
|
| 20 |
};
|
| 21 |
|
| 22 |
-
export const
|
|
|
|
|
|
| 1 |
+
import { clsx, type ClassValue } from "clsx";
|
| 2 |
import { twMerge } from "tailwind-merge";
|
| 3 |
|
| 4 |
export function cn(...inputs: ClassValue[]) {
|
|
|
|
| 16 |
];
|
| 17 |
|
| 18 |
export const getPTag = (repoId: string) => {
|
| 19 |
+
const base = process.env.PUBLIC_BASE_URL || "https://omnidev.hf.co";
|
| 20 |
+
return `<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="${base}/logo.svg" alt="OmniDev Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="${base}" style="color: #fff;text-decoration: underline;" target="_blank">OmniDev</a> - <a href="${base}?remix=${repoId}" style="color: #fff;text-decoration: underline;" target="_blank">Remix</a></p>`;
|
| 21 |
};
|
| 22 |
|
| 23 |
+
export const IS_UNLIMITED = (process.env.UNLIMITED_MODE || "").toLowerCase() === "true";
|
| 24 |
+
export const MAX_FREE_PROJECTS = IS_UNLIMITED ? 1000000 : 3;
|
middleware.ts
CHANGED
|
@@ -10,7 +10,8 @@ export function middleware(request: NextRequest) {
|
|
| 10 |
if (request.nextUrl.pathname.startsWith('/_next/static')) {
|
| 11 |
response.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
|
| 12 |
}
|
| 13 |
-
|
|
|
|
| 14 |
|
| 15 |
return response;
|
| 16 |
}
|
|
|
|
| 10 |
if (request.nextUrl.pathname.startsWith('/_next/static')) {
|
| 11 |
response.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
|
| 12 |
}
|
| 13 |
+
const canonicalBase = process.env.PUBLIC_BASE_URL || `https://${request.nextUrl.host}`;
|
| 14 |
+
response.headers.set('X-Canonical-URL', `${canonicalBase}${request.nextUrl.pathname}`);
|
| 15 |
|
| 16 |
return response;
|
| 17 |
}
|