jbilcke-hf HF Staff commited on
Commit
1f122c3
ยท
0 Parent(s):

๐Ÿฟ

Browse files
This view is limited to 50 files because it contains too many changes. ย  See raw diff
Files changed (50) hide show
  1. .env +9 -0
  2. .eslintrc.json +3 -0
  3. .gitignore +37 -0
  4. .nvmrc +1 -0
  5. Dockerfile +65 -0
  6. README.md +13 -0
  7. TODO.md +6 -0
  8. components.json +16 -0
  9. next.config.js +6 -0
  10. package-lock.json +0 -0
  11. package.json +78 -0
  12. postcss.config.js +6 -0
  13. public/bubble.jpg +0 -0
  14. public/favicon.ico +0 -0
  15. public/favicon/favicon-114-precomposed.png +0 -0
  16. public/favicon/favicon-120-precomposed.png +0 -0
  17. public/favicon/favicon-144-precomposed.png +0 -0
  18. public/favicon/favicon-152-precomposed.png +0 -0
  19. public/favicon/favicon-180-precomposed.png +0 -0
  20. public/favicon/favicon-192.png +0 -0
  21. public/favicon/favicon-32.png +0 -0
  22. public/favicon/favicon-36.png +0 -0
  23. public/favicon/favicon-48.png +0 -0
  24. public/favicon/favicon-57.png +0 -0
  25. public/favicon/favicon-60.png +0 -0
  26. public/favicon/favicon-72-precomposed.png +0 -0
  27. public/favicon/favicon-72.png +0 -0
  28. public/favicon/favicon-76.png +0 -0
  29. public/favicon/favicon-96.png +0 -0
  30. public/favicon/favicon.ico +0 -0
  31. public/favicon/manifest.json +41 -0
  32. public/icon.png +0 -0
  33. public/mask.png +0 -0
  34. public/next.svg +1 -0
  35. public/vercel.svg +1 -0
  36. src/app/admin/index.tsx +0 -0
  37. src/app/favicon.ico +0 -0
  38. src/app/globals.css +39 -0
  39. src/app/icon.png +0 -0
  40. src/app/interface/channel-card/index.tsx +34 -0
  41. src/app/interface/channel-list/index.tsx +43 -0
  42. src/app/interface/fonts/index.tsx +7 -0
  43. src/app/interface/left-menu/index.tsx +45 -0
  44. src/app/interface/left-menu/menu-item/index.tsx +61 -0
  45. src/app/interface/top-menu/index.tsx +75 -0
  46. src/app/interface/video-card/index.tsx +34 -0
  47. src/app/interface/video-list/index.tsx +43 -0
  48. src/app/layout.tsx +30 -0
  49. src/app/main.tsx +36 -0
  50. src/app/page.tsx +46 -0
.env ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+
2
+ ADMIN_HUGGING_FACE_API_TOKEN=""
3
+ ADMIN_HUGGING_FACE_USERNAME=""
4
+
5
+ # ----------- CENSORSHIP -------
6
+ ENABLE_CENSORSHIP=
7
+ FINGERPRINT_KEY=
8
+ MODERATION_KEY=
9
+
.eslintrc.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "extends": "next/core-web-vitals"
3
+ }
.gitignore ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+
8
+ # testing
9
+ /coverage
10
+
11
+ # next.js
12
+ /.next/
13
+ /out/
14
+
15
+ # production
16
+ /build
17
+
18
+ # misc
19
+ .DS_Store
20
+ *.pem
21
+
22
+ # debug
23
+ npm-debug.log*
24
+ yarn-debug.log*
25
+ yarn-error.log*
26
+
27
+ # local env files
28
+ .env*.local
29
+
30
+ # vercel
31
+ .vercel
32
+
33
+ # typescript
34
+ *.tsbuildinfo
35
+ next-env.d.ts
36
+
37
+ /sandbox/
.nvmrc ADDED
@@ -0,0 +1 @@
 
 
1
+ v20.9.0
Dockerfile ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:20-alpine AS base
2
+
3
+ # Install dependencies only when needed
4
+ FROM base AS deps
5
+ # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
6
+ RUN apk add --no-cache libc6-compat
7
+ WORKDIR /app
8
+
9
+ # Install dependencies based on the preferred package manager
10
+ COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
11
+ RUN \
12
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
13
+ elif [ -f package-lock.json ]; then npm ci; \
14
+ elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
15
+ else echo "Lockfile not found." && exit 1; \
16
+ fi
17
+
18
+ # Uncomment the following lines if you want to use a secret at buildtime,
19
+ # for example to access your private npm packages
20
+ # RUN --mount=type=secret,id=HF_EXAMPLE_SECRET,mode=0444,required=true \
21
+ # $(cat /run/secrets/HF_EXAMPLE_SECRET)
22
+
23
+ # Rebuild the source code only when needed
24
+ FROM base AS builder
25
+ WORKDIR /app
26
+ COPY --from=deps /app/node_modules ./node_modules
27
+ COPY . .
28
+
29
+ # Next.js collects completely anonymous telemetry data about general usage.
30
+ # Learn more here: https://nextjs.org/telemetry
31
+ # Uncomment the following line in case you want to disable telemetry during the build.
32
+ # ENV NEXT_TELEMETRY_DISABLED 1
33
+
34
+ # RUN yarn build
35
+
36
+ # If you use yarn, comment out this line and use the line above
37
+ RUN npm run build
38
+
39
+ # Production image, copy all the files and run next
40
+ FROM base AS runner
41
+ WORKDIR /app
42
+
43
+ ENV NODE_ENV production
44
+ # Uncomment the following line in case you want to disable telemetry during runtime.
45
+ # ENV NEXT_TELEMETRY_DISABLED 1
46
+
47
+ RUN addgroup --system --gid 1001 nodejs
48
+ RUN adduser --system --uid 1001 nextjs
49
+
50
+ COPY --from=builder /app/public ./public
51
+
52
+ # Automatically leverage output traces to reduce image size
53
+ # https://nextjs.org/docs/advanced-features/output-file-tracing
54
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
55
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
56
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/cache ./.next/cache
57
+ # COPY --from=builder --chown=nextjs:nodejs /app/.next/cache/fetch-cache ./.next/cache/fetch-cache
58
+
59
+ USER nextjs
60
+
61
+ EXPOSE 3000
62
+
63
+ ENV PORT 3000
64
+
65
+ CMD ["node", "server.js"]
README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: AI Tube
3
+ emoji: ๐Ÿฟ
4
+ colorFrom: red
5
+ colorTo: red
6
+ sdk: docker
7
+ pinned: true
8
+ app_port: 3000
9
+ ---
10
+
11
+ # ๐Ÿฟ AI Tube
12
+
13
+ (To be continued)
TODO.md ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+
2
+ Allow browsing some loras
3
+
4
+ Funny use cases to try:
5
+ - Hugging Face
6
+ - Zelda 64 (will be a bit more tricky since it uses some custom Replicate stuff)
components.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.js",
8
+ "css": "app/globals.css",
9
+ "baseColor": "stone",
10
+ "cssVariables": false
11
+ },
12
+ "aliases": {
13
+ "components": "@/components",
14
+ "utils": "@/lib/utils"
15
+ }
16
+ }
next.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ output: 'standalone',
4
+ }
5
+
6
+ module.exports = nextConfig
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai-tube",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@huggingface/inference": "^2.6.4",
13
+ "@radix-ui/react-accordion": "^1.1.2",
14
+ "@radix-ui/react-avatar": "^1.0.3",
15
+ "@radix-ui/react-checkbox": "^1.0.4",
16
+ "@radix-ui/react-collapsible": "^1.0.3",
17
+ "@radix-ui/react-dialog": "^1.0.4",
18
+ "@radix-ui/react-dropdown-menu": "^2.0.5",
19
+ "@radix-ui/react-icons": "^1.3.0",
20
+ "@radix-ui/react-label": "^2.0.2",
21
+ "@radix-ui/react-menubar": "^1.0.3",
22
+ "@radix-ui/react-popover": "^1.0.6",
23
+ "@radix-ui/react-select": "^1.2.2",
24
+ "@radix-ui/react-separator": "^1.0.3",
25
+ "@radix-ui/react-slider": "^1.1.2",
26
+ "@radix-ui/react-slot": "^1.0.2",
27
+ "@radix-ui/react-switch": "^1.0.3",
28
+ "@radix-ui/react-toast": "^1.1.4",
29
+ "@radix-ui/react-tooltip": "^1.0.6",
30
+ "@react-spring/web": "^9.7.3",
31
+ "@types/node": "20.4.2",
32
+ "@types/react": "18.2.15",
33
+ "@types/react-dom": "18.2.7",
34
+ "@types/uuid": "^9.0.2",
35
+ "autoprefixer": "10.4.14",
36
+ "class-variance-authority": "^0.6.1",
37
+ "clsx": "^2.0.0",
38
+ "cmdk": "^0.2.0",
39
+ "cookies-next": "^2.1.2",
40
+ "eslint": "8.45.0",
41
+ "eslint-config-next": "13.4.10",
42
+ "hash-wasm": "^4.11.0",
43
+ "lucide-react": "^0.260.0",
44
+ "next": "^14.0.3",
45
+ "pick": "^0.0.1",
46
+ "postcss": "8.4.26",
47
+ "pythonia": "^1.0.4",
48
+ "qs": "^6.11.2",
49
+ "react": "18.2.0",
50
+ "react-circular-progressbar": "^2.1.0",
51
+ "react-dom": "18.2.0",
52
+ "react-icons": "^4.12.0",
53
+ "react-smooth-scroll-hook": "^1.3.4",
54
+ "react-virtualized-auto-sizer": "^1.0.20",
55
+ "replicate": "^0.17.0",
56
+ "sbd": "^1.0.19",
57
+ "sentence-splitter": "^4.3.0",
58
+ "sharp": "^0.32.5",
59
+ "styled-components": "^6.0.7",
60
+ "tailwind-merge": "^1.13.2",
61
+ "tailwindcss": "3.3.3",
62
+ "tailwindcss-animate": "^1.0.6",
63
+ "temp-dir": "^3.0.0",
64
+ "ts-node": "^10.9.1",
65
+ "type-fest": "^4.8.2",
66
+ "typescript": "5.1.6",
67
+ "usehooks-ts": "^2.9.1",
68
+ "uuid": "^9.0.0",
69
+ "zustand": "^4.4.1"
70
+ },
71
+ "devDependencies": {
72
+ "@types/proper-lockfile": "^4.1.2",
73
+ "@types/qs": "^6.9.7",
74
+ "@types/react-virtualized": "^9.21.22",
75
+ "@types/sbd": "^1.0.3",
76
+ "daisyui": "^3.7.4"
77
+ }
78
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
public/bubble.jpg ADDED
public/favicon.ico ADDED
public/favicon/favicon-114-precomposed.png ADDED
public/favicon/favicon-120-precomposed.png ADDED
public/favicon/favicon-144-precomposed.png ADDED
public/favicon/favicon-152-precomposed.png ADDED
public/favicon/favicon-180-precomposed.png ADDED
public/favicon/favicon-192.png ADDED
public/favicon/favicon-32.png ADDED
public/favicon/favicon-36.png ADDED
public/favicon/favicon-48.png ADDED
public/favicon/favicon-57.png ADDED
public/favicon/favicon-60.png ADDED
public/favicon/favicon-72-precomposed.png ADDED
public/favicon/favicon-72.png ADDED
public/favicon/favicon-76.png ADDED
public/favicon/favicon-96.png ADDED
public/favicon/favicon.ico ADDED
public/favicon/manifest.json ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "pollo",
3
+ "icons": [
4
+ {
5
+ "src": "\/favicon-36.png",
6
+ "sizes": "36x36",
7
+ "type": "image\/png",
8
+ "density": 0.75
9
+ },
10
+ {
11
+ "src": "\/favicon-48.png",
12
+ "sizes": "48x48",
13
+ "type": "image\/png",
14
+ "density": 1
15
+ },
16
+ {
17
+ "src": "\/favicon-72.png",
18
+ "sizes": "72x72",
19
+ "type": "image\/png",
20
+ "density": 1.5
21
+ },
22
+ {
23
+ "src": "\/favicon-96.png",
24
+ "sizes": "96x96",
25
+ "type": "image\/png",
26
+ "density": 2
27
+ },
28
+ {
29
+ "src": "\/favicon-144.png",
30
+ "sizes": "144x144",
31
+ "type": "image\/png",
32
+ "density": 3
33
+ },
34
+ {
35
+ "src": "\/favicon-192.png",
36
+ "sizes": "192x192",
37
+ "type": "image\/png",
38
+ "density": 4
39
+ }
40
+ ]
41
+ }
public/icon.png ADDED
public/mask.png ADDED
public/next.svg ADDED
public/vercel.svg ADDED
src/app/admin/index.tsx ADDED
File without changes
src/app/favicon.ico ADDED
src/app/globals.css ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ --foreground-rgb: 0, 0, 0;
7
+ --background-start-rgb: 214, 219, 220;
8
+ --background-end-rgb: 255, 255, 255;
9
+ }
10
+
11
+ @media (prefers-color-scheme: dark) {
12
+ :root {
13
+ --foreground-rgb: 255, 255, 255;
14
+ --background-start-rgb: 0, 0, 0;
15
+ --background-end-rgb: 0, 0, 0;
16
+ }
17
+ }
18
+
19
+ body {
20
+ color: rgb(var(--foreground-rgb));
21
+ background: linear-gradient(
22
+ to bottom,
23
+ transparent,
24
+ rgb(var(--background-end-rgb))
25
+ )
26
+ rgb(var(--background-start-rgb));
27
+ }
28
+
29
+
30
+ /* this is the trick to bypass the style={{}} attribute when printing */
31
+ @media print {
32
+ .comic-page[style] { width: 100vw !important; }
33
+ }
34
+
35
+
36
+ .render-to-image .comic-panel {
37
+ height: auto !important;
38
+ /* max-width: fit-content !important; */
39
+ }
src/app/icon.png ADDED
src/app/interface/channel-card/index.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { cn } from "@/lib/utils"
2
+ import { ChannelInfo } from "@/types"
3
+
4
+ export function ChannelCard({
5
+ channel,
6
+ className = "",
7
+ }: {
8
+ channel: ChannelInfo
9
+ className?: string
10
+ }) {
11
+
12
+ return (
13
+ <div
14
+ className={cn(
15
+ `flex flex-col`,
16
+ `w-[300px] h-[400px]`,
17
+ `bg-line-900`,
18
+ className,
19
+ )}>
20
+ <div
21
+ className={cn(
22
+ `rounded-lg overflow-hidden`
23
+ )}
24
+ >
25
+ <img src="" />
26
+ </div>
27
+ <div className={cn(
28
+
29
+ )}>
30
+ <h3>{channel.label}</h3>
31
+ </div>
32
+ </div>
33
+ )
34
+ }
src/app/interface/channel-list/index.tsx ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { cn } from "@/lib/utils"
2
+ import { ChannelInfo } from "@/types"
3
+
4
+ import { ChannelCard } from "../channel-card"
5
+
6
+ export function ChannelList({
7
+ channels,
8
+ layout = "flex",
9
+ className = "",
10
+ }: {
11
+ channels: ChannelInfo[]
12
+
13
+ /**
14
+ * Layout mode
15
+ *
16
+ * This isn't necessarily based on screen size, it can also be:
17
+ * - based on the device type (eg. a smart TV)
18
+ * - a design choice for a particular page
19
+ */
20
+ layout?: "grid" | "flex"
21
+
22
+ className?: string
23
+ }) {
24
+
25
+ return (
26
+ <div
27
+ className={cn(
28
+ layout === "grid"
29
+ ? `grid grid-cols-4 gap-4`
30
+ : `flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4`,
31
+ className,
32
+ )}
33
+ >
34
+ {channels.map((channel) => (
35
+ <ChannelCard
36
+ key={channel.id}
37
+ channel={channel}
38
+ className=""
39
+ />
40
+ ))}
41
+ </div>
42
+ )
43
+ }
src/app/interface/fonts/index.tsx ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { Grandstander, Klee_One } from 'next/font/google'
4
+
5
+ // If loading a variable font, you don't need to specify the font weight
6
+ export const headingFont = Grandstander({ subsets: ['latin'] })
7
+ export const paragraphFont = Klee_One({ subsets: ['latin'], weight: "600" })
src/app/interface/left-menu/index.tsx ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { GrChannel } from "react-icons/gr"
2
+ import { MdVideoLibrary } from "react-icons/md"
3
+ import { RiHome8Line } from "react-icons/ri"
4
+
5
+ import { useStore } from "@/app/state/useStore"
6
+ import { cn } from "@/lib/utils"
7
+ import { MenuItem } from "./menu-item"
8
+
9
+ export function LeftMenu() {
10
+ const view = useStore(s => s.view)
11
+ const setView = useStore(s => s.setView)
12
+ return (
13
+ <div className={cn(
14
+ `flex flex-col items-center`,
15
+ `justify-items-stretch`,
16
+ `w-24 px-1 pt-4`,
17
+ // `bg-orange-500`,
18
+ )}>
19
+ <MenuItem
20
+ icon={<RiHome8Line className="h-6 w-6" />}
21
+ selected={view === "home"}
22
+ onClick={() => setView("home")}
23
+ >
24
+ Home
25
+ </MenuItem>
26
+ <MenuItem
27
+ icon={<GrChannel className="h-5 w-5" />}
28
+ selected={view === "channels_public"}
29
+ onClick={() => setView("channels_public")}
30
+ >
31
+ Channels
32
+ </MenuItem>
33
+ <MenuItem
34
+ icon={<MdVideoLibrary className="h-6 w-6" />}
35
+ selected={
36
+ view === "channels_admin" ||
37
+ view === "channel_admin"
38
+ }
39
+ onClick={() => setView("channels_admin")}
40
+ >
41
+ My Content
42
+ </MenuItem>
43
+ </div>
44
+ )
45
+ }
src/app/interface/left-menu/menu-item/index.tsx ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ReactNode } from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export function MenuItem({
6
+ icon = null,
7
+ children = null,
8
+ selected = false,
9
+ onClick = undefined,
10
+ className = "",
11
+ }: {
12
+ icon?: ReactNode
13
+ children?: ReactNode
14
+ selected?: boolean
15
+ onClick?: () => void
16
+ className?: string
17
+ }) {
18
+
19
+ return (
20
+ <div className={cn(
21
+ `flex flex-col`,
22
+ `items-center justify-center justify-items-stretch`,
23
+ // `bg-green-500`,
24
+ `cursor-pointer`,
25
+ `w-full h-21`,
26
+ `p-1`,
27
+ `group`
28
+ )}
29
+ onClick={() => {
30
+ if (onClick && !selected) {
31
+ onClick()
32
+ }
33
+ }}
34
+ >
35
+ <div
36
+ className={cn(
37
+ `flex flex-col`,
38
+ `items-center justify-center`,
39
+ `w-full h-full`,
40
+ `space-y-1.5`,
41
+ `rounded-xl`,
42
+ `text-xs`,
43
+ `transition-all duration-300 ease-in-out`,
44
+ // `bg-yellow-500`,
45
+
46
+ selected
47
+ ? `bg-neutral-100/10`
48
+ : `group-hover:bg-neutral-100/10 bg-neutral-100/0`,
49
+
50
+
51
+ className,
52
+ )}
53
+ >
54
+ {icon}
55
+ <div className="text-center">
56
+ {children}
57
+ </div>
58
+ </div>
59
+ </div>
60
+ )
61
+ }
src/app/interface/top-menu/index.tsx ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { VideoCategory, videoCategoriesWithLabels } from "@/app/state/categories"
2
+ import { useStore } from "@/app/state/useStore"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export function TopMenu() {
6
+ const displayMode = useStore(s => s.displayMode)
7
+ const setDisplayMode = useStore(s => s.setDisplayMode)
8
+ const currentChannel = useStore(s => s.currentChannel)
9
+ const setCurrentChannel = useStore(s => s.setCurrentChannel)
10
+ const currentCategory = useStore(s => s.currentCategory)
11
+ const setCurrentCategory = useStore(s => s.setCurrentCategory)
12
+ const currentVideos = useStore(s => s.currentVideos)
13
+ const currentVideo = useStore(s => s.currentVideo)
14
+ const setCurrentVideo = useStore(s => s.setCurrentVideo)
15
+
16
+ return (
17
+ <div className={cn(
18
+ `flex flex-col`,
19
+ `overflow-hidden`,
20
+ `w-full h-28`,
21
+ )}>
22
+ <div className={cn(
23
+ `flex flex-row justify-between`,
24
+ `w-full`
25
+ )}>
26
+ <div className={cn(
27
+ `flex flex-col items-center justify-center`,
28
+ `px-4 py-2 w-32`,
29
+ )}>
30
+ <div className="flex flex-row items-center">
31
+ <span className="text-3xl mr-1">๐Ÿฟ </span>
32
+ <span className="text-xl font-semibold">HugTube</span>
33
+ </div>
34
+ </div>
35
+ <div className={cn(
36
+ `flex flex-col items-center justify-center`,
37
+ `px-4 py-2 w-max-64`,
38
+ )}>
39
+ Search bar goes here
40
+ </div>
41
+ <div className={cn()}>
42
+ &nbsp; {/* unused for now */}
43
+ </div>
44
+ </div>
45
+ <div className={cn(
46
+ `flex flex-row space-x-3`,
47
+ `text-[13px] font-semibold`,
48
+ )}>
49
+ {Object.entries(videoCategoriesWithLabels)
50
+ .map(([ key, label ]) => (
51
+ <div
52
+ key={key}
53
+ className={cn(
54
+ `flex flex-col items-center justify-center`,
55
+ `rounded-lg px-3 py-1 h-8`,
56
+ `cursor-pointer`,
57
+ `transition-all duration-300 ease-in-out`,
58
+ currentCategory === key
59
+ ? `bg-neutral-100 text-neutral-800`
60
+ : `bg-neutral-800 text-neutral-50/90 hover:bg-neutral-700 hover:text-neutral-50/90`,
61
+ // `text-clip`
62
+ )}
63
+ onClick={() => {
64
+ setCurrentCategory(key as VideoCategory)
65
+ }}
66
+ >
67
+ <span className={cn(
68
+ `text-center`
69
+ )}>{label}</span>
70
+ </div>
71
+ ))}
72
+ </div>
73
+ </div>
74
+ )
75
+ }
src/app/interface/video-card/index.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { cn } from "@/lib/utils"
2
+ import { FullVideoInfo } from "@/types"
3
+
4
+ export function VideoCard({
5
+ video,
6
+ className = "",
7
+ }: {
8
+ video: FullVideoInfo
9
+ className?: string
10
+ }) {
11
+
12
+ return (
13
+ <div
14
+ className={cn(
15
+ `flex flex-col`,
16
+ `w-[300px] h-[400px]`,
17
+ `bg-line-900`,
18
+ className,
19
+ )}>
20
+ <div
21
+ className={cn(
22
+ `rounded-lg overflow-hidden`
23
+ )}
24
+ >
25
+ <img src="" />
26
+ </div>
27
+ <div className={cn(
28
+
29
+ )}>
30
+ <h3>{video.label}</h3>
31
+ </div>
32
+ </div>
33
+ )
34
+ }
src/app/interface/video-list/index.tsx ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { cn } from "@/lib/utils"
2
+ import { FullVideoInfo } from "@/types"
3
+
4
+ import { VideoCard } from "../video-card"
5
+
6
+ export function VideoList({
7
+ videos,
8
+ layout = "flex",
9
+ className = "",
10
+ }: {
11
+ videos: FullVideoInfo[]
12
+
13
+ /**
14
+ * Layout mode
15
+ *
16
+ * This isn't necessarily based on screen size, it can also be:
17
+ * - based on the device type (eg. a smart TV)
18
+ * - a design choice for a particular page
19
+ */
20
+ layout?: "grid" | "flex"
21
+
22
+ className?: string
23
+ }) {
24
+
25
+ return (
26
+ <div
27
+ className={cn(
28
+ layout === "grid"
29
+ ? `grid grid-cols-4 gap-4`
30
+ : `flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4`,
31
+ className,
32
+ )}
33
+ >
34
+ {videos.map((video) => (
35
+ <VideoCard
36
+ key={video.id}
37
+ video={video}
38
+ className=""
39
+ />
40
+ ))}
41
+ </div>
42
+ )
43
+ }
src/app/layout.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from 'next'
2
+ import { Inter } from 'next/font/google'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ import './globals.css'
7
+
8
+ const inter = Inter({ subsets: ['latin'] })
9
+
10
+ export const metadata: Metadata = {
11
+ title: '๐Ÿฟ AI Tube',
12
+ description: '๐Ÿฟ AI Tube',
13
+ }
14
+
15
+ export default function RootLayout({
16
+ children,
17
+ }: {
18
+ children: React.ReactNode
19
+ }) {
20
+ return (
21
+ <html lang="en">
22
+ <body className={cn(
23
+ `h-full w-full overflow-auto`,
24
+ inter.className
25
+ )}>
26
+ {children}
27
+ </body>
28
+ </html>
29
+ )
30
+ }
src/app/main.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { cn } from "@/lib/utils"
4
+ import { TopMenu } from "./interface/top-menu"
5
+ import { LeftMenu } from "./interface/left-menu"
6
+ import { useStore } from "./state/useStore"
7
+ import { HomeView } from "./views/home-view"
8
+ import { ChannelsPublicView } from "./views/channels-public-view"
9
+ import { ChannelsAdminView } from "./views/channels-admin-view"
10
+ import { ChannelPublicView } from "./views/channel-public-view"
11
+ import { ChannelAdminView } from "./views/channel-admin-view"
12
+ import { VideoPublicView } from "./views/video-public-view"
13
+
14
+ export function Main() {
15
+ const view = useStore(s => s.view)
16
+
17
+ return (
18
+ <div className={cn(
19
+ `flex flex-row h-screen w-screen inset-0 overflow-hidden`,
20
+ )}>
21
+ <LeftMenu />
22
+ <div className={cn(
23
+ `flex flex-col`,
24
+ `w-[calc(100vh-56px)]`
25
+ )}>
26
+ <TopMenu />
27
+ {view === "home" && <HomeView />}
28
+ {view === "channels_admin" && <ChannelsAdminView />}
29
+ {view === "channels_public" && <ChannelsPublicView />}
30
+ {view === "channel_public" && <ChannelPublicView />}
31
+ {view === "channel_admin" && <ChannelAdminView />}
32
+ {view === "video_public" && <VideoPublicView />}
33
+ </div>
34
+ </div>
35
+ )
36
+ }
src/app/page.tsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useEffect, useState } from "react"
4
+ import Head from "next/head"
5
+ import Script from "next/script"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ import { Main } from "./main"
10
+
11
+ // https://nextjs.org/docs/pages/building-your-application/optimizing/fonts
12
+
13
+ export default function Page() {
14
+ const [isLoaded, setLoaded] = useState(false)
15
+ useEffect(() => { setLoaded(true) }, [])
16
+ return (
17
+ <>
18
+ <Head>
19
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
20
+ <link rel="preconnect" href="https://fonts.googleapis.com" crossOrigin="anonymous" />
21
+ <meta name="viewport" content="width=device-width, initial-scale=0.86, maximum-scale=5.0, minimum-scale=0.86" />
22
+ </Head>
23
+ <main className={cn(
24
+ `light text-neutral-100`,
25
+ // `bg-gradient-to-r from-green-500 to-yellow-400`,
26
+ `bg-gradient-to-r from-neutral-950 to-neutral-950`,
27
+ )}>
28
+ {isLoaded && <Main />}
29
+ {/*
30
+ TODO: use a new tracker
31
+ This is the kind of project on which we want custom analytics!
32
+ <Script src="https://www.googletagmanager.com/gtag/js?id=GTM-NJ2ZZFBX" />
33
+ <Script id="google-analytics">
34
+ {`
35
+ window.dataLayer = window.dataLayer || [];
36
+ function gtag(){dataLayer.push(arguments);}
37
+ gtag('js', new Date());
38
+
39
+ gtag('config', 'GTM-NJ2ZZFBX');
40
+ `}
41
+ </Script>
42
+ */}
43
+ </main>
44
+ </>
45
+ )
46
+ }