Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
gallery viewer
Browse files- package-lock.json +10 -0
- package.json +1 -0
- src/lib/components/UserIsLogged.svelte +1 -1
- src/lib/components/community/Card.svelte +24 -10
- src/lib/components/community/reactions/Reaction.svelte +8 -12
- src/lib/components/community/reactions/Reactions.svelte +28 -23
- src/lib/components/community/viewer/Viewer.svelte +131 -0
- src/lib/components/generate/Response.svelte +1 -3
- src/lib/components/models/Card.svelte +4 -1
- src/lib/components/models/drawer/Drawer.svelte +21 -15
- src/lib/components/models/drawer/comments/Comments.svelte +3 -2
- src/lib/stores/use-gallery.ts +17 -0
- src/lib/stores/use-model.ts +9 -1
- src/lib/type.ts +2 -0
- src/lib/utils/toaster.ts +19 -0
- src/routes/+layout.svelte +2 -0
- src/routes/+page.svelte +0 -2
- src/routes/+page.ts +1 -1
- src/routes/api/community/[id]/+server.ts +90 -0
- src/routes/api/models/[id]/comments/+server.ts +12 -0
- src/routes/gallery/+page.svelte +2 -0
package-lock.json
CHANGED
@@ -30,6 +30,7 @@
|
|
30 |
"@types/node": "^20.11.2",
|
31 |
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
32 |
"@typescript-eslint/parser": "^6.0.0",
|
|
|
33 |
"autoprefixer": "^10.4.16",
|
34 |
"eslint": "^8.28.0",
|
35 |
"eslint-config-prettier": "^9.0.0",
|
@@ -3049,6 +3050,15 @@
|
|
3049 |
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
|
3050 |
"dev": true
|
3051 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3052 |
"node_modules/acorn": {
|
3053 |
"version": "8.11.2",
|
3054 |
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
|
|
|
30 |
"@types/node": "^20.11.2",
|
31 |
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
32 |
"@typescript-eslint/parser": "^6.0.0",
|
33 |
+
"@zerodevx/svelte-toast": "^0.9.5",
|
34 |
"autoprefixer": "^10.4.16",
|
35 |
"eslint": "^8.28.0",
|
36 |
"eslint-config-prettier": "^9.0.0",
|
|
|
3050 |
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
|
3051 |
"dev": true
|
3052 |
},
|
3053 |
+
"node_modules/@zerodevx/svelte-toast": {
|
3054 |
+
"version": "0.9.5",
|
3055 |
+
"resolved": "https://registry.npmjs.org/@zerodevx/svelte-toast/-/svelte-toast-0.9.5.tgz",
|
3056 |
+
"integrity": "sha512-JLeB/oRdJfT+dz9A5bgd3Z7TuQnBQbeUtXrGIrNWMGqWbabpepBF2KxtWVhL2qtxpRqhae2f6NAOzH7xs4jUSw==",
|
3057 |
+
"dev": true,
|
3058 |
+
"peerDependencies": {
|
3059 |
+
"svelte": "^3.57.0 || ^4.0.0"
|
3060 |
+
}
|
3061 |
+
},
|
3062 |
"node_modules/acorn": {
|
3063 |
"version": "8.11.2",
|
3064 |
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
|
package.json
CHANGED
@@ -21,6 +21,7 @@
|
|
21 |
"@types/node": "^20.11.2",
|
22 |
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
23 |
"@typescript-eslint/parser": "^6.0.0",
|
|
|
24 |
"autoprefixer": "^10.4.16",
|
25 |
"eslint": "^8.28.0",
|
26 |
"eslint-config-prettier": "^9.0.0",
|
|
|
21 |
"@types/node": "^20.11.2",
|
22 |
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
23 |
"@typescript-eslint/parser": "^6.0.0",
|
24 |
+
"@zerodevx/svelte-toast": "^0.9.5",
|
25 |
"autoprefixer": "^10.4.16",
|
26 |
"eslint": "^8.28.0",
|
27 |
"eslint-config-prettier": "^9.0.0",
|
src/lib/components/UserIsLogged.svelte
CHANGED
@@ -15,7 +15,7 @@
|
|
15 |
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
16 |
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
17 |
<div
|
18 |
-
class="w-full cursor-pointer"
|
19 |
on:click={handleClick}
|
20 |
>
|
21 |
<div class:pointer-events-none={!user}>
|
|
|
15 |
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
16 |
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
17 |
<div
|
18 |
+
class="w-full cursor-pointer block"
|
19 |
on:click={handleClick}
|
20 |
>
|
21 |
<div class:pointer-events-none={!user}>
|
src/lib/components/community/Card.svelte
CHANGED
@@ -1,15 +1,34 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import type { CommunityCard } from "$lib/type";
|
3 |
-
import Button from "$lib/components/Button.svelte";
|
4 |
import { env } from "$env/dynamic/public";
|
5 |
-
|
|
|
|
|
|
|
|
|
6 |
import Reactions from "./reactions/Reactions.svelte";
|
7 |
|
8 |
export let card: CommunityCard;
|
9 |
-
</script>
|
10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
<div
|
12 |
class="cursor-pointer group bg-neutral-700 rounded-xl h-[400px] relative flex items-start justify-between flex-col p-5 transition-all duration-200 brightness-75 hover:brightness-100 z-[1] overflow-hidden"
|
|
|
13 |
>
|
14 |
<div class="w-full h-full absolute top-0 left-0 -z-[1] rounded-xl overflow-hidden">
|
15 |
<div class="w-full h-full bg-center bg-cover transition-all duration-200 group-hover:scale-110 " style="background-image: url('{env.PUBLIC_FILE_UPLOAD_DIR}/{card.image}');"></div>
|
@@ -19,11 +38,6 @@
|
|
19 |
<p class="text-white font-semibold text-lg">{card.prompt}</p>
|
20 |
<p class="text-white/75 font-regular text-base">{card.model.id}</p>
|
21 |
</div>
|
22 |
-
<Button theme="light" size="md" href={`/generate?model=${card.model.id}`}>
|
23 |
-
Try it now
|
24 |
-
</Button>
|
25 |
-
</div>
|
26 |
-
<div class="flex items-center justify-start gap-2">
|
27 |
-
<Reactions reactions={card.reactions} gallery_id={card.id} />
|
28 |
</div>
|
|
|
29 |
</div>
|
|
|
1 |
<script lang="ts">
|
|
|
|
|
2 |
import { env } from "$env/dynamic/public";
|
3 |
+
import { goto } from "$app/navigation";
|
4 |
+
import { page } from "$app/stores";
|
5 |
+
|
6 |
+
import type { CommunityCard } from "$lib/type";
|
7 |
+
import { galleryStore } from "$lib/stores/use-gallery";
|
8 |
import Reactions from "./reactions/Reactions.svelte";
|
9 |
|
10 |
export let card: CommunityCard;
|
|
|
11 |
|
12 |
+
const handleClick = async () => {
|
13 |
+
const request = await fetch(`/api/community/${card?.id}`);
|
14 |
+
const { gallery, next, previous } = await request.json();
|
15 |
+
galleryStore.set({
|
16 |
+
gallery,
|
17 |
+
open: true,
|
18 |
+
next,
|
19 |
+
previous
|
20 |
+
});
|
21 |
+
|
22 |
+
$page.url.searchParams.set('gallery', card?.id);
|
23 |
+
goto(`?${$page.url.searchParams.toString()}`);
|
24 |
+
};
|
25 |
+
</script>
|
26 |
+
|
27 |
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
28 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
29 |
<div
|
30 |
class="cursor-pointer group bg-neutral-700 rounded-xl h-[400px] relative flex items-start justify-between flex-col p-5 transition-all duration-200 brightness-75 hover:brightness-100 z-[1] overflow-hidden"
|
31 |
+
on:click={handleClick}
|
32 |
>
|
33 |
<div class="w-full h-full absolute top-0 left-0 -z-[1] rounded-xl overflow-hidden">
|
34 |
<div class="w-full h-full bg-center bg-cover transition-all duration-200 group-hover:scale-110 " style="background-image: url('{env.PUBLIC_FILE_UPLOAD_DIR}/{card.image}');"></div>
|
|
|
38 |
<p class="text-white font-semibold text-lg">{card.prompt}</p>
|
39 |
<p class="text-white/75 font-regular text-base">{card.model.id}</p>
|
40 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
</div>
|
42 |
+
<Reactions reactions={card.reactions} gallery_id={card.id} />
|
43 |
</div>
|
src/lib/components/community/reactions/Reaction.svelte
CHANGED
@@ -1,6 +1,4 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import UserIsLogged from "$lib/components/UserIsLogged.svelte";
|
3 |
-
|
4 |
export let emoji: string;
|
5 |
export let count: number;
|
6 |
export let gallery_id: string;
|
@@ -22,13 +20,11 @@
|
|
22 |
}
|
23 |
</script>
|
24 |
|
25 |
-
<
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
>
|
31 |
-
|
32 |
-
|
33 |
-
</button>
|
34 |
-
</UserIsLogged>
|
|
|
1 |
<script lang="ts">
|
|
|
|
|
2 |
export let emoji: string;
|
3 |
export let count: number;
|
4 |
export let gallery_id: string;
|
|
|
20 |
}
|
21 |
</script>
|
22 |
|
23 |
+
<button
|
24 |
+
class="rounded-full bg-white text-neutral-800 font-bold flex items-center justify-start gap-1.5 px-3 py-1 border border-white hover:bg-neutral-200 text-sm"
|
25 |
+
class:bg-opacity-60={!liked}
|
26 |
+
on:click={() => handleReaction(emoji)}
|
27 |
+
>
|
28 |
+
<span class="text-base">{emoji}</span>
|
29 |
+
{count}
|
30 |
+
</button>
|
|
|
|
src/lib/components/community/reactions/Reactions.svelte
CHANGED
@@ -1,9 +1,10 @@
|
|
1 |
<script lang="ts">
|
2 |
-
|
3 |
import Add from "$lib/components/community/reactions/Add.svelte";
|
4 |
import Reaction from "$lib/components/community/reactions/Reaction.svelte";
|
5 |
import { get } from 'svelte/store';
|
6 |
import { userStore } from "$lib/stores/use-user";
|
|
|
7 |
|
8 |
let user = get(userStore);
|
9 |
|
@@ -24,26 +25,30 @@
|
|
24 |
$: groupedReactions = groupReactionsByEmoji(reactions);
|
25 |
</script>
|
26 |
|
27 |
-
|
28 |
-
<
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
reactions = [...reactions, { emoji, userId: user?.sub, galleryId: gallery_id, id }];
|
38 |
-
}
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
<Add
|
43 |
-
count={groupedReactions?.length}
|
44 |
-
reactions={groupedReactions}
|
45 |
-
{gallery_id}
|
46 |
-
onAdd={(emoji, id) => {
|
47 |
-
reactions = [...reactions, { emoji, userId: user?.sub, galleryId: gallery_id, id }];
|
48 |
-
}}
|
49 |
-
/>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import type { ReactionType } from "$lib/type";
|
3 |
import Add from "$lib/components/community/reactions/Add.svelte";
|
4 |
import Reaction from "$lib/components/community/reactions/Reaction.svelte";
|
5 |
import { get } from 'svelte/store';
|
6 |
import { userStore } from "$lib/stores/use-user";
|
7 |
+
import UserIsLogged from "$lib/components/UserIsLogged.svelte";
|
8 |
|
9 |
let user = get(userStore);
|
10 |
|
|
|
25 |
$: groupedReactions = groupReactionsByEmoji(reactions);
|
26 |
</script>
|
27 |
|
28 |
+
<UserIsLogged>
|
29 |
+
<div class="flex items-center justify-start gap-2">
|
30 |
+
{#each groupedReactions as reaction}
|
31 |
+
<Reaction
|
32 |
+
emoji={reaction.emoji}
|
33 |
+
count={reaction?.count}
|
34 |
+
liked={reaction?.liked}
|
35 |
+
{gallery_id}
|
36 |
+
onReact={(emoji, id, deleted) => {
|
37 |
+
if (deleted) {
|
38 |
+
reactions = reactions.filter((reaction) => reaction.id !== id);
|
39 |
+
} else {
|
40 |
+
reactions = [...reactions, { emoji, userId: user?.sub, galleryId: gallery_id, id }];
|
41 |
+
}
|
42 |
+
}}
|
43 |
+
/>
|
44 |
+
{/each}
|
45 |
+
<Add
|
46 |
+
count={groupedReactions?.length}
|
47 |
+
reactions={groupedReactions}
|
48 |
+
{gallery_id}
|
49 |
+
onAdd={(emoji, id) => {
|
50 |
reactions = [...reactions, { emoji, userId: user?.sub, galleryId: gallery_id, id }];
|
51 |
+
}}
|
52 |
+
/>
|
53 |
+
</div>
|
54 |
+
</UserIsLogged>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/components/community/viewer/Viewer.svelte
ADDED
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { clickoutside } from '@svelte-put/clickoutside';
|
3 |
+
import { goto } from "$app/navigation";
|
4 |
+
import { page } from "$app/stores";
|
5 |
+
import { get } from "svelte/store";
|
6 |
+
import { env } from "$env/dynamic/public";
|
7 |
+
import Icon from "@iconify/svelte";
|
8 |
+
|
9 |
+
import { galleryStore } from "$lib/stores/use-gallery";
|
10 |
+
import UserIsLogged from '$lib/components/UserIsLogged.svelte';
|
11 |
+
import Reactions from '../reactions/Reactions.svelte';
|
12 |
+
import Button from '$lib/components/Button.svelte';
|
13 |
+
|
14 |
+
let { open, gallery, previous, next } = get(galleryStore);
|
15 |
+
let loading = false;
|
16 |
+
|
17 |
+
galleryStore.subscribe((value) => {
|
18 |
+
open = value?.open;
|
19 |
+
gallery = value?.gallery;
|
20 |
+
previous = value?.previous;
|
21 |
+
next = value?.next;
|
22 |
+
});
|
23 |
+
|
24 |
+
const handleClose = () => {
|
25 |
+
galleryStore.update((value) => {
|
26 |
+
return {
|
27 |
+
...value,
|
28 |
+
open: false,
|
29 |
+
};
|
30 |
+
});
|
31 |
+
|
32 |
+
$page.url.searchParams.delete('model');
|
33 |
+
goto(`?${$page.url.searchParams.toString()}`);
|
34 |
+
};
|
35 |
+
|
36 |
+
const handlePagination = async (id?: string) => {
|
37 |
+
if (!id) return;
|
38 |
+
loading = true;
|
39 |
+
const request = await fetch(`/api/community/${id}`);
|
40 |
+
const { gallery, next, previous } = await request.json();
|
41 |
+
galleryStore.set({
|
42 |
+
gallery,
|
43 |
+
open: true,
|
44 |
+
next,
|
45 |
+
previous
|
46 |
+
});
|
47 |
+
loading = false;
|
48 |
+
$page.url.searchParams.set('gallery', id);
|
49 |
+
goto(`?${$page.url.searchParams.toString()}`);
|
50 |
+
};
|
51 |
+
</script>
|
52 |
+
|
53 |
+
<div
|
54 |
+
class="w-full fixed top-0 left-0 h-full bg-black bg-opacity-50 z-40 backdrop-blur transition-all duration-100 p-6 lg:p-10 flex items-center justify-center"
|
55 |
+
class:opacity-0={!open}
|
56 |
+
class:pointer-events-none={!open}
|
57 |
+
>
|
58 |
+
<div
|
59 |
+
class="mx-auto lg:h-2/3 w-full max-w-6xl bg-neutral-900 transition-all duration-200 grid grid-cols-1 lg:grid-cols-2 rounded-xl overflow-hidden"
|
60 |
+
class:translate-x-full={!open}
|
61 |
+
use:clickoutside on:clickoutside={handleClose}
|
62 |
+
>
|
63 |
+
{#if gallery?.id}
|
64 |
+
<img src={env.PUBLIC_FILE_UPLOAD_DIR}/{gallery?.image} alt={gallery?.prompt} class="w-full h-full object-cover" />
|
65 |
+
<div class="flex flex-col justify-between w-full">
|
66 |
+
<div class="w-full p-8">
|
67 |
+
<header class="w-full flex items-start justify-between">
|
68 |
+
<div class="flex items-center justify-start gap-4">
|
69 |
+
<img src={gallery?.user?.picture} class="w-12 h-12 rounded-full object-cover" alt={gallery?.user?.name} />
|
70 |
+
<div>
|
71 |
+
<p class="text-neutral-100 font-bold text-lg">
|
72 |
+
{gallery?.user?.name}
|
73 |
+
</p>
|
74 |
+
<p class="text-neutral-400 text-sm">
|
75 |
+
@{gallery?.user?.preferred_username}
|
76 |
+
</p>
|
77 |
+
</div>
|
78 |
+
</div>
|
79 |
+
<button on:click={handleClose}>
|
80 |
+
<Icon icon="carbon:close" class="w-6 h-6 text-white" />
|
81 |
+
</button>
|
82 |
+
</header>
|
83 |
+
<div class="mt-8 grid grid-cols-1 gap-5">
|
84 |
+
<Reactions reactions={gallery?.reactions} gallery_id={gallery.id} />
|
85 |
+
<div>
|
86 |
+
<a
|
87 |
+
href="/generate?model={gallery?.model?.id}"
|
88 |
+
class="flex items-center justify-start gap-4 rounded-lg cursor-pointer w-full text-left transition-all duration-200 hover:bg-neutral-950/50 p-3 -mx-3 group relative"
|
89 |
+
>
|
90 |
+
<img src={gallery?.model?.image} alt={gallery?.model?.title} class="w-14 h-14 rounded-lg object-cover" />
|
91 |
+
<div>
|
92 |
+
<p class="text-neutral-200 text-base font-medium">{gallery?.model?.title}</p>
|
93 |
+
<p class="text-neutral-400 text-sm">{gallery?.model?.id}</p>
|
94 |
+
</div>
|
95 |
+
<div class="rounded-full absolute top-1/2 -translate-y-1/2 text-neutral-100 w-8 h-8 right-4 bg-pink-500 flex items-center justify-center transition-all duration-200 group-hover:opacity-100 opacity-0">
|
96 |
+
<Icon icon="tabler:arrow-up" class="w-5 h-5 transform rotate-45 font-bold" />
|
97 |
+
</div>
|
98 |
+
</a>
|
99 |
+
</div>
|
100 |
+
<div>
|
101 |
+
<p class="text-neutral-400 font-semibold text-xs uppercase">
|
102 |
+
Prompt
|
103 |
+
</p>
|
104 |
+
<p class="text-neutral-200 text-base font-medium mt-2">"{gallery?.prompt}"</p>
|
105 |
+
</div>
|
106 |
+
</div>
|
107 |
+
</div>
|
108 |
+
<footer class="border-t border-neutral-800 px-8 py-6 flex items-center justify-between">
|
109 |
+
<Button
|
110 |
+
size="lg"
|
111 |
+
theme="dark"
|
112 |
+
disabled={!previous}
|
113 |
+
loading={loading}
|
114 |
+
onClick={() => handlePagination(previous)}
|
115 |
+
>
|
116 |
+
Previous
|
117 |
+
</Button>
|
118 |
+
<Button
|
119 |
+
size="lg"
|
120 |
+
theme="light"
|
121 |
+
loading={loading}
|
122 |
+
disabled={!next}
|
123 |
+
onClick={() => handlePagination(next)}
|
124 |
+
>
|
125 |
+
Next
|
126 |
+
</Button>
|
127 |
+
</footer>
|
128 |
+
</div>
|
129 |
+
{/if}
|
130 |
+
</div>
|
131 |
+
</div>
|
src/lib/components/generate/Response.svelte
CHANGED
@@ -39,8 +39,6 @@
|
|
39 |
generation = value;
|
40 |
})
|
41 |
|
42 |
-
$: console.log(generation);
|
43 |
-
|
44 |
// create a ms countup depending on the generation time, to show the user how long it took to generate the image
|
45 |
let ms = 0;
|
46 |
let interval: any;
|
@@ -109,7 +107,7 @@
|
|
109 |
<p class="text-neutral-400 font-semibold text-xs uppercase">
|
110 |
Model selected
|
111 |
</p>
|
112 |
-
<div class="flex items-center justify-start gap-4 px-2 py-2.5
|
113 |
<img src={generation?.form?.model.image} alt={generation?.form?.model.title} class="w-14 h-14 rounded-lg object-cover" />
|
114 |
<div>
|
115 |
<p class="text-neutral-200 text-base font-medium">{generation?.form?.model.title}</p>
|
|
|
39 |
generation = value;
|
40 |
})
|
41 |
|
|
|
|
|
42 |
// create a ms countup depending on the generation time, to show the user how long it took to generate the image
|
43 |
let ms = 0;
|
44 |
let interval: any;
|
|
|
107 |
<p class="text-neutral-400 font-semibold text-xs uppercase">
|
108 |
Model selected
|
109 |
</p>
|
110 |
+
<div class="flex items-center justify-start gap-4 px-2 py-2.5 rounded-lg cursor-pointer w-full text-left">
|
111 |
<img src={generation?.form?.model.image} alt={generation?.form?.model.title} class="w-14 h-14 rounded-lg object-cover" />
|
112 |
<div>
|
113 |
<p class="text-neutral-200 text-base font-medium">{generation?.form?.model.title}</p>
|
src/lib/components/models/Card.svelte
CHANGED
@@ -10,7 +10,10 @@
|
|
10 |
const handleClick = async () => {
|
11 |
const request = await fetch(`/api/models/${card?.id?.replace("/", "@")}?full=true`);
|
12 |
const { model } = await request.json();
|
13 |
-
modelStore.set(
|
|
|
|
|
|
|
14 |
$page.url.searchParams.set('model', card?.id);
|
15 |
goto(`?${$page.url.searchParams.toString()}`);
|
16 |
};
|
|
|
10 |
const handleClick = async () => {
|
11 |
const request = await fetch(`/api/models/${card?.id?.replace("/", "@")}?full=true`);
|
12 |
const { model } = await request.json();
|
13 |
+
modelStore.set({
|
14 |
+
model,
|
15 |
+
open: true
|
16 |
+
});
|
17 |
$page.url.searchParams.set('model', card?.id);
|
18 |
goto(`?${$page.url.searchParams.toString()}`);
|
19 |
};
|
src/lib/components/models/drawer/Drawer.svelte
CHANGED
@@ -10,14 +10,20 @@
|
|
10 |
import Comments from '$lib/components/models/drawer/comments/Comments.svelte';
|
11 |
import { env } from '$env/dynamic/public';
|
12 |
|
13 |
-
let
|
14 |
|
15 |
modelStore.subscribe((value) => {
|
16 |
-
|
|
|
17 |
});
|
18 |
|
19 |
const handleClose = () => {
|
20 |
-
modelStore.
|
|
|
|
|
|
|
|
|
|
|
21 |
|
22 |
$page.url.searchParams.delete('model');
|
23 |
goto(`?${$page.url.searchParams.toString()}`);
|
@@ -26,30 +32,30 @@
|
|
26 |
|
27 |
<div
|
28 |
class="w-full fixed top-0 left-0 h-full bg-black bg-opacity-50 z-40 backdrop-blur transition-all duration-100"
|
29 |
-
class:opacity-0={!
|
30 |
-
class:pointer-events-none={!
|
31 |
>
|
32 |
<div
|
33 |
class="ml-auto w-full max-w-3xl bg-neutral-950 h-full border-l border-neutral-800 transition-all duration-200 flex flex-col justify-between"
|
34 |
-
class:translate-x-full={!
|
35 |
use:clickoutside on:clickoutside={handleClose}
|
36 |
>
|
37 |
<div class="p-8 overflow-auto">
|
38 |
<header class="flex w-full justify-between items-start mb-6">
|
39 |
<div class="flex items-center justify-start gap-3 lg:gap-6">
|
40 |
-
<img src={
|
41 |
<div>
|
42 |
<p class="text-white font-semibold text-lg lg:text-2xl mb-1 truncate">
|
43 |
-
{
|
44 |
</p>
|
45 |
<div class="justify-start items-center gap-2 flex">
|
46 |
<div class="bg-red-500 bg-opacity-20 border border-red-500 px-3 py-1.5 rounded-full text-neutral-100 flex items-center justify-center gap-1 font-bold text-xs">
|
47 |
<Icon icon="solar:heart-bold" class="lg:w-4 lg:h-4 w-3 h-3 text-red-500" />
|
48 |
-
{
|
49 |
</div>
|
50 |
<div class="bg-blue-500 bg-opacity-20 border border-blue-500 px-3 py-1.5 rounded-full text-neutral-100 flex items-center justify-center gap-1 font-bold text-xs">
|
51 |
<Icon icon="solar:download-square-bold" class="lg:w-4 lg:h-4 w-3 h-3 text-blue-500" />
|
52 |
-
{
|
53 |
</div>
|
54 |
</div>
|
55 |
</div>
|
@@ -59,11 +65,11 @@
|
|
59 |
</button>
|
60 |
</header>
|
61 |
<main>
|
62 |
-
{#if
|
63 |
<div>
|
64 |
<p class="text-neutral-400 uppercase text-xs font-bold">Examples</p>
|
65 |
<div class="grid grid-cols-3 md:grid-cols-5 lg:grid-cols-6 gap-5 mt-2">
|
66 |
-
{#each
|
67 |
<div class="w-full h-[120px] relative z-[1] mb-3 overflow-hidden">
|
68 |
<img src="{env.PUBLIC_FILE_UPLOAD_DIR}/{example.image}" class="w-full h-full bg-center bg-cover rounded-lg object-cover object-center bg-neutral-800" alt={example.prompt} />
|
69 |
</div>
|
@@ -75,11 +81,11 @@
|
|
75 |
</div>
|
76 |
<footer class="p-8 border-t border-neutral-900 bg-neutral-900/30">
|
77 |
<p class="font-semibold text-neutral-100 text-base lg:text-lg mb-6">
|
78 |
-
Commentaires ({
|
79 |
</p>
|
80 |
-
{#if
|
81 |
<UserIsLogged>
|
82 |
-
<Comments comments={
|
83 |
</UserIsLogged>
|
84 |
{/if}
|
85 |
</footer>
|
|
|
10 |
import Comments from '$lib/components/models/drawer/comments/Comments.svelte';
|
11 |
import { env } from '$env/dynamic/public';
|
12 |
|
13 |
+
let { open, model } = get(modelStore);
|
14 |
|
15 |
modelStore.subscribe((value) => {
|
16 |
+
open = value?.open;
|
17 |
+
model = value?.model;
|
18 |
});
|
19 |
|
20 |
const handleClose = () => {
|
21 |
+
modelStore.update((value) => {
|
22 |
+
return {
|
23 |
+
...value,
|
24 |
+
open: false,
|
25 |
+
};
|
26 |
+
});
|
27 |
|
28 |
$page.url.searchParams.delete('model');
|
29 |
goto(`?${$page.url.searchParams.toString()}`);
|
|
|
32 |
|
33 |
<div
|
34 |
class="w-full fixed top-0 left-0 h-full bg-black bg-opacity-50 z-40 backdrop-blur transition-all duration-100"
|
35 |
+
class:opacity-0={!open}
|
36 |
+
class:pointer-events-none={!open}
|
37 |
>
|
38 |
<div
|
39 |
class="ml-auto w-full max-w-3xl bg-neutral-950 h-full border-l border-neutral-800 transition-all duration-200 flex flex-col justify-between"
|
40 |
+
class:translate-x-full={!open}
|
41 |
use:clickoutside on:clickoutside={handleClose}
|
42 |
>
|
43 |
<div class="p-8 overflow-auto">
|
44 |
<header class="flex w-full justify-between items-start mb-6">
|
45 |
<div class="flex items-center justify-start gap-3 lg:gap-6">
|
46 |
+
<img src={model?.image} class="lg:w-16 lg:h-16 w-12 h-12 rounded-xl bg-neutral-800 object-cover" alt={model?.id} />
|
47 |
<div>
|
48 |
<p class="text-white font-semibold text-lg lg:text-2xl mb-1 truncate">
|
49 |
+
{model?.title ?? model?.id}
|
50 |
</p>
|
51 |
<div class="justify-start items-center gap-2 flex">
|
52 |
<div class="bg-red-500 bg-opacity-20 border border-red-500 px-3 py-1.5 rounded-full text-neutral-100 flex items-center justify-center gap-1 font-bold text-xs">
|
53 |
<Icon icon="solar:heart-bold" class="lg:w-4 lg:h-4 w-3 h-3 text-red-500" />
|
54 |
+
{model?.likes ?? 0}
|
55 |
</div>
|
56 |
<div class="bg-blue-500 bg-opacity-20 border border-blue-500 px-3 py-1.5 rounded-full text-neutral-100 flex items-center justify-center gap-1 font-bold text-xs">
|
57 |
<Icon icon="solar:download-square-bold" class="lg:w-4 lg:h-4 w-3 h-3 text-blue-500" />
|
58 |
+
{model?.downloads ?? 0}
|
59 |
</div>
|
60 |
</div>
|
61 |
</div>
|
|
|
65 |
</button>
|
66 |
</header>
|
67 |
<main>
|
68 |
+
{#if model?.gallery && model?.gallery?.length > 0}
|
69 |
<div>
|
70 |
<p class="text-neutral-400 uppercase text-xs font-bold">Examples</p>
|
71 |
<div class="grid grid-cols-3 md:grid-cols-5 lg:grid-cols-6 gap-5 mt-2">
|
72 |
+
{#each model?.gallery as example}
|
73 |
<div class="w-full h-[120px] relative z-[1] mb-3 overflow-hidden">
|
74 |
<img src="{env.PUBLIC_FILE_UPLOAD_DIR}/{example.image}" class="w-full h-full bg-center bg-cover rounded-lg object-cover object-center bg-neutral-800" alt={example.prompt} />
|
75 |
</div>
|
|
|
81 |
</div>
|
82 |
<footer class="p-8 border-t border-neutral-900 bg-neutral-900/30">
|
83 |
<p class="font-semibold text-neutral-100 text-base lg:text-lg mb-6">
|
84 |
+
Commentaires ({model?.comments?.length ?? 0})
|
85 |
</p>
|
86 |
+
{#if model?.id}
|
87 |
<UserIsLogged>
|
88 |
+
<Comments comments={model?.comments} model={model} />
|
89 |
</UserIsLogged>
|
90 |
{/if}
|
91 |
</footer>
|
src/lib/components/models/drawer/comments/Comments.svelte
CHANGED
@@ -1,4 +1,6 @@
|
|
1 |
<script lang="ts">
|
|
|
|
|
2 |
import Button from "$lib/components/Button.svelte";
|
3 |
import type { ModelCard, CommentType } from "$lib/type";
|
4 |
import Comment from "./Comment.svelte";
|
@@ -8,7 +10,6 @@
|
|
8 |
|
9 |
let text = "";
|
10 |
let loading = false;
|
11 |
-
let error: string | undefined = undefined;
|
12 |
|
13 |
const handleSubmit = async () => {
|
14 |
loading = true;
|
@@ -22,7 +23,7 @@
|
|
22 |
|
23 |
const comment_response = await comment_request.json();
|
24 |
if (comment_response.error) {
|
25 |
-
error
|
26 |
} else {
|
27 |
comments = [comment_response.comment, ...comments];
|
28 |
text = "";
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { error } from "$lib/utils/toaster";
|
3 |
+
|
4 |
import Button from "$lib/components/Button.svelte";
|
5 |
import type { ModelCard, CommentType } from "$lib/type";
|
6 |
import Comment from "./Comment.svelte";
|
|
|
10 |
|
11 |
let text = "";
|
12 |
let loading = false;
|
|
|
13 |
|
14 |
const handleSubmit = async () => {
|
15 |
loading = true;
|
|
|
23 |
|
24 |
const comment_response = await comment_request.json();
|
25 |
if (comment_response.error) {
|
26 |
+
error(comment_response.error)
|
27 |
} else {
|
28 |
comments = [comment_response.comment, ...comments];
|
29 |
text = "";
|
src/lib/stores/use-gallery.ts
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { writable } from "svelte/store";
|
2 |
+
|
3 |
+
import type { CommunityCard } from "$lib/type";
|
4 |
+
|
5 |
+
interface Props {
|
6 |
+
open: boolean,
|
7 |
+
gallery: CommunityCard | undefined,
|
8 |
+
previous?: string,
|
9 |
+
next?: string,
|
10 |
+
}
|
11 |
+
|
12 |
+
export const galleryStore = writable<Props>({
|
13 |
+
open: false,
|
14 |
+
gallery: undefined,
|
15 |
+
previous: undefined,
|
16 |
+
next: undefined,
|
17 |
+
});
|
src/lib/stores/use-model.ts
CHANGED
@@ -2,4 +2,12 @@ import { writable } from "svelte/store";
|
|
2 |
|
3 |
import type { ModelCard } from "$lib/type";
|
4 |
|
5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
import type { ModelCard } from "$lib/type";
|
4 |
|
5 |
+
interface Props {
|
6 |
+
open: boolean,
|
7 |
+
model: ModelCard | undefined
|
8 |
+
}
|
9 |
+
|
10 |
+
export const modelStore = writable<Props>({
|
11 |
+
open: false,
|
12 |
+
model: undefined
|
13 |
+
});
|
src/lib/type.ts
CHANGED
@@ -10,6 +10,8 @@ export interface CommunityCard {
|
|
10 |
id: string,
|
11 |
model: ModelCard,
|
12 |
prompt: string,
|
|
|
|
|
13 |
image: string,
|
14 |
}
|
15 |
|
|
|
10 |
id: string,
|
11 |
model: ModelCard,
|
12 |
prompt: string,
|
13 |
+
createdAt: Date,
|
14 |
+
user: UserType,
|
15 |
image: string,
|
16 |
}
|
17 |
|
src/lib/utils/toaster.ts
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { toast } from '@zerodevx/svelte-toast'
|
2 |
+
|
3 |
+
export const success = (message: string) => toast.push(message, {
|
4 |
+
theme: {
|
5 |
+
'--toastBackground': '#4caf50',
|
6 |
+
'--toastProgressBackground': '#81c784',
|
7 |
+
'--toastProgressAfterBackground': '#4caf50',
|
8 |
+
'--toastColor': '#fff',
|
9 |
+
}
|
10 |
+
})
|
11 |
+
|
12 |
+
export const error = (message: string) => toast.push(message, {
|
13 |
+
theme: {
|
14 |
+
'--toastBackground': '#f44336',
|
15 |
+
'--toastProgressBackground': '#e57373',
|
16 |
+
'--toastProgressAfterBackground': '#f44336',
|
17 |
+
'--toastColor': '#fff',
|
18 |
+
}
|
19 |
+
})
|
src/routes/+layout.svelte
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
<script>
|
2 |
import { get } from "svelte/store";
|
3 |
import Icon from "@iconify/svelte";
|
|
|
4 |
|
5 |
import Sidebar from "$lib/components/sidebar/Sidebar.svelte";
|
6 |
import "$lib/styles/tailwind.css"
|
@@ -23,6 +24,7 @@
|
|
23 |
<main id="app" class="flex-1 h-screen overflow-y-auto">
|
24 |
<slot />
|
25 |
</main>
|
|
|
26 |
<Dialog {open} onClose={() => loginModalStore.set(false)}>
|
27 |
<div class="grid grid-cols-1 gap-7 text-center">
|
28 |
<Icon icon="fluent-emoji:sparkles" class="w-12 h-12 mx-auto" />
|
|
|
1 |
<script>
|
2 |
import { get } from "svelte/store";
|
3 |
import Icon from "@iconify/svelte";
|
4 |
+
import { SvelteToast } from '@zerodevx/svelte-toast'
|
5 |
|
6 |
import Sidebar from "$lib/components/sidebar/Sidebar.svelte";
|
7 |
import "$lib/styles/tailwind.css"
|
|
|
24 |
<main id="app" class="flex-1 h-screen overflow-y-auto">
|
25 |
<slot />
|
26 |
</main>
|
27 |
+
<SvelteToast />
|
28 |
<Dialog {open} onClose={() => loginModalStore.set(false)}>
|
29 |
<div class="grid grid-cols-1 gap-7 text-center">
|
30 |
<Icon icon="fluent-emoji:sparkles" class="w-12 h-12 mx-auto" />
|
src/routes/+page.svelte
CHANGED
@@ -16,8 +16,6 @@
|
|
16 |
|
17 |
export let data
|
18 |
|
19 |
-
console.log(data)
|
20 |
-
|
21 |
let form = {
|
22 |
filter: "hotest",
|
23 |
search: "",
|
|
|
16 |
|
17 |
export let data
|
18 |
|
|
|
|
|
19 |
let form = {
|
20 |
filter: "hotest",
|
21 |
search: "",
|
src/routes/+page.ts
CHANGED
@@ -4,7 +4,7 @@ export async function load({ fetch, url }) {
|
|
4 |
let model;
|
5 |
|
6 |
if (model_param) {
|
7 |
-
const model_request = await fetch(`/api/models/${model_param?.replace("/", "@")}`, {
|
8 |
method: "GET",
|
9 |
headers: {
|
10 |
"Content-Type": "application/json"
|
|
|
4 |
let model;
|
5 |
|
6 |
if (model_param) {
|
7 |
+
const model_request = await fetch(`/api/models/${model_param?.replace("/", "@")}?full=true`, {
|
8 |
method: "GET",
|
9 |
headers: {
|
10 |
"Content-Type": "application/json"
|
src/routes/api/community/[id]/+server.ts
ADDED
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { json, type RequestEvent } from '@sveltejs/kit';
|
2 |
+
import prisma from '$lib/prisma';
|
3 |
+
|
4 |
+
/** @type {import('./$types').RequestHandler} */
|
5 |
+
|
6 |
+
export async function GET({ params } : RequestEvent) {
|
7 |
+
const id = params.id;
|
8 |
+
|
9 |
+
const gallery = await prisma.gallery.findFirst({
|
10 |
+
where: {
|
11 |
+
id,
|
12 |
+
},
|
13 |
+
select: {
|
14 |
+
image: true,
|
15 |
+
id: true,
|
16 |
+
prompt: true,
|
17 |
+
createdAt: true,
|
18 |
+
user: {
|
19 |
+
select: {
|
20 |
+
id: true,
|
21 |
+
name: true,
|
22 |
+
sub: true,
|
23 |
+
picture: true,
|
24 |
+
preferred_username: true,
|
25 |
+
}
|
26 |
+
},
|
27 |
+
model: {
|
28 |
+
select: {
|
29 |
+
title: true,
|
30 |
+
image: true,
|
31 |
+
id: true,
|
32 |
+
}
|
33 |
+
},
|
34 |
+
reactions: {
|
35 |
+
select: {
|
36 |
+
id: true,
|
37 |
+
emoji: true,
|
38 |
+
userId: true,
|
39 |
+
user: {
|
40 |
+
select: {
|
41 |
+
id: true,
|
42 |
+
name: true,
|
43 |
+
sub: true,
|
44 |
+
picture: true,
|
45 |
+
preferred_username: true,
|
46 |
+
}
|
47 |
+
}
|
48 |
+
}
|
49 |
+
}
|
50 |
+
}
|
51 |
+
})
|
52 |
+
|
53 |
+
if (!gallery) {
|
54 |
+
return json({
|
55 |
+
error: {
|
56 |
+
token: "Gallery not found"
|
57 |
+
}
|
58 |
+
}, { status: 404 })
|
59 |
+
}
|
60 |
+
|
61 |
+
const next = await prisma.gallery.findFirst({
|
62 |
+
where: {
|
63 |
+
isPublic: true,
|
64 |
+
createdAt: {
|
65 |
+
lt: gallery.createdAt
|
66 |
+
}
|
67 |
+
},
|
68 |
+
select: {
|
69 |
+
id: true,
|
70 |
+
}
|
71 |
+
})
|
72 |
+
|
73 |
+
const previous = await prisma.gallery.findFirst({
|
74 |
+
where: {
|
75 |
+
isPublic: true,
|
76 |
+
createdAt: {
|
77 |
+
gt: gallery.createdAt
|
78 |
+
}
|
79 |
+
},
|
80 |
+
select: {
|
81 |
+
id: true,
|
82 |
+
}
|
83 |
+
})
|
84 |
+
|
85 |
+
return json({
|
86 |
+
gallery,
|
87 |
+
next: next ? next.id : undefined,
|
88 |
+
previous: previous ? previous.id : undefined,
|
89 |
+
})
|
90 |
+
}
|
src/routes/api/models/[id]/comments/+server.ts
CHANGED
@@ -66,6 +66,18 @@ export async function POST({ cookies, request, params } : RequestEvent) {
|
|
66 |
id
|
67 |
}
|
68 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
}
|
70 |
})
|
71 |
|
|
|
66 |
id
|
67 |
}
|
68 |
}
|
69 |
+
},
|
70 |
+
select: {
|
71 |
+
id: true,
|
72 |
+
text: true,
|
73 |
+
createdAt: true,
|
74 |
+
user: {
|
75 |
+
select: {
|
76 |
+
name: true,
|
77 |
+
picture: true,
|
78 |
+
sub: true,
|
79 |
+
}
|
80 |
+
}
|
81 |
}
|
82 |
})
|
83 |
|
src/routes/gallery/+page.svelte
CHANGED
@@ -8,6 +8,7 @@
|
|
8 |
import Radio from "$lib/components/fields/Radio.svelte";
|
9 |
import { COMMUNITY_FILTER_OPTIONS } from "$lib/utils/index.js";
|
10 |
import GoTop from "$lib/components/GoTop.svelte";
|
|
|
11 |
// import UserIsLogged from "$lib/components/UserIsLogged.svelte";
|
12 |
|
13 |
export let data
|
@@ -83,4 +84,5 @@
|
|
83 |
/>
|
84 |
<GoTop />
|
85 |
</div>
|
|
|
86 |
</main>
|
|
|
8 |
import Radio from "$lib/components/fields/Radio.svelte";
|
9 |
import { COMMUNITY_FILTER_OPTIONS } from "$lib/utils/index.js";
|
10 |
import GoTop from "$lib/components/GoTop.svelte";
|
11 |
+
import GalleryViewer from "$lib/components/community/viewer/Viewer.svelte";
|
12 |
// import UserIsLogged from "$lib/components/UserIsLogged.svelte";
|
13 |
|
14 |
export let data
|
|
|
84 |
/>
|
85 |
<GoTop />
|
86 |
</div>
|
87 |
+
<GalleryViewer />
|
88 |
</main>
|