Commit
·
8185bfc
1
Parent(s):
829f9f6
refactor to local use only
Browse files- README.md +49 -16
- app/(public)/projects/page.tsx +2 -2
- app/actions/auth.ts +0 -18
- app/actions/projects.ts +0 -63
- app/api/ask-ai/route.ts +81 -241
- app/api/auth/route.ts +0 -86
- app/api/me/projects/[namespace]/[repoId]/route.ts +0 -237
- app/api/me/projects/route.ts +0 -126
- app/api/me/route.ts +0 -25
- app/auth/callback/page.tsx +0 -72
- app/auth/page.tsx +0 -28
- app/layout.tsx +2 -22
- app/projects/[namespace]/[repoId]/page.tsx +0 -40
- components/contexts/app-context.tsx +1 -2
- components/editor/ask-ai/index.tsx +8 -33
- components/editor/ask-ai/settings.tsx +64 -126
- components/editor/deploy-button/index.tsx +71 -103
- components/editor/footer/index.tsx +15 -19
- components/editor/index.tsx +11 -25
- components/editor/preview/index.tsx +88 -27
- components/editor/save-button/index.tsx +22 -19
- components/login-modal/index.tsx +5 -18
- components/my-projects/load-project.tsx +90 -135
- components/my-projects/project-card.tsx +3 -12
- components/pro-modal/index.tsx +4 -56
- components/public/navigation/index.tsx +0 -16
- components/user-menu/index.tsx +2 -8
- hooks/useUser.ts +11 -65
- lib/api.ts +4 -19
- lib/auth.ts +12 -62
- lib/consts.ts +11 -6
- lib/get-cookie-name.ts +0 -3
- lib/mongodb.ts +0 -28
- lib/providers.ts +8 -47
- package-lock.json +21 -45
- package.json +1 -2
README.md
CHANGED
|
@@ -1,22 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
---
|
| 15 |
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
-
##
|
|
|
|
| 21 |
|
| 22 |
-
Follow [this discussion](https://huggingface.co/spaces/enzostvs/deepsite/discussions/74)
|
|
|
|
| 1 |
+
|
| 2 |
+
# DeepSite v2 🚀
|
| 3 |
+
|
| 4 |
+

|
| 5 |
+
|
| 6 |
+
Run **DeepSite** in your own environment, without relying on external services!
|
| 7 |
+
Perfect for those who want to customize, integrate, or have full control over the platform.
|
| 8 |
+
|
| 9 |
---
|
| 10 |
+
|
| 11 |
+
## How to run DeepSite v2 locally
|
| 12 |
+
|
| 13 |
+
### 1. Clone the repository
|
| 14 |
+
```bash
|
| 15 |
+
git clone https://github.com/MartinsMessias/deepsite-locally.git
|
| 16 |
+
cd deepsite-locally
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
### 2. Install dependencies
|
| 20 |
+
Make sure you have **Node.js** installed (recommended v18+).
|
| 21 |
+
```bash
|
| 22 |
+
npm install
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
### 3. Run in development mode
|
| 26 |
+
```bash
|
| 27 |
+
npm run dev
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
### 4. For build and production
|
| 31 |
+
```bash
|
| 32 |
+
npm run build
|
| 33 |
+
npm run start
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
---
|
| 37 |
|
| 38 |
+
## Available scripts
|
| 39 |
+
|
| 40 |
+
- `npm run dev` — Starts the development environment (Next.js + Turbopack)
|
| 41 |
+
- `npm run build` — Builds for production
|
| 42 |
+
- `npm run start` — Runs the server in production mode
|
| 43 |
+
- `npm run lint` — Runs the linter
|
| 44 |
+
|
| 45 |
+
## Main dependencies
|
| 46 |
|
| 47 |
+
Next.js, React 19, Mongoose, TailwindCSS, Radix UI, Lucide, Monaco Editor, React Query, Zod, Axios, Sonner, and more.
|
| 48 |
+
|
| 49 |
+
See all dependencies in [`package.json`](./package.json).
|
| 50 |
+
|
| 51 |
+
---
|
| 52 |
|
| 53 |
+
## Keywords
|
| 54 |
+
deepsite local hosting, deepsite run locally, deepsite self-hosted, how to run deepsite locally, install deepsite on your machine, deepsite local server setup, deepsite offline mode, deepsite localhost tutorial, deploy deepsite on your own server, deepsite self-install guide, how to host deepsite on localhost step-by-step, can deepsite run offline on my computer, deepsite docker installation guide, full guide to running deepsite locally without internet, deepsite self-host vs cloud hosting comparison, deepsite performance tips when running locally, requirements to run deepsite on local environment, best practices for self-hosting deepsite platform, how to speed up deepsite in a local environment, common errors when running deepsite locally and how to fix, deepsite vs other ai site builders local run comparison, top reasons to run deepsite on your own server, is deepsite open-source and local-friendly
|
| 55 |
|
|
|
app/(public)/projects/page.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
import { redirect } from "next/navigation";
|
| 2 |
|
| 3 |
import { MyProjects } from "@/components/my-projects";
|
| 4 |
-
import { getProjects } from "@/app/actions/projects";
|
| 5 |
|
| 6 |
export default async function ProjectsPage() {
|
| 7 |
-
const { ok, projects } =
|
|
|
|
| 8 |
if (!ok) {
|
| 9 |
redirect("/");
|
| 10 |
}
|
|
|
|
| 1 |
import { redirect } from "next/navigation";
|
| 2 |
|
| 3 |
import { MyProjects } from "@/components/my-projects";
|
|
|
|
| 4 |
|
| 5 |
export default async function ProjectsPage() {
|
| 6 |
+
const { ok, projects } = { ok: true, projects: [] };
|
| 7 |
+
|
| 8 |
if (!ok) {
|
| 9 |
redirect("/");
|
| 10 |
}
|
app/actions/auth.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
| 1 |
-
"use server";
|
| 2 |
-
|
| 3 |
-
import { headers } from "next/headers";
|
| 4 |
-
|
| 5 |
-
export async function getAuth() {
|
| 6 |
-
const authList = await headers();
|
| 7 |
-
const host = authList.get("host") ?? "localhost:3000";
|
| 8 |
-
const url = host.includes("/spaces/enzostvs")
|
| 9 |
-
? "enzostvs-deepsite.hf.space"
|
| 10 |
-
: host;
|
| 11 |
-
const redirect_uri =
|
| 12 |
-
`${host.includes("localhost") ? "http://" : "https://"}` +
|
| 13 |
-
url +
|
| 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;
|
| 18 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/actions/projects.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
| 1 |
-
"use server";
|
| 2 |
-
|
| 3 |
-
import { isAuthenticated } from "@/lib/auth";
|
| 4 |
-
import { NextResponse } from "next/server";
|
| 5 |
-
import dbConnect from "@/lib/mongodb";
|
| 6 |
-
import Project from "@/models/Project";
|
| 7 |
-
import { Project as ProjectType } from "@/types";
|
| 8 |
-
|
| 9 |
-
export async function getProjects(): Promise<{
|
| 10 |
-
ok: boolean;
|
| 11 |
-
projects: ProjectType[];
|
| 12 |
-
}> {
|
| 13 |
-
const user = await isAuthenticated();
|
| 14 |
-
|
| 15 |
-
if (user instanceof NextResponse || !user) {
|
| 16 |
-
return {
|
| 17 |
-
ok: false,
|
| 18 |
-
projects: [],
|
| 19 |
-
};
|
| 20 |
-
}
|
| 21 |
-
|
| 22 |
-
await dbConnect();
|
| 23 |
-
const projects = await Project.find({
|
| 24 |
-
user_id: user?.id,
|
| 25 |
-
})
|
| 26 |
-
.sort({ _createdAt: -1 })
|
| 27 |
-
.limit(100)
|
| 28 |
-
.lean();
|
| 29 |
-
if (!projects) {
|
| 30 |
-
return {
|
| 31 |
-
ok: false,
|
| 32 |
-
projects: [],
|
| 33 |
-
};
|
| 34 |
-
}
|
| 35 |
-
return {
|
| 36 |
-
ok: true,
|
| 37 |
-
projects: JSON.parse(JSON.stringify(projects)) as ProjectType[],
|
| 38 |
-
};
|
| 39 |
-
}
|
| 40 |
-
|
| 41 |
-
export async function getProject(
|
| 42 |
-
namespace: string,
|
| 43 |
-
repoId: string
|
| 44 |
-
): Promise<ProjectType | null> {
|
| 45 |
-
const user = await isAuthenticated();
|
| 46 |
-
|
| 47 |
-
if (user instanceof NextResponse || !user) {
|
| 48 |
-
return null;
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
await dbConnect();
|
| 52 |
-
const project = await Project.findOne({
|
| 53 |
-
user_id: user.id,
|
| 54 |
-
namespace,
|
| 55 |
-
repoId,
|
| 56 |
-
}).lean();
|
| 57 |
-
|
| 58 |
-
if (!project) {
|
| 59 |
-
return null;
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
return JSON.parse(JSON.stringify(project)) as ProjectType;
|
| 63 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/ask-ai/route.ts
CHANGED
|
@@ -1,28 +1,24 @@
|
|
| 1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
import type { NextRequest } from "next/server";
|
| 3 |
import { NextResponse } from "next/server";
|
| 4 |
-
import
|
| 5 |
-
import { InferenceClient } from "@huggingface/inference";
|
| 6 |
|
| 7 |
-
import { MODELS
|
| 8 |
import {
|
| 9 |
DIVIDER,
|
| 10 |
FOLLOW_UP_SYSTEM_PROMPT,
|
| 11 |
INITIAL_SYSTEM_PROMPT,
|
| 12 |
-
MAX_REQUESTS_PER_IP,
|
| 13 |
REPLACE_END,
|
| 14 |
SEARCH_START,
|
| 15 |
} from "@/lib/prompts";
|
| 16 |
-
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 17 |
-
|
| 18 |
-
const ipAddresses = new Map();
|
| 19 |
|
| 20 |
export async function POST(request: NextRequest) {
|
| 21 |
-
const authHeaders = await headers();
|
| 22 |
-
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
| 23 |
-
|
| 24 |
const body = await request.json();
|
| 25 |
-
const { prompt,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
if (!model || (!prompt && !redesignMarkdown)) {
|
| 28 |
return NextResponse.json(
|
|
@@ -41,56 +37,6 @@ export async function POST(request: NextRequest) {
|
|
| 41 |
);
|
| 42 |
}
|
| 43 |
|
| 44 |
-
if (!selectedModel.providers.includes(provider) && provider !== "auto") {
|
| 45 |
-
return NextResponse.json(
|
| 46 |
-
{
|
| 47 |
-
ok: false,
|
| 48 |
-
error: `The selected model does not support the ${provider} provider.`,
|
| 49 |
-
openSelectProvider: true,
|
| 50 |
-
},
|
| 51 |
-
{ status: 400 }
|
| 52 |
-
);
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
let token = userToken;
|
| 56 |
-
let billTo: string | null = null;
|
| 57 |
-
|
| 58 |
-
/**
|
| 59 |
-
* Handle local usage token, this bypass the need for a user token
|
| 60 |
-
* and allows local testing without authentication.
|
| 61 |
-
* This is useful for development and testing purposes.
|
| 62 |
-
*/
|
| 63 |
-
if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
|
| 64 |
-
token = process.env.HF_TOKEN;
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
const ip = authHeaders.get("x-forwarded-for")?.includes(",")
|
| 68 |
-
? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
|
| 69 |
-
: authHeaders.get("x-forwarded-for");
|
| 70 |
-
|
| 71 |
-
if (!token) {
|
| 72 |
-
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
| 73 |
-
if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
| 74 |
-
return NextResponse.json(
|
| 75 |
-
{
|
| 76 |
-
ok: false,
|
| 77 |
-
openLogin: true,
|
| 78 |
-
message: "Log In to continue using the service",
|
| 79 |
-
},
|
| 80 |
-
{ status: 429 }
|
| 81 |
-
);
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
-
token = process.env.DEFAULT_HF_TOKEN as string;
|
| 85 |
-
billTo = "huggingface";
|
| 86 |
-
}
|
| 87 |
-
|
| 88 |
-
const DEFAULT_PROVIDER = PROVIDERS.novita;
|
| 89 |
-
const selectedProvider =
|
| 90 |
-
provider === "auto"
|
| 91 |
-
? PROVIDERS[selectedModel.autoProvider as keyof typeof PROVIDERS]
|
| 92 |
-
: PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER;
|
| 93 |
-
|
| 94 |
try {
|
| 95 |
// Create a stream response
|
| 96 |
const encoder = new TextEncoder();
|
|
@@ -109,96 +55,44 @@ export async function POST(request: NextRequest) {
|
|
| 109 |
(async () => {
|
| 110 |
let completeResponse = "";
|
| 111 |
try {
|
| 112 |
-
const
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
while (true) {
|
| 137 |
-
const { done, value } = await chatCompletion.next();
|
| 138 |
-
if (done) {
|
| 139 |
break;
|
| 140 |
}
|
| 141 |
-
|
| 142 |
-
const chunk = value.choices[0]?.delta?.content;
|
| 143 |
-
if (chunk) {
|
| 144 |
-
let newChunk = chunk;
|
| 145 |
-
if (!selectedModel?.isThinker) {
|
| 146 |
-
if (provider !== "sambanova") {
|
| 147 |
-
await writer.write(encoder.encode(chunk));
|
| 148 |
-
completeResponse += chunk;
|
| 149 |
-
|
| 150 |
-
if (completeResponse.includes("</html>")) {
|
| 151 |
-
break;
|
| 152 |
-
}
|
| 153 |
-
} else {
|
| 154 |
-
if (chunk.includes("</html>")) {
|
| 155 |
-
newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");
|
| 156 |
-
}
|
| 157 |
-
completeResponse += newChunk;
|
| 158 |
-
await writer.write(encoder.encode(newChunk));
|
| 159 |
-
if (newChunk.includes("</html>")) {
|
| 160 |
-
break;
|
| 161 |
-
}
|
| 162 |
-
}
|
| 163 |
-
} else {
|
| 164 |
-
const lastThinkTagIndex =
|
| 165 |
-
completeResponse.lastIndexOf("</think>");
|
| 166 |
-
completeResponse += newChunk;
|
| 167 |
-
await writer.write(encoder.encode(newChunk));
|
| 168 |
-
if (lastThinkTagIndex !== -1) {
|
| 169 |
-
const afterLastThinkTag = completeResponse.slice(
|
| 170 |
-
lastThinkTagIndex + "</think>".length
|
| 171 |
-
);
|
| 172 |
-
if (afterLastThinkTag.includes("</html>")) {
|
| 173 |
-
break;
|
| 174 |
-
}
|
| 175 |
-
}
|
| 176 |
-
}
|
| 177 |
-
}
|
| 178 |
}
|
| 179 |
} catch (error: any) {
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
} else {
|
| 191 |
-
await writer.write(
|
| 192 |
-
encoder.encode(
|
| 193 |
-
JSON.stringify({
|
| 194 |
-
ok: false,
|
| 195 |
-
message:
|
| 196 |
-
error.message ||
|
| 197 |
-
"An error occurred while processing your request.",
|
| 198 |
-
})
|
| 199 |
-
)
|
| 200 |
-
);
|
| 201 |
-
}
|
| 202 |
} finally {
|
| 203 |
await writer?.close();
|
| 204 |
}
|
|
@@ -209,7 +103,6 @@ export async function POST(request: NextRequest) {
|
|
| 209 |
return NextResponse.json(
|
| 210 |
{
|
| 211 |
ok: false,
|
| 212 |
-
openSelectProvider: true,
|
| 213 |
message:
|
| 214 |
error?.message || "An error occurred while processing your request.",
|
| 215 |
},
|
|
@@ -219,11 +112,13 @@ export async function POST(request: NextRequest) {
|
|
| 219 |
}
|
| 220 |
|
| 221 |
export async function PUT(request: NextRequest) {
|
| 222 |
-
const authHeaders = await headers();
|
| 223 |
-
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
| 224 |
-
|
| 225 |
const body = await request.json();
|
| 226 |
-
const { prompt, html, previousPrompt,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
| 228 |
if (!prompt || !html) {
|
| 229 |
return NextResponse.json(
|
|
@@ -232,87 +127,43 @@ export async function PUT(request: NextRequest) {
|
|
| 232 |
);
|
| 233 |
}
|
| 234 |
|
| 235 |
-
const selectedModel = MODELS
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
* This is useful for development and testing purposes.
|
| 244 |
-
*/
|
| 245 |
-
if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
|
| 246 |
-
token = process.env.HF_TOKEN;
|
| 247 |
}
|
| 248 |
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
if (!token) {
|
| 254 |
-
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
| 255 |
-
if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
| 256 |
-
return NextResponse.json(
|
| 257 |
{
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
message: "Log In to continue using the service",
|
| 261 |
},
|
| 262 |
-
{
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
model: selectedModel.value,
|
| 282 |
-
provider: selectedProvider.id as any,
|
| 283 |
-
messages: [
|
| 284 |
-
{
|
| 285 |
-
role: "system",
|
| 286 |
-
content: FOLLOW_UP_SYSTEM_PROMPT,
|
| 287 |
-
},
|
| 288 |
-
{
|
| 289 |
-
role: "user",
|
| 290 |
-
content: previousPrompt
|
| 291 |
-
? previousPrompt
|
| 292 |
-
: "You are modifying the HTML file based on the user's request.",
|
| 293 |
-
},
|
| 294 |
-
{
|
| 295 |
-
role: "assistant",
|
| 296 |
-
|
| 297 |
-
content: `The current code is: \n\`\`\`html\n${html}\n\`\`\` ${
|
| 298 |
-
selectedElementHtml
|
| 299 |
-
? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\``
|
| 300 |
-
: ""
|
| 301 |
-
}`,
|
| 302 |
-
},
|
| 303 |
-
{
|
| 304 |
-
role: "user",
|
| 305 |
-
content: prompt,
|
| 306 |
-
},
|
| 307 |
-
],
|
| 308 |
-
...(selectedProvider.id !== "sambanova"
|
| 309 |
-
? {
|
| 310 |
-
max_tokens: selectedProvider.max_tokens,
|
| 311 |
-
}
|
| 312 |
-
: {}),
|
| 313 |
-
},
|
| 314 |
-
billTo ? { billTo } : {}
|
| 315 |
-
);
|
| 316 |
|
| 317 |
const chunk = response.choices[0]?.message?.content;
|
| 318 |
if (!chunk) {
|
|
@@ -387,24 +238,13 @@ export async function PUT(request: NextRequest) {
|
|
| 387 |
);
|
| 388 |
}
|
| 389 |
} catch (error: any) {
|
| 390 |
-
if (error.message?.includes("exceeded your monthly included credits")) {
|
| 391 |
-
return NextResponse.json(
|
| 392 |
-
{
|
| 393 |
-
ok: false,
|
| 394 |
-
openProModal: true,
|
| 395 |
-
message: error.message,
|
| 396 |
-
},
|
| 397 |
-
{ status: 402 }
|
| 398 |
-
);
|
| 399 |
-
}
|
| 400 |
return NextResponse.json(
|
| 401 |
{
|
| 402 |
ok: false,
|
| 403 |
-
openSelectProvider: true,
|
| 404 |
message:
|
| 405 |
error.message || "An error occurred while processing your request.",
|
| 406 |
},
|
| 407 |
{ status: 500 }
|
| 408 |
);
|
| 409 |
}
|
| 410 |
-
}
|
|
|
|
| 1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
import type { NextRequest } from "next/server";
|
| 3 |
import { NextResponse } from "next/server";
|
| 4 |
+
import OpenAI from "openai";
|
|
|
|
| 5 |
|
| 6 |
+
import { MODELS } from "@/lib/providers";
|
| 7 |
import {
|
| 8 |
DIVIDER,
|
| 9 |
FOLLOW_UP_SYSTEM_PROMPT,
|
| 10 |
INITIAL_SYSTEM_PROMPT,
|
|
|
|
| 11 |
REPLACE_END,
|
| 12 |
SEARCH_START,
|
| 13 |
} from "@/lib/prompts";
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
export async function POST(request: NextRequest) {
|
|
|
|
|
|
|
|
|
|
| 16 |
const body = await request.json();
|
| 17 |
+
const { prompt, model, redesignMarkdown, html, apiKey, customModel } = body;
|
| 18 |
+
|
| 19 |
+
const openai = new OpenAI({
|
| 20 |
+
apiKey: apiKey || process.env.OPENAI_API_KEY,
|
| 21 |
+
});
|
| 22 |
|
| 23 |
if (!model || (!prompt && !redesignMarkdown)) {
|
| 24 |
return NextResponse.json(
|
|
|
|
| 37 |
);
|
| 38 |
}
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
try {
|
| 41 |
// Create a stream response
|
| 42 |
const encoder = new TextEncoder();
|
|
|
|
| 55 |
(async () => {
|
| 56 |
let completeResponse = "";
|
| 57 |
try {
|
| 58 |
+
const chatCompletion = await openai.chat.completions.create({
|
| 59 |
+
model: customModel || selectedModel.value,
|
| 60 |
+
messages: [
|
| 61 |
+
{
|
| 62 |
+
role: "system",
|
| 63 |
+
content: INITIAL_SYSTEM_PROMPT,
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
role: "user",
|
| 67 |
+
content: redesignMarkdown
|
| 68 |
+
? `Here is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown.`
|
| 69 |
+
: html
|
| 70 |
+
? `Here is my current HTML code:\n\n\`\`\`html\n${html}\n\`\`\`\n\nNow, please create a new design based on this HTML.`
|
| 71 |
+
: prompt,
|
| 72 |
+
},
|
| 73 |
+
],
|
| 74 |
+
stream: true,
|
| 75 |
+
});
|
| 76 |
+
|
| 77 |
+
for await (const chunk of chatCompletion) {
|
| 78 |
+
const content = chunk.choices[0]?.delta?.content || "";
|
| 79 |
+
await writer.write(encoder.encode(content));
|
| 80 |
+
completeResponse += content;
|
| 81 |
+
if (completeResponse.includes("</html>")) {
|
|
|
|
|
|
|
|
|
|
| 82 |
break;
|
| 83 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
}
|
| 85 |
} catch (error: any) {
|
| 86 |
+
await writer.write(
|
| 87 |
+
encoder.encode(
|
| 88 |
+
JSON.stringify({
|
| 89 |
+
ok: false,
|
| 90 |
+
message:
|
| 91 |
+
error.message ||
|
| 92 |
+
"An error occurred while processing your request.",
|
| 93 |
+
})
|
| 94 |
+
)
|
| 95 |
+
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
} finally {
|
| 97 |
await writer?.close();
|
| 98 |
}
|
|
|
|
| 103 |
return NextResponse.json(
|
| 104 |
{
|
| 105 |
ok: false,
|
|
|
|
| 106 |
message:
|
| 107 |
error?.message || "An error occurred while processing your request.",
|
| 108 |
},
|
|
|
|
| 112 |
}
|
| 113 |
|
| 114 |
export async function PUT(request: NextRequest) {
|
|
|
|
|
|
|
|
|
|
| 115 |
const body = await request.json();
|
| 116 |
+
const { prompt, html, previousPrompt, selectedElementHtml, apiKey, model, baseUrl, customModel } = body;
|
| 117 |
+
|
| 118 |
+
const openai = new OpenAI({
|
| 119 |
+
apiKey: apiKey || process.env.OPENAI_API_KEY,
|
| 120 |
+
baseURL: baseUrl || process.env.OPENAI_BASE_URL,
|
| 121 |
+
});
|
| 122 |
|
| 123 |
if (!prompt || !html) {
|
| 124 |
return NextResponse.json(
|
|
|
|
| 127 |
);
|
| 128 |
}
|
| 129 |
|
| 130 |
+
const selectedModel = MODELS.find(
|
| 131 |
+
(m) => m.value === model || m.label === model
|
| 132 |
+
);
|
| 133 |
+
if (!selectedModel) {
|
| 134 |
+
return NextResponse.json(
|
| 135 |
+
{ ok: false, error: "Invalid model selected" },
|
| 136 |
+
{ status: 400 }
|
| 137 |
+
);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
}
|
| 139 |
|
| 140 |
+
try {
|
| 141 |
+
const response = await openai.chat.completions.create({
|
| 142 |
+
model: customModel || selectedModel.value,
|
| 143 |
+
messages: [
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
{
|
| 145 |
+
role: "system",
|
| 146 |
+
content: FOLLOW_UP_SYSTEM_PROMPT,
|
|
|
|
| 147 |
},
|
| 148 |
+
{
|
| 149 |
+
role: "user",
|
| 150 |
+
content: previousPrompt
|
| 151 |
+
? previousPrompt
|
| 152 |
+
: "You are modifying the HTML file based on the user's request.",
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
role: "assistant",
|
| 156 |
+
content: `The current code is: \n\`\`\`html\n${html}\n\`\`\` ${selectedElementHtml
|
| 157 |
+
? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\``
|
| 158 |
+
: ""}
|
| 159 |
+
`,
|
| 160 |
+
},
|
| 161 |
+
{
|
| 162 |
+
role: "user",
|
| 163 |
+
content: prompt,
|
| 164 |
+
},
|
| 165 |
+
],
|
| 166 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
const chunk = response.choices[0]?.message?.content;
|
| 169 |
if (!chunk) {
|
|
|
|
| 238 |
);
|
| 239 |
}
|
| 240 |
} catch (error: any) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
return NextResponse.json(
|
| 242 |
{
|
| 243 |
ok: false,
|
|
|
|
| 244 |
message:
|
| 245 |
error.message || "An error occurred while processing your request.",
|
| 246 |
},
|
| 247 |
{ status: 500 }
|
| 248 |
);
|
| 249 |
}
|
| 250 |
+
}
|
app/api/auth/route.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
| 1 |
-
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
-
|
| 3 |
-
export async function POST(req: NextRequest) {
|
| 4 |
-
const body = await req.json();
|
| 5 |
-
const { code } = body;
|
| 6 |
-
|
| 7 |
-
if (!code) {
|
| 8 |
-
return NextResponse.json(
|
| 9 |
-
{ error: "Code is required" },
|
| 10 |
-
{
|
| 11 |
-
status: 400,
|
| 12 |
-
headers: {
|
| 13 |
-
"Content-Type": "application/json",
|
| 14 |
-
},
|
| 15 |
-
}
|
| 16 |
-
);
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
const Authorization = `Basic ${Buffer.from(
|
| 20 |
-
`${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`
|
| 21 |
-
).toString("base64")}`;
|
| 22 |
-
|
| 23 |
-
const host =
|
| 24 |
-
req.headers.get("host") ?? req.headers.get("origin") ?? "localhost:3000";
|
| 25 |
-
|
| 26 |
-
const url = host.includes("/spaces/enzostvs")
|
| 27 |
-
? "enzostvs-deepsite.hf.space"
|
| 28 |
-
: host;
|
| 29 |
-
const redirect_uri =
|
| 30 |
-
`${host.includes("localhost") ? "http://" : "https://"}` +
|
| 31 |
-
url +
|
| 32 |
-
"/auth/callback";
|
| 33 |
-
const request_auth = await fetch("https://huggingface.co/oauth/token", {
|
| 34 |
-
method: "POST",
|
| 35 |
-
headers: {
|
| 36 |
-
"Content-Type": "application/x-www-form-urlencoded",
|
| 37 |
-
Authorization,
|
| 38 |
-
},
|
| 39 |
-
body: new URLSearchParams({
|
| 40 |
-
grant_type: "authorization_code",
|
| 41 |
-
code,
|
| 42 |
-
redirect_uri,
|
| 43 |
-
}),
|
| 44 |
-
});
|
| 45 |
-
|
| 46 |
-
const response = await request_auth.json();
|
| 47 |
-
if (!response.access_token) {
|
| 48 |
-
return NextResponse.json(
|
| 49 |
-
{ error: "Failed to retrieve access token" },
|
| 50 |
-
{
|
| 51 |
-
status: 400,
|
| 52 |
-
headers: {
|
| 53 |
-
"Content-Type": "application/json",
|
| 54 |
-
},
|
| 55 |
-
}
|
| 56 |
-
);
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
|
| 60 |
-
headers: {
|
| 61 |
-
Authorization: `Bearer ${response.access_token}`,
|
| 62 |
-
},
|
| 63 |
-
});
|
| 64 |
-
|
| 65 |
-
if (!userResponse.ok) {
|
| 66 |
-
return NextResponse.json(
|
| 67 |
-
{ user: null, errCode: userResponse.status },
|
| 68 |
-
{ status: userResponse.status }
|
| 69 |
-
);
|
| 70 |
-
}
|
| 71 |
-
const user = await userResponse.json();
|
| 72 |
-
|
| 73 |
-
return NextResponse.json(
|
| 74 |
-
{
|
| 75 |
-
access_token: response.access_token,
|
| 76 |
-
expires_in: response.expires_in,
|
| 77 |
-
user,
|
| 78 |
-
},
|
| 79 |
-
{
|
| 80 |
-
status: 200,
|
| 81 |
-
headers: {
|
| 82 |
-
"Content-Type": "application/json",
|
| 83 |
-
},
|
| 84 |
-
}
|
| 85 |
-
);
|
| 86 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/me/projects/[namespace]/[repoId]/route.ts
DELETED
|
@@ -1,237 +0,0 @@
|
|
| 1 |
-
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
-
import { RepoDesignation, spaceInfo, uploadFile } from "@huggingface/hub";
|
| 3 |
-
|
| 4 |
-
import { isAuthenticated } from "@/lib/auth";
|
| 5 |
-
import Project from "@/models/Project";
|
| 6 |
-
import dbConnect from "@/lib/mongodb";
|
| 7 |
-
import { getPTag } from "@/lib/utils";
|
| 8 |
-
|
| 9 |
-
export async function GET(
|
| 10 |
-
req: NextRequest,
|
| 11 |
-
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
| 12 |
-
) {
|
| 13 |
-
const user = await isAuthenticated();
|
| 14 |
-
|
| 15 |
-
if (user instanceof NextResponse || !user) {
|
| 16 |
-
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
await dbConnect();
|
| 20 |
-
const param = await params;
|
| 21 |
-
const { namespace, repoId } = param;
|
| 22 |
-
|
| 23 |
-
const project = await Project.findOne({
|
| 24 |
-
user_id: user.id,
|
| 25 |
-
space_id: `${namespace}/${repoId}`,
|
| 26 |
-
}).lean();
|
| 27 |
-
if (!project) {
|
| 28 |
-
return NextResponse.json(
|
| 29 |
-
{
|
| 30 |
-
ok: false,
|
| 31 |
-
error: "Project not found",
|
| 32 |
-
},
|
| 33 |
-
{ status: 404 }
|
| 34 |
-
);
|
| 35 |
-
}
|
| 36 |
-
const space_url = `https://huggingface.co/spaces/${namespace}/${repoId}/raw/main/index.html`;
|
| 37 |
-
try {
|
| 38 |
-
const space = await spaceInfo({
|
| 39 |
-
name: namespace + "/" + repoId,
|
| 40 |
-
accessToken: user.token as string,
|
| 41 |
-
additionalFields: ["author"],
|
| 42 |
-
});
|
| 43 |
-
|
| 44 |
-
if (!space || space.sdk !== "static") {
|
| 45 |
-
return NextResponse.json(
|
| 46 |
-
{
|
| 47 |
-
ok: false,
|
| 48 |
-
error: "Space is not a static space",
|
| 49 |
-
},
|
| 50 |
-
{ status: 404 }
|
| 51 |
-
);
|
| 52 |
-
}
|
| 53 |
-
if (space.author !== user.name) {
|
| 54 |
-
return NextResponse.json(
|
| 55 |
-
{
|
| 56 |
-
ok: false,
|
| 57 |
-
error: "Space does not belong to the authenticated user",
|
| 58 |
-
},
|
| 59 |
-
{ status: 403 }
|
| 60 |
-
);
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
const response = await fetch(space_url);
|
| 64 |
-
if (!response.ok) {
|
| 65 |
-
return NextResponse.json(
|
| 66 |
-
{
|
| 67 |
-
ok: false,
|
| 68 |
-
error: "Failed to fetch space HTML",
|
| 69 |
-
},
|
| 70 |
-
{ status: 404 }
|
| 71 |
-
);
|
| 72 |
-
}
|
| 73 |
-
let html = await response.text();
|
| 74 |
-
// remove the last p tag including this url https://enzostvs-deepsite.hf.space
|
| 75 |
-
html = html.replace(getPTag(namespace + "/" + repoId), "");
|
| 76 |
-
|
| 77 |
-
return NextResponse.json(
|
| 78 |
-
{
|
| 79 |
-
project: {
|
| 80 |
-
...project,
|
| 81 |
-
html,
|
| 82 |
-
},
|
| 83 |
-
ok: true,
|
| 84 |
-
},
|
| 85 |
-
{ status: 200 }
|
| 86 |
-
);
|
| 87 |
-
|
| 88 |
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 89 |
-
} catch (error: any) {
|
| 90 |
-
if (error.statusCode === 404) {
|
| 91 |
-
await Project.deleteOne({
|
| 92 |
-
user_id: user.id,
|
| 93 |
-
space_id: `${namespace}/${repoId}`,
|
| 94 |
-
});
|
| 95 |
-
return NextResponse.json(
|
| 96 |
-
{ error: "Space not found", ok: false },
|
| 97 |
-
{ status: 404 }
|
| 98 |
-
);
|
| 99 |
-
}
|
| 100 |
-
return NextResponse.json(
|
| 101 |
-
{ error: error.message, ok: false },
|
| 102 |
-
{ status: 500 }
|
| 103 |
-
);
|
| 104 |
-
}
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
export async function PUT(
|
| 108 |
-
req: NextRequest,
|
| 109 |
-
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
| 110 |
-
) {
|
| 111 |
-
const user = await isAuthenticated();
|
| 112 |
-
|
| 113 |
-
if (user instanceof NextResponse || !user) {
|
| 114 |
-
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
await dbConnect();
|
| 118 |
-
const param = await params;
|
| 119 |
-
const { namespace, repoId } = param;
|
| 120 |
-
const { html, prompts } = await req.json();
|
| 121 |
-
|
| 122 |
-
const project = await Project.findOne({
|
| 123 |
-
user_id: user.id,
|
| 124 |
-
space_id: `${namespace}/${repoId}`,
|
| 125 |
-
}).lean();
|
| 126 |
-
if (!project) {
|
| 127 |
-
return NextResponse.json(
|
| 128 |
-
{
|
| 129 |
-
ok: false,
|
| 130 |
-
error: "Project not found",
|
| 131 |
-
},
|
| 132 |
-
{ status: 404 }
|
| 133 |
-
);
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
const repo: RepoDesignation = {
|
| 137 |
-
type: "space",
|
| 138 |
-
name: `${namespace}/${repoId}`,
|
| 139 |
-
};
|
| 140 |
-
|
| 141 |
-
const newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}</body>`);
|
| 142 |
-
const file = new File([newHtml], "index.html", { type: "text/html" });
|
| 143 |
-
await uploadFile({
|
| 144 |
-
repo,
|
| 145 |
-
file,
|
| 146 |
-
accessToken: user.token as string,
|
| 147 |
-
commitTitle: `${prompts[prompts.length - 1]} - Follow Up Deployment`,
|
| 148 |
-
});
|
| 149 |
-
|
| 150 |
-
await Project.updateOne(
|
| 151 |
-
{ user_id: user.id, space_id: `${namespace}/${repoId}` },
|
| 152 |
-
{
|
| 153 |
-
$set: {
|
| 154 |
-
prompts: [
|
| 155 |
-
...(project && "prompts" in project ? project.prompts : []),
|
| 156 |
-
...prompts,
|
| 157 |
-
],
|
| 158 |
-
},
|
| 159 |
-
}
|
| 160 |
-
);
|
| 161 |
-
return NextResponse.json({ ok: true }, { status: 200 });
|
| 162 |
-
}
|
| 163 |
-
|
| 164 |
-
export async function POST(
|
| 165 |
-
req: NextRequest,
|
| 166 |
-
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
| 167 |
-
) {
|
| 168 |
-
const user = await isAuthenticated();
|
| 169 |
-
|
| 170 |
-
if (user instanceof NextResponse || !user) {
|
| 171 |
-
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 172 |
-
}
|
| 173 |
-
|
| 174 |
-
await dbConnect();
|
| 175 |
-
const param = await params;
|
| 176 |
-
const { namespace, repoId } = param;
|
| 177 |
-
|
| 178 |
-
const space = await spaceInfo({
|
| 179 |
-
name: namespace + "/" + repoId,
|
| 180 |
-
accessToken: user.token as string,
|
| 181 |
-
additionalFields: ["author"],
|
| 182 |
-
});
|
| 183 |
-
|
| 184 |
-
if (!space || space.sdk !== "static") {
|
| 185 |
-
return NextResponse.json(
|
| 186 |
-
{
|
| 187 |
-
ok: false,
|
| 188 |
-
error: "Space is not a static space",
|
| 189 |
-
},
|
| 190 |
-
{ status: 404 }
|
| 191 |
-
);
|
| 192 |
-
}
|
| 193 |
-
if (space.author !== user.name) {
|
| 194 |
-
return NextResponse.json(
|
| 195 |
-
{
|
| 196 |
-
ok: false,
|
| 197 |
-
error: "Space does not belong to the authenticated user",
|
| 198 |
-
},
|
| 199 |
-
{ status: 403 }
|
| 200 |
-
);
|
| 201 |
-
}
|
| 202 |
-
|
| 203 |
-
const project = await Project.findOne({
|
| 204 |
-
user_id: user.id,
|
| 205 |
-
space_id: `${namespace}/${repoId}`,
|
| 206 |
-
}).lean();
|
| 207 |
-
if (project) {
|
| 208 |
-
// redirect to the project page if it already exists
|
| 209 |
-
return NextResponse.json(
|
| 210 |
-
{
|
| 211 |
-
ok: false,
|
| 212 |
-
error: "Project already exists",
|
| 213 |
-
redirect: `/projects/${namespace}/${repoId}`,
|
| 214 |
-
},
|
| 215 |
-
{ status: 400 }
|
| 216 |
-
);
|
| 217 |
-
}
|
| 218 |
-
|
| 219 |
-
const newProject = new Project({
|
| 220 |
-
user_id: user.id,
|
| 221 |
-
space_id: `${namespace}/${repoId}`,
|
| 222 |
-
prompts: [],
|
| 223 |
-
});
|
| 224 |
-
|
| 225 |
-
await newProject.save();
|
| 226 |
-
return NextResponse.json(
|
| 227 |
-
{
|
| 228 |
-
ok: true,
|
| 229 |
-
project: {
|
| 230 |
-
id: newProject._id,
|
| 231 |
-
space_id: newProject.space_id,
|
| 232 |
-
prompts: newProject.prompts,
|
| 233 |
-
},
|
| 234 |
-
},
|
| 235 |
-
{ status: 201 }
|
| 236 |
-
);
|
| 237 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/me/projects/route.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
| 1 |
-
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
-
import { createRepo, RepoDesignation, uploadFiles } from "@huggingface/hub";
|
| 3 |
-
|
| 4 |
-
import { isAuthenticated } from "@/lib/auth";
|
| 5 |
-
import Project from "@/models/Project";
|
| 6 |
-
import dbConnect from "@/lib/mongodb";
|
| 7 |
-
import { COLORS, getPTag } from "@/lib/utils";
|
| 8 |
-
// import type user
|
| 9 |
-
export async function GET() {
|
| 10 |
-
const user = await isAuthenticated();
|
| 11 |
-
|
| 12 |
-
if (user instanceof NextResponse || !user) {
|
| 13 |
-
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 14 |
-
}
|
| 15 |
-
|
| 16 |
-
await dbConnect();
|
| 17 |
-
|
| 18 |
-
const projects = await Project.find({
|
| 19 |
-
user_id: user?.id,
|
| 20 |
-
})
|
| 21 |
-
.sort({ _createdAt: -1 })
|
| 22 |
-
.limit(100)
|
| 23 |
-
.lean();
|
| 24 |
-
if (!projects) {
|
| 25 |
-
return NextResponse.json(
|
| 26 |
-
{
|
| 27 |
-
ok: false,
|
| 28 |
-
projects: [],
|
| 29 |
-
},
|
| 30 |
-
{ status: 404 }
|
| 31 |
-
);
|
| 32 |
-
}
|
| 33 |
-
return NextResponse.json(
|
| 34 |
-
{
|
| 35 |
-
ok: true,
|
| 36 |
-
projects,
|
| 37 |
-
},
|
| 38 |
-
{ status: 200 }
|
| 39 |
-
);
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
/**
|
| 43 |
-
* This API route creates a new project in Hugging Face Spaces.
|
| 44 |
-
* It requires an Authorization header with a valid token and a JSON body with the project details.
|
| 45 |
-
*/
|
| 46 |
-
export async function POST(request: NextRequest) {
|
| 47 |
-
const user = await isAuthenticated();
|
| 48 |
-
|
| 49 |
-
if (user instanceof NextResponse || !user) {
|
| 50 |
-
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
const { title, html, prompts } = await request.json();
|
| 54 |
-
|
| 55 |
-
if (!title || !html) {
|
| 56 |
-
return NextResponse.json(
|
| 57 |
-
{ message: "Title and HTML content are required.", ok: false },
|
| 58 |
-
{ status: 400 }
|
| 59 |
-
);
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
await dbConnect();
|
| 63 |
-
|
| 64 |
-
try {
|
| 65 |
-
let readme = "";
|
| 66 |
-
let newHtml = html;
|
| 67 |
-
|
| 68 |
-
const newTitle = title
|
| 69 |
-
.toLowerCase()
|
| 70 |
-
.replace(/[^a-z0-9]+/g, "-")
|
| 71 |
-
.split("-")
|
| 72 |
-
.filter(Boolean)
|
| 73 |
-
.join("-")
|
| 74 |
-
.slice(0, 96);
|
| 75 |
-
|
| 76 |
-
const repo: RepoDesignation = {
|
| 77 |
-
type: "space",
|
| 78 |
-
name: `${user.name}/${newTitle}`,
|
| 79 |
-
};
|
| 80 |
-
|
| 81 |
-
const { repoUrl } = await createRepo({
|
| 82 |
-
repo,
|
| 83 |
-
accessToken: user.token as string,
|
| 84 |
-
});
|
| 85 |
-
const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
|
| 86 |
-
const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
|
| 87 |
-
readme = `---
|
| 88 |
-
title: ${newTitle}
|
| 89 |
-
emoji: 🐳
|
| 90 |
-
colorFrom: ${colorFrom}
|
| 91 |
-
colorTo: ${colorTo}
|
| 92 |
-
sdk: static
|
| 93 |
-
pinned: false
|
| 94 |
-
tags:
|
| 95 |
-
- deepsite
|
| 96 |
-
---
|
| 97 |
-
|
| 98 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference`;
|
| 99 |
-
|
| 100 |
-
newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}</body>`);
|
| 101 |
-
const file = new File([newHtml], "index.html", { type: "text/html" });
|
| 102 |
-
const readmeFile = new File([readme], "README.md", {
|
| 103 |
-
type: "text/markdown",
|
| 104 |
-
});
|
| 105 |
-
const files = [file, readmeFile];
|
| 106 |
-
await uploadFiles({
|
| 107 |
-
repo,
|
| 108 |
-
files,
|
| 109 |
-
accessToken: user.token as string,
|
| 110 |
-
commitTitle: `${prompts[prompts.length - 1]} - Initial Deployment`,
|
| 111 |
-
});
|
| 112 |
-
const path = repoUrl.split("/").slice(-2).join("/");
|
| 113 |
-
const project = await Project.create({
|
| 114 |
-
user_id: user.id,
|
| 115 |
-
space_id: path,
|
| 116 |
-
prompts,
|
| 117 |
-
});
|
| 118 |
-
return NextResponse.json({ project, path, ok: true }, { status: 201 });
|
| 119 |
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 120 |
-
} catch (err: any) {
|
| 121 |
-
return NextResponse.json(
|
| 122 |
-
{ error: err.message, ok: false },
|
| 123 |
-
{ status: 500 }
|
| 124 |
-
);
|
| 125 |
-
}
|
| 126 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/me/route.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
| 1 |
-
import { headers } from "next/headers";
|
| 2 |
-
import { NextResponse } from "next/server";
|
| 3 |
-
|
| 4 |
-
export async function GET() {
|
| 5 |
-
const authHeaders = await headers();
|
| 6 |
-
const token = authHeaders.get("Authorization");
|
| 7 |
-
if (!token) {
|
| 8 |
-
return NextResponse.json({ user: null, errCode: 401 }, { status: 401 });
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
|
| 12 |
-
headers: {
|
| 13 |
-
Authorization: `${token}`,
|
| 14 |
-
},
|
| 15 |
-
});
|
| 16 |
-
|
| 17 |
-
if (!userResponse.ok) {
|
| 18 |
-
return NextResponse.json(
|
| 19 |
-
{ user: null, errCode: userResponse.status },
|
| 20 |
-
{ status: userResponse.status }
|
| 21 |
-
);
|
| 22 |
-
}
|
| 23 |
-
const user = await userResponse.json();
|
| 24 |
-
return NextResponse.json({ user, errCode: null }, { status: 200 });
|
| 25 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/auth/callback/page.tsx
DELETED
|
@@ -1,72 +0,0 @@
|
|
| 1 |
-
"use client";
|
| 2 |
-
import Link from "next/link";
|
| 3 |
-
import { useUser } from "@/hooks/useUser";
|
| 4 |
-
import { use, useState } from "react";
|
| 5 |
-
import { useMount, useTimeoutFn } from "react-use";
|
| 6 |
-
|
| 7 |
-
import { Button } from "@/components/ui/button";
|
| 8 |
-
export default function AuthCallback({
|
| 9 |
-
searchParams,
|
| 10 |
-
}: {
|
| 11 |
-
searchParams: Promise<{ code: string }>;
|
| 12 |
-
}) {
|
| 13 |
-
const [showButton, setShowButton] = useState(false);
|
| 14 |
-
const { code } = use(searchParams);
|
| 15 |
-
const { loginFromCode } = useUser();
|
| 16 |
-
|
| 17 |
-
useMount(async () => {
|
| 18 |
-
if (code) {
|
| 19 |
-
await loginFromCode(code);
|
| 20 |
-
}
|
| 21 |
-
});
|
| 22 |
-
|
| 23 |
-
useTimeoutFn(
|
| 24 |
-
() => setShowButton(true),
|
| 25 |
-
7000 // Show button after 5 seconds
|
| 26 |
-
);
|
| 27 |
-
|
| 28 |
-
return (
|
| 29 |
-
<div className="h-screen flex flex-col justify-center items-center">
|
| 30 |
-
<div className="!rounded-2xl !p-0 !bg-white !border-neutral-100 min-w-xs text-center overflow-hidden ring-[8px] ring-white/20">
|
| 31 |
-
<header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
|
| 32 |
-
<div className="flex items-center justify-center -space-x-4 mb-3">
|
| 33 |
-
<div className="size-9 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
| 34 |
-
🚀
|
| 35 |
-
</div>
|
| 36 |
-
<div className="size-11 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-2xl z-2">
|
| 37 |
-
👋
|
| 38 |
-
</div>
|
| 39 |
-
<div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
| 40 |
-
🙌
|
| 41 |
-
</div>
|
| 42 |
-
</div>
|
| 43 |
-
<p className="text-xl font-semibold text-neutral-950">
|
| 44 |
-
Login In Progress...
|
| 45 |
-
</p>
|
| 46 |
-
<p className="text-sm text-neutral-500 mt-1.5">
|
| 47 |
-
Wait a moment while we log you in with your code.
|
| 48 |
-
</p>
|
| 49 |
-
</header>
|
| 50 |
-
<main className="space-y-4 p-6">
|
| 51 |
-
<div>
|
| 52 |
-
<p className="text-sm text-neutral-700 mb-4 max-w-xs">
|
| 53 |
-
If you are not redirected automatically in the next 5 seconds,
|
| 54 |
-
please click the button below
|
| 55 |
-
</p>
|
| 56 |
-
{showButton ? (
|
| 57 |
-
<Link href="/">
|
| 58 |
-
<Button variant="black" className="relative">
|
| 59 |
-
Go to Home
|
| 60 |
-
</Button>
|
| 61 |
-
</Link>
|
| 62 |
-
) : (
|
| 63 |
-
<p className="text-xs text-neutral-500">
|
| 64 |
-
Please wait, we are logging you in...
|
| 65 |
-
</p>
|
| 66 |
-
)}
|
| 67 |
-
</div>
|
| 68 |
-
</main>
|
| 69 |
-
</div>
|
| 70 |
-
</div>
|
| 71 |
-
);
|
| 72 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/auth/page.tsx
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
import { redirect } from "next/navigation";
|
| 2 |
-
import { Metadata } from "next";
|
| 3 |
-
|
| 4 |
-
import { getAuth } from "@/app/actions/auth";
|
| 5 |
-
|
| 6 |
-
export const revalidate = 1;
|
| 7 |
-
|
| 8 |
-
export const metadata: Metadata = {
|
| 9 |
-
robots: "noindex, nofollow",
|
| 10 |
-
};
|
| 11 |
-
|
| 12 |
-
export default async function Auth() {
|
| 13 |
-
const loginRedirectUrl = await getAuth();
|
| 14 |
-
if (loginRedirectUrl) {
|
| 15 |
-
redirect(loginRedirectUrl);
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
return (
|
| 19 |
-
<div className="p-4">
|
| 20 |
-
<div className="border bg-red-500/10 border-red-500/20 text-red-500 px-5 py-3 rounded-lg">
|
| 21 |
-
<h1 className="text-xl font-bold">Error</h1>
|
| 22 |
-
<p className="text-sm">
|
| 23 |
-
An error occurred while trying to log in. Please try again later.
|
| 24 |
-
</p>
|
| 25 |
-
</div>
|
| 26 |
-
</div>
|
| 27 |
-
);
|
| 28 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/layout.tsx
CHANGED
|
@@ -1,13 +1,10 @@
|
|
| 1 |
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
import type { Metadata, Viewport } from "next";
|
| 3 |
import { Inter, PT_Sans } from "next/font/google";
|
| 4 |
-
|
| 5 |
|
| 6 |
import TanstackProvider from "@/components/providers/tanstack-query-provider";
|
| 7 |
import "@/assets/globals.css";
|
| 8 |
import { Toaster } from "@/components/ui/sonner";
|
| 9 |
-
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 10 |
-
import { apiServer } from "@/lib/api";
|
| 11 |
import AppContext from "@/components/contexts/app-context";
|
| 12 |
import Script from "next/script";
|
| 13 |
|
|
@@ -66,28 +63,11 @@ export const viewport: Viewport = {
|
|
| 66 |
themeColor: "#000000",
|
| 67 |
};
|
| 68 |
|
| 69 |
-
async function getMe() {
|
| 70 |
-
const cookieStore = await cookies();
|
| 71 |
-
const token = cookieStore.get(MY_TOKEN_KEY())?.value;
|
| 72 |
-
if (!token) return { user: null, errCode: null };
|
| 73 |
-
try {
|
| 74 |
-
const res = await apiServer.get("/me", {
|
| 75 |
-
headers: {
|
| 76 |
-
Authorization: `Bearer ${token}`,
|
| 77 |
-
},
|
| 78 |
-
});
|
| 79 |
-
return { user: res.data.user, errCode: null };
|
| 80 |
-
} catch (err: any) {
|
| 81 |
-
return { user: null, errCode: err.status };
|
| 82 |
-
}
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
export default async function RootLayout({
|
| 86 |
children,
|
| 87 |
}: Readonly<{
|
| 88 |
children: React.ReactNode;
|
| 89 |
}>) {
|
| 90 |
-
const data = await getMe();
|
| 91 |
return (
|
| 92 |
<html lang="en">
|
| 93 |
<Script
|
|
@@ -100,7 +80,7 @@ export default async function RootLayout({
|
|
| 100 |
>
|
| 101 |
<Toaster richColors position="bottom-center" />
|
| 102 |
<TanstackProvider>
|
| 103 |
-
<AppContext
|
| 104 |
</TanstackProvider>
|
| 105 |
</body>
|
| 106 |
</html>
|
|
|
|
|
|
|
| 1 |
import type { Metadata, Viewport } from "next";
|
| 2 |
import { Inter, PT_Sans } from "next/font/google";
|
| 3 |
+
|
| 4 |
|
| 5 |
import TanstackProvider from "@/components/providers/tanstack-query-provider";
|
| 6 |
import "@/assets/globals.css";
|
| 7 |
import { Toaster } from "@/components/ui/sonner";
|
|
|
|
|
|
|
| 8 |
import AppContext from "@/components/contexts/app-context";
|
| 9 |
import Script from "next/script";
|
| 10 |
|
|
|
|
| 63 |
themeColor: "#000000",
|
| 64 |
};
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
export default async function RootLayout({
|
| 67 |
children,
|
| 68 |
}: Readonly<{
|
| 69 |
children: React.ReactNode;
|
| 70 |
}>) {
|
|
|
|
| 71 |
return (
|
| 72 |
<html lang="en">
|
| 73 |
<Script
|
|
|
|
| 80 |
>
|
| 81 |
<Toaster richColors position="bottom-center" />
|
| 82 |
<TanstackProvider>
|
| 83 |
+
<AppContext>{children}</AppContext>
|
| 84 |
</TanstackProvider>
|
| 85 |
</body>
|
| 86 |
</html>
|
app/projects/[namespace]/[repoId]/page.tsx
DELETED
|
@@ -1,40 +0,0 @@
|
|
| 1 |
-
import { cookies } from "next/headers";
|
| 2 |
-
import { redirect } from "next/navigation";
|
| 3 |
-
|
| 4 |
-
import { apiServer } from "@/lib/api";
|
| 5 |
-
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 6 |
-
import { AppEditor } from "@/components/editor";
|
| 7 |
-
|
| 8 |
-
async function getProject(namespace: string, repoId: string) {
|
| 9 |
-
// TODO replace with a server action
|
| 10 |
-
const cookieStore = await cookies();
|
| 11 |
-
const token = cookieStore.get(MY_TOKEN_KEY())?.value;
|
| 12 |
-
if (!token) return {};
|
| 13 |
-
try {
|
| 14 |
-
const { data } = await apiServer.get(
|
| 15 |
-
`/me/projects/${namespace}/${repoId}`,
|
| 16 |
-
{
|
| 17 |
-
headers: {
|
| 18 |
-
Authorization: `Bearer ${token}`,
|
| 19 |
-
},
|
| 20 |
-
}
|
| 21 |
-
);
|
| 22 |
-
|
| 23 |
-
return data.project;
|
| 24 |
-
} catch {
|
| 25 |
-
return {};
|
| 26 |
-
}
|
| 27 |
-
}
|
| 28 |
-
|
| 29 |
-
export default async function ProjectNamespacePage({
|
| 30 |
-
params,
|
| 31 |
-
}: {
|
| 32 |
-
params: Promise<{ namespace: string; repoId: string }>;
|
| 33 |
-
}) {
|
| 34 |
-
const { namespace, repoId } = await params;
|
| 35 |
-
const project = await getProject(namespace, repoId);
|
| 36 |
-
if (!project?.html) {
|
| 37 |
-
redirect("/projects");
|
| 38 |
-
}
|
| 39 |
-
return <AppEditor project={project} />;
|
| 40 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/contexts/app-context.tsx
CHANGED
|
@@ -19,7 +19,7 @@ export default function AppContext({
|
|
| 19 |
errCode: number | null;
|
| 20 |
};
|
| 21 |
}) {
|
| 22 |
-
const {
|
| 23 |
useUser(initialData);
|
| 24 |
const pathname = usePathname();
|
| 25 |
const router = useRouter();
|
|
@@ -45,7 +45,6 @@ export default function AppContext({
|
|
| 45 |
|
| 46 |
if (!message.code) return;
|
| 47 |
if (message.type === "user-oauth" && message?.code && !events.code) {
|
| 48 |
-
loginFromCode(message.code);
|
| 49 |
}
|
| 50 |
});
|
| 51 |
|
|
|
|
| 19 |
errCode: number | null;
|
| 20 |
};
|
| 21 |
}) {
|
| 22 |
+
const { user, logout, loading, errCode } =
|
| 23 |
useUser(initialData);
|
| 24 |
const pathname = usePathname();
|
| 25 |
const router = useRouter();
|
|
|
|
| 45 |
|
| 46 |
if (!message.code) return;
|
| 47 |
if (message.type === "user-oauth" && message?.code && !events.code) {
|
|
|
|
| 48 |
}
|
| 49 |
});
|
| 50 |
|
components/editor/ask-ai/index.tsx
CHANGED
|
@@ -7,13 +7,11 @@ import { useLocalStorage, useUpdateEffect } from "react-use";
|
|
| 7 |
import { ArrowUp, ChevronDown, Crosshair } from "lucide-react";
|
| 8 |
import { FaStopCircle } from "react-icons/fa";
|
| 9 |
|
| 10 |
-
import ProModal from "@/components/pro-modal";
|
| 11 |
import { Button } from "@/components/ui/button";
|
| 12 |
import { MODELS } from "@/lib/providers";
|
| 13 |
import { HtmlHistory } from "@/types";
|
| 14 |
import { InviteFriends } from "@/components/invite-friends";
|
| 15 |
import { Settings } from "@/components/editor/ask-ai/settings";
|
| 16 |
-
import { LoginModal } from "@/components/login-modal";
|
| 17 |
import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
|
| 18 |
import Loading from "@/components/loading";
|
| 19 |
import { Checkbox } from "@/components/ui/checkbox";
|
|
@@ -52,7 +50,6 @@ export function AskAI({
|
|
| 52 |
const refThink = useRef<HTMLDivElement | null>(null);
|
| 53 |
const audio = useRef<HTMLAudioElement | null>(null);
|
| 54 |
|
| 55 |
-
const [open, setOpen] = useState(false);
|
| 56 |
const [prompt, setPrompt] = useState("");
|
| 57 |
const [hasAsked, setHasAsked] = useState(false);
|
| 58 |
const [previousPrompt, setPreviousPrompt] = useState("");
|
|
@@ -60,7 +57,6 @@ export function AskAI({
|
|
| 60 |
const [model, setModel] = useLocalStorage("model", MODELS[0].value);
|
| 61 |
const [openProvider, setOpenProvider] = useState(false);
|
| 62 |
const [providerError, setProviderError] = useState("");
|
| 63 |
-
const [openProModal, setOpenProModal] = useState(false);
|
| 64 |
const [think, setThink] = useState<string | undefined>(undefined);
|
| 65 |
const [openThink, setOpenThink] = useState(false);
|
| 66 |
const [isThinking, setIsThinking] = useState(true);
|
|
@@ -88,6 +84,9 @@ export function AskAI({
|
|
| 88 |
const selectedElementHtml = selectedElement
|
| 89 |
? selectedElement.outerHTML
|
| 90 |
: "";
|
|
|
|
|
|
|
|
|
|
| 91 |
const request = await fetch("/api/ask-ai", {
|
| 92 |
method: "PUT",
|
| 93 |
body: JSON.stringify({
|
|
@@ -97,6 +96,9 @@ export function AskAI({
|
|
| 97 |
model,
|
| 98 |
html,
|
| 99 |
selectedElementHtml,
|
|
|
|
|
|
|
|
|
|
| 100 |
}),
|
| 101 |
headers: {
|
| 102 |
"Content-Type": "application/json",
|
|
@@ -107,16 +109,6 @@ export function AskAI({
|
|
| 107 |
if (request && request.body) {
|
| 108 |
const res = await request.json();
|
| 109 |
if (!request.ok) {
|
| 110 |
-
if (res.openLogin) {
|
| 111 |
-
setOpen(true);
|
| 112 |
-
} else if (res.openSelectProvider) {
|
| 113 |
-
setOpenProvider(true);
|
| 114 |
-
setProviderError(res.message);
|
| 115 |
-
} else if (res.openProModal) {
|
| 116 |
-
setOpenProModal(true);
|
| 117 |
-
} else {
|
| 118 |
-
toast.error(res.message);
|
| 119 |
-
}
|
| 120 |
setisAiWorking(false);
|
| 121 |
return;
|
| 122 |
}
|
|
@@ -129,6 +121,7 @@ export function AskAI({
|
|
| 129 |
if (audio.current) audio.current.play();
|
| 130 |
}
|
| 131 |
} else {
|
|
|
|
| 132 |
const request = await fetch("/api/ask-ai", {
|
| 133 |
method: "POST",
|
| 134 |
body: JSON.stringify({
|
|
@@ -137,6 +130,7 @@ export function AskAI({
|
|
| 137 |
model,
|
| 138 |
html: isSameHtml ? "" : html,
|
| 139 |
redesignMarkdown,
|
|
|
|
| 140 |
}),
|
| 141 |
headers: {
|
| 142 |
"Content-Type": "application/json",
|
|
@@ -159,16 +153,6 @@ export function AskAI({
|
|
| 159 |
contentResponse.trim().endsWith("}");
|
| 160 |
const jsonResponse = isJson ? JSON.parse(contentResponse) : null;
|
| 161 |
if (jsonResponse && !jsonResponse.ok) {
|
| 162 |
-
if (jsonResponse.openLogin) {
|
| 163 |
-
setOpen(true);
|
| 164 |
-
} else if (jsonResponse.openSelectProvider) {
|
| 165 |
-
setOpenProvider(true);
|
| 166 |
-
setProviderError(jsonResponse.message);
|
| 167 |
-
} else if (jsonResponse.openProModal) {
|
| 168 |
-
setOpenProModal(true);
|
| 169 |
-
} else {
|
| 170 |
-
toast.error(jsonResponse.message);
|
| 171 |
-
}
|
| 172 |
setisAiWorking(false);
|
| 173 |
return;
|
| 174 |
}
|
|
@@ -251,9 +235,6 @@ export function AskAI({
|
|
| 251 |
} catch (error: any) {
|
| 252 |
setisAiWorking(false);
|
| 253 |
toast.error(error.message);
|
| 254 |
-
if (error.openLogin) {
|
| 255 |
-
setOpen(true);
|
| 256 |
-
}
|
| 257 |
}
|
| 258 |
};
|
| 259 |
|
|
@@ -428,12 +409,6 @@ export function AskAI({
|
|
| 428 |
</Button>
|
| 429 |
</div>
|
| 430 |
</div>
|
| 431 |
-
<LoginModal open={open} onClose={() => setOpen(false)} html={html} />
|
| 432 |
-
<ProModal
|
| 433 |
-
html={html}
|
| 434 |
-
open={openProModal}
|
| 435 |
-
onClose={() => setOpenProModal(false)}
|
| 436 |
-
/>
|
| 437 |
{!isSameHtml && (
|
| 438 |
<div className="absolute top-0 right-0 -translate-y-[calc(100%+8px)] select-none text-xs text-neutral-400 flex items-center justify-center gap-2 bg-neutral-800 border border-neutral-700 rounded-md p-1 pr-2.5">
|
| 439 |
<label
|
|
|
|
| 7 |
import { ArrowUp, ChevronDown, Crosshair } from "lucide-react";
|
| 8 |
import { FaStopCircle } from "react-icons/fa";
|
| 9 |
|
|
|
|
| 10 |
import { Button } from "@/components/ui/button";
|
| 11 |
import { MODELS } from "@/lib/providers";
|
| 12 |
import { HtmlHistory } from "@/types";
|
| 13 |
import { InviteFriends } from "@/components/invite-friends";
|
| 14 |
import { Settings } from "@/components/editor/ask-ai/settings";
|
|
|
|
| 15 |
import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
|
| 16 |
import Loading from "@/components/loading";
|
| 17 |
import { Checkbox } from "@/components/ui/checkbox";
|
|
|
|
| 50 |
const refThink = useRef<HTMLDivElement | null>(null);
|
| 51 |
const audio = useRef<HTMLAudioElement | null>(null);
|
| 52 |
|
|
|
|
| 53 |
const [prompt, setPrompt] = useState("");
|
| 54 |
const [hasAsked, setHasAsked] = useState(false);
|
| 55 |
const [previousPrompt, setPreviousPrompt] = useState("");
|
|
|
|
| 57 |
const [model, setModel] = useLocalStorage("model", MODELS[0].value);
|
| 58 |
const [openProvider, setOpenProvider] = useState(false);
|
| 59 |
const [providerError, setProviderError] = useState("");
|
|
|
|
| 60 |
const [think, setThink] = useState<string | undefined>(undefined);
|
| 61 |
const [openThink, setOpenThink] = useState(false);
|
| 62 |
const [isThinking, setIsThinking] = useState(true);
|
|
|
|
| 84 |
const selectedElementHtml = selectedElement
|
| 85 |
? selectedElement.outerHTML
|
| 86 |
: "";
|
| 87 |
+
const apiKey = localStorage.getItem("openai_api_key");
|
| 88 |
+
const baseUrl = localStorage.getItem("openai_base_url");
|
| 89 |
+
const customModel = localStorage.getItem("openai_model");
|
| 90 |
const request = await fetch("/api/ask-ai", {
|
| 91 |
method: "PUT",
|
| 92 |
body: JSON.stringify({
|
|
|
|
| 96 |
model,
|
| 97 |
html,
|
| 98 |
selectedElementHtml,
|
| 99 |
+
apiKey,
|
| 100 |
+
baseUrl,
|
| 101 |
+
customModel,
|
| 102 |
}),
|
| 103 |
headers: {
|
| 104 |
"Content-Type": "application/json",
|
|
|
|
| 109 |
if (request && request.body) {
|
| 110 |
const res = await request.json();
|
| 111 |
if (!request.ok) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
setisAiWorking(false);
|
| 113 |
return;
|
| 114 |
}
|
|
|
|
| 121 |
if (audio.current) audio.current.play();
|
| 122 |
}
|
| 123 |
} else {
|
| 124 |
+
const apiKey = localStorage.getItem("openai_api_key");
|
| 125 |
const request = await fetch("/api/ask-ai", {
|
| 126 |
method: "POST",
|
| 127 |
body: JSON.stringify({
|
|
|
|
| 130 |
model,
|
| 131 |
html: isSameHtml ? "" : html,
|
| 132 |
redesignMarkdown,
|
| 133 |
+
apiKey,
|
| 134 |
}),
|
| 135 |
headers: {
|
| 136 |
"Content-Type": "application/json",
|
|
|
|
| 153 |
contentResponse.trim().endsWith("}");
|
| 154 |
const jsonResponse = isJson ? JSON.parse(contentResponse) : null;
|
| 155 |
if (jsonResponse && !jsonResponse.ok) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
setisAiWorking(false);
|
| 157 |
return;
|
| 158 |
}
|
|
|
|
| 235 |
} catch (error: any) {
|
| 236 |
setisAiWorking(false);
|
| 237 |
toast.error(error.message);
|
|
|
|
|
|
|
|
|
|
| 238 |
}
|
| 239 |
};
|
| 240 |
|
|
|
|
| 409 |
</Button>
|
| 410 |
</div>
|
| 411 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
{!isSameHtml && (
|
| 413 |
<div className="absolute top-0 right-0 -translate-y-[calc(100%+8px)] select-none text-xs text-neutral-400 flex items-center justify-center gap-2 bg-neutral-800 border border-neutral-700 rounded-md p-1 pr-2.5">
|
| 414 |
<label
|
components/editor/ask-ai/settings.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
-
import classNames from "classnames";
|
| 2 |
import { PiGearSixFill } from "react-icons/pi";
|
| 3 |
-
import {
|
| 4 |
|
| 5 |
import {
|
| 6 |
Popover,
|
|
@@ -9,18 +8,10 @@ import {
|
|
| 9 |
} from "@/components/ui/popover";
|
| 10 |
import { PROVIDERS, MODELS } from "@/lib/providers";
|
| 11 |
import { Button } from "@/components/ui/button";
|
| 12 |
-
import {
|
| 13 |
-
Select,
|
| 14 |
-
SelectContent,
|
| 15 |
-
SelectGroup,
|
| 16 |
-
SelectItem,
|
| 17 |
-
SelectLabel,
|
| 18 |
-
SelectTrigger,
|
| 19 |
-
SelectValue,
|
| 20 |
-
} from "@/components/ui/select";
|
| 21 |
import { useMemo } from "react";
|
| 22 |
import { useUpdateEffect } from "react-use";
|
| 23 |
-
import
|
|
|
|
| 24 |
|
| 25 |
export function Settings({
|
| 26 |
open,
|
|
@@ -28,9 +19,7 @@ export function Settings({
|
|
| 28 |
provider,
|
| 29 |
model,
|
| 30 |
error,
|
| 31 |
-
isFollowUp = false,
|
| 32 |
onChange,
|
| 33 |
-
onModelChange,
|
| 34 |
}: {
|
| 35 |
open: boolean;
|
| 36 |
provider: string;
|
|
@@ -41,6 +30,16 @@ export function Settings({
|
|
| 41 |
onChange: (provider: string) => void;
|
| 42 |
onModelChange: (model: string) => void;
|
| 43 |
}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
const modelAvailableProviders = useMemo(() => {
|
| 45 |
const availableProviders = MODELS.find(
|
| 46 |
(m: { value: string }) => m.value === model
|
|
@@ -57,6 +56,14 @@ export function Settings({
|
|
| 57 |
}
|
| 58 |
}, [model, provider]);
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
return (
|
| 61 |
<div className="">
|
| 62 |
<Popover open={open} onOpenChange={onClose}>
|
|
@@ -81,121 +88,52 @@ export function Settings({
|
|
| 81 |
)}
|
| 82 |
<label className="block">
|
| 83 |
<p className="text-neutral-300 text-sm mb-2.5">
|
| 84 |
-
|
| 85 |
</p>
|
| 86 |
-
<
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
{MODELS.map(
|
| 94 |
-
({
|
| 95 |
-
value,
|
| 96 |
-
label,
|
| 97 |
-
isNew = false,
|
| 98 |
-
isThinker = false,
|
| 99 |
-
}: {
|
| 100 |
-
value: string;
|
| 101 |
-
label: string;
|
| 102 |
-
isNew?: boolean;
|
| 103 |
-
isThinker?: boolean;
|
| 104 |
-
}) => (
|
| 105 |
-
<SelectItem
|
| 106 |
-
key={value}
|
| 107 |
-
value={value}
|
| 108 |
-
className=""
|
| 109 |
-
disabled={isThinker && isFollowUp}
|
| 110 |
-
>
|
| 111 |
-
{label}
|
| 112 |
-
{isNew && (
|
| 113 |
-
<span className="text-xs bg-gradient-to-br from-sky-400 to-sky-600 text-white rounded-full px-1.5 py-0.5">
|
| 114 |
-
New
|
| 115 |
-
</span>
|
| 116 |
-
)}
|
| 117 |
-
</SelectItem>
|
| 118 |
-
)
|
| 119 |
-
)}
|
| 120 |
-
</SelectGroup>
|
| 121 |
-
</SelectContent>
|
| 122 |
-
</Select>
|
| 123 |
</label>
|
| 124 |
-
|
| 125 |
-
<
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
>
|
| 159 |
-
<div
|
| 160 |
-
className={classNames(
|
| 161 |
-
"w-4 h-4 rounded-full shadow-md transition-all duration-200 bg-neutral-200",
|
| 162 |
-
{
|
| 163 |
-
"translate-x-4": provider === "auto",
|
| 164 |
-
}
|
| 165 |
-
)}
|
| 166 |
-
/>
|
| 167 |
-
</div>
|
| 168 |
-
</div>
|
| 169 |
-
<label className="block">
|
| 170 |
-
<p className="text-neutral-300 text-sm mb-2">
|
| 171 |
-
Inference Provider
|
| 172 |
-
</p>
|
| 173 |
-
<div className="grid grid-cols-2 gap-1.5">
|
| 174 |
-
{modelAvailableProviders.map((id: string) => (
|
| 175 |
-
<Button
|
| 176 |
-
key={id}
|
| 177 |
-
variant={id === provider ? "default" : "secondary"}
|
| 178 |
-
size="sm"
|
| 179 |
-
onClick={() => {
|
| 180 |
-
onChange(id);
|
| 181 |
-
}}
|
| 182 |
-
>
|
| 183 |
-
<Image
|
| 184 |
-
src={`/providers/${id}.svg`}
|
| 185 |
-
alt={PROVIDERS[id as keyof typeof PROVIDERS].name}
|
| 186 |
-
className="size-5 mr-2"
|
| 187 |
-
width={20}
|
| 188 |
-
height={20}
|
| 189 |
-
/>
|
| 190 |
-
{PROVIDERS[id as keyof typeof PROVIDERS].name}
|
| 191 |
-
{id === provider && (
|
| 192 |
-
<RiCheckboxCircleFill className="ml-2 size-4 text-blue-500" />
|
| 193 |
-
)}
|
| 194 |
-
</Button>
|
| 195 |
-
))}
|
| 196 |
-
</div>
|
| 197 |
-
</label>
|
| 198 |
</div>
|
|
|
|
| 199 |
</main>
|
| 200 |
</PopoverContent>
|
| 201 |
</Popover>
|
|
|
|
|
|
|
| 1 |
import { PiGearSixFill } from "react-icons/pi";
|
| 2 |
+
import { useState, useEffect } from "react";
|
| 3 |
|
| 4 |
import {
|
| 5 |
Popover,
|
|
|
|
| 8 |
} from "@/components/ui/popover";
|
| 9 |
import { PROVIDERS, MODELS } from "@/lib/providers";
|
| 10 |
import { Button } from "@/components/ui/button";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
import { useMemo } from "react";
|
| 12 |
import { useUpdateEffect } from "react-use";
|
| 13 |
+
import { Input } from "@/components/ui/input";
|
| 14 |
+
import { toast } from "sonner";
|
| 15 |
|
| 16 |
export function Settings({
|
| 17 |
open,
|
|
|
|
| 19 |
provider,
|
| 20 |
model,
|
| 21 |
error,
|
|
|
|
| 22 |
onChange,
|
|
|
|
| 23 |
}: {
|
| 24 |
open: boolean;
|
| 25 |
provider: string;
|
|
|
|
| 30 |
onChange: (provider: string) => void;
|
| 31 |
onModelChange: (model: string) => void;
|
| 32 |
}) {
|
| 33 |
+
const [apiKey, setApiKey] = useState("");
|
| 34 |
+
const [baseUrl, setBaseUrl] = useState("");
|
| 35 |
+
const [customModel, setCustomModel] = useState("");
|
| 36 |
+
|
| 37 |
+
useEffect(() => {
|
| 38 |
+
setApiKey(localStorage.getItem("openai_api_key") || "");
|
| 39 |
+
setBaseUrl(localStorage.getItem("openai_base_url") || "");
|
| 40 |
+
setCustomModel(localStorage.getItem("openai_model") || "");
|
| 41 |
+
}, [open]);
|
| 42 |
+
|
| 43 |
const modelAvailableProviders = useMemo(() => {
|
| 44 |
const availableProviders = MODELS.find(
|
| 45 |
(m: { value: string }) => m.value === model
|
|
|
|
| 56 |
}
|
| 57 |
}, [model, provider]);
|
| 58 |
|
| 59 |
+
const handleSaveSettings = () => {
|
| 60 |
+
localStorage.setItem("openai_api_key", apiKey);
|
| 61 |
+
localStorage.setItem("openai_base_url", baseUrl);
|
| 62 |
+
localStorage.setItem("openai_model", customModel);
|
| 63 |
+
toast.success("Settings saved!");
|
| 64 |
+
onClose(false);
|
| 65 |
+
};
|
| 66 |
+
|
| 67 |
return (
|
| 68 |
<div className="">
|
| 69 |
<Popover open={open} onOpenChange={onClose}>
|
|
|
|
| 88 |
)}
|
| 89 |
<label className="block">
|
| 90 |
<p className="text-neutral-300 text-sm mb-2.5">
|
| 91 |
+
API Key
|
| 92 |
</p>
|
| 93 |
+
<Input
|
| 94 |
+
type="password"
|
| 95 |
+
placeholder="Enter your OpenAI API key"
|
| 96 |
+
value={apiKey}
|
| 97 |
+
onChange={(e) => setApiKey(e.target.value)}
|
| 98 |
+
className="!bg-neutral-800 !border-neutral-700 !text-neutral-200"
|
| 99 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
</label>
|
| 101 |
+
<label className="block">
|
| 102 |
+
<p className="text-neutral-300 text-sm mb-2.5">
|
| 103 |
+
Base URL (optional)
|
| 104 |
+
</p>
|
| 105 |
+
<Input
|
| 106 |
+
type="text"
|
| 107 |
+
placeholder="e.g., https://api.openai.com/v1"
|
| 108 |
+
value={baseUrl}
|
| 109 |
+
onChange={(e) => setBaseUrl(e.target.value)}
|
| 110 |
+
className="!bg-neutral-800 !border-neutral-700 !text-neutral-200"
|
| 111 |
+
/>
|
| 112 |
+
</label>
|
| 113 |
+
<label className="block">
|
| 114 |
+
<p className="text-neutral-300 text-sm mb-2.5">
|
| 115 |
+
Custom Model
|
| 116 |
+
</p>
|
| 117 |
+
<Input
|
| 118 |
+
type="text"
|
| 119 |
+
placeholder="e.g., gpt-4o-mini"
|
| 120 |
+
value={customModel || "gpt-4.1"}
|
| 121 |
+
onChange={(e) => setCustomModel(e.target.value)}
|
| 122 |
+
className="!bg-neutral-800 !border-neutral-700 !text-neutral-200"
|
| 123 |
+
/>
|
| 124 |
+
</label>
|
| 125 |
+
<Button
|
| 126 |
+
variant="default"
|
| 127 |
+
size="sm"
|
| 128 |
+
onClick={handleSaveSettings}
|
| 129 |
+
className="mt-2 w-full"
|
| 130 |
+
>
|
| 131 |
+
Save Settings
|
| 132 |
+
</Button>
|
| 133 |
+
<div className="bg-amber-500/10 border-amber-500/10 p-3 text-xs text-amber-500 border rounded-lg">
|
| 134 |
+
Accepts any OpenAI-compatible provider. Enter the corresponding API key and base URL (e.g., OpenRouter, DeepSeek, etc.).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
</div>
|
| 136 |
+
|
| 137 |
</main>
|
| 138 |
</PopoverContent>
|
| 139 |
</Popover>
|
components/editor/deploy-button/index.tsx
CHANGED
|
@@ -16,8 +16,6 @@ import {
|
|
| 16 |
} from "@/components/ui/popover";
|
| 17 |
import { Input } from "@/components/ui/input";
|
| 18 |
import { api } from "@/lib/api";
|
| 19 |
-
import { LoginModal } from "@/components/login-modal";
|
| 20 |
-
import { useUser } from "@/hooks/useUser";
|
| 21 |
|
| 22 |
export function DeployButton({
|
| 23 |
html,
|
|
@@ -27,9 +25,7 @@ export function DeployButton({
|
|
| 27 |
prompts: string[];
|
| 28 |
}) {
|
| 29 |
const router = useRouter();
|
| 30 |
-
|
| 31 |
-
const [loading, setLoading] = useState(false);
|
| 32 |
-
const [open, setOpen] = useState(false);
|
| 33 |
|
| 34 |
const [config, setConfig] = useState({
|
| 35 |
title: "",
|
|
@@ -65,108 +61,80 @@ export function DeployButton({
|
|
| 65 |
return (
|
| 66 |
<div className="flex items-center justify-end gap-5">
|
| 67 |
<div className="relative flex items-center justify-end">
|
| 68 |
-
|
| 69 |
-
<
|
| 70 |
-
<
|
| 71 |
-
<
|
| 72 |
-
<
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
>
|
| 85 |
-
<
|
| 86 |
-
<div className="flex items-center justify-center -
|
| 87 |
-
|
| 88 |
-
🚀
|
| 89 |
-
</div>
|
| 90 |
-
<div className="size-11 rounded-full bg-red-200 shadow-2xl flex items-center justify-center z-2">
|
| 91 |
-
<Image
|
| 92 |
-
src={SpaceIcon}
|
| 93 |
-
alt="Space Icon"
|
| 94 |
-
className="size-7"
|
| 95 |
-
/>
|
| 96 |
-
</div>
|
| 97 |
-
<div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
| 98 |
-
👻
|
| 99 |
-
</div>
|
| 100 |
</div>
|
| 101 |
-
<
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
a way to share your project with the world.
|
| 107 |
-
</p>
|
| 108 |
-
</header>
|
| 109 |
-
<main className="space-y-4 p-6">
|
| 110 |
-
<div>
|
| 111 |
-
<p className="text-sm text-neutral-700 mb-2">
|
| 112 |
-
Choose a title for your space:
|
| 113 |
-
</p>
|
| 114 |
-
<Input
|
| 115 |
-
type="text"
|
| 116 |
-
placeholder="My Awesome Website"
|
| 117 |
-
value={config.title}
|
| 118 |
-
onChange={(e) =>
|
| 119 |
-
setConfig({ ...config, title: e.target.value })
|
| 120 |
-
}
|
| 121 |
-
className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
|
| 122 |
/>
|
| 123 |
</div>
|
| 124 |
-
<div>
|
| 125 |
-
|
| 126 |
-
Then, let's deploy it!
|
| 127 |
-
</p>
|
| 128 |
-
<Button
|
| 129 |
-
variant="black"
|
| 130 |
-
onClick={createSpace}
|
| 131 |
-
className="relative w-full"
|
| 132 |
-
disabled={loading}
|
| 133 |
-
>
|
| 134 |
-
Deploy Space <Rocket className="size-4" />
|
| 135 |
-
{loading && (
|
| 136 |
-
<Loading className="ml-2 size-4 animate-spin" />
|
| 137 |
-
)}
|
| 138 |
-
</Button>
|
| 139 |
</div>
|
| 140 |
-
</
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
>
|
| 150 |
-
<
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
</div>
|
| 171 |
</div>
|
| 172 |
);
|
|
|
|
| 16 |
} from "@/components/ui/popover";
|
| 17 |
import { Input } from "@/components/ui/input";
|
| 18 |
import { api } from "@/lib/api";
|
|
|
|
|
|
|
| 19 |
|
| 20 |
export function DeployButton({
|
| 21 |
html,
|
|
|
|
| 25 |
prompts: string[];
|
| 26 |
}) {
|
| 27 |
const router = useRouter();
|
| 28 |
+
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
|
| 29 |
|
| 30 |
const [config, setConfig] = useState({
|
| 31 |
title: "",
|
|
|
|
| 61 |
return (
|
| 62 |
<div className="flex items-center justify-end gap-5">
|
| 63 |
<div className="relative flex items-center justify-end">
|
| 64 |
+
<Popover>
|
| 65 |
+
<PopoverTrigger asChild>
|
| 66 |
+
<div>
|
| 67 |
+
<Button variant="default" className="max-lg:hidden !px-4">
|
| 68 |
+
<MdSave className="size-4" />
|
| 69 |
+
Save your Project
|
| 70 |
+
</Button>
|
| 71 |
+
<Button variant="default" size="sm" className="lg:hidden">
|
| 72 |
+
Deploy
|
| 73 |
+
</Button>
|
| 74 |
+
</div>
|
| 75 |
+
</PopoverTrigger>
|
| 76 |
+
<PopoverContent
|
| 77 |
+
className="!rounded-2xl !p-0 !bg-white !border-neutral-200 min-w-xs text-center overflow-hidden"
|
| 78 |
+
align="end"
|
| 79 |
+
>
|
| 80 |
+
<header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
|
| 81 |
+
<div className="flex items-center justify-center -space-x-4 mb-3">
|
| 82 |
+
<div className="size-9 rounded-full bg-amber-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
| 83 |
+
🚀
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
</div>
|
| 85 |
+
<div className="size-11 rounded-full bg-red-200 shadow-2xl flex items-center justify-center z-2">
|
| 86 |
+
<Image
|
| 87 |
+
src={SpaceIcon}
|
| 88 |
+
alt="Space Icon"
|
| 89 |
+
className="size-7"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
/>
|
| 91 |
</div>
|
| 92 |
+
<div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
| 93 |
+
👻
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
</div>
|
| 95 |
+
</div>
|
| 96 |
+
<p className="text-xl font-semibold text-neutral-950">
|
| 97 |
+
Deploy as Space!
|
| 98 |
+
</p>
|
| 99 |
+
<p className="text-sm text-neutral-500 mt-1.5">
|
| 100 |
+
Save and Deploy your project to a Space on the Hub. Spaces are
|
| 101 |
+
a way to share your project with the world.
|
| 102 |
+
</p>
|
| 103 |
+
</header>
|
| 104 |
+
<main className="space-y-4 p-6">
|
| 105 |
+
<div>
|
| 106 |
+
<p className="text-sm text-neutral-700 mb-2">
|
| 107 |
+
Choose a title for your space:
|
| 108 |
+
</p>
|
| 109 |
+
<Input
|
| 110 |
+
type="text"
|
| 111 |
+
placeholder="My Awesome Website"
|
| 112 |
+
value={config.title}
|
| 113 |
+
onChange={(e) =>
|
| 114 |
+
setConfig({ ...config, title: e.target.value })
|
| 115 |
+
}
|
| 116 |
+
className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
|
| 117 |
+
/>
|
| 118 |
+
</div>
|
| 119 |
+
<div>
|
| 120 |
+
<p className="text-sm text-neutral-700 mb-2">
|
| 121 |
+
Then, let's deploy it!
|
| 122 |
+
</p>
|
| 123 |
+
<Button
|
| 124 |
+
variant="black"
|
| 125 |
+
onClick={createSpace}
|
| 126 |
+
className="relative w-full"
|
| 127 |
+
disabled={loading}
|
| 128 |
+
>
|
| 129 |
+
Deploy Space <Rocket className="size-4" />
|
| 130 |
+
{loading && (
|
| 131 |
+
<Loading className="ml-2 size-4 animate-spin" />
|
| 132 |
+
)}
|
| 133 |
+
</Button>
|
| 134 |
+
</div>
|
| 135 |
+
</main>
|
| 136 |
+
</PopoverContent>
|
| 137 |
+
</Popover>
|
| 138 |
</div>
|
| 139 |
</div>
|
| 140 |
);
|
components/editor/footer/index.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import classNames from "classnames";
|
| 2 |
import { FaMobileAlt } from "react-icons/fa";
|
| 3 |
-
import {
|
| 4 |
import { FaLaptopCode } from "react-icons/fa6";
|
| 5 |
import { HtmlHistory } from "@/types";
|
| 6 |
import { Button } from "@/components/ui/button";
|
|
@@ -74,24 +74,20 @@ export function Footer({
|
|
| 74 |
)}
|
| 75 |
</div>
|
| 76 |
<div className="flex justify-end items-center gap-2.5">
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
<HelpCircle className="size-3.5" />
|
| 92 |
-
<span className="max-lg:hidden">Help</span>
|
| 93 |
-
</Button>
|
| 94 |
-
</a>
|
| 95 |
<Button size="sm" variant="outline" onClick={handleRefreshIframe}>
|
| 96 |
<RefreshCcw className="size-3.5" />
|
| 97 |
<span className="max-lg:hidden">Refresh Preview</span>
|
|
|
|
| 1 |
import classNames from "classnames";
|
| 2 |
import { FaMobileAlt } from "react-icons/fa";
|
| 3 |
+
import { RefreshCcw } from "lucide-react";
|
| 4 |
import { FaLaptopCode } from "react-icons/fa6";
|
| 5 |
import { HtmlHistory } from "@/types";
|
| 6 |
import { Button } from "@/components/ui/button";
|
|
|
|
| 74 |
)}
|
| 75 |
</div>
|
| 76 |
<div className="flex justify-end items-center gap-2.5">
|
| 77 |
+
|
| 78 |
+
<style>{`
|
| 79 |
+
@keyframes blink-star {
|
| 80 |
+
0%, 100% { opacity: 1; }
|
| 81 |
+
50% { opacity: 0.2; }
|
| 82 |
+
}
|
| 83 |
+
`}</style>
|
| 84 |
+
<span className="max-lg:hidden"><a
|
| 85 |
+
href="https://github.com/MartinsMessias/deepsite-locally"
|
| 86 |
+
target="_blank"
|
| 87 |
+
className="text-xs lg:text-sm font-medium py-2 px-3 lg:px-4 rounded-lg flex items-center transition-all duration-100 cursor-pointer"
|
| 88 |
+
>
|
| 89 |
+
<span style={{ animation: 'blink-star 2s infinite' }}>⭐</span> <span>Give a Star on GitHub</span>
|
| 90 |
+
</a></span>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
<Button size="sm" variant="outline" onClick={handleRefreshIframe}>
|
| 92 |
<RefreshCcw className="size-3.5" />
|
| 93 |
<span className="max-lg:hidden">Refresh Preview</span>
|
components/editor/index.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
"use client";
|
| 2 |
import { useRef, useState } from "react";
|
| 3 |
import { toast } from "sonner";
|
|
@@ -13,7 +14,7 @@ import {
|
|
| 13 |
useUpdateEffect,
|
| 14 |
} from "react-use";
|
| 15 |
import classNames from "classnames";
|
| 16 |
-
import { useRouter
|
| 17 |
|
| 18 |
import { Header } from "@/components/editor/header";
|
| 19 |
import { Footer } from "@/components/editor/footer";
|
|
@@ -21,7 +22,6 @@ import { defaultHTML } from "@/lib/consts";
|
|
| 21 |
import { Preview } from "@/components/editor/preview";
|
| 22 |
import { useEditor } from "@/hooks/useEditor";
|
| 23 |
import { AskAI } from "@/components/editor/ask-ai";
|
| 24 |
-
import { DeployButton } from "./deploy-button";
|
| 25 |
import { Project } from "@/types";
|
| 26 |
import { SaveButton } from "./save-button";
|
| 27 |
import { LoadProject } from "../my-projects/load-project";
|
|
@@ -33,16 +33,13 @@ export const AppEditor = ({ project }: { project?: Project | null }) => {
|
|
| 33 |
const { html, setHtml, htmlHistory, setHtmlHistory, prompts, setPrompts } =
|
| 34 |
useEditor(project?.html ?? (htmlStorage as string) ?? defaultHTML);
|
| 35 |
// get query params from URL
|
| 36 |
-
const searchParams = useSearchParams();
|
| 37 |
const router = useRouter();
|
| 38 |
-
const deploy = searchParams.get("deploy") === "true";
|
| 39 |
|
| 40 |
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
| 41 |
const preview = useRef<HTMLDivElement>(null);
|
| 42 |
const editor = useRef<HTMLDivElement>(null);
|
| 43 |
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
| 44 |
const resizer = useRef<HTMLDivElement>(null);
|
| 45 |
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 46 |
const monacoRef = useRef<any>(null);
|
| 47 |
|
| 48 |
const [currentTab, setCurrentTab] = useState("chat");
|
|
@@ -114,20 +111,7 @@ export const AppEditor = ({ project }: { project?: Project | null }) => {
|
|
| 114 |
};
|
| 115 |
|
| 116 |
useMount(() => {
|
| 117 |
-
|
| 118 |
-
toast.success("Your project is deployed! 🎉", {
|
| 119 |
-
action: {
|
| 120 |
-
label: "See Project",
|
| 121 |
-
onClick: () => {
|
| 122 |
-
window.open(
|
| 123 |
-
`https://huggingface.co/spaces/${project?.space_id}`,
|
| 124 |
-
"_blank"
|
| 125 |
-
);
|
| 126 |
-
},
|
| 127 |
-
},
|
| 128 |
-
});
|
| 129 |
-
router.replace(`/projects/${project?.space_id}`);
|
| 130 |
-
}
|
| 131 |
if (htmlStorage) {
|
| 132 |
removeHtmlStorage();
|
| 133 |
toast.warning("Previous HTML content restored from local storage.");
|
|
@@ -180,14 +164,16 @@ export const AppEditor = ({ project }: { project?: Project | null }) => {
|
|
| 180 |
<Header tab={currentTab} onNewTab={setCurrentTab}>
|
| 181 |
<LoadProject
|
| 182 |
onSuccess={(project: Project) => {
|
| 183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
}}
|
| 185 |
/>
|
| 186 |
-
{
|
| 187 |
-
<SaveButton html={html} prompts={prompts} />
|
| 188 |
-
) : (
|
| 189 |
-
<DeployButton html={html} prompts={prompts} />
|
| 190 |
-
)}
|
| 191 |
</Header>
|
| 192 |
<main className="bg-neutral-950 flex-1 max-lg:flex-col flex w-full max-lg:h-[calc(100%-82px)] relative">
|
| 193 |
{currentTab === "chat" && (
|
|
|
|
| 1 |
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
"use client";
|
| 3 |
import { useRef, useState } from "react";
|
| 4 |
import { toast } from "sonner";
|
|
|
|
| 14 |
useUpdateEffect,
|
| 15 |
} from "react-use";
|
| 16 |
import classNames from "classnames";
|
| 17 |
+
import { useRouter } from "next/navigation";
|
| 18 |
|
| 19 |
import { Header } from "@/components/editor/header";
|
| 20 |
import { Footer } from "@/components/editor/footer";
|
|
|
|
| 22 |
import { Preview } from "@/components/editor/preview";
|
| 23 |
import { useEditor } from "@/hooks/useEditor";
|
| 24 |
import { AskAI } from "@/components/editor/ask-ai";
|
|
|
|
| 25 |
import { Project } from "@/types";
|
| 26 |
import { SaveButton } from "./save-button";
|
| 27 |
import { LoadProject } from "../my-projects/load-project";
|
|
|
|
| 33 |
const { html, setHtml, htmlHistory, setHtmlHistory, prompts, setPrompts } =
|
| 34 |
useEditor(project?.html ?? (htmlStorage as string) ?? defaultHTML);
|
| 35 |
// get query params from URL
|
|
|
|
| 36 |
const router = useRouter();
|
|
|
|
| 37 |
|
| 38 |
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
| 39 |
const preview = useRef<HTMLDivElement>(null);
|
| 40 |
const editor = useRef<HTMLDivElement>(null);
|
| 41 |
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
| 42 |
const resizer = useRef<HTMLDivElement>(null);
|
|
|
|
| 43 |
const monacoRef = useRef<any>(null);
|
| 44 |
|
| 45 |
const [currentTab, setCurrentTab] = useState("chat");
|
|
|
|
| 111 |
};
|
| 112 |
|
| 113 |
useMount(() => {
|
| 114 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
if (htmlStorage) {
|
| 116 |
removeHtmlStorage();
|
| 117 |
toast.warning("Previous HTML content restored from local storage.");
|
|
|
|
| 164 |
<Header tab={currentTab} onNewTab={setCurrentTab}>
|
| 165 |
<LoadProject
|
| 166 |
onSuccess={(project: Project) => {
|
| 167 |
+
if (project.space_id && project.space_id !== "local") {
|
| 168 |
+
router.push(`/projects/${project.space_id}`);
|
| 169 |
+
} else {
|
| 170 |
+
setHtml(project.html);
|
| 171 |
+
setPrompts(project.prompts || []);
|
| 172 |
+
toast.success("Projeto HTML carregado.");
|
| 173 |
+
}
|
| 174 |
}}
|
| 175 |
/>
|
| 176 |
+
<SaveButton html={html} prompts={prompts} />
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
</Header>
|
| 178 |
<main className="bg-neutral-950 flex-1 max-lg:flex-col flex w-full max-lg:h-[calc(100%-82px)] relative">
|
| 179 |
{currentTab === "chat" && (
|
components/editor/preview/index.tsx
CHANGED
|
@@ -129,7 +129,7 @@ export const Preview = ({
|
|
| 129 |
/>
|
| 130 |
{!isAiWorking && hoveredElement && selectedElement && (
|
| 131 |
<div
|
| 132 |
-
className="cursor-pointer absolute bg-sky-500/10 border-[2px] border-dashed border-sky-500
|
| 133 |
style={{
|
| 134 |
top:
|
| 135 |
selectedElement.getBoundingClientRect().top +
|
|
@@ -141,36 +141,97 @@ export const Preview = ({
|
|
| 141 |
height: selectedElement.getBoundingClientRect().height,
|
| 142 |
}}
|
| 143 |
>
|
| 144 |
-
<span className="bg-sky-500
|
| 145 |
{htmlTagToText(selectedElement.tagName.toLowerCase())}
|
| 146 |
</span>
|
| 147 |
</div>
|
| 148 |
)}
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
</div>
|
| 175 |
);
|
| 176 |
};
|
|
|
|
| 129 |
/>
|
| 130 |
{!isAiWorking && hoveredElement && selectedElement && (
|
| 131 |
<div
|
| 132 |
+
className="cursor-pointer absolute bg-sky-500/10 border-[2px] border-dashed border-sky-500 p-3 z-10 pointer-events-none"
|
| 133 |
style={{
|
| 134 |
top:
|
| 135 |
selectedElement.getBoundingClientRect().top +
|
|
|
|
| 141 |
height: selectedElement.getBoundingClientRect().height,
|
| 142 |
}}
|
| 143 |
>
|
| 144 |
+
<span className="bg-sky-500 text-sm text-neutral-100 px-2 py-0.5 -translate-y-7 absolute top-0 left-0">
|
| 145 |
{htmlTagToText(selectedElement.tagName.toLowerCase())}
|
| 146 |
</span>
|
| 147 |
</div>
|
| 148 |
)}
|
| 149 |
+
{device === "mobile" ? (
|
| 150 |
+
<div
|
| 151 |
+
style={{
|
| 152 |
+
width: 320,
|
| 153 |
+
height: 684,
|
| 154 |
+
border: "16px solid #222",
|
| 155 |
+
borderRadius: 40,
|
| 156 |
+
boxShadow: "0 8px 40px #0008",
|
| 157 |
+
position: "relative",
|
| 158 |
+
background: "#111",
|
| 159 |
+
display: "flex",
|
| 160 |
+
alignItems: "center",
|
| 161 |
+
justifyContent: "center",
|
| 162 |
+
padding: 0,
|
| 163 |
+
}}
|
| 164 |
+
>
|
| 165 |
+
<div
|
| 166 |
+
style={{
|
| 167 |
+
position: "absolute",
|
| 168 |
+
top: 10,
|
| 169 |
+
left: "50%",
|
| 170 |
+
transform: "translateX(-50%)",
|
| 171 |
+
width: 60,
|
| 172 |
+
height: 5,
|
| 173 |
+
background: "#444",
|
| 174 |
+
borderRadius: 5,
|
| 175 |
+
zIndex: 3,
|
| 176 |
+
}}
|
| 177 |
+
/>
|
| 178 |
+
<iframe
|
| 179 |
+
id="preview-iframe"
|
| 180 |
+
ref={iframeRef}
|
| 181 |
+
title="output"
|
| 182 |
+
className={classNames(
|
| 183 |
+
"w-full h-full select-none transition-all duration-200 bg-black",
|
| 184 |
+
{
|
| 185 |
+
"pointer-events-none": isResizing || isAiWorking,
|
| 186 |
+
"rounded-[32px]": false,
|
| 187 |
+
}
|
| 188 |
+
)}
|
| 189 |
+
style={{
|
| 190 |
+
border: "none",
|
| 191 |
+
width: 288,
|
| 192 |
+
height: 608,
|
| 193 |
+
borderRadius: 28,
|
| 194 |
+
marginTop: 24,
|
| 195 |
+
marginBottom: 24,
|
| 196 |
+
background: "#000",
|
| 197 |
+
}}
|
| 198 |
+
srcDoc={html}
|
| 199 |
+
onLoad={() => {
|
| 200 |
+
if (iframeRef?.current?.contentWindow?.document?.body) {
|
| 201 |
+
iframeRef.current.contentWindow.document.body.scrollIntoView({
|
| 202 |
+
block: isAiWorking ? "end" : "start",
|
| 203 |
+
inline: "nearest",
|
| 204 |
+
behavior: isAiWorking ? "instant" : "smooth",
|
| 205 |
+
});
|
| 206 |
+
}
|
| 207 |
+
}}
|
| 208 |
+
/>
|
| 209 |
+
</div>
|
| 210 |
+
) : (
|
| 211 |
+
<iframe
|
| 212 |
+
id="preview-iframe"
|
| 213 |
+
ref={iframeRef}
|
| 214 |
+
title="output"
|
| 215 |
+
className={classNames(
|
| 216 |
+
"w-full select-none transition-all duration-200 bg-black h-full",
|
| 217 |
+
{
|
| 218 |
+
"pointer-events-none": isResizing || isAiWorking,
|
| 219 |
+
"lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl":
|
| 220 |
+
currentTab !== "preview" && device === "desktop",
|
| 221 |
+
}
|
| 222 |
+
)}
|
| 223 |
+
srcDoc={html}
|
| 224 |
+
onLoad={() => {
|
| 225 |
+
if (iframeRef?.current?.contentWindow?.document?.body) {
|
| 226 |
+
iframeRef.current.contentWindow.document.body.scrollIntoView({
|
| 227 |
+
block: isAiWorking ? "end" : "start",
|
| 228 |
+
inline: "nearest",
|
| 229 |
+
behavior: isAiWorking ? "instant" : "smooth",
|
| 230 |
+
});
|
| 231 |
+
}
|
| 232 |
+
}}
|
| 233 |
+
/>
|
| 234 |
+
)}
|
| 235 |
</div>
|
| 236 |
);
|
| 237 |
};
|
components/editor/save-button/index.tsx
CHANGED
|
@@ -31,17 +31,7 @@ export function SaveButton({
|
|
| 31 |
prompts,
|
| 32 |
});
|
| 33 |
if (res.data.ok) {
|
| 34 |
-
toast.success("Your space is updated! 🎉"
|
| 35 |
-
action: {
|
| 36 |
-
label: "See Space",
|
| 37 |
-
onClick: () => {
|
| 38 |
-
window.open(
|
| 39 |
-
`https://huggingface.co/spaces/${namespace}/${repoId}`,
|
| 40 |
-
"_blank"
|
| 41 |
-
);
|
| 42 |
-
},
|
| 43 |
-
},
|
| 44 |
-
});
|
| 45 |
} else {
|
| 46 |
toast.error(res?.data?.error || "Failed to update space");
|
| 47 |
}
|
|
@@ -55,20 +45,33 @@ export function SaveButton({
|
|
| 55 |
<>
|
| 56 |
<Button
|
| 57 |
variant="default"
|
| 58 |
-
|
|
|
|
| 59 |
onClick={updateSpace}
|
| 60 |
>
|
| 61 |
-
<
|
| 62 |
-
Save your Project{" "}
|
| 63 |
-
{loading && <Loading className="ml-2 size-4 animate-spin" />}
|
| 64 |
</Button>
|
| 65 |
<Button
|
| 66 |
variant="default"
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
>
|
| 71 |
-
Save
|
|
|
|
| 72 |
</Button>
|
| 73 |
</>
|
| 74 |
);
|
|
|
|
| 31 |
prompts,
|
| 32 |
});
|
| 33 |
if (res.data.ok) {
|
| 34 |
+
toast.success("Your space is updated! 🎉");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
} else {
|
| 36 |
toast.error(res?.data?.error || "Failed to update space");
|
| 37 |
}
|
|
|
|
| 45 |
<>
|
| 46 |
<Button
|
| 47 |
variant="default"
|
| 48 |
+
size="sm"
|
| 49 |
+
className="lg:hidden relative"
|
| 50 |
onClick={updateSpace}
|
| 51 |
>
|
| 52 |
+
Save {loading && <Loading className="ml-2 size-4 animate-spin" />}
|
|
|
|
|
|
|
| 53 |
</Button>
|
| 54 |
<Button
|
| 55 |
variant="default"
|
| 56 |
+
className="max-lg:hidden !px-4 relative"
|
| 57 |
+
onClick={() => {
|
| 58 |
+
let filename = prompt("Nome do arquivo .html:", "index.html");
|
| 59 |
+
if (!filename) return;
|
| 60 |
+
if (!filename.endsWith('.html')) filename += '.html';
|
| 61 |
+
const blob = new Blob([html], { type: "text/html" });
|
| 62 |
+
const url = URL.createObjectURL(blob);
|
| 63 |
+
const a = document.createElement("a");
|
| 64 |
+
a.href = url;
|
| 65 |
+
a.download = filename;
|
| 66 |
+
document.body.appendChild(a);
|
| 67 |
+
a.click();
|
| 68 |
+
document.body.removeChild(a);
|
| 69 |
+
URL.revokeObjectURL(url);
|
| 70 |
+
toast.success("HTML baixado com sucesso!");
|
| 71 |
+
}}
|
| 72 |
>
|
| 73 |
+
Save to File
|
| 74 |
+
<MdSave className="ml-2" />
|
| 75 |
</Button>
|
| 76 |
</>
|
| 77 |
);
|
components/login-modal/index.tsx
CHANGED
|
@@ -1,15 +1,11 @@
|
|
| 1 |
-
import { useLocalStorage } from "react-use";
|
| 2 |
import { Button } from "@/components/ui/button";
|
| 3 |
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
| 4 |
-
import { useUser } from "@/hooks/useUser";
|
| 5 |
-
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
| 6 |
|
| 7 |
export const LoginModal = ({
|
| 8 |
open,
|
| 9 |
-
html,
|
| 10 |
onClose,
|
| 11 |
-
title = "
|
| 12 |
-
description = "
|
| 13 |
}: {
|
| 14 |
open: boolean;
|
| 15 |
html?: string;
|
|
@@ -17,15 +13,6 @@ export const LoginModal = ({
|
|
| 17 |
title?: string;
|
| 18 |
description?: string;
|
| 19 |
}) => {
|
| 20 |
-
const { openLoginWindow } = useUser();
|
| 21 |
-
const [, setStorage] = useLocalStorage("html_content");
|
| 22 |
-
const handleClick = async () => {
|
| 23 |
-
if (html && !isTheSameHtml(html)) {
|
| 24 |
-
setStorage(html);
|
| 25 |
-
}
|
| 26 |
-
openLoginWindow();
|
| 27 |
-
onClose(false);
|
| 28 |
-
};
|
| 29 |
return (
|
| 30 |
<Dialog open={open} onOpenChange={onClose}>
|
| 31 |
<DialogContent className="sm:max-w-lg lg:!p-8 !rounded-3xl !bg-white !border-neutral-100">
|
|
@@ -50,12 +37,12 @@ export const LoginModal = ({
|
|
| 50 |
variant="black"
|
| 51 |
size="lg"
|
| 52 |
className="w-full !text-base !h-11 mt-8"
|
| 53 |
-
onClick={
|
| 54 |
>
|
| 55 |
-
|
| 56 |
</Button>
|
| 57 |
</main>
|
| 58 |
</DialogContent>
|
| 59 |
</Dialog>
|
| 60 |
);
|
| 61 |
-
};
|
|
|
|
|
|
|
| 1 |
import { Button } from "@/components/ui/button";
|
| 2 |
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
|
|
|
|
|
|
| 3 |
|
| 4 |
export const LoginModal = ({
|
| 5 |
open,
|
|
|
|
| 6 |
onClose,
|
| 7 |
+
title = "Login is not available",
|
| 8 |
+
description = "This feature is currently disabled as the application no longer relies on Hugging Face authentication.",
|
| 9 |
}: {
|
| 10 |
open: boolean;
|
| 11 |
html?: string;
|
|
|
|
| 13 |
title?: string;
|
| 14 |
description?: string;
|
| 15 |
}) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
return (
|
| 17 |
<Dialog open={open} onOpenChange={onClose}>
|
| 18 |
<DialogContent className="sm:max-w-lg lg:!p-8 !rounded-3xl !bg-white !border-neutral-100">
|
|
|
|
| 37 |
variant="black"
|
| 38 |
size="lg"
|
| 39 |
className="w-full !text-base !h-11 mt-8"
|
| 40 |
+
onClick={() => onClose(false)}
|
| 41 |
>
|
| 42 |
+
Close
|
| 43 |
</Button>
|
| 44 |
</main>
|
| 45 |
</DialogContent>
|
| 46 |
</Dialog>
|
| 47 |
);
|
| 48 |
+
};
|
components/my-projects/load-project.tsx
CHANGED
|
@@ -13,9 +13,6 @@ import {
|
|
| 13 |
import Loading from "@/components/loading";
|
| 14 |
import { Input } from "../ui/input";
|
| 15 |
import { toast } from "sonner";
|
| 16 |
-
import { api } from "@/lib/api";
|
| 17 |
-
import { useUser } from "@/hooks/useUser";
|
| 18 |
-
import { LoginModal } from "../login-modal";
|
| 19 |
import { useRouter } from "next/navigation";
|
| 20 |
|
| 21 |
export const LoadProject = ({
|
|
@@ -25,46 +22,28 @@ export const LoadProject = ({
|
|
| 25 |
fullXsBtn?: boolean;
|
| 26 |
onSuccess: (project: Project) => void;
|
| 27 |
}) => {
|
| 28 |
-
|
| 29 |
-
const router = useRouter();
|
| 30 |
|
| 31 |
-
const [openLoginModal, setOpenLoginModal] = useState(false);
|
| 32 |
const [open, setOpen] = useState(false);
|
| 33 |
const [url, setUrl] = useState<string>("");
|
| 34 |
const [isLoading, setIsLoading] = useState(false);
|
| 35 |
|
| 36 |
-
const checkIfUrlIsValid = (url: string) => {
|
| 37 |
-
// should match a hugging face spaces URL like: https://huggingface.co/spaces/username/project or https://hf.co/spaces/username/project
|
| 38 |
-
const urlPattern = new RegExp(
|
| 39 |
-
/^(https?:\/\/)?(huggingface\.co|hf\.co)\/spaces\/([\w-]+)\/([\w-]+)$/,
|
| 40 |
-
"i"
|
| 41 |
-
);
|
| 42 |
-
return urlPattern.test(url);
|
| 43 |
-
};
|
| 44 |
-
|
| 45 |
const handleClick = async () => {
|
| 46 |
if (isLoading) return; // Prevent multiple clicks while loading
|
| 47 |
if (!url) {
|
| 48 |
toast.error("Please enter a URL.");
|
| 49 |
return;
|
| 50 |
}
|
| 51 |
-
if (!checkIfUrlIsValid(url)) {
|
| 52 |
-
toast.error("Please enter a valid Hugging Face Spaces URL.");
|
| 53 |
-
return;
|
| 54 |
-
}
|
| 55 |
|
| 56 |
-
|
| 57 |
-
.replace("https://huggingface.co/spaces/", "")
|
| 58 |
-
.replace("https://hf.co/spaces/", "")
|
| 59 |
-
.split("/");
|
| 60 |
|
| 61 |
setIsLoading(true);
|
| 62 |
try {
|
| 63 |
-
|
|
|
|
| 64 |
toast.success("Project imported successfully!");
|
| 65 |
setOpen(false);
|
| 66 |
setUrl("");
|
| 67 |
-
onSuccess(response.data.project);
|
| 68 |
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 69 |
} catch (error: any) {
|
| 70 |
if (error?.response?.data?.redirect) {
|
|
@@ -79,122 +58,98 @@ export const LoadProject = ({
|
|
| 79 |
};
|
| 80 |
|
| 81 |
return (
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
<Button
|
| 86 |
-
variant="outline"
|
| 87 |
-
className="max-lg:hidden"
|
| 88 |
-
onClick={() => setOpenLoginModal(true)}
|
| 89 |
-
>
|
| 90 |
<Import className="size-4 mr-1.5" />
|
| 91 |
Load existing Project
|
| 92 |
</Button>
|
| 93 |
-
<Button
|
| 94 |
-
variant="outline"
|
| 95 |
-
size="sm"
|
| 96 |
-
className="lg:hidden"
|
| 97 |
-
onClick={() => setOpenLoginModal(true)}
|
| 98 |
-
>
|
| 99 |
{fullXsBtn && <Import className="size-3.5 mr-1" />}
|
| 100 |
Load
|
| 101 |
{fullXsBtn && " existing Project"}
|
| 102 |
</Button>
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
overlay={false}
|
| 185 |
-
className="ml-2 size-4 animate-spin"
|
| 186 |
-
/>
|
| 187 |
-
Fetching your Space...
|
| 188 |
-
</>
|
| 189 |
-
) : (
|
| 190 |
-
<>Import your Space</>
|
| 191 |
-
)}
|
| 192 |
-
</Button>
|
| 193 |
-
</div>
|
| 194 |
-
</main>
|
| 195 |
-
</DialogContent>
|
| 196 |
-
</Dialog>
|
| 197 |
-
)}
|
| 198 |
-
</>
|
| 199 |
);
|
| 200 |
};
|
|
|
|
| 13 |
import Loading from "@/components/loading";
|
| 14 |
import { Input } from "../ui/input";
|
| 15 |
import { toast } from "sonner";
|
|
|
|
|
|
|
|
|
|
| 16 |
import { useRouter } from "next/navigation";
|
| 17 |
|
| 18 |
export const LoadProject = ({
|
|
|
|
| 22 |
fullXsBtn?: boolean;
|
| 23 |
onSuccess: (project: Project) => void;
|
| 24 |
}) => {
|
| 25 |
+
const router = useRouter();
|
|
|
|
| 26 |
|
|
|
|
| 27 |
const [open, setOpen] = useState(false);
|
| 28 |
const [url, setUrl] = useState<string>("");
|
| 29 |
const [isLoading, setIsLoading] = useState(false);
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
const handleClick = async () => {
|
| 32 |
if (isLoading) return; // Prevent multiple clicks while loading
|
| 33 |
if (!url) {
|
| 34 |
toast.error("Please enter a URL.");
|
| 35 |
return;
|
| 36 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
+
// The URL validation and parsing logic is removed as we are no longer using Hugging Face
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
setIsLoading(true);
|
| 41 |
try {
|
| 42 |
+
// You will need to implement your own logic to import a project from a URL
|
| 43 |
+
// For now, this will just display a success message
|
| 44 |
toast.success("Project imported successfully!");
|
| 45 |
setOpen(false);
|
| 46 |
setUrl("");
|
|
|
|
| 47 |
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 48 |
} catch (error: any) {
|
| 49 |
if (error?.response?.data?.redirect) {
|
|
|
|
| 58 |
};
|
| 59 |
|
| 60 |
return (
|
| 61 |
+
<Dialog open={open} onOpenChange={setOpen}>
|
| 62 |
+
<DialogTrigger asChild>
|
| 63 |
+
<div>
|
| 64 |
+
<Button variant="outline" className="max-lg:hidden">
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
<Import className="size-4 mr-1.5" />
|
| 66 |
Load existing Project
|
| 67 |
</Button>
|
| 68 |
+
<Button variant="outline" size="sm" className="lg:hidden">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
{fullXsBtn && <Import className="size-3.5 mr-1" />}
|
| 70 |
Load
|
| 71 |
{fullXsBtn && " existing Project"}
|
| 72 |
</Button>
|
| 73 |
+
</div>
|
| 74 |
+
</DialogTrigger>
|
| 75 |
+
<DialogContent className="sm:max-w-md !p-0 !rounded-3xl !bg-white !border-neutral-100 overflow-hidden text-center">
|
| 76 |
+
<DialogTitle className="hidden" />
|
| 77 |
+
<header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
|
| 78 |
+
<p className="text-2xl font-semibold text-neutral-950">
|
| 79 |
+
Import a Project
|
| 80 |
+
</p>
|
| 81 |
+
<p className="text-base text-neutral-500 mt-1.5">
|
| 82 |
+
Enter the URL of your project to import it.
|
| 83 |
+
</p>
|
| 84 |
+
</header>
|
| 85 |
+
<main className="space-y-4 px-9 pb-9 pt-2">
|
| 86 |
+
<div>
|
| 87 |
+
<p className="text-sm text-neutral-700 mb-2">
|
| 88 |
+
Load HTML from your computer
|
| 89 |
+
</p>
|
| 90 |
+
<Input
|
| 91 |
+
type="file"
|
| 92 |
+
accept=".html"
|
| 93 |
+
onChange={(e) => {
|
| 94 |
+
const file = e.target.files?.[0];
|
| 95 |
+
if (file) {
|
| 96 |
+
const reader = new FileReader();
|
| 97 |
+
reader.onload = (event) => {
|
| 98 |
+
const htmlContent = event.target?.result as string;
|
| 99 |
+
onSuccess({
|
| 100 |
+
html: htmlContent,
|
| 101 |
+
prompts: [],
|
| 102 |
+
title: "Imported Project",
|
| 103 |
+
user_id: "local",
|
| 104 |
+
space_id: "local"
|
| 105 |
+
});
|
| 106 |
+
setOpen(false);
|
| 107 |
+
};
|
| 108 |
+
reader.readAsText(file);
|
| 109 |
+
}
|
| 110 |
+
}}
|
| 111 |
+
className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
|
| 112 |
+
/>
|
| 113 |
+
</div>
|
| 114 |
+
<div className="text-sm text-neutral-700 mb-2">
|
| 115 |
+
OR
|
| 116 |
+
</div>
|
| 117 |
+
<div>
|
| 118 |
+
<p className="text-sm text-neutral-700 mb-2">
|
| 119 |
+
Enter your Project URL
|
| 120 |
+
</p>
|
| 121 |
+
<Input
|
| 122 |
+
type="text"
|
| 123 |
+
placeholder="https://example.com/my-project"
|
| 124 |
+
value={url}
|
| 125 |
+
onChange={(e) => setUrl(e.target.value)}
|
| 126 |
+
className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
|
| 127 |
+
/>
|
| 128 |
+
</div>
|
| 129 |
+
<div>
|
| 130 |
+
<p className="text-sm text-neutral-700 mb-2">
|
| 131 |
+
Then, let's import it!
|
| 132 |
+
</p>
|
| 133 |
+
<Button
|
| 134 |
+
variant="black"
|
| 135 |
+
onClick={handleClick}
|
| 136 |
+
className="relative w-full"
|
| 137 |
+
>
|
| 138 |
+
{isLoading ? (
|
| 139 |
+
<>
|
| 140 |
+
<Loading
|
| 141 |
+
overlay={false}
|
| 142 |
+
className="ml-2 size-4 animate-spin"
|
| 143 |
+
/>
|
| 144 |
+
Importing...
|
| 145 |
+
</>
|
| 146 |
+
) : (
|
| 147 |
+
<>Import Project</>
|
| 148 |
+
)}
|
| 149 |
+
</Button>
|
| 150 |
+
</div>
|
| 151 |
+
</main>
|
| 152 |
+
</DialogContent>
|
| 153 |
+
</Dialog>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
);
|
| 155 |
};
|
components/my-projects/project-card.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import Link from "next/link";
|
| 2 |
import { formatDistance } from "date-fns";
|
| 3 |
-
import { EllipsisVertical
|
| 4 |
|
| 5 |
import { Project } from "@/types";
|
| 6 |
import { Button } from "@/components/ui/button";
|
|
@@ -8,8 +8,7 @@ import {
|
|
| 8 |
DropdownMenu,
|
| 9 |
DropdownMenuContent,
|
| 10 |
DropdownMenuGroup,
|
| 11 |
-
|
| 12 |
-
DropdownMenuTrigger,
|
| 13 |
} from "@/components/ui/dropdown-menu";
|
| 14 |
|
| 15 |
export function ProjectCard({ project }: { project: Project }) {
|
|
@@ -56,15 +55,7 @@ export function ProjectCard({ project }: { project: Project }) {
|
|
| 56 |
</DropdownMenuTrigger>
|
| 57 |
<DropdownMenuContent className="w-56" align="start">
|
| 58 |
<DropdownMenuGroup>
|
| 59 |
-
|
| 60 |
-
href={`https://huggingface.co/spaces/${project.space_id}/settings`}
|
| 61 |
-
target="_blank"
|
| 62 |
-
>
|
| 63 |
-
<DropdownMenuItem>
|
| 64 |
-
<Settings className="size-4 text-neutral-100" />
|
| 65 |
-
Project Settings
|
| 66 |
-
</DropdownMenuItem>
|
| 67 |
-
</a>
|
| 68 |
</DropdownMenuGroup>
|
| 69 |
</DropdownMenuContent>
|
| 70 |
</DropdownMenu>
|
|
|
|
| 1 |
import Link from "next/link";
|
| 2 |
import { formatDistance } from "date-fns";
|
| 3 |
+
import { EllipsisVertical } from "lucide-react";
|
| 4 |
|
| 5 |
import { Project } from "@/types";
|
| 6 |
import { Button } from "@/components/ui/button";
|
|
|
|
| 8 |
DropdownMenu,
|
| 9 |
DropdownMenuContent,
|
| 10 |
DropdownMenuGroup,
|
| 11 |
+
DropdownMenuTrigger,
|
|
|
|
| 12 |
} from "@/components/ui/dropdown-menu";
|
| 13 |
|
| 14 |
export function ProjectCard({ project }: { project: Project }) {
|
|
|
|
| 55 |
</DropdownMenuTrigger>
|
| 56 |
<DropdownMenuContent className="w-56" align="start">
|
| 57 |
<DropdownMenuGroup>
|
| 58 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
</DropdownMenuGroup>
|
| 60 |
</DropdownMenuContent>
|
| 61 |
</DropdownMenu>
|
components/pro-modal/index.tsx
CHANGED
|
@@ -1,24 +1,15 @@
|
|
| 1 |
-
import { useLocalStorage } from "react-use";
|
| 2 |
import { Button } from "@/components/ui/button";
|
| 3 |
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
| 4 |
-
import { CheckCheck } from "lucide-react";
|
| 5 |
-
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
| 6 |
|
| 7 |
export const ProModal = ({
|
| 8 |
open,
|
| 9 |
-
html,
|
| 10 |
onClose,
|
| 11 |
}: {
|
| 12 |
open: boolean;
|
| 13 |
-
html: string;
|
| 14 |
onClose: React.Dispatch<React.SetStateAction<boolean>>;
|
| 15 |
}) => {
|
| 16 |
-
const [, setStorage] = useLocalStorage("html_content");
|
| 17 |
const handleProClick = () => {
|
| 18 |
-
|
| 19 |
-
setStorage(html);
|
| 20 |
-
}
|
| 21 |
-
window.open("https://huggingface.co/subscribe/pro?from=DeepSite", "_blank");
|
| 22 |
onClose(false);
|
| 23 |
};
|
| 24 |
return (
|
|
@@ -26,55 +17,19 @@ export const ProModal = ({
|
|
| 26 |
<DialogContent className="sm:max-w-lg lg:!p-8 !rounded-3xl !bg-white !border-neutral-100">
|
| 27 |
<DialogTitle className="hidden" />
|
| 28 |
<main className="flex flex-col items-start text-left relative pt-2">
|
| 29 |
-
<div className="flex items-center justify-start -space-x-4 mb-5">
|
| 30 |
-
<div className="size-14 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-3xl opacity-50">
|
| 31 |
-
🚀
|
| 32 |
-
</div>
|
| 33 |
-
<div className="size-16 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-4xl z-2">
|
| 34 |
-
🤩
|
| 35 |
-
</div>
|
| 36 |
-
<div className="size-14 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-3xl opacity-50">
|
| 37 |
-
🥳
|
| 38 |
-
</div>
|
| 39 |
-
</div>
|
| 40 |
<h2 className="text-2xl font-bold text-neutral-950">
|
| 41 |
-
|
| 42 |
</h2>
|
| 43 |
<p className="text-neutral-500 text-base mt-2 max-w-sm">
|
| 44 |
-
|
| 45 |
</p>
|
| 46 |
-
<hr className="bg-neutral-200 w-full max-w-[150px] my-6" />
|
| 47 |
-
<p className="text-lg mt-3 text-neutral-900 font-semibold">
|
| 48 |
-
Upgrade to a <ProTag className="mx-1" /> Account, and unlock your
|
| 49 |
-
DeepSite high quota access ⚡
|
| 50 |
-
</p>
|
| 51 |
-
<ul className="mt-3 space-y-1 text-neutral-500">
|
| 52 |
-
<li className="text-sm text-neutral-500 space-x-2 flex items-center justify-start gap-2 mb-3">
|
| 53 |
-
You'll also unlock some Hugging Face PRO features, like:
|
| 54 |
-
</li>
|
| 55 |
-
<li className="text-sm space-x-2 flex items-center justify-start gap-2">
|
| 56 |
-
<CheckCheck className="text-emerald-500 size-4" />
|
| 57 |
-
Get acces to thousands of AI app (ZeroGPU) with high quota
|
| 58 |
-
</li>
|
| 59 |
-
<li className="text-sm space-x-2 flex items-center justify-start gap-2">
|
| 60 |
-
<CheckCheck className="text-emerald-500 size-4" />
|
| 61 |
-
Get exclusive early access to new features and updates
|
| 62 |
-
</li>
|
| 63 |
-
<li className="text-sm space-x-2 flex items-center justify-start gap-2">
|
| 64 |
-
<CheckCheck className="text-emerald-500 size-4" />
|
| 65 |
-
Get free credits across all Inference Providers
|
| 66 |
-
</li>
|
| 67 |
-
<li className="text-sm text-neutral-500 space-x-2 flex items-center justify-start gap-2 mt-3">
|
| 68 |
-
... and lots more!
|
| 69 |
-
</li>
|
| 70 |
-
</ul>
|
| 71 |
<Button
|
| 72 |
variant="black"
|
| 73 |
size="lg"
|
| 74 |
className="w-full !text-base !h-11 mt-8"
|
| 75 |
onClick={handleProClick}
|
| 76 |
>
|
| 77 |
-
|
| 78 |
</Button>
|
| 79 |
</main>
|
| 80 |
</DialogContent>
|
|
@@ -82,11 +37,4 @@ export const ProModal = ({
|
|
| 82 |
);
|
| 83 |
};
|
| 84 |
|
| 85 |
-
const ProTag = ({ className }: { className?: string }) => (
|
| 86 |
-
<span
|
| 87 |
-
className={`${className} bg-linear-to-br shadow-green-500/10 dark:shadow-green-500/20 inline-block -skew-x-12 border border-gray-200 from-pink-300 via-green-200 to-yellow-200 text-xs font-bold text-black shadow-lg rounded-md px-2.5 py-0.5`}
|
| 88 |
-
>
|
| 89 |
-
PRO
|
| 90 |
-
</span>
|
| 91 |
-
);
|
| 92 |
export default ProModal;
|
|
|
|
|
|
|
| 1 |
import { Button } from "@/components/ui/button";
|
| 2 |
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
|
|
|
|
|
|
| 3 |
|
| 4 |
export const ProModal = ({
|
| 5 |
open,
|
|
|
|
| 6 |
onClose,
|
| 7 |
}: {
|
| 8 |
open: boolean;
|
|
|
|
| 9 |
onClose: React.Dispatch<React.SetStateAction<boolean>>;
|
| 10 |
}) => {
|
|
|
|
| 11 |
const handleProClick = () => {
|
| 12 |
+
// Replace with your own subscription logic
|
|
|
|
|
|
|
|
|
|
| 13 |
onClose(false);
|
| 14 |
};
|
| 15 |
return (
|
|
|
|
| 17 |
<DialogContent className="sm:max-w-lg lg:!p-8 !rounded-3xl !bg-white !border-neutral-100">
|
| 18 |
<DialogTitle className="hidden" />
|
| 19 |
<main className="flex flex-col items-start text-left relative pt-2">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
<h2 className="text-2xl font-bold text-neutral-950">
|
| 21 |
+
Upgrade to Pro
|
| 22 |
</h2>
|
| 23 |
<p className="text-neutral-500 text-base mt-2 max-w-sm">
|
| 24 |
+
You have reached the monthly free limit.
|
| 25 |
</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
<Button
|
| 27 |
variant="black"
|
| 28 |
size="lg"
|
| 29 |
className="w-full !text-base !h-11 mt-8"
|
| 30 |
onClick={handleProClick}
|
| 31 |
>
|
| 32 |
+
Upgrade
|
| 33 |
</Button>
|
| 34 |
</main>
|
| 35 |
</DialogContent>
|
|
|
|
| 37 |
);
|
| 38 |
};
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
export default ProModal;
|
components/public/navigation/index.tsx
CHANGED
|
@@ -6,10 +6,7 @@ import Link from "next/link";
|
|
| 6 |
import { useMount, useUnmount } from "react-use";
|
| 7 |
import classNames from "classnames";
|
| 8 |
|
| 9 |
-
import { Button } from "@/components/ui/button";
|
| 10 |
import Logo from "@/assets/logo.svg";
|
| 11 |
-
import { useUser } from "@/hooks/useUser";
|
| 12 |
-
import { UserMenu } from "@/components/user-menu";
|
| 13 |
|
| 14 |
const navigationLinks = [
|
| 15 |
{
|
|
@@ -31,7 +28,6 @@ const navigationLinks = [
|
|
| 31 |
];
|
| 32 |
|
| 33 |
export default function Navigation() {
|
| 34 |
-
const { openLoginWindow, user } = useUser();
|
| 35 |
const [hash, setHash] = useState("");
|
| 36 |
|
| 37 |
const selectorRef = useRef<HTMLDivElement>(null);
|
|
@@ -138,18 +134,6 @@ export default function Navigation() {
|
|
| 138 |
<div className="size-1 bg-white rounded-full" />
|
| 139 |
</div>
|
| 140 |
</ul>
|
| 141 |
-
<div className="flex items-center justify-end gap-2">
|
| 142 |
-
{user ? (
|
| 143 |
-
<UserMenu className="!pl-3 !pr-4 !py-2 !h-auto !rounded-lg" />
|
| 144 |
-
) : (
|
| 145 |
-
<>
|
| 146 |
-
<Button variant="link" size={"sm"} onClick={openLoginWindow}>
|
| 147 |
-
Log In
|
| 148 |
-
</Button>
|
| 149 |
-
<Button size={"sm"}>Sign Up</Button>
|
| 150 |
-
</>
|
| 151 |
-
)}
|
| 152 |
-
</div>
|
| 153 |
</nav>
|
| 154 |
</div>
|
| 155 |
);
|
|
|
|
| 6 |
import { useMount, useUnmount } from "react-use";
|
| 7 |
import classNames from "classnames";
|
| 8 |
|
|
|
|
| 9 |
import Logo from "@/assets/logo.svg";
|
|
|
|
|
|
|
| 10 |
|
| 11 |
const navigationLinks = [
|
| 12 |
{
|
|
|
|
| 28 |
];
|
| 29 |
|
| 30 |
export default function Navigation() {
|
|
|
|
| 31 |
const [hash, setHash] = useState("");
|
| 32 |
|
| 33 |
const selectorRef = useRef<HTMLDivElement>(null);
|
|
|
|
| 134 |
<div className="size-1 bg-white rounded-full" />
|
| 135 |
</div>
|
| 136 |
</ul>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
</nav>
|
| 138 |
</div>
|
| 139 |
);
|
components/user-menu/index.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
import {
|
| 2 |
-
|
| 3 |
-
CirclePlus,
|
| 4 |
FolderCode,
|
| 5 |
Import,
|
| 6 |
LogOut,
|
|
@@ -63,12 +62,7 @@ export const UserMenu = ({ className }: { className?: string }) => {
|
|
| 63 |
View Projects
|
| 64 |
</DropdownMenuItem>
|
| 65 |
</Link>
|
| 66 |
-
|
| 67 |
-
<DropdownMenuItem>
|
| 68 |
-
<ChartSpline className="size-4 text-neutral-100" />
|
| 69 |
-
Usage Quota
|
| 70 |
-
</DropdownMenuItem>
|
| 71 |
-
</a>
|
| 72 |
</DropdownMenuGroup>
|
| 73 |
<DropdownMenuSeparator />
|
| 74 |
<DropdownMenuItem
|
|
|
|
| 1 |
import {
|
| 2 |
+
CirclePlus,
|
|
|
|
| 3 |
FolderCode,
|
| 4 |
Import,
|
| 5 |
LogOut,
|
|
|
|
| 62 |
View Projects
|
| 63 |
</DropdownMenuItem>
|
| 64 |
</Link>
|
| 65 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
</DropdownMenuGroup>
|
| 67 |
<DropdownMenuSeparator />
|
| 68 |
<DropdownMenuItem
|
hooks/useUser.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
| 1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
"use client";
|
| 3 |
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
| 4 |
-
|
| 5 |
import { useRouter } from "next/navigation";
|
| 6 |
|
| 7 |
import { User } from "@/types";
|
| 8 |
-
import
|
| 9 |
-
import { api } from "@/lib/api";
|
| 10 |
import { toast } from "sonner";
|
| 11 |
|
| 12 |
export const useUser = (initialData?: {
|
| 13 |
user: User | null;
|
| 14 |
errCode: number | null;
|
| 15 |
}) => {
|
| 16 |
-
const cookie_name = MY_TOKEN_KEY();
|
| 17 |
const client = useQueryClient();
|
| 18 |
const router = useRouter();
|
| 19 |
-
const [, setCookie, removeCookie] = useCookie(cookie_name);
|
| 20 |
-
const [currentRoute, setCurrentRoute] = useCookie("deepsite-currentRoute");
|
| 21 |
|
| 22 |
const { data: { user, errCode } = { user: null, errCode: null }, isLoading } =
|
| 23 |
useQuery({
|
| 24 |
queryKey: ["user.me"],
|
| 25 |
queryFn: async () => {
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
},
|
| 28 |
refetchOnWindowFocus: false,
|
| 29 |
refetchOnReconnect: false,
|
|
@@ -32,63 +32,11 @@ export const useUser = (initialData?: {
|
|
| 32 |
initialData: initialData
|
| 33 |
? { user: initialData?.user, errCode: initialData?.errCode }
|
| 34 |
: undefined,
|
| 35 |
-
enabled:
|
| 36 |
});
|
| 37 |
|
| 38 |
-
const { data: loadingAuth } = useQuery({
|
| 39 |
-
queryKey: ["loadingAuth"],
|
| 40 |
-
queryFn: async () => false,
|
| 41 |
-
refetchOnWindowFocus: false,
|
| 42 |
-
refetchOnReconnect: false,
|
| 43 |
-
refetchOnMount: false,
|
| 44 |
-
});
|
| 45 |
-
const setLoadingAuth = (value: boolean) => {
|
| 46 |
-
client.setQueryData(["setLoadingAuth"], value);
|
| 47 |
-
};
|
| 48 |
-
|
| 49 |
-
const openLoginWindow = async () => {
|
| 50 |
-
setCurrentRoute(window.location.pathname);
|
| 51 |
-
return router.push("/auth");
|
| 52 |
-
};
|
| 53 |
-
|
| 54 |
-
const loginFromCode = async (code: string) => {
|
| 55 |
-
setLoadingAuth(true);
|
| 56 |
-
if (loadingAuth) return;
|
| 57 |
-
await api
|
| 58 |
-
.post("/auth", { code })
|
| 59 |
-
.then(async (res: any) => {
|
| 60 |
-
if (res.data) {
|
| 61 |
-
setCookie(res.data.access_token, {
|
| 62 |
-
expires: res.data.expires_in
|
| 63 |
-
? new Date(Date.now() + res.data.expires_in * 1000)
|
| 64 |
-
: undefined,
|
| 65 |
-
sameSite: "none",
|
| 66 |
-
secure: true,
|
| 67 |
-
});
|
| 68 |
-
client.setQueryData(["user.me"], {
|
| 69 |
-
user: res.data.user,
|
| 70 |
-
errCode: null,
|
| 71 |
-
});
|
| 72 |
-
if (currentRoute) {
|
| 73 |
-
router.push(currentRoute);
|
| 74 |
-
setCurrentRoute("");
|
| 75 |
-
} else {
|
| 76 |
-
router.push("/projects");
|
| 77 |
-
}
|
| 78 |
-
toast.success("Login successful");
|
| 79 |
-
}
|
| 80 |
-
})
|
| 81 |
-
.catch((err: any) => {
|
| 82 |
-
toast.error(err?.data?.message ?? err.message ?? "An error occurred");
|
| 83 |
-
})
|
| 84 |
-
.finally(() => {
|
| 85 |
-
setLoadingAuth(false);
|
| 86 |
-
});
|
| 87 |
-
};
|
| 88 |
-
|
| 89 |
const logout = async () => {
|
| 90 |
-
|
| 91 |
-
router.push("/");
|
| 92 |
toast.success("Logout successful");
|
| 93 |
client.setQueryData(["user.me"], {
|
| 94 |
user: null,
|
|
@@ -100,9 +48,7 @@ export const useUser = (initialData?: {
|
|
| 100 |
return {
|
| 101 |
user,
|
| 102 |
errCode,
|
| 103 |
-
loading: isLoading
|
| 104 |
-
openLoginWindow,
|
| 105 |
-
loginFromCode,
|
| 106 |
logout,
|
| 107 |
};
|
| 108 |
-
};
|
|
|
|
| 1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
"use client";
|
| 3 |
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
| 4 |
+
|
| 5 |
import { useRouter } from "next/navigation";
|
| 6 |
|
| 7 |
import { User } from "@/types";
|
| 8 |
+
import { isAuthenticated } from "@/lib/auth";
|
|
|
|
| 9 |
import { toast } from "sonner";
|
| 10 |
|
| 11 |
export const useUser = (initialData?: {
|
| 12 |
user: User | null;
|
| 13 |
errCode: number | null;
|
| 14 |
}) => {
|
|
|
|
| 15 |
const client = useQueryClient();
|
| 16 |
const router = useRouter();
|
|
|
|
|
|
|
| 17 |
|
| 18 |
const { data: { user, errCode } = { user: null, errCode: null }, isLoading } =
|
| 19 |
useQuery({
|
| 20 |
queryKey: ["user.me"],
|
| 21 |
queryFn: async () => {
|
| 22 |
+
const authResult = await isAuthenticated();
|
| 23 |
+
if (authResult && "id" in authResult) {
|
| 24 |
+
return { user: authResult as User, errCode: null };
|
| 25 |
+
}
|
| 26 |
+
return { user: null, errCode: 401 };
|
| 27 |
},
|
| 28 |
refetchOnWindowFocus: false,
|
| 29 |
refetchOnReconnect: false,
|
|
|
|
| 32 |
initialData: initialData
|
| 33 |
? { user: initialData?.user, errCode: initialData?.errCode }
|
| 34 |
: undefined,
|
| 35 |
+
enabled: true, // Enable the query to fetch the mock user
|
| 36 |
});
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
const logout = async () => {
|
| 39 |
+
// Since authentication is removed, just reload the page
|
|
|
|
| 40 |
toast.success("Logout successful");
|
| 41 |
client.setQueryData(["user.me"], {
|
| 42 |
user: null,
|
|
|
|
| 48 |
return {
|
| 49 |
user,
|
| 50 |
errCode,
|
| 51 |
+
loading: isLoading,
|
|
|
|
|
|
|
| 52 |
logout,
|
| 53 |
};
|
| 54 |
+
};
|
lib/api.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import axios from "axios";
|
| 2 |
-
|
| 3 |
|
| 4 |
export const api = axios.create({
|
| 5 |
baseURL: `/api`,
|
|
@@ -15,21 +15,6 @@ export const apiServer = axios.create({
|
|
| 15 |
},
|
| 16 |
});
|
| 17 |
|
| 18 |
-
api.interceptors.request.use(
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
const cookie_name = MY_TOKEN_KEY();
|
| 22 |
-
const token = document.cookie
|
| 23 |
-
.split("; ")
|
| 24 |
-
.find((row) => row.startsWith(`${cookie_name}=`))
|
| 25 |
-
?.split("=")[1];
|
| 26 |
-
if (token) {
|
| 27 |
-
config.headers.Authorization = `Bearer ${token}`;
|
| 28 |
-
}
|
| 29 |
-
return config;
|
| 30 |
-
},
|
| 31 |
-
(error) => {
|
| 32 |
-
// Handle the error
|
| 33 |
-
return Promise.reject(error);
|
| 34 |
-
}
|
| 35 |
-
);
|
|
|
|
| 1 |
import axios from "axios";
|
| 2 |
+
|
| 3 |
|
| 4 |
export const api = axios.create({
|
| 5 |
baseURL: `/api`,
|
|
|
|
| 15 |
},
|
| 16 |
});
|
| 17 |
|
| 18 |
+
api.interceptors.request.use((config) => {
|
| 19 |
+
return config;
|
| 20 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lib/auth.ts
CHANGED
|
@@ -1,72 +1,22 @@
|
|
| 1 |
import { User } from "@/types";
|
| 2 |
import { NextResponse } from "next/server";
|
| 3 |
-
import { cookies, headers } from "next/headers";
|
| 4 |
-
import MY_TOKEN_KEY from "./get-cookie-name";
|
| 5 |
|
| 6 |
// UserResponse = type User & { token: string };
|
| 7 |
type UserResponse = User & { token: string };
|
| 8 |
|
| 9 |
-
export const isAuthenticated = async ():
|
| 10 |
-
|
| 11 |
-
const
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
:
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
{
|
| 20 |
-
ok: false,
|
| 21 |
-
message: "Wrong castle fam :(",
|
| 22 |
-
},
|
| 23 |
-
{
|
| 24 |
-
status: 401,
|
| 25 |
-
headers: {
|
| 26 |
-
"Content-Type": "application/json",
|
| 27 |
-
},
|
| 28 |
-
}
|
| 29 |
-
);
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
const user = await fetch("https://huggingface.co/api/whoami-v2", {
|
| 33 |
-
headers: {
|
| 34 |
-
Authorization: token,
|
| 35 |
-
},
|
| 36 |
-
method: "GET",
|
| 37 |
-
})
|
| 38 |
-
.then((res) => res.json())
|
| 39 |
-
.catch(() => {
|
| 40 |
-
return NextResponse.json(
|
| 41 |
-
{
|
| 42 |
-
ok: false,
|
| 43 |
-
message: "Invalid token",
|
| 44 |
-
},
|
| 45 |
-
{
|
| 46 |
-
status: 401,
|
| 47 |
-
headers: {
|
| 48 |
-
"Content-Type": "application/json",
|
| 49 |
-
},
|
| 50 |
-
}
|
| 51 |
-
);
|
| 52 |
-
});
|
| 53 |
-
if (!user || !user.id) {
|
| 54 |
-
return NextResponse.json(
|
| 55 |
-
{
|
| 56 |
-
ok: false,
|
| 57 |
-
message: "Invalid token",
|
| 58 |
-
},
|
| 59 |
-
{
|
| 60 |
-
status: 401,
|
| 61 |
-
headers: {
|
| 62 |
-
"Content-Type": "application/json",
|
| 63 |
-
},
|
| 64 |
-
}
|
| 65 |
-
);
|
| 66 |
-
}
|
| 67 |
|
| 68 |
return {
|
| 69 |
...user,
|
| 70 |
-
token: token
|
| 71 |
};
|
| 72 |
-
};
|
|
|
|
| 1 |
import { User } from "@/types";
|
| 2 |
import { NextResponse } from "next/server";
|
|
|
|
|
|
|
| 3 |
|
| 4 |
// UserResponse = type User & { token: string };
|
| 5 |
type UserResponse = User & { token: string };
|
| 6 |
|
| 7 |
+
export const isAuthenticated = async (): Promise<UserResponse | NextResponse<unknown> | undefined> => {
|
| 8 |
+
// Mock user for now, as Hugging Face authentication is removed
|
| 9 |
+
const user: User = {
|
| 10 |
+
id: "mock-user-id",
|
| 11 |
+
name: "Mock User",
|
| 12 |
+
fullname: "Mock User",
|
| 13 |
+
avatarUrl: "https://www.gravatar.com/avatar/?d=mp",
|
| 14 |
+
isPro: false,
|
| 15 |
+
isLocalUse: true,
|
| 16 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
return {
|
| 19 |
...user,
|
| 20 |
+
token: "mock-token",
|
| 21 |
};
|
| 22 |
+
};
|
lib/consts.ts
CHANGED
|
@@ -5,16 +5,21 @@ export const defaultHTML = `<!DOCTYPE html>
|
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<meta charset="utf-8">
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
</head>
|
| 9 |
-
<body class="flex justify-center items-center h-screen overflow-hidden
|
| 10 |
-
<div class="w-full">
|
| 11 |
-
<span class="text-xs rounded-full mb-
|
| 12 |
-
<h1 class="text-4xl lg:text-
|
| 13 |
-
<span class="text-2xl lg:text-4xl text-
|
| 14 |
Ask me anything.
|
| 15 |
</h1>
|
| 16 |
</div>
|
| 17 |
-
<img src="https://enzostvs-deepsite.hf.space/arrow.svg" class="absolute bottom-8 left-0 w-[100px] transform rotate-[30deg]" />
|
| 18 |
<script></script>
|
| 19 |
</body>
|
| 20 |
</html>
|
|
|
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<meta charset="utf-8">
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<style>
|
| 9 |
+
body { background: #181a20; }
|
| 10 |
+
.glass { background: #23242a; border-radius: 14px; }
|
| 11 |
+
.vibe-badge { border: 1px solid #ffe06650; background: #23242a; color: #ffe066; font-weight: 600; }
|
| 12 |
+
.neon { text-shadow: 0 0 1.5px #ffe066; }
|
| 13 |
+
</style>
|
| 14 |
</head>
|
| 15 |
+
<body class="flex justify-center items-center h-screen overflow-hidden font-sans text-center px-6 text-white">
|
| 16 |
+
<div class="w-full glass p-8">
|
| 17 |
+
<span class="text-xs rounded-full mb-4 inline-block px-3 py-1 vibe-badge">🔥 New version dropped!</span>
|
| 18 |
+
<h1 class="text-4xl lg:text-7xl font-black font-sans tracking-tight neon">
|
| 19 |
+
<span class="text-2xl lg:text-4xl block font-bold text-neutral-400 mb-1">I'm ready to work,</span>
|
| 20 |
Ask me anything.
|
| 21 |
</h1>
|
| 22 |
</div>
|
|
|
|
| 23 |
<script></script>
|
| 24 |
</body>
|
| 25 |
</html>
|
lib/get-cookie-name.ts
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
export default function MY_TOKEN_KEY() {
|
| 2 |
-
return "deepsite-auth-token";
|
| 3 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
lib/mongodb.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
import mongoose from "mongoose";
|
| 2 |
-
|
| 3 |
-
const MONGODB_URI = process.env.MONGODB_URI;
|
| 4 |
-
// @ts-expect-error iknown issue with mongoose types
|
| 5 |
-
let cached = global.mongoose;
|
| 6 |
-
|
| 7 |
-
if (!cached) {
|
| 8 |
-
// @ts-expect-error iknown issue with mongoose types
|
| 9 |
-
cached = global.mongoose = { conn: null, promise: null };
|
| 10 |
-
}
|
| 11 |
-
|
| 12 |
-
async function dbConnect() {
|
| 13 |
-
if (cached.conn) {
|
| 14 |
-
return cached.conn;
|
| 15 |
-
}
|
| 16 |
-
|
| 17 |
-
if (!cached.promise) {
|
| 18 |
-
cached.promise = mongoose
|
| 19 |
-
.connect(MONGODB_URI as string)
|
| 20 |
-
.then((mongoose) => {
|
| 21 |
-
return mongoose;
|
| 22 |
-
});
|
| 23 |
-
}
|
| 24 |
-
cached.conn = await cached.promise;
|
| 25 |
-
return cached.conn;
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
export default dbConnect;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lib/providers.ts
CHANGED
|
@@ -1,56 +1,17 @@
|
|
| 1 |
export const PROVIDERS = {
|
| 2 |
-
"
|
| 3 |
-
name: "
|
| 4 |
-
max_tokens: 131_000,
|
| 5 |
-
id: "fireworks-ai",
|
| 6 |
-
},
|
| 7 |
-
nebius: {
|
| 8 |
-
name: "Nebius AI Studio",
|
| 9 |
-
max_tokens: 131_000,
|
| 10 |
-
id: "nebius",
|
| 11 |
-
},
|
| 12 |
-
sambanova: {
|
| 13 |
-
name: "SambaNova",
|
| 14 |
-
max_tokens: 32_000,
|
| 15 |
-
id: "sambanova",
|
| 16 |
-
},
|
| 17 |
-
novita: {
|
| 18 |
-
name: "NovitaAI",
|
| 19 |
-
max_tokens: 16_000,
|
| 20 |
-
id: "novita",
|
| 21 |
-
},
|
| 22 |
-
hyperbolic: {
|
| 23 |
-
name: "Hyperbolic",
|
| 24 |
-
max_tokens: 131_000,
|
| 25 |
-
id: "hyperbolic",
|
| 26 |
-
},
|
| 27 |
-
together: {
|
| 28 |
-
name: "Together AI",
|
| 29 |
max_tokens: 128_000,
|
| 30 |
-
id: "
|
| 31 |
},
|
| 32 |
};
|
| 33 |
|
| 34 |
export const MODELS = [
|
| 35 |
{
|
| 36 |
-
value: "
|
| 37 |
-
label: "
|
| 38 |
-
providers: ["
|
| 39 |
-
autoProvider: "
|
| 40 |
-
|
| 41 |
-
{
|
| 42 |
-
value: "deepseek-ai/DeepSeek-R1-0528",
|
| 43 |
-
label: "DeepSeek R1 0528",
|
| 44 |
-
providers: [
|
| 45 |
-
"fireworks-ai",
|
| 46 |
-
"novita",
|
| 47 |
-
"hyperbolic",
|
| 48 |
-
"nebius",
|
| 49 |
-
"together",
|
| 50 |
-
"sambanova",
|
| 51 |
-
],
|
| 52 |
-
autoProvider: "novita",
|
| 53 |
-
isNew: true,
|
| 54 |
-
isThinker: true,
|
| 55 |
},
|
| 56 |
];
|
|
|
|
| 1 |
export const PROVIDERS = {
|
| 2 |
+
"openai-compatible": {
|
| 3 |
+
name: "OpenAI Compatible",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
max_tokens: 128_000,
|
| 5 |
+
id: "openai-compatible",
|
| 6 |
},
|
| 7 |
};
|
| 8 |
|
| 9 |
export const MODELS = [
|
| 10 |
{
|
| 11 |
+
value: "gpt-4o-mini", // Default model, can be overridden by user
|
| 12 |
+
label: "GPT-4o Mini (Default)",
|
| 13 |
+
providers: ["openai-compatible"],
|
| 14 |
+
autoProvider: "openai-compatible",
|
| 15 |
+
isThinker: false,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
},
|
| 17 |
];
|
package-lock.json
CHANGED
|
@@ -8,8 +8,6 @@
|
|
| 8 |
"name": "deepsite-v2",
|
| 9 |
"version": "0.1.0",
|
| 10 |
"dependencies": {
|
| 11 |
-
"@huggingface/hub": "^2.2.0",
|
| 12 |
-
"@huggingface/inference": "^4.0.3",
|
| 13 |
"@monaco-editor/react": "^4.7.0-rc.0",
|
| 14 |
"@radix-ui/react-avatar": "^1.1.10",
|
| 15 |
"@radix-ui/react-checkbox": "^1.3.2",
|
|
@@ -37,6 +35,7 @@
|
|
| 37 |
"mongoose": "^8.15.1",
|
| 38 |
"next": "15.3.3",
|
| 39 |
"next-themes": "^0.4.6",
|
|
|
|
| 40 |
"react": "^19.0.0",
|
| 41 |
"react-dom": "^19.0.0",
|
| 42 |
"react-icons": "^5.5.0",
|
|
@@ -299,49 +298,6 @@
|
|
| 299 |
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
| 300 |
"license": "MIT"
|
| 301 |
},
|
| 302 |
-
"node_modules/@huggingface/hub": {
|
| 303 |
-
"version": "2.2.0",
|
| 304 |
-
"resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-2.2.0.tgz",
|
| 305 |
-
"integrity": "sha512-G+VS1eMp80KovIHBlsiEigS6I6qmI4j+VQ1UZ8CaXT+pw2A7tj6e/crfxFdKNE2uOK5oQkRFiCBJykMwrWQ8OA==",
|
| 306 |
-
"license": "MIT",
|
| 307 |
-
"dependencies": {
|
| 308 |
-
"@huggingface/tasks": "^0.19.11"
|
| 309 |
-
},
|
| 310 |
-
"bin": {
|
| 311 |
-
"hfjs": "dist/cli.js"
|
| 312 |
-
},
|
| 313 |
-
"engines": {
|
| 314 |
-
"node": ">=18"
|
| 315 |
-
}
|
| 316 |
-
},
|
| 317 |
-
"node_modules/@huggingface/inference": {
|
| 318 |
-
"version": "4.0.3",
|
| 319 |
-
"resolved": "https://registry.npmjs.org/@huggingface/inference/-/inference-4.0.3.tgz",
|
| 320 |
-
"integrity": "sha512-TE1IvY+4DQJhjpJsFdBqDHu0Jg+p/+9G5UmW3hmLBcUJzy7P4J0yZky+VFniSVxI8dFA4eKKcW8/uYD+yZM9NA==",
|
| 321 |
-
"license": "MIT",
|
| 322 |
-
"dependencies": {
|
| 323 |
-
"@huggingface/jinja": "^0.5.0",
|
| 324 |
-
"@huggingface/tasks": "^0.19.12"
|
| 325 |
-
},
|
| 326 |
-
"engines": {
|
| 327 |
-
"node": ">=18"
|
| 328 |
-
}
|
| 329 |
-
},
|
| 330 |
-
"node_modules/@huggingface/jinja": {
|
| 331 |
-
"version": "0.5.0",
|
| 332 |
-
"resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.5.0.tgz",
|
| 333 |
-
"integrity": "sha512-Ptc03/jGRiYRoi0bUYKZ14MkDslsBRT24oxmsvUlfYrvQMldrxCevhPnT+hfX8awKTT8/f/0ZBBWldoeAcMHdQ==",
|
| 334 |
-
"license": "MIT",
|
| 335 |
-
"engines": {
|
| 336 |
-
"node": ">=18"
|
| 337 |
-
}
|
| 338 |
-
},
|
| 339 |
-
"node_modules/@huggingface/tasks": {
|
| 340 |
-
"version": "0.19.12",
|
| 341 |
-
"resolved": "https://registry.npmjs.org/@huggingface/tasks/-/tasks-0.19.12.tgz",
|
| 342 |
-
"integrity": "sha512-rg5+phoJg7SCqbUx9O9I+bfZkZBf6rqf2XGuN7M+uuwBSO4zO3V9s/Iqein1QSEhktR4g5OVBUZimodkX4ISkg==",
|
| 343 |
-
"license": "MIT"
|
| 344 |
-
},
|
| 345 |
"node_modules/@humanfs/core": {
|
| 346 |
"version": "0.19.1",
|
| 347 |
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
|
@@ -6885,6 +6841,26 @@
|
|
| 6885 |
"url": "https://github.com/sponsors/ljharb"
|
| 6886 |
}
|
| 6887 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6888 |
"node_modules/optionator": {
|
| 6889 |
"version": "0.9.4",
|
| 6890 |
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
|
|
|
| 8 |
"name": "deepsite-v2",
|
| 9 |
"version": "0.1.0",
|
| 10 |
"dependencies": {
|
|
|
|
|
|
|
| 11 |
"@monaco-editor/react": "^4.7.0-rc.0",
|
| 12 |
"@radix-ui/react-avatar": "^1.1.10",
|
| 13 |
"@radix-ui/react-checkbox": "^1.3.2",
|
|
|
|
| 35 |
"mongoose": "^8.15.1",
|
| 36 |
"next": "15.3.3",
|
| 37 |
"next-themes": "^0.4.6",
|
| 38 |
+
"openai": "^5.9.0",
|
| 39 |
"react": "^19.0.0",
|
| 40 |
"react-dom": "^19.0.0",
|
| 41 |
"react-icons": "^5.5.0",
|
|
|
|
| 298 |
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
| 299 |
"license": "MIT"
|
| 300 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
"node_modules/@humanfs/core": {
|
| 302 |
"version": "0.19.1",
|
| 303 |
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
|
|
|
| 6841 |
"url": "https://github.com/sponsors/ljharb"
|
| 6842 |
}
|
| 6843 |
},
|
| 6844 |
+
"node_modules/openai": {
|
| 6845 |
+
"version": "5.9.0",
|
| 6846 |
+
"resolved": "https://registry.npmjs.org/openai/-/openai-5.9.0.tgz",
|
| 6847 |
+
"integrity": "sha512-cmLC0pfqLLhBGxE4aZPyRPjydgYCncppV2ClQkKmW79hNjCvmzkfhz8rN5/YVDmjVQlFV+UsF1JIuNjNgeagyQ==",
|
| 6848 |
+
"bin": {
|
| 6849 |
+
"openai": "bin/cli"
|
| 6850 |
+
},
|
| 6851 |
+
"peerDependencies": {
|
| 6852 |
+
"ws": "^8.18.0",
|
| 6853 |
+
"zod": "^3.23.8"
|
| 6854 |
+
},
|
| 6855 |
+
"peerDependenciesMeta": {
|
| 6856 |
+
"ws": {
|
| 6857 |
+
"optional": true
|
| 6858 |
+
},
|
| 6859 |
+
"zod": {
|
| 6860 |
+
"optional": true
|
| 6861 |
+
}
|
| 6862 |
+
}
|
| 6863 |
+
},
|
| 6864 |
"node_modules/optionator": {
|
| 6865 |
"version": "0.9.4",
|
| 6866 |
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
package.json
CHANGED
|
@@ -9,8 +9,6 @@
|
|
| 9 |
"lint": "next lint"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
| 12 |
-
"@huggingface/hub": "^2.2.0",
|
| 13 |
-
"@huggingface/inference": "^4.0.3",
|
| 14 |
"@monaco-editor/react": "^4.7.0-rc.0",
|
| 15 |
"@radix-ui/react-avatar": "^1.1.10",
|
| 16 |
"@radix-ui/react-checkbox": "^1.3.2",
|
|
@@ -38,6 +36,7 @@
|
|
| 38 |
"mongoose": "^8.15.1",
|
| 39 |
"next": "15.3.3",
|
| 40 |
"next-themes": "^0.4.6",
|
|
|
|
| 41 |
"react": "^19.0.0",
|
| 42 |
"react-dom": "^19.0.0",
|
| 43 |
"react-icons": "^5.5.0",
|
|
|
|
| 9 |
"lint": "next lint"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
|
|
|
|
|
|
| 12 |
"@monaco-editor/react": "^4.7.0-rc.0",
|
| 13 |
"@radix-ui/react-avatar": "^1.1.10",
|
| 14 |
"@radix-ui/react-checkbox": "^1.3.2",
|
|
|
|
| 36 |
"mongoose": "^8.15.1",
|
| 37 |
"next": "15.3.3",
|
| 38 |
"next-themes": "^0.4.6",
|
| 39 |
+
"openai": "^5.9.0",
|
| 40 |
"react": "^19.0.0",
|
| 41 |
"react-dom": "^19.0.0",
|
| 42 |
"react-icons": "^5.5.0",
|