Thomas G. Lopes commited on
Commit
d7d2be5
·
1 Parent(s): bee7674

images upload

Browse files
src/routes/canvas/+page.svelte CHANGED
@@ -8,6 +8,7 @@
8
  import ChatNode from "./chat-node.svelte";
9
  import { edges, nodes } from "./state.js";
10
  import { models } from "$lib/state/models.svelte";
 
11
 
12
  const nodeTypes = { chat: ChatNode } as const;
13
 
@@ -80,3 +81,5 @@
80
  </SvelteFlow>
81
  {/if}
82
  </div>
 
 
 
8
  import ChatNode from "./chat-node.svelte";
9
  import { edges, nodes } from "./state.js";
10
  import { models } from "$lib/state/models.svelte";
11
+ import ImgPreview from "$lib/components/inference-playground/img-preview.svelte";
12
 
13
  const nodeTypes = { chat: ChatNode } as const;
14
 
 
81
  </SvelteFlow>
82
  {/if}
83
  </div>
84
+
85
+ <ImgPreview />
src/routes/canvas/chat-node.svelte CHANGED
@@ -1,28 +1,32 @@
1
  <script lang="ts">
 
2
  import { TextareaAutosize } from "$lib/spells/textarea-autosize.svelte";
 
3
  import { models } from "$lib/state/models.svelte";
4
  import { token } from "$lib/state/token.svelte";
5
  import type { Model } from "$lib/types.js";
 
6
  import { InferenceClient } from "@huggingface/inference";
 
7
  import { Handle, Position, useSvelteFlow, type Edge, type Node, type NodeProps } from "@xyflow/svelte";
 
 
 
8
  import { onMount } from "svelte";
9
- import { edges, nodes } from "./state.js";
10
- import IconLoading from "~icons/lucide/loader-2";
11
- import IconAdd from "~icons/lucide/plus";
12
- import IconX from "~icons/lucide/x";
13
- import IconStop from "~icons/lucide/square";
14
  import IconCopy from "~icons/lucide/copy";
 
15
  import IconAttachment from "~icons/lucide/paperclip";
16
- import IconCode from "~icons/lucide/code";
17
  import IconSend from "~icons/lucide/send";
18
- import type { ChatCompletionInputMessage } from "@huggingface/tasks";
 
19
  import ModelPicker from "./model-picker.svelte";
20
  import ProviderPicker from "./provider-picker.svelte";
21
- import { ElementSize } from "runed";
22
- import { marked } from "marked";
23
- import { images } from "$lib/state/images.svelte.js";
24
- import { AsyncQueue } from "$lib/utils/queue.js";
25
- import { FileUpload } from "melt/builders";
26
 
27
  type Props = Omit<NodeProps, "data"> & {
28
  data: { query: string; response: string; modelId?: Model["id"]; provider?: string; imageIds?: string[] };
@@ -315,7 +319,17 @@
315
  <!-- Message container with top bar, textarea, and bottom bar -->
316
  <div
317
  class="rounded-lg border border-gray-200 bg-gray-50 focus-within:border-gray-900 focus-within:ring-2 focus-within:ring-gray-900/10"
 
318
  >
 
 
 
 
 
 
 
 
 
319
  <!-- Top bar with buttons and character count -->
320
  <div class="flex items-center justify-between border-b border-gray-200 px-4 py-2">
321
  <div class="flex items-center gap-2">
@@ -323,8 +337,10 @@
323
  type="button"
324
  class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-gray-600 transition-colors hover:bg-gray-200 hover:text-gray-900"
325
  title="Attach file"
 
326
  >
327
  <IconAttachment class="h-4 w-4" />
 
328
  </button>
329
  <button
330
  type="button"
@@ -355,6 +371,39 @@
355
  </div>
356
  </div>
357
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  <!-- Bottom bar with hint and send button (outside textarea) -->
359
  <div class="mt-2 flex items-center justify-between">
360
  <div class="text-xs text-gray-500">Shift + Enter for newline</div>
 
1
  <script lang="ts">
2
+ import { previewImage } from "$lib/components/inference-playground/img-preview.svelte";
3
  import { TextareaAutosize } from "$lib/spells/textarea-autosize.svelte";
4
+ import { images } from "$lib/state/images.svelte.js";
5
  import { models } from "$lib/state/models.svelte";
6
  import { token } from "$lib/state/token.svelte";
7
  import type { Model } from "$lib/types.js";
8
+ import { AsyncQueue } from "$lib/utils/queue.js";
9
  import { InferenceClient } from "@huggingface/inference";
10
+ import type { ChatCompletionInputMessage } from "@huggingface/tasks";
11
  import { Handle, Position, useSvelteFlow, type Edge, type Node, type NodeProps } from "@xyflow/svelte";
12
+ import { marked } from "marked";
13
+ import { FileUpload } from "melt/builders";
14
+ import { ElementSize } from "runed";
15
  import { onMount } from "svelte";
16
+ import { fade } from "svelte/transition";
17
+ import IconImage from "~icons/carbon/image-reference";
18
+ import IconMaximize from "~icons/carbon/maximize";
19
+ import IconCode from "~icons/lucide/code";
 
20
  import IconCopy from "~icons/lucide/copy";
21
+ import IconLoading from "~icons/lucide/loader-2";
22
  import IconAttachment from "~icons/lucide/paperclip";
23
+ import IconAdd from "~icons/lucide/plus";
24
  import IconSend from "~icons/lucide/send";
25
+ import IconStop from "~icons/lucide/square";
26
+ import IconX from "~icons/lucide/x";
27
  import ModelPicker from "./model-picker.svelte";
28
  import ProviderPicker from "./provider-picker.svelte";
29
+ import { edges, nodes } from "./state.js";
 
 
 
 
30
 
31
  type Props = Omit<NodeProps, "data"> & {
32
  data: { query: string; response: string; modelId?: Model["id"]; provider?: string; imageIds?: string[] };
 
319
  <!-- Message container with top bar, textarea, and bottom bar -->
320
  <div
321
  class="rounded-lg border border-gray-200 bg-gray-50 focus-within:border-gray-900 focus-within:ring-2 focus-within:ring-gray-900/10"
322
+ {...fileUpload.dropzone}
323
  >
324
+ {#if fileUpload.isDragging}
325
+ <div
326
+ class="absolute inset-2 z-10 flex flex-col items-center justify-center rounded-xl bg-gray-800/50 backdrop-blur-md"
327
+ transition:fade={{ duration: 100 }}
328
+ >
329
+ <IconImage />
330
+ <p>Drop the image here to upload</p>
331
+ </div>
332
+ {/if}
333
  <!-- Top bar with buttons and character count -->
334
  <div class="flex items-center justify-between border-b border-gray-200 px-4 py-2">
335
  <div class="flex items-center gap-2">
 
337
  type="button"
338
  class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-gray-600 transition-colors hover:bg-gray-200 hover:text-gray-900"
339
  title="Attach file"
340
+ {...fileUpload.trigger}
341
  >
342
  <IconAttachment class="h-4 w-4" />
343
+ <input {...fileUpload.input} />
344
  </button>
345
  <button
346
  type="button"
 
371
  </div>
372
  </div>
373
 
374
+ <div class="mt-2">
375
+ <div class="flex items-center gap-2">
376
+ {#each data.imageIds ?? [] as imgKey (imgKey)}
377
+ {#await images.get(imgKey)}
378
+ <!-- nothing -->
379
+ {:then imgSrc}
380
+ <div class="group/img relative">
381
+ <button
382
+ aria-label="expand"
383
+ class="absolute inset-0 z-10 grid place-items-center rounded-md bg-gray-800/70 text-white opacity-0 group-hover/img:opacity-100"
384
+ onclick={() => previewImage(imgSrc)}
385
+ >
386
+ <IconMaximize />
387
+ </button>
388
+ <img src={imgSrc} alt="uploaded" class="size-12 rounded-md object-cover" />
389
+ <button
390
+ aria-label="remove"
391
+ type="button"
392
+ onclick={async e => {
393
+ e.stopPropagation();
394
+ updateNodeData(id, { imageIds: data.imageIds?.filter(i => i !== imgKey) });
395
+ images.delete(imgKey);
396
+ }}
397
+ 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"
398
+ >
399
+
400
+ </button>
401
+ </div>
402
+ {/await}
403
+ {/each}
404
+ </div>
405
+ </div>
406
+
407
  <!-- Bottom bar with hint and send button (outside textarea) -->
408
  <div class="mt-2 flex items-center justify-between">
409
  <div class="text-xs text-gray-500">Shift + Enter for newline</div>