Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Commit
•
93f8352
1
Parent(s):
3942037
small beginnings..
Browse files- src/app/interface/channel-card/index.tsx +12 -9
- src/app/interface/channel-list/index.tsx +1 -1
- src/app/interface/left-menu/index.tsx +2 -2
- src/app/interface/top-header/index.tsx +3 -6
- src/app/server/actions/ai-tube-hf/getChannels.ts +17 -10
- src/app/server/actions/ai-tube-hf/getVideoRequestsFromChannel.ts +9 -3
- src/app/server/actions/ai-tube-hf/uploadVideoRequestToDataset.ts +2 -2
- src/app/server/actions/utils/parseDatasetPrompt.ts +17 -15
- src/app/server/actions/utils/parseDatasetReadme.ts +32 -17
- src/app/views/public-channels-view/index.tsx +1 -0
- src/app/views/user-channels-view/index.tsx +1 -0
- src/types.ts +5 -0
- tailwind.config.js +3 -0
src/app/interface/channel-card/index.tsx
CHANGED
@@ -16,9 +16,10 @@ export function ChannelCard({
|
|
16 |
className={cn(
|
17 |
`flex flex-col`,
|
18 |
`items-center justify-center`,
|
|
|
19 |
`w-52 h-52`,
|
20 |
`rounded-lg`,
|
21 |
-
`
|
22 |
`text-neutral-100/80 hover:text-neutral-100/100`,
|
23 |
`cursor-pointer`,
|
24 |
className,
|
@@ -31,31 +32,33 @@ export function ChannelCard({
|
|
31 |
>
|
32 |
<div
|
33 |
className={cn(
|
34 |
-
`
|
|
|
|
|
35 |
)}
|
36 |
>
|
37 |
-
<img src=
|
38 |
</div>
|
39 |
|
40 |
<div className={cn(
|
41 |
`flex flex-col`,
|
42 |
`items-center justify-center text-center`,
|
43 |
-
`space-y-
|
44 |
)}>
|
45 |
-
<div className="text-center text-
|
46 |
{/*<div className="text-center text-sm font-semibold">
|
47 |
by <a href={
|
48 |
`https://huggingface.co/${channel.datasetUser}`
|
49 |
} target="_blank">@{channel.datasetUser}</a>
|
50 |
</div>
|
51 |
*/}
|
52 |
-
<div className="text-center text-
|
53 |
@{channel.datasetUser}
|
54 |
</div>
|
55 |
-
<div className="flex flex-row items-center justify-center">
|
56 |
-
<div className="text-center text-
|
57 |
<div className="px-1">-</div>
|
58 |
-
<div className="text-center text-
|
59 |
</div>
|
60 |
</div>
|
61 |
</div>
|
|
|
16 |
className={cn(
|
17 |
`flex flex-col`,
|
18 |
`items-center justify-center`,
|
19 |
+
`space-y-1`,
|
20 |
`w-52 h-52`,
|
21 |
`rounded-lg`,
|
22 |
+
`hover:bg-neutral-800/30`,
|
23 |
`text-neutral-100/80 hover:text-neutral-100/100`,
|
24 |
`cursor-pointer`,
|
25 |
className,
|
|
|
32 |
>
|
33 |
<div
|
34 |
className={cn(
|
35 |
+
`flex flex-col items-center justify-center`,
|
36 |
+
`rounded-full overflow-hidden`,
|
37 |
+
`w-26 h-26`
|
38 |
)}
|
39 |
>
|
40 |
+
<img src={channel.thumbnail} />
|
41 |
</div>
|
42 |
|
43 |
<div className={cn(
|
44 |
`flex flex-col`,
|
45 |
`items-center justify-center text-center`,
|
46 |
+
`space-y-1`
|
47 |
)}>
|
48 |
+
<div className="text-center text-base font-medium text-zinc-100">{channel.label}</div>
|
49 |
{/*<div className="text-center text-sm font-semibold">
|
50 |
by <a href={
|
51 |
`https://huggingface.co/${channel.datasetUser}`
|
52 |
} target="_blank">@{channel.datasetUser}</a>
|
53 |
</div>
|
54 |
*/}
|
55 |
+
<div className="text-center text-xs font-medium">
|
56 |
@{channel.datasetUser}
|
57 |
</div>
|
58 |
+
<div className="flex flex-row items-center justify-center text-neutral-400">
|
59 |
+
<div className="text-center text-xs">{0} videos</div>
|
60 |
<div className="px-1">-</div>
|
61 |
+
<div className="text-center text-xs">{channel.likes} likes</div>
|
62 |
</div>
|
63 |
</div>
|
64 |
</div>
|
src/app/interface/channel-list/index.tsx
CHANGED
@@ -29,7 +29,7 @@ export function ChannelList({
|
|
29 |
<div
|
30 |
className={cn(
|
31 |
layout === "grid"
|
32 |
-
? `grid grid-cols-
|
33 |
: `flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4`,
|
34 |
className,
|
35 |
)}
|
|
|
29 |
<div
|
30 |
className={cn(
|
31 |
layout === "grid"
|
32 |
+
? `grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4`
|
33 |
: `flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4`,
|
34 |
className,
|
35 |
)}
|
src/app/interface/left-menu/index.tsx
CHANGED
@@ -33,13 +33,13 @@ export function LeftMenu() {
|
|
33 |
>
|
34 |
Discover
|
35 |
</MenuItem>
|
36 |
-
|
37 |
icon={<GrChannel className="h-5 w-5" />}
|
38 |
selected={view === "public_channels"}
|
39 |
onClick={() => setView("public_channels")}
|
40 |
>
|
41 |
Channels
|
42 |
-
</MenuItem>
|
43 |
</div>
|
44 |
<div className={cn(
|
45 |
`flex flex-col w-full`,
|
|
|
33 |
>
|
34 |
Discover
|
35 |
</MenuItem>
|
36 |
+
<MenuItem
|
37 |
icon={<GrChannel className="h-5 w-5" />}
|
38 |
selected={view === "public_channels"}
|
39 |
onClick={() => setView("public_channels")}
|
40 |
>
|
41 |
Channels
|
42 |
+
</MenuItem>
|
43 |
</div>
|
44 |
<div className={cn(
|
45 |
`flex flex-col w-full`,
|
src/app/interface/top-header/index.tsx
CHANGED
@@ -86,11 +86,7 @@ export function TopHeader() {
|
|
86 |
</div>
|
87 |
</div>
|
88 |
<div className="font-semibold">
|
89 |
-
|
90 |
-
? "My account"
|
91 |
-
: view === "public_channels"
|
92 |
-
? "AI Channels"
|
93 |
-
: "AiTube" }
|
94 |
</div>
|
95 |
</div>
|
96 |
</div>
|
@@ -98,8 +94,9 @@ export function TopHeader() {
|
|
98 |
`transition-all duration-200 ease-in-out`,
|
99 |
`flex flex-col items-center justify-center`,
|
100 |
`px-4 py-2 w-max-64`,
|
|
|
101 |
)}>
|
102 |
-
|
103 |
</div>
|
104 |
<div className={cn()}>
|
105 |
{/* more buttons? unused for now */}
|
|
|
86 |
</div>
|
87 |
</div>
|
88 |
<div className="font-semibold">
|
89 |
+
AiTube
|
|
|
|
|
|
|
|
|
90 |
</div>
|
91 |
</div>
|
92 |
</div>
|
|
|
94 |
`transition-all duration-200 ease-in-out`,
|
95 |
`flex flex-col items-center justify-center`,
|
96 |
`px-4 py-2 w-max-64`,
|
97 |
+
`text-neutral-400 text-sm italic`
|
98 |
)}>
|
99 |
+
Ai Tube is a platform where all videos are generated using AI, for research and experimentation purposes.
|
100 |
</div>
|
101 |
<div className={cn()}>
|
102 |
{/* more buttons? unused for now */}
|
src/app/server/actions/ai-tube-hf/getChannels.ts
CHANGED
@@ -11,7 +11,7 @@ export async function getChannels(options: {
|
|
11 |
owner?: string
|
12 |
renewCache?: boolean
|
13 |
} = {}): Promise<ChannelInfo[]> {
|
14 |
-
|
15 |
let credentials: Credentials = adminCredentials
|
16 |
let owner = options?.owner
|
17 |
|
@@ -38,13 +38,12 @@ export async function getChannels(options: {
|
|
38 |
? { owner } // search channels of a specific user
|
39 |
: prefix // global search (note: might be costly?)
|
40 |
|
41 |
-
|
42 |
-
|
43 |
for await (const { id, name, likes, updatedAt } of listDatasets({
|
44 |
search,
|
45 |
credentials,
|
46 |
requestInit: options?.renewCache
|
47 |
-
? { cache: "no-
|
48 |
: undefined
|
49 |
})) {
|
50 |
|
@@ -56,7 +55,8 @@ export async function getChannels(options: {
|
|
56 |
: [name, name]
|
57 |
|
58 |
// console.log(`found a candidate dataset "${datasetName}" owned by @${datasetUser}`)
|
59 |
-
|
|
|
60 |
if (!datasetName.startsWith(prefix)) {
|
61 |
continue
|
62 |
}
|
@@ -73,9 +73,10 @@ export async function getChannels(options: {
|
|
73 |
// TODO parse the README to get the proper label
|
74 |
let label = slug.replaceAll("-", " ")
|
75 |
|
76 |
-
|
77 |
let prompt = ""
|
78 |
let description = ""
|
|
|
79 |
let tags: string[] = []
|
80 |
|
81 |
// console.log(`going to read datasets/${name}`)
|
@@ -94,15 +95,21 @@ export async function getChannels(options: {
|
|
94 |
prompt = parsedDatasetReadme.prompt
|
95 |
label = parsedDatasetReadme.pretty_name
|
96 |
description = parsedDatasetReadme.description
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
|
98 |
-
|
99 |
|
100 |
tags = parsedDatasetReadme.tags
|
101 |
-
.
|
102 |
-
.map(tag => tag.replaceAll(prefix, "").trim()) // remove the prefix
|
103 |
.filter(tag => tag) // remove empty tags
|
104 |
|
105 |
-
|
106 |
} catch (err) {
|
107 |
// console.log("failed to read the readme:", err)
|
108 |
}
|
|
|
11 |
owner?: string
|
12 |
renewCache?: boolean
|
13 |
} = {}): Promise<ChannelInfo[]> {
|
14 |
+
// console.log("getChannels")
|
15 |
let credentials: Credentials = adminCredentials
|
16 |
let owner = options?.owner
|
17 |
|
|
|
38 |
? { owner } // search channels of a specific user
|
39 |
: prefix // global search (note: might be costly?)
|
40 |
|
41 |
+
|
|
|
42 |
for await (const { id, name, likes, updatedAt } of listDatasets({
|
43 |
search,
|
44 |
credentials,
|
45 |
requestInit: options?.renewCache
|
46 |
+
? { cache: "no-store" }
|
47 |
: undefined
|
48 |
})) {
|
49 |
|
|
|
55 |
: [name, name]
|
56 |
|
57 |
// console.log(`found a candidate dataset "${datasetName}" owned by @${datasetUser}`)
|
58 |
+
|
59 |
+
// ignore channels which don't start with ai-tube
|
60 |
if (!datasetName.startsWith(prefix)) {
|
61 |
continue
|
62 |
}
|
|
|
73 |
// TODO parse the README to get the proper label
|
74 |
let label = slug.replaceAll("-", " ")
|
75 |
|
76 |
+
let thumbnail = ""
|
77 |
let prompt = ""
|
78 |
let description = ""
|
79 |
+
let voice = ""
|
80 |
let tags: string[] = []
|
81 |
|
82 |
// console.log(`going to read datasets/${name}`)
|
|
|
95 |
prompt = parsedDatasetReadme.prompt
|
96 |
label = parsedDatasetReadme.pretty_name
|
97 |
description = parsedDatasetReadme.description
|
98 |
+
thumbnail = parsedDatasetReadme.thumbnail || "thumbnail.jpg"
|
99 |
+
|
100 |
+
thumbnail =
|
101 |
+
thumbnail.startsWith("http")
|
102 |
+
? thumbnail
|
103 |
+
: (thumbnail.endsWith(".jpg") || thumbnail.endsWith(".jpeg"))
|
104 |
+
? `https://huggingface.co/datasets/${name}/resolve/main/${thumbnail}`
|
105 |
+
: ""
|
106 |
|
107 |
+
voice = parsedDatasetReadme.voice
|
108 |
|
109 |
tags = parsedDatasetReadme.tags
|
110 |
+
.map(tag => tag.trim()) // clean them up
|
|
|
111 |
.filter(tag => tag) // remove empty tags
|
112 |
|
|
|
113 |
} catch (err) {
|
114 |
// console.log("failed to read the readme:", err)
|
115 |
}
|
src/app/server/actions/ai-tube-hf/getVideoRequestsFromChannel.ts
CHANGED
@@ -71,22 +71,28 @@ export async function getVideoRequestsFromChannel({
|
|
71 |
continue
|
72 |
}
|
73 |
|
74 |
-
const { title, description, tags, prompt } = parseDatasetPrompt(rawMarkdown)
|
75 |
|
76 |
if (!title || !description || !prompt) {
|
77 |
// console.log("dataset prompt is incomplete or unparseable")
|
78 |
continue
|
79 |
}
|
80 |
// console.log("prompt parsed markdown:", { title, description, tags })
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
|
82 |
const video: VideoRequest = {
|
83 |
id,
|
84 |
label: title,
|
85 |
description,
|
86 |
prompt,
|
87 |
-
thumbnailUrl
|
88 |
|
89 |
-
updatedAt: file.lastCommit?.date ||
|
90 |
tags, // read them from the file?
|
91 |
channel,
|
92 |
}
|
|
|
71 |
continue
|
72 |
}
|
73 |
|
74 |
+
const { title, description, tags, prompt, thumbnail } = parseDatasetPrompt(rawMarkdown)
|
75 |
|
76 |
if (!title || !description || !prompt) {
|
77 |
// console.log("dataset prompt is incomplete or unparseable")
|
78 |
continue
|
79 |
}
|
80 |
// console.log("prompt parsed markdown:", { title, description, tags })
|
81 |
+
let thumbnailUrl =
|
82 |
+
thumbnail.startsWith("http")
|
83 |
+
? thumbnail
|
84 |
+
: (thumbnail.endsWith(".jpg") || thumbnail.endsWith(".jpeg"))
|
85 |
+
? `https://huggingface.co/${repo}/resolve/main/${thumbnail}`
|
86 |
+
: ""
|
87 |
|
88 |
const video: VideoRequest = {
|
89 |
id,
|
90 |
label: title,
|
91 |
description,
|
92 |
prompt,
|
93 |
+
thumbnailUrl,
|
94 |
|
95 |
+
updatedAt: file.lastCommit?.date || new Date().toISOString(),
|
96 |
tags, // read them from the file?
|
97 |
channel,
|
98 |
}
|
src/app/server/actions/ai-tube-hf/uploadVideoRequestToDataset.ts
CHANGED
@@ -75,7 +75,7 @@ ${prompt}
|
|
75 |
label: title,
|
76 |
description,
|
77 |
prompt,
|
78 |
-
thumbnailUrl:
|
79 |
updatedAt: new Date().toISOString(),
|
80 |
tags,
|
81 |
channel,
|
@@ -87,7 +87,7 @@ ${prompt}
|
|
87 |
label: title,
|
88 |
description,
|
89 |
prompt,
|
90 |
-
thumbnailUrl:
|
91 |
assetUrl: "", // will be generated in async
|
92 |
numberOfViews: 0,
|
93 |
numberOfLikes: 0,
|
|
|
75 |
label: title,
|
76 |
description,
|
77 |
prompt,
|
78 |
+
thumbnailUrl: channel.thumbnail,
|
79 |
updatedAt: new Date().toISOString(),
|
80 |
tags,
|
81 |
channel,
|
|
|
87 |
label: title,
|
88 |
description,
|
89 |
prompt,
|
90 |
+
thumbnailUrl: channel.thumbnail, // will be generated in async
|
91 |
assetUrl: "", // will be generated in async
|
92 |
numberOfViews: 0,
|
93 |
numberOfLikes: 0,
|
src/app/server/actions/utils/parseDatasetPrompt.ts
CHANGED
@@ -3,13 +3,14 @@ import { ParsedDatasetPrompt } from "@/types"
|
|
3 |
|
4 |
export function parseDatasetPrompt(markdown: string = ""): ParsedDatasetPrompt {
|
5 |
try {
|
6 |
-
const { title, description, tags, prompt } = parseMarkdown(markdown)
|
7 |
|
8 |
return {
|
9 |
title: typeof title === "string" && title ? title : "",
|
10 |
description: typeof description === "string" && description ? description : "",
|
11 |
tags: tags && typeof tags === "string" ? tags.split("-").map(x => x.trim()).filter(x => x) : [],
|
12 |
prompt: typeof prompt === "string" && prompt ? prompt : "",
|
|
|
13 |
}
|
14 |
} catch (err) {
|
15 |
return {
|
@@ -17,6 +18,7 @@ export function parseDatasetPrompt(markdown: string = ""): ParsedDatasetPrompt {
|
|
17 |
description: "",
|
18 |
tags: [],
|
19 |
prompt: "",
|
|
|
20 |
}
|
21 |
}
|
22 |
}
|
@@ -31,26 +33,26 @@ function parseMarkdown(markdown: string): {
|
|
31 |
description: string
|
32 |
tags: string
|
33 |
prompt: string
|
|
|
34 |
} {
|
35 |
-
|
36 |
-
|
|
|
|
|
37 |
|
38 |
-
let match;
|
39 |
const sections: { [key: string]: string } = {};
|
40 |
|
41 |
-
|
42 |
while ((match = sectionRegex.exec(markdown))) {
|
43 |
-
const
|
44 |
-
sections[key.toLowerCase()] =
|
45 |
}
|
46 |
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
};
|
54 |
-
|
55 |
-
return result;
|
56 |
}
|
|
|
3 |
|
4 |
export function parseDatasetPrompt(markdown: string = ""): ParsedDatasetPrompt {
|
5 |
try {
|
6 |
+
const { title, description, tags, prompt, thumbnail } = parseMarkdown(markdown)
|
7 |
|
8 |
return {
|
9 |
title: typeof title === "string" && title ? title : "",
|
10 |
description: typeof description === "string" && description ? description : "",
|
11 |
tags: tags && typeof tags === "string" ? tags.split("-").map(x => x.trim()).filter(x => x) : [],
|
12 |
prompt: typeof prompt === "string" && prompt ? prompt : "",
|
13 |
+
thumbnail: typeof thumbnail === "string" && thumbnail ? thumbnail : "",
|
14 |
}
|
15 |
} catch (err) {
|
16 |
return {
|
|
|
18 |
description: "",
|
19 |
tags: [],
|
20 |
prompt: "",
|
21 |
+
thumbnail: "",
|
22 |
}
|
23 |
}
|
24 |
}
|
|
|
33 |
description: string
|
34 |
tags: string
|
35 |
prompt: string
|
36 |
+
thumbnail: string
|
37 |
} {
|
38 |
+
markdown = markdown.trim()
|
39 |
+
|
40 |
+
// Improved regular expression to find markdown sections and accommodate multi-line content.
|
41 |
+
const sectionRegex = /^#+\s+(?<key>.+?)\n\n?(?<content>[^#]+)/gm;
|
42 |
|
|
|
43 |
const sections: { [key: string]: string } = {};
|
44 |
|
45 |
+
let match;
|
46 |
while ((match = sectionRegex.exec(markdown))) {
|
47 |
+
const { key, content } = match.groups as { key: string; content: string };
|
48 |
+
sections[key.trim().toLowerCase()] = content.trim();
|
49 |
}
|
50 |
|
51 |
+
return {
|
52 |
+
title: sections["title"] || "",
|
53 |
+
description: sections["description"] || "",
|
54 |
+
tags: sections["tags"] || "",
|
55 |
+
prompt: sections["prompt"] || "",
|
56 |
+
thumbnail: sections["thumbnail"] || "",
|
57 |
};
|
|
|
|
|
58 |
}
|
src/app/server/actions/utils/parseDatasetReadme.ts
CHANGED
@@ -5,14 +5,22 @@ 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 |
-
|
|
|
|
|
11 |
|
12 |
return {
|
13 |
license: typeof metadata?.license === "string" ? metadata.license : "",
|
14 |
pretty_name: typeof metadata?.pretty_name === "string" ? metadata.pretty_name : "",
|
15 |
-
|
|
|
|
|
|
|
|
|
16 |
description,
|
17 |
prompt,
|
18 |
}
|
@@ -20,7 +28,11 @@ export function parseDatasetReadme(markdown: string = ""): ParsedDatasetReadme {
|
|
20 |
return {
|
21 |
license: "",
|
22 |
pretty_name: "",
|
23 |
-
|
|
|
|
|
|
|
|
|
24 |
description: "",
|
25 |
prompt: "",
|
26 |
}
|
@@ -33,28 +45,31 @@ export function parseDatasetReadme(markdown: string = ""): ParsedDatasetReadme {
|
|
33 |
* @returns A JSON object with { "description": "...", "prompt": "..." }
|
34 |
*/
|
35 |
function parseMarkdown(markdown: string): {
|
|
|
|
|
|
|
36 |
description: string
|
37 |
prompt: string
|
38 |
-
|
39 |
} {
|
40 |
-
//
|
41 |
-
|
|
|
42 |
|
43 |
-
let match;
|
44 |
const sections: { [key: string]: string } = {};
|
45 |
|
46 |
-
|
47 |
while ((match = sectionRegex.exec(markdown))) {
|
48 |
-
const
|
49 |
-
sections[key.toLowerCase()] =
|
50 |
}
|
51 |
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
|
|
|
|
57 |
};
|
58 |
-
|
59 |
-
return result;
|
60 |
}
|
|
|
5 |
|
6 |
export function parseDatasetReadme(markdown: string = ""): ParsedDatasetReadme {
|
7 |
try {
|
8 |
+
markdown = markdown.trim()
|
9 |
+
|
10 |
const { metadata, content } = metadataParser(markdown) as ParsedMetadataAndContent
|
11 |
|
12 |
+
// console.log("DEBUG README:", { metadata, content })
|
13 |
+
|
14 |
+
const { model, thumbnail, voice, description, prompt, tags } = parseMarkdown(content)
|
15 |
|
16 |
return {
|
17 |
license: typeof metadata?.license === "string" ? metadata.license : "",
|
18 |
pretty_name: typeof metadata?.pretty_name === "string" ? metadata.pretty_name : "",
|
19 |
+
hf_tags: Array.isArray(metadata?.tags) ? metadata.tags : [],
|
20 |
+
tags: tags && typeof tags === "string" ? tags.split("-").map(x => x.trim()).filter(x => x) : [],
|
21 |
+
model,
|
22 |
+
thumbnail,
|
23 |
+
voice,
|
24 |
description,
|
25 |
prompt,
|
26 |
}
|
|
|
28 |
return {
|
29 |
license: "",
|
30 |
pretty_name: "",
|
31 |
+
hf_tags: [], // Hugging Face tags
|
32 |
+
tags: [],
|
33 |
+
model: "",
|
34 |
+
thumbnail: "",
|
35 |
+
voice: "",
|
36 |
description: "",
|
37 |
prompt: "",
|
38 |
}
|
|
|
45 |
* @returns A JSON object with { "description": "...", "prompt": "..." }
|
46 |
*/
|
47 |
function parseMarkdown(markdown: string): {
|
48 |
+
model: string
|
49 |
+
thumbnail: string
|
50 |
+
voice: string
|
51 |
description: string
|
52 |
prompt: string
|
53 |
+
tags: string
|
54 |
} {
|
55 |
+
// console.log("markdown:", markdown)
|
56 |
+
// Improved regular expression to find markdown sections and accommodate multi-line content.
|
57 |
+
const sectionRegex = /^#+\s+(?<key>.+?)\n\n?(?<content>[^#]+)/gm;
|
58 |
|
|
|
59 |
const sections: { [key: string]: string } = {};
|
60 |
|
61 |
+
let match;
|
62 |
while ((match = sectionRegex.exec(markdown))) {
|
63 |
+
const { key, content } = match.groups as { key: string; content: string };
|
64 |
+
sections[key.trim().toLowerCase()] = content.trim();
|
65 |
}
|
66 |
|
67 |
+
return {
|
68 |
+
description: sections["description"] || "",
|
69 |
+
model: sections["model"] || "",
|
70 |
+
thumbnail: sections["thumbnail"] || "",
|
71 |
+
voice: sections["voice"] || "",
|
72 |
+
prompt: sections["prompt"] || "",
|
73 |
+
tags: sections["tags"] || "",
|
74 |
};
|
|
|
|
|
75 |
}
|
src/app/views/public-channels-view/index.tsx
CHANGED
@@ -33,6 +33,7 @@ export function PublicChannelsView() {
|
|
33 |
return (
|
34 |
<div className={cn(`flex flex-col`)}>
|
35 |
<ChannelList
|
|
|
36 |
channels={currentChannels}
|
37 |
/>
|
38 |
</div>
|
|
|
33 |
return (
|
34 |
<div className={cn(`flex flex-col`)}>
|
35 |
<ChannelList
|
36 |
+
layout="grid"
|
37 |
channels={currentChannels}
|
38 |
/>
|
39 |
</div>
|
src/app/views/user-channels-view/index.tsx
CHANGED
@@ -69,6 +69,7 @@ export function UserChannelsView() {
|
|
69 |
<div className="flex flex-col space-y-4">
|
70 |
<h2 className="text-3xl font-bold">Your custom channels:</h2>
|
71 |
{currentChannels?.length ? <ChannelList
|
|
|
72 |
channels={currentChannels}
|
73 |
onSelect={(channel) => {
|
74 |
setCurrentChannel(channel)
|
|
|
69 |
<div className="flex flex-col space-y-4">
|
70 |
<h2 className="text-3xl font-bold">Your custom channels:</h2>
|
71 |
{currentChannels?.length ? <ChannelList
|
72 |
+
layout="grid"
|
73 |
channels={currentChannels}
|
74 |
onSelect={(channel) => {
|
75 |
setCurrentChannel(channel)
|
src/types.ts
CHANGED
@@ -374,7 +374,11 @@ export type Settings = {
|
|
374 |
export type ParsedDatasetReadme = {
|
375 |
license: string
|
376 |
pretty_name: string
|
|
|
|
|
|
|
377 |
tags: string[]
|
|
|
378 |
description: string
|
379 |
prompt: string
|
380 |
}
|
@@ -393,6 +397,7 @@ export type ParsedDatasetPrompt = {
|
|
393 |
description: string
|
394 |
tags: string[]
|
395 |
prompt: string
|
|
|
396 |
}
|
397 |
|
398 |
|
|
|
374 |
export type ParsedDatasetReadme = {
|
375 |
license: string
|
376 |
pretty_name: string
|
377 |
+
model: string
|
378 |
+
thumbnail: string
|
379 |
+
voice: string
|
380 |
tags: string[]
|
381 |
+
hf_tags: string[]
|
382 |
description: string
|
383 |
prompt: string
|
384 |
}
|
|
|
397 |
description: string
|
398 |
tags: string[]
|
399 |
prompt: string
|
400 |
+
thumbnail: string
|
401 |
}
|
402 |
|
403 |
|
tailwind.config.js
CHANGED
@@ -53,6 +53,8 @@ module.exports = {
|
|
53 |
20: '5rem', // 80px
|
54 |
21: '5.25rem', // 84px
|
55 |
22: '5.5rem', // 88px
|
|
|
|
|
56 |
},
|
57 |
width: {
|
58 |
17: '4.25rem', // 68px
|
@@ -61,6 +63,7 @@ module.exports = {
|
|
61 |
20: '5rem', // 80px
|
62 |
21: '5.25rem', // 84px
|
63 |
22: '5.5rem', // 88px
|
|
|
64 |
}
|
65 |
},
|
66 |
},
|
|
|
53 |
20: '5rem', // 80px
|
54 |
21: '5.25rem', // 84px
|
55 |
22: '5.5rem', // 88px
|
56 |
+
22: '5.5rem', // 88px
|
57 |
+
26: '6.5rem', // 104px
|
58 |
},
|
59 |
width: {
|
60 |
17: '4.25rem', // 68px
|
|
|
63 |
20: '5rem', // 80px
|
64 |
21: '5.25rem', // 84px
|
65 |
22: '5.5rem', // 88px
|
66 |
+
26: '6.5rem', // 104px
|
67 |
}
|
68 |
},
|
69 |
},
|