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..."

                                        />

                                        <

                                            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..."

                                        />

                                        <

                                            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>

                                        <

                                            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>

                                        <

                                            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

Archivo

Acción

app/Http/Controllers/ChatbotController.php

✅ Métodos edit y update

resources/js/Pages/Chatbots/Edit.vue

✅ Creado

resources/js/Components/Chatbots/ChatbotListItem.vue

✅ Ruta actualizada

resources/js/Pages/Chatbots/Show.vue

✅ Ruta actualizada

routes/web.php

✅ Ya existe (Resource)

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

  1. 🔄 Implementar eliminación de chatbots

  2. 💬 Crear sistema de conversaciones

  3. 📄 Gestionar fuentes de conocimiento

  4. 🎨 Mejorar la experiencia de usuario

Comentarios

Entradas más populares de este blog

1. Introducción al curso de Chatbots personalizados con Laravel & OpenAI

2. Prerequisitos para el Curso de Chatbots con Laravel y OpenAI

4. Herramientas de Calidad