Spaces:
Sleeping
Sleeping
Julian Bilcke commited on
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 = {
|