feat(umind): Umi, la mascota, y un solo vocabulario en toda la interfaz
La mascota. Umi sale del logo, no es un dibujo aparte: su cuerpo es la "u" del monograma y el punto del logo pasa a ser su antena, así que símbolo y personaje son la misma forma vista dos veces. Por eso puede estar al lado del logo sin competirle. Tiene estados, y no son decoración: cada pantalla vacía dice algo distinto y la cara lo dice antes que el texto. Dormida cuando todavía no pasó nada, buscando cuando falta cargarle información, contenta cuando no hay errores, alerta cuando algo se rompió. Los ojos toman el color de la superficie de atrás en vez de ser blancos, así se apoya sobre cualquier tarjeta y en modo oscuro no quedan dos puntos flotando. Las pantallas vacías. Había una buena y ocho que decían "Sin canales configurados." y nada más — el usuario quedaba resolviendo solo qué significa eso y qué hacer. Ahora las ocho explican qué va ahí y por qué importa: "No está atendiendo en ningún lado", "Todavía no sabe nada de tu negocio". El vocabulario. La misma cosa tenía dos nombres según la pantalla: tenant y espacio, tool y herramienta. Un producto que se llama distinto a sí mismo en cada lugar se siente como varios productos pegados. Queda: espacio, agente, herramienta, fuente. Y "al AI" pasa a "a la IA", que además es el género que ya usaba el resto del panel. Y la barra de scroll de las pestañas, que se veía gris y gruesa sobre el contenido: en escritorio las siete entran holgadas y no hacía falta, en móvil siguen deslizándose pero sin la barra encima. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5086087e1b
commit
3ceb61c035
@@ -3,6 +3,7 @@ import { computed, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { api } from '../lib/api.js'
|
||||
import { apiUmind, contexto } from '../lib/contexto.js'
|
||||
import UiEmptyState from '../components/ui/UiEmptyState.vue'
|
||||
|
||||
const props = defineProps({
|
||||
tenantId: { type: String, required: true },
|
||||
@@ -248,7 +249,7 @@ async function guardarTool() {
|
||||
}
|
||||
|
||||
async function eliminarTool(t) {
|
||||
if (!confirm(`¿Eliminar la tool "${t.nombre}"?`)) return
|
||||
if (!confirm(`¿Eliminar la herramienta "${t.nombre}"?`)) return
|
||||
await api.del(apiUmind(`/umind/tools/${t.ID}`))
|
||||
await cargarTools()
|
||||
}
|
||||
@@ -482,7 +483,7 @@ watch(
|
||||
|
||||
<p v-if="error" class="text-sm text-red-600 dark:text-red-400 mb-4">{{ error }}</p>
|
||||
|
||||
<div class="flex gap-1.5 mb-6 overflow-x-auto pb-1">
|
||||
<div class="flex gap-1.5 mb-6 overflow-x-auto sin-barra pb-1">
|
||||
<button
|
||||
v-for="[key, label] in tabs"
|
||||
:key="key"
|
||||
@@ -554,7 +555,12 @@ watch(
|
||||
</form>
|
||||
</div>
|
||||
<div class="card divide-y divide-borde">
|
||||
<div v-if="documentos.length === 0" class="p-6 text-sm text-tenue">Sin fuentes todavía.</div>
|
||||
<UiEmptyState
|
||||
v-if="documentos.length === 0"
|
||||
estado="buscando"
|
||||
titulo="Todavía no sabe nada de tu negocio"
|
||||
detalle="Sin información cargada va a contestar que no sabe. Empezá por escribir tus horarios y lo que te preguntan todos los días — es lo más rápido y lo que más se nota."
|
||||
/>
|
||||
<div v-for="d in documentos" :key="d.ID" class="p-4 flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-sm text-texto">{{ d.origen }}</div>
|
||||
@@ -592,13 +598,17 @@ watch(
|
||||
<!-- Herramientas -->
|
||||
<div v-else-if="tab === 'herramientas'">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<p class="label">Máximo 10 tools activas por agente.</p>
|
||||
<p class="label">Máximo 10 herramientas activas por agente.</p>
|
||||
<button class="btn-primary" @click="nuevaTool">
|
||||
+ Nueva tool
|
||||
+ Nueva herramienta
|
||||
</button>
|
||||
</div>
|
||||
<div class="card divide-y divide-borde">
|
||||
<div v-if="tools.length === 0" class="p-6 text-sm text-tenue">Sin tools custom todavía.</div>
|
||||
<UiEmptyState
|
||||
v-if="tools.length === 0"
|
||||
titulo="Sin herramientas conectadas"
|
||||
detalle="Las herramientas le dejan consultar tus sistemas mientras conversa: stock, estado de un pedido, disponibilidad de turnos. Sin ninguna, responde solo con lo que tiene cargado."
|
||||
/>
|
||||
<div v-for="t in tools" :key="t.ID" class="p-4 flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-sm text-texto font-mono">{{ t.nombre }}</div>
|
||||
@@ -618,7 +628,7 @@ watch(
|
||||
|
||||
<div v-if="showToolForm" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50" @click.self="showToolForm = false">
|
||||
<div class="card p-6 w-full max-w-xl max-h-[85vh] overflow-y-auto">
|
||||
<h2 class="font-semibold text-texto mb-4">{{ editingTool ? 'Editar tool' : 'Nueva tool' }}</h2>
|
||||
<h2 class="font-semibold text-texto mb-4">{{ editingTool ? 'Editar herramienta' : 'Nueva herramienta' }}</h2>
|
||||
<form class="space-y-3" @submit.prevent="guardarTool">
|
||||
<div>
|
||||
<label class="label">Nombre (identificador, ej: consultar_stock)</label>
|
||||
@@ -712,7 +722,11 @@ watch(
|
||||
</button>
|
||||
</div>
|
||||
<div class="card divide-y divide-borde">
|
||||
<div v-if="canales.length === 0" class="p-6 text-sm text-tenue">Sin canales configurados.</div>
|
||||
<UiEmptyState
|
||||
v-if="canales.length === 0"
|
||||
titulo="No está atendiendo en ningún lado"
|
||||
detalle="Conectá WhatsApp o Telegram para que empiece a responderle a tus clientes. El widget de tu web funciona aparte, con la clave de sitio de arriba."
|
||||
/>
|
||||
<div v-for="c in canales" :key="c.ID" class="p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -825,7 +839,11 @@ watch(
|
||||
</button>
|
||||
</div>
|
||||
<div class="card divide-y divide-borde">
|
||||
<div v-if="conexiones.length === 0" class="p-6 text-sm text-tenue">Sin cuentas conectadas.</div>
|
||||
<UiEmptyState
|
||||
v-if="conexiones.length === 0"
|
||||
titulo="Sin cuentas conectadas"
|
||||
detalle="Conectando una cuenta de correo, el agente puede leer y responder mensajes con tu dirección."
|
||||
/>
|
||||
<div v-for="c in conexiones" :key="c.ID" class="p-4 flex items-center justify-between">
|
||||
<div>
|
||||
<span class="font-medium text-texto capitalize">{{ c.proveedor }}</span>
|
||||
@@ -843,7 +861,7 @@ watch(
|
||||
<div v-else-if="tab === 'chat'" class="card p-4 flex flex-col h-[28rem]">
|
||||
<div class="flex-1 overflow-y-auto space-y-2 mb-3">
|
||||
<p v-if="chatMensajes.length === 0" class="text-sm text-tenue">
|
||||
Probá este agente tal cual lo va a ver un visitante — usa la misma config de IA y las mismas tools/base de conocimiento.
|
||||
Probá este agente tal cual lo va a ver un visitante — usa la misma config de IA, las mismas herramientas y la misma base de conocimiento.
|
||||
</p>
|
||||
<div
|
||||
v-for="(m, i) in chatMensajes"
|
||||
@@ -866,7 +884,11 @@ watch(
|
||||
<!-- Conversaciones -->
|
||||
<div v-else-if="tab === 'conversaciones'" class="grid grid-cols-3 gap-4">
|
||||
<div class="col-span-1 card divide-y divide-borde max-h-[28rem] overflow-y-auto">
|
||||
<div v-if="sesiones.length === 0" class="p-4 text-sm text-tenue">Sin conversaciones.</div>
|
||||
<UiEmptyState
|
||||
v-if="sesiones.length === 0"
|
||||
titulo="Nadie escribió todavía"
|
||||
detalle="Acá vas a ver todo lo que le preguntan y qué contestó."
|
||||
/>
|
||||
<button
|
||||
v-for="s in sesiones"
|
||||
:key="s.session_id"
|
||||
@@ -894,10 +916,15 @@ watch(
|
||||
<!-- Auditoría -->
|
||||
<div v-else>
|
||||
<p class="label mb-4">
|
||||
Errores y eventos técnicos de este agente — fallos al llamar al AI, a tools, a correo o a los canales. Últimos 100.
|
||||
Errores y eventos técnicos de este agente — fallos al llamar a la IA, a una herramienta, al correo o a los canales. Últimos 100.
|
||||
</p>
|
||||
<div class="card divide-y divide-borde max-h-[32rem] overflow-y-auto">
|
||||
<div v-if="eventos.length === 0" class="p-6 text-sm text-tenue">Sin eventos registrados — buena señal.</div>
|
||||
<UiEmptyState
|
||||
v-if="eventos.length === 0"
|
||||
estado="contenta"
|
||||
titulo="Ningún problema registrado"
|
||||
detalle="Acá aparecen los errores: una herramienta que no responde, una fuente que no se pudo leer. Que esté vacío es buena señal."
|
||||
/>
|
||||
<details v-for="e in eventos" :key="e.ID" class="p-3">
|
||||
<summary class="cursor-pointer flex items-center gap-2 text-sm">
|
||||
<span class="px-1.5 py-0.5 rounded text-xs shrink-0" :class="nivelColor(e.nivel)">{{ e.nivel }}</span>
|
||||
|
||||
Reference in New Issue
Block a user