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
  */