15. Formulario para editar Chatbots
Formulario para Editar Chatbots
1. Creación del Componente ChatbotForm
1.1. Estructura del Componente
Crear resources/js/Components/Chatbots/ChatbotForm.vue:
vue
<script setup>
import { defineProps, computed } from 'vue'
import { useForm } from '@inertiajs/vue3'
const props = defineProps({
form: {
type: Object,
required: true
},
isEditing: {
type: Boolean,
default: false
}
})
// Modelos disponibles
const models = [
{ value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo' },
{ value: 'gpt-4', label: 'GPT-4' },
{ value: 'gpt-4-turbo', label: 'GPT-4 Turbo' },
{ value: 'gpt-4o', label: 'GPT-4o' },
]
</script>
<template>
<div class="grid grid-cols-6 gap-6">
<!-- Nombre -->
<div class="col-span-6 sm:col-span-4">
<label
for="name"
class="block text-sm font-medium text-gray-700 dark:text-gray-300"
>
Nombre
</label>
<input
id="name"
v-model="form.name"
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-blue-500 focus:ring-blue-500"
placeholder="Ej: Chef Ecuador, Asistente de Ventas..."
/>
<p
v-if="form.errors.name"
class="mt-1 text-sm text-red-600 dark:text-red-400"
>
{{ form.errors.name }}
</p>
</div>
<!-- System Prompt -->
<div class="col-span-6 sm:col-span-4">
<label
for="system_prompt"
class="block text-sm font-medium text-gray-700 dark:text-gray-300"
>
System Prompt
</label>
<textarea
id="system_prompt"
v-model="form.system_prompt"
rows="6"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-blue-500 focus:ring-blue-500"
placeholder="Instrucciones que guiarán el comportamiento del chatbot..."
/>
<p
v-if="form.errors.system_prompt"
class="mt-1 text-sm text-red-600 dark:text-red-400"
>
{{ form.errors.system_prompt }}
</p>
</div>
<!-- Modelo -->
<div class="col-span-6 sm:col-span-4">
<label
for="model"
class="block text-sm font-medium text-gray-700 dark:text-gray-300"
>
Modelo de IA
</label>
<select
id="model"
v-model="form.model"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-blue-500 focus:ring-blue-500"
>
<option
v-for="model in models"
:key="model.value"
:value="model.value"
>
{{ model.label }}
</option>
</select>
<p
v-if="form.errors.model"
class="mt-1 text-sm text-red-600 dark:text-red-400"
>
{{ form.errors.model }}
</p>
</div>
<!-- Temperatura -->
<div class="col-span-6 sm:col-span-4">
<label
for="temperature"
class="block text-sm font-medium text-gray-700 dark:text-gray-300"
>
Temperatura: {{ form.temperature }}
</label>
<input
id="temperature"
v-model="form.temperature"
type="range"
min="0"
max="1"
step="0.1"
class="mt-1 block w-full"
/>
<div class="flex justify-between text-xs text-gray-500 dark:text-gray-400">
<span>Preciso</span>
<span>Creativo</span>
</div>
<p
v-if="form.errors.temperature"
class="mt-1 text-sm text-red-600 dark:text-red-400"
>
{{ form.errors.temperature }}
</p>
</div>
</div>
</template>
1.2. Importar el Componente en la Vista Edit
En resources/js/Pages/Chatbots/Edit.vue:
vue
<script setup>
import { defineProps, ref } from 'vue'
import { Link, useForm } from '@inertiajs/vue3'
import AppLayout from '@/Layouts/AppLayout.vue'
import { ArrowLeftIcon } from '@heroicons/vue/24/solid'
import FormSection from '@/Components/FormSection.vue'
import PrimaryButton from '@/Components/PrimaryButton.vue'
import ActionMessage from '@/Components/ActionMessage.vue'
import ChatbotForm from '@/Components/Chatbots/ChatbotForm.vue' // 👈 Importar
const props = defineProps({
chatbot: {
type: Object,
required: true
}
})
// Formulario con los datos del chatbot
const form = useForm({
name: props.chatbot.name,
system_prompt: props.chatbot.system_prompt,
model: props.chatbot.model,
temperature: props.chatbot.temperature.toString(), // Convertir a string
})
const showSuccess = ref(false)
// Enviar formulario
const handleSubmit = () => {
form.put(route('chatbots.update', props.chatbot.id), {
preserveScroll: true,
onSuccess: () => {
showSuccess.value = true
setTimeout(() => {
showSuccess.value = false
}, 3000)
}
})
}
</script>
<template>
<AppLayout :title="`Editar: ${chatbot.name}`">
<!-- Header -->
<template #header>
<div class="flex items-center gap-4">
<Link
:href="route('chatbots.show', chatbot.id)"
class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
aria-label="Volver al detalle del chatbot"
>
<ArrowLeftIcon class="w-6 h-6" />
</Link>
<h1 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
Editar: {{ chatbot.name }}
</h1>
</div>
</template>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-xl sm:rounded-lg">
<div class="p-6 lg:p-8">
<!-- Formulario -->
<FormSection @submitted="handleSubmit">
<template #title>
Información del Chatbot
</template>
<template #description>
Actualiza la información de tu chatbot de inteligencia artificial.
</template>
<template #form>
<!-- 👇 Usar el componente ChatbotForm -->
<ChatbotForm
:form="form"
:is-editing="true"
/>
</template>
<template #actions>
<div class="flex items-center gap-4">
<PrimaryButton
:disabled="form.processing"
:class="{
'cursor-not-allowed opacity-50': form.processing
}"
>
{{ form.processing ? 'Guardando...' : 'Guardar Cambios' }}
</PrimaryButton>
<ActionMessage :on="showSuccess" class="mr-2">
<span class="text-green-600 dark:text-green-400">
✓ Chatbot actualizado correctamente
</span>
</ActionMessage>
<Link
:href="route('chatbots.show', chatbot.id)"
class="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
>
Cancelar
</Link>
</div>
</template>
</FormSection>
</div>
</div>
</div>
</div>
</AppLayout>
</template>
2. Problema de Autenticación (CSRF)
2.1. ¿Por qué ocurre el error "Esta acción no está autorizada"?
Causa: Laravel requiere un token CSRF para todas las peticiones POST, PUT, PATCH y DELETE.
Solución 1: Usar @csrf en formularios Blade
blade
<form method="POST">
@csrf
<!-- ... -->
</form>
Solución 2: Inertia maneja automáticamente el CSRF
Inertia.js incluye automáticamente el token CSRF en las peticiones.
2.2. Verificar Configuración de Sanctum
En config/sanctum.php:
php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),
En app/Http/Kernel.php:
php
protected $middlewareGroups = [
'web' => [
// ... otros middleware
\App\Http\Middleware\VerifyCsrfToken::class, // Verificar que esté presente
],
];
3. Implementación Completa del Formulario
3.1. Mejoras en el Componente ChatbotForm
Agregar vista previa del System Prompt:
vue
<script setup>
import { ref, computed } from 'vue'
import { EyeIcon, EyeSlashIcon } from '@heroicons/vue/24/solid'
// ... props y configuraciones
const showPreview = ref(false)
const togglePreview = () => {
showPreview.value = !showPreview.value
}
</script>
<template>
<!-- System Prompt con vista previa -->
<div class="col-span-6 sm:col-span-4">
<div class="flex justify-between items-center">
<label
for="system_prompt"
class="block text-sm font-medium text-gray-700 dark:text-gray-300"
>
System Prompt
</label>
<button
type="button"
@click="togglePreview"
class="text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
>
<EyeIcon v-if="!showPreview" class="w-4 h-4 inline-block" />
<EyeSlashIcon v-else class="w-4 h-4 inline-block" />
{{ showPreview ? 'Ocultar' : 'Vista previa' }}
</button>
</div>
<textarea
id="system_prompt"
v-model="form.system_prompt"
rows="6"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-blue-500 focus:ring-blue-500 font-mono text-sm"
placeholder="Instrucciones que guiarán el comportamiento del chatbot..."
/>
<!-- Vista previa -->
<div
v-if="showPreview"
class="mt-2 p-4 bg-gray-50 dark:bg-gray-900 rounded-md border border-gray-200 dark:border-gray-700 whitespace-pre-wrap text-sm"
>
{{ form.system_prompt || 'El prompt aparecerá aquí...' }}
</div>
<p
v-if="form.errors.system_prompt"
class="mt-1 text-sm text-red-600 dark:text-red-400"
>
{{ form.errors.system_prompt }}
</p>
</div>
</template>
3.2. Validación en Tiempo Real
Agregar validaciones en el componente padre:
vue
<script setup>
import { computed } from 'vue'
import { useForm } from '@inertiajs/vue3'
// ... código existente
const form = useForm({
name: props.chatbot.name,
system_prompt: props.chatbot.system_prompt,
model: props.chatbot.model,
temperature: props.chatbot.temperature.toString(),
})
// Validación en tiempo real
const isValid = computed(() => {
return form.name.length > 0 &&
form.name.length <= 255 &&
form.system_prompt.length >= 10 &&
form.system_prompt.length <= 5000 &&
form.model.length > 0 &&
parseFloat(form.temperature) >= 0 &&
parseFloat(form.temperature) <= 1
})
// Mensajes de validación personalizados
const validationMessages = computed(() => {
const messages = []
if (form.name.length === 0) {
messages.push('El nombre es obligatorio')
} else if (form.name.length > 255) {
messages.push('El nombre no puede exceder 255 caracteres')
}
if (form.system_prompt.length < 10) {
messages.push('El system prompt debe tener al menos 10 caracteres')
} else if (form.system_prompt.length > 5000) {
messages.push('El system prompt no puede exceder 5000 caracteres')
}
const temp = parseFloat(form.temperature)
if (isNaN(temp) || temp < 0 || temp > 1) {
messages.push('La temperatura debe estar entre 0 y 1')
}
return messages
})
</script>
<template>
<FormSection @submitted="handleSubmit">
<!-- ... -->
<template #actions>
<div class="flex items-center gap-4">
<PrimaryButton
:disabled="!isValid || form.processing"
:class="{
'cursor-not-allowed opacity-50': !isValid || form.processing
}"
>
{{ form.processing ? 'Guardando...' : 'Guardar Cambios' }}
</PrimaryButton>
<!-- Mostrar errores de validación -->
<div v-if="validationMessages.length > 0" class="text-sm text-red-600 dark:text-red-400">
<p v-for="(msg, index) in validationMessages" :key="index">
• {{ msg }}
</p>
</div>
<ActionMessage :on="showSuccess" class="mr-2">
<span class="text-green-600 dark:text-green-400">
✓ Chatbot actualizado correctamente
</span>
</ActionMessage>
<Link
:href="route('chatbots.show', chatbot.id)"
class="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
>
Cancelar
</Link>
</div>
</template>
</FormSection>
</template>
4. Manejo de Errores
4.1. Mostrar Errores del Servidor
En Edit.vue:
vue
<script setup>
import { ref } from 'vue'
import { useForm } from '@inertiajs/vue3'
import { ExclamationCircleIcon } from '@heroicons/vue/24/solid'
// ... código existente
const serverErrors = ref([])
const handleSubmit = () => {
form.put(route('chatbots.update', props.chatbot.id), {
preserveScroll: true,
onSuccess: () => {
showSuccess.value = true
serverErrors.value = []
setTimeout(() => {
showSuccess.value = false
}, 3000)
},
onError: (errors) => {
// Convertir errores en array de mensajes
serverErrors.value = Object.values(errors).flat()
}
})
}
</script>
<template>
<FormSection @submitted="handleSubmit">
<!-- Mostrar errores del servidor -->
<div v-if="serverErrors.length > 0" class="col-span-6">
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md p-4">
<div class="flex items-start gap-3">
<ExclamationCircleIcon class="w-5 h-5 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" />
<div>
<h4 class="text-sm font-medium text-red-800 dark:text-red-200">
Error al guardar los cambios
</h4>
<ul class="mt-1 text-sm text-red-700 dark:text-red-300 list-disc list-inside">
<li v-for="(error, index) in serverErrors" :key="index">
{{ error }}
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- ... resto del formulario -->
</FormSection>
</template>
5. Resumen
5.1. Archivos Creados/Modificados
5.2. Funcionalidades Implementadas
✅ Componente reutilizable para formularios
✅ Vista previa del system prompt
✅ Validaciones en tiempo real
✅ Manejo de errores del servidor
✅ Mensajes de éxito y error
✅ Control deslizante para temperatura
✅ Select para modelos de IA
5.3. Comandos Útiles
bash
# Verificar rutas
php artisan route:list
# Limpiar caché de rutas
php artisan route:clear
¡El formulario de edición está completamente funcional! ✏️
Comentarios
Publicar un comentario