Img preview (#70)
Browse files
package.json
CHANGED
@@ -16,6 +16,7 @@
|
|
16 |
"devDependencies": {
|
17 |
"@eslint/eslintrc": "^3.3.0",
|
18 |
"@eslint/js": "^9.22.0",
|
|
|
19 |
"@huggingface/hub": "^1.0.1",
|
20 |
"@huggingface/inference": "^3.5.1",
|
21 |
"@huggingface/tasks": "^0.17.1",
|
@@ -36,7 +37,7 @@
|
|
36 |
"globals": "^16.0.0",
|
37 |
"highlight.js": "^11.10.0",
|
38 |
"jiti": "^2.4.2",
|
39 |
-
"melt": "^0.
|
40 |
"postcss": "^8.4.38",
|
41 |
"prettier": "^3.1.1",
|
42 |
"prettier-plugin-svelte": "^3.2.6",
|
|
|
16 |
"devDependencies": {
|
17 |
"@eslint/eslintrc": "^3.3.0",
|
18 |
"@eslint/js": "^9.22.0",
|
19 |
+
"@floating-ui/dom": "^1.6.13",
|
20 |
"@huggingface/hub": "^1.0.1",
|
21 |
"@huggingface/inference": "^3.5.1",
|
22 |
"@huggingface/tasks": "^0.17.1",
|
|
|
37 |
"globals": "^16.0.0",
|
38 |
"highlight.js": "^11.10.0",
|
39 |
"jiti": "^2.4.2",
|
40 |
+
"melt": "^0.20.2",
|
41 |
"postcss": "^8.4.38",
|
42 |
"prettier": "^3.1.1",
|
43 |
"prettier-plugin-svelte": "^3.2.6",
|
pnpm-lock.yaml
CHANGED
@@ -21,6 +21,9 @@ importers:
|
|
21 |
'@eslint/js':
|
22 |
specifier: ^9.22.0
|
23 |
version: 9.22.0
|
|
|
|
|
|
|
24 |
'@huggingface/hub':
|
25 |
specifier: ^1.0.1
|
26 |
version: 1.0.1
|
@@ -82,8 +85,8 @@ importers:
|
|
82 |
specifier: ^2.4.2
|
83 |
version: 2.4.2
|
84 |
melt:
|
85 |
-
specifier: ^0.
|
86 |
-
version: 0.
|
87 |
postcss:
|
88 |
specifier: ^8.4.38
|
89 |
version: 8.5.3
|
@@ -1545,8 +1548,8 @@ packages:
|
|
1545 | |
1546 |
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
1547 |
|
1548 |
-
melt@0.
|
1549 |
-
resolution: {integrity: sha512-
|
1550 |
peerDependencies:
|
1551 |
'@floating-ui/dom': ^1.6.0
|
1552 |
svelte: ^5.0.0
|
@@ -3471,7 +3474,7 @@ snapshots:
|
|
3471 |
dependencies:
|
3472 |
'@jridgewell/sourcemap-codec': 1.5.0
|
3473 |
|
3474 |
-
melt@0.
|
3475 |
dependencies:
|
3476 |
'@floating-ui/dom': 1.6.13
|
3477 |
jest-axe: 9.0.0
|
|
|
21 |
'@eslint/js':
|
22 |
specifier: ^9.22.0
|
23 |
version: 9.22.0
|
24 |
+
'@floating-ui/dom':
|
25 |
+
specifier: ^1.6.13
|
26 |
+
version: 1.6.13
|
27 |
'@huggingface/hub':
|
28 |
specifier: ^1.0.1
|
29 |
version: 1.0.1
|
|
|
85 |
specifier: ^2.4.2
|
86 |
version: 2.4.2
|
87 |
melt:
|
88 |
+
specifier: ^0.20.2
|
89 |
+
version: 0.20.2(@floating-ui/[email protected])([email protected])
|
90 |
postcss:
|
91 |
specifier: ^8.4.38
|
92 |
version: 8.5.3
|
|
|
1548 | |
1549 |
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
1550 |
|
1551 |
+
melt@0.20.2:
|
1552 |
+
resolution: {integrity: sha512-vINBKFVqFN8LVU6K7AbEwlGTOzSi5BQulxhKkIroxPWnCA3LVsQEIDZabtf9TtnzFa/th0Rm8J0N+hW1v7VJDw==}
|
1553 |
peerDependencies:
|
1554 |
'@floating-ui/dom': ^1.6.0
|
1555 |
svelte: ^5.0.0
|
|
|
3474 |
dependencies:
|
3475 |
'@jridgewell/sourcemap-codec': 1.5.0
|
3476 |
|
3477 |
+
melt@0.20.2(@floating-ui/[email protected])([email protected]):
|
3478 |
dependencies:
|
3479 |
'@floating-ui/dom': 1.6.13
|
3480 |
jest-axe: 9.0.0
|
src/lib/components/inference-playground/img-preview.svelte
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { clickOutside } from "$lib/actions/click-outside.js";
|
3 |
+
import { fade, scale } from "svelte/transition";
|
4 |
+
import IconCross from "~icons/carbon/close";
|
5 |
+
|
6 |
+
interface Props {
|
7 |
+
img?: string;
|
8 |
+
}
|
9 |
+
|
10 |
+
let { img = $bindable() }: Props = $props();
|
11 |
+
|
12 |
+
let dialog: HTMLDialogElement | undefined = $state();
|
13 |
+
|
14 |
+
$effect(() => {
|
15 |
+
if (img) {
|
16 |
+
dialog?.showModal();
|
17 |
+
} else {
|
18 |
+
setTimeout(() => dialog?.close(), 250);
|
19 |
+
}
|
20 |
+
});
|
21 |
+
</script>
|
22 |
+
|
23 |
+
<dialog
|
24 |
+
class="backdrop:bg-transparent"
|
25 |
+
bind:this={dialog}
|
26 |
+
onclose={e => {
|
27 |
+
e.preventDefault();
|
28 |
+
img = undefined;
|
29 |
+
}}
|
30 |
+
>
|
31 |
+
{#if img}
|
32 |
+
<!-- Backdrop -->
|
33 |
+
<div
|
34 |
+
class="fixed inset-0 z-50 flex items-center justify-center overflow-hidden bg-black/50 backdrop-blur-sm"
|
35 |
+
transition:fade={{ duration: 150 }}
|
36 |
+
>
|
37 |
+
<!-- Content -->
|
38 |
+
<img
|
39 |
+
class="max-h-[calc(100vh-120px)] max-w-[calc(100vw-120px)] object-contain"
|
40 |
+
src={img}
|
41 |
+
alt=""
|
42 |
+
use:clickOutside={() => (img = undefined)}
|
43 |
+
transition:scale={{ start: 0.975, duration: 250 }}
|
44 |
+
/>
|
45 |
+
|
46 |
+
<button
|
47 |
+
type="button"
|
48 |
+
class="absolute top-3 right-3 inline-flex h-8 w-8 items-center justify-center rounded-lg bg-transparent text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900 dark:hover:bg-gray-600 dark:hover:text-white"
|
49 |
+
onclick={() => (img = undefined)}
|
50 |
+
aria-label="Close modal"
|
51 |
+
>
|
52 |
+
<div class="text-xl">
|
53 |
+
<IconCross />
|
54 |
+
</div>
|
55 |
+
</button>
|
56 |
+
</div>
|
57 |
+
{/if}
|
58 |
+
</dialog>
|
src/lib/components/inference-playground/message.svelte
CHANGED
@@ -6,6 +6,8 @@
|
|
6 |
import { FileUpload } from "melt/builders";
|
7 |
import { fade } from "svelte/transition";
|
8 |
import IconImage from "~icons/carbon/image-reference";
|
|
|
|
|
9 |
|
10 |
type Props = {
|
11 |
conversation: Conversation;
|
@@ -41,6 +43,8 @@
|
|
41 |
},
|
42 |
disabled: () => !canUploadImgs,
|
43 |
});
|
|
|
|
|
44 |
</script>
|
45 |
|
46 |
<div
|
@@ -119,16 +123,28 @@
|
|
119 |
</Tooltip>
|
120 |
</div>
|
121 |
</div>
|
|
|
122 |
{#if message.images?.length}
|
123 |
<div class="mt-2">
|
124 |
<div class="flex items-center gap-2">
|
125 |
{#each message.images as img (img)}
|
126 |
<div class="group/img relative">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
127 |
<img src={img} alt="uploaded" class="size-12 rounded-lg object-cover" />
|
128 |
<button
|
|
|
129 |
type="button"
|
130 |
-
onclick={
|
131 |
-
|
|
|
|
|
|
|
132 |
>
|
133 |
✕
|
134 |
</button>
|
@@ -138,3 +154,5 @@
|
|
138 |
</div>
|
139 |
{/if}
|
140 |
</div>
|
|
|
|
|
|
6 |
import { FileUpload } from "melt/builders";
|
7 |
import { fade } from "svelte/transition";
|
8 |
import IconImage from "~icons/carbon/image-reference";
|
9 |
+
import IconMaximize from "~icons/carbon/maximize";
|
10 |
+
import ImgPreview from "./img-preview.svelte";
|
11 |
|
12 |
type Props = {
|
13 |
conversation: Conversation;
|
|
|
43 |
},
|
44 |
disabled: () => !canUploadImgs,
|
45 |
});
|
46 |
+
|
47 |
+
let previewImg = $state<string>();
|
48 |
</script>
|
49 |
|
50 |
<div
|
|
|
123 |
</Tooltip>
|
124 |
</div>
|
125 |
</div>
|
126 |
+
|
127 |
{#if message.images?.length}
|
128 |
<div class="mt-2">
|
129 |
<div class="flex items-center gap-2">
|
130 |
{#each message.images as img (img)}
|
131 |
<div class="group/img relative">
|
132 |
+
<button
|
133 |
+
aria-label="expand"
|
134 |
+
class="absolute inset-0 z-10 grid place-items-center bg-gray-800/70 opacity-0 group-hover/img:opacity-100"
|
135 |
+
onclick={() => (previewImg = img)}
|
136 |
+
>
|
137 |
+
<IconMaximize />
|
138 |
+
</button>
|
139 |
<img src={img} alt="uploaded" class="size-12 rounded-lg object-cover" />
|
140 |
<button
|
141 |
+
aria-label="remove"
|
142 |
type="button"
|
143 |
+
onclick={e => {
|
144 |
+
e.stopPropagation();
|
145 |
+
message.images = message.images?.filter(i => i !== img);
|
146 |
+
}}
|
147 |
+
class="invisible absolute -top-1 -right-1 z-20 grid size-5 place-items-center rounded-full bg-gray-800 text-xs text-white group-hover/img:visible hover:bg-gray-700"
|
148 |
>
|
149 |
✕
|
150 |
</button>
|
|
|
154 |
</div>
|
155 |
{/if}
|
156 |
</div>
|
157 |
+
|
158 |
+
<ImgPreview bind:img={previewImg} />
|
src/lib/components/inference-playground/project-select.svelte
CHANGED
@@ -8,6 +8,7 @@
|
|
8 |
import IconSave from "~icons/carbon/save";
|
9 |
import IconDelete from "~icons/carbon/trash-can";
|
10 |
import { prompt } from "../prompts.svelte";
|
|
|
11 |
|
12 |
interface Props {
|
13 |
class?: string;
|
@@ -47,14 +48,29 @@
|
|
47 |
<IconCaret />
|
48 |
</div>
|
49 |
</button>
|
|
|
50 |
{#if isDefault}
|
51 |
-
<
|
52 |
-
|
53 |
-
|
|
|
|
|
|
|
|
|
|
|
54 |
{:else}
|
55 |
-
<
|
56 |
-
|
57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
{/if}
|
59 |
</div>
|
60 |
|
|
|
8 |
import IconSave from "~icons/carbon/save";
|
9 |
import IconDelete from "~icons/carbon/trash-can";
|
10 |
import { prompt } from "../prompts.svelte";
|
11 |
+
import Tooltip from "../tooltip.svelte";
|
12 |
|
13 |
interface Props {
|
14 |
class?: string;
|
|
|
48 |
<IconCaret />
|
49 |
</div>
|
50 |
</button>
|
51 |
+
|
52 |
{#if isDefault}
|
53 |
+
<Tooltip>
|
54 |
+
{#snippet trigger(tooltip)}
|
55 |
+
<button class="btn size-[32px] p-0" {...tooltip.trigger} onclick={saveProject}>
|
56 |
+
<IconSave />
|
57 |
+
</button>
|
58 |
+
{/snippet}
|
59 |
+
Save to Project
|
60 |
+
</Tooltip>
|
61 |
{:else}
|
62 |
+
<Tooltip>
|
63 |
+
{#snippet trigger(tooltip)}
|
64 |
+
<button
|
65 |
+
class="btn size-[32px] p-0"
|
66 |
+
{...tooltip.trigger}
|
67 |
+
onclick={() => (session.$.activeProjectId = "default")}
|
68 |
+
>
|
69 |
+
<IconCross />
|
70 |
+
</button>
|
71 |
+
{/snippet}
|
72 |
+
Close project
|
73 |
+
</Tooltip>
|
74 |
{/if}
|
75 |
</div>
|
76 |
|
src/lib/components/tooltip.svelte
CHANGED
@@ -1,19 +1,27 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import { Tooltip, type TooltipProps } from "melt/builders";
|
3 |
import { type ComponentProps, type Extracted } from "melt";
|
|
|
4 |
import type { Snippet } from "svelte";
|
5 |
|
|
|
|
|
6 |
interface Props {
|
7 |
children: Snippet;
|
8 |
trigger: Snippet<[Tooltip]>;
|
9 |
-
placement?: NonNullable<
|
10 |
openDelay?: ComponentProps<TooltipProps>["openDelay"];
|
11 |
}
|
12 |
-
const { children, trigger, placement = "top", openDelay }: Props = $props();
|
13 |
|
14 |
const tooltip = new Tooltip({
|
15 |
forceVisible: true,
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
openDelay: () => openDelay,
|
18 |
});
|
19 |
</script>
|
|
|
1 |
<script lang="ts">
|
|
|
2 |
import { type ComponentProps, type Extracted } from "melt";
|
3 |
+
import { Tooltip, type TooltipProps } from "melt/builders";
|
4 |
import type { Snippet } from "svelte";
|
5 |
|
6 |
+
type FloatingConfig = NonNullable<Extracted<TooltipProps["floatingConfig"]>>;
|
7 |
+
|
8 |
interface Props {
|
9 |
children: Snippet;
|
10 |
trigger: Snippet<[Tooltip]>;
|
11 |
+
placement?: NonNullable<FloatingConfig["computePosition"]>["placement"];
|
12 |
openDelay?: ComponentProps<TooltipProps>["openDelay"];
|
13 |
}
|
14 |
+
const { children, trigger, placement = "top", openDelay = 500 }: Props = $props();
|
15 |
|
16 |
const tooltip = new Tooltip({
|
17 |
forceVisible: true,
|
18 |
+
floatingConfig: () => ({
|
19 |
+
computePosition: { placement },
|
20 |
+
flip: {
|
21 |
+
fallbackPlacements: ["bottom"],
|
22 |
+
padding: 10,
|
23 |
+
},
|
24 |
+
}),
|
25 |
openDelay: () => openDelay,
|
26 |
});
|
27 |
</script>
|