|
|
@@ -1,5 +1,5 @@
|
|
|
<script setup>
|
|
|
-import { computed, onUnmounted, ref } from "vue";
|
|
|
+import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
|
|
|
import ChatWindow from "../../components/ChatWindow.vue";
|
|
|
import LayoutSistema from "../../layout/LayoutSistema.vue";
|
|
|
import { useChat } from "../../composables/useChat.js";
|
|
|
@@ -43,6 +43,76 @@ async function onLandingSend() {
|
|
|
landingDraft.value = "";
|
|
|
await sendMessage(text);
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+const FRASES_EXEMPLO = [
|
|
|
+ "Qual é a política de férias da empresa?",
|
|
|
+ "Resuma o manual de integração de novos funcionários",
|
|
|
+ "Como solicito reembolso de despesas de viagem?",
|
|
|
+ "Quais são os benefícios disponíveis para colaboradores?",
|
|
|
+];
|
|
|
+const PLACEHOLDER_PADRAO = "Digite sua pergunta...";
|
|
|
+const placeholderTexto = ref(PLACEHOLDER_PADRAO);
|
|
|
+let typingTimer = null;
|
|
|
+let fraseIdx = 0;
|
|
|
+
|
|
|
+const prefereMenosMovimento =
|
|
|
+ typeof window !== "undefined" &&
|
|
|
+ window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches === true;
|
|
|
+
|
|
|
+function digitar(i) {
|
|
|
+ const frase = FRASES_EXEMPLO[fraseIdx];
|
|
|
+ placeholderTexto.value = frase.slice(0, i) + (i < frase.length ? "▍" : "");
|
|
|
+ if (i < frase.length) typingTimer = setTimeout(() => digitar(i + 1), 40 + Math.random() * 45);
|
|
|
+ else typingTimer = setTimeout(() => apagar(frase.length), 2400);
|
|
|
+}
|
|
|
+
|
|
|
+function apagar(i) {
|
|
|
+ const frase = FRASES_EXEMPLO[fraseIdx];
|
|
|
+ placeholderTexto.value = frase.slice(0, i) + (i > 0 ? "▍" : "");
|
|
|
+ if (i > 0) typingTimer = setTimeout(() => apagar(i - 1), 18);
|
|
|
+ else {
|
|
|
+ fraseIdx = (fraseIdx + 1) % FRASES_EXEMPLO.length;
|
|
|
+ typingTimer = setTimeout(() => digitar(0), 400);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function iniciarTypewriter() {
|
|
|
+ if (prefereMenosMovimento || typingTimer) return;
|
|
|
+ digitar(0);
|
|
|
+}
|
|
|
+
|
|
|
+function pararTypewriter() {
|
|
|
+ clearTimeout(typingTimer);
|
|
|
+ typingTimer = null;
|
|
|
+ placeholderTexto.value = PLACEHOLDER_PADRAO;
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ [isLanding, landingDraft],
|
|
|
+ ([landing, draft]) => {
|
|
|
+ if (landing && !draft) iniciarTypewriter();
|
|
|
+ else pararTypewriter();
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+);
|
|
|
+onUnmounted(pararTypewriter);
|
|
|
+
|
|
|
+
|
|
|
+const textareaEl = ref(null);
|
|
|
+
|
|
|
+function ajustarAltura() {
|
|
|
+ const el = textareaEl.value;
|
|
|
+ if (!el) return;
|
|
|
+ el.style.height = "auto";
|
|
|
+ el.style.height = `${Math.min(el.scrollHeight, 220)}px`;
|
|
|
+}
|
|
|
+
|
|
|
+watch(landingDraft, () => nextTick(ajustarAltura));
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ textareaEl.value?.focus();
|
|
|
+});
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
@@ -54,18 +124,19 @@ async function onLandingSend() {
|
|
|
Digite sua pergunta abaixo. Você também pode alimentar a base pela barra lateral.
|
|
|
</div>
|
|
|
|
|
|
- <div class="mt-6 rounded-2xl border border-gray-300 bg-white/70 p-4 shadow-sm backdrop-blur-md dark:border-white/10 dark:bg-white/[0.04]">
|
|
|
+ <div class="mt-6 rounded-2xl border border-gray-300 bg-white/70 p-4 shadow-sm backdrop-blur-md transition-[border-color,box-shadow] duration-200 focus-within:border-ring/60 focus-within:ring-2 focus-within:ring-ring/15 dark:border-white/10 dark:bg-white/[0.04] dark:focus-within:border-ring/60">
|
|
|
<textarea
|
|
|
+ ref="textareaEl"
|
|
|
v-model="landingDraft"
|
|
|
rows="3"
|
|
|
- placeholder="Digite sua pergunta..."
|
|
|
- class="min-h-20 resize-none border-0 bg-transparent shadow-none focus:ring-0 !outline-none"
|
|
|
- style="resize: none"
|
|
|
+ :placeholder="placeholderTexto"
|
|
|
+ aria-label="Digite sua pergunta"
|
|
|
+ class="min-h-20 max-h-[220px] w-full resize-none border-0 bg-transparent shadow-none focus:ring-0 !outline-none"
|
|
|
@keydown.enter.exact.prevent="onLandingSend"
|
|
|
/>
|
|
|
<div class="mt-3 flex items-center justify-between gap-3 ">
|
|
|
<span class="text-xs text-gray-400 dark:text-gray-500">Enter para enviar · Shift+Enter para nova linha</span>
|
|
|
- <BaseButton :disabled="chatLoading || !landingDraft.trim()" @click="onLandingSend">
|
|
|
+ <BaseButton :disabled="chatLoading || !landingDraft.trim()" :loading="chatLoading" @click="onLandingSend">
|
|
|
<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>
|