14. Página para editar Chatbots
Página para Editar Chatbots
1. Configuración de las Rutas de Edición
1.1. Actualizar Botón Editar en el Listado
En ChatbotListItem.vue:
vue
<template>
<div class="flex space-x-2 mt-4 md:mt-0">
<!-- Botón Ver -->
<PrimaryButton
:href="route('chatbots.show', chatbot.id)"
aria-label="Ver detalles del chatbot"
class="gap-2"
>
<EyeIcon class="w-4 h-4" />
<span>Ver</span>
</PrimaryButton>
<!-- 👇 Botón Editar actualizado -->
<SecondaryButton
:href="route('chatbots.edit', chatbot.id)"
aria-label="Editar chatbot"
class="gap-2"
>
<PencilIcon class="w-4 h-4" />
<span>Editar</span>
</SecondaryButton>
<!-- Botón Eliminar -->
<DangerButton
aria-label="Eliminar chatbot"
class="gap-2"
@click="$emit('delete', chatbot.id)"
>
<TrashIcon class="w-4 h-4" />
<span>Eliminar</span>
</DangerButton>
</div>
</template>
1.2. Actualizar Botón Editar en la Vista Show
En resources/js/Pages/Chatbots/Show.vue:
vue
<template>
<div class="space-y-3">
<!-- Chatear -->
<SecondaryButton
:href="route('chats.create', chatbot.id)"
class="w-full justify-center gap-2"
aria-label="Iniciar chat con este chatbot"
>
<ChatBubbleLeftRightIcon class="w-5 h-5" />
<span>Chatear</span>
</SecondaryButton>
<!-- 👇 Botón Editar actualizado -->
<SecondaryButton
:href="route('chatbots.edit', chatbot.id)"
class="w-full justify-center gap-2"
aria-label="Editar chatbot"
>
<PencilIcon class="w-5 h-5" />
<span>Editar</span>
</SecondaryButton>
<!-- Eliminar -->
<DangerButton
class="w-full justify-center gap-2"
aria-label="Eliminar chatbot"
>
<TrashIcon class="w-5 h-5" />
<span>Eliminar</span>
</DangerButton>
</div>
</template>
2. Controlador - Método Edit
2.1. Actualizar ChatbotController
En app/Http/Controllers/ChatbotController.php:
php
<?php
namespace App\Http\Controllers;
use App\Models\Chatbot;
use Illuminate\Http\Request;
use Inertia\Inertia;
class ChatbotController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$chatbots = $request->user()->chatbots;
return Inertia::render('Chatbots/Index', [
'chatbots' => $chatbots,
]);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Request $request, Chatbot $chatbot)
{
// Verificar que el chatbot pertenece al usuario
if ($chatbot->user_id !== $request->user()->id) {
abort(403, 'No tienes permiso para editar este chatbot.');
}
return Inertia::render('Chatbots/Edit', [
'chatbot' => $chatbot,
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Chatbot $chatbot)
{
// Verificar que el chatbot pertenece al usuario
if ($chatbot->user_id !== $request->user()->id) {
abort(403, 'No tienes permiso para editar este chatbot.');
}
// Validar datos
$validated = $request->validate([
'name' => 'required|string|max:255',
'system_prompt' => 'required|string',
'model' => 'required|string|max:255',
'temperature' => 'required|numeric|min:0|max:1',
]);
// Actualizar chatbot
$chatbot->update($validated);
return redirect()->route('chatbots.show', $chatbot)
->with('success', 'Chatbot actualizado correctamente.');
}
// ... otros métodos
}
3. Creación de la Vista Edit
3.1. Duplicar y Adaptar Show.vue
Crear 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'
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,
})
// Estado del formulario
const isSubmitting = ref(false)
const showSuccess = ref(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' },
]
// Enviar formulario
const submit = () => {
isSubmitting.value = true
showSuccess.value = false
form.put(route('chatbots.update', props.chatbot.id), {
preserveScroll: true,
onSuccess: () => {
isSubmitting.value = false
showSuccess.value = true
// Ocultar mensaje después de 3 segundos
setTimeout(() => {
showSuccess.value = false
}, 3000)
},
onError: () => {
isSubmitting.value = false
}
})
}
</script>
<template>
<AppLayout :title="`Editar: ${chatbot.name}`">
<!-- Header -->
<template #header>
<div class="flex items-center gap-4">
<!-- Botón de volver al detalle -->
<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>
<!-- Contenido principal -->
<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 de edición -->
<FormSection @submitted="submit">
<!-- Título -->
<template #title>
Información del Chatbot
</template>
<!-- Descripción -->
<template #description>
Actualiza la información de tu chatbot de inteligencia artificial.
</template>
<!-- Campos del formulario -->
<template #form>
<div class="col-span-6 sm:col-span-4">
<!-- Nombre -->
<div class="mb-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="mb-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="mb-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="mb-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>
<!-- Acciones del formulario -->
<template #actions>
<div class="flex items-center gap-4">
<!-- Botón de guardar -->
<PrimaryButton
:disabled="form.processing"
:class="{
'cursor-not-allowed opacity-50': form.processing
}"
>
{{ form.processing ? 'Guardando...' : 'Guardar Cambios' }}
</PrimaryButton>
<!-- Mensaje de éxito -->
<ActionMessage :on="showSuccess" class="mr-2">
<span class="text-green-600 dark:text-green-400">
✓ Chatbot actualizado correctamente
</span>
</ActionMessage>
<!-- Botón de cancelar -->
<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>
4. Mejoras Adicionales
4.1. Confirmación de Cambios
Agregar confirmación antes de salir sin guardar:
vue
<script setup>
import { onBeforeUnmount, watch } from 'vue'
import { usePage } from '@inertiajs/vue3'
// ... código existente
// Detectar cambios sin guardar
const hasUnsavedChanges = ref(false)
watch(
() => form.data(),
(newVal, oldVal) => {
hasUnsavedChanges.value = true
},
{ deep: true }
)
// Confirmar al salir
const confirmLeave = (e) => {
if (hasUnsavedChanges.value) {
e.preventDefault()
e.returnValue = 'Tienes cambios sin guardar. ¿Estás seguro de que quieres salir?'
}
}
onBeforeUnmount(() => {
window.removeEventListener('beforeunload', confirmLeave)
})
// Agregar listener cuando hay cambios
watch(hasUnsavedChanges, (value) => {
if (value) {
window.addEventListener('beforeunload', confirmLeave)
} else {
window.removeEventListener('beforeunload', confirmLeave)
}
})
</script>
4.2. Vista Previa del System Prompt
Agregar vista previa en tiempo real:
vue
<template>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Editor -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
System Prompt
</label>
<textarea
v-model="form.system_prompt"
rows="10"
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"
/>
</div>
<!-- Vista previa -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Vista Previa
</label>
<div class="mt-1 p-4 bg-gray-50 dark:bg-gray-900 rounded-md border border-gray-200 dark:border-gray-700 min-h-[200px] whitespace-pre-wrap text-sm">
{{ form.system_prompt || 'El prompt aparecerá aquí...' }}
</div>
</div>
</div>
</template>
4.3. Estados de Guardado
Agregar indicadores visuales:
vue
<template>
<div class="flex items-center gap-4">
<!-- Estado de guardado -->
<div v-if="form.processing" class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-2">
<svg class="animate-spin h-4 w-4" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" />
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
Guardando...
</div>
<!-- Último guardado -->
<div v-else-if="lastSaved" class="text-sm text-gray-500 dark:text-gray-400">
Último guardado: {{ lastSaved }}
</div>
</div>
</template>
5. Validaciones
5.1. Validaciones del Lado del Cliente
vue
<script setup>
import { computed } from 'vue'
// Validación en tiempo real
const isValid = computed(() => {
return form.name.length > 0 &&
form.system_prompt.length > 0 &&
form.model.length > 0 &&
form.temperature >= 0 &&
form.temperature <= 1
})
// ... en el template
<PrimaryButton
:disabled="!isValid || form.processing"
:class="{
'cursor-not-allowed opacity-50': !isValid || form.processing
}"
>
Guardar Cambios
</PrimaryButton>
</template>
5.2. Validaciones Personalizadas
En el controlador:
php
public function update(Request $request, Chatbot $chatbot)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'system_prompt' => [
'required',
'string',
'min:10', // Mínimo 10 caracteres
'max:5000', // Máximo 5000 caracteres
],
'model' => 'required|in:gpt-3.5-turbo,gpt-4,gpt-4-turbo,gpt-4o',
'temperature' => 'required|numeric|min:0|max:1',
]);
// ... actualizar
}
6. Resumen
6.1. Archivos Creados/Modificados
6.2. Funcionalidades Implementadas
✅ Formulario de edición con todos los campos
✅ Validaciones del lado del servidor y cliente
✅ Indicadores de estado (cargando, éxito)
✅ Vista previa del system prompt
✅ Control deslizante para temperatura
✅ Mensajes de error y éxito
✅ Botón de cancelar para volver al detalle
6.3. Próximos Pasos
🔄 Implementar eliminación de chatbots
💬 Crear sistema de conversaciones
📄 Gestionar fuentes de conocimiento
🎨 Mejorar la experiencia de usuario
Comentarios
Publicar un comentario