jbilcke-hf HF staff commited on
Commit
0ed5b20
β€’
1 Parent(s): 2f84587

working on the Hugging Face Sign-In

Browse files
.env CHANGED
@@ -23,6 +23,11 @@ NEXT_PUBLIC_CAN_REDRAW="false"
23
  # Set to "true" to create artificial delays and smooth out traffic
24
  NEXT_PUBLIC_ENABLE_RATE_LIMITER="false"
25
 
 
 
 
 
 
26
  # ------------- PROVIDER AUTH ------------
27
  # You only need to configure the access token(s) for the provider(s) you want to use
28
 
 
23
  # Set to "true" to create artificial delays and smooth out traffic
24
  NEXT_PUBLIC_ENABLE_RATE_LIMITER="false"
25
 
26
+ # ------------- HUGGING FACE OAUTH -------------
27
+ NEXT_PUBLIC_ENABLE_HUGGING_FACE_OAUTH="false"
28
+ NEXT_PUBLIC_HUGGING_FACE_OAUTH_CLIENT_ID=""
29
+ HUGGING_FACE_OAUTH_SECRET=""
30
+
31
  # ------------- PROVIDER AUTH ------------
32
  # You only need to configure the access token(s) for the provider(s) you want to use
33
 
package-lock.json CHANGED
@@ -8,6 +8,7 @@
8
  "name": "@jbilcke/comic-factory",
9
  "version": "0.0.0",
10
  "dependencies": {
 
11
  "@huggingface/inference": "^2.6.1",
12
  "@radix-ui/react-accordion": "^1.1.2",
13
  "@radix-ui/react-avatar": "^1.0.3",
@@ -208,6 +209,17 @@
208
  "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
209
  "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
210
  },
 
 
 
 
 
 
 
 
 
 
 
211
  "node_modules/@huggingface/inference": {
212
  "version": "2.6.4",
213
  "resolved": "https://registry.npmjs.org/@huggingface/inference/-/inference-2.6.4.tgz",
@@ -4217,6 +4229,11 @@
4217
  "url": "https://github.com/sponsors/ljharb"
4218
  }
4219
  },
 
 
 
 
 
4220
  "node_modules/hasown": {
4221
  "version": "2.0.1",
4222
  "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
 
8
  "name": "@jbilcke/comic-factory",
9
  "version": "0.0.0",
10
  "dependencies": {
11
+ "@huggingface/hub": "^0.14.2",
12
  "@huggingface/inference": "^2.6.1",
13
  "@radix-ui/react-accordion": "^1.1.2",
14
  "@radix-ui/react-avatar": "^1.0.3",
 
209
  "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
210
  "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
211
  },
212
+ "node_modules/@huggingface/hub": {
213
+ "version": "0.14.2",
214
+ "resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-0.14.2.tgz",
215
+ "integrity": "sha512-OwOzr/p92ToK0ATdyVEqfecoAO0Ab037Up4V3IHbJLpS3KjI4p1kkjSXJ+bml0Ngq+n0094poxIRYoK+pv1b+w==",
216
+ "dependencies": {
217
+ "hash-wasm": "^4.9.0"
218
+ },
219
+ "engines": {
220
+ "node": ">=18"
221
+ }
222
+ },
223
  "node_modules/@huggingface/inference": {
224
  "version": "2.6.4",
225
  "resolved": "https://registry.npmjs.org/@huggingface/inference/-/inference-2.6.4.tgz",
 
4229
  "url": "https://github.com/sponsors/ljharb"
4230
  }
4231
  },
4232
+ "node_modules/hash-wasm": {
4233
+ "version": "4.11.0",
4234
+ "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.11.0.tgz",
4235
+ "integrity": "sha512-HVusNXlVqHe0fzIzdQOGolnFN6mX/fqcrSAOcTBXdvzrXVHwTz11vXeKRmkR5gTuwVpvHZEIyKoePDvuAR+XwQ=="
4236
+ },
4237
  "node_modules/hasown": {
4238
  "version": "2.0.1",
4239
  "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
package.json CHANGED
@@ -9,6 +9,7 @@
9
  "lint": "next lint"
10
  },
11
  "dependencies": {
 
12
  "@huggingface/inference": "^2.6.1",
13
  "@radix-ui/react-accordion": "^1.1.2",
14
  "@radix-ui/react-avatar": "^1.0.3",
 
9
  "lint": "next lint"
10
  },
11
  "dependencies": {
12
+ "@huggingface/hub": "^0.14.2",
13
  "@huggingface/inference": "^2.6.1",
14
  "@radix-ui/react-accordion": "^1.1.2",
15
  "@radix-ui/react-avatar": "^1.0.3",
src/app/interface/bottom-bar/index.tsx CHANGED
@@ -8,6 +8,7 @@ import { sleep } from "@/lib/sleep"
8
  import { AIClipFactory } from "../ai-clip-factory"
9
  import { Share } from "../share"
10
  import { SettingsDialog } from "../settings-dialog"
 
11
 
12
  export function BottomBar() {
13
  const download = useStore(state => state.download)
@@ -79,6 +80,7 @@ export function BottomBar() {
79
  `scale-[0.9]`
80
  )}>
81
  <About />
 
82
  {/*
83
  Thank you clip factory for your service 🫑
84
  <AIClipFactory />
 
8
  import { AIClipFactory } from "../ai-clip-factory"
9
  import { Share } from "../share"
10
  import { SettingsDialog } from "../settings-dialog"
11
+ import { Login } from "../login"
12
 
13
  export function BottomBar() {
14
  const download = useStore(state => state.download)
 
80
  `scale-[0.9]`
81
  )}>
82
  <About />
83
+ <Login />
84
  {/*
85
  Thank you clip factory for your service 🫑
86
  <AIClipFactory />
src/app/interface/login/index.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useEffect } from "react"
4
+
5
+ import { Button } from "@/components/ui/button"
6
+ import { useOAuth } from "@/lib/useOAuth"
7
+
8
+ export function Login() {
9
+ const { canLogin, login, isLoggedIn, oauthResult } = useOAuth({ debug: true })
10
+
11
+ useEffect(() => {
12
+ if (!oauthResult) {
13
+ return
14
+ }
15
+
16
+ const { userInfo } = oauthResult
17
+
18
+ // TODO use the Inference API
19
+
20
+ if (userInfo.isPro) {
21
+ // TODO we could do something with the fact the user is PRO versus other types of users
22
+ }
23
+ }, [canLogin, isLoggedIn, oauthResult])
24
+
25
+ if (isLoggedIn || canLogin) {
26
+ return <Button onClick={login}>Sign-in with Hugging Face</Button>
27
+ } else {
28
+ return null
29
+ }
30
+ }
src/app/interface/settings-dialog/defaultSettings.ts CHANGED
@@ -3,6 +3,7 @@ import { RenderingModelVendor, Settings } from "@/types"
3
  export const defaultSettings: Settings = {
4
  renderingModelVendor: "SERVER" as RenderingModelVendor,
5
  renderingUseTurbo: false,
 
6
  huggingfaceApiKey: "",
7
  huggingfaceInferenceApiModel: "stabilityai/stable-diffusion-xl-base-1.0",
8
  huggingfaceInferenceApiModelTrigger: "",
 
3
  export const defaultSettings: Settings = {
4
  renderingModelVendor: "SERVER" as RenderingModelVendor,
5
  renderingUseTurbo: false,
6
+ huggingFaceOAuth: "",
7
  huggingfaceApiKey: "",
8
  huggingfaceInferenceApiModel: "stabilityai/stable-diffusion-xl-base-1.0",
9
  huggingfaceInferenceApiModelTrigger: "",
src/app/interface/settings-dialog/getSettings.ts CHANGED
@@ -10,6 +10,7 @@ export function getSettings(): Settings {
10
  return {
11
  renderingModelVendor: getValidString(localStorage?.getItem?.(localStorageKeys.renderingModelVendor), defaultSettings.renderingModelVendor) as RenderingModelVendor,
12
  renderingUseTurbo: getValidBoolean(localStorage?.getItem?.(localStorageKeys.renderingUseTurbo), defaultSettings.renderingUseTurbo),
 
13
  huggingfaceApiKey: getValidString(localStorage?.getItem?.(localStorageKeys.huggingfaceApiKey), defaultSettings.huggingfaceApiKey),
14
  huggingfaceInferenceApiModel: getValidString(localStorage?.getItem?.(localStorageKeys.huggingfaceInferenceApiModel), defaultSettings.huggingfaceInferenceApiModel),
15
  huggingfaceInferenceApiModelTrigger: getValidString(localStorage?.getItem?.(localStorageKeys.huggingfaceInferenceApiModelTrigger), defaultSettings.huggingfaceInferenceApiModelTrigger),
 
10
  return {
11
  renderingModelVendor: getValidString(localStorage?.getItem?.(localStorageKeys.renderingModelVendor), defaultSettings.renderingModelVendor) as RenderingModelVendor,
12
  renderingUseTurbo: getValidBoolean(localStorage?.getItem?.(localStorageKeys.renderingUseTurbo), defaultSettings.renderingUseTurbo),
13
+ huggingFaceOAuth: getValidString(localStorage?.getItem?.(localStorageKeys.huggingFaceOAuth), defaultSettings.huggingFaceOAuth),
14
  huggingfaceApiKey: getValidString(localStorage?.getItem?.(localStorageKeys.huggingfaceApiKey), defaultSettings.huggingfaceApiKey),
15
  huggingfaceInferenceApiModel: getValidString(localStorage?.getItem?.(localStorageKeys.huggingfaceInferenceApiModel), defaultSettings.huggingfaceInferenceApiModel),
16
  huggingfaceInferenceApiModelTrigger: getValidString(localStorage?.getItem?.(localStorageKeys.huggingfaceInferenceApiModelTrigger), defaultSettings.huggingfaceInferenceApiModelTrigger),
src/app/interface/settings-dialog/localStorageKeys.ts CHANGED
@@ -3,6 +3,7 @@ import { Settings } from "@/types"
3
  export const localStorageKeys: Record<keyof Settings, string> = {
4
  renderingModelVendor: "CONF_RENDERING_MODEL_VENDOR",
5
  renderingUseTurbo: "CONF_RENDERING_USE_TURBO",
 
6
  huggingfaceApiKey: "CONF_AUTH_HF_API_TOKEN",
7
  huggingfaceInferenceApiModel: "CONF_RENDERING_HF_INFERENCE_API_BASE_MODEL",
8
  huggingfaceInferenceApiModelTrigger: "CONF_RENDERING_HF_INFERENCE_API_BASE_MODEL_TRIGGER",
 
3
  export const localStorageKeys: Record<keyof Settings, string> = {
4
  renderingModelVendor: "CONF_RENDERING_MODEL_VENDOR",
5
  renderingUseTurbo: "CONF_RENDERING_USE_TURBO",
6
+ huggingFaceOAuth: "CONF_AUTH_HF_OAUTH",
7
  huggingfaceApiKey: "CONF_AUTH_HF_API_TOKEN",
8
  huggingfaceInferenceApiModel: "CONF_RENDERING_HF_INFERENCE_API_BASE_MODEL",
9
  huggingfaceInferenceApiModelTrigger: "CONF_RENDERING_HF_INFERENCE_API_BASE_MODEL_TRIGGER",
src/lib/getValidOAuth.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { OAuthResult } from "@huggingface/hub"
2
+
3
+ // return a valid OAuthResult, or else undefined
4
+ export function getValidOAuth(rawInput?: any): OAuthResult | undefined {
5
+ try {
6
+ let untypedOAuthResult: any
7
+ try {
8
+ untypedOAuthResult = JSON.parse(rawInput)
9
+ if (!untypedOAuthResult) { throw new Error("no valid serialized oauth result") }
10
+ } catch (err) {
11
+ untypedOAuthResult = rawInput
12
+ }
13
+
14
+ const maybeValidOAuth = untypedOAuthResult as OAuthResult
15
+
16
+ const accessTokenExpiresAt = new Date(maybeValidOAuth.accessTokenExpiresAt)
17
+
18
+ // Get the current date
19
+ const currentDate = new Date()
20
+
21
+ if (accessTokenExpiresAt.getTime() < currentDate.getTime()) {
22
+ throw new Error("the serialized oauth result has expired")
23
+ }
24
+
25
+ return maybeValidOAuth
26
+ } catch (err) {
27
+ // console.error(err)
28
+ return undefined
29
+ }
30
+ }
src/lib/useBetaEnabled.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useSearchParams } from "next/navigation"
4
+
5
+ export function useBetaEnabled(): boolean {
6
+ const searchParams = useSearchParams()
7
+ const isBetaEnabled = searchParams.get("beta") === "true"
8
+ return isBetaEnabled
9
+ }
src/lib/useOAuth.ts ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { OAuthResult, oauthHandleRedirectIfPresent, oauthLoginUrl } from "@huggingface/hub"
4
+ import { useSearchParams } from "next/navigation"
5
+ import { useEffect, useState } from "react"
6
+ import { useOAuthClientId } from "./useOAuthClientId"
7
+ import { useOAuthEnabled } from "./useOAuthEnabled"
8
+ import { useBetaEnabled } from "./useBetaEnabled"
9
+ import { usePersistedOAuth } from "./usePersistedOAuth"
10
+ import { getValidOAuth } from "./getValidOAuth"
11
+
12
+ export function useOAuth({
13
+ debug = false
14
+ }: {
15
+ debug?: boolean
16
+ } = {
17
+ debug: false
18
+ }): {
19
+ clientId: string
20
+ redirectUrl: string
21
+ scopes: string
22
+ canLogin: boolean
23
+ login: () => Promise<void>
24
+ isLoggedIn: boolean
25
+ oauthResult?: OAuthResult
26
+ } {
27
+ const [oauthResult, setOAuthResult] = usePersistedOAuth()
28
+
29
+ const clientId = useOAuthClientId()
30
+ const redirectUrl = "http://localhost:3000"
31
+ const scopes = "openid profile inference-api"
32
+
33
+ const isOAuthEnabled = useOAuthEnabled()
34
+ const isBetaEnabled = useBetaEnabled()
35
+
36
+ const searchParams = useSearchParams()
37
+ const code = searchParams.get("code")
38
+ const state = searchParams.get("state")
39
+
40
+ const hasReceivedFreshOAuth = Boolean(code && state)
41
+
42
+ const canLogin: boolean = Boolean(clientId && isOAuthEnabled && isBetaEnabled)
43
+ const isLoggedIn = Boolean(oauthResult)
44
+
45
+ if (debug) {
46
+ console.log("useOAuth debug:", {
47
+ oauthResult,
48
+ clientId,
49
+ redirectUrl,
50
+ scopes,
51
+ isOAuthEnabled,
52
+ isBetaEnabled,
53
+ code,
54
+ state,
55
+ hasReceivedFreshOAuth,
56
+ canLogin,
57
+ isLoggedIn,
58
+ })
59
+ }
60
+
61
+ useEffect(() => {
62
+ // no need to perfor the rest if the operation is there is nothing in the url
63
+ if (hasReceivedFreshOAuth) {
64
+ (async () => {
65
+ const maybeValidOAuth = await oauthHandleRedirectIfPresent()
66
+
67
+ const newOAuth = getValidOAuth(maybeValidOAuth)
68
+
69
+ if (!newOAuth) {
70
+ if (debug) {
71
+ console.log("useOAuth::useEffect 1: got something in the url but no valid oauth data to show.. something went terribly wrong")
72
+ }
73
+ } else {
74
+ if (debug) {
75
+ console.log("useOAuth::useEffect 1: correctly received the new oauth result, saving it to local storage:", newOAuth)
76
+ }
77
+ setOAuthResult(newOAuth)
78
+ }
79
+ })()
80
+ }
81
+ }, [debug, hasReceivedFreshOAuth])
82
+
83
+ // for debugging purpose
84
+ useEffect(() => {
85
+ if (!debug) {
86
+ return
87
+ }
88
+ console.log(`useOAuth::useEffect 2: canLogin? ${canLogin}`)
89
+ if (!canLogin) {
90
+ return
91
+ }
92
+ console.log(`useOAuth::useEffect2: isLoggedIn? ${isLoggedIn}`)
93
+ if (!isLoggedIn) {
94
+ return
95
+ }
96
+ console.log(`useOAuth::useEffect 2: oauthResult:`, oauthResult)
97
+ }, [debug, canLogin, isLoggedIn, oauthResult])
98
+
99
+ const login = async () => {
100
+ window.location.href = await oauthLoginUrl({
101
+ clientId,
102
+ redirectUrl,
103
+ scopes,
104
+ })
105
+ }
106
+
107
+ return {
108
+ clientId,
109
+ redirectUrl,
110
+ scopes,
111
+ canLogin,
112
+ login,
113
+ isLoggedIn,
114
+ oauthResult
115
+ }
116
+ }
src/lib/useOAuthClientId.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ export function useOAuthClientId(): string {
4
+ const oauthClientId = `${process.env.NEXT_PUBLIC_HUGGING_FACE_OAUTH_CLIENT_ID || ""}`
5
+ return oauthClientId
6
+ }
src/lib/useOAuthEnabled.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ export function useOAuthEnabled(): boolean {
4
+ const isOAuthEnabled = `${process.env.NEXT_PUBLIC_ENABLE_HUGGING_FACE_OAUTH || "false"}` === "true"
5
+ return isOAuthEnabled
6
+ }
src/lib/usePersistedOAuth.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useLocalStorage } from "usehooks-ts"
4
+ import { OAuthResult } from "@huggingface/hub"
5
+
6
+ import { defaultSettings } from "@/app/interface/settings-dialog/defaultSettings"
7
+ import { localStorageKeys } from "@/app/interface/settings-dialog/localStorageKeys"
8
+ import { getValidOAuth } from "./getValidOAuth"
9
+
10
+ export function usePersistedOAuth(): [OAuthResult | undefined, (oauthResult: OAuthResult) => void] {
11
+ const [serializedHuggingFaceOAuth, setSerializedHuggingFaceOAuth] = useLocalStorage<string>(
12
+ localStorageKeys.huggingFaceOAuth,
13
+ defaultSettings.huggingFaceOAuth
14
+ )
15
+
16
+ const oauthResult = getValidOAuth(serializedHuggingFaceOAuth)
17
+
18
+ const setOAuthResult = (oauthResult: OAuthResult) => {
19
+ setSerializedHuggingFaceOAuth(JSON.stringify(oauthResult))
20
+ }
21
+
22
+ return [oauthResult, setOAuthResult]
23
+ }
src/types.ts CHANGED
@@ -157,6 +157,7 @@ export type LayoutProps = {
157
  export type Settings = {
158
  renderingModelVendor: RenderingModelVendor
159
  renderingUseTurbo: boolean
 
160
  huggingfaceApiKey: string
161
  huggingfaceInferenceApiModel: string
162
  huggingfaceInferenceApiModelTrigger: string
 
157
  export type Settings = {
158
  renderingModelVendor: RenderingModelVendor
159
  renderingUseTurbo: boolean
160
+ huggingFaceOAuth: string
161
  huggingfaceApiKey: string
162
  huggingfaceInferenceApiModel: string
163
  huggingfaceInferenceApiModelTrigger: string