jbilcke-hf HF staff commited on
Commit
f62b8d3
β€’
1 Parent(s): a64395a

work in progress, starting to take shape

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 +5 -0
  2. declarations.d.ts +1 -0
  3. package-lock.json +65 -46
  4. package.json +2 -2
  5. src/app/interface/channel-card/index.tsx +17 -4
  6. src/app/interface/channel-list/index.tsx +4 -0
  7. src/app/interface/left-menu/index.tsx +47 -26
  8. src/app/interface/top-menu/index.tsx +12 -12
  9. src/app/interface/video-card/index.tsx +2 -2
  10. src/app/interface/video-list/index.tsx +2 -2
  11. src/app/main.tsx +19 -12
  12. src/app/server/actions/ai-tube-hf/README.md +3 -0
  13. src/app/server/actions/{api.ts β†’ ai-tube-hf/getChannels.ts} +53 -16
  14. src/app/server/actions/ai-tube-hf/getIndex.ts +48 -0
  15. src/app/server/actions/ai-tube-hf/getVideoRequestsFromChannel.ts +112 -0
  16. src/app/server/actions/ai-tube-hf/uploadVideoRequestToDataset.ts +114 -0
  17. src/app/server/actions/ai-tube-robot/README.md +3 -0
  18. src/app/server/actions/ai-tube-robot/updateQueue.ts +42 -0
  19. src/app/server/actions/config.ts +9 -0
  20. src/app/server/actions/datasets.ts +0 -32
  21. src/app/server/actions/{generateImage.ts β†’ generation/generateImage.txt} +0 -0
  22. src/app/server/actions/{generateStoryLines.txt β†’ generation/generateStoryLines.txt} +0 -0
  23. src/app/server/actions/generation/videochain.ts +161 -0
  24. src/app/server/actions/python-api.ts +0 -19
  25. src/app/server/actions/submitVideoRequest.ts +47 -0
  26. src/app/server/actions/{censorship.ts β†’ utils/censorship.ts} +0 -0
  27. src/app/server/actions/utils/parseDatasetPrompt.ts +49 -0
  28. src/app/server/actions/utils/parseDatasetReadme.ts +62 -0
  29. src/app/server/config.ts +0 -6
  30. src/app/state/categories.ts +12 -7
  31. src/app/state/useStore.ts +15 -13
  32. src/app/views/channel-public-view/index.tsx +0 -56
  33. src/app/views/home-view/index.tsx +2 -2
  34. src/app/views/{channel-admin-view β†’ public-channel-view}/index.tsx +20 -33
  35. src/app/views/{channels-public-view β†’ public-channels-view}/index.tsx +1 -1
  36. src/app/views/{video-public-view β†’ public-video-view}/index.tsx +3 -3
  37. src/app/views/user-account-view/index.tsx +43 -0
  38. src/app/views/user-channel-view/index.tsx +151 -0
  39. src/app/views/{channels-admin-view β†’ user-channels-view}/index.tsx +12 -23
  40. src/huggingface/hub/src/index.ts +1 -1
  41. src/huggingface/hub/src/lib/commit.ts +8 -1
  42. src/huggingface/hub/src/lib/create-repo.ts +3 -1
  43. src/huggingface/hub/src/lib/delete-repo.ts +2 -0
  44. src/huggingface/hub/src/lib/download-file.ts +2 -0
  45. src/huggingface/hub/src/lib/file-download-info.ts +5 -0
  46. src/huggingface/hub/src/lib/file-exists.ts +2 -0
  47. src/huggingface/hub/src/lib/list-datasets.ts +1 -0
  48. src/huggingface/hub/src/lib/list-files.ts +2 -0
  49. src/huggingface/hub/src/lib/list-models.ts +1 -0
  50. src/huggingface/hub/src/lib/list-spaces.ts +1 -0
.env CHANGED
@@ -2,6 +2,11 @@
2
  ADMIN_HUGGING_FACE_API_TOKEN=""
3
  ADMIN_HUGGING_FACE_USERNAME=""
4
 
 
 
 
 
 
5
  # ----------- CENSORSHIP -------
6
  ENABLE_CENSORSHIP=
7
  FINGERPRINT_KEY=
 
2
  ADMIN_HUGGING_FACE_API_TOKEN=""
3
  ADMIN_HUGGING_FACE_USERNAME=""
4
 
5
+ AI_TUBE_ROBOT_API="https://jbilcke-hf-ai-tube-robot.hf.space"
6
+
7
+ VIDEOCHAIN_API_URL=""
8
+ VIDEOCHAIN_API_TOKEN=""
9
+
10
  # ----------- CENSORSHIP -------
11
  ENABLE_CENSORSHIP=
12
  FINGERPRINT_KEY=
declarations.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ declare module 'markdown-yaml-metadata-parser';
package-lock.json CHANGED
@@ -40,10 +40,10 @@
40
  "eslint-config-next": "13.4.10",
41
  "hash-wasm": "^4.11.0",
42
  "lucide-react": "^0.260.0",
 
43
  "next": "^14.0.3",
44
  "pick": "^0.0.1",
45
  "postcss": "8.4.26",
46
- "pythonia": "^1.0.4",
47
  "qs": "^6.11.2",
48
  "react": "18.2.0",
49
  "react-circular-progressbar": "^2.1.0",
@@ -64,7 +64,7 @@
64
  "type-fest": "^4.8.2",
65
  "typescript": "5.1.6",
66
  "usehooks-ts": "^2.9.1",
67
- "uuid": "^9.0.0",
68
  "zustand": "^4.4.1"
69
  },
70
  "devDependencies": {
@@ -95,9 +95,9 @@
95
  }
96
  },
97
  "node_modules/@babel/runtime": {
98
- "version": "7.23.4",
99
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.4.tgz",
100
- "integrity": "sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==",
101
  "dependencies": {
102
  "regenerator-runtime": "^0.14.0"
103
  },
@@ -2361,11 +2361,6 @@
2361
  "url": "https://github.com/sponsors/ljharb"
2362
  }
2363
  },
2364
- "node_modules/caller": {
2365
- "version": "1.1.0",
2366
- "resolved": "https://registry.npmjs.org/caller/-/caller-1.1.0.tgz",
2367
- "integrity": "sha512-n+21IZC3j06YpCWaxmUy5AnVqhmCIM2bQtqQyy00HJlmStRt6kwDX5F9Z97pqwAB+G/tgSz6q/kUBbNyQzIubw=="
2368
- },
2369
  "node_modules/callsites": {
2370
  "version": "3.1.0",
2371
  "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -3011,6 +3006,14 @@
3011
  "node": ">=8"
3012
  }
3013
  },
 
 
 
 
 
 
 
 
3014
  "node_modules/detect-node-es": {
3015
  "version": "1.1.0",
3016
  "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -3108,9 +3111,9 @@
3108
  }
3109
  },
3110
  "node_modules/electron-to-chromium": {
3111
- "version": "1.4.595",
3112
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.595.tgz",
3113
- "integrity": "sha512-+ozvXuamBhDOKvMNUQvecxfbyICmIAwS4GpLmR0bsiSBlGnLaOcs2Cj7J8XSbW+YEaN3Xl3ffgpm+srTUWFwFQ=="
3114
  },
3115
  "node_modules/emoji-regex": {
3116
  "version": "9.2.2",
@@ -3624,6 +3627,18 @@
3624
  "url": "https://opencollective.com/eslint"
3625
  }
3626
  },
 
 
 
 
 
 
 
 
 
 
 
 
3627
  "node_modules/esquery": {
3628
  "version": "1.5.0",
3629
  "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
@@ -4717,6 +4732,38 @@
4717
  "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
4718
  "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
4719
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4720
  "node_modules/merge2": {
4721
  "version": "1.4.1",
4722
  "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -5409,18 +5456,6 @@
5409
  "node": ">=6"
5410
  }
5411
  },
5412
- "node_modules/pythonia": {
5413
- "version": "1.0.4",
5414
- "resolved": "https://registry.npmjs.org/pythonia/-/pythonia-1.0.4.tgz",
5415
- "integrity": "sha512-YciqyN0ii93gmJ1S9GmB873tZPtk6TeF/35DWLHrTn+PxnHCPtaXyvjPucK8gLNgt7XSqawmNxdp6JNFjWQL4g==",
5416
- "dependencies": {
5417
- "caller": "^1.0.1",
5418
- "chalk": "^4.1.2"
5419
- },
5420
- "peerDependencies": {
5421
- "ws": "^7.5.1"
5422
- }
5423
- },
5424
  "node_modules/qs": {
5425
  "version": "6.11.2",
5426
  "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
@@ -6027,6 +6062,11 @@
6027
  "node": ">=0.10.0"
6028
  }
6029
  },
 
 
 
 
 
6030
  "node_modules/streamsearch": {
6031
  "version": "1.1.0",
6032
  "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -6849,27 +6889,6 @@
6849
  "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
6850
  "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
6851
  },
6852
- "node_modules/ws": {
6853
- "version": "7.5.9",
6854
- "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
6855
- "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
6856
- "peer": true,
6857
- "engines": {
6858
- "node": ">=8.3.0"
6859
- },
6860
- "peerDependencies": {
6861
- "bufferutil": "^4.0.1",
6862
- "utf-8-validate": "^5.0.2"
6863
- },
6864
- "peerDependenciesMeta": {
6865
- "bufferutil": {
6866
- "optional": true
6867
- },
6868
- "utf-8-validate": {
6869
- "optional": true
6870
- }
6871
- }
6872
- },
6873
  "node_modules/yallist": {
6874
  "version": "4.0.0",
6875
  "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
 
40
  "eslint-config-next": "13.4.10",
41
  "hash-wasm": "^4.11.0",
42
  "lucide-react": "^0.260.0",
43
+ "markdown-yaml-metadata-parser": "^3.0.0",
44
  "next": "^14.0.3",
45
  "pick": "^0.0.1",
46
  "postcss": "8.4.26",
 
47
  "qs": "^6.11.2",
48
  "react": "18.2.0",
49
  "react-circular-progressbar": "^2.1.0",
 
64
  "type-fest": "^4.8.2",
65
  "typescript": "5.1.6",
66
  "usehooks-ts": "^2.9.1",
67
+ "uuid": "^9.0.1",
68
  "zustand": "^4.4.1"
69
  },
70
  "devDependencies": {
 
95
  }
96
  },
97
  "node_modules/@babel/runtime": {
98
+ "version": "7.23.5",
99
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz",
100
+ "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==",
101
  "dependencies": {
102
  "regenerator-runtime": "^0.14.0"
103
  },
 
2361
  "url": "https://github.com/sponsors/ljharb"
2362
  }
2363
  },
 
 
 
 
 
2364
  "node_modules/callsites": {
2365
  "version": "3.1.0",
2366
  "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
 
3006
  "node": ">=8"
3007
  }
3008
  },
3009
+ "node_modules/detect-newline": {
3010
+ "version": "3.1.0",
3011
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
3012
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
3013
+ "engines": {
3014
+ "node": ">=8"
3015
+ }
3016
+ },
3017
  "node_modules/detect-node-es": {
3018
  "version": "1.1.0",
3019
  "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
 
3111
  }
3112
  },
3113
  "node_modules/electron-to-chromium": {
3114
+ "version": "1.4.596",
3115
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.596.tgz",
3116
+ "integrity": "sha512-zW3zbZ40Icb2BCWjm47nxwcFGYlIgdXkAx85XDO7cyky9J4QQfq8t0W19/TLZqq3JPQXtlv8BPIGmfa9Jb4scg=="
3117
  },
3118
  "node_modules/emoji-regex": {
3119
  "version": "9.2.2",
 
3627
  "url": "https://opencollective.com/eslint"
3628
  }
3629
  },
3630
+ "node_modules/esprima": {
3631
+ "version": "4.0.1",
3632
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
3633
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
3634
+ "bin": {
3635
+ "esparse": "bin/esparse.js",
3636
+ "esvalidate": "bin/esvalidate.js"
3637
+ },
3638
+ "engines": {
3639
+ "node": ">=4"
3640
+ }
3641
+ },
3642
  "node_modules/esquery": {
3643
  "version": "1.5.0",
3644
  "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
 
4732
  "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
4733
  "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
4734
  },
4735
+ "node_modules/markdown-yaml-metadata-parser": {
4736
+ "version": "3.0.0",
4737
+ "resolved": "https://registry.npmjs.org/markdown-yaml-metadata-parser/-/markdown-yaml-metadata-parser-3.0.0.tgz",
4738
+ "integrity": "sha512-gRxEfuGIpb9pS1nQyASx3+l99e1hyTaK/+zDuvGcZJvr+OlksZ5O+q7opPcQP25j/z7NoOYEp17Lxgq5Sn4vDg==",
4739
+ "dependencies": {
4740
+ "detect-newline": "^3.1.0",
4741
+ "js-yaml": "^3.14.1"
4742
+ },
4743
+ "engines": {
4744
+ "node": ">=10.0.0"
4745
+ }
4746
+ },
4747
+ "node_modules/markdown-yaml-metadata-parser/node_modules/argparse": {
4748
+ "version": "1.0.10",
4749
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
4750
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
4751
+ "dependencies": {
4752
+ "sprintf-js": "~1.0.2"
4753
+ }
4754
+ },
4755
+ "node_modules/markdown-yaml-metadata-parser/node_modules/js-yaml": {
4756
+ "version": "3.14.1",
4757
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
4758
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
4759
+ "dependencies": {
4760
+ "argparse": "^1.0.7",
4761
+ "esprima": "^4.0.0"
4762
+ },
4763
+ "bin": {
4764
+ "js-yaml": "bin/js-yaml.js"
4765
+ }
4766
+ },
4767
  "node_modules/merge2": {
4768
  "version": "1.4.1",
4769
  "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
 
5456
  "node": ">=6"
5457
  }
5458
  },
 
 
 
 
 
 
 
 
 
 
 
 
5459
  "node_modules/qs": {
5460
  "version": "6.11.2",
5461
  "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
 
6062
  "node": ">=0.10.0"
6063
  }
6064
  },
6065
+ "node_modules/sprintf-js": {
6066
+ "version": "1.0.3",
6067
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
6068
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
6069
+ },
6070
  "node_modules/streamsearch": {
6071
  "version": "1.1.0",
6072
  "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
 
6889
  "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
6890
  "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
6891
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6892
  "node_modules/yallist": {
6893
  "version": "4.0.0",
6894
  "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
package.json CHANGED
@@ -41,10 +41,10 @@
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",
@@ -65,7 +65,7 @@
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": {
 
41
  "eslint-config-next": "13.4.10",
42
  "hash-wasm": "^4.11.0",
43
  "lucide-react": "^0.260.0",
44
+ "markdown-yaml-metadata-parser": "^3.0.0",
45
  "next": "^14.0.3",
46
  "pick": "^0.0.1",
47
  "postcss": "8.4.26",
 
48
  "qs": "^6.11.2",
49
  "react": "18.2.0",
50
  "react-circular-progressbar": "^2.1.0",
 
65
  "type-fest": "^4.8.2",
66
  "typescript": "5.1.6",
67
  "usehooks-ts": "^2.9.1",
68
+ "uuid": "^9.0.1",
69
  "zustand": "^4.4.1"
70
  },
71
  "devDependencies": {
src/app/interface/channel-card/index.tsx CHANGED
@@ -3,9 +3,11 @@ import { ChannelInfo } from "@/types"
3
 
4
  export function ChannelCard({
5
  channel,
 
6
  className = "",
7
  }: {
8
  channel: ChannelInfo
 
9
  className?: string
10
  }) {
11
 
@@ -13,10 +15,19 @@ export function ChannelCard({
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`
@@ -24,10 +35,12 @@ export function ChannelCard({
24
  >
25
  <img src="" />
26
  </div>
27
- <div className={cn(
28
 
 
 
29
  )}>
30
  <h3>{channel.label}</h3>
 
31
  </div>
32
  </div>
33
  )
 
3
 
4
  export function ChannelCard({
5
  channel,
6
+ onClick,
7
  className = "",
8
  }: {
9
  channel: ChannelInfo
10
+ onClick?: (channel: ChannelInfo) => void
11
  className?: string
12
  }) {
13
 
 
15
  <div
16
  className={cn(
17
  `flex flex-col`,
18
+ `items-center justify-center`,
19
+ `w-[300px] h-[200px]`,
20
+ `rounded-lg`,
21
+ `bg-neutral-800 hover:bg-neutral-500/80`,
22
+ `cursor-pointer`,
23
  className,
24
+ )}
25
+ onClick={() => {
26
+ if (onClick) {
27
+ onClick(channel)
28
+ }
29
+ }}
30
+ >
31
  <div
32
  className={cn(
33
  `rounded-lg overflow-hidden`
 
35
  >
36
  <img src="" />
37
  </div>
 
38
 
39
+ <div className={cn(
40
+ `text-center`
41
  )}>
42
  <h3>{channel.label}</h3>
43
+ <p>{channel.likes} likes</p>
44
  </div>
45
  </div>
46
  )
src/app/interface/channel-list/index.tsx CHANGED
@@ -5,11 +5,14 @@ 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
  *
@@ -35,6 +38,7 @@ export function ChannelList({
35
  <ChannelCard
36
  key={channel.id}
37
  channel={channel}
 
38
  className=""
39
  />
40
  ))}
 
5
 
6
  export function ChannelList({
7
  channels,
8
+ onSelect,
9
  layout = "flex",
10
  className = "",
11
  }: {
12
  channels: ChannelInfo[]
13
 
14
+ onSelect?: (channel: ChannelInfo) => void
15
+
16
  /**
17
  * Layout mode
18
  *
 
38
  <ChannelCard
39
  key={channel.id}
40
  channel={channel}
41
+ onClick={onSelect}
42
  className=""
43
  />
44
  ))}
src/app/interface/left-menu/index.tsx CHANGED
@@ -1,6 +1,8 @@
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"
@@ -11,35 +13,54 @@ export function LeftMenu() {
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
  }
 
1
  import { GrChannel } from "react-icons/gr"
2
  import { MdVideoLibrary } from "react-icons/md"
3
  import { RiHome8Line } from "react-icons/ri"
4
+ import { PiRobot } from "react-icons/pi"
5
+ import { CgProfile } from "react-icons/cg"
6
 
7
  import { useStore } from "@/app/state/useStore"
8
  import { cn } from "@/lib/utils"
 
13
  const setView = useStore(s => s.setView)
14
  return (
15
  <div className={cn(
16
+ `flex flex-col`,
 
17
  `w-24 px-1 pt-4`,
18
  // `bg-orange-500`,
19
  )}>
20
+ <div className={cn(
21
+ `flex flex-col w-full`,
22
+ )}>
23
+ <MenuItem
24
+ icon={<RiHome8Line className="h-6 w-6" />}
25
+ selected={view === "home"}
26
+ onClick={() => setView("home")}
27
+ >
28
+ Discover
29
+ </MenuItem>
30
+ <MenuItem
31
+ icon={<GrChannel className="h-5 w-5" />}
32
+ selected={view === "public_channels"}
33
+ onClick={() => setView("public_channels")}
34
+ >
35
+ Channels
36
+ </MenuItem>
37
+ </div>
38
+ <div className={cn(
39
+ `flex flex-col w-full`,
40
+ )}>
41
+ {/*<MenuItem
42
+ icon={<MdVideoLibrary className="h-6 w-6" />}
43
+ selected={view === "user_videos"}
44
+ onClick={() => setView("user_videos")}
45
+ >
46
+ My Videos
47
+ </MenuItem>
48
+ */}
49
+ <MenuItem
50
+ icon={<PiRobot className="h-6 w-6" />}
51
+ selected={view === "user_channels"}
52
+ onClick={() => setView("user_channels")}
53
+ >
54
+ My Robots
55
+ </MenuItem>
56
+ <MenuItem
57
+ icon={<CgProfile className="h-6 w-6" />}
58
+ selected={view === "user_account"}
59
+ onClick={() => setView("user_account")}
60
+ >
61
+ Account
62
+ </MenuItem>
63
+ </div>
64
  </div>
65
  )
66
  }
src/app/interface/top-menu/index.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { VideoCategory, videoCategoriesWithLabels } from "@/app/state/categories"
2
  import { useStore } from "@/app/state/useStore"
3
  import { cn } from "@/lib/utils"
4
 
@@ -7,8 +7,8 @@ export function TopMenu() {
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)
@@ -17,26 +17,26 @@ export function TopMenu() {
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 */}
@@ -55,13 +55,13 @@ export function TopMenu() {
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(
 
1
+ import { videoCategoriesWithLabels } from "@/app/state/categories"
2
  import { useStore } from "@/app/state/useStore"
3
  import { cn } from "@/lib/utils"
4
 
 
7
  const setDisplayMode = useStore(s => s.setDisplayMode)
8
  const currentChannel = useStore(s => s.currentChannel)
9
  const setCurrentChannel = useStore(s => s.setCurrentChannel)
10
+ const currentTag = useStore(s => s.currentTag)
11
+ const setCurrentTag = useStore(s => s.setCurrentTag)
12
  const currentVideos = useStore(s => s.currentVideos)
13
  const currentVideo = useStore(s => s.currentVideo)
14
  const setCurrentVideo = useStore(s => s.setCurrentVideo)
 
17
  <div className={cn(
18
  `flex flex-col`,
19
  `overflow-hidden`,
20
+ `w-full h-28 pt-4`,
21
  )}>
22
  <div className={cn(
23
  `flex flex-row justify-between`,
24
  `w-full`
25
  )}>
26
  <div className={cn(
27
+ `flex flex-col items-start justify-center`,
28
+ `py-2 w-64`,
29
  )}>
30
+ <div className="flex flex-row items-center justify-start">
31
+ <span className="text-4xl mr-1">🍿 </span>
32
+ <span className="text-4xl font-semibold">AI Tube</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 */}
 
55
  `rounded-lg px-3 py-1 h-8`,
56
  `cursor-pointer`,
57
  `transition-all duration-300 ease-in-out`,
58
+ currentTag === 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
+ setCurrentTag(key)
65
  }}
66
  >
67
  <span className={cn(
src/app/interface/video-card/index.tsx CHANGED
@@ -1,11 +1,11 @@
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
 
 
1
  import { cn } from "@/lib/utils"
2
+ import { VideoInfo } from "@/types"
3
 
4
  export function VideoCard({
5
  video,
6
  className = "",
7
  }: {
8
+ video: VideoInfo
9
  className?: string
10
  }) {
11
 
src/app/interface/video-list/index.tsx CHANGED
@@ -1,5 +1,5 @@
1
  import { cn } from "@/lib/utils"
2
- import { FullVideoInfo } from "@/types"
3
 
4
  import { VideoCard } from "../video-card"
5
 
@@ -8,7 +8,7 @@ export function VideoList({
8
  layout = "flex",
9
  className = "",
10
  }: {
11
- videos: FullVideoInfo[]
12
 
13
  /**
14
  * Layout mode
 
1
  import { cn } from "@/lib/utils"
2
+ import { VideoInfo } from "@/types"
3
 
4
  import { VideoCard } from "../video-card"
5
 
 
8
  layout = "flex",
9
  className = "",
10
  }: {
11
+ videos: VideoInfo[]
12
 
13
  /**
14
  * Layout mode
src/app/main.tsx CHANGED
@@ -5,11 +5,12 @@ 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)
@@ -22,15 +23,21 @@ export function Main() {
22
  <LeftMenu />
23
  <div className={cn(
24
  `flex flex-col`,
25
- `w-[calc(100vh-96px)]`
 
26
  )}>
27
  <TopMenu />
28
- {view === "home" && <HomeView />}
29
- {view === "channels_admin" && <ChannelsAdminView />}
30
- {view === "channels_public" && <ChannelsPublicView />}
31
- {view === "channel_public" && <ChannelPublicView />}
32
- {view === "channel_admin" && <ChannelAdminView />}
33
- {view === "video_public" && <VideoPublicView />}
 
 
 
 
 
34
  </div>
35
  </div>
36
  )
 
5
  import { LeftMenu } from "./interface/left-menu"
6
  import { useStore } from "./state/useStore"
7
  import { HomeView } from "./views/home-view"
8
+ import { PublicChannelsView } from "./views/public-channels-view"
9
+ import { UserChannelsView } from "./views/user-channels-view"
10
+ import { PublicChannelView } from "./views/public-channel-view"
11
+ import { UserChannelView } from "./views/user-channel-view"
12
+ import { PublicVideoView } from "./views/public-video-view"
13
+ import { UserAccountView } from "./views/user-account-view"
14
 
15
  export function Main() {
16
  const view = useStore(s => s.view)
 
23
  <LeftMenu />
24
  <div className={cn(
25
  `flex flex-col`,
26
+ `w-[calc(100vh-96px)]`,
27
+ `px-2`
28
  )}>
29
  <TopMenu />
30
+ <div className="pt-4">
31
+ {view === "home" && <HomeView />}
32
+ {view === "public_video" && <PublicVideoView />}
33
+ {view === "public_channels" && <PublicChannelsView />}
34
+ {view === "public_channel" && <PublicChannelView />}
35
+ {view === "user_channels" && <UserChannelsView />}
36
+ {/*view === "user_videos" && <UserVideosView />*/}
37
+ {view === "user_channel" && <UserChannelView />}
38
+ {view === "user_account" && <UserAccountView />}
39
+
40
+ </div>
41
  </div>
42
  </div>
43
  )
src/app/server/actions/ai-tube-hf/README.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # server/actions/ai-tube-hf
2
+
3
+ Utility functions to manipulate channels hosted as Hugging Face Datasets
src/app/server/actions/{api.ts β†’ ai-tube-hf/getChannels.ts} RENAMED
@@ -1,12 +1,10 @@
1
  "use server"
2
 
3
- import { Credentials, listDatasets, whoAmI } from "@/huggingface/hub/src"
 
4
  import { ChannelInfo } from "@/types"
5
 
6
- const adminApiKey = `${process.env.ADMIN_HUGGING_FACE_API_TOKEN || ""}`
7
- const adminUsername = `${process.env.ADMIN_HUGGING_FACE_USERNAME || ""}`
8
-
9
- const adminCredentials: Credentials = { accessToken: adminApiKey }
10
 
11
  export async function getChannels(options: {
12
  apiKey?: string
@@ -39,46 +37,85 @@ export async function getChannels(options: {
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
47
  })) {
48
 
 
 
49
  const chunks = name.split("/")
50
- const [datasetUsername, datasetName] = chunks.length === 2
51
  ? chunks
52
  : [name, name]
53
 
54
- // console.log(`found a candidate dataset "${datasetName}" owned by @${datasetUsername}`)
55
 
56
  if (!datasetName.startsWith(prefix)) {
57
  continue
58
  }
59
 
 
 
 
 
 
60
  const slug = datasetName.replaceAll(prefix, "")
61
 
62
- console.log(`found an AI Tube channel: "${slug}"`)
63
 
64
  // TODO parse the README to get the proper label
65
- const label = slug.replaceAll("-", " ")
66
-
67
 
68
- // TODO parse the README to get this
69
- // we could also use the user's avatar by default
70
  const thumbnail = ""
 
 
 
71
 
72
- const prompt = "" // TODO parse the README to get this
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
  const channel: ChannelInfo = {
75
  id,
 
 
76
  slug,
77
- label,
 
78
  thumbnail,
79
  prompt,
80
  likes,
81
- // updatedAt
 
82
  }
83
 
84
  channels.push(channel)
 
1
  "use server"
2
 
3
+ import { Credentials, downloadFile, listDatasets, 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 getChannels(options: {
10
  apiKey?: string
 
37
  ? { owner } // search channels of a specific user
38
  : prefix // global search (note: might be costly?)
39
 
40
+ // console.log("search:", search)
41
 
42
  for await (const { id, name, likes, updatedAt } of listDatasets({
43
  search,
44
  credentials
45
  })) {
46
 
47
+ // TODO: need to handle better cases where the username is missing
48
+
49
  const chunks = name.split("/")
50
+ const [datasetUser, datasetName] = chunks.length === 2
51
  ? chunks
52
  : [name, name]
53
 
54
+ // console.log(`found a candidate dataset "${datasetName}" owned by @${datasetUser}`)
55
 
56
  if (!datasetName.startsWith(prefix)) {
57
  continue
58
  }
59
 
60
+ // ignore the video index
61
+ if (datasetName === "ai-tube-index") {
62
+ continue
63
+ }
64
+
65
  const slug = datasetName.replaceAll(prefix, "")
66
 
67
+ // console.log(`found an AI Tube channel: "${slug}"`)
68
 
69
  // TODO parse the README to get the proper label
70
+ let label = slug.replaceAll("-", " ")
 
71
 
 
 
72
  const thumbnail = ""
73
+ let prompt = ""
74
+ let description = ""
75
+ let tags: string[] = []
76
 
77
+ // console.log(`going to read datasets/${name}`)
78
+ try {
79
+ const response = await downloadFile({
80
+ repo: `datasets/${name}`,
81
+ path: "README.md",
82
+ credentials
83
+ })
84
+ const readme = await response?.text()
85
+
86
+ const ParsedDatasetReadme = parseDatasetReadme(readme)
87
+
88
+ // console.log("ParsedDatasetReadme: ", ParsedDatasetReadme)
89
+
90
+
91
+ prompt = ParsedDatasetReadme.prompt
92
+ label = ParsedDatasetReadme.pretty_name
93
+ description = ParsedDatasetReadme.description
94
+
95
+ const prefix = "ai-tube:"
96
+
97
+ tags = ParsedDatasetReadme.tags
98
+ .filter(tag => tag.startsWith(prefix)) // remove any tag not belonging to us
99
+ .map(tag => tag.replaceAll(prefix, "").trim()) // remove the prefix
100
+ .filter(tag => tag) // remove empty tags
101
+
102
+
103
+ } catch (err) {
104
+ console.log("failed to read the readme:", err)
105
+ }
106
 
107
  const channel: ChannelInfo = {
108
  id,
109
+ datasetUser,
110
+ datasetName,
111
  slug,
112
+ label,
113
+ description,
114
  thumbnail,
115
  prompt,
116
  likes,
117
+ tags,
118
+ updatedAt: updatedAt.toISOString()
119
  }
120
 
121
  channels.push(channel)
src/app/server/actions/ai-tube-hf/getIndex.ts ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { downloadFile } from "@/huggingface/hub/src"
2
+ import { VideoInfo, VideoStatus } from "@/types"
3
+
4
+ import { adminCredentials, adminUsername } from "../config"
5
+
6
+ export async function getIndex({
7
+ status,
8
+ renewCache,
9
+ }: {
10
+ status: VideoStatus
11
+
12
+ /**
13
+ * Renew the cache
14
+ *
15
+ * This is was the batch job daemon will use, as in normal time
16
+ * we will want to use the cache since the file might be large
17
+ *
18
+ * it is also possible that we decide to *never* renew the cache from a user's perspective,
19
+ * and only renew it manually when a video changes status
20
+ *
21
+ * that way user requests will always be snappy!
22
+ */
23
+ renewCache?: boolean
24
+ }): Promise<Record<string, VideoInfo>> {
25
+
26
+ // grab the current video index
27
+ const response = await downloadFile({
28
+ credentials: adminCredentials,
29
+ repo: `datasets/${adminUsername}/ai-tube-index`,
30
+ path: `${status}.json`,
31
+
32
+ })
33
+
34
+ // attention, this list might grow, especially the "published" one
35
+ // published videos should be put in a big dataset folder of files
36
+ // named "<id>.json" and "<id>.mp4" like in VideoChain
37
+ const jsonResponse = await response?.json()
38
+ if (
39
+ typeof jsonResponse === "undefined" &&
40
+ typeof jsonResponse !== "object" &&
41
+ Array.isArray(jsonResponse) ||
42
+ jsonResponse === null) {
43
+ throw new Error("index is not an object, admin repair needed")
44
+ }
45
+ const videos = jsonResponse as Record<string, VideoInfo>
46
+
47
+ return videos
48
+ }
src/app/server/actions/ai-tube-hf/getVideoRequestsFromChannel.ts ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import { Credentials, downloadFile, listFiles, whoAmI } from "@/huggingface/hub/src"
4
+ import { parseDatasetReadme } from "@/app/server/actions/utils/parseDatasetReadme"
5
+ import { ChannelInfo, VideoRequest } from "@/types"
6
+
7
+ import { adminCredentials } from "../config"
8
+
9
+ /**
10
+ * Return all the videos requests created by a user on their channel
11
+ *
12
+ * @param options
13
+ * @returns
14
+ */
15
+ export async function getVideoRequestsFromChannel(options: {
16
+ channel: ChannelInfo,
17
+ apiKey?: string,
18
+ renewCache?: boolean
19
+ }): Promise<Record<string, VideoRequest>> {
20
+
21
+ let credentials: Credentials = adminCredentials
22
+
23
+ if (options?.apiKey) {
24
+ try {
25
+ credentials = { accessToken: options.apiKey }
26
+ const { name: username } = await whoAmI({ credentials })
27
+ if (!username) {
28
+ throw new Error(`couldn't get the username`)
29
+ }
30
+ } catch (err) {
31
+ console.error(err)
32
+ return {}
33
+ }
34
+ }
35
+
36
+ let videos: Record<string, VideoRequest> = {}
37
+
38
+ const repo = `datasets/${options.channel.datasetUser}/${options.channel.datasetName}`
39
+
40
+ console.log(`scanning ${repo}`)
41
+
42
+ for await (const file of listFiles({
43
+ repo,
44
+ // recursive: true,
45
+ // expand: true,
46
+ credentials,
47
+ requestInit: {
48
+ // cache invalidation should be called right after adding a new video
49
+ cache: options?.renewCache ? "no-cache" : "default",
50
+ next: {
51
+ revalidate: 10, // otherwise we only update very 10 seconds by default
52
+ // tags: [] // tags used for cache invalidation (ie. this is added to the cache key)
53
+ }
54
+ }
55
+ })) {
56
+
57
+ // TODO we should add some safety mechanisms here:
58
+ // skip lists of files that are too long
59
+ // skip files that are too big
60
+ // skip files with file.security.safe !== true
61
+
62
+ console.log("file.path:", file.path)
63
+ /// { type, oid, size, path }
64
+ if (file.path === "README.md") {
65
+ console.log("found the README")
66
+ // TODO: read this readme
67
+ } else if (file.path.startsWith("prompt_") && file.path.endsWith(".txt")) {
68
+ console.log("yes!!")
69
+ const fileWithoutSuffix = file.path.split(".txt").shift() || ""
70
+ const words = fileWithoutSuffix.split("_")
71
+ console.log("debug:", { path: file.path, fileWithoutSuffix, words })
72
+ if (words.length !== 3) {
73
+ console.log("found an invalid prompt file format: " + file.path)
74
+ continue
75
+ }
76
+ const [_prefix, date, id] = words
77
+ console.log("found a prompt:", file.path)
78
+
79
+ try {
80
+ const response = await downloadFile({
81
+ repo,
82
+ path: file.path,
83
+ credentials
84
+ })
85
+ const rawMarkdown = await response?.text()
86
+
87
+ const parsedDatasetReadme = parseDatasetReadme(rawMarkdown)
88
+ console.log("prompt parsed markdown:", parsedDatasetReadme)
89
+ } catch (err) {
90
+ console.log("failed to parse the prompt file")
91
+ continue
92
+ }
93
+ const video: VideoRequest = {
94
+ id,
95
+ label: "",
96
+ description: "",
97
+ prompt: "",
98
+ thumbnailUrl: "",
99
+
100
+ updatedAt: file.lastCommit?.date || "",
101
+ tags: [], // read them from the file?
102
+ channel: options.channel
103
+ }
104
+
105
+ videos[id] = video
106
+ } else if (file.path.endsWith(".mp4")) {
107
+ console.log("found a video:", file.path)
108
+ }
109
+ }
110
+
111
+ return videos
112
+ }
src/app/server/actions/ai-tube-hf/uploadVideoRequestToDataset.ts ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import { Blob } from "buffer"
4
+ import { v4 as uuidv4 } from "uuid"
5
+
6
+ import { Credentials, uploadFile, whoAmI } from "@/huggingface/hub/src"
7
+ import { ChannelInfo, VideoInfo, VideoRequest } from "@/types"
8
+
9
+ /**
10
+ * Save the video request to the user's own dataset
11
+ *
12
+ * @param param0
13
+ * @returns
14
+ */
15
+ export async function uploadVideoRequestToDataset({
16
+ channel,
17
+ apiKey,
18
+ title,
19
+ description,
20
+ prompt,
21
+ tags,
22
+ }: {
23
+ channel: ChannelInfo
24
+ apiKey: string
25
+ title: string
26
+ description: string
27
+ prompt: string
28
+ tags: string[]
29
+ }): Promise<{
30
+ videoRequest: VideoRequest
31
+ videoInfo: VideoInfo
32
+ }> {
33
+ if (!apiKey) {
34
+ throw new Error(`the apiKey is required`)
35
+ }
36
+
37
+ let credentials: Credentials = { accessToken: apiKey }
38
+
39
+ const { name: username } = await whoAmI({ credentials })
40
+ if (!username) {
41
+ throw new Error(`couldn't get the username`)
42
+ }
43
+
44
+ const date = new Date()
45
+ const dateSlug = date.toISOString().replace(/[^0-9]/gi, '').slice(0, 12);
46
+
47
+ // there is a bug in the [^] maybe, because all characters are removed
48
+ // const nameSlug = title.replaceAll(/\S+/gi, "-").replaceAll(/[^A-Za-z0-9\-_]/gi, "")
49
+ // const fileName = `prompt-${dateSlug}-${nameSlug}.txt`
50
+
51
+ const videoId = uuidv4()
52
+
53
+ const fileName = `prompt_${dateSlug}_${videoId}.txt`
54
+
55
+ // Convert string to a Buffer
56
+ const blob = new Blob([`
57
+ # Title
58
+ ${title}
59
+
60
+ # Description
61
+ ${description}
62
+
63
+ # Tags
64
+
65
+ ${tags.map(tag => `- ${tag}\n`)}
66
+
67
+ # Prompt
68
+ ${prompt}
69
+ `]);
70
+
71
+
72
+ await uploadFile({
73
+ credentials,
74
+ repo: `datasets/${channel.datasetUser}/${channel.datasetName}`,
75
+ file: {
76
+ path: fileName,
77
+ content: blob as any,
78
+ },
79
+ commitTitle: "Add new video prompt",
80
+ })
81
+
82
+ // TODO: now we ping the robot to come read our prompt
83
+
84
+ const newVideoRequest: VideoRequest = {
85
+ id: videoId,
86
+ label: title,
87
+ description,
88
+ prompt,
89
+ thumbnailUrl: "",
90
+ updatedAt: new Date().toISOString(),
91
+ tags: [...channel.tags],
92
+ channel,
93
+ }
94
+
95
+ const newVideo: VideoInfo = {
96
+ id: videoId,
97
+ status: "submitted",
98
+ label: title,
99
+ description,,
100
+ prompt,
101
+ thumbnailUrl: "", // will be generated in async
102
+ assetUrl: "", // will be generated in async
103
+ numberOfViews: 0,
104
+ numberOfLikes: 0,
105
+ updatedAt: new Date().toISOString(),
106
+ tags: [...channel.tags],
107
+ channel,
108
+ }
109
+
110
+ return {
111
+ videoRequest: newVideoRequest,
112
+ videoInfo: newVideo
113
+ }
114
+ }
src/app/server/actions/ai-tube-robot/README.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # server/actions/ai-tube-robot
2
+
3
+ API client for the AI Tube Robot
src/app/server/actions/ai-tube-robot/updateQueue.ts ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import { ChannelInfo, UpdateQueueResponse } from "@/types"
4
+
5
+ import { aiTubeRobotApi } from "../config"
6
+
7
+ export async function updateQueue({
8
+ channel,
9
+ apiKey,
10
+ }: {
11
+ channel?: ChannelInfo
12
+ apiKey: string
13
+ }): Promise<number> {
14
+ if (!apiKey) {
15
+ throw new Error(`the apiKey is required`)
16
+ }
17
+
18
+ const res = await fetch(`${aiTubeRobotApi}/update-queue`, {
19
+ method: "POST",
20
+ headers: {
21
+ Accept: "application/json",
22
+ "Content-Type": "application/json",
23
+ // Authorization: `Bearer ${apiToken}`,
24
+ },
25
+ body: JSON.stringify({
26
+ apiKey,
27
+ channel
28
+ }),
29
+ cache: 'no-store',
30
+ // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
31
+ // next: { revalidate: 1 }
32
+ })
33
+
34
+ if (res.status !== 200) {
35
+ // This will activate the closest `error.js` Error Boundary
36
+ throw new Error('Failed to fetch data')
37
+ }
38
+
39
+ const response = (await res.json()) as UpdateQueueResponse
40
+ // console.log("response:", response)
41
+ return response.nbUpdated
42
+ }
src/app/server/actions/config.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { Credentials } from "@/huggingface/hub/src"
3
+
4
+ export const adminApiKey = `${process.env.ADMIN_HUGGING_FACE_API_TOKEN || ""}`
5
+ export const adminUsername = `${process.env.ADMIN_HUGGING_FACE_USERNAME || ""}`
6
+
7
+ export const adminCredentials: Credentials = { accessToken: adminApiKey }
8
+
9
+ export const aiTubeRobotApi = `${process.env.AI_TUBE_ROBOT_API || ""}`
src/app/server/actions/datasets.ts DELETED
@@ -1,32 +0,0 @@
1
- import { ChannelInfo, FullVideoInfo } from "@/types"
2
-
3
- export async function getPublicChannels({
4
- userHuggingFaceApiToken
5
- }: {
6
- userHuggingFaceApiToken: string
7
- }): Promise<ChannelInfo[]> {
8
- // search on Hugging Face for
9
- // TODO: we should probably cache this, and use a fixed list
10
- return []
11
- }
12
-
13
- export async function getPrivateChannels({
14
- userHuggingFaceApiToken
15
- }: {
16
- userHuggingFaceApiToken: string
17
- }): Promise<ChannelInfo[]> {
18
- return []
19
- }
20
-
21
- export async function getPrivateChannelVideos({
22
- userHuggingFaceApiToken,
23
- channel,
24
- }: {
25
- userHuggingFaceApiToken: string
26
- channel: ChannelInfo
27
- }): Promise<FullVideoInfo[]> {
28
- // TODO:
29
- // call the Hugging Face API to grab all the files in the dataset
30
- // we only get the first 30, that's enough for our demo
31
- return []
32
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/server/actions/{generateImage.ts β†’ generation/generateImage.txt} RENAMED
File without changes
src/app/server/actions/{generateStoryLines.txt β†’ generation/generateStoryLines.txt} RENAMED
File without changes
src/app/server/actions/generation/videochain.ts ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ // note: there is no / at the end in the variable
3
+ // so we have to add it ourselves if needed
4
+ const apiUrl = process.env.VIDEOCHAIN_API_URL
5
+
6
+ export const GET = async <T>(path: string = '', defaultValue: T): Promise<T> => {
7
+ try {
8
+ const res = await fetch(`${apiUrl}/${path}`, {
9
+ method: "GET",
10
+ headers: {
11
+ Accept: "application/json",
12
+ Authorization: `Bearer ${process.env.SECRET_ACCESS_TOKEN}`,
13
+ },
14
+ cache: 'no-store',
15
+ // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
16
+ // next: { revalidate: 1 }
17
+ })
18
+
19
+ // The return value is *not* serialized
20
+ // You can return Date, Map, Set, etc.
21
+
22
+ // Recommendation: handle errors
23
+ if (res.status !== 200) {
24
+ // This will activate the closest `error.js` Error Boundary
25
+ throw new Error('Failed to fetch data')
26
+ }
27
+
28
+ const data = await res.json()
29
+
30
+ return ((data as T) || defaultValue)
31
+ } catch (err) {
32
+ console.error(err)
33
+ return defaultValue
34
+ }
35
+ }
36
+
37
+
38
+ export const DELETE = async <T>(path: string = '', defaultValue: T): Promise<T> => {
39
+ try {
40
+ const res = await fetch(`${apiUrl}/${path}`, {
41
+ method: "DELETE",
42
+ headers: {
43
+ Accept: "application/json",
44
+ Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
45
+ },
46
+ cache: 'no-store',
47
+ // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
48
+ // next: { revalidate: 1 }
49
+ })
50
+
51
+ // The return value is *not* serialized
52
+ // You can return Date, Map, Set, etc.
53
+
54
+ // Recommendation: handle errors
55
+ if (res.status !== 200) {
56
+ // This will activate the closest `error.js` Error Boundary
57
+ throw new Error('Failed to fetch data')
58
+ }
59
+
60
+ const data = await res.json()
61
+
62
+ return ((data as T) || defaultValue)
63
+ } catch (err) {
64
+ console.error(err)
65
+ return defaultValue
66
+ }
67
+ }
68
+
69
+ export const POST = async <S, T>(path: string = '', payload: S, defaultValue: T): Promise<T> => {
70
+ try {
71
+ const res = await fetch(`${apiUrl}/${path}`, {
72
+ method: "POST",
73
+ headers: {
74
+ Accept: "application/json",
75
+ "Content-Type": "application/json",
76
+ Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
77
+ },
78
+ body: JSON.stringify(payload),
79
+ // cache: 'no-store',
80
+ // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
81
+ next: { revalidate: 1 }
82
+ })
83
+ // The return value is *not* serialized
84
+ // You can return Date, Map, Set, etc.
85
+
86
+ // Recommendation: handle errors
87
+ if (res.status !== 200) {
88
+ // This will activate the closest `error.js` Error Boundary
89
+ throw new Error('Failed to post data')
90
+ }
91
+
92
+ const data = await res.json()
93
+
94
+ return ((data as T) || defaultValue)
95
+ } catch (err) {
96
+ return defaultValue
97
+ }
98
+ }
99
+
100
+
101
+ export const PUT = async <S, T>(path: string = '', payload: S, defaultValue: T): Promise<T> => {
102
+ try {
103
+ const res = await fetch(`${apiUrl}/${path}`, {
104
+ method: "PUT",
105
+ headers: {
106
+ Accept: "application/json",
107
+ "Content-Type": "application/json",
108
+ Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
109
+ },
110
+ body: JSON.stringify(payload),
111
+ // cache: 'no-store',
112
+ // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
113
+ next: { revalidate: 1 }
114
+ })
115
+ // The return value is *not* serialized
116
+ // You can return Date, Map, Set, etc.
117
+
118
+ // Recommendation: handle errors
119
+ if (res.status !== 200) {
120
+ // This will activate the closest `error.js` Error Boundary
121
+ throw new Error('Failed to post data')
122
+ }
123
+
124
+ const data = await res.json()
125
+
126
+ return ((data as T) || defaultValue)
127
+ } catch (err) {
128
+ return defaultValue
129
+ }
130
+ }
131
+
132
+ export const PATCH = async <S, T>(path: string = '', payload: S, defaultValue: T): Promise<T> => {
133
+ try {
134
+ const res = await fetch(`${apiUrl}/${path}`, {
135
+ method: "PATCH",
136
+ headers: {
137
+ Accept: "application/json",
138
+ "Content-Type": "application/json",
139
+ Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
140
+ },
141
+ body: JSON.stringify(payload),
142
+ // cache: 'no-store',
143
+ // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
144
+ next: { revalidate: 1 }
145
+ })
146
+ // The return value is *not* serialized
147
+ // You can return Date, Map, Set, etc.
148
+
149
+ // Recommendation: handle errors
150
+ if (res.status !== 200) {
151
+ // This will activate the closest `error.js` Error Boundary
152
+ throw new Error('Failed to post data')
153
+ }
154
+
155
+ const data = await res.json()
156
+
157
+ return ((data as T) || defaultValue)
158
+ } catch (err) {
159
+ return defaultValue
160
+ }
161
+ }
src/app/server/actions/python-api.ts DELETED
@@ -1,19 +0,0 @@
1
- "use server"
2
-
3
- import { python } from "pythonia"
4
-
5
- const apiKey = `${process.env.ADMIN_HUGGING_FACE_API_TOKEN || ""}`
6
-
7
- export async function listDatasetCommunityPosts(): Promise<any[]> {
8
-
9
- const { HfApi } = await python("huggingface_hub")
10
-
11
- const hf = await HfApi({
12
- endpoint: "https://huggingface.co",
13
- token: apiKey
14
- })
15
- // TODO
16
-
17
- return [] as any[]
18
- }
19
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/server/actions/submitVideoRequest.ts ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import { ChannelInfo, VideoInfo } from "@/types"
4
+
5
+ import { uploadVideoRequestToDataset } from "./ai-tube-hf/uploadVideoRequestToDataset"
6
+ import { updateQueue } from "./ai-tube-robot/updateQueue"
7
+
8
+ export async function submitVideoRequest({
9
+ channel,
10
+ apiKey,
11
+ title,
12
+ description,
13
+ prompt,
14
+ tags,
15
+ }: {
16
+ channel: ChannelInfo
17
+ apiKey: string
18
+ title: string
19
+ description: string
20
+ prompt: string
21
+ tags: string[]
22
+ }): Promise<VideoInfo> {
23
+ if (!apiKey) {
24
+ throw new Error(`the apiKey is required`)
25
+ }
26
+
27
+ const { videoRequest, videoInfo } = await uploadVideoRequestToDataset({
28
+ channel,
29
+ apiKey,
30
+ title,
31
+ description,
32
+ prompt,
33
+ tags
34
+ })
35
+
36
+ try {
37
+ await updateQueue({ apiKey, channel })
38
+
39
+ return {
40
+ ...videoInfo,
41
+ status: "queued"
42
+ }
43
+ } catch (err) {
44
+ console.error(`failed to update the queue, but this can be done later :)`)
45
+ return videoInfo
46
+ }
47
+ }
src/app/server/actions/{censorship.ts β†’ utils/censorship.ts} RENAMED
File without changes
src/app/server/actions/utils/parseDatasetPrompt.ts ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { ParsedDatasetPrompt } from "@/types"
3
+
4
+ export function parseDatasetPrompt(markdown: string = ""): ParsedDatasetPrompt {
5
+ try {
6
+ const { title, description, prompt } = parseMarkdown(markdown)
7
+
8
+ return {
9
+ title: typeof title === "string" && title ? title : "",
10
+ description: typeof description === "string" && description ? description : "",
11
+ prompt: typeof prompt === "string" && prompt ? prompt : "",
12
+ }
13
+ } catch (err) {
14
+ return {
15
+ title: "",
16
+ description: "",
17
+ prompt: "",
18
+ }
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Simple Markdown Parser to extract sections into a JSON object
24
+ * @param markdown A Markdown string containing Description and Prompt sections
25
+ * @returns A JSON object with { "description": "...", "prompt": "..." }
26
+ */
27
+ function parseMarkdown(markdown: string): ParsedDatasetPrompt {
28
+ // Regular expression to find markdown sections based on the provided structure
29
+ const sectionRegex = /^## (.+?)\n\n([\s\S]+?)(?=\n## |$)/gm;
30
+
31
+ let match;
32
+ const sections: { [key: string]: string } = {};
33
+
34
+ // Iterate over each section match to populate the sections object
35
+ while ((match = sectionRegex.exec(markdown))) {
36
+ const [, key, value] = match;
37
+ sections[key.toLowerCase()] = value.trim();
38
+ }
39
+
40
+ // Create the resulting JSON object with "description" and "prompt" keys
41
+ const result = {
42
+ title: sections['title'] || '',
43
+ description: sections['description'] || '',
44
+ // categories: sections['categories'] || '',
45
+ prompt: sections['prompt'] || '',
46
+ };
47
+
48
+ return result;
49
+ }
src/app/server/actions/utils/parseDatasetReadme.ts ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import metadataParser from "markdown-yaml-metadata-parser"
3
+
4
+ import { ParsedDatasetReadme, ParsedMetadataAndContent } from "@/types"
5
+
6
+ export function parseDatasetReadme(markdown: string = ""): ParsedDatasetReadme {
7
+ try {
8
+ const { metadata, content } = metadataParser(markdown) as ParsedMetadataAndContent
9
+
10
+ // console.log("DEBUG README:", { metadata, content })
11
+
12
+ const { description, prompt } = parseMarkdown(content)
13
+
14
+ return {
15
+ license: typeof metadata?.license === "string" ? metadata.license : "",
16
+ pretty_name: typeof metadata?.pretty_name === "string" ? metadata.pretty_name : "",
17
+ tags: Array.isArray(metadata?.tags) ? metadata.tags : [],
18
+ description,
19
+ prompt,
20
+ }
21
+ } catch (err) {
22
+ return {
23
+ license: "",
24
+ pretty_name: "",
25
+ tags: [], // Hugging Face tags
26
+ description: "",
27
+ prompt: "",
28
+ }
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Simple Markdown Parser to extract sections into a JSON object
34
+ * @param markdown A Markdown string containing Description and Prompt sections
35
+ * @returns A JSON object with { "description": "...", "prompt": "..." }
36
+ */
37
+ function parseMarkdown(markdown: string): {
38
+ description: string
39
+ prompt: string
40
+ // categories: string
41
+ } {
42
+ // Regular expression to find markdown sections based on the provided structure
43
+ const sectionRegex = /^## (.+?)\n\n([\s\S]+?)(?=\n## |$)/gm;
44
+
45
+ let match;
46
+ const sections: { [key: string]: string } = {};
47
+
48
+ // Iterate over each section match to populate the sections object
49
+ while ((match = sectionRegex.exec(markdown))) {
50
+ const [, key, value] = match;
51
+ sections[key.toLowerCase()] = value.trim();
52
+ }
53
+
54
+ // Create the resulting JSON object with "description" and "prompt" keys
55
+ const result = {
56
+ description: sections['description'] || '',
57
+ // categories: sections['categories'] || '',
58
+ prompt: sections['prompt'] || '',
59
+ };
60
+
61
+ return result;
62
+ }
src/app/server/config.ts DELETED
@@ -1,6 +0,0 @@
1
- import path from "node:path"
2
-
3
- // see the .env file fore more informations
4
- export const storagePath = `${process.env.STORAGE_PATH || './sandbox'}`
5
-
6
- export const partiesDirFilePath = path.join(storagePath, "parties")
 
 
 
 
 
 
 
src/app/state/categories.ts CHANGED
@@ -1,13 +1,18 @@
 
 
 
 
 
1
  export const videoCategoriesWithLabels = {
2
  // "random": "Random",
3
  // "lofi": "Lofi Hip-Hop",
4
- "sports": "Sports",
5
- "education": "Education",
6
- "timetravel": "Time Travel", // vlogs etc
7
- "gaming": "Gaming",
8
- "trailers": "Trailers",
9
- "aitubers": "AI tubers",
10
- "ads": "100% Ads",
11
  }
12
 
13
  export type VideoCategory = keyof typeof videoCategoriesWithLabels
 
1
+
2
+
3
+ // TODO:
4
+ // this is obsolete, we should search on the Hugging Face platform instead
5
+
6
  export const videoCategoriesWithLabels = {
7
  // "random": "Random",
8
  // "lofi": "Lofi Hip-Hop",
9
+ "Sports": "Sports",
10
+ "Education": "Education",
11
+ "Time Travel": "Time Travel", // vlogs etc
12
+ // "gaming": "Gaming",
13
+ // "trailers": "Trailers",
14
+ // "aitubers": "AI tubers",
15
+ // "ads": "100% Ads",
16
  }
17
 
18
  export type VideoCategory = keyof typeof videoCategoriesWithLabels
src/app/state/useStore.ts CHANGED
@@ -2,8 +2,7 @@
2
 
3
  import { create } from "zustand"
4
 
5
- import { VideoCategory } from "./categories"
6
- import { ChannelInfo, FullVideoInfo, InterfaceDisplayMode, InterfaceView } from "@/types"
7
 
8
  export const useStore = create<{
9
  displayMode: InterfaceDisplayMode
@@ -18,14 +17,17 @@ export const useStore = create<{
18
  currentChannels: ChannelInfo[]
19
  setCurrentChannels: (currentChannels?: ChannelInfo[]) => void
20
 
21
- currentCategory?: VideoCategory
22
- setCurrentCategory: (currentCategory?: VideoCategory) => void
23
 
24
- currentVideos: FullVideoInfo[]
25
- setCurrentVideos: (currentVideos: FullVideoInfo[]) => void
26
 
27
- currentVideo?: FullVideoInfo
28
- setCurrentVideo: (currentVideo?: FullVideoInfo) => void
 
 
 
29
  }>((set, get) => ({
30
  displayMode: "desktop",
31
  setDisplayMode: (displayMode: InterfaceDisplayMode) => {
@@ -50,18 +52,18 @@ export const useStore = create<{
50
  set({ currentChannels: Array.isArray(currentChannels) ? currentChannels : [] })
51
  },
52
 
53
- currentCategory: undefined,
54
- setCurrentCategory: (currentCategory?: VideoCategory) => {
55
- set({ currentCategory })
56
  },
57
 
58
  currentVideos: [],
59
- setCurrentVideos: (currentVideos: FullVideoInfo[] = []) => {
60
  set({
61
  currentVideos: Array.isArray(currentVideos) ? currentVideos : []
62
  })
63
  },
64
 
65
  currentVideo: undefined,
66
- setCurrentVideo: (currentVideo?: FullVideoInfo) => { set({ currentVideo }) },
67
  }))
 
2
 
3
  import { create } from "zustand"
4
 
5
+ import { ChannelInfo, VideoInfo, InterfaceDisplayMode, InterfaceView } from "@/types"
 
6
 
7
  export const useStore = create<{
8
  displayMode: InterfaceDisplayMode
 
17
  currentChannels: ChannelInfo[]
18
  setCurrentChannels: (currentChannels?: ChannelInfo[]) => void
19
 
20
+ currentTag?: string
21
+ setCurrentTag: (currentTag?: string) => void
22
 
23
+ currentVideos: VideoInfo[]
24
+ setCurrentVideos: (currentVideos: VideoInfo[]) => void
25
 
26
+ currentVideo?: VideoInfo
27
+ setCurrentVideo: (currentVideo?: VideoInfo) => void
28
+
29
+ // currentPrompts: VideoInfo[]
30
+ // setCurrentPrompts: (currentPrompts: VideoInfo[]) => void
31
  }>((set, get) => ({
32
  displayMode: "desktop",
33
  setDisplayMode: (displayMode: InterfaceDisplayMode) => {
 
52
  set({ currentChannels: Array.isArray(currentChannels) ? currentChannels : [] })
53
  },
54
 
55
+ currentTag: undefined,
56
+ setCurrentTag: (currentTag?: string) => {
57
+ set({ currentTag })
58
  },
59
 
60
  currentVideos: [],
61
+ setCurrentVideos: (currentVideos: VideoInfo[] = []) => {
62
  set({
63
  currentVideos: Array.isArray(currentVideos) ? currentVideos : []
64
  })
65
  },
66
 
67
  currentVideo: undefined,
68
+ setCurrentVideo: (currentVideo?: VideoInfo) => { set({ currentVideo }) },
69
  }))
src/app/views/channel-public-view/index.tsx DELETED
@@ -1,56 +0,0 @@
1
- import { useEffect } from "react"
2
-
3
- import { useStore } from "@/app/state/useStore"
4
- import { cn } from "@/lib/utils"
5
- import { FullVideoInfo } from "@/types"
6
- import { VideoList } from "@/app/interface/video-list"
7
-
8
- export function ChannelPublicView() {
9
- const displayMode = useStore(s => s.displayMode)
10
- const setDisplayMode = useStore(s => s.setDisplayMode)
11
- const currentChannel = useStore(s => s.currentChannel)
12
- const setCurrentChannel = useStore(s => s.setCurrentChannel)
13
- const currentCategory = useStore(s => s.currentCategory)
14
- const setCurrentCategory = useStore(s => s.setCurrentCategory)
15
- const currentVideos = useStore(s => s.currentVideos)
16
- const setCurrentVideos = useStore(s => s.setCurrentVideos)
17
- const currentVideo = useStore(s => s.currentVideo)
18
- const setCurrentVideo = useStore(s => s.setCurrentVideo)
19
-
20
- useEffect(() => {
21
-
22
- // we use fake data for now
23
- // this will be pulled from the Hugging Face API
24
- const newVideos: FullVideoInfo[] = [
25
- {
26
- id: "42",
27
- label: "Test Julian",
28
- thumbnailUrl: "",
29
- assetUrl: "",
30
- numberOfViews: 0,
31
- createdAt: "2023-11-27",
32
- categories: [],
33
- channelId: "",
34
- channel: {
35
- id: "",
36
- slug: "",
37
- label: "Hugging Face",
38
- thumbnail: "",
39
- prompt: "",
40
- likes: 0,
41
- }
42
- }
43
- ]
44
- setCurrentVideos(newVideos)
45
- }, [currentCategory])
46
-
47
- return (
48
- <div className={cn(
49
- `flex flex-col`
50
- )}>
51
- <VideoList
52
- videos={currentVideos}
53
- />
54
- </div>
55
- )
56
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/views/home-view/index.tsx CHANGED
@@ -2,7 +2,7 @@ import { useEffect } from "react"
2
 
3
  import { useStore } from "@/app/state/useStore"
4
  import { cn } from "@/lib/utils"
5
- import { FullVideoInfo } from "@/types"
6
 
7
  export function HomeView() {
8
  const displayMode = useStore(s => s.displayMode)
@@ -20,7 +20,7 @@ export function HomeView() {
20
 
21
  // we use fake data for now
22
  // this will be pulled from the Hugging Face API
23
- const newCategoryVideos: FullVideoInfo[] = [
24
  {
25
  id: "42",
26
  label: "Test Julian",
 
2
 
3
  import { useStore } from "@/app/state/useStore"
4
  import { cn } from "@/lib/utils"
5
+ import { VideoInfo } from "@/types"
6
 
7
  export function HomeView() {
8
  const displayMode = useStore(s => s.displayMode)
 
20
 
21
  // we use fake data for now
22
  // this will be pulled from the Hugging Face API
23
+ const newCategoryVideos: VideoInfo[] = [
24
  {
25
  id: "42",
26
  label: "Test Julian",
src/app/views/{channel-admin-view β†’ public-channel-view}/index.tsx RENAMED
@@ -1,48 +1,35 @@
1
- import { useEffect } from "react"
2
 
3
  import { useStore } from "@/app/state/useStore"
4
  import { cn } from "@/lib/utils"
5
- import { FullVideoInfo } from "@/types"
6
  import { VideoList } from "@/app/interface/video-list"
 
 
 
 
7
 
8
- export function ChannelAdminView() {
9
- const displayMode = useStore(s => s.displayMode)
10
- const setDisplayMode = useStore(s => s.setDisplayMode)
11
  const currentChannel = useStore(s => s.currentChannel)
12
- const setCurrentChannel = useStore(s => s.setCurrentChannel)
13
- const currentCategory = useStore(s => s.currentCategory)
14
- const setCurrentCategory = useStore(s => s.setCurrentCategory)
15
  const currentVideos = useStore(s => s.currentVideos)
16
  const setCurrentVideos = useStore(s => s.setCurrentVideos)
17
- const currentVideo = useStore(s => s.currentVideo)
18
  const setCurrentVideo = useStore(s => s.setCurrentVideo)
19
 
20
  useEffect(() => {
 
 
 
21
 
22
- // we use fake data for now
23
- // this will be pulled from the Hugging Face API
24
- const newVideos: FullVideoInfo[] = [
25
- {
26
- id: "42",
27
- label: "Test Julian",
28
- thumbnailUrl: "",
29
- assetUrl: "",
30
- numberOfViews: 0,
31
- createdAt: "2023-11-27",
32
- categories: [],
33
- channelId: "",
34
- channel: {
35
- id: "",
36
- slug: "",
37
- label: "Hugging Face",
38
- thumbnail: "",
39
- prompt: "",
40
- likes: 0,
41
- }
42
- }
43
- ]
44
- setCurrentVideos(newVideos)
45
- }, [currentCategory])
46
 
47
  return (
48
  <div className={cn(
 
1
+ import { useEffect, useTransition } from "react"
2
 
3
  import { useStore } from "@/app/state/useStore"
4
  import { cn } from "@/lib/utils"
5
+ import { VideoInfo } from "@/types"
6
  import { VideoList } from "@/app/interface/video-list"
7
+ import { getChannelVideos } from "@/app/server/actions/api"
8
+ import { useLocalStorage } from "usehooks-ts"
9
+ import { localStorageKeys } from "@/app/state/locaStorageKeys"
10
+ import { defaultSettings } from "@/app/state/defaultSettings"
11
 
12
+ export function PublicChannelView() {
13
+ const [_isPending, startTransition] = useTransition()
 
14
  const currentChannel = useStore(s => s.currentChannel)
 
 
 
15
  const currentVideos = useStore(s => s.currentVideos)
16
  const setCurrentVideos = useStore(s => s.setCurrentVideos)
 
17
  const setCurrentVideo = useStore(s => s.setCurrentVideo)
18
 
19
  useEffect(() => {
20
+ if (!currentChannel) {
21
+ return
22
+ }
23
 
24
+ startTransition(async () => {
25
+ const videos = await getChannelVideos({
26
+ channel: currentChannel,
27
+ })
28
+ console.log("videos:", videos)
29
+ })
30
+
31
+ setCurrentVideos([])
32
+ }, [currentChannel, currentChannel?.id])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  return (
35
  <div className={cn(
src/app/views/{channels-public-view β†’ public-channels-view}/index.tsx RENAMED
@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils"
5
  import { getChannels } from "@/app/server/actions/api"
6
  import { ChannelList } from "@/app/interface/channel-list"
7
 
8
- export function ChannelsPublicView() {
9
  const [_isPending, startTransition] = useTransition()
10
 
11
  const currentChannels = useStore(s => s.currentChannels)
 
5
  import { getChannels } from "@/app/server/actions/api"
6
  import { ChannelList } from "@/app/interface/channel-list"
7
 
8
+ export function PublicChannelsView() {
9
  const [_isPending, startTransition] = useTransition()
10
 
11
  const currentChannels = useStore(s => s.currentChannels)
src/app/views/{video-public-view β†’ public-video-view}/index.tsx RENAMED
@@ -3,13 +3,13 @@ import { useEffect } from "react"
3
  import { useStore } from "@/app/state/useStore"
4
  import { cn } from "@/lib/utils"
5
 
6
- export function VideoPublicView() {
7
  const displayMode = useStore(s => s.displayMode)
8
  const setDisplayMode = useStore(s => s.setDisplayMode)
9
  const currentChannel = useStore(s => s.currentChannel)
10
  const setCurrentChannel = useStore(s => s.setCurrentChannel)
11
- const currentCategory = useStore(s => s.currentCategory)
12
- const setCurrentCategory = useStore(s => s.setCurrentCategory)
13
  const currentVideos = useStore(s => s.currentVideos)
14
  const currentVideo = useStore(s => s.currentVideo)
15
  const setCurrentVideo = useStore(s => s.setCurrentVideo)
 
3
  import { useStore } from "@/app/state/useStore"
4
  import { cn } from "@/lib/utils"
5
 
6
+ export function PublicVideoView() {
7
  const displayMode = useStore(s => s.displayMode)
8
  const setDisplayMode = useStore(s => s.setDisplayMode)
9
  const currentChannel = useStore(s => s.currentChannel)
10
  const setCurrentChannel = useStore(s => s.setCurrentChannel)
11
+ const currentTag = useStore(s => s.currentTag)
12
+ const setCurrentTag = useStore(s => s.setCurrentTag)
13
  const currentVideos = useStore(s => s.currentVideos)
14
  const currentVideo = useStore(s => s.currentVideo)
15
  const setCurrentVideo = useStore(s => s.setCurrentVideo)
src/app/views/user-account-view/index.tsx ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useTransition } from "react"
4
+ import { useLocalStorage } from "usehooks-ts"
5
+
6
+ import { cn } from "@/lib/utils"
7
+ import { Input } from "@/components/ui/input"
8
+ import { localStorageKeys } from "@/app/state/locaStorageKeys"
9
+ import { defaultSettings } from "@/app/state/defaultSettings"
10
+
11
+ export function UserAccountView() {
12
+ const [huggingfaceApiKey, setHuggingfaceApiKey] = useLocalStorage<string>(
13
+ localStorageKeys.huggingfaceApiKey,
14
+ defaultSettings.huggingfaceApiKey
15
+ )
16
+
17
+ return (
18
+ <div className={cn(
19
+ `flex flex-col space-y-4`
20
+ )}>
21
+ <div className="flex flex-col space-y-2">
22
+ <div className="flex flex-row space-x-2 items-center">
23
+ <label className="flex w-64">Hugging Face token:</label>
24
+ <Input
25
+ placeholder="Hugging Face token (with WRITE access)"
26
+ type="password"
27
+ className="font-mono"
28
+ onChange={(x) => {
29
+ setHuggingfaceApiKey(x.target.value)
30
+ }}
31
+ value={huggingfaceApiKey}
32
+ />
33
+ </div>
34
+ <p className="text-neutral-100/70">
35
+ Note: your Hugging Face token must be a <span className="font-bold font-mono text-yellow-300">WRITE</span> access token.
36
+ </p>
37
+ </div>
38
+ {huggingfaceApiKey
39
+ ? <p>You are ready to go!</p>
40
+ : <p>Please setup your accountabove to get started</p>}
41
+ </div>
42
+ )
43
+ }
src/app/views/user-channel-view/index.tsx ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useState, useTransition } from "react"
2
+
3
+ import { useStore } from "@/app/state/useStore"
4
+ import { cn } from "@/lib/utils"
5
+ import { VideoInfo } from "@/types"
6
+ import { VideoList } from "@/app/interface/video-list"
7
+ import { submitVideoRequest, getChannelVideos } from "@/app/server/actions/api"
8
+ import { useLocalStorage } from "usehooks-ts"
9
+ import { localStorageKeys } from "@/app/state/locaStorageKeys"
10
+ import { defaultSettings } from "@/app/state/defaultSettings"
11
+ import { Input } from "@/components/ui/input"
12
+ import { Textarea } from "@/components/ui/textarea"
13
+ import { Button } from "@/components/ui/button"
14
+
15
+ export function UserChannelView() {
16
+ const [_isPending, startTransition] = useTransition()
17
+ const [huggingfaceApiKey, setHuggingfaceApiKey] = useLocalStorage<string>(
18
+ localStorageKeys.huggingfaceApiKey,
19
+ defaultSettings.huggingfaceApiKey
20
+ )
21
+ const [titleDraft, setTitleDraft] = useState("")
22
+ const [promptDraft, setPromptDraft] = useState("")
23
+
24
+ const [isSubmitting, setIsSubmitting] = useState(false)
25
+
26
+ const currentChannel = useStore(s => s.currentChannel)
27
+ const currentVideos = useStore(s => s.currentVideos)
28
+ const setCurrentVideos = useStore(s => s.setCurrentVideos)
29
+ const setCurrentVideo = useStore(s => s.setCurrentVideo)
30
+
31
+ useEffect(() => {
32
+ if (!currentChannel) {
33
+ return
34
+ }
35
+
36
+ startTransition(async () => {
37
+ const videos = await getChannelVideos({
38
+ channel: currentChannel,
39
+ apiKey: huggingfaceApiKey,
40
+ })
41
+ console.log("videos:", videos)
42
+ })
43
+
44
+ setCurrentVideos([])
45
+ }, [huggingfaceApiKey, currentChannel, currentChannel?.id])
46
+
47
+ const handleSubmit = () => {
48
+ if (!currentChannel) {
49
+ return
50
+ }
51
+ if (!titleDraft || !promptDraft) {
52
+ console.log("missing title or prompt")
53
+ return
54
+ }
55
+
56
+ setIsSubmitting(true)
57
+
58
+ startTransition(async () => {
59
+ try {
60
+ const newVideo = await submitVideoRequest({
61
+ channel: currentChannel,
62
+ apiKey: huggingfaceApiKey,
63
+ title: titleDraft,
64
+ prompt: promptDraft
65
+ })
66
+
67
+ // in case of success we update the frontend immediately
68
+ // with our draft video
69
+ setCurrentVideos([newVideo, ...currentVideos])
70
+ setPromptDraft("")
71
+ setTitleDraft("")
72
+
73
+ // also renew the cache on Next's side
74
+ await getChannelVideos({
75
+ channel: currentChannel,
76
+ apiKey: huggingfaceApiKey,
77
+ renewCache: true,
78
+ })
79
+ } catch (err) {
80
+ console.error(err)
81
+ } finally {
82
+ setIsSubmitting(false)
83
+ }
84
+ })
85
+ }
86
+
87
+ return (
88
+ <div className={cn(
89
+ `flex flex-col space-y-8`
90
+ )}>
91
+ <h2 className="text-3xl font-bold">Robot channel settings:</h2>
92
+ <p>TODO</p>
93
+
94
+ <h2 className="text-3xl font-bold">Schedule a new prompt:</h2>
95
+
96
+ <div className="flex flex-row space-x-2 items-start">
97
+ <label className="flex w-24">Title:</label>
98
+ <div className="flex flex-col space-y-2 flex-grow">
99
+ <Input
100
+ placeholder="Title"
101
+ className="font-mono"
102
+ onChange={(x) => {
103
+ setTitleDraft(x.target.value)
104
+ }}
105
+ value={titleDraft}
106
+ />
107
+ <p className="text-neutral-100/70">
108
+ Title of the video, keep it short.
109
+ </p>
110
+ </div>
111
+ </div>
112
+
113
+ <div className="flex flex-row space-x-2 items-start">
114
+ <label className="flex w-24">Prompt:</label>
115
+ <div className="flex flex-col space-y-2 flex-grow">
116
+ <Textarea
117
+ placeholder="Prompt"
118
+ className="font-mono"
119
+ rows={6}
120
+ onChange={(x) => {
121
+ setPromptDraft(x.target.value)
122
+ }}
123
+ value={promptDraft}
124
+ />
125
+ <p className="text-neutral-100/70">
126
+ Describe your video in natural language.
127
+ </p>
128
+ </div>
129
+ </div>
130
+
131
+ <div className="flex flex-row space-x-2 items-center justify-between">
132
+ <Button
133
+ onClick={handleSubmit}
134
+ disabled={isSubmitting}
135
+ className={cn(
136
+ isSubmitting ? `opacity-50` : `opacity-100`
137
+ )}
138
+ >
139
+ {isSubmitting ? 'Adding to the queue..' : 'Add prompt to the queue'}
140
+ </Button>
141
+ <p>Note: It can take a few hours for the video to be generated.</p>
142
+ </div>
143
+
144
+ <h2 className="text-3xl font-bold">Current video prompts:</h2>
145
+
146
+ <VideoList
147
+ videos={currentVideos}
148
+ />
149
+ </div>
150
+ )
151
+ }
src/app/views/{channels-admin-view β†’ user-channels-view}/index.tsx RENAMED
@@ -5,19 +5,21 @@ import { useLocalStorage } from "usehooks-ts"
5
 
6
  import { useStore } from "@/app/state/useStore"
7
  import { cn } from "@/lib/utils"
8
- import { getChannels } from "@/app/server/actions/api"
9
  import { ChannelList } from "@/app/interface/channel-list"
10
- import { Input } from "@/components/ui/input"
11
  import { localStorageKeys } from "@/app/state/locaStorageKeys"
12
  import { defaultSettings } from "@/app/state/defaultSettings"
13
 
14
- export function ChannelsAdminView() {
15
  const [_isPending, startTransition] = useTransition()
16
- const [huggingfaceApiKey, setHuggingfaceApiKey] = useLocalStorage<string>(
17
  localStorageKeys.huggingfaceApiKey,
18
  defaultSettings.huggingfaceApiKey
19
  )
20
 
 
 
 
21
  const currentChannels = useStore(s => s.currentChannels)
22
  const setCurrentChannels = useStore(s => s.setCurrentChannels)
23
  const [isLoaded, setLoaded] = useState(false)
@@ -36,33 +38,20 @@ export function ChannelsAdminView() {
36
  }
37
  })
38
  }
39
- }, [isLoaded])
40
 
41
  return (
42
  <div className={cn(
43
  `flex flex-col space-y-4`
44
  )}>
45
- <div className="flex flex-col space-y-2">
46
- <div className="flex flex-row space-x-2 items-center">
47
- <label className="flex w-64">Hugging Face token:</label>
48
- <Input
49
- placeholder="Hugging Face token (with WRITE access)"
50
- type="password"
51
- className="font-mono"
52
- onChange={(x) => {
53
- setHuggingfaceApiKey(x.target.value)
54
- }}
55
- value={huggingfaceApiKey}
56
- />
57
- </div>
58
- <p className="text-neutral-100/70">
59
- Note: your Hugging Face token must be a <span className="font-bold font-mono text-yellow-300">WRITE</span> access token.
60
- </p>
61
- </div>
62
  {huggingfaceApiKey ?
63
  <ChannelList
64
  channels={currentChannels}
65
- /> : null}
 
 
 
 
66
  </div>
67
  )
68
  }
 
5
 
6
  import { useStore } from "@/app/state/useStore"
7
  import { cn } from "@/lib/utils"
8
+ import { getChannels } from "@/app/server/actions/ai-tube-hf/getChannels"
9
  import { ChannelList } from "@/app/interface/channel-list"
 
10
  import { localStorageKeys } from "@/app/state/locaStorageKeys"
11
  import { defaultSettings } from "@/app/state/defaultSettings"
12
 
13
+ export function UserChannelsView() {
14
  const [_isPending, startTransition] = useTransition()
15
+ const [huggingfaceApiKey,] = useLocalStorage<string>(
16
  localStorageKeys.huggingfaceApiKey,
17
  defaultSettings.huggingfaceApiKey
18
  )
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)
 
38
  }
39
  })
40
  }
41
+ }, [isLoaded, huggingfaceApiKey])
42
 
43
  return (
44
  <div className={cn(
45
  `flex flex-col space-y-4`
46
  )}>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  {huggingfaceApiKey ?
48
  <ChannelList
49
  channels={currentChannels}
50
+ onSelect={(channel) => {
51
+ setCurrentChannel(channel)
52
+ setView("user_channel")
53
+ }}
54
+ /> : <p>Please setup your account to get started creating robot channels!</p>}
55
  </div>
56
  )
57
  }
src/huggingface/hub/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from "./lib";
2
  // Typescript 5 will add 'export type *'
3
  export type {
4
  AccessToken,
 
1
+ export * from "./lib/index";
2
  // Typescript 5 will add 'export type *'
3
  export type {
4
  AccessToken,
src/huggingface/hub/src/lib/commit.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { isFrontend, base64FromBytes } from "../../../shared";
2
  import { HUB_URL } from "../consts";
3
  import { HubApiError, createApiError, InvalidApiResponseFormatError } from "../error";
4
  import type {
@@ -80,6 +80,7 @@ export interface CommitParams {
80
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
81
  */
82
  fetch?: typeof fetch;
 
83
  abortSignal?: AbortSignal;
84
  }
85
 
@@ -193,6 +194,7 @@ export async function* commitIter(params: CommitParams): AsyncGenerator<CommitPr
193
  },
194
  body: JSON.stringify(payload),
195
  signal: abortSignal,
 
196
  }
197
  );
198
 
@@ -268,6 +270,7 @@ export async function* commitIter(params: CommitParams): AsyncGenerator<CommitPr
268
  },
269
  body: JSON.stringify(payload),
270
  signal: abortSignal,
 
271
  }
272
  );
273
 
@@ -360,6 +363,7 @@ export async function* commitIter(params: CommitParams): AsyncGenerator<CommitPr
360
  progressCallback,
361
  },
362
  } as any),
 
363
  });
364
 
365
  if (!res.ok) {
@@ -392,6 +396,7 @@ export async function* commitIter(params: CommitParams): AsyncGenerator<CommitPr
392
  "Content-Type": "application/vnd.git-lfs+json",
393
  },
394
  signal: abortSignal,
 
395
  });
396
 
397
  if (!res.ok) {
@@ -430,6 +435,7 @@ export async function* commitIter(params: CommitParams): AsyncGenerator<CommitPr
430
  }),
431
  },
432
  } as any),
 
433
  });
434
 
435
  if (!res.ok) {
@@ -519,6 +525,7 @@ export async function* commitIter(params: CommitParams): AsyncGenerator<CommitPr
519
  },
520
  },
521
  } as any),
 
522
  }
523
  )
524
  .then(async (res) => {
 
1
+ import { isFrontend, base64FromBytes } from "../../../shared/index";
2
  import { HUB_URL } from "../consts";
3
  import { HubApiError, createApiError, InvalidApiResponseFormatError } from "../error";
4
  import type {
 
80
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
81
  */
82
  fetch?: typeof fetch;
83
+ requestInit?: RequestInit;
84
  abortSignal?: AbortSignal;
85
  }
86
 
 
194
  },
195
  body: JSON.stringify(payload),
196
  signal: abortSignal,
197
+ ...params.requestInit,
198
  }
199
  );
200
 
 
270
  },
271
  body: JSON.stringify(payload),
272
  signal: abortSignal,
273
+ ...params.requestInit,
274
  }
275
  );
276
 
 
363
  progressCallback,
364
  },
365
  } as any),
366
+ ...params.requestInit,
367
  });
368
 
369
  if (!res.ok) {
 
396
  "Content-Type": "application/vnd.git-lfs+json",
397
  },
398
  signal: abortSignal,
399
+ ...params.requestInit,
400
  });
401
 
402
  if (!res.ok) {
 
435
  }),
436
  },
437
  } as any),
438
+ ...params.requestInit,
439
  });
440
 
441
  if (!res.ok) {
 
525
  },
526
  },
527
  } as any),
528
+ ...params.requestInit,
529
  }
530
  )
531
  .then(async (res) => {
src/huggingface/hub/src/lib/create-repo.ts CHANGED
@@ -2,7 +2,7 @@ import { HUB_URL } from "../consts";
2
  import { createApiError } from "../error";
3
  import type { ApiCreateRepoPayload } from "../types/api/api-create-repo";
4
  import type { Credentials, RepoDesignation, SpaceSdk } from "../types/public";
5
- import { base64FromBytes } from "../../../shared";
6
  import { checkCredentials } from "../utils/checkCredentials";
7
  import { toRepoId } from "../utils/toRepoId";
8
 
@@ -22,6 +22,7 @@ export async function createRepo(params: {
22
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
23
  */
24
  fetch?: typeof fetch;
 
25
  }): Promise<{ repoUrl: string }> {
26
  checkCredentials(params.credentials);
27
  const repoId = toRepoId(params.repo);
@@ -64,6 +65,7 @@ export async function createRepo(params: {
64
  Authorization: `Bearer ${params.credentials.accessToken}`,
65
  "Content-Type": "application/json",
66
  },
 
67
  });
68
 
69
  if (!res.ok) {
 
2
  import { createApiError } from "../error";
3
  import type { ApiCreateRepoPayload } from "../types/api/api-create-repo";
4
  import type { Credentials, RepoDesignation, SpaceSdk } from "../types/public";
5
+ import { base64FromBytes } from "../../../shared/index";
6
  import { checkCredentials } from "../utils/checkCredentials";
7
  import { toRepoId } from "../utils/toRepoId";
8
 
 
22
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
23
  */
24
  fetch?: typeof fetch;
25
+ requestInit?: RequestInit;
26
  }): Promise<{ repoUrl: string }> {
27
  checkCredentials(params.credentials);
28
  const repoId = toRepoId(params.repo);
 
65
  Authorization: `Bearer ${params.credentials.accessToken}`,
66
  "Content-Type": "application/json",
67
  },
68
+ ...params.requestInit,
69
  });
70
 
71
  if (!res.ok) {
src/huggingface/hub/src/lib/delete-repo.ts CHANGED
@@ -12,6 +12,7 @@ export async function deleteRepo(params: {
12
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
13
  */
14
  fetch?: typeof fetch;
 
15
  }): Promise<void> {
16
  checkCredentials(params.credentials);
17
  const repoId = toRepoId(params.repo);
@@ -28,6 +29,7 @@ export async function deleteRepo(params: {
28
  Authorization: `Bearer ${params.credentials.accessToken}`,
29
  "Content-Type": "application/json",
30
  },
 
31
  });
32
 
33
  if (!res.ok) {
 
12
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
13
  */
14
  fetch?: typeof fetch;
15
+ requestInit?: RequestInit;
16
  }): Promise<void> {
17
  checkCredentials(params.credentials);
18
  const repoId = toRepoId(params.repo);
 
29
  Authorization: `Bearer ${params.credentials.accessToken}`,
30
  "Content-Type": "application/json",
31
  },
32
+ ...params.requestInit,
33
  });
34
 
35
  if (!res.ok) {
src/huggingface/hub/src/lib/download-file.ts CHANGED
@@ -27,6 +27,7 @@ export async function downloadFile(params: {
27
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
28
  */
29
  fetch?: typeof fetch;
 
30
  }): Promise<Response | null> {
31
  checkCredentials(params.credentials);
32
  const repoId = toRepoId(params.repo);
@@ -47,6 +48,7 @@ export async function downloadFile(params: {
47
  }
48
  : {}),
49
  },
 
50
  });
51
 
52
  if (resp.status === 404 && resp.headers.get("X-Error-Code") === "EntryNotFound") {
 
27
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
28
  */
29
  fetch?: typeof fetch;
30
+ requestInit?: RequestInit;
31
  }): Promise<Response | null> {
32
  checkCredentials(params.credentials);
33
  const repoId = toRepoId(params.repo);
 
48
  }
49
  : {}),
50
  },
51
+ ...params.requestInit,
52
  });
53
 
54
  if (resp.status === 404 && resp.headers.get("X-Error-Code") === "EntryNotFound") {
src/huggingface/hub/src/lib/file-download-info.ts CHANGED
@@ -25,6 +25,10 @@ export async function fileDownloadInfo(params: {
25
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
26
  */
27
  fetch?: typeof fetch;
 
 
 
 
28
  /**
29
  * To get the raw pointer file behind a LFS file
30
  */
@@ -54,6 +58,7 @@ export async function fileDownloadInfo(params: {
54
  Range: "bytes=0-0",
55
  }
56
  : {},
 
57
  });
58
 
59
  if (resp.status === 404 && resp.headers.get("X-Error-Code") === "EntryNotFound") {
 
25
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
26
  */
27
  fetch?: typeof fetch;
28
+ /**
29
+ * Custom fetch parameters
30
+ */
31
+ requestInit?: RequestInit;
32
  /**
33
  * To get the raw pointer file behind a LFS file
34
  */
 
58
  Range: "bytes=0-0",
59
  }
60
  : {},
61
+ ...params.requestInit,
62
  });
63
 
64
  if (resp.status === 404 && resp.headers.get("X-Error-Code") === "EntryNotFound") {
src/huggingface/hub/src/lib/file-exists.ts CHANGED
@@ -14,6 +14,7 @@ export async function fileExists(params: {
14
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
15
  */
16
  fetch?: typeof fetch;
 
17
  }): Promise<boolean> {
18
  checkCredentials(params.credentials);
19
  const repoId = toRepoId(params.repo);
@@ -26,6 +27,7 @@ export async function fileExists(params: {
26
  const resp = await (params.fetch ?? fetch)(url, {
27
  method: "HEAD",
28
  headers: params.credentials ? { Authorization: `Bearer ${params.credentials.accessToken}` } : {},
 
29
  });
30
 
31
  if (resp.status === 404) {
 
14
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
15
  */
16
  fetch?: typeof fetch;
17
+ requestInit?: RequestInit;
18
  }): Promise<boolean> {
19
  checkCredentials(params.credentials);
20
  const repoId = toRepoId(params.repo);
 
27
  const resp = await (params.fetch ?? fetch)(url, {
28
  method: "HEAD",
29
  headers: params.credentials ? { Authorization: `Bearer ${params.credentials.accessToken}` } : {},
30
+ ...params.requestInit,
31
  });
32
 
33
  if (resp.status === 404) {
src/huggingface/hub/src/lib/list-datasets.ts CHANGED
@@ -27,6 +27,7 @@ export async function* listDatasets(params?: {
27
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
28
  */
29
  fetch?: typeof fetch;
 
30
  }): AsyncGenerator<DatasetEntry> {
31
  checkCredentials(params?.credentials);
32
  const search = new URLSearchParams([
 
27
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
28
  */
29
  fetch?: typeof fetch;
30
+ requestInit?: RequestInit;
31
  }): AsyncGenerator<DatasetEntry> {
32
  checkCredentials(params?.credentials);
33
  const search = new URLSearchParams([
src/huggingface/hub/src/lib/list-files.ts CHANGED
@@ -57,6 +57,7 @@ export async function* listFiles(params: {
57
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
58
  */
59
  fetch?: typeof fetch;
 
60
  }): AsyncGenerator<ListFileEntry> {
61
  checkCredentials(params.credentials);
62
  const repoId = toRepoId(params.repo);
@@ -70,6 +71,7 @@ export async function* listFiles(params: {
70
  accept: "application/json",
71
  ...(params.credentials ? { Authorization: `Bearer ${params.credentials.accessToken}` } : undefined),
72
  },
 
73
  });
74
 
75
  if (!res.ok) {
 
57
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
58
  */
59
  fetch?: typeof fetch;
60
+ requestInit?: RequestInit;
61
  }): AsyncGenerator<ListFileEntry> {
62
  checkCredentials(params.credentials);
63
  const repoId = toRepoId(params.repo);
 
71
  accept: "application/json",
72
  ...(params.credentials ? { Authorization: `Bearer ${params.credentials.accessToken}` } : undefined),
73
  },
74
+ ...params.requestInit,
75
  });
76
 
77
  if (!res.ok) {
src/huggingface/hub/src/lib/list-models.ts CHANGED
@@ -29,6 +29,7 @@ export async function* listModels(params?: {
29
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
30
  */
31
  fetch?: typeof fetch;
 
32
  }): AsyncGenerator<ModelEntry> {
33
  checkCredentials(params?.credentials);
34
  const search = new URLSearchParams([
 
29
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
30
  */
31
  fetch?: typeof fetch;
32
+ requestInit?: RequestInit;
33
  }): AsyncGenerator<ModelEntry> {
34
  checkCredentials(params?.credentials);
35
  const search = new URLSearchParams([
src/huggingface/hub/src/lib/list-spaces.ts CHANGED
@@ -28,6 +28,7 @@ export async function* listSpaces(params?: {
28
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
29
  */
30
  fetch?: typeof fetch;
 
31
  /**
32
  * Additional fields to fetch from huggingface.co.
33
  */
 
28
  * Custom fetch function to use instead of the default one, for example to use a proxy or edit headers.
29
  */
30
  fetch?: typeof fetch;
31
+ requestInit?: RequestInit;
32
  /**
33
  * Additional fields to fetch from huggingface.co.
34
  */