Thomas G. Lopes commited on
Commit
4766af6
·
unverified ·
2 Parent(s): d882221 e24cc70

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.18.4",
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.18.4
86
- version: 0.18.4(@floating-ui/[email protected])([email protected])
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.18.4:
1549
- resolution: {integrity: sha512-AH7im4MEmHS/8MFhGDnfiuF4ibdm0guFWiBvBaF93YIzA0hhuWMcGxD8HdEsQ8q1Q6xIIRm9FdGd0WICc7Nk2A==}
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.18.4(@floating-ui/[email protected])([email protected]):
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={() => (message.images = message.images?.filter(i => i !== img))}
131
- class="invisible absolute -top-1 -right-1 grid size-5 place-items-center rounded-full bg-gray-800 text-xs text-white group-hover/img:visible hover:bg-gray-700"
 
 
 
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
- <button class="btn size-[32px] p-0" onclick={saveProject}>
52
- <IconSave />
53
- </button>
 
 
 
 
 
54
  {:else}
55
- <button class="btn size-[32px] p-0" onclick={() => (session.$.activeProjectId = "default")}>
56
- <IconCross />
57
- </button>
 
 
 
 
 
 
 
 
 
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<Extracted<TooltipProps["computePositionOptions"]>>["placement"];
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
- computePositionOptions: () => ({ placement }),
 
 
 
 
 
 
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>