jbilcke-hf HF staff commited on
Commit
a3f1817
1 Parent(s): b710c3a

renaming to avoid confusion

Browse files
src/app/account/channel/page.tsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import { Main } from "../../main"
2
+
3
+ export default async function AccountChannelPage() {
4
+ return (<Main />)
5
+ }
src/app/channel/page.tsx CHANGED
@@ -1,7 +1,8 @@
 
1
  import { Main } from "../main"
2
  import { getChannel } from "../server/actions/ai-tube-hf/getChannel"
3
 
4
- export default async function ChannelPage() {
5
- // const channel = await getChannel({ channelId, neverThrow: true })
6
- return (<Main />)
7
  }
 
1
+ import { AppQueryProps } from "@/types"
2
  import { Main } from "../main"
3
  import { getChannel } from "../server/actions/ai-tube-hf/getChannel"
4
 
5
+ export default async function ChannelPage({ searchParams: { c: channelId } }: AppQueryProps) {
6
+ const channel = await getChannel({ channelId, neverThrow: true })
7
+ return (<Main channel={channel} />)
8
  }
src/app/interface/left-menu/index.tsx CHANGED
@@ -13,10 +13,6 @@ import Link from "next/link"
13
 
14
  export function LeftMenu() {
15
  const view = useStore(s => s.view)
16
- const setView = useStore(s => s.setView)
17
- const menuMode = useStore(s => s.menuMode)
18
- const setMenuMode = useStore(s => s.setMenuMode)
19
- const setCurrentVideo = useStore(s => s.setCurrentVideo)
20
 
21
  return (
22
  <div className={cn(
@@ -63,7 +59,7 @@ export function LeftMenu() {
63
  <Link href="/account">
64
  <MenuItem
65
  icon={<CgProfile className="h-6 w-6" />}
66
- selected={view === "user_channels"}
67
  >
68
  Account
69
  </MenuItem>
 
13
 
14
  export function LeftMenu() {
15
  const view = useStore(s => s.view)
 
 
 
 
16
 
17
  return (
18
  <div className={cn(
 
59
  <Link href="/account">
60
  <MenuItem
61
  icon={<CgProfile className="h-6 w-6" />}
62
+ selected={view === "user_account" || view === "user_channel"}
63
  >
64
  Account
65
  </MenuItem>
src/app/interface/top-header/index.tsx CHANGED
@@ -10,7 +10,6 @@ const pathway = Pathway_Gothic_One({
10
  display: "swap"
11
  })
12
 
13
- import { videoCategoriesWithLabels } from "@/app/state/categories"
14
  import { useStore } from "@/app/state/useStore"
15
  import { cn } from "@/lib/utils"
16
  import { getTags } from '@/app/server/actions/ai-tube-hf/getTags'
@@ -27,13 +26,8 @@ export function TopHeader() {
27
 
28
  const setMenuMode = useStore(s => s.setMenuMode)
29
 
30
-
31
- const currentChannel = useStore(s => s.currentChannel)
32
- const setCurrentChannel = useStore(s => s.setCurrentChannel)
33
  const currentTag = useStore(s => s.currentTag)
34
  const setCurrentTag = useStore(s => s.setCurrentTag)
35
- const currentVideos = useStore(s => s.currentVideos)
36
- const currentVideo = useStore(s => s.currentVideo)
37
 
38
  const currentTags = useStore(s => s.currentTags)
39
  const setCurrentTags = useStore(s => s.setCurrentTags)
 
10
  display: "swap"
11
  })
12
 
 
13
  import { useStore } from "@/app/state/useStore"
14
  import { cn } from "@/lib/utils"
15
  import { getTags } from '@/app/server/actions/ai-tube-hf/getTags'
 
26
 
27
  const setMenuMode = useStore(s => s.setMenuMode)
28
 
 
 
 
29
  const currentTag = useStore(s => s.currentTag)
30
  const setCurrentTag = useStore(s => s.setCurrentTag)
 
 
31
 
32
  const currentTags = useStore(s => s.currentTags)
33
  const setCurrentTags = useStore(s => s.setCurrentTags)
src/app/main.tsx CHANGED
@@ -8,7 +8,7 @@ import { UserChannelView } from "./views/user-channel-view"
8
  import { PublicVideoView } from "./views/public-video-view"
9
  import { UserAccountView } from "./views/user-account-view"
10
  import { NotFoundView } from "./views/not-found-view"
11
- import { VideoInfo } from "@/types"
12
  import { useEffect } from "react"
13
  import { usePathname, useRouter } from "next/navigation"
14
  import { TubeLayout } from "./interface/tube-layout"
@@ -22,15 +22,17 @@ import { TubeLayout } from "./interface/tube-layout"
22
  // one benefit of doing this is that we will able to add some animations/transitions
23
  // more easily
24
  export function Main({
25
- video
 
26
  }: {
27
  // server side params
28
  video?: VideoInfo
 
29
  }) {
30
  const pathname = usePathname()
31
  const router = useRouter()
32
 
33
- const setCurrentVideo = useStore(s => s.setCurrentVideo)
34
  const setView = useStore(s => s.setView)
35
  const setPathname = useStore(s => s.setPathname)
36
 
@@ -40,7 +42,7 @@ export function Main({
40
  useEffect(() => {
41
  // note: it is important to ALWAYS set the current video to videoId
42
  // even if it's undefined
43
- setCurrentVideo(video)
44
 
45
  if (videoId) {
46
  // this is a hack for hugging face:
 
8
  import { PublicVideoView } from "./views/public-video-view"
9
  import { UserAccountView } from "./views/user-account-view"
10
  import { NotFoundView } from "./views/not-found-view"
11
+ import { ChannelInfo, VideoInfo } from "@/types"
12
  import { useEffect } from "react"
13
  import { usePathname, useRouter } from "next/navigation"
14
  import { TubeLayout } from "./interface/tube-layout"
 
22
  // one benefit of doing this is that we will able to add some animations/transitions
23
  // more easily
24
  export function Main({
25
+ video,
26
+ channel,
27
  }: {
28
  // server side params
29
  video?: VideoInfo
30
+ channel?: ChannelInfo
31
  }) {
32
  const pathname = usePathname()
33
  const router = useRouter()
34
 
35
+ const setPublicVideo = useStore(s => s.setPublicVideo)
36
  const setView = useStore(s => s.setView)
37
  const setPathname = useStore(s => s.setPathname)
38
 
 
42
  useEffect(() => {
43
  // note: it is important to ALWAYS set the current video to videoId
44
  // even if it's undefined
45
+ setPublicVideo(video)
46
 
47
  if (videoId) {
48
  // this is a hack for hugging face:
src/app/server/actions/ai-tube-hf/getChannel.ts CHANGED
@@ -1,131 +1,40 @@
1
  "use server"
2
 
3
- import { Credentials, downloadFile, whoAmI } from "@/huggingface/hub/src"
4
- import { parseDatasetReadme } from "@/app/server/actions/utils/parseDatasetReadme"
5
  import { ChannelInfo } from "@/types"
6
 
7
- import { adminCredentials } from "../config"
8
 
9
  export async function getChannel(options: {
10
- id: string
11
- name: string
12
- likes: number
13
- updatedAt: Date
14
  apiKey?: string
15
  owner?: string
16
  renewCache?: boolean
17
- }): Promise<ChannelInfo> {
18
- // console.log("getChannels")
19
- let credentials: Credentials = adminCredentials
20
- let owner = options.owner
21
-
22
- if (options.apiKey) {
23
- try {
24
- credentials = { accessToken: options.apiKey }
25
- const { name: username } = await whoAmI({ credentials })
26
- if (!username) {
27
- throw new Error(`couldn't get the username`)
28
- }
29
- // everything is in order,
30
- owner = username
31
- } catch (err) {
32
- console.error(err)
33
- throw err
34
  }
35
- }
36
-
37
-
38
- const prefix = "ai-tube-"
39
-
40
- const name = options.name
41
-
42
- const chunks = name.split("/")
43
- const [datasetUser, datasetName] = chunks.length === 2
44
- ? chunks
45
- : [name, name]
46
-
47
- // console.log(`found a candidate dataset "${datasetName}" owned by @${datasetUser}`)
48
-
49
- // ignore channels which don't start with ai-tube
50
- if (!datasetName.startsWith(prefix)) {
51
- throw new Error("this is not an AI Tube channel")
52
- }
53
-
54
- // ignore the video index
55
- if (datasetName === "ai-tube-index") {
56
- throw new Error("cannot get channel of ai-tube-index: time-space continuum broken!")
57
- }
58
-
59
- const slug = datasetName.replaceAll(prefix, "")
60
-
61
- // console.log(`found an AI Tube channel: "${slug}"`)
62
-
63
- // TODO parse the README to get the proper label
64
- let label = slug.replaceAll("-", " ")
65
 
66
- let model = ""
67
- let lora = ""
68
- let style = ""
69
- let thumbnail = ""
70
- let prompt = ""
71
- let description = ""
72
- let voice = ""
73
- let tags: string[] = []
74
-
75
- // console.log(`going to read datasets/${name}`)
76
- try {
77
- const response = await downloadFile({
78
- repo: `datasets/${name}`,
79
- path: "README.md",
80
- credentials
81
  })
82
- const readme = await response?.text()
83
-
84
- const parsedDatasetReadme = parseDatasetReadme(readme)
85
-
86
- // console.log("parsedDatasetReadme: ", parsedDatasetReadme)
87
 
88
- prompt = parsedDatasetReadme.prompt
89
- label = parsedDatasetReadme.pretty_name
90
- description = parsedDatasetReadme.description
91
- thumbnail = parsedDatasetReadme.thumbnail || "thumbnail.jpg"
92
- model = parsedDatasetReadme.model || ""
93
- lora = parsedDatasetReadme.lora || ""
94
- style = parsedDatasetReadme.style || ""
95
- voice = parsedDatasetReadme.voice || ""
96
-
97
- thumbnail =
98
- thumbnail.startsWith("http")
99
- ? thumbnail
100
- : (thumbnail.endsWith(".jpg") || thumbnail.endsWith(".jpeg"))
101
- ? `https://huggingface.co/datasets/${name}/resolve/main/${thumbnail}`
102
- : ""
103
 
104
- tags = parsedDatasetReadme.tags
105
- .map(tag => tag.trim()) // clean them up
106
- .filter(tag => tag) // remove empty tags
107
-
108
  } catch (err) {
109
- // console.log("failed to read the readme:", err)
110
- }
 
111
 
112
- const channel: ChannelInfo = {
113
- id: options.id,
114
- datasetUser,
115
- datasetName,
116
- slug,
117
- label,
118
- description,
119
- model,
120
- lora,
121
- style,
122
- voice,
123
- thumbnail,
124
- prompt,
125
- likes: options.likes,
126
- tags,
127
- updatedAt: options.updatedAt.toISOString()
128
  }
129
-
130
- return channel
131
  }
 
1
  "use server"
2
 
3
+
 
4
  import { ChannelInfo } from "@/types"
5
 
6
+ import { getChannels } from "./getChannels"
7
 
8
  export async function getChannel(options: {
9
+ channelId?: string | string[] | null
 
 
 
10
  apiKey?: string
11
  owner?: string
12
  renewCache?: boolean
13
+ neverThrow?: boolean
14
+ } = {}): Promise<ChannelInfo | undefined> {
15
+ try {
16
+ const id = `${options?.channelId || ""}`
17
+ if (!id) {
18
+ throw new Error(`invalid channel id: "${id}"`)
 
 
 
 
 
 
 
 
 
 
 
19
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
+ const channels = await getChannels({
22
+ channelId: id,
23
+ apiKey: options?.apiKey,
24
+ owner: options?.owner,
25
+ renewCache: options.renewCache,
 
 
 
 
 
 
 
 
 
 
26
  })
 
 
 
 
 
27
 
28
+ if (channels.length === 1) {
29
+ return channels[0]
30
+ }
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ throw new Error(`couldn't find channel ${options.channelId}`)
 
 
 
33
  } catch (err) {
34
+ if (options.neverThrow) {
35
+ return undefined
36
+ }
37
 
38
+ throw err
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  }
 
 
40
  }
src/app/server/actions/ai-tube-hf/getChannelVideos.ts ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import { ChannelInfo, VideoInfo, VideoStatus } from "@/types"
4
+
5
+ import { getVideoRequestsFromChannel } from "./getVideoRequestsFromChannel"
6
+ import { adminApiKey } from "../config"
7
+ import { getIndex } from "./getIndex"
8
+
9
+ // return
10
+ export async function getChannelVideos({
11
+ channel,
12
+ status,
13
+ }: {
14
+ channel: ChannelInfo
15
+
16
+ // filter videos by status
17
+ status?: VideoStatus
18
+ }): Promise<VideoInfo[]> {
19
+
20
+ const videos = await getVideoRequestsFromChannel({
21
+ channel,
22
+ apiKey: adminApiKey,
23
+ renewCache: true
24
+ })
25
+
26
+ // TODO: use a database instead
27
+ // normally
28
+ const queued = await getIndex({ status: "queued" })
29
+ const published = await getIndex({ status: "published" })
30
+
31
+ return videos.map(v => {
32
+ let video: VideoInfo = {
33
+ id: v.id,
34
+ status: "submitted",
35
+ label: v.label,
36
+ description: v.description,
37
+ prompt: v.prompt,
38
+ thumbnailUrl: v.thumbnailUrl,
39
+ assetUrl: "",
40
+ numberOfViews: 0,
41
+ numberOfLikes: 0,
42
+ updatedAt: v.updatedAt,
43
+ tags: v.tags,
44
+ channel,
45
+ }
46
+
47
+ if (queued[v.id]) {
48
+ video = queued[v.id]
49
+ } else if (published[v.id]) {
50
+ video = published[v.id]
51
+ }
52
+
53
+ return video
54
+ }).filter(video => {
55
+ if (!status || typeof status === "undefined") {
56
+ return true
57
+ }
58
+
59
+ return video.status === status
60
+ })
61
+ }
src/app/server/actions/ai-tube-hf/getChannels.ts CHANGED
@@ -4,9 +4,10 @@ import { Credentials, listDatasets, whoAmI } from "@/huggingface/hub/src"
4
  import { ChannelInfo } from "@/types"
5
 
6
  import { adminCredentials } from "../config"
7
- import { getChannel } from "./getChannel"
8
 
9
  export async function getChannels(options: {
 
10
  apiKey?: string
11
  owner?: string
12
  renewCache?: boolean
@@ -38,7 +39,8 @@ export async function getChannels(options: {
38
  ? { owner } // search channels of a specific user
39
  : prefix // global search (note: might be costly?)
40
 
41
- console.log("search:", search)
 
42
  for await (const { id, name, likes, updatedAt } of listDatasets({
43
  search,
44
  credentials,
@@ -46,6 +48,9 @@ export async function getChannels(options: {
46
  ? { cache: "no-store" }
47
  : undefined
48
  })) {
 
 
 
49
 
50
  // TODO: need to handle better cases where the username is missing
51
 
@@ -66,12 +71,16 @@ export async function getChannels(options: {
66
  continue
67
  }
68
 
69
- const channel = await getChannel({
70
  ...options,
71
  id, name, likes, updatedAt
72
  })
73
 
74
  channels.push(channel)
 
 
 
 
75
  }
76
 
77
  return channels
 
4
  import { ChannelInfo } from "@/types"
5
 
6
  import { adminCredentials } from "../config"
7
+ import { parseChannel } from "./parseChannel"
8
 
9
  export async function getChannels(options: {
10
+ channelId?: string
11
  apiKey?: string
12
  owner?: string
13
  renewCache?: boolean
 
39
  ? { owner } // search channels of a specific user
40
  : prefix // global search (note: might be costly?)
41
 
42
+ // console.log("search:", search)
43
+
44
  for await (const { id, name, likes, updatedAt } of listDatasets({
45
  search,
46
  credentials,
 
48
  ? { cache: "no-store" }
49
  : undefined
50
  })) {
51
+ if (options.channelId && options.channelId !== id) {
52
+ continue
53
+ }
54
 
55
  // TODO: need to handle better cases where the username is missing
56
 
 
71
  continue
72
  }
73
 
74
+ const channel = await parseChannel({
75
  ...options,
76
  id, name, likes, updatedAt
77
  })
78
 
79
  channels.push(channel)
80
+
81
+ if (options.channelId && options.channelId === id) {
82
+ break
83
+ }
84
  }
85
 
86
  return channels
src/app/server/actions/ai-tube-hf/parseChannel.ts ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import { Credentials, downloadFile, whoAmI } from "@/huggingface/hub/src"
4
+ import { parseDatasetReadme } from "@/app/server/actions/utils/parseDatasetReadme"
5
+ import { ChannelInfo } from "@/types"
6
+
7
+ import { adminCredentials } from "../config"
8
+
9
+ export async function parseChannel(options: {
10
+ id: string
11
+ name: string
12
+ likes: number
13
+ updatedAt: Date
14
+ apiKey?: string
15
+ owner?: string
16
+ renewCache?: boolean
17
+ }): Promise<ChannelInfo> {
18
+ // console.log("getChannels")
19
+ let credentials: Credentials = adminCredentials
20
+ let owner = options.owner
21
+
22
+ if (options.apiKey) {
23
+ try {
24
+ credentials = { accessToken: options.apiKey }
25
+ const { name: username } = await whoAmI({ credentials })
26
+ if (!username) {
27
+ throw new Error(`couldn't get the username`)
28
+ }
29
+ // everything is in order,
30
+ owner = username
31
+ } catch (err) {
32
+ console.error(err)
33
+ throw err
34
+ }
35
+ }
36
+
37
+ const prefix = "ai-tube-"
38
+
39
+ const name = options.name
40
+
41
+ const chunks = name.split("/")
42
+ const [datasetUser, datasetName] = chunks.length === 2
43
+ ? chunks
44
+ : [name, name]
45
+
46
+ // console.log(`found a candidate dataset "${datasetName}" owned by @${datasetUser}`)
47
+
48
+ // ignore channels which don't start with ai-tube
49
+ if (!datasetName.startsWith(prefix)) {
50
+ throw new Error("this is not an AI Tube channel")
51
+ }
52
+
53
+ // ignore the video index
54
+ if (datasetName === "ai-tube-index") {
55
+ throw new Error("cannot get channel of ai-tube-index: time-space continuum broken!")
56
+ }
57
+
58
+ const slug = datasetName.replaceAll(prefix, "")
59
+
60
+ // console.log(`found an AI Tube channel: "${slug}"`)
61
+
62
+ // TODO parse the README to get the proper label
63
+ let label = slug.replaceAll("-", " ")
64
+
65
+ let model = ""
66
+ let lora = ""
67
+ let style = ""
68
+ let thumbnail = ""
69
+ let prompt = ""
70
+ let description = ""
71
+ let voice = ""
72
+ let tags: string[] = []
73
+
74
+ // console.log(`going to read datasets/${name}`)
75
+ try {
76
+ const response = await downloadFile({
77
+ repo: `datasets/${name}`,
78
+ path: "README.md",
79
+ credentials
80
+ })
81
+ const readme = await response?.text()
82
+
83
+ const parsedDatasetReadme = parseDatasetReadme(readme)
84
+
85
+ // console.log("parsedDatasetReadme: ", parsedDatasetReadme)
86
+
87
+ prompt = parsedDatasetReadme.prompt
88
+ label = parsedDatasetReadme.pretty_name
89
+ description = parsedDatasetReadme.description
90
+ thumbnail = parsedDatasetReadme.thumbnail || "thumbnail.jpg"
91
+ model = parsedDatasetReadme.model || ""
92
+ lora = parsedDatasetReadme.lora || ""
93
+ style = parsedDatasetReadme.style || ""
94
+ voice = parsedDatasetReadme.voice || ""
95
+
96
+ thumbnail =
97
+ thumbnail.startsWith("http")
98
+ ? thumbnail
99
+ : (thumbnail.endsWith(".jpg") || thumbnail.endsWith(".jpeg"))
100
+ ? `https://huggingface.co/datasets/${name}/resolve/main/${thumbnail}`
101
+ : ""
102
+
103
+ tags = parsedDatasetReadme.tags
104
+ .map(tag => tag.trim()) // clean them up
105
+ .filter(tag => tag) // remove empty tags
106
+
107
+ } catch (err) {
108
+ // console.log("failed to read the readme:", err)
109
+ }
110
+
111
+ const channel: ChannelInfo = {
112
+ id: options.id,
113
+ datasetUser,
114
+ datasetName,
115
+ slug,
116
+ label,
117
+ description,
118
+ model,
119
+ lora,
120
+ style,
121
+ voice,
122
+ thumbnail,
123
+ prompt,
124
+ likes: options.likes,
125
+ tags,
126
+ updatedAt: options.updatedAt.toISOString()
127
+ }
128
+
129
+ return channel
130
+ }
src/app/state/useStore.ts CHANGED
@@ -19,11 +19,17 @@ export const useStore = create<{
19
 
20
  setPathname: (patname: string) => void
21
 
22
- currentChannel?: ChannelInfo
23
- setCurrentChannel: (currentChannel?: ChannelInfo) => void
24
 
25
- currentChannels: ChannelInfo[]
26
- setCurrentChannels: (currentChannels?: ChannelInfo[]) => void
 
 
 
 
 
 
27
 
28
  currentTag?: string
29
  setCurrentTag: (currentTag?: string) => void
@@ -37,11 +43,17 @@ export const useStore = create<{
37
  currentModel?: string
38
  setCurrentModel: (currentModel?: string) => void
39
 
40
- currentVideos: VideoInfo[]
41
- setCurrentVideos: (currentVideos: VideoInfo[]) => void
 
 
 
 
 
 
42
 
43
- currentVideo?: VideoInfo
44
- setCurrentVideo: (currentVideo?: VideoInfo) => void
45
 
46
  // currentPrompts: VideoInfo[]
47
  // setCurrentPrompts: (currentPrompts: VideoInfo[]) => void
@@ -62,7 +74,9 @@ export const useStore = create<{
62
  "/": "home",
63
  "/watch": "public_video",
64
  "/channels": "public_channels",
65
- "/account": "user_account"
 
 
66
  }
67
 
68
  set({ view: routes[pathname] || "not_found" })
@@ -78,16 +92,28 @@ export const useStore = create<{
78
  set({ menuMode })
79
  },
80
 
81
- currentChannel: undefined,
82
- setCurrentChannel: (currentChannel?: ChannelInfo) => {
 
 
 
 
 
 
 
 
 
 
 
 
83
  // TODO: download videos for this new channel
84
- set({ currentChannel })
85
  },
86
 
87
- currentChannels: [],
88
- setCurrentChannels: (currentChannels: ChannelInfo[] = []) => {
89
  // TODO: download videos for this new channel
90
- set({ currentChannels: Array.isArray(currentChannels) ? currentChannels : [] })
91
  },
92
 
93
  currentTag: undefined,
@@ -110,13 +136,25 @@ export const useStore = create<{
110
  set({ currentModel })
111
  },
112
 
113
- currentVideos: [],
114
- setCurrentVideos: (currentVideos: VideoInfo[] = []) => {
 
 
 
 
 
115
  set({
116
- currentVideos: Array.isArray(currentVideos) ? currentVideos : []
117
  })
118
  },
119
 
120
- currentVideo: undefined,
121
- setCurrentVideo: (currentVideo?: VideoInfo) => { set({ currentVideo }) },
 
 
 
 
 
 
 
122
  }))
 
19
 
20
  setPathname: (patname: string) => void
21
 
22
+ publicChannel?: ChannelInfo
23
+ setPublicChannel: (setPublicChannel?: ChannelInfo) => void
24
 
25
+ publicChannels: ChannelInfo[]
26
+ setPublicChannels: (publicChannels?: ChannelInfo[]) => void
27
+
28
+ userChannel?: ChannelInfo
29
+ setUserChannel: (userChannel?: ChannelInfo) => void
30
+
31
+ userChannels: ChannelInfo[]
32
+ setUserChannels: (userChannels?: ChannelInfo[]) => void
33
 
34
  currentTag?: string
35
  setCurrentTag: (currentTag?: string) => void
 
43
  currentModel?: string
44
  setCurrentModel: (currentModel?: string) => void
45
 
46
+ publicVideo?: VideoInfo
47
+ setPublicVideo: (publicVideo?: VideoInfo) => void
48
+
49
+ publicVideos: VideoInfo[]
50
+ setPublicVideos: (userVideos: VideoInfo[]) => void
51
+
52
+ userVideo?: VideoInfo
53
+ setUserVideo: (userVideo?: VideoInfo) => void
54
 
55
+ userVideos: VideoInfo[]
56
+ setUserVideos: (userVideos: VideoInfo[]) => void
57
 
58
  // currentPrompts: VideoInfo[]
59
  // setCurrentPrompts: (currentPrompts: VideoInfo[]) => void
 
74
  "/": "home",
75
  "/watch": "public_video",
76
  "/channels": "public_channels",
77
+ "/channel": "public_channel",
78
+ "/account": "user_account",
79
+ "/account/channel": "user_channel",
80
  }
81
 
82
  set({ view: routes[pathname] || "not_found" })
 
92
  set({ menuMode })
93
  },
94
 
95
+ publicChannel: undefined,
96
+ setPublicChannel: (publicChannel?: ChannelInfo) => {
97
+ // TODO: download videos for this new channel
98
+ set({ publicChannel })
99
+ },
100
+
101
+ publicChannels: [],
102
+ setPublicChannels: (publicChannels: ChannelInfo[] = []) => {
103
+ // TODO: download videos for this new channel
104
+ set({ publicChannels: Array.isArray(publicChannels) ? publicChannels : [] })
105
+ },
106
+
107
+ userChannel: undefined,
108
+ setUserChannel: (userChannel?: ChannelInfo) => {
109
  // TODO: download videos for this new channel
110
+ set({ userChannel })
111
  },
112
 
113
+ userChannels: [],
114
+ setUserChannels: (userChannels: ChannelInfo[] = []) => {
115
  // TODO: download videos for this new channel
116
+ set({ userChannels: Array.isArray(userChannels) ? userChannels : [] })
117
  },
118
 
119
  currentTag: undefined,
 
136
  set({ currentModel })
137
  },
138
 
139
+ publicVideo: undefined,
140
+ setPublicVideo: (publicVideo?: VideoInfo) => {
141
+ set({ publicVideo })
142
+ },
143
+
144
+ publicVideos: [],
145
+ setPublicVideos: (publicVideos: VideoInfo[] = []) => {
146
  set({
147
+ publicVideos: Array.isArray(publicVideos) ? publicVideos : []
148
  })
149
  },
150
 
151
+ userVideo: undefined,
152
+ setUserVideo: (userVideo?: VideoInfo) => { set({ userVideo }) },
153
+
154
+ userVideos: [],
155
+ setUserVideos: (userVideos: VideoInfo[] = []) => {
156
+ set({
157
+ userVideos: Array.isArray(userVideos) ? userVideos : []
158
+ })
159
+ },
160
  }))
src/app/views/home-view/index.tsx CHANGED
@@ -11,19 +11,11 @@ import { getTags } from "@/app/server/actions/ai-tube-hf/getTags"
11
 
12
  export function HomeView() {
13
  const [_isPending, startTransition] = useTransition()
14
-
15
  const setView = useStore(s => s.setView)
16
- const displayMode = useStore(s => s.displayMode)
17
- const setDisplayMode = useStore(s => s.setDisplayMode)
18
- const currentChannel = useStore(s => s.currentChannel)
19
- const setCurrentChannel = useStore(s => s.setCurrentChannel)
20
  const currentTag = useStore(s => s.currentTag)
21
- const setCurrentTag = useStore(s => s.setCurrentTag)
22
- const setCurrentTags = useStore(s => s.setCurrentTags)
23
- const currentVideos = useStore(s => s.currentVideos)
24
- const setCurrentVideos = useStore(s => s.setCurrentVideos)
25
- const currentVideo = useStore(s => s.currentVideo)
26
- const setCurrentVideo = useStore(s => s.setCurrentVideo)
27
 
28
  useEffect(() => {
29
  startTransition(async () => {
@@ -33,13 +25,13 @@ export function HomeView() {
33
  maxVideos: 25
34
  })
35
 
36
- setCurrentVideos(videos)
37
  })
38
  }, [currentTag])
39
 
40
  const handleSelect = (video: VideoInfo) => {
41
- setCurrentVideo(video)
42
  setView("public_video")
 
43
  }
44
 
45
  return (
@@ -47,7 +39,7 @@ export function HomeView() {
47
  `pr-4`
48
  )}>
49
  <VideoList
50
- videos={currentVideos}
51
  layout="grid"
52
  onSelect={handleSelect}
53
  />
 
11
 
12
  export function HomeView() {
13
  const [_isPending, startTransition] = useTransition()
 
14
  const setView = useStore(s => s.setView)
 
 
 
 
15
  const currentTag = useStore(s => s.currentTag)
16
+ const setPublicVideos = useStore(s => s.setPublicVideos)
17
+ const setPublicVideo = useStore(s => s.setPublicVideo)
18
+ const publicVideos = useStore(s => s.publicVideos)
 
 
 
19
 
20
  useEffect(() => {
21
  startTransition(async () => {
 
25
  maxVideos: 25
26
  })
27
 
28
+ setPublicVideos(videos)
29
  })
30
  }, [currentTag])
31
 
32
  const handleSelect = (video: VideoInfo) => {
 
33
  setView("public_video")
34
+ setPublicVideo(video)
35
  }
36
 
37
  return (
 
39
  `pr-4`
40
  )}>
41
  <VideoList
42
+ videos={publicVideos}
43
  layout="grid"
44
  onSelect={handleSelect}
45
  />
src/app/views/public-channel-view/index.tsx CHANGED
@@ -5,38 +5,38 @@ import { useEffect, useTransition } from "react"
5
  import { useStore } from "@/app/state/useStore"
6
  import { cn } from "@/lib/utils"
7
  import { VideoList } from "@/app/interface/video-list"
 
8
 
9
 
10
  export function PublicChannelView() {
11
  const [_isPending, startTransition] = useTransition()
12
- const currentChannel = useStore(s => s.currentChannel)
13
- const currentVideos = useStore(s => s.currentVideos)
14
- const setCurrentVideos = useStore(s => s.setCurrentVideos)
15
- const setCurrentVideo = useStore(s => s.setCurrentVideo)
16
 
17
  useEffect(() => {
18
- if (!currentChannel) {
19
  return
20
  }
21
 
22
  startTransition(async () => {
23
- /*
24
  const videos = await getChannelVideos({
25
- channel: currentChannel,
 
26
  })
27
- console.log("videos:", videos)
28
- */
29
  })
30
 
31
- setCurrentVideos([])
32
- }, [currentChannel, currentChannel?.id])
33
 
34
  return (
35
  <div className={cn(
36
  `flex flex-col`
37
  )}>
38
  <VideoList
39
- videos={currentVideos}
 
40
  />
41
  </div>
42
  )
 
5
  import { useStore } from "@/app/state/useStore"
6
  import { cn } from "@/lib/utils"
7
  import { VideoList } from "@/app/interface/video-list"
8
+ import { getChannelVideos } from "@/app/server/actions/ai-tube-hf/getChannelVideos"
9
 
10
 
11
  export function PublicChannelView() {
12
  const [_isPending, startTransition] = useTransition()
13
+ const publicChannel = useStore(s => s.publicChannel)
14
+ const publicVideos = useStore(s => s.publicVideos)
15
+ const setPublicVideos = useStore(s => s.setPublicVideos)
 
16
 
17
  useEffect(() => {
18
+ if (!publicChannel) {
19
  return
20
  }
21
 
22
  startTransition(async () => {
 
23
  const videos = await getChannelVideos({
24
+ channel: publicChannel,
25
+ status: "published",
26
  })
27
+ setPublicVideos(videos)
 
28
  })
29
 
30
+ setPublicVideos([])
31
+ }, [publicChannel, publicChannel?.id])
32
 
33
  return (
34
  <div className={cn(
35
  `flex flex-col`
36
  )}>
37
  <VideoList
38
+ layout="grid"
39
+ videos={publicVideos}
40
  />
41
  </div>
42
  )
src/app/views/public-channels-view/index.tsx CHANGED
@@ -10,8 +10,8 @@ import { ChannelList } from "@/app/interface/channel-list"
10
  export function PublicChannelsView() {
11
  const [_isPending, startTransition] = useTransition()
12
 
13
- const currentChannels = useStore(s => s.currentChannels)
14
- const setCurrentChannels = useStore(s => s.setCurrentChannels)
15
  const [isLoaded, setLoaded] = useState(false)
16
 
17
  useEffect(() => {
@@ -19,10 +19,10 @@ export function PublicChannelsView() {
19
  startTransition(async () => {
20
  try {
21
  const channels = await getChannels()
22
- setCurrentChannels(channels)
23
  } catch (err) {
24
  console.error("failed to load the public channels", err)
25
- setCurrentChannels([])
26
  } finally {
27
  setLoaded(true)
28
  }
@@ -34,7 +34,7 @@ export function PublicChannelsView() {
34
  <div className={cn(`flex flex-col`)}>
35
  <ChannelList
36
  layout="grid"
37
- channels={currentChannels}
38
  />
39
  </div>
40
  )
 
10
  export function PublicChannelsView() {
11
  const [_isPending, startTransition] = useTransition()
12
 
13
+ const publicChannels = useStore(s => s.publicChannels)
14
+ const setPublicChannels = useStore(s => s.setPublicChannels)
15
  const [isLoaded, setLoaded] = useState(false)
16
 
17
  useEffect(() => {
 
19
  startTransition(async () => {
20
  try {
21
  const channels = await getChannels()
22
+ setPublicChannels(channels)
23
  } catch (err) {
24
  console.error("failed to load the public channels", err)
25
+ setPublicChannels([])
26
  } finally {
27
  setLoaded(true)
28
  }
 
34
  <div className={cn(`flex flex-col`)}>
35
  <ChannelList
36
  layout="grid"
37
+ channels={publicChannels}
38
  />
39
  </div>
40
  )
src/app/views/public-video-view/index.tsx CHANGED
@@ -10,7 +10,7 @@ import { VideoInfo } from "@/types"
10
 
11
 
12
  export function PublicVideoView() {
13
- const video = useStore(s => s.currentVideo)
14
 
15
  const videoId = `${video?.id || ""}`
16
 
 
10
 
11
 
12
  export function PublicVideoView() {
13
+ const video = useStore(s => s.publicVideo)
14
 
15
  const videoId = `${video?.id || ""}`
16
 
src/app/views/user-account-view/index.tsx CHANGED
@@ -18,10 +18,10 @@ export function UserAccountView() {
18
  defaultSettings.huggingfaceApiKey
19
  )
20
  const setView = useStore(s => s.setView)
21
- const setCurrentChannel = useStore(s => s.setCurrentChannel)
22
 
23
- const currentChannels = useStore(s => s.currentChannels)
24
- const setCurrentChannels = useStore(s => s.setCurrentChannels)
25
  const [isLoaded, setLoaded] = useState(false)
26
 
27
  useEffect(() => {
@@ -32,10 +32,10 @@ export function UserAccountView() {
32
  apiKey: huggingfaceApiKey,
33
  renewCache: true,
34
  })
35
- setCurrentChannels(channels)
36
  } catch (err) {
37
  console.error("failed to load the channel for the current user:", err)
38
- setCurrentChannels([])
39
  } finally {
40
  setLoaded(true)
41
  }
@@ -71,11 +71,11 @@ export function UserAccountView() {
71
  {huggingfaceApiKey ?
72
  <div className="flex flex-col space-y-4">
73
  <h2 className="text-3xl font-bold">Your custom channels:</h2>
74
- {currentChannels?.length ? <ChannelList
75
  layout="grid"
76
- channels={currentChannels}
77
- onSelect={(channel) => {
78
- setCurrentChannel(channel)
79
  setView("user_channel")
80
  }}
81
  /> : <p>Ask <span className="font-mono">@jbilcke-hf</span> for help to create a channel!</p>}
 
18
  defaultSettings.huggingfaceApiKey
19
  )
20
  const setView = useStore(s => s.setView)
21
+ const setUserChannel = useStore(s => s.setUserChannel)
22
 
23
+ const userChannels = useStore(s => s.userChannels)
24
+ const setUserChannels = useStore(s => s.setUserChannels)
25
  const [isLoaded, setLoaded] = useState(false)
26
 
27
  useEffect(() => {
 
32
  apiKey: huggingfaceApiKey,
33
  renewCache: true,
34
  })
35
+ setUserChannels(channels)
36
  } catch (err) {
37
  console.error("failed to load the channel for the current user:", err)
38
+ setUserChannels([])
39
  } finally {
40
  setLoaded(true)
41
  }
 
71
  {huggingfaceApiKey ?
72
  <div className="flex flex-col space-y-4">
73
  <h2 className="text-3xl font-bold">Your custom channels:</h2>
74
+ {userChannels?.length ? <ChannelList
75
  layout="grid"
76
+ channels={userChannels}
77
+ onSelect={(userChannel) => {
78
+ setUserChannel(userChannel)
79
  setView("user_channel")
80
  }}
81
  /> : <p>Ask <span className="font-mono">@jbilcke-hf</span> for help to create a channel!</p>}
src/app/views/user-channel-view/index.tsx CHANGED
@@ -13,8 +13,8 @@ import { Input } from "@/components/ui/input"
13
  import { Textarea } from "@/components/ui/textarea"
14
  import { Button } from "@/components/ui/button"
15
  import { submitVideoRequest } from "@/app/server/actions/submitVideoRequest"
16
- import { getVideoRequestsFromChannel } from "@/app/server/actions/ai-tube-hf/getVideoRequestsFromChannel"
17
  import { PendingVideoList } from "@/app/interface/pending-video-list"
 
18
 
19
  export function UserChannelView() {
20
  const [_isPending, startTransition] = useTransition()
@@ -32,49 +32,35 @@ export function UserChannelView() {
32
 
33
  const [isSubmitting, setIsSubmitting] = useState(false)
34
 
35
- const currentChannel = useStore(s => s.currentChannel)
36
- const currentVideos = useStore(s => s.currentVideos)
37
- const setCurrentVideos = useStore(s => s.setCurrentVideos)
38
- const setCurrentVideo = useStore(s => s.setCurrentVideo)
 
 
 
39
 
40
- console.log("CURRENT VIDEOS:", currentVideos)
41
  useEffect(() => {
42
- if (!currentChannel) {
43
  return
44
  }
45
 
46
  startTransition(async () => {
47
 
48
- const videoRequests = await getVideoRequestsFromChannel({
49
- channel: currentChannel,
50
- apiKey: huggingfaceApiKey,
51
- renewCache: true
52
  })
53
 
54
- const videos: VideoInfo[] = Object.values(videoRequests).map(videoRequest => ({
55
- id: videoRequest.id,
56
- status: "submitted",
57
- label: videoRequest.label,
58
- description: videoRequest.description,
59
- prompt: videoRequest.prompt,
60
- thumbnailUrl: videoRequest.thumbnailUrl,
61
- assetUrl: "",
62
- numberOfViews: 0,
63
- numberOfLikes: 0,
64
- updatedAt: videoRequest.updatedAt,
65
- tags: videoRequest.tags,
66
- channel: currentChannel
67
- }))
68
-
69
  console.log("setCurrentVideos:", videos)
70
 
71
- setCurrentVideos(videos)
72
  })
73
 
74
- }, [huggingfaceApiKey, currentChannel, currentChannel?.id])
75
 
76
  const handleSubmit = () => {
77
- if (!currentChannel) {
78
  return
79
  }
80
  if (!titleDraft || !promptDraft) {
@@ -87,7 +73,7 @@ export function UserChannelView() {
87
  startTransition(async () => {
88
  try {
89
  const newVideo = await submitVideoRequest({
90
- channel: currentChannel,
91
  apiKey: huggingfaceApiKey,
92
  title: titleDraft,
93
  description: descriptionDraft,
@@ -97,7 +83,7 @@ export function UserChannelView() {
97
 
98
  // in case of success we update the frontend immediately
99
  // with our draft video
100
- setCurrentVideos([newVideo, ...currentVideos])
101
  setPromptDraft("")
102
  setDescriptionDraft("")
103
  setTagsDraft("")
@@ -223,7 +209,7 @@ export function UserChannelView() {
223
  <h2 className="text-3xl font-bold">Current video prompts:</h2>
224
 
225
  <PendingVideoList
226
- videos={currentVideos}
227
  onDelete={handleDelete}
228
  />
229
  </div>
 
13
  import { Textarea } from "@/components/ui/textarea"
14
  import { Button } from "@/components/ui/button"
15
  import { submitVideoRequest } from "@/app/server/actions/submitVideoRequest"
 
16
  import { PendingVideoList } from "@/app/interface/pending-video-list"
17
+ import { getChannelVideos } from "@/app/server/actions/ai-tube-hf/getChannelVideos"
18
 
19
  export function UserChannelView() {
20
  const [_isPending, startTransition] = useTransition()
 
32
 
33
  const [isSubmitting, setIsSubmitting] = useState(false)
34
 
35
+ const userChannel = useStore(s => s.userChannel)
36
+ const userChannels = useStore(s => s.userChannels)
37
+ const userVideos = useStore(s => s.userVideos)
38
+ const setUserChannel = useStore(s => s.setUserChannel)
39
+ const setUserChannels = useStore(s => s.setUserChannels)
40
+ const setUserVideos = useStore(s => s.setUserVideos)
41
+
42
 
 
43
  useEffect(() => {
44
+ if (!userChannel) {
45
  return
46
  }
47
 
48
  startTransition(async () => {
49
 
50
+ const videos = await getChannelVideos({
51
+ channel: userChannel,
52
+ // status: undefined, // we want *all* status
 
53
  })
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  console.log("setCurrentVideos:", videos)
56
 
57
+ setUserVideos(videos)
58
  })
59
 
60
+ }, [huggingfaceApiKey, userChannel, userChannel?.id])
61
 
62
  const handleSubmit = () => {
63
+ if (!userChannel) {
64
  return
65
  }
66
  if (!titleDraft || !promptDraft) {
 
73
  startTransition(async () => {
74
  try {
75
  const newVideo = await submitVideoRequest({
76
+ channel: userChannel,
77
  apiKey: huggingfaceApiKey,
78
  title: titleDraft,
79
  description: descriptionDraft,
 
83
 
84
  // in case of success we update the frontend immediately
85
  // with our draft video
86
+ setUserVideos([newVideo, ...userVideos])
87
  setPromptDraft("")
88
  setDescriptionDraft("")
89
  setTagsDraft("")
 
209
  <h2 className="text-3xl font-bold">Current video prompts:</h2>
210
 
211
  <PendingVideoList
212
+ videos={userVideos}
213
  onDelete={handleDelete}
214
  />
215
  </div>