enzostvs HF staff commited on
Commit
b1a4d81
β€’
1 Parent(s): 17aecfb

add user validation

Browse files
package-lock.json CHANGED
@@ -11,6 +11,8 @@
11
  "@iconify/svelte": "^3.1.4",
12
  "@prisma/client": "^5.7.1",
13
  "@sveltejs/adapter-node": "^1.3.1",
 
 
14
  "svelte-infinite-scroll": "^2.0.1"
15
  },
16
  "devDependencies": {
@@ -20,6 +22,7 @@
20
  "@sveltejs/enhanced-img": "^0.1.7",
21
  "@sveltejs/kit": "^1.27.4",
22
  "@types/cookie": "^0.5.1",
 
23
  "@typescript-eslint/eslint-plugin": "^6.0.0",
24
  "@typescript-eslint/parser": "^6.0.0",
25
  "autoprefixer": "^10.4.16",
@@ -1346,6 +1349,14 @@
1346
  "vite": "^4.0.0"
1347
  }
1348
  },
 
 
 
 
 
 
 
 
1349
  "node_modules/@sveltejs/vite-plugin-svelte": {
1350
  "version": "2.5.3",
1351
  "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.5.3.tgz",
@@ -1393,6 +1404,12 @@
1393
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
1394
  "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
1395
  },
 
 
 
 
 
 
1396
  "node_modules/@types/json-schema": {
1397
  "version": "7.0.15",
1398
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -2019,9 +2036,9 @@
2019
  "dev": true
2020
  },
2021
  "node_modules/cookie": {
2022
- "version": "0.5.0",
2023
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
2024
- "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
2025
  "engines": {
2026
  "node": ">= 0.6"
2027
  }
@@ -2893,6 +2910,14 @@
2893
  "jiti": "bin/jiti.js"
2894
  }
2895
  },
 
 
 
 
 
 
 
 
2896
  "node_modules/js-yaml": {
2897
  "version": "4.1.0",
2898
  "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
 
11
  "@iconify/svelte": "^3.1.4",
12
  "@prisma/client": "^5.7.1",
13
  "@sveltejs/adapter-node": "^1.3.1",
14
+ "cookie": "^0.6.0",
15
+ "js-cookie": "^3.0.5",
16
  "svelte-infinite-scroll": "^2.0.1"
17
  },
18
  "devDependencies": {
 
22
  "@sveltejs/enhanced-img": "^0.1.7",
23
  "@sveltejs/kit": "^1.27.4",
24
  "@types/cookie": "^0.5.1",
25
+ "@types/js-cookie": "^3.0.6",
26
  "@typescript-eslint/eslint-plugin": "^6.0.0",
27
  "@typescript-eslint/parser": "^6.0.0",
28
  "autoprefixer": "^10.4.16",
 
1349
  "vite": "^4.0.0"
1350
  }
1351
  },
1352
+ "node_modules/@sveltejs/kit/node_modules/cookie": {
1353
+ "version": "0.5.0",
1354
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
1355
+ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
1356
+ "engines": {
1357
+ "node": ">= 0.6"
1358
+ }
1359
+ },
1360
  "node_modules/@sveltejs/vite-plugin-svelte": {
1361
  "version": "2.5.3",
1362
  "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.5.3.tgz",
 
1404
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
1405
  "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
1406
  },
1407
+ "node_modules/@types/js-cookie": {
1408
+ "version": "3.0.6",
1409
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
1410
+ "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
1411
+ "dev": true
1412
+ },
1413
  "node_modules/@types/json-schema": {
1414
  "version": "7.0.15",
1415
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
 
2036
  "dev": true
2037
  },
2038
  "node_modules/cookie": {
2039
+ "version": "0.6.0",
2040
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
2041
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
2042
  "engines": {
2043
  "node": ">= 0.6"
2044
  }
 
2910
  "jiti": "bin/jiti.js"
2911
  }
2912
  },
2913
+ "node_modules/js-cookie": {
2914
+ "version": "3.0.5",
2915
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
2916
+ "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
2917
+ "engines": {
2918
+ "node": ">=14"
2919
+ }
2920
+ },
2921
  "node_modules/js-yaml": {
2922
  "version": "4.1.0",
2923
  "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
package.json CHANGED
@@ -17,6 +17,7 @@
17
  "@sveltejs/enhanced-img": "^0.1.7",
18
  "@sveltejs/kit": "^1.27.4",
19
  "@types/cookie": "^0.5.1",
 
20
  "@typescript-eslint/eslint-plugin": "^6.0.0",
21
  "@typescript-eslint/parser": "^6.0.0",
22
  "autoprefixer": "^10.4.16",
@@ -40,6 +41,8 @@
40
  "@iconify/svelte": "^3.1.4",
41
  "@prisma/client": "^5.7.1",
42
  "@sveltejs/adapter-node": "^1.3.1",
 
 
43
  "svelte-infinite-scroll": "^2.0.1"
44
  }
45
  }
 
17
  "@sveltejs/enhanced-img": "^0.1.7",
18
  "@sveltejs/kit": "^1.27.4",
19
  "@types/cookie": "^0.5.1",
20
+ "@types/js-cookie": "^3.0.6",
21
  "@typescript-eslint/eslint-plugin": "^6.0.0",
22
  "@typescript-eslint/parser": "^6.0.0",
23
  "autoprefixer": "^10.4.16",
 
41
  "@iconify/svelte": "^3.1.4",
42
  "@prisma/client": "^5.7.1",
43
  "@sveltejs/adapter-node": "^1.3.1",
44
+ "cookie": "^0.6.0",
45
+ "js-cookie": "^3.0.5",
46
  "svelte-infinite-scroll": "^2.0.1"
47
  }
48
  }
prisma/dev.db CHANGED
Binary files a/prisma/dev.db and b/prisma/dev.db differ
 
prisma/migrations/20240104145615_init/migration.sql DELETED
@@ -1,25 +0,0 @@
1
- /*
2
- Warnings:
3
-
4
- - You are about to drop the column `downloads` on the `Model` table. All the data in the column will be lost.
5
- - You are about to drop the column `image` on the `Model` table. All the data in the column will be lost.
6
- - You are about to drop the column `likes` on the `Model` table. All the data in the column will be lost.
7
- - You are about to drop the column `title` on the `Model` table. All the data in the column will be lost.
8
- - You are about to drop the column `trigger_word` on the `Model` table. All the data in the column will be lost.
9
- - You are about to drop the column `weights` on the `Model` table. All the data in the column will be lost.
10
-
11
- */
12
- -- RedefineTables
13
- PRAGMA foreign_keys=OFF;
14
- CREATE TABLE "new_Model" (
15
- "id" TEXT NOT NULL PRIMARY KEY,
16
- "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
17
- "repo" TEXT NOT NULL,
18
- "isPublic" BOOLEAN NOT NULL DEFAULT false
19
- );
20
- INSERT INTO "new_Model" ("createdAt", "id", "isPublic", "repo") SELECT "createdAt", "id", "isPublic", "repo" FROM "Model";
21
- DROP TABLE "Model";
22
- ALTER TABLE "new_Model" RENAME TO "Model";
23
- CREATE UNIQUE INDEX "Model_repo_key" ON "Model"("repo");
24
- PRAGMA foreign_key_check;
25
- PRAGMA foreign_keys=ON;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
prisma/migrations/20240104153500_init/migration.sql DELETED
@@ -1,2 +0,0 @@
1
- -- AlterTable
2
- ALTER TABLE "Model" ADD COLUMN "image" TEXT;
 
 
 
prisma/migrations/20240104154419_init/migration.sql DELETED
@@ -1,25 +0,0 @@
1
- /*
2
- Warnings:
3
-
4
- - Added the required column `title` to the `Model` table without a default value. This is not possible if the table is not empty.
5
- - Made the column `image` on table `Model` required. This step will fail if there are existing NULL values in that column.
6
-
7
- */
8
- -- RedefineTables
9
- PRAGMA foreign_keys=OFF;
10
- CREATE TABLE "new_Model" (
11
- "id" TEXT NOT NULL PRIMARY KEY,
12
- "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
13
- "repo" TEXT NOT NULL,
14
- "title" TEXT NOT NULL,
15
- "image" TEXT NOT NULL,
16
- "likes" INTEGER,
17
- "downloads" INTEGER,
18
- "isPublic" BOOLEAN NOT NULL DEFAULT false
19
- );
20
- INSERT INTO "new_Model" ("createdAt", "id", "image", "isPublic", "repo") SELECT "createdAt", "id", "image", "isPublic", "repo" FROM "Model";
21
- DROP TABLE "Model";
22
- ALTER TABLE "new_Model" RENAME TO "Model";
23
- CREATE UNIQUE INDEX "Model_repo_key" ON "Model"("repo");
24
- PRAGMA foreign_key_check;
25
- PRAGMA foreign_keys=ON;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
prisma/migrations/{20240104143521_init β†’ 20240104191224_init}/migration.sql RENAMED
@@ -3,10 +3,8 @@ CREATE TABLE "Model" (
3
  "id" TEXT NOT NULL PRIMARY KEY,
4
  "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
5
  "repo" TEXT NOT NULL,
6
- "title" TEXT,
7
- "trigger_word" TEXT,
8
  "image" TEXT,
9
- "weights" TEXT,
10
  "likes" INTEGER,
11
  "downloads" INTEGER,
12
  "isPublic" BOOLEAN NOT NULL DEFAULT false
 
3
  "id" TEXT NOT NULL PRIMARY KEY,
4
  "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
5
  "repo" TEXT NOT NULL,
6
+ "title" TEXT NOT NULL,
 
7
  "image" TEXT,
 
8
  "likes" INTEGER,
9
  "downloads" INTEGER,
10
  "isPublic" BOOLEAN NOT NULL DEFAULT false
prisma/schema.prisma CHANGED
@@ -11,14 +11,15 @@ datasource db {
11
  }
12
 
13
  model Model {
14
- id String @id @default(uuid())
15
- createdAt DateTime @default(now())
16
- repo String @unique
17
- title String
18
  // trigger_word String?
19
- image String
20
  // weights String?
21
- likes Int?
22
- downloads Int?
23
- isPublic Boolean @default(false)
 
24
  }
 
11
  }
12
 
13
  model Model {
14
+ id String @id @default(uuid())
15
+ createdAt DateTime @default(now())
16
+ repo String @unique
17
+ title String
18
  // trigger_word String?
19
+ image String
20
  // weights String?
21
+ likes Int?
22
+ downloads Int?
23
+ isPublic Boolean @default(false)
24
+ hf_user_id String?
25
  }
src/lib/components/Button.svelte CHANGED
@@ -6,6 +6,7 @@ import { goto } from '$app/navigation';
6
  export let size: "md" | "lg" = "md";
7
  export let href: string | undefined = undefined;
8
  export let icon: string | undefined = undefined;
 
9
  export let iconPosition: "left" | "right" = "left";
10
  export let disabled: boolean = false;
11
  export let loading: boolean = false;
@@ -13,7 +14,8 @@ import { goto } from '$app/navigation';
13
 
14
  const handleClick = async () => {
15
  if (href) {
16
- goto(href);
 
17
  return
18
  }
19
  if (disabled || loading) return;
 
6
  export let size: "md" | "lg" = "md";
7
  export let href: string | undefined = undefined;
8
  export let icon: string | undefined = undefined;
9
+ export let target: "_blank" | "_self" | undefined = undefined;
10
  export let iconPosition: "left" | "right" = "left";
11
  export let disabled: boolean = false;
12
  export let loading: boolean = false;
 
14
 
15
  const handleClick = async () => {
16
  if (href) {
17
+ if (target) window.open(href, target);
18
+ else goto(href);
19
  return
20
  }
21
  if (disabled || loading) return;
src/lib/components/GoTop.svelte CHANGED
@@ -7,15 +7,24 @@
7
  element?.scrollTo({ top: 0, behavior: 'smooth' });
8
  }
9
 
10
- // WIP display only if scroll > 0 dynamically
11
- $: visible = browser ? (document?.getElementById('app') as HTMLElement)?.scrollTop > 0 : false;
 
 
 
 
 
 
 
 
12
  </script>
13
 
14
  <button
15
- class="rounded-full h-12 w-12 bg-white shadow-lg brightness-90 transition-all duration-200 hover:brightness-110 fixed bottom-8 right-8 flex items-center justify-center text-indigo-500 z-10"
16
  class:opacity-0={!visible}
17
  class:pointer-events-none={!visible}
18
  on:click={goTop}
19
- >
20
- <Icon icon="foundation:arrow-up" class="w-8 h-8" />
 
21
  </button>
 
7
  element?.scrollTo({ top: 0, behavior: 'smooth' });
8
  }
9
 
10
+ let visible = false;
11
+
12
+ if (browser) {
13
+ const element = document.getElementById('app');
14
+ element?.addEventListener('scroll', () => {
15
+ const scroll = element?.scrollTop ?? 0;
16
+ visible = scroll > 100;
17
+ });
18
+ }
19
+
20
  </script>
21
 
22
  <button
23
+ class="rounded-full text-sm text-neutral-950 font-semibold px-3 py-1 bg-white shadow-lg brightness-90 transition-all duration-200 hover:brightness-110 fixed bottom-8 right-8 flex items-center gap-1.5 justify-center z-10"
24
  class:opacity-0={!visible}
25
  class:pointer-events-none={!visible}
26
  on:click={goTop}
27
+ >
28
+ <Icon icon="foundation:arrow-up" class="w-4 h-4" />
29
+ Go back to top
30
  </button>
src/lib/components/community/Card.svelte CHANGED
@@ -4,7 +4,6 @@
4
  import type { CommunityCard } from "$lib/type";
5
 
6
  export let card: CommunityCard;
7
-
8
  </script>
9
 
10
  <div
@@ -21,6 +20,6 @@
21
  {#each card.reactions as reaction}
22
  <Reaction emoji={reaction.emoji} count={reaction?.users?.length} />
23
  {/each}
24
- <Add />
25
  </div>
26
  </div>
 
4
  import type { CommunityCard } from "$lib/type";
5
 
6
  export let card: CommunityCard;
 
7
  </script>
8
 
9
  <div
 
20
  {#each card.reactions as reaction}
21
  <Reaction emoji={reaction.emoji} count={reaction?.users?.length} />
22
  {/each}
23
+ <Add count={card?.reactions?.length} />
24
  </div>
25
  </div>
src/lib/components/community/reactions/Add.svelte CHANGED
@@ -3,6 +3,8 @@
3
  import Icon from "@iconify/svelte";
4
  import { REACTION_EMOJIS } from "$lib/utils";
5
 
 
 
6
  let isOpen: boolean = false;
7
  $: uuid = Math.random().toString(36).substring(7);
8
 
@@ -30,12 +32,12 @@
30
  <Icon icon="fluent:emoji-add-16-regular" class="w-5 h-5" />
31
  </button>
32
  <div
33
- class="opacity-0 pointer-events-none absolute left-0 top-0 max-w-max flex items-center justify-center bg-white px-4 py-1 rounded-full gap-0 text-2xl -translate-y-[calc(100%+8px)] -translate-x-1/2"
34
  class:opacity-100={isOpen}
35
  class:pointer-events-auto={isOpen}
36
  >
37
  {#each REACTION_EMOJIS as emoji}
38
- <div class="w-9 h-9 hover:bg-neutral-200 rounded-full text-center flex items-center justify-center">{emoji}</div>
39
  {/each}
40
  </div>
41
  </div>
 
3
  import Icon from "@iconify/svelte";
4
  import { REACTION_EMOJIS } from "$lib/utils";
5
 
6
+ export let count: number;
7
+
8
  let isOpen: boolean = false;
9
  $: uuid = Math.random().toString(36).substring(7);
10
 
 
32
  <Icon icon="fluent:emoji-add-16-regular" class="w-5 h-5" />
33
  </button>
34
  <div
35
+ class={`opacity-0 pointer-events-none absolute max-w-max flex items-center justify-center bg-white px-1 py-1 rounded-full gap-0 text-xl ${count > 0 ? "-translate-y-[calc(100%+8px)] -translate-x-1/2 left-0 top-0" : "right-0 translate-x-[calc(100%+8px)]"}`}
36
  class:opacity-100={isOpen}
37
  class:pointer-events-auto={isOpen}
38
  >
39
  {#each REACTION_EMOJIS as emoji}
40
+ <div class="w-8 h-8 hover:bg-neutral-200 rounded-full text-center flex items-center justify-center">{emoji}</div>
41
  {/each}
42
  </div>
43
  </div>
src/lib/components/models/Card.svelte CHANGED
@@ -14,7 +14,7 @@
14
  <!-- <div class="w-full h-full bg-center bg-cover rounded-lg bg-neutral-800 relative" style=""> -->
15
  <!-- <Loading /> -->
16
  <!-- </div> -->
17
- <img src="{card.image}" class="w-full h-full bg-center bg-cover rounded-lg object-cover object-center" alt="{card?.title}" />
18
  <div class="group-hover:opacity-100 opacity-0 translate-x-full group-hover:translate-x-0 transition-all duration-200 absolute right-3 bottom-3">
19
  <Button theme="light" size="md">
20
  Try it now
 
14
  <!-- <div class="w-full h-full bg-center bg-cover rounded-lg bg-neutral-800 relative" style=""> -->
15
  <!-- <Loading /> -->
16
  <!-- </div> -->
17
+ <img src="{card.image}" class="w-full h-full bg-center bg-cover rounded-lg object-cover object-center bg-neutral-800" alt="{card?.title}" />
18
  <div class="group-hover:opacity-100 opacity-0 translate-x-full group-hover:translate-x-0 transition-all duration-200 absolute right-3 bottom-3">
19
  <Button theme="light" size="md">
20
  Try it now
src/lib/components/models/Submit.svelte CHANGED
@@ -1,12 +1,59 @@
1
  <script>
 
 
 
2
  import Input from "$lib/components/fields/Input.svelte";
3
  import Button from "$lib/components/Button.svelte";
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  </script>
6
  <div class="grid grid-cols-1 gap-8">
7
- <p class="bg-yellow-500/40 rounded-full text-xs text-yellow-400 px-3 py-1 font-semibold max-w-max">
8
- You need to be logged in to submit a model.
9
- </p>
 
 
 
 
 
 
 
 
 
 
10
  <header>
11
  <p class="text-white font-semibold text-lg">
12
  Submit a Model
@@ -17,16 +64,52 @@
17
  </header>
18
  <main class="grid grid-cols-1 gap-6">
19
  <div>
20
- <p class="text-xs uppercase text-neutral-400 font-semibold mb-2">HuggingFace model URL</p>
21
- <Input placeholder="enzostvs/hair-color" prefix="huggingface.co/" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  </div>
23
  <div>
24
- <p class="text-xs uppercase text-neutral-400 font-semibold mb-2">Title</p>
25
- <Input placeholder="Simpson style" />
 
 
 
 
 
 
 
 
 
 
 
 
26
  </div>
27
  </main>
28
  <footer class="flex items-center justify-end gap-3">
29
  <Button theme="dark" size="md">Cancel</Button>
30
- <Button theme="blue" size="md">Submit</Button>
31
  </footer>
32
  </div>
 
1
  <script>
2
+ import { get } from 'svelte/store';
3
+ import { userStore } from "$lib/stores/use-user";
4
+
5
  import Input from "$lib/components/fields/Input.svelte";
6
  import Button from "$lib/components/Button.svelte";
7
 
8
+ let user = get(userStore);
9
+ let model = {
10
+ repo: 'enzostvs/hair-colorrr',
11
+ title: 'testt',
12
+ image: 'dewdwedd',
13
+ }
14
+ let error = {
15
+ repo: '',
16
+ title: '',
17
+ image: ''
18
+ }
19
+
20
+ const handleSubmit = async () => {
21
+ fetch('/api/models/submit', {
22
+ method: 'POST',
23
+ headers: {
24
+ 'Content-Type': 'application/json',
25
+ },
26
+ body: JSON.stringify(model),
27
+ })
28
+ .then(response => response.json())
29
+ .then(data => {
30
+ if (data.error) {
31
+ error = data.error;
32
+ } else {
33
+ console.log('Success:', data);
34
+ error = {
35
+ repo: '',
36
+ title: '',
37
+ image: ''
38
+ }
39
+ }
40
+ })
41
+ }
42
  </script>
43
  <div class="grid grid-cols-1 gap-8">
44
+ {#if user?.picture}
45
+ <div class="flex items-center justify-start gap-3">
46
+ <img src={user.picture} alt="User avatar" class="w-8 h-8 rounded-full border border-white inline-block" />
47
+ <div class="w-full text-left text-white">
48
+ <p class="text-base font-semibold">{user.name}</p>
49
+ <p class="text-xs leading-none text-neutral-400">{user.preferred_username}</p>
50
+ </div>
51
+ </div>
52
+ {:else}
53
+ <p class="bg-yellow-500/40 rounded-full text-xs text-yellow-400 px-3 py-1 font-semibold max-w-max">
54
+ You need to be logged in to submit a model.
55
+ </p>
56
+ {/if}
57
  <header>
58
  <p class="text-white font-semibold text-lg">
59
  Submit a Model
 
64
  </header>
65
  <main class="grid grid-cols-1 gap-6">
66
  <div>
67
+ <p class="text-xs uppercase text-neutral-400 font-semibold mb-2">
68
+ HuggingFace model URL
69
+ <span class="text-red-500">*</span>
70
+ </p>
71
+ <Input
72
+ value={model.repo}
73
+ placeholder="enzostvs/hair-color"
74
+ prefix="huggingface.co/"
75
+ onChange={(value) => model.repo = value}
76
+ />
77
+ {#if error.repo}
78
+ <p class="text-xs text-red-500 mt-1">
79
+ {error.repo}
80
+ </p>
81
+ {/if}
82
+ </div>
83
+ <div>
84
+ <p class="text-xs uppercase text-neutral-400 font-semibold mb-2">
85
+ Title
86
+ <span class="text-red-500">*</span>
87
+ </p>
88
+ <Input
89
+ value={model.title}
90
+ placeholder="Simpson style"
91
+ onChange={(value) => model.title = value}
92
+ />
93
  </div>
94
  <div>
95
+ <p class="text-xs uppercase text-neutral-400 font-semibold mb-2">
96
+ Thumbnail image
97
+ <span class="text-red-500">*</span>
98
+ </p>
99
+ <Input
100
+ value={model.image}
101
+ placeholder="https://"
102
+ onChange={(value) => model.image = value}
103
+ />
104
+ {#if error.image}
105
+ <p class="text-xs text-red-500 mt-1">
106
+ {error.image}
107
+ </p>
108
+ {/if}
109
  </div>
110
  </main>
111
  <footer class="flex items-center justify-end gap-3">
112
  <Button theme="dark" size="md">Cancel</Button>
113
+ <Button theme="blue" size="md" onClick={handleSubmit}>Submit</Button>
114
  </footer>
115
  </div>
src/lib/components/sidebar/Menu.svelte CHANGED
@@ -3,7 +3,6 @@
3
 
4
  export let href: string;
5
 
6
-
7
  $: active_class = $page.url.pathname === href ? 'bg-neutral-900 !border-neutral-800' : '';
8
  </script>
9
 
 
3
 
4
  export let href: string;
5
 
 
6
  $: active_class = $page.url.pathname === href ? 'bg-neutral-900 !border-neutral-800' : '';
7
  </script>
8
 
src/lib/components/sidebar/Sidebar.svelte CHANGED
@@ -1,10 +1,15 @@
1
  <script lang="ts">
 
2
  import Icon from "@iconify/svelte"
 
 
 
 
3
 
4
  import Menu from "./Menu.svelte";
5
- import HFLogo from "$lib/assets/hf-logo.svg";
6
 
7
  let isOpen = false;
 
8
 
9
  const handleClick = () => {
10
  const app = document.getElementById("app");
@@ -14,19 +19,10 @@
14
  isOpen = !isOpen;
15
  }
16
 
17
- const menus = [{
18
- icon: "solar:gallery-bold-duotone",
19
- label: "Gallery",
20
- href: "/",
21
- }, {
22
- icon: "uim:cube",
23
- label: "Models",
24
- href: "/models",
25
- }, {
26
- icon: "fluent:glance-horizontal-sparkles-16-filled",
27
- label: "Generate",
28
- href: "/generate",
29
- }]
30
  </script>
31
 
32
  <button class="bg-transparent absolute top-10 right-8 cursor-pointer xl:hidden" on:click="{handleClick}">
@@ -39,7 +35,7 @@
39
  </header>
40
  <div class="px-4">
41
  <ul class="grid grid-cols-1 gap-2">
42
- {#each menus as menu}
43
  <Menu href={menu.href}>
44
  <Icon icon={menu.icon} class="w-5 h-5" />
45
  {menu.label}
@@ -54,8 +50,23 @@
54
  </Menu>
55
  </div>
56
  </div>
57
- <footer class="text-white text-center text-base pb-8 px-8 flex items-center justify-center gap-2 cursor-pointer">
58
- <img src={HFLogo} alt="Hugging Face logo" class="w-8 h-8 inline-block" />
59
- <u>Sign in with Hugging Face</u>
60
- </footer>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  </aside>
 
1
  <script lang="ts">
2
+ import cookies from 'js-cookie';
3
  import Icon from "@iconify/svelte"
4
+ import { get } from 'svelte/store';
5
+ import { userStore } from "$lib/stores/use-user";
6
+ import { SIDEBAR_MENUS } from "$lib/utils";
7
+ import HFLogo from "$lib/assets/hf-logo.svg";
8
 
9
  import Menu from "./Menu.svelte";
 
10
 
11
  let isOpen = false;
12
+ let user = get(userStore);
13
 
14
  const handleClick = () => {
15
  const app = document.getElementById("app");
 
19
  isOpen = !isOpen;
20
  }
21
 
22
+ const logout = async () => {
23
+ cookies.remove("hf_access_token");
24
+ window.location.href = "/";
25
+ }
 
 
 
 
 
 
 
 
 
26
  </script>
27
 
28
  <button class="bg-transparent absolute top-10 right-8 cursor-pointer xl:hidden" on:click="{handleClick}">
 
35
  </header>
36
  <div class="px-4">
37
  <ul class="grid grid-cols-1 gap-2">
38
+ {#each SIDEBAR_MENUS as menu}
39
  <Menu href={menu.href}>
40
  <Icon icon={menu.icon} class="w-5 h-5" />
41
  {menu.label}
 
50
  </Menu>
51
  </div>
52
  </div>
53
+ {#if user?.picture}
54
+ <footer class="text-white text-center text-base pb-8 px-8 flex items-center justify-between gap-4">
55
+ <div class="flex items-center justify-start gap-4">
56
+ <img src={user.picture} alt="User avatar" class="w-10 h-10 rounded-full border-2 border-white inline-block" />
57
+ <div class="w-full text-left">
58
+ <p class="text-lg font-semibold">{user.name}</p>
59
+ <p class="text-sm leading-none text-neutral-400">{user.preferred_username}</p>
60
+ </div>
61
+ </div>
62
+ <button on:click={logout}>
63
+ <Icon icon="solar:logout-2-bold" class="text-red-500 hover:text-red-400 w-7 h-7" />
64
+ </button>
65
+ </footer>
66
+ {:else}
67
+ <footer class="text-white text-center text-base pb-8 px-8 flex items-center justify-center gap-2 cursor-pointer">
68
+ <img src={HFLogo} alt="Hugging Face logo" class="w-8 h-8 inline-block" />
69
+ <u>Sign in with Hugging Face</u>
70
+ </footer>
71
+ {/if}
72
  </aside>
src/lib/stores/use-user.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import { writable } from "svelte/store";
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
+ export const userStore = writable<any>(null);
src/lib/utils/index.ts CHANGED
@@ -28,4 +28,30 @@ export const MODELS_FILTER_OPTIONS = [
28
  icon: "ph:fire-bold",
29
  iconColor: "text-orange-500"
30
  },
31
- ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  icon: "ph:fire-bold",
29
  iconColor: "text-orange-500"
30
  },
31
+ ];
32
+
33
+ export const SIDEBAR_MENUS = [{
34
+ icon: "solar:gallery-bold-duotone",
35
+ label: "Gallery",
36
+ href: "/",
37
+ }, {
38
+ icon: "uim:cube",
39
+ label: "Models",
40
+ href: "/models",
41
+ }, {
42
+ icon: "fluent:glance-horizontal-sparkles-16-filled",
43
+ label: "Generate",
44
+ href: "/generate",
45
+ }]
46
+
47
+ export const tokenIsAvailable = async (token: string) => {
48
+ const userRequest = await fetch("https://huggingface.co/oauth/userinfo", {
49
+ method: "GET",
50
+ headers: {
51
+ Authorization: `Bearer ${token}`,
52
+ },
53
+ })
54
+
55
+ const user = await userRequest.clone().json().catch(() => ({}));
56
+ return !!user?.sub
57
+ }
src/routes/+layout.server.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export async function load({ fetch }) {
2
+ const response = await fetch("/api/@me", {
3
+ method: "GET",
4
+ headers: {
5
+ "Content-Type": "application/json"
6
+ }
7
+ })
8
+ const user = await response.json()
9
+ return user
10
+ }
src/routes/+layout.svelte CHANGED
@@ -1,6 +1,10 @@
1
  <script>
2
  import Sidebar from "$lib/components/sidebar/Sidebar.svelte";
3
  import "$lib/styles/tailwind.css"
 
 
 
 
4
  </script>
5
 
6
  <div class="flex items-start">
 
1
  <script>
2
  import Sidebar from "$lib/components/sidebar/Sidebar.svelte";
3
  import "$lib/styles/tailwind.css"
4
+ import { userStore } from "$lib/stores/use-user";
5
+
6
+ export let data;
7
+ userStore.set(data.user);
8
  </script>
9
 
10
  <div class="flex items-start">
src/routes/api/@me/+server.ts ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { json, type RequestEvent } from '@sveltejs/kit';
2
+
3
+ /** @type {import('./$types').RequestHandler} */
4
+
5
+ export async function GET(request : RequestEvent) {
6
+ if (!request.cookies.get('hf_access_token')) {
7
+ return json({
8
+ error: {
9
+ token: "You must be logged"
10
+ }
11
+ }, { status: 401 })
12
+ }
13
+
14
+ const response = await fetch("https://huggingface.co/oauth/userinfo", {
15
+ method: "GET",
16
+ headers: {
17
+ Authorization: `Bearer ${request.cookies.get('hf_access_token')}`,
18
+ },
19
+ })
20
+
21
+ const user = await response.clone().json().catch(() => ({}));
22
+
23
+ if (!user?.sub) {
24
+ return json({
25
+ error: {
26
+ token: "Token is invalid"
27
+ }
28
+ }, { status: 401 })
29
+ }
30
+
31
+ return json({
32
+ user
33
+ })
34
+ }
src/routes/api/models/+server.ts CHANGED
@@ -8,7 +8,6 @@ import prisma from '$lib/prisma';
8
  // refer to bulk-create-models +server.ts for example
9
 
10
  export async function GET(request : RequestEvent) {
11
-
12
  const page = parseInt(request.url.searchParams.get('page') || '0')
13
  const filter = request.url.searchParams.get('filter') || 'hotest'
14
  const search = request.url.searchParams.get('search') || ''
@@ -28,7 +27,15 @@ export async function GET(request : RequestEvent) {
28
  take: 20,
29
  })
30
 
31
- const total_reposId = await prisma.model.count()
 
 
 
 
 
 
 
 
32
 
33
  const hasError = false
34
  if (hasError) {
 
8
  // refer to bulk-create-models +server.ts for example
9
 
10
  export async function GET(request : RequestEvent) {
 
11
  const page = parseInt(request.url.searchParams.get('page') || '0')
12
  const filter = request.url.searchParams.get('filter') || 'hotest'
13
  const search = request.url.searchParams.get('search') || ''
 
27
  take: 20,
28
  })
29
 
30
+ const total_reposId = await prisma.model.count({
31
+ where: {
32
+ isPublic: true,
33
+ OR: [
34
+ { title: { contains: search } },
35
+ { repo: { contains: search } },
36
+ ]
37
+ },
38
+ })
39
 
40
  const hasError = false
41
  if (hasError) {
src/routes/api/models/submit/+server.ts ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { json } from '@sveltejs/kit';
2
+ import prisma from '$lib/prisma';
3
+
4
+ import { tokenIsAvailable } from '$lib/utils';
5
+
6
+ /** @type {import('./$types').RequestHandler} */
7
+
8
+ export async function POST({ request, fetch, cookies }) {
9
+ const model = await request.json();
10
+
11
+ const token = cookies.get('hf_access_token')
12
+ if (!token) {
13
+ return json({
14
+ error: {
15
+ token: "You must be logged"
16
+ }
17
+ }, { status: 401 })
18
+ }
19
+
20
+ const is_token_available = await tokenIsAvailable(token)
21
+ if (!is_token_available) {
22
+ return json({
23
+ error: {
24
+ token: "Invalid token"
25
+ }
26
+ }, { status: 401 })
27
+ }
28
+
29
+ // get model on hugging face
30
+ const res = await fetch(`https://huggingface.co/api/models/${model.repo}`)
31
+ const data = await res.json();
32
+
33
+ if (data?.error) {
34
+ return json({
35
+ error: {
36
+ repo: "Model not found on Hugging Face"
37
+ }
38
+ }, { status: 404 })
39
+ }
40
+
41
+ // check model.image is valid url and is an image
42
+ const imageRes = await fetch(model.image)
43
+ const imageBlob = await imageRes.blob()
44
+ const isImage = imageBlob.type.startsWith("image/")
45
+ const isValidUrl = imageRes.status === 200
46
+
47
+ if (!isImage || !isValidUrl) {
48
+ return json({
49
+ error: {
50
+ image: "Invalid image url"
51
+ }
52
+ }, { status: 400 })
53
+ }
54
+
55
+ await prisma.model.create({
56
+ data: {
57
+ repo: model.repo,
58
+ image: model.image,
59
+ title: model.title,
60
+ likes: data.likes,
61
+ downloads: data.downloads,
62
+ isPublic: false,
63
+ }
64
+ })
65
+
66
+ return json({
67
+ success: true
68
+ })
69
+ }
src/routes/models/+page.svelte CHANGED
@@ -58,7 +58,7 @@
58
  <div class="flex items-start sm:items-center justify-between mt-5 flex-col sm:flex-row gap-5 sm:justify-between">
59
  <Radio options={MODELS_FILTER_OPTIONS} value="{form.filter}" onChange={handleChangeFilter} />
60
  <div class="items-center justify-end gap-5 hidden lg:flex">
61
- <Button icon="ic:round-plus" theme="dark" size="lg">Create</Button>
62
  <Button
63
  icon="octicon:upload-16"
64
  theme="blue"
@@ -69,7 +69,7 @@
69
  </Button>
70
  </div>
71
  <div class="items-center justify-end gap-3 flex lg:hidden">
72
- <Button icon="ic:round-plus" theme="dark" size="md">Create</Button>
73
  <Button
74
  icon="octicon:upload-16"
75
  theme="blue"
 
58
  <div class="flex items-start sm:items-center justify-between mt-5 flex-col sm:flex-row gap-5 sm:justify-between">
59
  <Radio options={MODELS_FILTER_OPTIONS} value="{form.filter}" onChange={handleChangeFilter} />
60
  <div class="items-center justify-end gap-5 hidden lg:flex">
61
+ <Button href="https://huggingface.co/new" target="_blank" icon="ic:round-plus" theme="dark" size="lg">Create</Button>
62
  <Button
63
  icon="octicon:upload-16"
64
  theme="blue"
 
69
  </Button>
70
  </div>
71
  <div class="items-center justify-end gap-3 flex lg:hidden">
72
+ <Button href="https://huggingface.co/new" target="_blank" icon="ic:round-plus" theme="dark" size="md">Create</Button>
73
  <Button
74
  icon="octicon:upload-16"
75
  theme="blue"