Skip to content

Commit 30fac74

Browse files
committed
feat: Add Datasets page and DIV2K
1 parent ac902e4 commit 30fac74

11 files changed

Lines changed: 1162 additions & 17 deletions

File tree

data/tag-categories.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,5 +162,17 @@
162162
"helper:invalid-scale",
163163
"helper:invalid-channels"
164164
]
165+
},
166+
"dataset": {
167+
"name": "Dataset",
168+
"description": "Tags specific to datasets",
169+
"order": 9,
170+
"simple": true,
171+
"tags": [
172+
"dataset:realistic",
173+
"dataset:anime",
174+
"dataset:manga",
175+
"dataset:game-textures"
176+
]
165177
}
166178
}

data/tags.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,22 @@
292292
"name": "RGBA",
293293
"description": "The model upscales images with transparency."
294294
},
295+
"dataset:anime": {
296+
"name": "Anime",
297+
"description": "Anime dataset"
298+
},
299+
"dataset:game-textures": {
300+
"name": "Game Textures",
301+
"description": "Game textures dataset"
302+
},
303+
"dataset:manga": {
304+
"name": "Manga",
305+
"description": "Manga dataset"
306+
},
307+
"dataset:realistic": {
308+
"name": "Realistic",
309+
"description": "Realistic dataset"
310+
},
295311
"helper:invalid-channels": {
296312
"name": "Invalid channels",
297313
"description": "The number of input or output channels of the model is not valid."

data/users.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
"alsa": {
1515
"name": "Alsa"
1616
},
17+
"andrey-ignatov": {
18+
"name": "Andrey Ignatov"
19+
},
1720
"aptitude": {
1821
"name": "aptitude"
1922
},
@@ -83,6 +86,9 @@
8386
"dinjerr": {
8487
"name": "DinJerr"
8588
},
89+
"eirikur-agustsson": {
90+
"name": "Eirikur Agustsson"
91+
},
8692
"eula": {
8793
"name": "end user license agreement#9756"
8894
},
@@ -128,6 +134,9 @@
128134
"jingyunliang": {
129135
"name": "JingyunLiang"
130136
},
137+
"jiqing-wu": {
138+
"name": "Jiqing Wu"
139+
},
131140
"jixiaozhong": {
132141
"name": "jixiaozhong"
133142
},
@@ -164,6 +173,9 @@
164173
"loinne": {
165174
"name": "Loinne"
166175
},
176+
"luc-van-gool": {
177+
"name": "Luc Van Gool"
178+
},
167179
"lyonhrt": {
168180
"name": "LyonHrt"
169181
},
@@ -218,6 +230,9 @@
218230
"pokepress": {
219231
"name": "pokepress"
220232
},
233+
"radu-timofte": {
234+
"name": "Radu Timofte"
235+
},
221236
"rastrum": {
222237
"name": "Rastrum"
223238
},
@@ -248,6 +263,9 @@
248263
"sharekhan": {
249264
"name": "SharekhaN"
250265
},
266+
"shuhang-gu": {
267+
"name": "Shuhang Gu"
268+
},
251269
"sirosky": {
252270
"name": "Sirosky"
253271
},
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import { Menu, Transition } from '@headlessui/react';
2+
import { Fragment } from 'react';
3+
import { BsChevronDown, BsFillTrashFill } from 'react-icons/bs';
4+
import { FiExternalLink } from 'react-icons/fi';
5+
import { SiDropbox, SiGithub, SiGoogledrive, SiMega, SiMicrosoftonedrive } from 'react-icons/si';
6+
import Logo from '../../../public/logo.svg';
7+
import { isSelfHosted, toDirectDownloadLink } from '../../lib/download-util';
8+
import { joinClasses } from '../../lib/util';
9+
import { Link } from './link';
10+
11+
type DatasetDownloadButtonProps = {
12+
url: string;
13+
readonly?: boolean;
14+
onChange?: (url: string) => void;
15+
};
16+
17+
const hostFromUrl = (url: string): string => {
18+
try {
19+
const parsedUrl = new URL(url);
20+
const domainParts = parsedUrl.hostname.split('.');
21+
const domainAndTld = domainParts.slice(domainParts.length - 2).join('.');
22+
23+
if (domainAndTld === 'github.com') {
24+
return 'GitHub';
25+
}
26+
if (parsedUrl.hostname === 'drive.google.com') {
27+
return 'Google Drive';
28+
}
29+
if (parsedUrl.hostname === 'cdn.discordapp.com') {
30+
return 'Discord';
31+
}
32+
if (domainAndTld === '1drv.ms') {
33+
return 'OneDrive';
34+
}
35+
if (domainAndTld === 'mega.nz') {
36+
return 'Mega';
37+
}
38+
if (domainAndTld === 'mediafire.com') {
39+
return 'MediaFire';
40+
}
41+
if (domainAndTld === 'pcloud.link') {
42+
return 'pCloud';
43+
}
44+
if (domainAndTld === 'icedrive.net') {
45+
return 'Icedrive';
46+
}
47+
if (domainAndTld === 'dropbox.com') {
48+
return 'Dropbox';
49+
}
50+
return parsedUrl.hostname;
51+
} catch (e) {
52+
console.debug(e);
53+
return 'an unknown hoster';
54+
}
55+
};
56+
57+
const iconFromHost = (host: string) => {
58+
switch (host) {
59+
case 'GitHub':
60+
return <SiGithub className="block" />;
61+
case 'Google Drive':
62+
return <SiGoogledrive className="block" />;
63+
case 'OneDrive':
64+
return <SiMicrosoftonedrive className="block" />;
65+
case 'Mega':
66+
return <SiMega className="block" />;
67+
case 'Dropbox':
68+
return <SiDropbox className="block" />;
69+
default:
70+
return <FiExternalLink className="block" />;
71+
}
72+
};
73+
74+
const isMirrorExternal = (url: string) => !isSelfHosted(url);
75+
76+
export const DatasetDownloadButton = ({ url, readonly, onChange }: DatasetDownloadButtonProps) => {
77+
const isExternal = isMirrorExternal(url);
78+
const host = hostFromUrl(url);
79+
80+
const showMenu = !readonly;
81+
82+
return (
83+
<div className="flex w-full flex-row gap-0.5 rounded-xl bg-accent-500 dark:bg-accent-400">
84+
<Link
85+
external
86+
className={joinClasses(
87+
'inline-flex h-20 w-full cursor-pointer items-center rounded-l-lg border-0 bg-accent-600 text-center text-lg font-medium text-white transition duration-100 ease-in-out hover:bg-accent-500 dark:bg-accent-500 dark:hover:bg-accent-600',
88+
!showMenu && 'rounded-r-lg'
89+
)}
90+
href={toDirectDownloadLink(url)}
91+
type="button"
92+
>
93+
<div className="w-full">
94+
{isExternal ? (
95+
<FiExternalLink
96+
className="mr-2 h-4 w-4"
97+
viewBox="0 0 22 22"
98+
/>
99+
) : (
100+
<svg
101+
className="mr-2 h-4 w-4 fill-current"
102+
viewBox="0 0 20 20"
103+
xmlns="http://www.w3.org/2000/svg"
104+
>
105+
<path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z" />
106+
</svg>
107+
)}
108+
Visit Dataset Link
109+
{isExternal && (
110+
<div className="text-center text-sm font-normal">
111+
<span className="whitespace-nowrap px-1">Hosted by {host}</span>
112+
</div>
113+
)}
114+
</div>
115+
</Link>
116+
117+
{showMenu && (
118+
<Menu
119+
as="div"
120+
className="relative inline-block text-left"
121+
>
122+
<div>
123+
<Menu.Button
124+
aria-label="Edit dataset URL"
125+
className="inline-flex h-20 w-12 cursor-pointer items-center rounded-r-lg border-0 bg-accent-600 text-center align-middle text-lg font-medium text-white transition duration-100 ease-in-out hover:bg-accent-500 dark:bg-accent-500 dark:hover:bg-accent-600"
126+
>
127+
<BsChevronDown className="w-full" />
128+
</Menu.Button>
129+
</div>
130+
<Transition
131+
as={Fragment}
132+
enter="transition ease-out duration-100"
133+
enterFrom="transform opacity-0 scale-95"
134+
enterTo="transform opacity-100 scale-100"
135+
leave="transition ease-in duration-75"
136+
leaveFrom="transform opacity-100 scale-100"
137+
leaveTo="transform opacity-0 scale-95"
138+
>
139+
<Menu.Items className="absolute right-0 z-50 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-lg bg-fade-100 focus:outline-none dark:bg-fade-700">
140+
<div className="flex flex-col divide-y rounded-lg p-2 shadow-lg">
141+
{url !== '' && (
142+
<Menu.Item
143+
as="a"
144+
className="flex cursor-pointer rounded-lg p-2 transition-colors duration-100 ease-in-out ui-active:bg-fade-300 ui-active:text-black ui-not-active:text-black dark:ui-active:bg-fade-600 dark:ui-active:text-white dark:ui-not-active:text-white"
145+
onClick={() => {
146+
const newUrl = prompt('Edit URL', url);
147+
if (newUrl !== null && onChange) {
148+
onChange(newUrl);
149+
}
150+
}}
151+
>
152+
{isExternal ? (
153+
<div className="flex h-full w-full flex-row items-center gap-2 align-middle">
154+
<div className="m-0 block h-full align-middle">
155+
{iconFromHost(host)}
156+
</div>
157+
<div className="m-0 h-full align-middle">{host}</div>
158+
</div>
159+
) : (
160+
<Logo />
161+
)}
162+
{!readonly && (
163+
<button
164+
onClick={(e) => {
165+
e.stopPropagation();
166+
if (onChange) {
167+
onChange('');
168+
}
169+
}}
170+
>
171+
<BsFillTrashFill />
172+
</button>
173+
)}
174+
</Menu.Item>
175+
)}
176+
{!readonly && url === '' && (
177+
<Menu.Item
178+
as="a"
179+
className="cursor-pointer rounded-lg p-2 text-center transition-colors duration-100 ease-in-out ui-active:bg-fade-300 ui-active:text-black ui-not-active:text-black dark:ui-active:bg-fade-600 dark:ui-active:text-white dark:ui-not-active:text-white"
180+
onClick={() => {
181+
const newUrl = prompt('Enter a new URL');
182+
if (newUrl && onChange) {
183+
onChange(newUrl);
184+
}
185+
}}
186+
>
187+
+ Add URL
188+
</Menu.Item>
189+
)}
190+
</div>
191+
</Menu.Items>
192+
</Transition>
193+
</Menu>
194+
)}
195+
</div>
196+
);
197+
};

src/elements/components/editable-tags.tsx

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Popover, Transition } from '@headlessui/react';
22
import Link from 'next/link';
3-
import { Fragment, useEffect, useState } from 'react';
3+
import { Fragment, useEffect, useMemo, useState } from 'react';
44
import { BsChevronDown } from 'react-icons/bs';
55
import { useTags } from '../../lib/hooks/use-tags';
66
import { addImpliedTags, removeImplyingTags } from '../../lib/implied-tags';
@@ -11,13 +11,20 @@ import style from './editable-tags.module.scss';
1111
export interface SmallTagProps {
1212
tagId: TagId;
1313
name: string;
14+
context?: 'models' | 'datasets';
1415
}
15-
export function SmallTag({ tagId, name }: SmallTagProps) {
16+
export function SmallTag({ tagId, name, context = 'models' }: SmallTagProps) {
1617
return (
1718
<Link
1819
className={`${style.tag} bg-gray-200 text-xs text-gray-800 dark:bg-gray-700 dark:text-gray-100`}
19-
href={`/?t=${encodeURIComponent(tagId)}`}
20-
title={`Show all models with the ${name} tag`}
20+
href={
21+
context === 'datasets' ? `/datasets?t=${encodeURIComponent(tagId)}` : `/?t=${encodeURIComponent(tagId)}`
22+
}
23+
title={
24+
context === 'datasets'
25+
? `Show all datasets with the ${name} tag`
26+
: `Show all models with the ${name} tag`
27+
}
2128
>
2229
{name}
2330
</Link>
@@ -28,14 +35,16 @@ export interface EditableTagsProps {
2835
tags: readonly TagId[];
2936
onChange?: (value: TagId[]) => void;
3037
readonly?: boolean;
38+
context?: 'models' | 'datasets';
3139
}
32-
export function EditableTags({ tags, onChange, readonly }: EditableTagsProps) {
40+
export function EditableTags({ tags, onChange, readonly, context = 'models' }: EditableTagsProps) {
3341
const { tagData } = useTags();
3442

3543
return (
3644
<div className={style.tags}>
3745
{!readonly && onChange && (
3846
<EditTags
47+
context={context}
3948
tags={tags}
4049
onChange={onChange}
4150
/>
@@ -44,6 +53,7 @@ export function EditableTags({ tags, onChange, readonly }: EditableTagsProps) {
4453
const name = tagData.get(tagId)?.name ?? `unknown tag:${tagId}`;
4554
return (
4655
<SmallTag
56+
context={context}
4757
key={tagId}
4858
name={name}
4959
tagId={tagId}
@@ -54,9 +64,24 @@ export function EditableTags({ tags, onChange, readonly }: EditableTagsProps) {
5464
);
5565
}
5666

57-
function EditTags({ tags, onChange }: { tags: readonly TagId[]; onChange: (value: TagId[]) => void }) {
67+
function EditTags({
68+
tags,
69+
onChange,
70+
context = 'models',
71+
}: {
72+
tags: readonly TagId[];
73+
onChange: (value: TagId[]) => void;
74+
context?: 'models' | 'datasets';
75+
}) {
5876
const { tagData, categoryOrder } = useTags();
5977

78+
const filteredCategoryOrder = useMemo(() => {
79+
if (context === 'datasets') {
80+
return categoryOrder.filter(([id]) => id === 'dataset');
81+
}
82+
return categoryOrder.filter(([id]) => id !== 'dataset');
83+
}, [categoryOrder, context]);
84+
6085
const [currentTags, setCurrentTags] = useState(tags);
6186
useEffect(() => {
6287
setCurrentTags(tags);
@@ -104,7 +129,7 @@ function EditTags({ tags, onChange }: { tags: readonly TagId[]; onChange: (value
104129
}`}
105130
>
106131
<div className={style.editContainer}>
107-
{categoryOrder.map(([categoryId, category]) => {
132+
{filteredCategoryOrder.map(([categoryId, category]) => {
108133
const manual = category.tags.filter((tagId) => !isDerivedTag(tagId));
109134
if (manual.length === 0) {
110135
return <Fragment key={categoryId} />;

0 commit comments

Comments
 (0)