|
|
@@ -62,6 +62,50 @@ function formatDate(iso) {
|
|
|
return null;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+const FILE_TYPES = {
|
|
|
+ pdf: {
|
|
|
+ chipClass: "bg-red-100 text-red-600 dark:bg-red-500/10 dark:text-red-400",
|
|
|
+ icon: "doc",
|
|
|
+ },
|
|
|
+ doc: {
|
|
|
+ chipClass: "bg-blue-100 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400",
|
|
|
+ icon: "doc",
|
|
|
+ },
|
|
|
+ docx: {
|
|
|
+ chipClass: "bg-blue-100 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400",
|
|
|
+ icon: "doc",
|
|
|
+ },
|
|
|
+ png: {
|
|
|
+ chipClass: "bg-violet-100 text-violet-600 dark:bg-violet-500/10 dark:text-violet-400",
|
|
|
+ icon: "image",
|
|
|
+ },
|
|
|
+ jpg: {
|
|
|
+ chipClass: "bg-violet-100 text-violet-600 dark:bg-violet-500/10 dark:text-violet-400",
|
|
|
+ icon: "image",
|
|
|
+ },
|
|
|
+ jpeg: {
|
|
|
+ chipClass: "bg-violet-100 text-violet-600 dark:bg-violet-500/10 dark:text-violet-400",
|
|
|
+ icon: "image",
|
|
|
+ },
|
|
|
+ webp: {
|
|
|
+ chipClass: "bg-violet-100 text-violet-600 dark:bg-violet-500/10 dark:text-violet-400",
|
|
|
+ icon: "image",
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+const GENERIC_FILE_TYPE = {
|
|
|
+ chipClass: "bg-gray-100 text-gray-500 dark:bg-white/10 dark:text-gray-400",
|
|
|
+ icon: "doc",
|
|
|
+};
|
|
|
+
|
|
|
+function fileType(source) {
|
|
|
+ if (!source) return GENERIC_FILE_TYPE;
|
|
|
+ const clean = source.split(/[?#]/)[0];
|
|
|
+ const match = /\.([a-z0-9]+)$/i.exec(clean);
|
|
|
+ const ext = match ? match[1].toLowerCase() : "";
|
|
|
+ return FILE_TYPES[ext] ?? GENERIC_FILE_TYPE;
|
|
|
+}
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
@@ -98,7 +142,7 @@ function formatDate(iso) {
|
|
|
{{ error }}
|
|
|
</div>
|
|
|
|
|
|
- <SkeletonDocumentCard v-if="loading" :count="3" />
|
|
|
+ <SkeletonDocumentCard v-if="loading" :count="10" />
|
|
|
|
|
|
<div
|
|
|
v-else-if="items.length === 0 && !error"
|
|
|
@@ -115,30 +159,51 @@ function formatDate(iso) {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div v-else class="grid max-h-[520px] gap-2 overflow-auto">
|
|
|
+ <div v-else class="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6">
|
|
|
<div
|
|
|
v-for="(chunks, source) in filteredGrouped"
|
|
|
:key="source"
|
|
|
- class="overflow-hidden rounded-xl border border-gray-200 bg-background/60 dark:border-gray-700 dark:bg-background/10"
|
|
|
+ :title="source || 'sem fonte'"
|
|
|
+ class="group relative flex aspect-square flex-col items-center justify-center gap-1.5 overflow-hidden rounded-xl border border-gray-200 bg-background/60 p-3 text-center transition-all duration-150 hover:-translate-y-0.5 hover:border-gray-300 hover:shadow-md dark:border-gray-700 dark:bg-background/10 dark:hover:border-white/20"
|
|
|
>
|
|
|
- <div class="flex items-center justify-between gap-2 px-3 py-2.5">
|
|
|
- <div class="flex min-w-0 items-center gap-2">
|
|
|
- <svg class="h-4 w-4 shrink-0 text-gray-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
|
- <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
|
|
|
- </svg>
|
|
|
- <div class="min-w-0">
|
|
|
- <span class="block truncate text-sm font-medium text-gray-900 dark:text-gray-100">{{ source || "sem fonte" }}</span>
|
|
|
- <span v-if="chunks[0]?.ingestedAt" class="text-[10px] text-gray-400 dark:text-gray-500">
|
|
|
- {{ formatDate(chunks[0].ingestedAt) }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <span class="shrink-0 rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-500 dark:bg-white/10 dark:text-gray-400">
|
|
|
- {{ chunks.length }} chunk{{ chunks.length !== 1 ? 's' : '' }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
+ <button
|
|
|
+ v-if="source"
|
|
|
+ class="absolute right-1 top-1 rounded-lg border border-transparent p-1.5 text-gray-400 opacity-0 transition-opacity hover:border-red-200 hover:bg-red-50 hover:text-red-600 group-hover:opacity-100 dark:hover:border-red-900/50 dark:hover:bg-red-950/30 dark:hover:text-red-400"
|
|
|
+ :title="`Excluir '${source}'`"
|
|
|
+ @click="confirmingSource = source"
|
|
|
+ >
|
|
|
+ <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="m19 7-.867 12.142A2 2 0 0 1 16.138 21H7.862a2 2 0 0 1-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v3M4 7h16" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg"
|
|
|
+ :class="fileType(source).chipClass"
|
|
|
+ >
|
|
|
+ <svg v-if="fileType(source).icon === 'image'" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75A2.25 2.25 0 0 1 4.5 4.5h15a2.25 2.25 0 0 1 2.25 2.25v10.5A2.25 2.25 0 0 1 19.5 19.5h-15a2.25 2.25 0 0 1-2.25-2.25V6.75Z" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="m3 16.5 5.159-5.159a2.25 2.25 0 0 1 3.182 0L16.5 16.5m-3-3 1.409-1.409a2.25 2.25 0 0 1 3.182 0L21 15" />
|
|
|
+ <circle cx="8.25" cy="8.625" r="1.125" />
|
|
|
+ </svg>
|
|
|
+ <svg v-else class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
|
|
|
- <div v-if="confirmingSource === source" class="flex shrink-0 items-center gap-1.5">
|
|
|
- <span class="text-xs text-gray-500 dark:text-gray-400">Excluir tudo?</span>
|
|
|
+ <span class="line-clamp-2 w-full break-words text-xs font-medium text-gray-900 dark:text-gray-100">
|
|
|
+ {{ source || "sem fonte" }}
|
|
|
+ </span>
|
|
|
+ <span class="text-[10px] text-gray-400 dark:text-gray-500">
|
|
|
+ {{ chunks.length }} chunk{{ chunks.length !== 1 ? 's' : '' }}<template v-if="chunks[0]?.ingestedAt"> · {{ formatDate(chunks[0].ingestedAt) }}</template>
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="confirmingSource === source"
|
|
|
+ class="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-background p-3 dark:bg-[#101827]"
|
|
|
+ >
|
|
|
+ <span class="text-xs text-gray-500 dark:text-gray-400">Excluir tudo?</span>
|
|
|
+ <div class="flex items-center gap-1.5">
|
|
|
<BaseButton
|
|
|
variant="danger"
|
|
|
size="sm"
|
|
|
@@ -152,31 +217,12 @@ function formatDate(iso) {
|
|
|
Cancelar
|
|
|
</BaseButton>
|
|
|
</div>
|
|
|
- <button
|
|
|
- v-else-if="source"
|
|
|
- class="shrink-0 rounded-lg border border-transparent p-1.5 text-gray-400 transition-colors hover:border-red-200 hover:bg-red-50 hover:text-red-600 dark:hover:border-red-900/50 dark:hover:bg-red-950/30 dark:hover:text-red-400"
|
|
|
- :title="`Excluir '${source}'`"
|
|
|
- @click="confirmingSource = source"
|
|
|
- >
|
|
|
- <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
|
- <path stroke-linecap="round" stroke-linejoin="round" d="m19 7-.867 12.142A2 2 0 0 1 16.138 21H7.862a2 2 0 0 1-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v3M4 7h16" />
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="border-t border-gray-100 px-3 py-2.5 dark:border-gray-700/50">
|
|
|
- <div class="line-clamp-3 whitespace-pre-wrap text-xs leading-relaxed text-gray-500 dark:text-gray-400">
|
|
|
- {{ chunks[0]?.text }}
|
|
|
- </div>
|
|
|
- <div v-if="chunks.length > 1" class="mt-1 text-xs text-gray-400 dark:text-gray-500">
|
|
|
- + {{ chunks.length - 1 }} chunk{{ chunks.length - 1 !== 1 ? 's' : '' }} adicionais
|
|
|
- </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
v-if="filterQuery && Object.keys(filteredGrouped).length === 0"
|
|
|
- class="py-6 text-center text-sm text-gray-400 dark:text-gray-500"
|
|
|
+ class="col-span-full py-6 text-center text-sm text-gray-400 dark:text-gray-500"
|
|
|
>
|
|
|
Nenhum documento corresponde ao filtro.
|
|
|
</div>
|