Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Commit
•
16891a6
1
Parent(s):
0fcac01
work on HF login
Browse files- src/app/interface/hf-login/index.tsx +2 -8
- src/app/main.tsx +0 -3
- src/app/state/defaultSettings.ts +1 -0
- src/app/state/localStorageKeys.ts +1 -0
- src/app/state/userCurrentUser.ts +183 -18
- src/app/views/public-video-view/index.tsx +5 -5
- src/app/views/user-account-view/index.tsx +31 -39
- src/lib/useHuggingFaceLogin.ts +0 -75
- src/types/general.ts +1 -0
src/app/interface/hf-login/index.tsx
CHANGED
@@ -2,21 +2,15 @@
|
|
2 |
|
3 |
import { useCurrentUser } from "@/app/state/userCurrentUser"
|
4 |
import { Button } from "@/components/ui/button"
|
5 |
-
import { useHuggingFaceLogin } from "@/lib/useHuggingFaceLogin"
|
6 |
|
7 |
export function HuggingFaceLogin() {
|
8 |
|
9 |
-
const user = useCurrentUser()
|
10 |
-
const hf = useHuggingFaceLogin()
|
11 |
|
12 |
// feature is not finished yet
|
13 |
if (!user?.userName || user?.userName !== "jbilcke-hf") { return }
|
14 |
|
15 |
-
console.log("user:", user)
|
16 |
-
console.log("hf.isLoggedIn:", hf.isLoggedIn)
|
17 |
-
console.log("hf.oauthResult:", hf.oauthResult)
|
18 |
-
|
19 |
return (
|
20 |
-
<div><Button onClick={
|
21 |
)
|
22 |
}
|
|
|
2 |
|
3 |
import { useCurrentUser } from "@/app/state/userCurrentUser"
|
4 |
import { Button } from "@/components/ui/button"
|
|
|
5 |
|
6 |
export function HuggingFaceLogin() {
|
7 |
|
8 |
+
const { user, login } = useCurrentUser()
|
|
|
9 |
|
10 |
// feature is not finished yet
|
11 |
if (!user?.userName || user?.userName !== "jbilcke-hf") { return }
|
12 |
|
|
|
|
|
|
|
|
|
13 |
return (
|
14 |
+
<div><Button onClick={login}>Sign in with Hugging Face</Button></div>
|
15 |
)
|
16 |
}
|
src/app/main.tsx
CHANGED
@@ -16,8 +16,6 @@ import { PublicMusicVideosView } from "./views/public-music-videos-view"
|
|
16 |
import { getCollectionKey } from "@/lib/getCollectionKey"
|
17 |
import { PublicVideoEmbedView } from "./views/public-video-embed-view"
|
18 |
|
19 |
-
import { HuggingFaceLogin } from "./interface/hf-login"
|
20 |
-
|
21 |
// this is where we transition from the server-side space
|
22 |
// and the client-side space
|
23 |
// basically, all the views are generated in client-side space
|
@@ -140,7 +138,6 @@ export function Main({
|
|
140 |
const view = useStore(s => s.view)
|
141 |
return (
|
142 |
<TubeLayout>
|
143 |
-
<HuggingFaceLogin />
|
144 |
{view === "home" && <HomeView />}
|
145 |
{view === "public_video_embed" && <PublicVideoEmbedView />}
|
146 |
{view === "public_video" && <PublicVideoView />}
|
|
|
16 |
import { getCollectionKey } from "@/lib/getCollectionKey"
|
17 |
import { PublicVideoEmbedView } from "./views/public-video-embed-view"
|
18 |
|
|
|
|
|
19 |
// this is where we transition from the server-side space
|
20 |
// and the client-side space
|
21 |
// basically, all the views are generated in client-side space
|
|
|
138 |
const view = useStore(s => s.view)
|
139 |
return (
|
140 |
<TubeLayout>
|
|
|
141 |
{view === "home" && <HomeView />}
|
142 |
{view === "public_video_embed" && <PublicVideoEmbedView />}
|
143 |
{view === "public_video" && <PublicVideoView />}
|
src/app/state/defaultSettings.ts
CHANGED
@@ -2,4 +2,5 @@ import { Settings } from "@/types/general"
|
|
2 |
|
3 |
export const defaultSettings: Settings = {
|
4 |
huggingfaceApiKey: "",
|
|
|
5 |
}
|
|
|
2 |
|
3 |
export const defaultSettings: Settings = {
|
4 |
huggingfaceApiKey: "",
|
5 |
+
huggingfaceTemporaryApiKey: "",
|
6 |
}
|
src/app/state/localStorageKeys.ts
CHANGED
@@ -3,4 +3,5 @@ import { Settings } from "@/types/general"
|
|
3 |
export const localStorageKeys: Record<keyof Settings, string> = {
|
4 |
// important: prefix with AI_TUBE to avoid collisions when running the app on localhost
|
5 |
huggingfaceApiKey: "AI_TUBE_CONF_AUTH_HF_API_TOKEN",
|
|
|
6 |
}
|
|
|
3 |
export const localStorageKeys: Record<keyof Settings, string> = {
|
4 |
// important: prefix with AI_TUBE to avoid collisions when running the app on localhost
|
5 |
huggingfaceApiKey: "AI_TUBE_CONF_AUTH_HF_API_TOKEN",
|
6 |
+
huggingfaceTemporaryApiKey: "AI_TUBE_CONF_AUTH_TEMPORARY_HF_API_TOKEN"
|
7 |
}
|
src/app/state/userCurrentUser.ts
CHANGED
@@ -1,48 +1,213 @@
|
|
1 |
-
import { useEffect, useTransition } from "react"
|
|
|
|
|
|
|
2 |
|
3 |
import { UserInfo } from "@/types/general"
|
4 |
|
5 |
import { useStore } from "./useStore"
|
6 |
-
|
7 |
import { localStorageKeys } from "./localStorageKeys"
|
8 |
import { defaultSettings } from "./defaultSettings"
|
9 |
import { getCurrentUser } from "../server/actions/users"
|
10 |
|
11 |
-
export function useCurrentUser(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
const [_pending, startTransition] = useTransition()
|
13 |
|
14 |
-
const
|
15 |
const setCurrentUser = useStore(s => s.setCurrentUser)
|
|
|
|
|
|
|
16 |
|
17 |
-
|
|
|
|
|
18 |
localStorageKeys.huggingfaceApiKey,
|
19 |
defaultSettings.huggingfaceApiKey
|
20 |
)
|
21 |
-
useEffect(() => {
|
22 |
-
startTransition(async () => {
|
23 |
|
24 |
-
|
25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
setCurrentUser(undefined)
|
27 |
-
return
|
28 |
}
|
|
|
29 |
|
30 |
-
|
31 |
-
|
32 |
-
return
|
33 |
-
}
|
34 |
try {
|
35 |
|
36 |
const user = await getCurrentUser(huggingfaceApiKey)
|
37 |
|
38 |
setCurrentUser(user)
|
|
|
|
|
39 |
} catch (err) {
|
40 |
-
console.error("failed to log in:", err)
|
41 |
setCurrentUser(undefined)
|
42 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
})
|
44 |
-
|
45 |
-
}, [huggingfaceApiKey, currentUser?.id])
|
46 |
|
47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
}
|
|
|
1 |
+
import { useEffect, useState, useTransition } from "react"
|
2 |
+
import { OAuthResult, oauthHandleRedirectIfPresent, oauthLoginUrl } from "@huggingface/hub"
|
3 |
+
|
4 |
+
import { useLocalStorage } from "usehooks-ts"
|
5 |
|
6 |
import { UserInfo } from "@/types/general"
|
7 |
|
8 |
import { useStore } from "./useStore"
|
9 |
+
|
10 |
import { localStorageKeys } from "./localStorageKeys"
|
11 |
import { defaultSettings } from "./defaultSettings"
|
12 |
import { getCurrentUser } from "../server/actions/users"
|
13 |
|
14 |
+
export function useCurrentUser({
|
15 |
+
isLoginRequired = false
|
16 |
+
}: {
|
17 |
+
// set this to true, and the page will automatically redirect to the
|
18 |
+
// HF login page if the session is expired
|
19 |
+
isLoginRequired?: boolean
|
20 |
+
} = {}): {
|
21 |
+
user?: UserInfo
|
22 |
+
login: () => void
|
23 |
+
checkSession: (isLoginRequired: boolean) => Promise<UserInfo | undefined>
|
24 |
+
apiKey: string
|
25 |
+
oauthResult?: OAuthResult
|
26 |
+
|
27 |
+
// the long standing API is a temporary solution for "PRO" users of AI Tube
|
28 |
+
// (users who use Clap files using external tools,
|
29 |
+
// or want ot use their own HF account to generate videos)
|
30 |
+
longStandingApiKey: string
|
31 |
+
setLongStandingApiKey: (apiKey: string, loginOnFailure: boolean) => void
|
32 |
+
} {
|
33 |
const [_pending, startTransition] = useTransition()
|
34 |
|
35 |
+
const user = useStore(s => s.currentUser)
|
36 |
const setCurrentUser = useStore(s => s.setCurrentUser)
|
37 |
+
const [oauthResult, setOauthResult] = useState<OAuthResult>()
|
38 |
+
|
39 |
+
const userId = `${user?.id || ""}`
|
40 |
|
41 |
+
// this is the legacy, long-standing API key
|
42 |
+
// which is still required for long generation of Clap files
|
43 |
+
const [huggingfaceApiKey, setHuggingfaceApiKey] = useLocalStorage<string>(
|
44 |
localStorageKeys.huggingfaceApiKey,
|
45 |
defaultSettings.huggingfaceApiKey
|
46 |
)
|
|
|
|
|
47 |
|
48 |
+
// this is the new recommended API to use, with short expiration rates
|
49 |
+
// in the future this API key will be enough for all our use cases
|
50 |
+
const [huggingfaceTemporaryApiKey, setHuggingfaceTemporaryApiKey] = useLocalStorage<string>(
|
51 |
+
localStorageKeys.huggingfaceTemporaryApiKey,
|
52 |
+
defaultSettings.huggingfaceTemporaryApiKey
|
53 |
+
)
|
54 |
+
|
55 |
+
// force the API call
|
56 |
+
const checkSession = async (isLoginRequired: boolean = false): Promise<UserInfo | undefined> => {
|
57 |
+
|
58 |
+
console.log("useCurrentUser.checkSession()")
|
59 |
+
// new way: try to use the safer temporary key whenever possible
|
60 |
+
if (huggingfaceTemporaryApiKey) {
|
61 |
+
try {
|
62 |
+
|
63 |
+
const user = await getCurrentUser(huggingfaceTemporaryApiKey)
|
64 |
+
|
65 |
+
setCurrentUser(user)
|
66 |
+
|
67 |
+
return user // we stop there, no need to try the legacy key
|
68 |
+
|
69 |
+
} catch (err) {
|
70 |
+
console.error("failed to log in using the temporary key:", err)
|
71 |
setCurrentUser(undefined)
|
|
|
72 |
}
|
73 |
+
}
|
74 |
|
75 |
+
// deprecated: the old static key which is harder to renew
|
76 |
+
if (huggingfaceApiKey) {
|
|
|
|
|
77 |
try {
|
78 |
|
79 |
const user = await getCurrentUser(huggingfaceApiKey)
|
80 |
|
81 |
setCurrentUser(user)
|
82 |
+
|
83 |
+
return user
|
84 |
} catch (err) {
|
85 |
+
console.error("failed to log in using the static key:", err)
|
86 |
setCurrentUser(undefined)
|
87 |
}
|
88 |
+
}
|
89 |
+
|
90 |
+
// when we reach this stage, we know that none of the API tokens were valid
|
91 |
+
// we are given the choice to request a login or not
|
92 |
+
// (depending on if it's a secret page or not)
|
93 |
+
|
94 |
+
if (isLoginRequired) {
|
95 |
+
await login()
|
96 |
+
}
|
97 |
+
|
98 |
+
return undefined
|
99 |
+
}
|
100 |
+
|
101 |
+
// can be called many times, but won't do the API call if not necessary
|
102 |
+
const main = async (isLoginRequired: boolean) => {
|
103 |
+
|
104 |
+
console.log("useCurrentUser()")
|
105 |
+
|
106 |
+
try {
|
107 |
+
const res = await oauthHandleRedirectIfPresent()
|
108 |
+
if (res) {
|
109 |
+
console.log("useCurrentUser(): we just received an oauth login!")
|
110 |
+
setOauthResult(res)
|
111 |
+
setHuggingfaceTemporaryApiKey(res.accessToken)
|
112 |
+
await checkSession(isLoginRequired)
|
113 |
+
return
|
114 |
+
}
|
115 |
+
} catch (err) {
|
116 |
+
}
|
117 |
+
|
118 |
+
// already logged-in, no need to spend an API call
|
119 |
+
// although it is worth noting that the API token might be expired at this stage
|
120 |
+
if (userId) {
|
121 |
+
return
|
122 |
+
}
|
123 |
+
|
124 |
+
console.log("useCurrentUser(): yes, we need to call synchronizeSession()")
|
125 |
+
await checkSession(isLoginRequired)
|
126 |
+
}
|
127 |
+
|
128 |
+
useEffect(() => {
|
129 |
+
startTransition(async () => { await main(isLoginRequired) })
|
130 |
+
}, [isLoginRequired, huggingfaceApiKey, huggingfaceTemporaryApiKey, userId])
|
131 |
+
|
132 |
+
|
133 |
+
const login = async () => {
|
134 |
+
const oauthUrl = await oauthLoginUrl({
|
135 |
+
/**
|
136 |
+
* OAuth client ID.
|
137 |
+
*
|
138 |
+
* For static Spaces, you can omit this and it will be loaded from the Space config, as long as `hf_oauth: true` is present in the README.md's metadata.
|
139 |
+
* For other Spaces, it is available to the backend in the OAUTH_CLIENT_ID environment variable, as long as `hf_oauth: true` is present in the README.md's metadata.
|
140 |
+
*
|
141 |
+
* You can also create a Developer Application at https://huggingface.co/settings/connected-applications and use its client ID.
|
142 |
+
*/
|
143 |
+
clientId: process.env.NEXT_PUBLIC_AI_TUBE_OAUTH_CLIENT_ID,
|
144 |
+
|
145 |
+
// hubUrl?: string;
|
146 |
+
|
147 |
+
/**
|
148 |
+
* OAuth scope, a list of space separate scopes.
|
149 |
+
*
|
150 |
+
* For static Spaces, you can omit this and it will be loaded from the Space config, as long as `hf_oauth: true` is present in the README.md's metadata.
|
151 |
+
* For other Spaces, it is available to the backend in the OAUTH_SCOPES environment variable, as long as `hf_oauth: true` is present in the README.md's metadata.
|
152 |
+
*
|
153 |
+
* Defaults to "openid profile".
|
154 |
+
*
|
155 |
+
* You can also create a Developer Application at https://huggingface.co/settings/connected-applications and use its scopes.
|
156 |
+
*
|
157 |
+
* See https://huggingface.co/docs/hub/oauth for a list of available scopes.
|
158 |
+
*/
|
159 |
+
scopes: "openid profile",
|
160 |
+
|
161 |
+
/**
|
162 |
+
* Redirect URI, defaults to the current URL.
|
163 |
+
*
|
164 |
+
* For Spaces, any URL within the Space is allowed.
|
165 |
+
*
|
166 |
+
* For Developer Applications, you can add any URL you want to the list of allowed redirect URIs at https://huggingface.co/settings/connected-applications.
|
167 |
+
*/
|
168 |
+
redirectUrl: "https://jbilcke-hf-ai-tube.hf.space",
|
169 |
+
|
170 |
+
/**
|
171 |
+
* State to pass to the OAuth provider, which will be returned in the call to `oauthLogin` after the redirect.
|
172 |
+
*/
|
173 |
+
// state: ""
|
174 |
})
|
|
|
|
|
175 |
|
176 |
+
window.location.href = oauthUrl
|
177 |
+
}
|
178 |
+
|
179 |
+
const setLongStandingApiKey = (apiKey: string, loginOnFailure: boolean) => {
|
180 |
+
(async () => {
|
181 |
+
try {
|
182 |
+
const user = await getCurrentUser(apiKey)
|
183 |
+
setHuggingfaceApiKey(apiKey)
|
184 |
+
setCurrentUser(user)
|
185 |
+
} catch (err) {
|
186 |
+
console.error("failed to log in using the long standing key:", err)
|
187 |
+
setHuggingfaceApiKey("")
|
188 |
+
setCurrentUser(undefined)
|
189 |
+
if (loginOnFailure) {
|
190 |
+
login()
|
191 |
+
}
|
192 |
+
}
|
193 |
+
})()
|
194 |
+
}
|
195 |
+
|
196 |
+
// this may correspond to either a short or a long standing api key
|
197 |
+
// in the future it may always be a short api key with auto renewal
|
198 |
+
const apiKey = user?.hfApiToken || ""
|
199 |
+
|
200 |
+
|
201 |
+
// for now, we still need to keep track of a logn api, but this is purely optional
|
202 |
+
const longStandingApiKey = huggingfaceApiKey
|
203 |
+
|
204 |
+
return {
|
205 |
+
user,
|
206 |
+
login,
|
207 |
+
checkSession,
|
208 |
+
oauthResult,
|
209 |
+
apiKey,
|
210 |
+
longStandingApiKey,
|
211 |
+
setLongStandingApiKey,
|
212 |
+
}
|
213 |
}
|
src/app/views/public-video-view/index.tsx
CHANGED
@@ -43,14 +43,14 @@ export function PublicVideoView() {
|
|
43 |
// EDIT: you know what, let's do this the dirty way for now
|
44 |
// const [desiredCurrentTime, setDesiredCurrentTime] = useState()
|
45 |
|
46 |
-
const
|
47 |
|
48 |
const [userThumbnail, setUserThumbnail] = useState("")
|
49 |
|
50 |
useEffect(() => {
|
51 |
-
setUserThumbnail(
|
52 |
|
53 |
-
}, [
|
54 |
|
55 |
const handleBadUserThumbnail = () => {
|
56 |
if (userThumbnail) {
|
@@ -388,7 +388,7 @@ export function PublicVideoView() {
|
|
388 |
</div>
|
389 |
|
390 |
{/* COMMENT INPUT BLOCK - HORIZONTAL */}
|
391 |
-
{
|
392 |
|
393 |
{/* AVATAR */}
|
394 |
<div
|
@@ -403,7 +403,7 @@ export function PublicVideoView() {
|
|
403 |
/>
|
404 |
</div>
|
405 |
: <DefaultAvatar
|
406 |
-
username={
|
407 |
bgColor="#fde047"
|
408 |
textColor="#1c1917"
|
409 |
width={36}
|
|
|
43 |
// EDIT: you know what, let's do this the dirty way for now
|
44 |
// const [desiredCurrentTime, setDesiredCurrentTime] = useState()
|
45 |
|
46 |
+
const { user } = useCurrentUser()
|
47 |
|
48 |
const [userThumbnail, setUserThumbnail] = useState("")
|
49 |
|
50 |
useEffect(() => {
|
51 |
+
setUserThumbnail(user?.thumbnail || "")
|
52 |
|
53 |
+
}, [user?.thumbnail])
|
54 |
|
55 |
const handleBadUserThumbnail = () => {
|
56 |
if (userThumbnail) {
|
|
|
388 |
</div>
|
389 |
|
390 |
{/* COMMENT INPUT BLOCK - HORIZONTAL */}
|
391 |
+
{user && <div className="flex flex-row w-full">
|
392 |
|
393 |
{/* AVATAR */}
|
394 |
<div
|
|
|
403 |
/>
|
404 |
</div>
|
405 |
: <DefaultAvatar
|
406 |
+
username={user?.userName}
|
407 |
bgColor="#fde047"
|
408 |
textColor="#1c1917"
|
409 |
width={36}
|
src/app/views/user-account-view/index.tsx
CHANGED
@@ -1,23 +1,20 @@
|
|
1 |
"use client"
|
2 |
|
3 |
import { useEffect, useState, useTransition } from "react"
|
4 |
-
import { useLocalStorage } from "usehooks-ts"
|
5 |
|
6 |
import { useStore } from "@/app/state/useStore"
|
7 |
import { cn } from "@/lib/utils"
|
8 |
import { ChannelList } from "@/app/interface/channel-list"
|
9 |
-
import { localStorageKeys } from "@/app/state/localStorageKeys"
|
10 |
-
import { defaultSettings } from "@/app/state/defaultSettings"
|
11 |
-
import { Input } from "@/components/ui/input"
|
12 |
|
13 |
import { getPrivateChannels } from "@/app/server/actions/ai-tube-hf/getPrivateChannels"
|
|
|
|
|
|
|
14 |
|
15 |
export function UserAccountView() {
|
16 |
const [_isPending, startTransition] = useTransition()
|
17 |
-
|
18 |
-
|
19 |
-
defaultSettings.huggingfaceApiKey
|
20 |
-
)
|
21 |
const setView = useStore(s => s.setView)
|
22 |
const userChannel = useStore(s => s.userChannel)
|
23 |
const setUserChannel = useStore(s => s.setUserChannel)
|
@@ -28,10 +25,10 @@ export function UserAccountView() {
|
|
28 |
|
29 |
useEffect(() => {
|
30 |
startTransition(async () => {
|
31 |
-
if (!isLoaded &&
|
32 |
try {
|
33 |
const newUserChannels = await getPrivateChannels({
|
34 |
-
apiKey
|
35 |
renewCache: true,
|
36 |
})
|
37 |
setUserChannels(newUserChannels)
|
@@ -43,36 +40,32 @@ export function UserAccountView() {
|
|
43 |
}
|
44 |
}
|
45 |
})
|
46 |
-
}, [isLoaded,
|
47 |
-
|
48 |
return (
|
49 |
<div className={cn(`flex flex-col space-y-4`)}>
|
50 |
-
<h2 className="text-3xl font-bold">Want your own channels? Setup your account!</h2>
|
51 |
-
|
52 |
-
<div className="flex flex-col space-y-4 max-w-2xl">
|
53 |
-
<div className="flex flex-row space-x-2 items-center">
|
54 |
-
<label className="flex w-64">Hugging Face token:</label>
|
55 |
-
<Input
|
56 |
-
placeholder="Hugging Face token (with WRITE access)"
|
57 |
-
type="password"
|
58 |
-
className="font-mono"
|
59 |
-
onChange={(x) => {
|
60 |
-
setHuggingfaceApiKey(x.target.value)
|
61 |
-
}}
|
62 |
-
value={huggingfaceApiKey}
|
63 |
-
/>
|
64 |
-
</div>
|
65 |
-
<p className="text-neutral-100/70">
|
66 |
-
Note: your Hugging Face token must be a <span className="font-bold font-mono text-yellow-300">WRITE</span> access token.
|
67 |
-
</p>
|
68 |
-
{huggingfaceApiKey
|
69 |
-
? <p className="">Nice, looks like you are ready to go!</p>
|
70 |
-
: <p>Please setup your account (see above) to get started</p>}
|
71 |
-
</div>
|
72 |
|
73 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
<div className="flex flex-col space-y-4">
|
75 |
-
<h2 className="text-3xl font-bold"
|
|
|
|
|
76 |
{userChannels?.length ? <ChannelList
|
77 |
layout="grid"
|
78 |
channels={[
|
@@ -88,9 +81,8 @@ export function UserAccountView() {
|
|
88 |
setView("user_channel")
|
89 |
}}
|
90 |
/>
|
91 |
-
: isLoaded ?
|
92 |
-
</div> :
|
93 |
-
|
94 |
</div>
|
95 |
)
|
96 |
}
|
|
|
1 |
"use client"
|
2 |
|
3 |
import { useEffect, useState, useTransition } from "react"
|
|
|
4 |
|
5 |
import { useStore } from "@/app/state/useStore"
|
6 |
import { cn } from "@/lib/utils"
|
7 |
import { ChannelList } from "@/app/interface/channel-list"
|
|
|
|
|
|
|
8 |
|
9 |
import { getPrivateChannels } from "@/app/server/actions/ai-tube-hf/getPrivateChannels"
|
10 |
+
import { useCurrentUser } from "@/app/state/userCurrentUser"
|
11 |
+
import { Button } from "@/components/ui/button"
|
12 |
+
import { Input } from "@/components/ui/input"
|
13 |
|
14 |
export function UserAccountView() {
|
15 |
const [_isPending, startTransition] = useTransition()
|
16 |
+
|
17 |
+
const { user, login, apiKey, longStandingApiKey, setLongStandingApiKey } = useCurrentUser({ isLoginRequired: true })
|
|
|
|
|
18 |
const setView = useStore(s => s.setView)
|
19 |
const userChannel = useStore(s => s.userChannel)
|
20 |
const setUserChannel = useStore(s => s.setUserChannel)
|
|
|
25 |
|
26 |
useEffect(() => {
|
27 |
startTransition(async () => {
|
28 |
+
if (!isLoaded && apiKey) {
|
29 |
try {
|
30 |
const newUserChannels = await getPrivateChannels({
|
31 |
+
apiKey,
|
32 |
renewCache: true,
|
33 |
})
|
34 |
setUserChannels(newUserChannels)
|
|
|
40 |
}
|
41 |
}
|
42 |
})
|
43 |
+
}, [isLoaded, apiKey, userChannels.map(c => c.id).join(","), setUserChannels, setLoaded])
|
44 |
+
|
45 |
return (
|
46 |
<div className={cn(`flex flex-col space-y-4`)}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
47 |
|
48 |
+
{
|
49 |
+
// this is a beta feature, only necessary for users who use Clap files
|
50 |
+
user?.userName.startsWith("jbilcke")
|
51 |
+
? <div className="flex flex-row space-x-2 items-center">
|
52 |
+
<label className="flex w-64">Save videos to my HF account</label>
|
53 |
+
<Input
|
54 |
+
placeholder="Hugging Face token (with WRITE access)"
|
55 |
+
type="password"
|
56 |
+
className="font-mono"
|
57 |
+
onChange={(x) => {
|
58 |
+
setLongStandingApiKey(x.target.value, false)
|
59 |
+
}}
|
60 |
+
value={longStandingApiKey}
|
61 |
+
/>
|
62 |
+
</div> : null}
|
63 |
+
|
64 |
+
{apiKey ?
|
65 |
<div className="flex flex-col space-y-4">
|
66 |
+
<h2 className="text-3xl font-bold">@{user?.userName} channels</h2>
|
67 |
+
<p>Don't see your channel? try to <Button onClick={login}>synchronize</Button> again.</p>
|
68 |
+
|
69 |
{userChannels?.length ? <ChannelList
|
70 |
layout="grid"
|
71 |
channels={[
|
|
|
81 |
setView("user_channel")
|
82 |
}}
|
83 |
/>
|
84 |
+
: isLoaded ? null : <p>Loading channels owned by @{user?.userName}..</p>}
|
85 |
+
</div> : <p>To create a channel, comment or like a video please <Button onClick={login}>Login with Hugging Face</Button>.</p>}
|
|
|
86 |
</div>
|
87 |
)
|
88 |
}
|
src/lib/useHuggingFaceLogin.ts
DELETED
@@ -1,75 +0,0 @@
|
|
1 |
-
import { useEffect, useState } from "react"
|
2 |
-
|
3 |
-
import { oauthHandleRedirectIfPresent, oauthLoginUrl } from "@huggingface/hub"
|
4 |
-
|
5 |
-
export function useHuggingFaceLogin(onLogin?: (data: any) => void) {
|
6 |
-
const [isLoggedIn, setLoggedIn] = useState(false)
|
7 |
-
const [oauthResult, setOauthResult] = useState<any>({})
|
8 |
-
|
9 |
-
useEffect(() => {
|
10 |
-
const fn = async () => {
|
11 |
-
const res = await oauthHandleRedirectIfPresent()
|
12 |
-
if (res) {
|
13 |
-
setOauthResult(res)
|
14 |
-
setLoggedIn(true)
|
15 |
-
onLogin?.(res)
|
16 |
-
}
|
17 |
-
}
|
18 |
-
fn()
|
19 |
-
}, [])
|
20 |
-
|
21 |
-
const login = async () => {
|
22 |
-
const oauthUrl = await oauthLoginUrl({
|
23 |
-
/**
|
24 |
-
* OAuth client ID.
|
25 |
-
*
|
26 |
-
* For static Spaces, you can omit this and it will be loaded from the Space config, as long as `hf_oauth: true` is present in the README.md's metadata.
|
27 |
-
* For other Spaces, it is available to the backend in the OAUTH_CLIENT_ID environment variable, as long as `hf_oauth: true` is present in the README.md's metadata.
|
28 |
-
*
|
29 |
-
* You can also create a Developer Application at https://huggingface.co/settings/connected-applications and use its client ID.
|
30 |
-
*/
|
31 |
-
clientId: process.env.NEXT_PUBLIC_AI_TUBE_OAUTH_CLIENT_ID,
|
32 |
-
|
33 |
-
// hubUrl?: string;
|
34 |
-
|
35 |
-
/**
|
36 |
-
* OAuth scope, a list of space separate scopes.
|
37 |
-
*
|
38 |
-
* For static Spaces, you can omit this and it will be loaded from the Space config, as long as `hf_oauth: true` is present in the README.md's metadata.
|
39 |
-
* For other Spaces, it is available to the backend in the OAUTH_SCOPES environment variable, as long as `hf_oauth: true` is present in the README.md's metadata.
|
40 |
-
*
|
41 |
-
* Defaults to "openid profile".
|
42 |
-
*
|
43 |
-
* You can also create a Developer Application at https://huggingface.co/settings/connected-applications and use its scopes.
|
44 |
-
*
|
45 |
-
* See https://huggingface.co/docs/hub/oauth for a list of available scopes.
|
46 |
-
*/
|
47 |
-
scopes: "openid profile",
|
48 |
-
|
49 |
-
/**
|
50 |
-
* Redirect URI, defaults to the current URL.
|
51 |
-
*
|
52 |
-
* For Spaces, any URL within the Space is allowed.
|
53 |
-
*
|
54 |
-
* For Developer Applications, you can add any URL you want to the list of allowed redirect URIs at https://huggingface.co/settings/connected-applications.
|
55 |
-
*/
|
56 |
-
redirectUrl: "https://jbilcke-hf-ai-tube.hf.space",
|
57 |
-
|
58 |
-
/**
|
59 |
-
* State to pass to the OAuth provider, which will be returned in the call to `oauthLogin` after the redirect.
|
60 |
-
*/
|
61 |
-
// state: ""
|
62 |
-
})
|
63 |
-
|
64 |
-
console.log("oauthUrl:", oauthUrl)
|
65 |
-
window.location.href = oauthUrl
|
66 |
-
}
|
67 |
-
|
68 |
-
console.log(JSON.stringify(oauthResult, null, 2))
|
69 |
-
|
70 |
-
return {
|
71 |
-
isLoggedIn,
|
72 |
-
login,
|
73 |
-
oauthResult,
|
74 |
-
}
|
75 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/types/general.ts
CHANGED
@@ -635,6 +635,7 @@ export type InterfaceView =
|
|
635 |
|
636 |
export type Settings = {
|
637 |
huggingfaceApiKey: string
|
|
|
638 |
}
|
639 |
|
640 |
export type ParsedDatasetReadme = {
|
|
|
635 |
|
636 |
export type Settings = {
|
637 |
huggingfaceApiKey: string
|
638 |
+
huggingfaceTemporaryApiKey: string
|
639 |
}
|
640 |
|
641 |
export type ParsedDatasetReadme = {
|