enzostvs HF Staff commited on
Commit
e7b5c01
·
1 Parent(s): 1b00a6d

allow to update space while project loaded

Browse files
server.js CHANGED
@@ -120,7 +120,7 @@ app.get("/api/@me", checkUser, async (req, res) => {
120
 
121
  app.post("/api/deploy", checkUser, async (req, res) => {
122
  const { html, title, path } = req.body;
123
- if (!html || !title) {
124
  return res.status(400).send({
125
  ok: false,
126
  message: "Missing required fields",
@@ -358,6 +358,7 @@ app.get("/api/remix/:username/:repo", async (req, res) => {
358
  const space = await spaceInfo({
359
  name: repoId,
360
  accessToken: token,
 
361
  });
362
 
363
  if (!space || space.sdk !== "static" || space.private) {
@@ -378,9 +379,28 @@ app.get("/api/remix/:username/:repo", async (req, res) => {
378
  // remove the last p tag including this url https://enzostvs-deepsite.hf.space
379
  html = html.replace(getPTag(repoId), "");
380
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  res.status(200).send({
382
  ok: true,
383
  html,
 
 
384
  });
385
  } catch (error) {
386
  return res.status(500).send({
 
120
 
121
  app.post("/api/deploy", checkUser, async (req, res) => {
122
  const { html, title, path } = req.body;
123
+ if (!html || (!path && !title)) {
124
  return res.status(400).send({
125
  ok: false,
126
  message: "Missing required fields",
 
358
  const space = await spaceInfo({
359
  name: repoId,
360
  accessToken: token,
361
+ additionalFields: ["author"],
362
  });
363
 
364
  if (!space || space.sdk !== "static" || space.private) {
 
379
  // remove the last p tag including this url https://enzostvs-deepsite.hf.space
380
  html = html.replace(getPTag(repoId), "");
381
 
382
+ let user = null;
383
+
384
+ if (token) {
385
+ const request_user = await fetch(
386
+ "https://huggingface.co/oauth/userinfo",
387
+ {
388
+ headers: {
389
+ Authorization: `Bearer ${hf_token}`,
390
+ },
391
+ }
392
+ )
393
+ .then((res) => res.json())
394
+ .catch(() => null);
395
+
396
+ user = request_user;
397
+ }
398
+
399
  res.status(200).send({
400
  ok: true,
401
  html,
402
+ isOwner: space.author === user?.preferred_username,
403
+ path: repoId,
404
  });
405
  } catch (error) {
406
  return res.status(500).send({
src/components/App.tsx CHANGED
@@ -18,7 +18,6 @@ import Tabs from "./tabs/tabs";
18
  import AskAI from "./ask-ai/ask-ai";
19
  import { Auth } from "./../../utils/types";
20
  import Preview from "./preview/preview";
21
- import LoadButton from "./load-button/load-button";
22
 
23
  function App() {
24
  const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");
@@ -183,10 +182,7 @@ function App() {
183
  }
184
  }}
185
  >
186
- <div className="flex items-center justify-end gap-5">
187
- <LoadButton auth={auth} setHtml={setHtml} />
188
- <DeployButton html={html} error={error} auth={auth} />
189
- </div>
190
  </Header>
191
  <main className="max-lg:flex-col flex w-full">
192
  <div
 
18
  import AskAI from "./ask-ai/ask-ai";
19
  import { Auth } from "./../../utils/types";
20
  import Preview from "./preview/preview";
 
21
 
22
  function App() {
23
  const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");
 
182
  }
183
  }}
184
  >
185
+ <DeployButton html={html} error={error} auth={auth} setHtml={setHtml} />
 
 
 
186
  </Header>
187
  <main className="max-lg:flex-col flex w-full">
188
  <div
src/components/deploy-button/deploy-button.tsx CHANGED
@@ -8,6 +8,7 @@ import SpaceIcon from "@/assets/space.svg";
8
  import Loading from "../loading/loading";
9
  import Login from "../login/login";
10
  import { Auth } from "./../../../utils/types";
 
11
 
12
  const MsgToast = ({ url }: { url: string }) => (
13
  <div className="w-full flex items-center justify-center gap-3">
@@ -27,10 +28,12 @@ function DeployButton({
27
  html,
28
  error = false,
29
  auth,
 
30
  }: {
31
  html: string;
32
  error: boolean;
33
  auth?: Auth;
 
34
  }) {
35
  const [open, setOpen] = useState(false);
36
  const [loading, setLoading] = useState(false);
@@ -78,128 +81,131 @@ function DeployButton({
78
  };
79
 
80
  return (
81
- <div className="relative flex items-center justify-end">
82
- {auth && (
83
- <>
84
- <button
85
- className="mr-2 cursor-pointer"
86
- onClick={() => {
87
- if (confirm("Are you sure you want to log out?")) {
88
- // go to /auth/logout page
89
- window.location.href = "/auth/logout";
90
- }
91
- }}
92
- >
93
- <FaPowerOff className="text-lg text-red-500" />
94
- </button>
95
- <p className="mr-3 text-xs lg:text-sm text-gray-300">
96
- <span className="max-lg:hidden">Connected as </span>
97
- <a
98
- href={`https://huggingface.co/${auth.preferred_username}`}
99
- target="_blank"
100
- className="underline hover:text-white"
101
  >
102
- {auth.preferred_username}
103
- </a>
104
- </p>
105
- </>
106
- )}
107
- <button
108
- className={classNames(
109
- "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",
110
- {
111
- "bg-pink-400": open,
112
- "bg-pink-500": !open,
113
- }
114
- )}
115
- onClick={() => setOpen(!open)}
116
- >
117
- {path ? "Update Space" : "Deploy to Space"}
118
- </button>
119
- <div
120
- className={classNames(
121
- "h-screen w-screen bg-black/20 fixed left-0 top-0 z-10",
122
- {
123
- "opacity-0 pointer-events-none": !open,
124
- }
125
- )}
126
- onClick={() => setOpen(false)}
127
- ></div>
128
- <div
129
- className={classNames(
130
- "absolute top-[calc(100%+8px)] right-0 z-10 w-80 bg-white border border-gray-200 rounded-lg shadow-lg transition-all duration-75 overflow-hidden",
131
- {
132
- "opacity-0 pointer-events-none": !open,
133
- }
134
- )}
135
- >
136
- {!auth ? (
137
- <Login html={html}>
138
- <p className="text-gray-500 text-sm mb-3">
139
- Host this project for free and share it with your friends.
140
  </p>
141
- </Login>
142
- ) : (
143
- <>
144
- <header className="flex items-center text-sm px-4 py-2 border-b border-gray-200 gap-2 bg-gray-100 font-semibold text-gray-700">
145
- <span className="text-xs bg-pink-500/10 text-pink-500 rounded-full pl-1.5 pr-2.5 py-0.5 flex items-center justify-start gap-1.5">
146
- <img src={SpaceIcon} alt="Space Icon" className="size-4" />
147
- Space
148
- </span>
149
- Configure Deployment
150
- </header>
151
- <main className="px-4 pt-3 pb-4 space-y-3">
152
- <p className="text-xs text-amber-600 bg-amber-500/10 rounded-md p-2">
153
- {path ? (
154
- <span>
155
- Your space is live at{" "}
156
- <a
157
- href={`https://huggingface.co/spaces/${path}`}
158
- target="_blank"
159
- className="underline hover:text-amber-700"
160
- >
161
- huggingface.co/{path}
162
- </a>
163
- . You can update it by deploying again.
164
- </span>
165
- ) : (
166
- "Deploy your project to a space on the Hub. Spaces are a way to share your project with the world."
167
- )}
168
- </p>
169
- {!path && (
170
- <label className="block">
171
- <p className="text-gray-600 text-sm font-medium mb-1.5">
172
- Space Title
173
- </p>
174
- <input
175
- type="text"
176
- value={config.title}
177
- className="mr-2 border rounded-md px-3 py-1.5 border-gray-300 w-full text-sm"
178
- placeholder="My Awesome Space"
179
- onChange={(e) =>
180
- setConfig({ ...config, title: e.target.value })
181
- }
182
- />
183
- </label>
184
- )}
185
- {error && (
186
- <p className="text-red-500 text-xs bg-red-500/10 rounded-md p-2">
187
- Your code has errors. Fix them before deploying.
188
- </p>
189
- )}
190
- <div className="pt-2 text-right">
191
- <button
192
- disabled={error || loading || !config.title}
193
- className="relative rounded-full bg-black px-5 py-2 text-white font-semibold text-xs hover:bg-black/90 transition-all duration-100 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:hover:bg-gray-300"
194
- onClick={createSpace}
195
- >
196
- {path ? "Update Space" : "Create Space"}
197
- {loading && <Loading />}
198
- </button>
199
- </div>
200
- </main>
201
  </>
202
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  </div>
204
  </div>
205
  );
 
8
  import Loading from "../loading/loading";
9
  import Login from "../login/login";
10
  import { Auth } from "./../../../utils/types";
11
+ import LoadButton from "../load-button/load-button";
12
 
13
  const MsgToast = ({ url }: { url: string }) => (
14
  <div className="w-full flex items-center justify-center gap-3">
 
28
  html,
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);
 
81
  };
82
 
83
  return (
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
+ <button
90
+ className="mr-2 cursor-pointer"
91
+ onClick={() => {
92
+ if (confirm("Are you sure you want to log out?")) {
93
+ // go to /auth/logout page
94
+ window.location.href = "/auth/logout";
95
+ }
96
+ }}
 
 
 
 
 
 
 
97
  >
98
+ <FaPowerOff className="text-lg text-red-500" />
99
+ </button>
100
+ <p className="mr-3 text-xs lg:text-sm text-gray-300">
101
+ <span className="max-lg:hidden">Connected as </span>
102
+ <a
103
+ href={`https://huggingface.co/${auth.preferred_username}`}
104
+ target="_blank"
105
+ className="underline hover:text-white"
106
+ >
107
+ {auth.preferred_username}
108
+ </a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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",
115
+ {
116
+ "bg-pink-400": open,
117
+ "bg-pink-500": !open,
118
+ }
119
+ )}
120
+ onClick={() => setOpen(!open)}
121
+ >
122
+ {path ? "Update Space" : "Deploy to Space"}
123
+ </button>
124
+ <div
125
+ className={classNames(
126
+ "h-screen w-screen bg-black/20 fixed left-0 top-0 z-10",
127
+ {
128
+ "opacity-0 pointer-events-none": !open,
129
+ }
130
+ )}
131
+ onClick={() => setOpen(false)}
132
+ ></div>
133
+ <div
134
+ className={classNames(
135
+ "absolute top-[calc(100%+8px)] right-0 z-10 w-80 bg-white border border-gray-200 rounded-lg shadow-lg transition-all duration-75 overflow-hidden",
136
+ {
137
+ "opacity-0 pointer-events-none": !open,
138
+ }
139
+ )}
140
+ >
141
+ {!auth ? (
142
+ <Login html={html}>
143
+ <p className="text-gray-500 text-sm mb-3">
144
+ Host this project for free and share it with your friends.
145
+ </p>
146
+ </Login>
147
+ ) : (
148
+ <>
149
+ <header className="flex items-center text-sm px-4 py-2 border-b border-gray-200 gap-2 bg-gray-100 font-semibold text-gray-700">
150
+ <span className="text-xs bg-pink-500/10 text-pink-500 rounded-full pl-1.5 pr-2.5 py-0.5 flex items-center justify-start gap-1.5">
151
+ <img src={SpaceIcon} alt="Space Icon" className="size-4" />
152
+ Space
153
+ </span>
154
+ Configure Deployment
155
+ </header>
156
+ <main className="px-4 pt-3 pb-4 space-y-3">
157
+ <p className="text-xs text-amber-600 bg-amber-500/10 rounded-md p-2">
158
+ {path ? (
159
+ <span>
160
+ Your space is live at{" "}
161
+ <a
162
+ href={`https://huggingface.co/spaces/${path}`}
163
+ target="_blank"
164
+ className="underline hover:text-amber-700"
165
+ >
166
+ huggingface.co/{path}
167
+ </a>
168
+ . You can update it by deploying again.
169
+ </span>
170
+ ) : (
171
+ "Deploy your project to a space on the Hub. Spaces are a way to share your project with the world."
172
+ )}
173
+ </p>
174
+ {!path && (
175
+ <label className="block">
176
+ <p className="text-gray-600 text-sm font-medium mb-1.5">
177
+ Space Title
178
+ </p>
179
+ <input
180
+ type="text"
181
+ value={config.title}
182
+ className="mr-2 border rounded-md px-3 py-1.5 border-gray-300 w-full text-sm"
183
+ placeholder="My Awesome Space"
184
+ onChange={(e) =>
185
+ setConfig({ ...config, title: e.target.value })
186
+ }
187
+ />
188
+ </label>
189
+ )}
190
+ {error && (
191
+ <p className="text-red-500 text-xs bg-red-500/10 rounded-md p-2">
192
+ Your code has errors. Fix them before deploying.
193
+ </p>
194
+ )}
195
+ <div className="pt-2 text-right">
196
+ <button
197
+ disabled={error || loading || (!path && !config.title)}
198
+ className="relative rounded-full bg-black px-5 py-2 text-white font-semibold text-xs hover:bg-black/90 transition-all duration-100 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:hover:bg-gray-300"
199
+ onClick={createSpace}
200
+ >
201
+ {path ? "Update Space" : "Create Space"}
202
+ {loading && <Loading />}
203
+ </button>
204
+ </div>
205
+ </main>
206
+ </>
207
+ )}
208
+ </div>
209
  </div>
210
  </div>
211
  );
src/components/load-button/load-button.tsx CHANGED
@@ -9,25 +9,32 @@ import { Auth } from "../../../utils/types";
9
  function LoadButton({
10
  auth,
11
  setHtml,
 
12
  }: {
13
  auth?: Auth;
14
  setHtml: (html: string) => void;
 
15
  }) {
16
  const [open, setOpen] = useState(false);
17
  const [loading, setLoading] = useState(false);
18
  const [error, setError] = useState(false);
19
- const [path, setPath] = useState<string | undefined>(undefined);
20
 
21
  const loadSpace = async () => {
22
  setLoading(true);
23
  try {
24
- const res = await fetch(`/api/remix/${path}`);
25
  const data = await res.json();
26
  if (res.ok) {
27
  if (data.html) {
28
  setHtml(data.html);
29
  toast.success("Project loaded successfully.");
30
  }
 
 
 
 
 
31
  setOpen(false);
32
  } else {
33
  toast.error(data.message);
@@ -85,14 +92,14 @@ function LoadButton({
85
  </p>
86
  <input
87
  type="text"
88
- value={path}
89
  className="mr-2 border rounded-md px-3 py-1.5 border-gray-300 w-full text-sm"
90
  placeholder="https://huggingface.co/spaces/username/space-name"
91
- onChange={(e) => setPath(e.target.value)}
92
  onFocus={() => setError(false)}
93
  onBlur={(e) => {
94
  const pathParts = e.target.value.split("/");
95
- setPath(
96
  `${pathParts[pathParts.length - 2]}/${
97
  pathParts[pathParts.length - 1]
98
  }`
@@ -108,7 +115,7 @@ function LoadButton({
108
  )}
109
  <div className="pt-2 text-right">
110
  <button
111
- disabled={error || loading || !path}
112
  className="relative rounded-full bg-black px-5 py-2 text-white font-semibold text-xs hover:bg-black/90 transition-all duration-100 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:hover:bg-gray-300"
113
  onClick={loadSpace}
114
  >
 
9
  function LoadButton({
10
  auth,
11
  setHtml,
12
+ setPath,
13
  }: {
14
  auth?: Auth;
15
  setHtml: (html: string) => void;
16
+ setPath: (path?: string) => void;
17
  }) {
18
  const [open, setOpen] = useState(false);
19
  const [loading, setLoading] = useState(false);
20
  const [error, setError] = useState(false);
21
+ const [url, setUrl] = useState<string | undefined>(undefined);
22
 
23
  const loadSpace = async () => {
24
  setLoading(true);
25
  try {
26
+ const res = await fetch(`/api/remix/${url}`);
27
  const data = await res.json();
28
  if (res.ok) {
29
  if (data.html) {
30
  setHtml(data.html);
31
  toast.success("Project loaded successfully.");
32
  }
33
+ if (data.isOwner) {
34
+ setPath(data.path);
35
+ } else {
36
+ setPath(undefined);
37
+ }
38
  setOpen(false);
39
  } else {
40
  toast.error(data.message);
 
92
  </p>
93
  <input
94
  type="text"
95
+ value={url}
96
  className="mr-2 border rounded-md px-3 py-1.5 border-gray-300 w-full text-sm"
97
  placeholder="https://huggingface.co/spaces/username/space-name"
98
+ onChange={(e) => setUrl(e.target.value)}
99
  onFocus={() => setError(false)}
100
  onBlur={(e) => {
101
  const pathParts = e.target.value.split("/");
102
+ setUrl(
103
  `${pathParts[pathParts.length - 2]}/${
104
  pathParts[pathParts.length - 1]
105
  }`
 
115
  )}
116
  <div className="pt-2 text-right">
117
  <button
118
+ disabled={error || loading || !url}
119
  className="relative rounded-full bg-black px-5 py-2 text-white font-semibold text-xs hover:bg-black/90 transition-all duration-100 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:hover:bg-gray-300"
120
  onClick={loadSpace}
121
  >