Spaces:
Runtime error
Runtime error
Charlie
commited on
Commit
·
b200338
1
Parent(s):
b508f56
update files
Browse files- dist/main.js +121 -365
- index.html +8 -4
- src/main.ts +124 -106
- src/oauth.ts +14 -43
- styles/main.css +11 -0
dist/main.js
CHANGED
@@ -1,291 +1,31 @@
|
|
1 |
-
// node_modules/@huggingface/hub/dist/index.mjs
|
2 |
-
var HUB_URL = "https://huggingface.co";
|
3 |
-
async function createApiError(response, opts) {
|
4 |
-
const error = new HubApiError(response.url, response.status, response.headers.get("X-Request-Id") ?? opts?.requestId);
|
5 |
-
error.message = `Api error with status ${error.statusCode}${opts?.message ? `. ${opts.message}` : ""}`;
|
6 |
-
const trailer = [`URL: ${error.url}`, error.requestId ? `Request ID: ${error.requestId}` : void 0].filter(Boolean).join(". ");
|
7 |
-
if (response.headers.get("Content-Type")?.startsWith("application/json")) {
|
8 |
-
const json = await response.json();
|
9 |
-
error.message = json.error || json.message || error.message;
|
10 |
-
if (json.error_description) {
|
11 |
-
error.message = error.message ? error.message + `: ${json.error_description}` : json.error_description;
|
12 |
-
}
|
13 |
-
error.data = json;
|
14 |
-
} else {
|
15 |
-
error.data = { message: await response.text() };
|
16 |
-
}
|
17 |
-
error.message += `. ${trailer}`;
|
18 |
-
throw error;
|
19 |
-
}
|
20 |
-
var HubApiError = class extends Error {
|
21 |
-
statusCode;
|
22 |
-
url;
|
23 |
-
requestId;
|
24 |
-
data;
|
25 |
-
constructor(url, statusCode, requestId, message) {
|
26 |
-
super(message);
|
27 |
-
this.statusCode = statusCode;
|
28 |
-
this.requestId = requestId;
|
29 |
-
this.url = url;
|
30 |
-
}
|
31 |
-
};
|
32 |
-
var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
|
33 |
-
var isWebWorker = typeof self === "object" && self.constructor && self.constructor.name === "DedicatedWorkerGlobalScope";
|
34 |
-
var resolve;
|
35 |
-
var waitPromise = new Promise((r) => {
|
36 |
-
resolve = r;
|
37 |
-
});
|
38 |
-
function base64FromBytes(arr) {
|
39 |
-
if (globalThis.Buffer) {
|
40 |
-
return globalThis.Buffer.from(arr).toString("base64");
|
41 |
-
} else {
|
42 |
-
const bin = [];
|
43 |
-
arr.forEach((byte) => {
|
44 |
-
bin.push(String.fromCharCode(byte));
|
45 |
-
});
|
46 |
-
return globalThis.btoa(bin.join(""));
|
47 |
-
}
|
48 |
-
}
|
49 |
-
var REGEX_COMMIT_HASH = new RegExp("^[0-9a-f]{40}$");
|
50 |
-
async function oauthHandleRedirect(opts) {
|
51 |
-
if (typeof window === "undefined" && !opts?.redirectedUrl) {
|
52 |
-
throw new Error("oauthHandleRedirect is only available in the browser, unless you provide redirectedUrl");
|
53 |
-
}
|
54 |
-
if (typeof localStorage === "undefined" && (!opts?.nonce || !opts?.codeVerifier)) {
|
55 |
-
throw new Error(
|
56 |
-
"oauthHandleRedirect requires localStorage to be available, unless you provide nonce and codeVerifier"
|
57 |
-
);
|
58 |
-
}
|
59 |
-
const redirectedUrl = opts?.redirectedUrl ?? window.location.href;
|
60 |
-
const searchParams = (() => {
|
61 |
-
try {
|
62 |
-
return new URL(redirectedUrl).searchParams;
|
63 |
-
} catch (err) {
|
64 |
-
throw new Error("Failed to parse redirected URL: " + redirectedUrl);
|
65 |
-
}
|
66 |
-
})();
|
67 |
-
const [error, errorDescription] = [searchParams.get("error"), searchParams.get("error_description")];
|
68 |
-
if (error) {
|
69 |
-
throw new Error(`${error}: ${errorDescription}`);
|
70 |
-
}
|
71 |
-
const code = searchParams.get("code");
|
72 |
-
const nonce = opts?.nonce ?? localStorage.getItem("huggingface.co:oauth:nonce");
|
73 |
-
if (!code) {
|
74 |
-
throw new Error("Missing oauth code from query parameters in redirected URL: " + redirectedUrl);
|
75 |
-
}
|
76 |
-
if (!nonce) {
|
77 |
-
throw new Error("Missing oauth nonce from localStorage");
|
78 |
-
}
|
79 |
-
const codeVerifier = opts?.codeVerifier ?? localStorage.getItem("huggingface.co:oauth:code_verifier");
|
80 |
-
if (!codeVerifier) {
|
81 |
-
throw new Error("Missing oauth code_verifier from localStorage");
|
82 |
-
}
|
83 |
-
const state = searchParams.get("state");
|
84 |
-
if (!state) {
|
85 |
-
throw new Error("Missing oauth state from query parameters in redirected URL");
|
86 |
-
}
|
87 |
-
let parsedState;
|
88 |
-
try {
|
89 |
-
parsedState = JSON.parse(state);
|
90 |
-
} catch {
|
91 |
-
throw new Error("Invalid oauth state in redirected URL, unable to parse JSON: " + state);
|
92 |
-
}
|
93 |
-
if (parsedState.nonce !== nonce) {
|
94 |
-
throw new Error("Invalid oauth state in redirected URL");
|
95 |
-
}
|
96 |
-
const hubUrl = opts?.hubUrl || HUB_URL;
|
97 |
-
const openidConfigUrl = `${new URL(hubUrl).origin}/.well-known/openid-configuration`;
|
98 |
-
const openidConfigRes = await fetch(openidConfigUrl, {
|
99 |
-
headers: {
|
100 |
-
Accept: "application/json"
|
101 |
-
}
|
102 |
-
});
|
103 |
-
if (!openidConfigRes.ok) {
|
104 |
-
throw await createApiError(openidConfigRes);
|
105 |
-
}
|
106 |
-
const openidConfig = await openidConfigRes.json();
|
107 |
-
const tokenRes = await fetch(openidConfig.token_endpoint, {
|
108 |
-
method: "POST",
|
109 |
-
headers: {
|
110 |
-
"Content-Type": "application/x-www-form-urlencoded"
|
111 |
-
},
|
112 |
-
body: new URLSearchParams({
|
113 |
-
grant_type: "authorization_code",
|
114 |
-
code,
|
115 |
-
redirect_uri: parsedState.redirectUri,
|
116 |
-
code_verifier: codeVerifier
|
117 |
-
}).toString()
|
118 |
-
});
|
119 |
-
if (!opts?.codeVerifier) {
|
120 |
-
localStorage.removeItem("huggingface.co:oauth:code_verifier");
|
121 |
-
}
|
122 |
-
if (!opts?.nonce) {
|
123 |
-
localStorage.removeItem("huggingface.co:oauth:nonce");
|
124 |
-
}
|
125 |
-
if (!tokenRes.ok) {
|
126 |
-
throw await createApiError(tokenRes);
|
127 |
-
}
|
128 |
-
const token = await tokenRes.json();
|
129 |
-
const accessTokenExpiresAt = new Date(Date.now() + token.expires_in * 1e3);
|
130 |
-
const userInfoRes = await fetch(openidConfig.userinfo_endpoint, {
|
131 |
-
headers: {
|
132 |
-
Authorization: `Bearer ${token.access_token}`
|
133 |
-
}
|
134 |
-
});
|
135 |
-
if (!userInfoRes.ok) {
|
136 |
-
throw await createApiError(userInfoRes);
|
137 |
-
}
|
138 |
-
const userInfo = await userInfoRes.json();
|
139 |
-
return {
|
140 |
-
accessToken: token.access_token,
|
141 |
-
accessTokenExpiresAt,
|
142 |
-
userInfo,
|
143 |
-
state: parsedState.state,
|
144 |
-
scope: token.scope
|
145 |
-
};
|
146 |
-
}
|
147 |
-
async function oauthHandleRedirectIfPresent(opts) {
|
148 |
-
if (typeof window === "undefined" && !opts?.redirectedUrl) {
|
149 |
-
throw new Error("oauthHandleRedirect is only available in the browser, unless you provide redirectedUrl");
|
150 |
-
}
|
151 |
-
if (typeof localStorage === "undefined" && (!opts?.nonce || !opts?.codeVerifier)) {
|
152 |
-
throw new Error(
|
153 |
-
"oauthHandleRedirect requires localStorage to be available, unless you provide nonce and codeVerifier"
|
154 |
-
);
|
155 |
-
}
|
156 |
-
const searchParams = new URLSearchParams(opts?.redirectedUrl ?? window.location.search);
|
157 |
-
if (searchParams.has("error")) {
|
158 |
-
return oauthHandleRedirect(opts);
|
159 |
-
}
|
160 |
-
if (searchParams.has("code")) {
|
161 |
-
if (!localStorage.getItem("huggingface.co:oauth:nonce")) {
|
162 |
-
console.warn(
|
163 |
-
"Missing oauth nonce from localStorage. This can happen when the user refreshes the page after logging in, without changing the URL."
|
164 |
-
);
|
165 |
-
return false;
|
166 |
-
}
|
167 |
-
return oauthHandleRedirect(opts);
|
168 |
-
}
|
169 |
-
return false;
|
170 |
-
}
|
171 |
-
async function oauthLoginUrl(opts) {
|
172 |
-
if (typeof window === "undefined" && (!opts?.redirectUrl || !opts?.clientId)) {
|
173 |
-
throw new Error("oauthLogin is only available in the browser, unless you provide clientId and redirectUrl");
|
174 |
-
}
|
175 |
-
if (typeof localStorage === "undefined" && !opts?.localStorage) {
|
176 |
-
throw new Error(
|
177 |
-
"oauthLogin requires localStorage to be available in the context, unless you provide a localStorage empty object as argument"
|
178 |
-
);
|
179 |
-
}
|
180 |
-
const hubUrl = opts?.hubUrl || HUB_URL;
|
181 |
-
const openidConfigUrl = `${new URL(hubUrl).origin}/.well-known/openid-configuration`;
|
182 |
-
const openidConfigRes = await fetch(openidConfigUrl, {
|
183 |
-
headers: {
|
184 |
-
Accept: "application/json"
|
185 |
-
}
|
186 |
-
});
|
187 |
-
if (!openidConfigRes.ok) {
|
188 |
-
throw await createApiError(openidConfigRes);
|
189 |
-
}
|
190 |
-
const opendidConfig = await openidConfigRes.json();
|
191 |
-
const newNonce = globalThis.crypto.randomUUID();
|
192 |
-
const newCodeVerifier = globalThis.crypto.randomUUID() + globalThis.crypto.randomUUID();
|
193 |
-
if (opts?.localStorage) {
|
194 |
-
if (opts.localStorage.codeVerifier !== void 0 && opts.localStorage.codeVerifier !== null) {
|
195 |
-
throw new Error(
|
196 |
-
"localStorage.codeVerifier must be a initially set to null or undefined, and will be filled by oauthLoginUrl"
|
197 |
-
);
|
198 |
-
}
|
199 |
-
if (opts.localStorage.nonce !== void 0 && opts.localStorage.nonce !== null) {
|
200 |
-
throw new Error(
|
201 |
-
"localStorage.nonce must be a initially set to null or undefined, and will be filled by oauthLoginUrl"
|
202 |
-
);
|
203 |
-
}
|
204 |
-
opts.localStorage.codeVerifier = newCodeVerifier;
|
205 |
-
opts.localStorage.nonce = newNonce;
|
206 |
-
} else {
|
207 |
-
localStorage.setItem("huggingface.co:oauth:nonce", newNonce);
|
208 |
-
localStorage.setItem("huggingface.co:oauth:code_verifier", newCodeVerifier);
|
209 |
-
}
|
210 |
-
const redirectUri = opts?.redirectUrl || (typeof window !== "undefined" ? window.location.href : void 0);
|
211 |
-
if (!redirectUri) {
|
212 |
-
throw new Error("Missing redirectUrl");
|
213 |
-
}
|
214 |
-
const state = JSON.stringify({
|
215 |
-
nonce: newNonce,
|
216 |
-
redirectUri,
|
217 |
-
state: opts?.state
|
218 |
-
});
|
219 |
-
const variables = (
|
220 |
-
// @ts-expect-error window.huggingface is defined inside static Spaces.
|
221 |
-
typeof window !== "undefined" ? window.huggingface?.variables ?? null : null
|
222 |
-
);
|
223 |
-
const clientId = opts?.clientId || variables?.OAUTH_CLIENT_ID;
|
224 |
-
if (!clientId) {
|
225 |
-
if (variables) {
|
226 |
-
throw new Error("Missing clientId, please add hf_oauth: true to the README.md's metadata in your static Space");
|
227 |
-
}
|
228 |
-
throw new Error("Missing clientId");
|
229 |
-
}
|
230 |
-
const challenge = base64FromBytes(
|
231 |
-
new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(newCodeVerifier)))
|
232 |
-
).replace(/[+]/g, "-").replace(/[/]/g, "_").replace(/=/g, "");
|
233 |
-
return `${opendidConfig.authorization_endpoint}?${new URLSearchParams({
|
234 |
-
client_id: clientId,
|
235 |
-
scope: opts?.scopes || variables?.OAUTH_SCOPES || "openid profile",
|
236 |
-
response_type: "code",
|
237 |
-
redirect_uri: redirectUri,
|
238 |
-
state,
|
239 |
-
code_challenge: challenge,
|
240 |
-
code_challenge_method: "S256"
|
241 |
-
}).toString()}`;
|
242 |
-
}
|
243 |
-
|
244 |
// src/oauth.ts
|
245 |
-
var
|
246 |
constructor() {
|
247 |
-
this.
|
248 |
-
const
|
249 |
-
if (
|
250 |
-
|
251 |
-
this.oauthResult = JSON.parse(storedOAuth);
|
252 |
-
} catch {
|
253 |
-
this.oauthResult = null;
|
254 |
-
}
|
255 |
}
|
256 |
}
|
257 |
-
|
258 |
-
this.
|
259 |
-
|
260 |
-
localStorage.setItem("oauth", JSON.stringify(this.oauthResult));
|
261 |
-
}
|
262 |
-
return this.oauthResult;
|
263 |
-
}
|
264 |
-
async initiateLogin() {
|
265 |
-
const loginUrl = await oauthLoginUrl({
|
266 |
-
scopes: window.huggingface?.variables?.OAUTH_SCOPES ?? ""
|
267 |
-
});
|
268 |
-
window.location.href = `${loginUrl}&prompt=consent`;
|
269 |
}
|
270 |
logout() {
|
271 |
-
|
272 |
-
|
273 |
window.location.reload();
|
274 |
}
|
275 |
getAccessToken() {
|
276 |
-
|
277 |
-
return this.oauthResult.accessToken ?? null;
|
278 |
-
}
|
279 |
-
return null;
|
280 |
}
|
281 |
isAuthenticated() {
|
282 |
-
return this.
|
283 |
}
|
284 |
};
|
285 |
-
var
|
286 |
|
287 |
// src/main.ts
|
288 |
-
console.log("huggingface env", window.huggingface);
|
289 |
async function getOrganizationInfo(accessToken) {
|
290 |
const response = await fetch("https://huggingface.co/api/whoami-v2", {
|
291 |
headers: {
|
@@ -366,102 +106,118 @@ async function createRepository(accessToken, name, organization, resourceGroupNa
|
|
366 |
}
|
367 |
}
|
368 |
var init = async () => {
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
414 |
}
|
|
|
|
|
|
|
|
|
415 |
}
|
416 |
-
};
|
417 |
-
}
|
418 |
-
const orgSelect = document.getElementById(
|
419 |
-
"org-select"
|
420 |
-
);
|
421 |
-
if (orgSelect && resourceGroupContainer) {
|
422 |
-
orgSelect.onchange = () => {
|
423 |
-
if (orgSelect.value) {
|
424 |
-
resourceGroupContainer.style.removeProperty("display");
|
425 |
-
} else {
|
426 |
-
resourceGroupContainer.style.display = "none";
|
427 |
-
}
|
428 |
-
};
|
429 |
-
}
|
430 |
-
try {
|
431 |
-
const orgInfo = await getOrganizationInfo(accessToken);
|
432 |
-
if (orgSelect && orgInfo.orgs) {
|
433 |
-
orgInfo.orgs.forEach((org) => {
|
434 |
-
const option = document.createElement("option");
|
435 |
-
option.value = org.name;
|
436 |
-
option.textContent = org.name;
|
437 |
-
orgSelect.appendChild(option);
|
438 |
-
});
|
439 |
}
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
);
|
450 |
}
|
451 |
-
}
|
452 |
-
|
453 |
-
|
454 |
-
const
|
455 |
-
if (
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
465 |
}
|
466 |
};
|
467 |
init().catch(console.error);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
// src/oauth.ts
|
2 |
+
var TokenManager = class {
|
3 |
constructor() {
|
4 |
+
this.token = null;
|
5 |
+
const storedToken = localStorage.getItem("token");
|
6 |
+
if (storedToken) {
|
7 |
+
this.token = storedToken;
|
|
|
|
|
|
|
|
|
8 |
}
|
9 |
}
|
10 |
+
setToken(token) {
|
11 |
+
this.token = token;
|
12 |
+
localStorage.setItem("token", token);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
}
|
14 |
logout() {
|
15 |
+
this.token = null;
|
16 |
+
localStorage.removeItem("token");
|
17 |
window.location.reload();
|
18 |
}
|
19 |
getAccessToken() {
|
20 |
+
return this.token;
|
|
|
|
|
|
|
21 |
}
|
22 |
isAuthenticated() {
|
23 |
+
return this.token !== null;
|
24 |
}
|
25 |
};
|
26 |
+
var tokenManager = new TokenManager();
|
27 |
|
28 |
// src/main.ts
|
|
|
29 |
async function getOrganizationInfo(accessToken) {
|
30 |
const response = await fetch("https://huggingface.co/api/whoami-v2", {
|
31 |
headers: {
|
|
|
106 |
}
|
107 |
}
|
108 |
var init = async () => {
|
109 |
+
if (tokenManager.isAuthenticated()) {
|
110 |
+
showAuthenticatedUI();
|
111 |
+
} else {
|
112 |
+
showUnauthenticatedUI();
|
113 |
+
}
|
114 |
+
};
|
115 |
+
var showAuthenticatedUI = async () => {
|
116 |
+
const accessToken = tokenManager.getAccessToken();
|
117 |
+
if (!accessToken) {
|
118 |
+
throw new Error("Access token not found");
|
119 |
+
}
|
120 |
+
const tokenForm = document.getElementById("token-form");
|
121 |
+
if (tokenForm) {
|
122 |
+
tokenForm.style.display = "none";
|
123 |
+
}
|
124 |
+
const repoForm = document.getElementById("repo-form");
|
125 |
+
const signoutButton = document.getElementById("signout");
|
126 |
+
if (repoForm) {
|
127 |
+
repoForm.style.removeProperty("display");
|
128 |
+
}
|
129 |
+
if (signoutButton) {
|
130 |
+
signoutButton.style.removeProperty("display");
|
131 |
+
signoutButton.onclick = () => tokenManager.logout();
|
132 |
+
}
|
133 |
+
const createRepoButton = document.getElementById("create-repo");
|
134 |
+
const repoNameInput = document.getElementById(
|
135 |
+
"repo-name"
|
136 |
+
);
|
137 |
+
const resourceGroupInput = document.getElementById(
|
138 |
+
"resource-group-name"
|
139 |
+
);
|
140 |
+
const resourceGroupContainer = document.getElementById(
|
141 |
+
"resource-group-container"
|
142 |
+
);
|
143 |
+
if (createRepoButton && repoNameInput) {
|
144 |
+
createRepoButton.onclick = async () => {
|
145 |
+
const repoName = repoNameInput.value.trim();
|
146 |
+
const orgSelect2 = document.getElementById(
|
147 |
+
"org-select"
|
148 |
+
);
|
149 |
+
if (repoName) {
|
150 |
+
try {
|
151 |
+
const selectedOrg = orgSelect2?.value || void 0;
|
152 |
+
const resourceGroupName = selectedOrg ? resourceGroupInput?.value.trim() : void 0;
|
153 |
+
console.log({ selectedOrg, resourceGroupName });
|
154 |
+
await createRepository(
|
155 |
+
accessToken,
|
156 |
+
repoName,
|
157 |
+
selectedOrg,
|
158 |
+
resourceGroupName
|
159 |
+
);
|
160 |
+
repoNameInput.value = "";
|
161 |
+
if (resourceGroupInput) {
|
162 |
+
resourceGroupInput.value = "";
|
163 |
}
|
164 |
+
alert("Repository and resource group created successfully!");
|
165 |
+
} catch (error) {
|
166 |
+
console.error("Failed to create repository:", error);
|
167 |
+
alert("Failed to create repository. Please try again.");
|
168 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
169 |
}
|
170 |
+
};
|
171 |
+
}
|
172 |
+
const orgSelect = document.getElementById("org-select");
|
173 |
+
if (orgSelect && resourceGroupContainer) {
|
174 |
+
orgSelect.onchange = () => {
|
175 |
+
if (orgSelect.value) {
|
176 |
+
resourceGroupContainer.style.removeProperty("display");
|
177 |
+
} else {
|
178 |
+
resourceGroupContainer.style.display = "none";
|
|
|
179 |
}
|
180 |
+
};
|
181 |
+
}
|
182 |
+
try {
|
183 |
+
const orgInfo = await getOrganizationInfo(accessToken);
|
184 |
+
if (orgSelect && orgInfo.orgs) {
|
185 |
+
orgInfo.orgs.forEach((org) => {
|
186 |
+
const option = document.createElement("option");
|
187 |
+
option.value = org.name;
|
188 |
+
option.textContent = org.name;
|
189 |
+
orgSelect.appendChild(option);
|
190 |
+
});
|
191 |
+
}
|
192 |
+
const preElement = document.querySelector("pre");
|
193 |
+
if (preElement) {
|
194 |
+
preElement.textContent = JSON.stringify(orgInfo, null, 2);
|
195 |
+
}
|
196 |
+
} catch (error) {
|
197 |
+
console.error("Failed to fetch organization info:", error);
|
198 |
+
}
|
199 |
+
};
|
200 |
+
var showUnauthenticatedUI = () => {
|
201 |
+
const tokenForm = document.getElementById("token-form");
|
202 |
+
const tokenInput = document.getElementById("token-input");
|
203 |
+
const tokenSubmit = document.getElementById("token-submit");
|
204 |
+
if (tokenForm && tokenInput && tokenSubmit) {
|
205 |
+
tokenForm.style.removeProperty("display");
|
206 |
+
tokenSubmit.onclick = () => {
|
207 |
+
const token = tokenInput.value.trim();
|
208 |
+
if (token) {
|
209 |
+
tokenManager.setToken(token);
|
210 |
+
showAuthenticatedUI();
|
211 |
+
}
|
212 |
+
};
|
213 |
+
}
|
214 |
+
const repoForm = document.getElementById("repo-form");
|
215 |
+
const signoutButton = document.getElementById("signout");
|
216 |
+
if (repoForm) {
|
217 |
+
repoForm.style.display = "none";
|
218 |
+
}
|
219 |
+
if (signoutButton) {
|
220 |
+
signoutButton.style.display = "none";
|
221 |
}
|
222 |
};
|
223 |
init().catch(console.error);
|
index.html
CHANGED
@@ -4,15 +4,19 @@
|
|
4 |
<head>
|
5 |
<meta charset="UTF-8">
|
6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7 |
-
<title>HuggingFace
|
8 |
<link rel="stylesheet" href="styles/main.css">
|
9 |
</head>
|
10 |
|
11 |
<body>
|
12 |
<div id="app">
|
13 |
-
<h1>HuggingFace
|
14 |
<div class="auth-container">
|
15 |
-
<
|
|
|
|
|
|
|
|
|
16 |
<button id="signout" style="display: none;" class="auth-button">Sign out</button>
|
17 |
</div>
|
18 |
<div id="repo-form" style="display: none;" class="repo-form">
|
@@ -26,7 +30,7 @@
|
|
26 |
<button id="create-repo" class="repo-button">Create Repository</button>
|
27 |
</div>
|
28 |
<div id="content">
|
29 |
-
<h2>
|
30 |
<pre class="oauth-result"></pre>
|
31 |
</div>
|
32 |
</div>
|
|
|
4 |
<head>
|
5 |
<meta charset="UTF-8">
|
6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7 |
+
<title>HuggingFace API Demo</title>
|
8 |
<link rel="stylesheet" href="styles/main.css">
|
9 |
</head>
|
10 |
|
11 |
<body>
|
12 |
<div id="app">
|
13 |
+
<h1>HuggingFace API Demo</h1>
|
14 |
<div class="auth-container">
|
15 |
+
<div id="token-form" class="token-form">
|
16 |
+
<input type="password" id="token-input" placeholder="Enter your HuggingFace API token"
|
17 |
+
class="repo-input">
|
18 |
+
<button id="token-submit" class="auth-button">Submit Token</button>
|
19 |
+
</div>
|
20 |
<button id="signout" style="display: none;" class="auth-button">Sign out</button>
|
21 |
</div>
|
22 |
<div id="repo-form" style="display: none;" class="repo-form">
|
|
|
30 |
<button id="create-repo" class="repo-button">Create Repository</button>
|
31 |
</div>
|
32 |
<div id="content">
|
33 |
+
<h2>User Info</h2>
|
34 |
<pre class="oauth-result"></pre>
|
35 |
</div>
|
36 |
</div>
|
src/main.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import {
|
2 |
|
3 |
interface OrganizationInfo {
|
4 |
type: string;
|
@@ -22,10 +22,6 @@ interface OrganizationMember {
|
|
22 |
role: string;
|
23 |
}
|
24 |
|
25 |
-
declare const window: HuggingFaceWindow;
|
26 |
-
|
27 |
-
console.log("huggingface env", window.huggingface);
|
28 |
-
|
29 |
async function getOrganizationInfo(
|
30 |
accessToken: string
|
31 |
): Promise<WhoAmIResponse> {
|
@@ -133,120 +129,142 @@ async function createRepository(
|
|
133 |
}
|
134 |
|
135 |
const init = async (): Promise<void> => {
|
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 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
190 |
}
|
|
|
|
|
|
|
|
|
191 |
}
|
192 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
193 |
}
|
194 |
|
195 |
-
//
|
196 |
-
const
|
197 |
-
|
198 |
-
|
199 |
-
if (orgSelect && resourceGroupContainer) {
|
200 |
-
orgSelect.onchange = () => {
|
201 |
-
if (orgSelect.value) {
|
202 |
-
resourceGroupContainer.style.removeProperty("display");
|
203 |
-
} else {
|
204 |
-
resourceGroupContainer.style.display = "none";
|
205 |
-
}
|
206 |
-
};
|
207 |
}
|
|
|
|
|
|
|
|
|
208 |
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
if (orgSelect && orgInfo.orgs) {
|
215 |
-
orgInfo.orgs.forEach((org) => {
|
216 |
-
const option = document.createElement("option");
|
217 |
-
option.value = org.name;
|
218 |
-
option.textContent = org.name;
|
219 |
-
orgSelect.appendChild(option);
|
220 |
-
});
|
221 |
-
}
|
222 |
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
},
|
231 |
-
null,
|
232 |
-
2
|
233 |
-
);
|
234 |
}
|
235 |
-
}
|
236 |
-
|
237 |
-
}
|
238 |
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
}
|
245 |
-
|
246 |
-
|
247 |
-
signinButton.style.removeProperty("display");
|
248 |
-
signinButton.onclick = () => oauthManager.initiateLogin();
|
249 |
-
}
|
250 |
}
|
251 |
};
|
252 |
|
|
|
1 |
+
import { tokenManager } from "./oauth";
|
2 |
|
3 |
interface OrganizationInfo {
|
4 |
type: string;
|
|
|
22 |
role: string;
|
23 |
}
|
24 |
|
|
|
|
|
|
|
|
|
25 |
async function getOrganizationInfo(
|
26 |
accessToken: string
|
27 |
): Promise<WhoAmIResponse> {
|
|
|
129 |
}
|
130 |
|
131 |
const init = async (): Promise<void> => {
|
132 |
+
if (tokenManager.isAuthenticated()) {
|
133 |
+
showAuthenticatedUI();
|
134 |
+
} else {
|
135 |
+
showUnauthenticatedUI();
|
136 |
+
}
|
137 |
+
};
|
138 |
|
139 |
+
const showAuthenticatedUI = async () => {
|
140 |
+
const accessToken = tokenManager.getAccessToken();
|
141 |
+
if (!accessToken) {
|
142 |
+
throw new Error("Access token not found");
|
143 |
+
}
|
144 |
|
145 |
+
// Hide token form
|
146 |
+
const tokenForm = document.getElementById("token-form");
|
147 |
+
if (tokenForm) {
|
148 |
+
tokenForm.style.display = "none";
|
149 |
+
}
|
150 |
|
151 |
+
// Show repo form and signout button
|
152 |
+
const repoForm = document.getElementById("repo-form");
|
153 |
+
const signoutButton = document.getElementById("signout");
|
154 |
+
if (repoForm) {
|
155 |
+
repoForm.style.removeProperty("display");
|
156 |
+
}
|
157 |
+
if (signoutButton) {
|
158 |
+
signoutButton.style.removeProperty("display");
|
159 |
+
signoutButton.onclick = () => tokenManager.logout();
|
160 |
+
}
|
|
|
161 |
|
162 |
+
// Add create repo functionality
|
163 |
+
const createRepoButton = document.getElementById("create-repo");
|
164 |
+
const repoNameInput = document.getElementById(
|
165 |
+
"repo-name"
|
166 |
+
) as HTMLInputElement;
|
167 |
+
const resourceGroupInput = document.getElementById(
|
168 |
+
"resource-group-name"
|
169 |
+
) as HTMLInputElement;
|
170 |
+
const resourceGroupContainer = document.getElementById(
|
171 |
+
"resource-group-container"
|
172 |
+
);
|
173 |
+
|
174 |
+
if (createRepoButton && repoNameInput) {
|
175 |
+
createRepoButton.onclick = async () => {
|
176 |
+
const repoName = repoNameInput.value.trim();
|
177 |
+
const orgSelect = document.getElementById(
|
178 |
+
"org-select"
|
179 |
+
) as HTMLSelectElement;
|
180 |
+
if (repoName) {
|
181 |
+
try {
|
182 |
+
const selectedOrg = orgSelect?.value || undefined;
|
183 |
+
const resourceGroupName = selectedOrg
|
184 |
+
? resourceGroupInput?.value.trim()
|
185 |
+
: undefined;
|
186 |
+
|
187 |
+
console.log({ selectedOrg, resourceGroupName });
|
188 |
+
await createRepository(
|
189 |
+
accessToken,
|
190 |
+
repoName,
|
191 |
+
selectedOrg,
|
192 |
+
resourceGroupName
|
193 |
+
);
|
194 |
+
repoNameInput.value = ""; // Clear input after success
|
195 |
+
if (resourceGroupInput) {
|
196 |
+
resourceGroupInput.value = ""; // Clear resource group input
|
197 |
}
|
198 |
+
alert("Repository and resource group created successfully!");
|
199 |
+
} catch (error) {
|
200 |
+
console.error("Failed to create repository:", error);
|
201 |
+
alert("Failed to create repository. Please try again.");
|
202 |
}
|
203 |
+
}
|
204 |
+
};
|
205 |
+
}
|
206 |
+
|
207 |
+
// Handle org select change to show/hide resource group input
|
208 |
+
const orgSelect = document.getElementById("org-select") as HTMLSelectElement;
|
209 |
+
if (orgSelect && resourceGroupContainer) {
|
210 |
+
orgSelect.onchange = () => {
|
211 |
+
if (orgSelect.value) {
|
212 |
+
resourceGroupContainer.style.removeProperty("display");
|
213 |
+
} else {
|
214 |
+
resourceGroupContainer.style.display = "none";
|
215 |
+
}
|
216 |
+
};
|
217 |
+
}
|
218 |
+
|
219 |
+
// Get organization info and populate org select
|
220 |
+
try {
|
221 |
+
const orgInfo = await getOrganizationInfo(accessToken);
|
222 |
+
|
223 |
+
// Populate org select
|
224 |
+
if (orgSelect && orgInfo.orgs) {
|
225 |
+
orgInfo.orgs.forEach((org) => {
|
226 |
+
const option = document.createElement("option");
|
227 |
+
option.value = org.name;
|
228 |
+
option.textContent = org.name;
|
229 |
+
orgSelect.appendChild(option);
|
230 |
+
});
|
231 |
}
|
232 |
|
233 |
+
// Display user info
|
234 |
+
const preElement = document.querySelector("pre");
|
235 |
+
if (preElement) {
|
236 |
+
preElement.textContent = JSON.stringify(orgInfo, null, 2);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
237 |
}
|
238 |
+
} catch (error) {
|
239 |
+
console.error("Failed to fetch organization info:", error);
|
240 |
+
}
|
241 |
+
};
|
242 |
|
243 |
+
const showUnauthenticatedUI = () => {
|
244 |
+
// Show token form
|
245 |
+
const tokenForm = document.getElementById("token-form");
|
246 |
+
const tokenInput = document.getElementById("token-input") as HTMLInputElement;
|
247 |
+
const tokenSubmit = document.getElementById("token-submit");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
|
249 |
+
if (tokenForm && tokenInput && tokenSubmit) {
|
250 |
+
tokenForm.style.removeProperty("display");
|
251 |
+
tokenSubmit.onclick = () => {
|
252 |
+
const token = tokenInput.value.trim();
|
253 |
+
if (token) {
|
254 |
+
tokenManager.setToken(token);
|
255 |
+
showAuthenticatedUI();
|
|
|
|
|
|
|
|
|
256 |
}
|
257 |
+
};
|
258 |
+
}
|
|
|
259 |
|
260 |
+
// Hide other UI elements
|
261 |
+
const repoForm = document.getElementById("repo-form");
|
262 |
+
const signoutButton = document.getElementById("signout");
|
263 |
+
if (repoForm) {
|
264 |
+
repoForm.style.display = "none";
|
265 |
+
}
|
266 |
+
if (signoutButton) {
|
267 |
+
signoutButton.style.display = "none";
|
|
|
|
|
|
|
268 |
}
|
269 |
};
|
270 |
|
src/oauth.ts
CHANGED
@@ -1,62 +1,33 @@
|
|
1 |
-
|
2 |
|
3 |
-
export
|
4 |
-
|
5 |
-
variables: {
|
6 |
-
OAUTH_SCOPES: string;
|
7 |
-
};
|
8 |
-
};
|
9 |
-
}
|
10 |
-
|
11 |
-
declare const window: HuggingFaceWindow;
|
12 |
-
|
13 |
-
export type OAuthResult = Record<string, any> | null | false;
|
14 |
-
|
15 |
-
export class OAuthManager {
|
16 |
-
private oauthResult: OAuthResult = null;
|
17 |
|
18 |
constructor() {
|
19 |
-
const
|
20 |
-
if (
|
21 |
-
|
22 |
-
this.oauthResult = JSON.parse(storedOAuth);
|
23 |
-
} catch {
|
24 |
-
this.oauthResult = null;
|
25 |
-
}
|
26 |
}
|
27 |
}
|
28 |
|
29 |
-
|
30 |
-
this.
|
31 |
-
|
32 |
-
localStorage.setItem("oauth", JSON.stringify(this.oauthResult));
|
33 |
-
}
|
34 |
-
return this.oauthResult;
|
35 |
-
}
|
36 |
-
|
37 |
-
async initiateLogin(): Promise<void> {
|
38 |
-
const loginUrl = await oauthLoginUrl({
|
39 |
-
scopes: window.huggingface?.variables?.OAUTH_SCOPES ?? "",
|
40 |
-
});
|
41 |
-
window.location.href = `${loginUrl}&prompt=consent`;
|
42 |
}
|
43 |
|
44 |
logout(): void {
|
45 |
-
|
46 |
-
|
47 |
window.location.reload();
|
48 |
}
|
49 |
|
50 |
getAccessToken(): string | null {
|
51 |
-
|
52 |
-
return this.oauthResult.accessToken ?? null;
|
53 |
-
}
|
54 |
-
return null;
|
55 |
}
|
56 |
|
57 |
isAuthenticated(): boolean {
|
58 |
-
return this.
|
59 |
}
|
60 |
}
|
61 |
|
62 |
-
export const
|
|
|
1 |
+
export type TokenResult = string | null;
|
2 |
|
3 |
+
export class TokenManager {
|
4 |
+
private token: TokenResult = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
6 |
constructor() {
|
7 |
+
const storedToken = localStorage.getItem("token");
|
8 |
+
if (storedToken) {
|
9 |
+
this.token = storedToken;
|
|
|
|
|
|
|
|
|
10 |
}
|
11 |
}
|
12 |
|
13 |
+
setToken(token: string): void {
|
14 |
+
this.token = token;
|
15 |
+
localStorage.setItem("token", token);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
}
|
17 |
|
18 |
logout(): void {
|
19 |
+
this.token = null;
|
20 |
+
localStorage.removeItem("token");
|
21 |
window.location.reload();
|
22 |
}
|
23 |
|
24 |
getAccessToken(): string | null {
|
25 |
+
return this.token;
|
|
|
|
|
|
|
26 |
}
|
27 |
|
28 |
isAuthenticated(): boolean {
|
29 |
+
return this.token !== null;
|
30 |
}
|
31 |
}
|
32 |
|
33 |
+
export const tokenManager = new TokenManager();
|
styles/main.css
CHANGED
@@ -34,6 +34,17 @@ h2 {
|
|
34 |
margin: 2rem 0;
|
35 |
}
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
.auth-button {
|
38 |
background-color: #ff9d00;
|
39 |
color: white;
|
|
|
34 |
margin: 2rem 0;
|
35 |
}
|
36 |
|
37 |
+
.token-form {
|
38 |
+
max-width: 400px;
|
39 |
+
margin: 0 auto;
|
40 |
+
display: flex;
|
41 |
+
gap: 1rem;
|
42 |
+
}
|
43 |
+
|
44 |
+
.token-form input {
|
45 |
+
flex: 1;
|
46 |
+
}
|
47 |
+
|
48 |
.auth-button {
|
49 |
background-color: #ff9d00;
|
50 |
color: white;
|