feat(umind): logo propio, plantillas de agente por rubro y página promocional
Logo. Hasta ahora no había: era el texto "uM" en un cuadrado, con tamaño y esquinas distintas en cada pantalla. Ahora hay un mark — la "u" del nombre con el punto de pulso integrado — probado a 16px, 20px y 32px sobre claro y oscuro, que es donde se cae la mayoría de los logos. Reemplaza el texto en el portal, el header, el sidebar del Studio y la promo, y va también de favicon. Plantillas por rubro. Un agente recién creado no sabía nada, y escribir el conocimiento desde cero frente a un campo en blanco es donde la mayoría abandona. Al crear uno se puede elegir un punto de partida —tienda, restaurante, consultorio, servicios, peluquería o la base genérica— y arranca con sus notas ya escritas, listas para editar. Los valores de ejemplo van entre corchetes a propósito, para que se vea que hay que reemplazarlos: una nota que parezca dato real termina en boca del asistente. Un test lo verifica nota por nota. Y la plantilla de salud abre con el límite — el asistente no da consejo médico ni interpreta síntomas — porque es el único rubro donde una respuesta inventada hace daño de verdad. El catálogo vive en código, no en base: es contenido editorial que mejoramos nosotros, no configuración que cada instalación toca por su lado. Las notas se cargan en segundo plano porque cada una necesita sus embeddings, y hacer esperar el alta sería castigar justo al que eligió plantilla. Con las opciones nuevas el formulario pasaba de largo la pantalla y el botón de guardar quedaba abajo del borde: el modal ahora tiene su propio scroll. Y la página promocional de uMind queda servida en /umind.html. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3ea17b0980
commit
af71b66bb8
@@ -140,9 +140,15 @@ onMounted(() => {
|
||||
md:static md:h-screen md:sticky md:top-0 md:translate-x-0"
|
||||
:class="menuAbierto ? 'translate-x-0' : '-translate-x-full'"
|
||||
>
|
||||
<div class="h-14 px-4 flex items-center border-b border-borde">
|
||||
<router-link to="/" class="text-base font-semibold text-texto">
|
||||
uMind <span class="text-brand">Studio</span>
|
||||
<div class="h-14 px-4 flex items-center gap-2 border-b border-borde">
|
||||
<router-link to="/" class="flex items-center gap-2 text-base font-semibold text-texto">
|
||||
<svg viewBox="0 0 96 96" class="w-6 h-6 shrink-0" aria-hidden="true">
|
||||
<rect width="96" height="96" rx="22" fill="#8eb02f" />
|
||||
<path d="M32,42 V58 A14,14 0 0 0 60,58 V42" fill="none" stroke="#fff" stroke-width="10" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M60,58 V64" fill="none" stroke="#fff" stroke-width="10" stroke-linecap="round" />
|
||||
<circle cx="60" cy="28" r="7" fill="#fff" />
|
||||
</svg>
|
||||
<span>uMind <span class="text-brand">Studio</span></span>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -62,23 +62,30 @@ const cupo = computed(() => {
|
||||
})
|
||||
|
||||
function vacio() {
|
||||
return { nombre: '', ai_config_id: null, tono: '', mensaje_bienvenida: '', color: '#8eb02f', activo: true }
|
||||
return { nombre: '', ai_config_id: null, tono: '', mensaje_bienvenida: '', color: '#8eb02f', activo: true, plantilla_rubro: '' }
|
||||
}
|
||||
|
||||
// Puntos de partida por rubro. Un agente vacío no sirve el primer día, y
|
||||
// escribir el conocimiento desde cero frente a un campo en blanco es donde la
|
||||
// mayoría abandona.
|
||||
const plantillas = ref([])
|
||||
|
||||
async function cargar() {
|
||||
error.value = ''
|
||||
cargando.value = true
|
||||
try {
|
||||
const [t, a, ai] = await Promise.all([
|
||||
const [t, a, ai, pl] = await Promise.all([
|
||||
api.get(apiUmind('/umind/tenants')),
|
||||
api.get(apiUmind(`/umind/agentes?tenant_id=${props.id}`)),
|
||||
api.get(apiUmind('/umind/ai-configs')),
|
||||
api.get(apiUmind('/umind/plantillas-rubro')),
|
||||
])
|
||||
tenant.value = (t.items || []).find((x) => String(x.ID) === props.id) || null
|
||||
agentes.value = a.items || []
|
||||
plan.value = a.plan || null
|
||||
resumen.value = a.resumen || {}
|
||||
aiConfigs.value = ai.items || []
|
||||
plantillas.value = pl.items || []
|
||||
} catch (e) {
|
||||
error.value = e.message
|
||||
} finally {
|
||||
@@ -231,13 +238,44 @@ watch(() => props.id, cargar, { immediate: true })
|
||||
</div>
|
||||
|
||||
<div v-if="showForm" class="fixed inset-0 bg-black/40 flex items-center justify-center p-4 z-50" @click.self="showForm = false">
|
||||
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-xl p-6 w-full max-w-lg">
|
||||
<!-- max-h + scroll propio: con las opciones de plantilla el formulario
|
||||
pasa de largo la pantalla, y sin esto el botón de guardar queda
|
||||
abajo del borde inferior, inalcanzable. -->
|
||||
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-xl p-6 w-full max-w-lg max-h-[calc(100vh-2rem)] overflow-y-auto">
|
||||
<h2 class="font-semibold text-gray-800 dark:text-gray-100 mb-4">{{ editing ? 'Editar agente' : 'Nuevo agente' }}</h2>
|
||||
<form class="space-y-3" @submit.prevent="guardar">
|
||||
<div>
|
||||
<label class="label">Nombre</label>
|
||||
<input v-model="form.nombre" required placeholder="ej: Ventas, Soporte" class="input" />
|
||||
</div>
|
||||
|
||||
<!-- Solo al crear: en un agente que ya existe, precargar notas
|
||||
pisaría el conocimiento que el dueño ya escribió. -->
|
||||
<div v-if="!editing && plantillas.length">
|
||||
<label class="label">Arrancar con</label>
|
||||
<div class="grid gap-1.5">
|
||||
<label
|
||||
v-for="p in [{ clave: '', nombre: 'Agente en blanco', descripcion: 'Sin conocimiento cargado. Lo escribís vos desde cero.' }, ...plantillas]"
|
||||
:key="p.clave"
|
||||
class="flex gap-2.5 p-2.5 rounded-lg border cursor-pointer transition-colors"
|
||||
:class="form.plantilla_rubro === p.clave
|
||||
? 'border-brand bg-brand/5'
|
||||
: 'border-borde hover:border-brand/40'"
|
||||
>
|
||||
<input v-model="form.plantilla_rubro" type="radio" :value="p.clave" class="mt-1 text-brand focus:ring-brand" />
|
||||
<span class="min-w-0">
|
||||
<span class="block text-sm text-texto">{{ p.nombre }}</span>
|
||||
<span class="block text-xs text-tenue">{{ p.descripcion }}</span>
|
||||
<span v-if="p.notas" class="block text-xs text-tenue mt-0.5">
|
||||
{{ p.notas }} notas listas para editar · {{ p.resumen }}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-xs text-tenue mt-1.5">
|
||||
Las notas vienen con ejemplos entre corchetes — abrilas y reemplazalas por tus datos reales.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Config de IA</label>
|
||||
<select v-model="form.ai_config_id" class="input">
|
||||
|
||||
Reference in New Issue
Block a user