Spaces:
Running
Running
Merge branch #enzostvs/deepsite' into 'nomadicsynth/deepsite'
Browse files- .env.example +7 -1
- README.md +5 -1
- middlewares/checkUser.js +3 -0
- server.js +33 -18
- src/components/App.tsx +11 -1
- src/components/ask-ai/ask-ai.tsx +3 -0
- src/components/deploy-button/deploy-button.tsx +34 -24
- src/components/settings/settings.tsx +10 -0
- utils/types.ts +1 -0
.env.example
CHANGED
@@ -2,4 +2,10 @@ OAUTH_CLIENT_ID=
|
|
2 |
OAUTH_CLIENT_SECRET=
|
3 |
APP_PORT=5173
|
4 |
REDIRECT_URI=http://localhost:5173/auth/login
|
5 |
-
DEFAULT_HF_TOKEN=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
OAUTH_CLIENT_SECRET=
|
3 |
APP_PORT=5173
|
4 |
REDIRECT_URI=http://localhost:5173/auth/login
|
5 |
+
DEFAULT_HF_TOKEN=
|
6 |
+
|
7 |
+
# Optional
|
8 |
+
# By setting this variable, you will bypass the login page + the free requests
|
9 |
+
# and will use the token directly.
|
10 |
+
# This is useful for testing purposes or local use.
|
11 |
+
HF_TOKEN=
|
README.md
CHANGED
@@ -18,4 +18,8 @@ models:
|
|
18 |
- deepseek-ai/DeepSeek-V3-0324
|
19 |
---
|
20 |
|
21 |
-
|
|
|
|
|
|
|
|
|
|
18 |
- deepseek-ai/DeepSeek-V3-0324
|
19 |
---
|
20 |
|
21 |
+
# DeepSite 🐳
|
22 |
+
DeepSite is a coding platform powered by DeepSeek AI, designed to make coding smarter and more efficient. Tailored for developers, data scientists, and AI engineers, it integrates generative AI into your coding projects to enhance creativity and productivity.
|
23 |
+
|
24 |
+
## How to use it locally
|
25 |
+
Follow [this discussion](https://huggingface.co/spaces/enzostvs/deepsite/discussions/74)
|
middlewares/checkUser.js
CHANGED
@@ -1,4 +1,7 @@
|
|
1 |
export default async function checkUser(req, res, next) {
|
|
|
|
|
|
|
2 |
const { hf_token } = req.cookies;
|
3 |
if (!hf_token) {
|
4 |
return res.status(401).send({
|
|
|
1 |
export default async function checkUser(req, res, next) {
|
2 |
+
if (process.env.HF_TOKEN && process.env.HF_TOKEN !== "") {
|
3 |
+
return next();
|
4 |
+
}
|
5 |
const { hf_token } = req.cookies;
|
6 |
if (!hf_token) {
|
7 |
return res.status(401).send({
|
server.js
CHANGED
@@ -42,11 +42,6 @@ const getPTag = (repoId) => {
|
|
42 |
};
|
43 |
|
44 |
app.get("/api/login", (_req, res) => {
|
45 |
-
// res.redirect(
|
46 |
-
// 302,
|
47 |
-
// `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`
|
48 |
-
// );
|
49 |
-
// redirect in new tab
|
50 |
const redirectUrl = `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`;
|
51 |
res.status(200).send({
|
52 |
ok: true,
|
@@ -101,7 +96,15 @@ app.get("/auth/logout", (req, res) => {
|
|
101 |
});
|
102 |
|
103 |
app.get("/api/@me", checkUser, async (req, res) => {
|
104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
try {
|
106 |
const request_user = await fetch("https://huggingface.co/oauth/userinfo", {
|
107 |
headers: {
|
@@ -125,7 +128,7 @@ app.get("/api/@me", checkUser, async (req, res) => {
|
|
125 |
});
|
126 |
|
127 |
app.post("/api/deploy", checkUser, async (req, res) => {
|
128 |
-
const { html, title, path } = req.body;
|
129 |
if (!html || (!path && !title)) {
|
130 |
return res.status(400).send({
|
131 |
ok: false,
|
@@ -133,7 +136,11 @@ app.post("/api/deploy", checkUser, async (req, res) => {
|
|
133 |
});
|
134 |
}
|
135 |
|
136 |
-
|
|
|
|
|
|
|
|
|
137 |
try {
|
138 |
const repo = {
|
139 |
type: "space",
|
@@ -180,7 +187,12 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
|
|
180 |
const file = new Blob([newHtml], { type: "text/html" });
|
181 |
file.name = "index.html"; // Add name property to the Blob
|
182 |
|
183 |
-
|
|
|
|
|
|
|
|
|
|
|
184 |
if (readme) {
|
185 |
const readmeFile = new Blob([readme], { type: "text/markdown" });
|
186 |
readmeFile.name = "README.md"; // Add name property to the Blob
|
@@ -209,8 +221,13 @@ app.post("/api/ask-ai", async (req, res) => {
|
|
209 |
});
|
210 |
}
|
211 |
|
212 |
-
|
213 |
let token = hf_token;
|
|
|
|
|
|
|
|
|
|
|
214 |
const ip =
|
215 |
req.headers["x-forwarded-for"]?.split(",")[0].trim() ||
|
216 |
req.headers["x-real-ip"] ||
|
@@ -218,7 +235,7 @@ app.post("/api/ask-ai", async (req, res) => {
|
|
218 |
req.ip ||
|
219 |
"0.0.0.0";
|
220 |
|
221 |
-
if (!
|
222 |
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
223 |
if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
224 |
return res.status(429).send({
|
@@ -244,12 +261,6 @@ app.post("/api/ask-ai", async (req, res) => {
|
|
244 |
if (html) TOKENS_USED += html.length;
|
245 |
|
246 |
const DEFAULT_PROVIDER = PROVIDERS.novita;
|
247 |
-
// const selectedProvider =
|
248 |
-
// provider === "auto"
|
249 |
-
// ? TOKENS_USED < PROVIDERS.sambanova.max_tokens
|
250 |
-
// ? PROVIDERS.sambanova
|
251 |
-
// : DEFAULT_PROVIDER
|
252 |
-
// : PROVIDERS[provider] ?? DEFAULT_PROVIDER;
|
253 |
const selectedProvider =
|
254 |
provider === "auto"
|
255 |
? DEFAULT_PROVIDER
|
@@ -355,7 +366,11 @@ app.get("/api/remix/:username/:repo", async (req, res) => {
|
|
355 |
const { username, repo } = req.params;
|
356 |
const { hf_token } = req.cookies;
|
357 |
|
358 |
-
|
|
|
|
|
|
|
|
|
359 |
|
360 |
const repoId = `${username}/${repo}`;
|
361 |
|
|
|
42 |
};
|
43 |
|
44 |
app.get("/api/login", (_req, res) => {
|
|
|
|
|
|
|
|
|
|
|
45 |
const redirectUrl = `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`;
|
46 |
res.status(200).send({
|
47 |
ok: true,
|
|
|
96 |
});
|
97 |
|
98 |
app.get("/api/@me", checkUser, async (req, res) => {
|
99 |
+
let { hf_token } = req.cookies;
|
100 |
+
|
101 |
+
if (process.env.HF_TOKEN && process.env.HF_TOKEN !== "") {
|
102 |
+
return res.send({
|
103 |
+
preferred_username: "local-use",
|
104 |
+
isLocalUse: true,
|
105 |
+
});
|
106 |
+
}
|
107 |
+
|
108 |
try {
|
109 |
const request_user = await fetch("https://huggingface.co/oauth/userinfo", {
|
110 |
headers: {
|
|
|
128 |
});
|
129 |
|
130 |
app.post("/api/deploy", checkUser, async (req, res) => {
|
131 |
+
const { html, title, path, prompts } = req.body;
|
132 |
if (!html || (!path && !title)) {
|
133 |
return res.status(400).send({
|
134 |
ok: false,
|
|
|
136 |
});
|
137 |
}
|
138 |
|
139 |
+
let { hf_token } = req.cookies;
|
140 |
+
if (process.env.HF_TOKEN && process.env.HF_TOKEN !== "") {
|
141 |
+
hf_token = process.env.HF_TOKEN;
|
142 |
+
}
|
143 |
+
|
144 |
try {
|
145 |
const repo = {
|
146 |
type: "space",
|
|
|
187 |
const file = new Blob([newHtml], { type: "text/html" });
|
188 |
file.name = "index.html"; // Add name property to the Blob
|
189 |
|
190 |
+
// create prompt.txt file with all the prompts used, split by new line
|
191 |
+
const newPrompts = ``.concat(prompts.map((prompt) => prompt).join("\n"));
|
192 |
+
const promptFile = new Blob([newPrompts], { type: "text/plain" });
|
193 |
+
promptFile.name = "prompts.txt"; // Add name property to the Blob
|
194 |
+
|
195 |
+
const files = [file, promptFile];
|
196 |
if (readme) {
|
197 |
const readmeFile = new Blob([readme], { type: "text/markdown" });
|
198 |
readmeFile.name = "README.md"; // Add name property to the Blob
|
|
|
221 |
});
|
222 |
}
|
223 |
|
224 |
+
let { hf_token } = req.cookies;
|
225 |
let token = hf_token;
|
226 |
+
|
227 |
+
if (process.env.HF_TOKEN && process.env.HF_TOKEN !== "") {
|
228 |
+
token = process.env.HF_TOKEN;
|
229 |
+
}
|
230 |
+
|
231 |
const ip =
|
232 |
req.headers["x-forwarded-for"]?.split(",")[0].trim() ||
|
233 |
req.headers["x-real-ip"] ||
|
|
|
235 |
req.ip ||
|
236 |
"0.0.0.0";
|
237 |
|
238 |
+
if (!token) {
|
239 |
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
240 |
if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
241 |
return res.status(429).send({
|
|
|
261 |
if (html) TOKENS_USED += html.length;
|
262 |
|
263 |
const DEFAULT_PROVIDER = PROVIDERS.novita;
|
|
|
|
|
|
|
|
|
|
|
|
|
264 |
const selectedProvider =
|
265 |
provider === "auto"
|
266 |
? DEFAULT_PROVIDER
|
|
|
366 |
const { username, repo } = req.params;
|
367 |
const { hf_token } = req.cookies;
|
368 |
|
369 |
+
let token = hf_token || process.env.DEFAULT_HF_TOKEN;
|
370 |
+
|
371 |
+
if (process.env.HF_TOKEN && process.env.HF_TOKEN !== "") {
|
372 |
+
token = process.env.HF_TOKEN;
|
373 |
+
}
|
374 |
|
375 |
const repoId = `${username}/${repo}`;
|
376 |
|
src/components/App.tsx
CHANGED
@@ -36,6 +36,7 @@ function App() {
|
|
36 |
const [currentView, setCurrentView] = useState<"editor" | "preview">(
|
37 |
"editor"
|
38 |
);
|
|
|
39 |
|
40 |
const fetchMe = async () => {
|
41 |
const res = await fetch("/api/@me");
|
@@ -182,7 +183,13 @@ function App() {
|
|
182 |
}
|
183 |
}}
|
184 |
>
|
185 |
-
<DeployButton
|
|
|
|
|
|
|
|
|
|
|
|
|
186 |
</Header>
|
187 |
<main className="max-lg:flex-col flex w-full">
|
188 |
<div
|
@@ -233,6 +240,9 @@ function App() {
|
|
233 |
isAiWorking={isAiWorking}
|
234 |
setisAiWorking={setisAiWorking}
|
235 |
setView={setCurrentView}
|
|
|
|
|
|
|
236 |
onScrollToBottom={() => {
|
237 |
editorRef.current?.revealLine(
|
238 |
editorRef.current?.getModel()?.getLineCount() ?? 0
|
|
|
36 |
const [currentView, setCurrentView] = useState<"editor" | "preview">(
|
37 |
"editor"
|
38 |
);
|
39 |
+
const [prompts, setPrompts] = useState<string[]>([]);
|
40 |
|
41 |
const fetchMe = async () => {
|
42 |
const res = await fetch("/api/@me");
|
|
|
183 |
}
|
184 |
}}
|
185 |
>
|
186 |
+
<DeployButton
|
187 |
+
html={html}
|
188 |
+
error={error}
|
189 |
+
auth={auth}
|
190 |
+
setHtml={setHtml}
|
191 |
+
prompts={prompts}
|
192 |
+
/>
|
193 |
</Header>
|
194 |
<main className="max-lg:flex-col flex w-full">
|
195 |
<div
|
|
|
240 |
isAiWorking={isAiWorking}
|
241 |
setisAiWorking={setisAiWorking}
|
242 |
setView={setCurrentView}
|
243 |
+
onNewPrompt={(prompt) => {
|
244 |
+
setPrompts((prev) => [...prev, prompt]);
|
245 |
+
}}
|
246 |
onScrollToBottom={() => {
|
247 |
editorRef.current?.revealLine(
|
248 |
editorRef.current?.getModel()?.getLineCount() ?? 0
|
src/components/ask-ai/ask-ai.tsx
CHANGED
@@ -21,11 +21,13 @@ function AskAI({
|
|
21 |
isAiWorking,
|
22 |
setisAiWorking,
|
23 |
setView,
|
|
|
24 |
}: {
|
25 |
html: string;
|
26 |
setHtml: (html: string) => void;
|
27 |
onScrollToBottom: () => void;
|
28 |
isAiWorking: boolean;
|
|
|
29 |
setView: React.Dispatch<React.SetStateAction<"editor" | "preview">>;
|
30 |
setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
|
31 |
}) {
|
@@ -49,6 +51,7 @@ function AskAI({
|
|
49 |
let contentResponse = "";
|
50 |
let lastRenderTime = 0;
|
51 |
try {
|
|
|
52 |
const request = await fetch("/api/ask-ai", {
|
53 |
method: "POST",
|
54 |
body: JSON.stringify({
|
|
|
21 |
isAiWorking,
|
22 |
setisAiWorking,
|
23 |
setView,
|
24 |
+
onNewPrompt,
|
25 |
}: {
|
26 |
html: string;
|
27 |
setHtml: (html: string) => void;
|
28 |
onScrollToBottom: () => void;
|
29 |
isAiWorking: boolean;
|
30 |
+
onNewPrompt: (prompt: string) => void;
|
31 |
setView: React.Dispatch<React.SetStateAction<"editor" | "preview">>;
|
32 |
setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
|
33 |
}) {
|
|
|
51 |
let contentResponse = "";
|
52 |
let lastRenderTime = 0;
|
53 |
try {
|
54 |
+
onNewPrompt(prompt);
|
55 |
const request = await fetch("/api/ask-ai", {
|
56 |
method: "POST",
|
57 |
body: JSON.stringify({
|
src/components/deploy-button/deploy-button.tsx
CHANGED
@@ -29,11 +29,13 @@ function DeployButton({
|
|
29 |
error = false,
|
30 |
auth,
|
31 |
setHtml,
|
|
|
32 |
}: {
|
33 |
html: string;
|
34 |
error: boolean;
|
35 |
auth?: Auth;
|
36 |
setHtml: (html: string) => void;
|
|
|
37 |
}) {
|
38 |
const [open, setOpen] = useState(false);
|
39 |
const [loading, setLoading] = useState(false);
|
@@ -53,6 +55,7 @@ function DeployButton({
|
|
53 |
title: config.title,
|
54 |
path,
|
55 |
html,
|
|
|
56 |
}),
|
57 |
headers: {
|
58 |
"Content-Type": "application/json",
|
@@ -84,31 +87,38 @@ function DeployButton({
|
|
84 |
<div className="flex items-center justify-end gap-5">
|
85 |
<LoadButton auth={auth} setHtml={setHtml} setPath={setPath} />
|
86 |
<div className="relative flex items-center justify-end">
|
87 |
-
{auth &&
|
88 |
-
|
89 |
-
|
90 |
-
className="mr-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
target="_blank"
|
105 |
-
className="underline hover:text-white"
|
106 |
>
|
107 |
-
|
108 |
-
</
|
109 |
-
|
110 |
-
|
111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
<button
|
113 |
className={classNames(
|
114 |
"relative cursor-pointer flex-none flex items-center justify-center rounded-md text-xs lg:text-sm font-semibold leading-5 lg:leading-6 py-1.5 px-5 hover:bg-pink-400 text-white shadow-sm dark:shadow-highlight/20",
|
|
|
29 |
error = false,
|
30 |
auth,
|
31 |
setHtml,
|
32 |
+
prompts,
|
33 |
}: {
|
34 |
html: string;
|
35 |
error: boolean;
|
36 |
auth?: Auth;
|
37 |
setHtml: (html: string) => void;
|
38 |
+
prompts: string[];
|
39 |
}) {
|
40 |
const [open, setOpen] = useState(false);
|
41 |
const [loading, setLoading] = useState(false);
|
|
|
55 |
title: config.title,
|
56 |
path,
|
57 |
html,
|
58 |
+
prompts,
|
59 |
}),
|
60 |
headers: {
|
61 |
"Content-Type": "application/json",
|
|
|
87 |
<div className="flex items-center justify-end gap-5">
|
88 |
<LoadButton auth={auth} setHtml={setHtml} setPath={setPath} />
|
89 |
<div className="relative flex items-center justify-end">
|
90 |
+
{auth &&
|
91 |
+
(auth.isLocalUse ? (
|
92 |
+
<>
|
93 |
+
<div className="bg-amber-500/10 border border-amber-10 text-amber-500 font-semibold leading-5 lg:leading-6 py-1 px-5 text-xs lg:text-sm rounded-md mr-4 select-none">
|
94 |
+
Local Usage
|
95 |
+
</div>
|
96 |
+
</>
|
97 |
+
) : (
|
98 |
+
<>
|
99 |
+
<button
|
100 |
+
className="mr-2 cursor-pointer"
|
101 |
+
onClick={() => {
|
102 |
+
if (confirm("Are you sure you want to log out?")) {
|
103 |
+
// go to /auth/logout page
|
104 |
+
window.location.href = "/auth/logout";
|
105 |
+
}
|
106 |
+
}}
|
|
|
|
|
107 |
>
|
108 |
+
<FaPowerOff className="text-lg text-red-500" />
|
109 |
+
</button>
|
110 |
+
<p className="mr-3 text-xs lg:text-sm text-gray-300">
|
111 |
+
<span className="max-lg:hidden">Connected as </span>
|
112 |
+
<a
|
113 |
+
href={`https://huggingface.co/${auth.preferred_username}`}
|
114 |
+
target="_blank"
|
115 |
+
className="underline hover:text-white"
|
116 |
+
>
|
117 |
+
{auth.preferred_username}
|
118 |
+
</a>
|
119 |
+
</p>
|
120 |
+
</>
|
121 |
+
))}
|
122 |
<button
|
123 |
className={classNames(
|
124 |
"relative cursor-pointer flex-none flex items-center justify-center rounded-md text-xs lg:text-sm font-semibold leading-5 lg:leading-6 py-1.5 px-5 hover:bg-pink-400 text-white shadow-sm dark:shadow-highlight/20",
|
src/components/settings/settings.tsx
CHANGED
@@ -54,6 +54,16 @@ function Settings({
|
|
54 |
<main className="px-4 pt-3 pb-4 space-y-4">
|
55 |
{/* toggle using tailwind css */}
|
56 |
<div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
<div className="flex items-center justify-between">
|
58 |
<p className="text-gray-800 text-sm font-medium flex items-center justify-between">
|
59 |
Use auto-provider
|
|
|
54 |
<main className="px-4 pt-3 pb-4 space-y-4">
|
55 |
{/* toggle using tailwind css */}
|
56 |
<div>
|
57 |
+
<a
|
58 |
+
href="https://huggingface.co/spaces/enzostvs/deepsite/discussions/74"
|
59 |
+
target="_blank"
|
60 |
+
className="w-full flex items-center justify-between text-gray-600 bg-gray-50 border border-gray-100 px-2 py-2 rounded-lg mb-3 text-sm font-medium hover:brightness-95"
|
61 |
+
>
|
62 |
+
How to use it locally?
|
63 |
+
<button className="bg-black text-white rounded-md px-3 py-1.5 text-xs font-semibold cursor-pointer">
|
64 |
+
See the guide
|
65 |
+
</button>
|
66 |
+
</a>
|
67 |
<div className="flex items-center justify-between">
|
68 |
<p className="text-gray-800 text-sm font-medium flex items-center justify-between">
|
69 |
Use auto-provider
|
utils/types.ts
CHANGED
@@ -2,4 +2,5 @@ export interface Auth {
|
|
2 |
preferred_username: string;
|
3 |
picture: string;
|
4 |
name: string;
|
|
|
5 |
}
|
|
|
2 |
preferred_username: string;
|
3 |
picture: string;
|
4 |
name: string;
|
5 |
+
isLocalUse?: boolean;
|
6 |
}
|