| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- <template>
- <div class="flex h-full min-h-[520px] w-full flex-col gap-4">
- <div
- ref="chatEl"
- class="min-h-0 flex-1 overflow-auto rounded-2xl border border-gray-200 bg-background/60 p-4 dark:border-gray-700 dark:bg-black/20"
- >
- <div v-if="!messages.length" class="grid h-full place-items-center">
- <div class="text-center">
- <div class="text-xl font-semibold tracking-tight">Nova conversa</div>
- <div class="mt-2 text-sm text-gray-500 dark:text-gray-400">Digite sua pergunta abaixo para começar.</div>
- </div>
- </div>
- <div v-else class="grid gap-4">
- <div v-for="(m, idx) in messages" :key="idx" class="flex flex-col" :class="m.role === 'user' ? 'items-end' : 'items-start'">
- <div class="mb-0.5 flex items-center gap-1.5" :class="m.role === 'user' ? 'flex-row-reverse' : ''">
- <div
- class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[9px] font-bold text-white"
- :class="m.role === 'user' ? 'bg-primary' : 'bg-gray-400 dark:bg-gray-600'"
- >
- {{ m.role === 'user' ? 'V' : 'O' }}
- </div>
- <span class="text-xs font-medium text-gray-400 dark:text-gray-500">
- {{ m.role === 'user' ? 'Você' : 'Oráculo' }}
- </span>
- <span v-if="m.sentAt" class="text-[10px] text-gray-300 dark:text-gray-600">
- {{ formatarHora(m.sentAt) }}
- </span>
- </div>
- <div
- class="max-w-[78%] whitespace-pre-wrap rounded-2xl border px-3 py-2 leading-snug text-sm"
- :class="
- m.role === 'user'
- ? 'border-primary/25 bg-primary/10 text-foreground'
- : 'border-gray-200 bg-white/50 text-foreground dark:border-gray-700 dark:bg-white/[0.07]'
- "
- >
- {{ m.content }}
- </div>
- </div>
- <div v-if="loading" class="flex flex-col items-start gap-0.5">
- <div class="mb-0.5 flex items-center gap-1.5">
- <div class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-gray-400 text-[9px] font-bold text-white dark:bg-gray-600">O</div>
- <span class="text-xs font-medium text-gray-400 dark:text-gray-500">Oráculo</span>
- </div>
- <div class="rounded-2xl border border-gray-200 bg-white/50 px-4 py-3 dark:border-gray-700 dark:bg-white/[0.07]">
- <span class="inline-flex items-center gap-1">
- <span class="h-1.5 w-1.5 animate-bounce rounded-full bg-gray-400 dark:bg-gray-500" style="animation-delay: 0ms" />
- <span class="h-1.5 w-1.5 animate-bounce rounded-full bg-gray-400 dark:bg-gray-500" style="animation-delay: 150ms" />
- <span class="h-1.5 w-1.5 animate-bounce rounded-full bg-gray-400 dark:bg-gray-500" style="animation-delay: 300ms" />
- </span>
- </div>
- </div>
- </div>
- </div>
- <div class="grid gap-3 md:grid-cols-[1fr_auto] md:items-end">
- <textarea
- v-model="draft"
- rows="2"
- placeholder="Digite sua pergunta..."
- class="resize-none"
- style="resize: none"
- @keydown.enter.exact.prevent="onSend"
- />
- <button
- class="inline-flex items-center justify-center gap-2 rounded-xl border border-primary bg-primary px-4 py-2.5 text-sm font-semibold text-primary-foreground transition-colors hover:border-[var(--primary-600)] hover:bg-[var(--primary-600)] disabled:opacity-50 disabled:cursor-not-allowed"
- :disabled="loading || !draft.trim()"
- @click="onSend"
- >
- <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path stroke-linecap="round" stroke-linejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
- </svg>
- Enviar
- </button>
- </div>
- <div v-if="error" class="rounded-xl border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-500/30 dark:bg-red-500/10 dark:text-red-300">
- Erro: {{ error }}
- </div>
- </div>
- </template>
- <script setup>
- import { ref, nextTick, watch } from "vue";
- const props = defineProps({
- messages: { type: Array, required: true },
- loading: { type: Boolean, required: true },
- error: { type: String, required: true }
- });
- const emit = defineEmits(["send"]);
- const draft = ref("");
- const chatEl = ref(null);
- function formatarHora(ts) {
- if (!ts) return "";
- try {
- return new Date(ts).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
- } catch {
- return "";
- }
- }
- watch(
- () => [props.messages.length, props.loading],
- async () => {
- await nextTick();
- const el = chatEl.value;
- if (!el) return;
- el.scrollTop = el.scrollHeight;
- },
- { immediate: true }
- );
- async function onSend() {
- const text = draft.value;
- draft.value = "";
- emit("send", text);
- await nextTick();
- }
- </script>
|