|
|
@@ -19,7 +19,15 @@ import { useAuth } from "../composables/useAuth.js";
|
|
|
|
|
|
const router = useRouter();
|
|
|
const route = useRoute();
|
|
|
-const { newConversation } = useChat();
|
|
|
+const {
|
|
|
+ conversations,
|
|
|
+ activeConversationId,
|
|
|
+ conversationsLoading,
|
|
|
+ setActiveConversation,
|
|
|
+ deleteConversation,
|
|
|
+ renameConversation,
|
|
|
+ newConversation,
|
|
|
+} = useChat();
|
|
|
const { user, logout } = useAuth();
|
|
|
|
|
|
const {
|
|
|
@@ -35,6 +43,9 @@ const docs = reactive({
|
|
|
error: "",
|
|
|
});
|
|
|
const logoutLoading = ref(false);
|
|
|
+const hoveredConversaId = ref(null);
|
|
|
+const editingConversaId = ref(null);
|
|
|
+const editingTitulo = ref("");
|
|
|
|
|
|
const sidebarRecolhida = ref(false);
|
|
|
const painelAberto = ref("");
|
|
|
@@ -99,6 +110,26 @@ const tituloPagina = computed(() => {
|
|
|
return "Oráculo";
|
|
|
});
|
|
|
|
|
|
+const conversacoesAgrupadas = computed(() => {
|
|
|
+ const hoje = new Date().setHours(0, 0, 0, 0);
|
|
|
+ const ontem = hoje - 86400000;
|
|
|
+ const semana = hoje - 6 * 86400000;
|
|
|
+ const grupos = [
|
|
|
+ { label: "Hoje", items: [] },
|
|
|
+ { label: "Ontem", items: [] },
|
|
|
+ { label: "Últimos 7 dias",items: [] },
|
|
|
+ { label: "Mais antigas", items: [] },
|
|
|
+ ];
|
|
|
+ for (const c of conversations.value) {
|
|
|
+ const t = c.updatedAt ?? 0;
|
|
|
+ if (t >= hoje) grupos[0].items.push(c);
|
|
|
+ else if (t >= ontem) grupos[1].items.push(c);
|
|
|
+ else if (t >= semana) grupos[2].items.push(c);
|
|
|
+ else grupos[3].items.push(c);
|
|
|
+ }
|
|
|
+ return grupos.filter((g) => g.items.length > 0);
|
|
|
+});
|
|
|
+
|
|
|
const tituloModal = computed(() => {
|
|
|
if (painelAberto.value === "update") return "Carregar documento";
|
|
|
if (painelAberto.value === "documentos") return "Documentos";
|
|
|
@@ -131,6 +162,27 @@ async function novaConversa() {
|
|
|
await router.push({ name: "home" });
|
|
|
}
|
|
|
|
|
|
+async function abrirConversa(id) {
|
|
|
+ await setActiveConversation(id);
|
|
|
+ await router.push({ name: "home" });
|
|
|
+}
|
|
|
+
|
|
|
+function iniciarEdicao(conversa) {
|
|
|
+ editingConversaId.value = conversa.id;
|
|
|
+ editingTitulo.value = conversa.title;
|
|
|
+}
|
|
|
+
|
|
|
+async function confirmarEdicao(id) {
|
|
|
+ if (editingTitulo.value.trim()) await renameConversation(id, editingTitulo.value);
|
|
|
+ editingConversaId.value = null;
|
|
|
+ editingTitulo.value = "";
|
|
|
+}
|
|
|
+
|
|
|
+function cancelarEdicao() {
|
|
|
+ editingConversaId.value = null;
|
|
|
+ editingTitulo.value = "";
|
|
|
+}
|
|
|
+
|
|
|
async function encerrarSessao() {
|
|
|
if (logoutLoading.value) return;
|
|
|
|
|
|
@@ -396,7 +448,7 @@ watch(
|
|
|
class="col-start-1 row-start-2 relative z-10 flex min-h-0 min-w-0 flex-col gap-3 overflow-hidden border-r border-gray-200 bg-transparent p-2 dark:border-gray-700 max-[980px]:row-start-3 max-[980px]:border-r-0 max-[980px]:border-t"
|
|
|
>
|
|
|
<div
|
|
|
- class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-[28px] border border-gray-200 bg-white/70 p-3 shadow-sm backdrop-blur-md dark:border-white/10 dark:bg-gradient-to-b dark:from-[#101827] dark:to-[#070b16] dark:shadow-[0_18px_50px_rgba(0,0,0,0.35)]"
|
|
|
+ class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-[28px] border border-gray-200 bg-white/70 p-3 shadow-sm backdrop-blur-md dark:border-white/10 dark:bg-[#101827] dark:shadow-[0_18px_50px_rgba(0,0,0,0.35)]"
|
|
|
>
|
|
|
<div
|
|
|
:class="
|
|
|
@@ -444,96 +496,148 @@ watch(
|
|
|
</svg>
|
|
|
</button>
|
|
|
</div>
|
|
|
- <nav class="grid gap-1.5">
|
|
|
+ <!-- Seção: Chat -->
|
|
|
+ <div
|
|
|
+ v-if="!sidebarRecolhida"
|
|
|
+ class="px-2 pt-1 pb-0.5 text-[10px] font-semibold uppercase tracking-widest text-gray-400 dark:text-white/30 select-none"
|
|
|
+ >
|
|
|
+ Chat
|
|
|
+ </div>
|
|
|
+ <nav class="grid gap-1">
|
|
|
<button
|
|
|
type="button"
|
|
|
- :class="classeItemSidebar(rotaAtiva === 'home')"
|
|
|
+ :class="classeItemSidebar(rotaAtiva === 'home' && !activeConversationId)"
|
|
|
:title="sidebarRecolhida ? 'Nova conversa' : ''"
|
|
|
@click="novaConversa"
|
|
|
>
|
|
|
<svg
|
|
|
- :class="classeIcon(rotaAtiva === 'home')"
|
|
|
+ :class="classeIcon(rotaAtiva === 'home' && !activeConversationId)"
|
|
|
viewBox="0 0 24 24"
|
|
|
fill="none"
|
|
|
stroke="currentColor"
|
|
|
stroke-width="1.8"
|
|
|
>
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M3 10.5 12 3l9 7.5V21a.75.75 0 0 1-.75.75H3.75A.75.75 0 0 1 3 21v-10.5Z"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M9 21v-7.5a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 .75.75V21"
|
|
|
- />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
|
|
</svg>
|
|
|
<span :class="classeRotulo">Nova conversa</span>
|
|
|
</button>
|
|
|
+ </nav>
|
|
|
|
|
|
- <button
|
|
|
- type="button"
|
|
|
- :class="classeItemSidebar(rotaAtiva === 'conversas-pesquisar')"
|
|
|
- :title="sidebarRecolhida ? 'Pesquisar conversas' : ''"
|
|
|
- @click="irPara('conversas-pesquisar')"
|
|
|
- >
|
|
|
- <svg
|
|
|
- :class="classeIcon(rotaAtiva === 'conversas-pesquisar')"
|
|
|
- viewBox="0 0 24 24"
|
|
|
- fill="none"
|
|
|
- stroke="currentColor"
|
|
|
- stroke-width="1.8"
|
|
|
+ <!-- Lista de conversas recentes (apenas sidebar expandida) -->
|
|
|
+ <div
|
|
|
+ v-if="!sidebarRecolhida"
|
|
|
+ class="mt-0.5 -mx-0.5 px-0.5"
|
|
|
+ >
|
|
|
+ <div v-if="conversationsLoading" class="px-3 py-2 text-xs text-gray-400 dark:text-white/30">
|
|
|
+ Carregando...
|
|
|
+ </div>
|
|
|
+ <template v-for="grupo in conversacoesAgrupadas" :key="grupo.label">
|
|
|
+ <div class="px-3 pt-2 pb-0.5 text-[10px] font-medium text-gray-400 dark:text-white/30 select-none">
|
|
|
+ {{ grupo.label }}
|
|
|
+ </div>
|
|
|
+ <div class="grid gap-1">
|
|
|
+ <button
|
|
|
+ v-for="c in grupo.items"
|
|
|
+ :key="c.id"
|
|
|
+ type="button"
|
|
|
+ class="relative w-full flex items-center gap-3 rounded-xl px-3 py-2.5 text-left text-sm transition-colors overflow-hidden !bg-none !border-0 !transform-none hover:!transform-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
|
+ :class="String(activeConversationId) === c.id
|
|
|
+ ? 'text-gray-900 bg-gray-200/70 dark:text-white dark:bg-white/10'
|
|
|
+ : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100/80 dark:text-white/70 dark:hover:text-white dark:hover:bg-white/10'"
|
|
|
+ @mouseenter="hoveredConversaId = c.id"
|
|
|
+ @mouseleave="hoveredConversaId = null"
|
|
|
+ @click="editingConversaId !== c.id && abrirConversa(c.id)"
|
|
|
>
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M10.5 18a7.5 7.5 0 1 1 0-15 7.5 7.5 0 0 1 0 15Z"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M16.5 16.5 21 21"
|
|
|
- />
|
|
|
- </svg>
|
|
|
- <span :class="classeRotulo">Pesquisar conversas</span>
|
|
|
- </button>
|
|
|
-
|
|
|
- <button
|
|
|
- type="button"
|
|
|
- :class="classeItemSidebar(rotaAtiva === 'conversas-historico')"
|
|
|
- :title="sidebarRecolhida ? 'Histórico' : ''"
|
|
|
- @click="irPara('conversas-historico')"
|
|
|
+ <template v-if="editingConversaId === c.id">
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ v-model="editingTitulo"
|
|
|
+ class="flex-1 min-w-0 bg-transparent text-sm outline-none border-b border-gray-400 dark:border-white/40 text-gray-900 dark:text-white"
|
|
|
+ @keydown.enter.prevent="confirmarEdicao(c.id)"
|
|
|
+ @keydown.escape.prevent="cancelarEdicao"
|
|
|
+ @click.stop
|
|
|
+ autofocus
|
|
|
+ />
|
|
|
+ <div class="flex items-center gap-0.5 shrink-0" @click.stop>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="rounded p-0.5 text-gray-500 hover:text-gray-900 dark:text-white/50 dark:hover:text-white transition-colors"
|
|
|
+ title="Confirmar"
|
|
|
+ @click.stop="confirmarEdicao(c.id)"
|
|
|
+ >
|
|
|
+ <svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="rounded p-0.5 text-gray-500 hover:text-gray-900 dark:text-white/50 dark:hover:text-white transition-colors"
|
|
|
+ title="Cancelar"
|
|
|
+ @click.stop="cancelarEdicao"
|
|
|
+ >
|
|
|
+ <svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <span class="flex-1 min-w-0 truncate leading-snug">{{ c.title }}</span>
|
|
|
+ <div
|
|
|
+ v-show="hoveredConversaId === c.id"
|
|
|
+ class="absolute right-0 top-0 h-full flex items-center gap-0.5 pr-2 pl-6"
|
|
|
+ :class="String(activeConversationId) === c.id
|
|
|
+ ? 'bg-gradient-to-l from-gray-200 dark:from-[#131f30] to-transparent'
|
|
|
+ : 'bg-gradient-to-l from-gray-100 dark:from-[#101827] to-transparent'"
|
|
|
+ @click.stop
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="rounded p-0.5 text-gray-400 hover:text-gray-700 dark:text-white/40 dark:hover:text-white transition-colors"
|
|
|
+ title="Renomear"
|
|
|
+ @click.stop="iniciarEdicao(c)"
|
|
|
+ >
|
|
|
+ <svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="rounded p-0.5 text-gray-400 hover:text-red-600 dark:text-white/40 dark:hover:text-red-400 transition-colors"
|
|
|
+ title="Excluir"
|
|
|
+ @click.stop="deleteConversation(c.id)"
|
|
|
+ >
|
|
|
+ <svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div
|
|
|
+ v-if="!conversationsLoading && conversacoesAgrupadas.length === 0"
|
|
|
+ class="px-3 py-2 text-xs text-gray-400 dark:text-white/30 italic"
|
|
|
>
|
|
|
- <svg
|
|
|
- :class="classeIcon(rotaAtiva === 'conversas-historico')"
|
|
|
- viewBox="0 0 24 24"
|
|
|
- fill="none"
|
|
|
- stroke="currentColor"
|
|
|
- stroke-width="1.8"
|
|
|
- >
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M12 6v6l4 2"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M21 12a9 9 0 1 1-3.2-6.9"
|
|
|
- />
|
|
|
- </svg>
|
|
|
- <span :class="classeRotulo">Histórico</span>
|
|
|
- </button>
|
|
|
- </nav>
|
|
|
+ Nenhuma conversa ainda
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="mt-auto grid gap-1.5 pt-3">
|
|
|
+ <!-- Seções inferiores: Biblioteca + Sistema -->
|
|
|
+ <div class="mt-auto flex-shrink-0 grid gap-1 pt-2">
|
|
|
<div class="my-1 h-px bg-black/10 dark:bg-white/10" />
|
|
|
+ <div
|
|
|
+ v-if="!sidebarRecolhida"
|
|
|
+ class="px-2 pb-0.5 text-[10px] font-semibold uppercase tracking-widest text-gray-400 dark:text-white/30 select-none"
|
|
|
+ >
|
|
|
+ Biblioteca
|
|
|
+ </div>
|
|
|
|
|
|
<button
|
|
|
type="button"
|
|
|
:class="classeItemSidebar(painelAberto === 'update')"
|
|
|
- :title="sidebarRecolhida ? 'Update' : ''"
|
|
|
+ :title="sidebarRecolhida ? 'Carregar documento' : ''"
|
|
|
@click="alternarPainel('update')"
|
|
|
>
|
|
|
<svg
|
|
|
@@ -543,23 +647,11 @@ watch(
|
|
|
stroke="currentColor"
|
|
|
stroke-width="1.8"
|
|
|
>
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M12 3v12"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M7.5 7.5 12 3l4.5 4.5"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M4 21h16"
|
|
|
- />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 3v12" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M7.5 7.5 12 3l4.5 4.5" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4 21h16" />
|
|
|
</svg>
|
|
|
- <span :class="classeRotulo">Carregar</span>
|
|
|
+ <span :class="classeRotulo">Carregar documento</span>
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
@@ -575,25 +667,21 @@ watch(
|
|
|
stroke="currentColor"
|
|
|
stroke-width="1.8"
|
|
|
>
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M7 3h7l3 3v15a.75.75 0 0 1-.75.75H7.75A.75.75 0 0 1 7 21V3Z"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M14 3v4h4"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M9 12h6M9 15.5h6"
|
|
|
- />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M7 3h7l3 3v15a.75.75 0 0 1-.75.75H7.75A.75.75 0 0 1 7 21V3Z" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M14 3v4h4" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6M9 15.5h6" />
|
|
|
</svg>
|
|
|
<span :class="classeRotulo">Documentos</span>
|
|
|
</button>
|
|
|
|
|
|
+ <div class="my-1 h-px bg-black/10 dark:bg-white/10" />
|
|
|
+ <div
|
|
|
+ v-if="!sidebarRecolhida"
|
|
|
+ class="px-2 pb-0.5 text-[10px] font-semibold uppercase tracking-widest text-gray-400 dark:text-white/30 select-none"
|
|
|
+ >
|
|
|
+ Sistema
|
|
|
+ </div>
|
|
|
+
|
|
|
<button
|
|
|
type="button"
|
|
|
:class="classeItemSidebar(rotaAtiva === 'configuracoes')"
|
|
|
@@ -607,51 +695,15 @@ watch(
|
|
|
stroke="currentColor"
|
|
|
stroke-width="1.8"
|
|
|
>
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M4 21v-7"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M4 10V3"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M12 21v-9"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M12 8V3"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M20 21v-5"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M20 12V3"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M1 14h6"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M9 8h6"
|
|
|
- />
|
|
|
- <path
|
|
|
- stroke-linecap="round"
|
|
|
- stroke-linejoin="round"
|
|
|
- d="M17 16h6"
|
|
|
- />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4 21v-7" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4 10V3" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 21v-9" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 8V3" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M20 21v-5" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M20 12V3" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M1 14h6" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 8h6" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M17 16h6" />
|
|
|
</svg>
|
|
|
<span :class="classeRotulo">Configurações</span>
|
|
|
</button>
|