← back to Exo

dashboard/src/lib/components/ChatAttachments.svelte

95 lines

<script lang="ts">
  import type { ChatUploadedFile } from "$lib/types/files";
  import { formatFileSize, getFileCategory } from "$lib/types/files";

  interface Props {
    files: ChatUploadedFile[];
    readonly?: boolean;
    onRemove?: (fileId: string) => void;
  }

  let { files, readonly = false, onRemove }: Props = $props();

  function getFileIcon(file: ChatUploadedFile): string {
    const category = getFileCategory(file.type, file.name);
    switch (category) {
      case "image":
        return "🖼";
      case "text":
        return "📄";
      case "pdf":
        return "📑";
      case "audio":
        return "🎵";
      default:
        return "📎";
    }
  }

  function truncateName(name: string, maxLen: number = 20): string {
    if (name.length <= maxLen) return name;
    const ext = name.slice(name.lastIndexOf("."));
    const base = name.slice(0, name.lastIndexOf("."));
    const available = maxLen - ext.length - 3;
    return base.slice(0, available) + "..." + ext;
  }
</script>

{#if files.length > 0}
  <div class="flex flex-wrap gap-2 mb-3 px-1">
    {#each files as file (file.id)}
      <div
        class="group relative flex items-center gap-2 bg-exo-dark-gray/80 border border-exo-yellow/30 rounded px-2.5 py-1.5 text-xs font-mono transition-all hover:border-exo-yellow/50 hover:shadow-[0_0_10px_rgba(255,215,0,0.1)]"
      >
        <!-- File preview or icon -->
        {#if file.preview && getFileCategory(file.type, file.name) === "image"}
          <img
            src={file.preview}
            alt={file.name}
            class="w-8 h-8 object-cover rounded border border-exo-yellow/20"
          />
        {:else}
          <span class="text-base">{getFileIcon(file)}</span>
        {/if}

        <!-- File info -->
        <div class="flex flex-col min-w-0">
          <span
            class="text-exo-yellow truncate max-w-[120px]"
            title={file.name}
          >
            {truncateName(file.name)}
          </span>
          <span class="text-exo-light-gray text-xs">
            {formatFileSize(file.size)}
          </span>
        </div>

        <!-- Remove button -->
        {#if !readonly && onRemove}
          <button
            type="button"
            onclick={() => onRemove?.(file.id)}
            class="ml-1 w-4 h-4 flex items-center justify-center text-exo-light-gray hover:text-red-400 transition-colors cursor-pointer"
            title="Remove file"
          >
            <svg
              class="w-3 h-3"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
            >
              <path
                stroke-linecap="round"
                stroke-linejoin="round"
                stroke-width="2"
                d="M6 18L18 6M6 6l12 12"
              />
            </svg>
          </button>
        {/if}
      </div>
    {/each}
  </div>
{/if}