Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Commit
β’
f62b8d3
1
Parent(s):
a64395a
work in progress, starting to take shape
Browse filesThis view is limited to 50 files because it contains too many changes. Β
See raw diff
- .env +5 -0
- declarations.d.ts +1 -0
- package-lock.json +65 -46
- package.json +2 -2
- src/app/interface/channel-card/index.tsx +17 -4
- src/app/interface/channel-list/index.tsx +4 -0
- src/app/interface/left-menu/index.tsx +47 -26
- src/app/interface/top-menu/index.tsx +12 -12
- src/app/interface/video-card/index.tsx +2 -2
- src/app/interface/video-list/index.tsx +2 -2
- src/app/main.tsx +19 -12
- src/app/server/actions/ai-tube-hf/README.md +3 -0
- src/app/server/actions/{api.ts β ai-tube-hf/getChannels.ts} +53 -16
- src/app/server/actions/ai-tube-hf/getIndex.ts +48 -0
- src/app/server/actions/ai-tube-hf/getVideoRequestsFromChannel.ts +112 -0
- src/app/server/actions/ai-tube-hf/uploadVideoRequestToDataset.ts +114 -0
- src/app/server/actions/ai-tube-robot/README.md +3 -0
- src/app/server/actions/ai-tube-robot/updateQueue.ts +42 -0
- src/app/server/actions/config.ts +9 -0
- src/app/server/actions/datasets.ts +0 -32
- src/app/server/actions/{generateImage.ts β generation/generateImage.txt} +0 -0
- src/app/server/actions/{generateStoryLines.txt β generation/generateStoryLines.txt} +0 -0
- src/app/server/actions/generation/videochain.ts +161 -0
- src/app/server/actions/python-api.ts +0 -19
- src/app/server/actions/submitVideoRequest.ts +47 -0
- src/app/server/actions/{censorship.ts β utils/censorship.ts} +0 -0
- src/app/server/actions/utils/parseDatasetPrompt.ts +49 -0
- src/app/server/actions/utils/parseDatasetReadme.ts +62 -0
- src/app/server/config.ts +0 -6
- src/app/state/categories.ts +12 -7
- src/app/state/useStore.ts +15 -13
- src/app/views/channel-public-view/index.tsx +0 -56
- src/app/views/home-view/index.tsx +2 -2
- src/app/views/{channel-admin-view β public-channel-view}/index.tsx +20 -33
- src/app/views/{channels-public-view β public-channels-view}/index.tsx +1 -1
- src/app/views/{video-public-view β public-video-view}/index.tsx +3 -3
- src/app/views/user-account-view/index.tsx +43 -0
- src/app/views/user-channel-view/index.tsx +151 -0
- src/app/views/{channels-admin-view β user-channels-view}/index.tsx +12 -23
- src/huggingface/hub/src/index.ts +1 -1
- src/huggingface/hub/src/lib/commit.ts +8 -1
- src/huggingface/hub/src/lib/create-repo.ts +3 -1
- src/huggingface/hub/src/lib/delete-repo.ts +2 -0
- src/huggingface/hub/src/lib/download-file.ts +2 -0
- src/huggingface/hub/src/lib/file-download-info.ts +5 -0
- src/huggingface/hub/src/lib/file-exists.ts +2 -0
- src/huggingface/hub/src/lib/list-datasets.ts +1 -0
- src/huggingface/hub/src/lib/list-files.ts +2 -0
- src/huggingface/hub/src/lib/list-models.ts +1 -0
- 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.
|
68 |
"zustand": "^4.4.1"
|
69 |
},
|
70 |
"devDependencies": {
|
@@ -95,9 +95,9 @@
|
|
95 |
}
|
96 |
},
|
97 |
"node_modules/@babel/runtime": {
|
98 |
-
"version": "7.23.
|
99 |
-
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.
|
100 |
-
"integrity": "sha512-
|
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.
|
3112 |
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.
|
3113 |
-
"integrity": "sha512
|
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.
|
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 |
-
`
|
17 |
-
`
|
|
|
|
|
|
|
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
|
15 |
-
`justify-items-stretch`,
|
16 |
`w-24 px-1 pt-4`,
|
17 |
// `bg-orange-500`,
|
18 |
)}>
|
19 |
-
<
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 {
|
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
|
11 |
-
const
|
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-
|
28 |
-
`
|
29 |
)}>
|
30 |
-
<div className="flex flex-row items-center">
|
31 |
-
<span className="text-
|
32 |
-
<span className="text-
|
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 |
{/* 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 |
-
|
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 |
-
|
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 |
{/* 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 {
|
3 |
|
4 |
export function VideoCard({
|
5 |
video,
|
6 |
className = "",
|
7 |
}: {
|
8 |
-
video:
|
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 {
|
3 |
|
4 |
import { VideoCard } from "../video-card"
|
5 |
|
@@ -8,7 +8,7 @@ export function VideoList({
|
|
8 |
layout = "flex",
|
9 |
className = "",
|
10 |
}: {
|
11 |
-
videos:
|
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 {
|
9 |
-
import {
|
10 |
-
import {
|
11 |
-
import {
|
12 |
-
import {
|
|
|
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 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 [
|
51 |
? chunks
|
52 |
: [name, name]
|
53 |
|
54 |
-
// console.log(`found a candidate dataset "${datasetName}" owned by @${
|
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 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
|
74 |
const channel: ChannelInfo = {
|
75 |
id,
|
|
|
|
|
76 |
slug,
|
77 |
-
label,
|
|
|
78 |
thumbnail,
|
79 |
prompt,
|
80 |
likes,
|
81 |
-
|
|
|
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 |
-
"
|
5 |
-
"
|
6 |
-
"
|
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 {
|
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 |
-
|
22 |
-
|
23 |
|
24 |
-
currentVideos:
|
25 |
-
setCurrentVideos: (currentVideos:
|
26 |
|
27 |
-
currentVideo?:
|
28 |
-
setCurrentVideo: (currentVideo?:
|
|
|
|
|
|
|
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 |
-
|
54 |
-
|
55 |
-
set({
|
56 |
},
|
57 |
|
58 |
currentVideos: [],
|
59 |
-
setCurrentVideos: (currentVideos:
|
60 |
set({
|
61 |
currentVideos: Array.isArray(currentVideos) ? currentVideos : []
|
62 |
})
|
63 |
},
|
64 |
|
65 |
currentVideo: undefined,
|
66 |
-
setCurrentVideo: (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 {
|
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:
|
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 {
|
6 |
import { VideoList } from "@/app/interface/video-list"
|
|
|
|
|
|
|
|
|
7 |
|
8 |
-
export function
|
9 |
-
const
|
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 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
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
|
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
|
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
|
12 |
-
const
|
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/
|
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
|
15 |
const [_isPending, startTransition] = useTransition()
|
16 |
-
const [huggingfaceApiKey,
|
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 |
-
|
|
|
|
|
|
|
|
|
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 |
*/
|