Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Commit
•
b161bd3
1
Parent(s):
f70dd7e
src
Browse files- src/app/channel/page.tsx +8 -1
- src/app/interface/channel-card/index.tsx +72 -69
- src/app/interface/top-header/index.tsx +1 -1
- src/app/main.tsx +31 -0
- src/app/server/actions/ai-tube-hf/getChannelVideos.ts +3 -1
- src/app/server/actions/stats.ts +2 -2
- src/app/views/home-view/index.tsx +4 -0
- src/app/views/public-channel-view/index.tsx +72 -1
- src/app/views/public-video-view/index.tsx +11 -6
src/app/channel/page.tsx
CHANGED
@@ -1,8 +1,15 @@
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
}
|
|
|
1 |
import { AppQueryProps } from "@/types"
|
2 |
import { Main } from "../main"
|
3 |
import { getChannel } from "../server/actions/ai-tube-hf/getChannel"
|
4 |
+
import { getChannelVideos } from "../server/actions/ai-tube-hf/getChannelVideos"
|
5 |
|
6 |
export default async function ChannelPage({ searchParams: { c: channelId } }: AppQueryProps) {
|
7 |
const channel = await getChannel({ channelId, neverThrow: true })
|
8 |
+
|
9 |
+
const publicVideos = await getChannelVideos({
|
10 |
+
channel: channel,
|
11 |
+
status: "published",
|
12 |
+
})
|
13 |
+
|
14 |
+
return (<Main channel={channel} publicVideos={publicVideos} />)
|
15 |
}
|
src/app/interface/channel-card/index.tsx
CHANGED
@@ -7,6 +7,7 @@ import { IoAdd } from "react-icons/io5"
|
|
7 |
import { cn } from "@/lib/utils"
|
8 |
import { ChannelInfo } from "@/types"
|
9 |
import { isCertifiedUser } from "@/app/certification"
|
|
|
10 |
|
11 |
const DefaultAvatar = dynamic(() => import("../default-avatar"), {
|
12 |
loading: () => null,
|
@@ -36,81 +37,83 @@ export function ChannelCard({
|
|
36 |
const isCreateButton = !channel.id
|
37 |
|
38 |
return (
|
39 |
-
|
40 |
-
className={cn(
|
41 |
-
`flex flex-col`,
|
42 |
-
`items-center justify-center`,
|
43 |
-
`space-y-1`,
|
44 |
-
`w-52 h-52`,
|
45 |
-
`rounded-lg`,
|
46 |
-
`text-neutral-100/80`,
|
47 |
-
isCreateButton ? '' : `hover:bg-neutral-800/30 hover:text-neutral-100/100`,
|
48 |
-
`cursor-pointer`,
|
49 |
-
className,
|
50 |
-
)}
|
51 |
-
onClick={() => {
|
52 |
-
if (onClick) {
|
53 |
-
onClick(channel)
|
54 |
-
}
|
55 |
-
}}
|
56 |
-
>
|
57 |
<div
|
58 |
className={cn(
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
>
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
|
87 |
-
<div className={cn(
|
88 |
-
`flex flex-col`,
|
89 |
-
`items-center justify-center text-center`,
|
90 |
-
`space-y-1`
|
91 |
-
)}>
|
92 |
<div className={cn(
|
93 |
-
`
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
</div>
|
103 |
-
*/}
|
104 |
-
{!isCreateButton && <div className="flex flex-row items-center space-x-0.5">
|
105 |
-
<div className="flex flex-row items-center text-center text-xs font-medium">@{channel.datasetUser}</div>
|
106 |
-
{isCertifiedUser(channel.datasetUser) ? <div className="text-xs text-neutral-400"><RiCheckboxCircleFill className="" /></div> : null}
|
107 |
-
</div>}
|
108 |
-
{!isCreateButton && <div className="flex flex-row items-center justify-center text-neutral-400">
|
109 |
-
<div className="text-center text-xs">{0} videos</div>
|
110 |
-
<div className="px-1">-</div>
|
111 |
-
<div className="text-center text-xs">{channel.likes} likes</div>
|
112 |
-
</div>}
|
113 |
</div>
|
114 |
-
</
|
115 |
)
|
116 |
}
|
|
|
7 |
import { cn } from "@/lib/utils"
|
8 |
import { ChannelInfo } from "@/types"
|
9 |
import { isCertifiedUser } from "@/app/certification"
|
10 |
+
import Link from "next/link"
|
11 |
|
12 |
const DefaultAvatar = dynamic(() => import("../default-avatar"), {
|
13 |
loading: () => null,
|
|
|
37 |
const isCreateButton = !channel.id
|
38 |
|
39 |
return (
|
40 |
+
// <Link href={`/channel?c=${channel.id}`}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
<div
|
42 |
className={cn(
|
43 |
+
`flex flex-col`,
|
44 |
+
`items-center justify-center`,
|
45 |
+
`space-y-1`,
|
46 |
+
`w-52 h-52`,
|
47 |
+
`rounded-lg`,
|
48 |
+
`text-neutral-100/80`,
|
49 |
+
isCreateButton ? '' : `hover:bg-neutral-800/30 hover:text-neutral-100/100`,
|
50 |
+
`cursor-pointer`,
|
51 |
+
className,
|
52 |
+
)}
|
53 |
+
onClick={() => {
|
54 |
+
if (onClick) {
|
55 |
+
onClick(channel)
|
56 |
+
}
|
57 |
+
}}
|
58 |
>
|
59 |
+
<div
|
60 |
+
className={cn(
|
61 |
+
`flex flex-col items-center justify-center`,
|
62 |
+
`rounded-full overflow-hidden`,
|
63 |
+
`w-26 h-26`
|
64 |
+
)}
|
65 |
+
>
|
66 |
+
{isCreateButton
|
67 |
+
? <div className={cn(
|
68 |
+
`flex flex-col justify-center items-center text-center`,
|
69 |
+
`w-full h-full rounded-full`,
|
70 |
+
`bg-neutral-700 hover:bg-neutral-600`,
|
71 |
+
`border-2 border-neutral-400 hover:border-neutral-300`
|
72 |
+
)}>
|
73 |
+
<IoAdd className="w-8 h-8" />
|
74 |
+
</div>
|
75 |
+
: channelThumbnail
|
76 |
+
? <img
|
77 |
+
src={channelThumbnail}
|
78 |
+
onError={handleBadChannelThumbnail}
|
79 |
+
/>
|
80 |
+
: <DefaultAvatar
|
81 |
+
username={channel.datasetUser}
|
82 |
+
bgColor="#fde047"
|
83 |
+
textColor="#1c1917"
|
84 |
+
width={104}
|
85 |
+
roundShape
|
86 |
+
/>}
|
87 |
+
</div>
|
88 |
|
|
|
|
|
|
|
|
|
|
|
89 |
<div className={cn(
|
90 |
+
`flex flex-col`,
|
91 |
+
`items-center justify-center text-center`,
|
92 |
+
`space-y-1`
|
93 |
+
)}>
|
94 |
+
<div className={cn(
|
95 |
+
`text-center text-base font-medium text-zinc-100`,
|
96 |
+
isCreateButton ? 'mt-2' : ''
|
97 |
+
)}>{
|
98 |
+
isCreateButton ? "Create a channel" : channel.label
|
99 |
+
}</div>
|
100 |
+
{/*<div className="text-center text-sm font-semibold">
|
101 |
+
by <a href={
|
102 |
+
`https://huggingface.co/${channel.datasetUser}`
|
103 |
+
} target="_blank">@{channel.datasetUser}</a>
|
104 |
+
</div>
|
105 |
+
*/}
|
106 |
+
{!isCreateButton && <div className="flex flex-row items-center space-x-0.5">
|
107 |
+
<div className="flex flex-row items-center text-center text-xs font-medium">@{channel.datasetUser}</div>
|
108 |
+
{isCertifiedUser(channel.datasetUser) ? <div className="text-xs text-neutral-400"><RiCheckboxCircleFill className="" /></div> : null}
|
109 |
+
</div>}
|
110 |
+
{!isCreateButton && <div className="flex flex-row items-center justify-center text-neutral-400">
|
111 |
+
<div className="text-center text-xs">{0} videos</div>
|
112 |
+
<div className="px-1">-</div>
|
113 |
+
<div className="text-center text-xs">{channel.likes} likes</div>
|
114 |
+
</div>}
|
115 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
</div>
|
117 |
+
// </Link>
|
118 |
)
|
119 |
}
|
src/app/interface/top-header/index.tsx
CHANGED
@@ -36,7 +36,7 @@ export function TopHeader() {
|
|
36 |
|
37 |
|
38 |
useEffect(() => {
|
39 |
-
if (view === "public_video") {
|
40 |
setHeaderMode("compact")
|
41 |
setMenuMode("slider_hidden")
|
42 |
} else {
|
|
|
36 |
|
37 |
|
38 |
useEffect(() => {
|
39 |
+
if (view === "public_video" || view === "public_channel") {
|
40 |
setHeaderMode("compact")
|
41 |
setMenuMode("slider_hidden")
|
42 |
} else {
|
src/app/main.tsx
CHANGED
@@ -23,10 +23,12 @@ import { TubeLayout } from "./interface/tube-layout"
|
|
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()
|
@@ -35,10 +37,21 @@ export function Main({
|
|
35 |
const setPublicVideo = useStore(s => s.setPublicVideo)
|
36 |
const setView = useStore(s => s.setView)
|
37 |
const setPathname = useStore(s => s.setPathname)
|
|
|
|
|
38 |
|
39 |
const videoId = `${video?.id || ""}`
|
40 |
// console.log("Main video= "+ videoId)
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
useEffect(() => {
|
43 |
// note: it is important to ALWAYS set the current video to videoId
|
44 |
// even if it's undefined
|
@@ -54,6 +67,24 @@ export function Main({
|
|
54 |
}
|
55 |
}, [videoId])
|
56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
|
58 |
// this is critical: it sync the current route (coming from server-side)
|
59 |
// with the zustand state manager
|
|
|
23 |
// more easily
|
24 |
export function Main({
|
25 |
video,
|
26 |
+
publicVideos,
|
27 |
channel,
|
28 |
}: {
|
29 |
// server side params
|
30 |
video?: VideoInfo
|
31 |
+
publicVideos?: VideoInfo[]
|
32 |
channel?: ChannelInfo
|
33 |
}) {
|
34 |
const pathname = usePathname()
|
|
|
37 |
const setPublicVideo = useStore(s => s.setPublicVideo)
|
38 |
const setView = useStore(s => s.setView)
|
39 |
const setPathname = useStore(s => s.setPathname)
|
40 |
+
const setPublicChannel = useStore(s => s.setPublicChannel)
|
41 |
+
const setPublicVideos = useStore(s => s.setPublicVideos)
|
42 |
|
43 |
const videoId = `${video?.id || ""}`
|
44 |
// console.log("Main video= "+ videoId)
|
45 |
|
46 |
+
const publicVideoIds = (publicVideos || []).map(v => v.id || "").filter(x => x)
|
47 |
+
|
48 |
+
useEffect(() => {
|
49 |
+
if (!publicVideos?.length) { return }
|
50 |
+
// note: it is important to ALWAYS set the current video to videoId
|
51 |
+
// even if it's undefined
|
52 |
+
setPublicVideos(publicVideos)
|
53 |
+
}, publicVideoIds)
|
54 |
+
|
55 |
useEffect(() => {
|
56 |
// note: it is important to ALWAYS set the current video to videoId
|
57 |
// even if it's undefined
|
|
|
67 |
}
|
68 |
}, [videoId])
|
69 |
|
70 |
+
const channelId = `${channel?.id || ""}`
|
71 |
+
// console.log("Main video= "+ videoId)
|
72 |
+
|
73 |
+
useEffect(() => {
|
74 |
+
// note: it is important to ALWAYS set the current video to videoId
|
75 |
+
// even if it's undefined
|
76 |
+
setPublicChannel(channel)
|
77 |
+
|
78 |
+
if (channelId) {
|
79 |
+
// this is a hack for hugging face:
|
80 |
+
// we allow the ?v=<id> param on the root of the domain
|
81 |
+
if (pathname !== "/channel") {
|
82 |
+
// console.log("we are on huggingface apparently!")
|
83 |
+
router.replace(`/channel?v=${channelId}`)
|
84 |
+
}
|
85 |
+
}
|
86 |
+
}, [channelId])
|
87 |
+
|
88 |
|
89 |
// this is critical: it sync the current route (coming from server-side)
|
90 |
// with the zustand state manager
|
src/app/server/actions/ai-tube-hf/getChannelVideos.ts
CHANGED
@@ -12,12 +12,14 @@ export async function getChannelVideos({
|
|
12 |
channel,
|
13 |
status,
|
14 |
}: {
|
15 |
-
channel
|
16 |
|
17 |
// filter videos by status
|
18 |
status?: VideoStatus
|
19 |
}): Promise<VideoInfo[]> {
|
20 |
|
|
|
|
|
21 |
const videos = await getVideoRequestsFromChannel({
|
22 |
channel,
|
23 |
apiKey: adminApiKey,
|
|
|
12 |
channel,
|
13 |
status,
|
14 |
}: {
|
15 |
+
channel?: ChannelInfo
|
16 |
|
17 |
// filter videos by status
|
18 |
status?: VideoStatus
|
19 |
}): Promise<VideoInfo[]> {
|
20 |
|
21 |
+
if (!channel) { return [] }
|
22 |
+
|
23 |
const videos = await getVideoRequestsFromChannel({
|
24 |
channel,
|
25 |
apiKey: adminApiKey,
|
src/app/server/actions/stats.ts
CHANGED
@@ -26,11 +26,11 @@ export async function getNumberOfViewsForVideos(videoIds: string[]): Promise<Rec
|
|
26 |
stats[videoId] = 0
|
27 |
}
|
28 |
|
29 |
-
|
30 |
const values = await redis.mget<number[]>(...ids)
|
31 |
|
32 |
values.forEach((nbViews, i) => {
|
33 |
-
const
|
|
|
34 |
stats[videoId] = nbViews || 0
|
35 |
})
|
36 |
|
|
|
26 |
stats[videoId] = 0
|
27 |
}
|
28 |
|
|
|
29 |
const values = await redis.mget<number[]>(...ids)
|
30 |
|
31 |
values.forEach((nbViews, i) => {
|
32 |
+
const redisId = `${ids[i] || ""}`
|
33 |
+
const videoId = redisId.replace(":stats:views", "").replace("videos:", "")
|
34 |
stats[videoId] = nbViews || 0
|
35 |
})
|
36 |
|
src/app/views/home-view/index.tsx
CHANGED
@@ -8,6 +8,7 @@ import { VideoInfo } from "@/types"
|
|
8 |
import { getVideos } from "@/app/server/actions/ai-tube-hf/getVideos"
|
9 |
import { VideoList } from "@/app/interface/video-list"
|
10 |
import { getTags } from "@/app/server/actions/ai-tube-hf/getTags"
|
|
|
11 |
|
12 |
export function HomeView() {
|
13 |
const [_isPending, startTransition] = useTransition()
|
@@ -25,6 +26,9 @@ export function HomeView() {
|
|
25 |
maxVideos: 25
|
26 |
})
|
27 |
|
|
|
|
|
|
|
28 |
setPublicVideos(videos)
|
29 |
})
|
30 |
}, [currentTag])
|
|
|
8 |
import { getVideos } from "@/app/server/actions/ai-tube-hf/getVideos"
|
9 |
import { VideoList } from "@/app/interface/video-list"
|
10 |
import { getTags } from "@/app/server/actions/ai-tube-hf/getTags"
|
11 |
+
import { extendVideosWithStats } from "@/app/server/actions/ai-tube-hf/extendVideosWithStats"
|
12 |
|
13 |
export function HomeView() {
|
14 |
const [_isPending, startTransition] = useTransition()
|
|
|
26 |
maxVideos: 25
|
27 |
})
|
28 |
|
29 |
+
// due to some caching on the first function.. we update with fresh data!
|
30 |
+
// const updatedVideos = await extendVideosWithStats(videos)
|
31 |
+
|
32 |
setPublicVideos(videos)
|
33 |
})
|
34 |
}, [currentTag])
|
src/app/views/public-channel-view/index.tsx
CHANGED
@@ -1,12 +1,16 @@
|
|
1 |
"use client"
|
2 |
|
3 |
-
import { useEffect, useTransition } from "react"
|
|
|
4 |
|
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()
|
@@ -14,7 +18,21 @@ export function PublicChannelView() {
|
|
14 |
const publicVideos = useStore(s => s.publicVideos)
|
15 |
const setPublicVideos = useStore(s => s.setPublicVideos)
|
16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
useEffect(() => {
|
|
|
|
|
18 |
if (!publicChannel) {
|
19 |
return
|
20 |
}
|
@@ -24,16 +42,69 @@ export function PublicChannelView() {
|
|
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 |
videos={publicVideos}
|
39 |
/>
|
|
|
1 |
"use client"
|
2 |
|
3 |
+
import { useEffect, useState, useTransition } from "react"
|
4 |
+
import dynamic from "next/dynamic"
|
5 |
|
6 |
import { useStore } from "@/app/state/useStore"
|
7 |
import { cn } from "@/lib/utils"
|
8 |
import { VideoList } from "@/app/interface/video-list"
|
9 |
import { getChannelVideos } from "@/app/server/actions/ai-tube-hf/getChannelVideos"
|
10 |
|
11 |
+
const DefaultAvatar = dynamic(() => import("../../interface/default-avatar"), {
|
12 |
+
loading: () => null,
|
13 |
+
})
|
14 |
|
15 |
export function PublicChannelView() {
|
16 |
const [_isPending, startTransition] = useTransition()
|
|
|
18 |
const publicVideos = useStore(s => s.publicVideos)
|
19 |
const setPublicVideos = useStore(s => s.setPublicVideos)
|
20 |
|
21 |
+
const [channelThumbnail, setChannelThumbnail] = useState(publicChannel?.thumbnail || "")
|
22 |
+
|
23 |
+
const handleBadChannelThumbnail = () => {
|
24 |
+
try {
|
25 |
+
if (channelThumbnail) {
|
26 |
+
setChannelThumbnail("")
|
27 |
+
}
|
28 |
+
} catch (err) {
|
29 |
+
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
useEffect(() => {
|
34 |
+
setChannelThumbnail(publicChannel?.thumbnail || "")
|
35 |
+
|
36 |
if (!publicChannel) {
|
37 |
return
|
38 |
}
|
|
|
42 |
channel: publicChannel,
|
43 |
status: "published",
|
44 |
})
|
45 |
+
console.log("videos:", videos)
|
46 |
setPublicVideos(videos)
|
47 |
})
|
48 |
|
49 |
setPublicVideos([])
|
50 |
}, [publicChannel, publicChannel?.id])
|
51 |
|
52 |
+
if (!publicChannel) { return null }
|
53 |
+
|
54 |
return (
|
55 |
<div className={cn(
|
56 |
`flex flex-col`
|
57 |
)}>
|
58 |
+
{/* BANNER */}
|
59 |
+
<div className={cn(
|
60 |
+
`flex flex-col items-center justify-center w-full h-44`
|
61 |
+
)}>
|
62 |
+
{channelThumbnail
|
63 |
+
? <img
|
64 |
+
src={channelThumbnail}
|
65 |
+
onError={handleBadChannelThumbnail}
|
66 |
+
className="w-full h-full overflow-hidden object-cover"
|
67 |
+
/>
|
68 |
+
: <DefaultAvatar
|
69 |
+
username={publicChannel.datasetUser}
|
70 |
+
bgColor="#fde047"
|
71 |
+
textColor="#1c1917"
|
72 |
+
width={160}
|
73 |
+
roundShape
|
74 |
+
/>}
|
75 |
+
</div>
|
76 |
+
|
77 |
+
{/* CHANNEL INFO - HORIZONTAL */}
|
78 |
+
<div className={cn(
|
79 |
+
`flex flex-row`
|
80 |
+
)}>
|
81 |
+
|
82 |
+
{/* AVATAR */}
|
83 |
+
<div className={cn(
|
84 |
+
`flex flex-col items-center justify-center w-full`
|
85 |
+
)}>
|
86 |
+
{channelThumbnail
|
87 |
+
? <img
|
88 |
+
src={channelThumbnail}
|
89 |
+
onError={handleBadChannelThumbnail}
|
90 |
+
className="w-40 h-40 overflow-hidden"
|
91 |
+
/>
|
92 |
+
: <DefaultAvatar
|
93 |
+
username={publicChannel.datasetUser}
|
94 |
+
bgColor="#fde047"
|
95 |
+
textColor="#1c1917"
|
96 |
+
width={160}
|
97 |
+
roundShape
|
98 |
+
/>}
|
99 |
+
</div>
|
100 |
+
|
101 |
+
<div className={cn(
|
102 |
+
`flex flex-col items-center justify-center w-full`
|
103 |
+
)}>
|
104 |
+
<h3 className="tex-xl text-zinc-100">{publicChannel.label}</h3>
|
105 |
+
</div>
|
106 |
+
</div>
|
107 |
+
|
108 |
<VideoList
|
109 |
videos={publicVideos}
|
110 |
/>
|
src/app/views/public-video-view/index.tsx
CHANGED
@@ -32,6 +32,7 @@ export function PublicVideoView() {
|
|
32 |
const [copied, setCopied] = useState<boolean>(false)
|
33 |
|
34 |
const [channelThumbnail, setChannelThumbnail] = useState(`${video?.channel.thumbnail || ""}`)
|
|
|
35 |
|
36 |
// we inject the current videoId in the URL, if it's not already present
|
37 |
// this is a hack for Hugging Face iframes
|
@@ -71,15 +72,19 @@ export function PublicVideoView() {
|
|
71 |
}
|
72 |
|
73 |
useEffect(() => {
|
74 |
-
if (!videoId) {
|
75 |
-
return
|
76 |
-
}
|
77 |
-
|
78 |
startTransition(async () => {
|
79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
})
|
81 |
|
82 |
-
}, [
|
83 |
|
84 |
if (!video) { return null }
|
85 |
|
|
|
32 |
const [copied, setCopied] = useState<boolean>(false)
|
33 |
|
34 |
const [channelThumbnail, setChannelThumbnail] = useState(`${video?.channel.thumbnail || ""}`)
|
35 |
+
const setPublicVideo = useStore(s => s.setPublicVideo)
|
36 |
|
37 |
// we inject the current videoId in the URL, if it's not already present
|
38 |
// this is a hack for Hugging Face iframes
|
|
|
72 |
}
|
73 |
|
74 |
useEffect(() => {
|
|
|
|
|
|
|
|
|
75 |
startTransition(async () => {
|
76 |
+
if (!video || !video.id) {
|
77 |
+
return
|
78 |
+
}
|
79 |
+
const numberOfViews = await watchVideo(videoId)
|
80 |
+
|
81 |
+
setPublicVideo({
|
82 |
+
...video,
|
83 |
+
numberOfViews
|
84 |
+
})
|
85 |
})
|
86 |
|
87 |
+
}, [video?.id])
|
88 |
|
89 |
if (!video) { return null }
|
90 |
|