Menu iconMenu icon
OpenAI API Biblia Volumen 2

Capítulo 6: Conjuntos de IA de Modelo Cruzado

6.2 Construyendo un Panel de Control para Creadores

Aquí es donde todas las capacidades que has desarrollado hasta ahora se unen para crear un sistema potente y unificado. Al integrar múltiples tecnologías de IA, podemos crear aplicaciones que son más que la suma de sus partes. Exploremos estas capacidades fundamentales en detalle:

  • La transcripción convierte las palabras habladas en texto escritoUsando modelos avanzados de reconocimiento de voz como Whisper, podemos convertir con precisión grabaciones de audio en texto, preservando la intención y el contexto del hablante. Esto forma la base para el procesamiento posterior.
  • La generación de contenido crea material nuevo y contextualmente relevanteLos modelos de lenguaje grandes pueden analizar el texto transcrito y generar nuevo contenido que mantiene la consistencia con el mensaje original mientras añade ideas valiosas o expande puntos clave.
  • La ingeniería de prompts elabora instrucciones precisas para modelos de IAA través de una construcción cuidadosa de prompts, podemos guiar a los modelos de IA para producir resultados más precisos y relevantes. Esto implica entender tanto las capacidades técnicas de los modelos como las formas matizadas de comunicarse con ellos.
  • La creación de imágenes transforma descripciones textuales en arte visualLos modelos como DALL·E pueden interpretar descripciones textuales y crear imágenes correspondientes, añadiendo una dimensión visual a nuestras aplicaciones y haciendo más tangibles los conceptos abstractos.

Estos componentes no solo existen uno al lado del otro - forman un pipeline interconectado donde cada paso mejora el siguiente. La salida de la transcripción alimenta la generación de contenido, que informa la ingeniería de prompts, llevando finalmente a la creación de imágenes. Esta integración perfecta crea un flujo de trabajo fluido donde los usuarios pueden comenzar con una simple grabación de voz y terminar con una salida multimedia rica, todo dentro de un sistema único y cohesivo. Al eliminar la necesidad de cambiar entre diferentes herramientas o interfaces, los usuarios pueden concentrarse en su proceso creativo en lugar de los detalles de implementación técnica.

6.2.1 Lo que vas a construir

En esta sección, diseñarás e implementarás un Panel de Control para Creadores - una interfaz web sofisticada que transforma la forma en que los creadores trabajan con la IA. Esta plataforma integral sirve como centro neurálgico para la creación de contenido, combinando múltiples tecnologías de IA en una experiencia fluida. Exploremos las características clave que hacen poderoso este panel:

  • Subir una grabación de voz
    Los creadores pueden subir fácilmente archivos de audio en varios formatos, simplificando el proceso de iniciar su proceso creativo con ideas o narraciones habladas.
  • Transcribir la grabación de voz a texto usando IA
    Utilizando tecnología avanzada de reconocimiento de voz por IA, el sistema convierte con precisión las palabras habladas en texto escrito, manteniendo los matices y el contexto de la grabación original.
  • Convertir esa transcripción en un prompt editable
    El sistema procesa de manera inteligente el texto transcrito para crear prompts estructurados y preparados para IA que pueden personalizarse para lograr el resultado creativo deseado.
  • Generar imágenes usando DALL·E basadas en el prompt
    Aprovechando las potentes capacidades de generación de imágenes de DALL·E, el sistema crea representaciones visuales que coinciden con los prompts especificados, dando vida a las ideas a través de obras de arte generadas por IA.
  • Resumir la transcripción
    El panel utiliza IA para destilar transcripciones largas en resúmenes concisos y significativos, ayudando a los creadores a captar rápidamente los conceptos y temas principales.
  • Mostrar todos los resultados para revisión y uso posterior en la producción de contenido
    Todo el contenido generado - desde transcripciones hasta imágenes - se presenta en un formato organizado y fácil de revisar, permitiendo a los creadores gestionar y utilizar sus recursos de manera eficiente.

Para construir este sistema robusto, implementarás un stack tecnológico moderno usando Flask para las operaciones del backend y una combinación limpia y adaptable de HTML y CSS para la interfaz del frontend. Esta arquitectura garantiza tanto la modularidad como la mantenibilidad, facilitando la actualización y escalabilidad del panel según sea necesario.

6.2.2 Implementación Paso a Paso

Paso 1: Configuración del Proyecto

Descarga el ejemplo de audio: https://files.cuantum.tech/audio/dashboard-project.mp3

Crea un nuevo directorio para tu proyecto y navega hacia él:

mkdir creator_dashboard
cd creator_dashboard

Se recomienda configurar un entorno virtual:

python -m venv venv
source venv/bin/activate  # En macOS/Linux
venv\\Scripts\\activate  # En Windows

Instala los paquetes de Python necesarios:

pip install flask openai python-dotenv

Organiza los archivos de tu proyecto de la siguiente manera:

/creator_dashboard

├── app.py
├── .env
└── templates/
    └── dashboard.html
└── utils/
    ├── __init__.py
    ├── transcribe.py
    ├── summarize.py
    ├── generate_prompt.py
    └── generate_image.py
  • app.py: El archivo principal de la aplicación Flask.
  • .env: Un archivo para almacenar tu clave de API de OpenAI.
  • templates/: Un directorio para plantillas HTML.
  • templates/dashboard.html: La plantilla HTML para la interfaz de usuario.
  • utils/: Un directorio para módulos Python que contienen funciones reutilizables.
    • __init__.py: Convierte el directorio utils en un paquete Python.
    • transcribe.py: Contiene la función para transcribir audio usando Whisper.
    • summarize.py: Contiene la función para resumir la transcripción usando un Modelo de Lenguaje Grande.
    • generate_prompt.py: Contiene la función para generar un prompt de imagen a partir del resumen usando un Modelo de Lenguaje Grande.
    • generate_image.py: Contiene la función para generar una imagen con DALL·E 3.

Paso 2: Crear los Módulos de Utilidades

Crea los siguientes archivos Python en el directorio utils/:

utils/transcribe.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def transcribe_audio(file_path: str) -> Optional[str]:
    """
    Transcribes an audio file using OpenAI's Whisper API.

    Args:
        file_path (str): The path to the audio file.

    Returns:
        Optional[str]: The transcribed text, or None on error.
    """
    try:
        logger.info(f"Transcribing audio: {file_path}")
        audio_file = open(file_path, "rb")
        response = openai.Audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
        )
        transcript = response.text
        audio_file.close()
        return transcript
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error during transcription: {e}")
        return None
  • Este módulo define la función transcribe_audio, que toma como entrada la ruta de un archivo de audio y utiliza la API Whisper de OpenAI para generar una transcripción de texto.
  • La función abre el archivo de audio en modo lectura binaria ("rb").
  • Llama a openai.Audio.transcriptions.create() para realizar la transcripción, especificando el modelo "whisper-1".
  • Extrae el texto transcrito de la respuesta de la API.
  • Incluye manejo de errores usando un bloque try...except para capturar posibles excepciones openai.error.OpenAIError (específicas de OpenAI) y Exception general para otros errores. Si ocurre un error, registra el error y devuelve None.
  • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
  • El archivo de audio se cierra después de la transcripción.

utils/summarize.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def summarize_transcript(text: str) -> Optional[str]:
    """
    Summarizes a text transcript using OpenAI's Chat Completion API.

    Args:
        text (str): The text transcript to summarize.

    Returns:
        Optional[str]: The summarized text, or None on error.
    """
    try:
        logger.info("Summarizing transcript")
        response = openai.chat.completions.create(
            model="gpt-4o",
            messages=[
                {"role": "system",
                 "content": "You are a helpful assistant.  Provide a concise summary of the text, suitable for generating a visual representation."},
                {"role": "user", "content": text}
            ],
        )
        summary = response.choices[0].message.content
        logger.info(f"Summary: {summary}")
        return summary
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating summary: {e}")
        return None
  • Este módulo define la función summarize_transcript, que toma como entrada una transcripción de texto y utiliza la API de Chat Completion de OpenAI para generar un resumen conciso.
  • El mensaje del sistema indica al modelo que actúe como un asistente útil y proporcione un resumen conciso del texto, adecuado para generar una representación visual.
  • El mensaje del usuario proporciona la transcripción como contenido para que el modelo lo resuma.
  • La función extrae el resumen de la respuesta de la API.
  • Incluye manejo de errores.

utils/generate_prompt.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def create_image_prompt(transcription: str) -> Optional[str]:
    """
    Generates a detailed image prompt from a text transcription using OpenAI's Chat Completion API.

    Args:
        transcription (str): The text transcription of the audio.

    Returns:
        Optional[str]: A detailed text prompt suitable for image generation, or None on error.
    """
    try:
        logger.info("Generating image prompt from transcription")
        response = openai.chat.completions.create(
            model="gpt-4o",  # Use a powerful chat model
            messages=[
                {
                    "role": "system",
                    "content": "You are a creative assistant. Your task is to create a vivid and detailed text description of a scene that could be used to generate an image with an AI image generation model. Focus on capturing the essence and key visual elements of the audio content.  Do not include any phrases like 'based on the audio' or 'from the user audio'.  Incorporate scene lighting, time of day, weather, and camera angle into the description.  Limit the description to 200 words.",
                },
                {"role": "user", "content": transcription},
            ],
        )
        prompt = response.choices[0].message.content
        prompt = prompt.strip()  # Remove leading/trailing spaces
        logger.info(f"Generated prompt: {prompt}")
        return prompt
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image prompt: {e}")
        return None
  • Este módulo define la función create_image_prompt, que toma como entrada el texto transcrito y utiliza la API de Chat Completion de OpenAI para generar un texto detallado que sirva como prompt para la generación de imágenes.
  • El mensaje del sistema instruye al modelo para actuar como un asistente creativo y generar una descripción vívida de una escena. Este prompt del sistema es clave para guiar al modelo de lenguaje a producir un prompt de alta calidad. Se le indica al modelo que se enfoque en elementos visuales e incorpore detalles como la iluminación, la hora del día, el clima y el ángulo de cámara. También se limita la longitud de la descripción a 200 palabras.
  • El mensaje del usuario proporciona el texto transcrito como contenido con el que el modelo debe trabajar.
  • La función extrae el prompt generado de la respuesta de la API.
  • Elimina los espacios en blanco al inicio y al final del prompt generado.
  • Incluye manejo de errores.

utils/generate_image.py:

import openai
import logging
from typing import Optional, Dict

logger = logging.getLogger(__name__)


def generate_dalle_image(prompt: str, model: str = "dall-e-3", size: str = "1024x1024",
                       response_format: str = "url", quality: str = "standard") -> Optional[str]:
    """
    Generates an image using OpenAI's DALL·E API.

    Args:
        prompt (str): The text prompt to generate the image from.
        model (str, optional): The DALL·E model to use. Defaults to "dall-e-3".
        size (str, optional): The size of the generated image. Defaults to "1024x1024".
        response_format (str, optional): The format of the response. Defaults to "url".
        quality (str, optional): The quality of the image. Defaults to "standard".

    Returns:
        Optional[str]: The URL of the generated image, or None on error.
    """
    try:
        logger.info(f"Generating image with prompt: {prompt}, model: {model}, size: {size}, format: {response_format}, quality: {quality}")
        response = openai.images.generate(
            prompt=prompt,
            model=model,
            size=size,
            response_format=response_format,
            quality=quality
        )
        image_url = response.data[0].url
        logger.info(f"Image URL: {image_url}")
        return image_url
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image: {e}")
        return None
  • Este módulo define la función generate_dalle_image, que toma un texto descriptivo como entrada y utiliza la API DALL·E de OpenAI para generar una imagen.
  • Llama al método openai.images.generate() para generar la imagen.
  • Acepta parámetros opcionales de modelsizeresponse_format, y quality, permitiendo al usuario configurar la generación de la imagen.
  • Extrae la URL de la imagen generada desde la respuesta de la API.
  • Incluye manejo de errores.

Paso 5: Crear la Aplicación Principal (app.py)

Crea un archivo Python llamado app.py en el directorio raíz de tu proyecto y agrega el siguiente código:

from flask import Flask, request, render_template, jsonify, make_response, redirect, url_for
import os
from dotenv import load_dotenv
import logging
from typing import Optional, Dict
from werkzeug.utils import secure_filename
from werkzeug.datastructures import FileStorage

# Import the utility functions from the utils directory
from utils.transcribe import transcribe_audio
from utils.generate_prompt import create_image_prompt
from utils.generate_image import generate_dalle_image
from utils.summarize import summarize_transcript

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'  # Store uploaded files
app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024  # 25MB max file size - increased for larger audio files
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)  # Create the upload folder if it doesn't exist

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

ALLOWED_EXTENSIONS = {'mp3', 'mp4', 'wav', 'm4a'}  # Allowed audio file extensions


def allowed_file(filename: str) -> bool:
    """
    Checks if the uploaded file has an allowed extension.

    Args:
        filename (str): The name of the file.

    Returns:
        bool: True if the file has an allowed extension, False otherwise.
    """
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route("/", methods=["GET", "POST"])
def index():
    """
    Handles the main route for the web application.
    Processes audio uploads, transcribes them, generates image prompts, and displays images.
    """
    transcript = None
    image_url = None
    prompt_summary = None
    error_message = None
    summary = None # Initialize summary

    if request.method == "POST":
        if 'audio_file' not in request.files:
            error_message = "No file part"
            logger.warning(error_message)
            return render_template("index.html", error_message=error_message)

        file: FileStorage = request.files['audio_file']  # Use type hinting
        if file.filename == '':
            error_message = "No file selected"
            logger.warning(request)
            return render_template("index.html", error_message=error_message)

        if file and allowed_file(file.filename):
            try:
                # Secure the filename and construct a safe path
                filename = secure_filename(file.filename)
                file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
                file.save(file_path)  # Save the uploaded file

                transcript = transcribe_audio(file_path)  # Transcribe audio
                if not transcript:
                    error_message = "Audio transcription failed. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                summary = summarize_transcript(transcript) # Summarize the transcript
                if not summary:
                    error_message = "Audio summary failed. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                prompt_summary = generate_image_prompt(transcript)  # Generate prompt
                if not prompt_summary:
                    error_message = "Failed to generate image prompt. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                image_url = generate_dalle_image(prompt_summary, model=request.form.get('model', 'dall-e-3'),
                                                size=request.form.get('size', '1024x1024'),
                                                response_format=request.form.get('format', 'url'),
                                                quality=request.form.get('quality', 'standard'))  # Generate image
                if not image_url:
                    error_message = "Failed to generate image. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                # Optionally, delete the uploaded file after processing
                os.remove(file_path)
                logger.info(f"Successfully processed audio file and generated image.")
                return render_template("index.html", transcript=transcript, image_url=image_url, prompt=prompt_summary, summary=summary)

            except Exception as e:
                error_message = f"An error occurred: {e}"
                logger.error(error_message)
                return render_template("index.html",  error_message=error_message)
        else:
            error_message = "Invalid file type. Please upload a valid audio file (MP3, MP4, WAV, M4A)."
            logger.warning(request)
            return render_template("index.html",  error_message=error_message)

    return render_template("index.html", transcript=transcript, image_url=image_url, prompt=prompt_summary,
                           error=error_message, summary=summary)



@app.errorhandler(500)
def internal_server_error(e):
    """Handles internal server errors."""
    logger.error(f"Internal Server Error: {e}")
    return render_template("error.html", error="Internal Server Error"), 500


if __name__ == "__main__":
    app.run(debug=True)

Desglose del Código:

  • Declaraciones de Importación: Importa los módulos necesarios de Flask, la biblioteca OpenAI, osdotenvloggingOptional y Dict para la tipificación, y secure_filename y FileStorage de Werkzeug.
  • Variables de Entorno: Carga la clave API de OpenAI desde el archivo .env.
  • Aplicación Flask:
    • Crea una instancia de la aplicación Flask.
    • Configura una carpeta de carga y tamaño máximo de archivo. El UPLOAD_FOLDER se establece como 'uploads', y MAX_CONTENT_LENGTH se establece en 25MB. La carpeta de carga se crea si no existe.
  • Configuración de Registro: Configura el sistema de registro.
  • Función allowed_file: Verifica si el archivo cargado tiene una extensión de audio permitida.
  • Función transcribe_audio:
    • Toma la ruta del archivo de audio como entrada.
    • Abre el archivo de audio en modo de lectura binaria ("rb").
    • Llama al método openai.Audio.transcriptions.create() de la API de OpenAI para transcribir el audio.
    • Extrae el texto transcrito de la respuesta de la API.
    • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
    • Incluye manejo de errores para errores de la API de OpenAI y otras excepciones. El archivo de audio se cierra después de la transcripción.
  • Función generate_image_prompt:
    • Toma el texto transcrito como entrada.
    • Utiliza la API de Chat Completion de OpenAI (openai.chat.completions.create()) con el modelo gpt-4o para generar un prompt de texto detallado adecuado para la generación de imágenes.
    • El mensaje del sistema instruye al modelo para que actúe como un asistente creativo y proporcione una descripción vívida y detallada de una escena que podría usarse para generar una imagen con un modelo de generación de imágenes de IA. El prompt del sistema es crucial para guiar al LLM en la generación de un prompt de alta calidad. Instruimos al LLM para que se centre en elementos visuales e incorpore detalles como iluminación, hora del día, clima y ángulo de la cámara. También limitamos la longitud de la descripción a 200 palabras.
    • Extrae el prompt generado de la respuesta de la API.
    • Elimina los espacios iniciales/finales del prompt generado.
    • Incluye manejo de errores.
  • Función generate_image:
    • Toma el prompt de imagen como entrada.
    • Llama al método openai.Image.create() de la API de OpenAI para generar una imagen usando DALL·E 3.
    • Acepta parámetros opcionales de modelsizeresponse_format, y quality, permitiendo al usuario configurar la generación de la imagen.
    • Extrae la URL de la imagen generada de la respuesta de la API.
    • Incluye manejo de errores.
  • Ruta index:
    • Maneja solicitudes GET y POST.
    • Para solicitudes GET, renderiza la página HTML inicial.
    • Para solicitudes POST (cuando el usuario carga un archivo de audio):
      • Valida el archivo cargado:
        • Verifica si existe la parte del archivo en la solicitud.
        • Verifica si se seleccionó un archivo.
        • Verifica si el tipo de archivo está permitido usando la función allowed_file.
      • Guarda el archivo cargado en una ubicación temporal usando un nombre de archivo seguro.
      • Llama a las funciones de utilidad para:
        • Transcribir el audio usando transcribe_audio().
        • Generar un prompt de imagen a partir de la transcripción usando create_image_prompt().
        • Generar una imagen a partir del prompt usando generate_dalle_image().
      • Resume la transcripción usando la api de chat completions de openai.
      • Maneja los errores que puedan ocurrir durante cualquiera de estos pasos, registrando el error y renderizando la plantilla index.html con un mensaje de error apropiado. El archivo temporal se elimina antes de renderizar la página de error.
      • Si todos los pasos son exitosos, renderiza la plantilla index.html, pasando el texto de transcripción, URL de la imagen y prompt generado para ser mostrados.
  • @app.errorhandler(500): Maneja errores HTTP 500 (Error Interno del Servidor) registrando el error y renderizando una página de error amigable para el usuario.
  • if __name__ == "__main__":: Inicia el servidor de desarrollo Flask si el script se ejecuta directamente.

Paso 6: Crear la Plantilla HTML (templates/dashboard.html)

Crea una carpeta llamada templates en el mismo directorio que app.py. Dentro de la carpeta templates, crea un archivo llamado dashboard.html con el siguiente código HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Creator Dashboard</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
    <style>
        /* --- General Styles --- */
        body {
            font-family: 'Inter', sans-serif;
            padding: 40px;
            background-color: #f9fafb; /* Tailwind's gray-50 */
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            color: #374151; /* Tailwind's gray-700 */
        }
        .container {
            max-width: 800px; /* Increased max-width */
            width: 95%; /* Take up most of the viewport */
            background-color: #fff;
            padding: 2rem;
            border-radius: 0.75rem; /* Tailwind's rounded-lg */
            box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.05); /* Tailwind's shadow-xl */
            text-align: center;
        }
        h2 {
            font-size: 2.25rem; /* Tailwind's text-3xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1.5rem; /* Tailwind's mb-6 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        p{
            color: #6b7280; /* Tailwind's gray-500 */
            margin-bottom: 1rem;
        }

        /* --- Form Styles --- */
        form {
            margin-top: 1rem; /* Tailwind's mt-4 */
            margin-bottom: 1.5rem;
            display: flex;
            flex-direction: column;
            align-items: center; /* Center form elements */
            gap: 0.5rem; /* Tailwind's gap-2 */
        }
        label {
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600;  /* Tailwind's font-semibold */
            color: #4b5563; /* Tailwind's gray-600 */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            display: block; /* Ensure label takes full width */
            text-align: left;
            width: 100%;
            max-width: 400px; /* Added max-width for label */
            margin-left: auto;
            margin-right: auto;
        }
        input[type="file"] {
            width: 100%;
            max-width: 400px; /* Added max-width for file input */
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            font-size: 1rem; /* Tailwind's text-base */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            margin-left: auto;
            margin-right: auto;
        }
        input[type="submit"] {
            padding: 0.75rem 1.5rem; /* Tailwind's px-6 py-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #4f46e5; /* Tailwind's bg-indigo-500 */
            color: #fff;
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600; /* Tailwind's font-semibold */
            cursor: pointer;
            transition: background-color 0.3s ease; /* Smooth transition */
            border: none;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Subtle shadow */
            margin-top: 1rem;
        }
        input[type="submit"]:hover {
            background-color: #4338ca; /* Tailwind's bg-indigo-700 on hover */
        }
        input[type="submit"]:focus {
            outline: none;
            box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3); /* Tailwind's ring-indigo-500 */
        }

        /* --- Result Styles --- */
        .result-container {
            margin-top: 2rem; /* Tailwind's mt-8 */
            padding: 1.5rem; /* Tailwind's p-6 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #f8fafc; /* Tailwind's bg-gray-50 */
            border: 1px solid #e2e8f0; /* Tailwind's border-gray-200 */
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */
            text-align: left;
        }
        h3 {
            font-size: 1.5rem; /* Tailwind's text-2xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1rem; /* Tailwind's mb-4 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        textarea {
            width: 100%;
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            resize: none;
            font-size: 1rem; /* Tailwind's text-base */
            line-height: 1.5rem; /* Tailwind's leading-relaxed */
            margin-top: 0.5rem; /* Tailwind's mt-2 */
            margin-bottom: 0;
            box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); /* Inner shadow */
            min-height: 100px;
        }
        textarea:focus {
            outline: none;
            border-color: #3b82f6; /* Tailwind's border-blue-500 */
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); /* Tailwind's ring-blue-500 */
        }
        img {
            max-width: 100%;
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            margin-top: 1.5rem; /* Tailwind's mt-6 */
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* Tailwind's shadow-md */
        }

        /* --- Error Styles --- */
        .error-message {
            color: #dc2626; /* Tailwind's text-red-600 */
            margin-top: 1rem; /* Tailwind's mt-4 */
            padding: 0.75rem;
            background-color: #fee2e2; /* Tailwind's bg-red-100 */
            border-radius: 0.375rem; /* Tailwind's rounded-md */
            border: 1px solid #fecaca; /* Tailwind's border-red-300 */
            text-align: center;
        }

        .prompt-select {
            margin-top: 1rem; /* Tailwind's mt-4 */
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 0.5rem;
            width: 100%;
        }

        .prompt-select label {
            font-size: 1rem;
            font-weight: 600;
            color: #4b5563;
            margin-bottom: 0.25rem;
            display: block; /* Ensure label takes full width */
            text-align: left;
            width: 100%;
            max-width: 400px; /* Added max-width for label */
            margin-left: auto;
            margin-right: auto;
        }

        .prompt-select select {
            width: 100%;
            max-width: 400px;
            padding: 0.75rem;
            border-radius: 0.5rem;
            border: 1px solid #d1d5db;
            font-size: 1rem;
            margin-bottom: 0.25rem;
            margin-left: auto;
            margin-right: auto;
            appearance: none;  /* Remove default arrow */
            background-image: url("data:image/svg+xml,%3Csvgxmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l4 4 4-4'%3E%3C/path%3E%3C/svg%3E"); /* Add custom arrow */
            background-repeat: no-repeat;
            background-position: right 0.75rem center;
            background-size: 1rem;
            padding-right: 2.5rem; /* Make space for the arrow */
        }

        .prompt-select select:focus {
            outline: none;
            border-color: #3b82f6;
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
        }


    </style>
</head>
<body>
    <div class="container">
        <h2>🎤🧠🎨 Multimodal Assistant</h2>
        <p> Upload an audio file to transcribe and generate a corresponding image. </p>
        <form method="POST" enctype="multipart/form-data">
            <label for="audio_file">Upload your voice note:</label><br>
            <input type="file" name="audio_file" accept="audio/*" required><br><br>

            <div class = "prompt-select">
                <label for="prompt_mode">Image Prompt Mode:</label>
                <select id="prompt_mode" name="prompt_mode">
                    <option value="detailed">Detailed Scene Description</option>
                    <option value="keywords">Keywords</option>
                    <option value="creative">Creative Interpretation</option>
                </select>
            </div>

            <input type="submit" value="Generate Visual Response">
        </form>

        {% if transcript %}
            <div class="result-container">
                <h3>📝 Transcript:</h3>
                <textarea readonly>{{ transcript }}</textarea>
            </div>
        {% endif %}

        {% if summary %}
            <div class="result-container">
                <h3>🔎 Summary:</h3>
                <p>{{ summary }}</p>
            </div>
        {% endif %}

        {% if prompt %}
            <div class="result-container">
                <h3>🎯 Scene Prompt:</h3>
                <p>{{ prompt }}</p>
            </div>
        {% endif %}

        {% if image_url %}
            <div class="result-container">
                <h3>🖼️ Generated Image:</h3>
                <img src="{{ image_url }}" alt="Generated image">
            </div>
        {% endif %}
        {% if error %}
            <div class="error-message">{{ error }}</div>
        {% endif %}
    </div>
</body>
</html>

6.2.3 ¿Qué hace que esto sea un Panel de Control?

Este diseño combina varios elementos clave que lo transforman de una interfaz simple a un panel de control integral:

  • Múltiples zonas de salida (texto, resumen, instrucción, imagen)La interfaz está dividida en secciones distintas, cada una dedicada a mostrar diferentes tipos de datos procesados. Esta organización permite a los usuarios seguir fácilmente la progresión desde la entrada de voz hasta la salida visual.
  • Interacción simple del usuario (procesamiento con un solo clic)A pesar del complejo procesamiento que ocurre tras bambalinas, los usuarios solo necesitan realizar una acción para iniciar todo el flujo de trabajo. Esta simplicidad hace que la herramienta sea accesible para usuarios de todos los niveles técnicos.
  • Formato limpio y legibleLa interfaz utiliza espaciado, tipografía y jerarquía visual consistentes para asegurar que la información sea fácilmente digerible. Cada sección está claramente etiquetada y visualmente separada de las demás.
  • Retroalimentación visual para reforzar la salida del modeloEl panel proporciona confirmación visual inmediata en cada paso del proceso, ayudando a los usuarios a entender cómo su entrada se está transformando a través de diferentes modelos de IA.
  • Arquitectura reutilizable, gracias a la estructura utils/El diseño modular separa la funcionalidad central en funciones de utilidad, haciendo que el código sea más fácil de mantener y adaptar para diferentes casos de uso.

6.2.4 Ideas de Casos de Uso

Este versátil panel tiene numerosas aplicaciones potenciales. Exploremos algunos casos de uso clave en detalle:

  • Un kit de herramientas de IA para creadores de contenido (convertir pensamientos en blogs + visuales)
    • Grabar sesiones de lluvia de ideas y convertirlas en publicaciones de blog estructuradas
    • Generar ilustraciones correspondientes para conceptos clave
    • Crear paquetes de contenido para redes sociales con visuales coincidentes
  • Un asistente de profesor (grabar voz ➝ resumir ➝ ilustrar)
    • Transformar planes de lección en materiales de aprendizaje visual
    • Crear contenido educativo atractivo con ilustraciones correspondientes
    • Generar ayudas visuales para conceptos complejos
  • Una herramienta de diario (registrar entradas de voz ➝ resumir + visualizar)
    • Convertir memorandos de voz diarios en entradas escritas organizadas
    • Crear tableros de estado de ánimo basados en el contenido del diario
    • Rastrear patrones emocionales a través de representaciones visuales

Resumen

En esta sección, has elevado tu asistente multimodal a un panel de control de nivel profesional. Esto es lo que has logrado:

  • Desglosar tu lógica en utilidades reutilizables
    • Creada estructura de código modular y mantenible
    • Implementada clara separación de responsabilidades
  • Aceptar entrada de audio y procesarla a través de modelos
    • Integración perfecta de múltiples tecnologías de IA
    • Pipeline de procesamiento eficiente
  • Presentar todo claramente en una IU cohesiva
    • Diseño de interfaz amigable para el usuario
    • Jerarquía de información intuitiva
  • Pasar de "demo" a herramienta
    • Implementación lista para producción
    • Arquitectura escalable

Este panel representa una interfaz de nivel profesional que ofrece valor real a los usuarios. Con su arquitectura robusta y diseño intuitivo, está listo para transformarse en un producto completo con un desarrollo adicional mínimo.

6.2 Construyendo un Panel de Control para Creadores

Aquí es donde todas las capacidades que has desarrollado hasta ahora se unen para crear un sistema potente y unificado. Al integrar múltiples tecnologías de IA, podemos crear aplicaciones que son más que la suma de sus partes. Exploremos estas capacidades fundamentales en detalle:

  • La transcripción convierte las palabras habladas en texto escritoUsando modelos avanzados de reconocimiento de voz como Whisper, podemos convertir con precisión grabaciones de audio en texto, preservando la intención y el contexto del hablante. Esto forma la base para el procesamiento posterior.
  • La generación de contenido crea material nuevo y contextualmente relevanteLos modelos de lenguaje grandes pueden analizar el texto transcrito y generar nuevo contenido que mantiene la consistencia con el mensaje original mientras añade ideas valiosas o expande puntos clave.
  • La ingeniería de prompts elabora instrucciones precisas para modelos de IAA través de una construcción cuidadosa de prompts, podemos guiar a los modelos de IA para producir resultados más precisos y relevantes. Esto implica entender tanto las capacidades técnicas de los modelos como las formas matizadas de comunicarse con ellos.
  • La creación de imágenes transforma descripciones textuales en arte visualLos modelos como DALL·E pueden interpretar descripciones textuales y crear imágenes correspondientes, añadiendo una dimensión visual a nuestras aplicaciones y haciendo más tangibles los conceptos abstractos.

Estos componentes no solo existen uno al lado del otro - forman un pipeline interconectado donde cada paso mejora el siguiente. La salida de la transcripción alimenta la generación de contenido, que informa la ingeniería de prompts, llevando finalmente a la creación de imágenes. Esta integración perfecta crea un flujo de trabajo fluido donde los usuarios pueden comenzar con una simple grabación de voz y terminar con una salida multimedia rica, todo dentro de un sistema único y cohesivo. Al eliminar la necesidad de cambiar entre diferentes herramientas o interfaces, los usuarios pueden concentrarse en su proceso creativo en lugar de los detalles de implementación técnica.

6.2.1 Lo que vas a construir

En esta sección, diseñarás e implementarás un Panel de Control para Creadores - una interfaz web sofisticada que transforma la forma en que los creadores trabajan con la IA. Esta plataforma integral sirve como centro neurálgico para la creación de contenido, combinando múltiples tecnologías de IA en una experiencia fluida. Exploremos las características clave que hacen poderoso este panel:

  • Subir una grabación de voz
    Los creadores pueden subir fácilmente archivos de audio en varios formatos, simplificando el proceso de iniciar su proceso creativo con ideas o narraciones habladas.
  • Transcribir la grabación de voz a texto usando IA
    Utilizando tecnología avanzada de reconocimiento de voz por IA, el sistema convierte con precisión las palabras habladas en texto escrito, manteniendo los matices y el contexto de la grabación original.
  • Convertir esa transcripción en un prompt editable
    El sistema procesa de manera inteligente el texto transcrito para crear prompts estructurados y preparados para IA que pueden personalizarse para lograr el resultado creativo deseado.
  • Generar imágenes usando DALL·E basadas en el prompt
    Aprovechando las potentes capacidades de generación de imágenes de DALL·E, el sistema crea representaciones visuales que coinciden con los prompts especificados, dando vida a las ideas a través de obras de arte generadas por IA.
  • Resumir la transcripción
    El panel utiliza IA para destilar transcripciones largas en resúmenes concisos y significativos, ayudando a los creadores a captar rápidamente los conceptos y temas principales.
  • Mostrar todos los resultados para revisión y uso posterior en la producción de contenido
    Todo el contenido generado - desde transcripciones hasta imágenes - se presenta en un formato organizado y fácil de revisar, permitiendo a los creadores gestionar y utilizar sus recursos de manera eficiente.

Para construir este sistema robusto, implementarás un stack tecnológico moderno usando Flask para las operaciones del backend y una combinación limpia y adaptable de HTML y CSS para la interfaz del frontend. Esta arquitectura garantiza tanto la modularidad como la mantenibilidad, facilitando la actualización y escalabilidad del panel según sea necesario.

6.2.2 Implementación Paso a Paso

Paso 1: Configuración del Proyecto

Descarga el ejemplo de audio: https://files.cuantum.tech/audio/dashboard-project.mp3

Crea un nuevo directorio para tu proyecto y navega hacia él:

mkdir creator_dashboard
cd creator_dashboard

Se recomienda configurar un entorno virtual:

python -m venv venv
source venv/bin/activate  # En macOS/Linux
venv\\Scripts\\activate  # En Windows

Instala los paquetes de Python necesarios:

pip install flask openai python-dotenv

Organiza los archivos de tu proyecto de la siguiente manera:

/creator_dashboard

├── app.py
├── .env
└── templates/
    └── dashboard.html
└── utils/
    ├── __init__.py
    ├── transcribe.py
    ├── summarize.py
    ├── generate_prompt.py
    └── generate_image.py
  • app.py: El archivo principal de la aplicación Flask.
  • .env: Un archivo para almacenar tu clave de API de OpenAI.
  • templates/: Un directorio para plantillas HTML.
  • templates/dashboard.html: La plantilla HTML para la interfaz de usuario.
  • utils/: Un directorio para módulos Python que contienen funciones reutilizables.
    • __init__.py: Convierte el directorio utils en un paquete Python.
    • transcribe.py: Contiene la función para transcribir audio usando Whisper.
    • summarize.py: Contiene la función para resumir la transcripción usando un Modelo de Lenguaje Grande.
    • generate_prompt.py: Contiene la función para generar un prompt de imagen a partir del resumen usando un Modelo de Lenguaje Grande.
    • generate_image.py: Contiene la función para generar una imagen con DALL·E 3.

Paso 2: Crear los Módulos de Utilidades

Crea los siguientes archivos Python en el directorio utils/:

utils/transcribe.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def transcribe_audio(file_path: str) -> Optional[str]:
    """
    Transcribes an audio file using OpenAI's Whisper API.

    Args:
        file_path (str): The path to the audio file.

    Returns:
        Optional[str]: The transcribed text, or None on error.
    """
    try:
        logger.info(f"Transcribing audio: {file_path}")
        audio_file = open(file_path, "rb")
        response = openai.Audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
        )
        transcript = response.text
        audio_file.close()
        return transcript
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error during transcription: {e}")
        return None
  • Este módulo define la función transcribe_audio, que toma como entrada la ruta de un archivo de audio y utiliza la API Whisper de OpenAI para generar una transcripción de texto.
  • La función abre el archivo de audio en modo lectura binaria ("rb").
  • Llama a openai.Audio.transcriptions.create() para realizar la transcripción, especificando el modelo "whisper-1".
  • Extrae el texto transcrito de la respuesta de la API.
  • Incluye manejo de errores usando un bloque try...except para capturar posibles excepciones openai.error.OpenAIError (específicas de OpenAI) y Exception general para otros errores. Si ocurre un error, registra el error y devuelve None.
  • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
  • El archivo de audio se cierra después de la transcripción.

utils/summarize.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def summarize_transcript(text: str) -> Optional[str]:
    """
    Summarizes a text transcript using OpenAI's Chat Completion API.

    Args:
        text (str): The text transcript to summarize.

    Returns:
        Optional[str]: The summarized text, or None on error.
    """
    try:
        logger.info("Summarizing transcript")
        response = openai.chat.completions.create(
            model="gpt-4o",
            messages=[
                {"role": "system",
                 "content": "You are a helpful assistant.  Provide a concise summary of the text, suitable for generating a visual representation."},
                {"role": "user", "content": text}
            ],
        )
        summary = response.choices[0].message.content
        logger.info(f"Summary: {summary}")
        return summary
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating summary: {e}")
        return None
  • Este módulo define la función summarize_transcript, que toma como entrada una transcripción de texto y utiliza la API de Chat Completion de OpenAI para generar un resumen conciso.
  • El mensaje del sistema indica al modelo que actúe como un asistente útil y proporcione un resumen conciso del texto, adecuado para generar una representación visual.
  • El mensaje del usuario proporciona la transcripción como contenido para que el modelo lo resuma.
  • La función extrae el resumen de la respuesta de la API.
  • Incluye manejo de errores.

utils/generate_prompt.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def create_image_prompt(transcription: str) -> Optional[str]:
    """
    Generates a detailed image prompt from a text transcription using OpenAI's Chat Completion API.

    Args:
        transcription (str): The text transcription of the audio.

    Returns:
        Optional[str]: A detailed text prompt suitable for image generation, or None on error.
    """
    try:
        logger.info("Generating image prompt from transcription")
        response = openai.chat.completions.create(
            model="gpt-4o",  # Use a powerful chat model
            messages=[
                {
                    "role": "system",
                    "content": "You are a creative assistant. Your task is to create a vivid and detailed text description of a scene that could be used to generate an image with an AI image generation model. Focus on capturing the essence and key visual elements of the audio content.  Do not include any phrases like 'based on the audio' or 'from the user audio'.  Incorporate scene lighting, time of day, weather, and camera angle into the description.  Limit the description to 200 words.",
                },
                {"role": "user", "content": transcription},
            ],
        )
        prompt = response.choices[0].message.content
        prompt = prompt.strip()  # Remove leading/trailing spaces
        logger.info(f"Generated prompt: {prompt}")
        return prompt
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image prompt: {e}")
        return None
  • Este módulo define la función create_image_prompt, que toma como entrada el texto transcrito y utiliza la API de Chat Completion de OpenAI para generar un texto detallado que sirva como prompt para la generación de imágenes.
  • El mensaje del sistema instruye al modelo para actuar como un asistente creativo y generar una descripción vívida de una escena. Este prompt del sistema es clave para guiar al modelo de lenguaje a producir un prompt de alta calidad. Se le indica al modelo que se enfoque en elementos visuales e incorpore detalles como la iluminación, la hora del día, el clima y el ángulo de cámara. También se limita la longitud de la descripción a 200 palabras.
  • El mensaje del usuario proporciona el texto transcrito como contenido con el que el modelo debe trabajar.
  • La función extrae el prompt generado de la respuesta de la API.
  • Elimina los espacios en blanco al inicio y al final del prompt generado.
  • Incluye manejo de errores.

utils/generate_image.py:

import openai
import logging
from typing import Optional, Dict

logger = logging.getLogger(__name__)


def generate_dalle_image(prompt: str, model: str = "dall-e-3", size: str = "1024x1024",
                       response_format: str = "url", quality: str = "standard") -> Optional[str]:
    """
    Generates an image using OpenAI's DALL·E API.

    Args:
        prompt (str): The text prompt to generate the image from.
        model (str, optional): The DALL·E model to use. Defaults to "dall-e-3".
        size (str, optional): The size of the generated image. Defaults to "1024x1024".
        response_format (str, optional): The format of the response. Defaults to "url".
        quality (str, optional): The quality of the image. Defaults to "standard".

    Returns:
        Optional[str]: The URL of the generated image, or None on error.
    """
    try:
        logger.info(f"Generating image with prompt: {prompt}, model: {model}, size: {size}, format: {response_format}, quality: {quality}")
        response = openai.images.generate(
            prompt=prompt,
            model=model,
            size=size,
            response_format=response_format,
            quality=quality
        )
        image_url = response.data[0].url
        logger.info(f"Image URL: {image_url}")
        return image_url
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image: {e}")
        return None
  • Este módulo define la función generate_dalle_image, que toma un texto descriptivo como entrada y utiliza la API DALL·E de OpenAI para generar una imagen.
  • Llama al método openai.images.generate() para generar la imagen.
  • Acepta parámetros opcionales de modelsizeresponse_format, y quality, permitiendo al usuario configurar la generación de la imagen.
  • Extrae la URL de la imagen generada desde la respuesta de la API.
  • Incluye manejo de errores.

Paso 5: Crear la Aplicación Principal (app.py)

Crea un archivo Python llamado app.py en el directorio raíz de tu proyecto y agrega el siguiente código:

from flask import Flask, request, render_template, jsonify, make_response, redirect, url_for
import os
from dotenv import load_dotenv
import logging
from typing import Optional, Dict
from werkzeug.utils import secure_filename
from werkzeug.datastructures import FileStorage

# Import the utility functions from the utils directory
from utils.transcribe import transcribe_audio
from utils.generate_prompt import create_image_prompt
from utils.generate_image import generate_dalle_image
from utils.summarize import summarize_transcript

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'  # Store uploaded files
app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024  # 25MB max file size - increased for larger audio files
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)  # Create the upload folder if it doesn't exist

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

ALLOWED_EXTENSIONS = {'mp3', 'mp4', 'wav', 'm4a'}  # Allowed audio file extensions


def allowed_file(filename: str) -> bool:
    """
    Checks if the uploaded file has an allowed extension.

    Args:
        filename (str): The name of the file.

    Returns:
        bool: True if the file has an allowed extension, False otherwise.
    """
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route("/", methods=["GET", "POST"])
def index():
    """
    Handles the main route for the web application.
    Processes audio uploads, transcribes them, generates image prompts, and displays images.
    """
    transcript = None
    image_url = None
    prompt_summary = None
    error_message = None
    summary = None # Initialize summary

    if request.method == "POST":
        if 'audio_file' not in request.files:
            error_message = "No file part"
            logger.warning(error_message)
            return render_template("index.html", error_message=error_message)

        file: FileStorage = request.files['audio_file']  # Use type hinting
        if file.filename == '':
            error_message = "No file selected"
            logger.warning(request)
            return render_template("index.html", error_message=error_message)

        if file and allowed_file(file.filename):
            try:
                # Secure the filename and construct a safe path
                filename = secure_filename(file.filename)
                file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
                file.save(file_path)  # Save the uploaded file

                transcript = transcribe_audio(file_path)  # Transcribe audio
                if not transcript:
                    error_message = "Audio transcription failed. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                summary = summarize_transcript(transcript) # Summarize the transcript
                if not summary:
                    error_message = "Audio summary failed. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                prompt_summary = generate_image_prompt(transcript)  # Generate prompt
                if not prompt_summary:
                    error_message = "Failed to generate image prompt. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                image_url = generate_dalle_image(prompt_summary, model=request.form.get('model', 'dall-e-3'),
                                                size=request.form.get('size', '1024x1024'),
                                                response_format=request.form.get('format', 'url'),
                                                quality=request.form.get('quality', 'standard'))  # Generate image
                if not image_url:
                    error_message = "Failed to generate image. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                # Optionally, delete the uploaded file after processing
                os.remove(file_path)
                logger.info(f"Successfully processed audio file and generated image.")
                return render_template("index.html", transcript=transcript, image_url=image_url, prompt=prompt_summary, summary=summary)

            except Exception as e:
                error_message = f"An error occurred: {e}"
                logger.error(error_message)
                return render_template("index.html",  error_message=error_message)
        else:
            error_message = "Invalid file type. Please upload a valid audio file (MP3, MP4, WAV, M4A)."
            logger.warning(request)
            return render_template("index.html",  error_message=error_message)

    return render_template("index.html", transcript=transcript, image_url=image_url, prompt=prompt_summary,
                           error=error_message, summary=summary)



@app.errorhandler(500)
def internal_server_error(e):
    """Handles internal server errors."""
    logger.error(f"Internal Server Error: {e}")
    return render_template("error.html", error="Internal Server Error"), 500


if __name__ == "__main__":
    app.run(debug=True)

Desglose del Código:

  • Declaraciones de Importación: Importa los módulos necesarios de Flask, la biblioteca OpenAI, osdotenvloggingOptional y Dict para la tipificación, y secure_filename y FileStorage de Werkzeug.
  • Variables de Entorno: Carga la clave API de OpenAI desde el archivo .env.
  • Aplicación Flask:
    • Crea una instancia de la aplicación Flask.
    • Configura una carpeta de carga y tamaño máximo de archivo. El UPLOAD_FOLDER se establece como 'uploads', y MAX_CONTENT_LENGTH se establece en 25MB. La carpeta de carga se crea si no existe.
  • Configuración de Registro: Configura el sistema de registro.
  • Función allowed_file: Verifica si el archivo cargado tiene una extensión de audio permitida.
  • Función transcribe_audio:
    • Toma la ruta del archivo de audio como entrada.
    • Abre el archivo de audio en modo de lectura binaria ("rb").
    • Llama al método openai.Audio.transcriptions.create() de la API de OpenAI para transcribir el audio.
    • Extrae el texto transcrito de la respuesta de la API.
    • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
    • Incluye manejo de errores para errores de la API de OpenAI y otras excepciones. El archivo de audio se cierra después de la transcripción.
  • Función generate_image_prompt:
    • Toma el texto transcrito como entrada.
    • Utiliza la API de Chat Completion de OpenAI (openai.chat.completions.create()) con el modelo gpt-4o para generar un prompt de texto detallado adecuado para la generación de imágenes.
    • El mensaje del sistema instruye al modelo para que actúe como un asistente creativo y proporcione una descripción vívida y detallada de una escena que podría usarse para generar una imagen con un modelo de generación de imágenes de IA. El prompt del sistema es crucial para guiar al LLM en la generación de un prompt de alta calidad. Instruimos al LLM para que se centre en elementos visuales e incorpore detalles como iluminación, hora del día, clima y ángulo de la cámara. También limitamos la longitud de la descripción a 200 palabras.
    • Extrae el prompt generado de la respuesta de la API.
    • Elimina los espacios iniciales/finales del prompt generado.
    • Incluye manejo de errores.
  • Función generate_image:
    • Toma el prompt de imagen como entrada.
    • Llama al método openai.Image.create() de la API de OpenAI para generar una imagen usando DALL·E 3.
    • Acepta parámetros opcionales de modelsizeresponse_format, y quality, permitiendo al usuario configurar la generación de la imagen.
    • Extrae la URL de la imagen generada de la respuesta de la API.
    • Incluye manejo de errores.
  • Ruta index:
    • Maneja solicitudes GET y POST.
    • Para solicitudes GET, renderiza la página HTML inicial.
    • Para solicitudes POST (cuando el usuario carga un archivo de audio):
      • Valida el archivo cargado:
        • Verifica si existe la parte del archivo en la solicitud.
        • Verifica si se seleccionó un archivo.
        • Verifica si el tipo de archivo está permitido usando la función allowed_file.
      • Guarda el archivo cargado en una ubicación temporal usando un nombre de archivo seguro.
      • Llama a las funciones de utilidad para:
        • Transcribir el audio usando transcribe_audio().
        • Generar un prompt de imagen a partir de la transcripción usando create_image_prompt().
        • Generar una imagen a partir del prompt usando generate_dalle_image().
      • Resume la transcripción usando la api de chat completions de openai.
      • Maneja los errores que puedan ocurrir durante cualquiera de estos pasos, registrando el error y renderizando la plantilla index.html con un mensaje de error apropiado. El archivo temporal se elimina antes de renderizar la página de error.
      • Si todos los pasos son exitosos, renderiza la plantilla index.html, pasando el texto de transcripción, URL de la imagen y prompt generado para ser mostrados.
  • @app.errorhandler(500): Maneja errores HTTP 500 (Error Interno del Servidor) registrando el error y renderizando una página de error amigable para el usuario.
  • if __name__ == "__main__":: Inicia el servidor de desarrollo Flask si el script se ejecuta directamente.

Paso 6: Crear la Plantilla HTML (templates/dashboard.html)

Crea una carpeta llamada templates en el mismo directorio que app.py. Dentro de la carpeta templates, crea un archivo llamado dashboard.html con el siguiente código HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Creator Dashboard</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
    <style>
        /* --- General Styles --- */
        body {
            font-family: 'Inter', sans-serif;
            padding: 40px;
            background-color: #f9fafb; /* Tailwind's gray-50 */
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            color: #374151; /* Tailwind's gray-700 */
        }
        .container {
            max-width: 800px; /* Increased max-width */
            width: 95%; /* Take up most of the viewport */
            background-color: #fff;
            padding: 2rem;
            border-radius: 0.75rem; /* Tailwind's rounded-lg */
            box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.05); /* Tailwind's shadow-xl */
            text-align: center;
        }
        h2 {
            font-size: 2.25rem; /* Tailwind's text-3xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1.5rem; /* Tailwind's mb-6 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        p{
            color: #6b7280; /* Tailwind's gray-500 */
            margin-bottom: 1rem;
        }

        /* --- Form Styles --- */
        form {
            margin-top: 1rem; /* Tailwind's mt-4 */
            margin-bottom: 1.5rem;
            display: flex;
            flex-direction: column;
            align-items: center; /* Center form elements */
            gap: 0.5rem; /* Tailwind's gap-2 */
        }
        label {
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600;  /* Tailwind's font-semibold */
            color: #4b5563; /* Tailwind's gray-600 */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            display: block; /* Ensure label takes full width */
            text-align: left;
            width: 100%;
            max-width: 400px; /* Added max-width for label */
            margin-left: auto;
            margin-right: auto;
        }
        input[type="file"] {
            width: 100%;
            max-width: 400px; /* Added max-width for file input */
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            font-size: 1rem; /* Tailwind's text-base */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            margin-left: auto;
            margin-right: auto;
        }
        input[type="submit"] {
            padding: 0.75rem 1.5rem; /* Tailwind's px-6 py-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #4f46e5; /* Tailwind's bg-indigo-500 */
            color: #fff;
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600; /* Tailwind's font-semibold */
            cursor: pointer;
            transition: background-color 0.3s ease; /* Smooth transition */
            border: none;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Subtle shadow */
            margin-top: 1rem;
        }
        input[type="submit"]:hover {
            background-color: #4338ca; /* Tailwind's bg-indigo-700 on hover */
        }
        input[type="submit"]:focus {
            outline: none;
            box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3); /* Tailwind's ring-indigo-500 */
        }

        /* --- Result Styles --- */
        .result-container {
            margin-top: 2rem; /* Tailwind's mt-8 */
            padding: 1.5rem; /* Tailwind's p-6 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #f8fafc; /* Tailwind's bg-gray-50 */
            border: 1px solid #e2e8f0; /* Tailwind's border-gray-200 */
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */
            text-align: left;
        }
        h3 {
            font-size: 1.5rem; /* Tailwind's text-2xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1rem; /* Tailwind's mb-4 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        textarea {
            width: 100%;
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            resize: none;
            font-size: 1rem; /* Tailwind's text-base */
            line-height: 1.5rem; /* Tailwind's leading-relaxed */
            margin-top: 0.5rem; /* Tailwind's mt-2 */
            margin-bottom: 0;
            box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); /* Inner shadow */
            min-height: 100px;
        }
        textarea:focus {
            outline: none;
            border-color: #3b82f6; /* Tailwind's border-blue-500 */
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); /* Tailwind's ring-blue-500 */
        }
        img {
            max-width: 100%;
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            margin-top: 1.5rem; /* Tailwind's mt-6 */
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* Tailwind's shadow-md */
        }

        /* --- Error Styles --- */
        .error-message {
            color: #dc2626; /* Tailwind's text-red-600 */
            margin-top: 1rem; /* Tailwind's mt-4 */
            padding: 0.75rem;
            background-color: #fee2e2; /* Tailwind's bg-red-100 */
            border-radius: 0.375rem; /* Tailwind's rounded-md */
            border: 1px solid #fecaca; /* Tailwind's border-red-300 */
            text-align: center;
        }

        .prompt-select {
            margin-top: 1rem; /* Tailwind's mt-4 */
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 0.5rem;
            width: 100%;
        }

        .prompt-select label {
            font-size: 1rem;
            font-weight: 600;
            color: #4b5563;
            margin-bottom: 0.25rem;
            display: block; /* Ensure label takes full width */
            text-align: left;
            width: 100%;
            max-width: 400px; /* Added max-width for label */
            margin-left: auto;
            margin-right: auto;
        }

        .prompt-select select {
            width: 100%;
            max-width: 400px;
            padding: 0.75rem;
            border-radius: 0.5rem;
            border: 1px solid #d1d5db;
            font-size: 1rem;
            margin-bottom: 0.25rem;
            margin-left: auto;
            margin-right: auto;
            appearance: none;  /* Remove default arrow */
            background-image: url("data:image/svg+xml,%3Csvgxmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l4 4 4-4'%3E%3C/path%3E%3C/svg%3E"); /* Add custom arrow */
            background-repeat: no-repeat;
            background-position: right 0.75rem center;
            background-size: 1rem;
            padding-right: 2.5rem; /* Make space for the arrow */
        }

        .prompt-select select:focus {
            outline: none;
            border-color: #3b82f6;
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
        }


    </style>
</head>
<body>
    <div class="container">
        <h2>🎤🧠🎨 Multimodal Assistant</h2>
        <p> Upload an audio file to transcribe and generate a corresponding image. </p>
        <form method="POST" enctype="multipart/form-data">
            <label for="audio_file">Upload your voice note:</label><br>
            <input type="file" name="audio_file" accept="audio/*" required><br><br>

            <div class = "prompt-select">
                <label for="prompt_mode">Image Prompt Mode:</label>
                <select id="prompt_mode" name="prompt_mode">
                    <option value="detailed">Detailed Scene Description</option>
                    <option value="keywords">Keywords</option>
                    <option value="creative">Creative Interpretation</option>
                </select>
            </div>

            <input type="submit" value="Generate Visual Response">
        </form>

        {% if transcript %}
            <div class="result-container">
                <h3>📝 Transcript:</h3>
                <textarea readonly>{{ transcript }}</textarea>
            </div>
        {% endif %}

        {% if summary %}
            <div class="result-container">
                <h3>🔎 Summary:</h3>
                <p>{{ summary }}</p>
            </div>
        {% endif %}

        {% if prompt %}
            <div class="result-container">
                <h3>🎯 Scene Prompt:</h3>
                <p>{{ prompt }}</p>
            </div>
        {% endif %}

        {% if image_url %}
            <div class="result-container">
                <h3>🖼️ Generated Image:</h3>
                <img src="{{ image_url }}" alt="Generated image">
            </div>
        {% endif %}
        {% if error %}
            <div class="error-message">{{ error }}</div>
        {% endif %}
    </div>
</body>
</html>

6.2.3 ¿Qué hace que esto sea un Panel de Control?

Este diseño combina varios elementos clave que lo transforman de una interfaz simple a un panel de control integral:

  • Múltiples zonas de salida (texto, resumen, instrucción, imagen)La interfaz está dividida en secciones distintas, cada una dedicada a mostrar diferentes tipos de datos procesados. Esta organización permite a los usuarios seguir fácilmente la progresión desde la entrada de voz hasta la salida visual.
  • Interacción simple del usuario (procesamiento con un solo clic)A pesar del complejo procesamiento que ocurre tras bambalinas, los usuarios solo necesitan realizar una acción para iniciar todo el flujo de trabajo. Esta simplicidad hace que la herramienta sea accesible para usuarios de todos los niveles técnicos.
  • Formato limpio y legibleLa interfaz utiliza espaciado, tipografía y jerarquía visual consistentes para asegurar que la información sea fácilmente digerible. Cada sección está claramente etiquetada y visualmente separada de las demás.
  • Retroalimentación visual para reforzar la salida del modeloEl panel proporciona confirmación visual inmediata en cada paso del proceso, ayudando a los usuarios a entender cómo su entrada se está transformando a través de diferentes modelos de IA.
  • Arquitectura reutilizable, gracias a la estructura utils/El diseño modular separa la funcionalidad central en funciones de utilidad, haciendo que el código sea más fácil de mantener y adaptar para diferentes casos de uso.

6.2.4 Ideas de Casos de Uso

Este versátil panel tiene numerosas aplicaciones potenciales. Exploremos algunos casos de uso clave en detalle:

  • Un kit de herramientas de IA para creadores de contenido (convertir pensamientos en blogs + visuales)
    • Grabar sesiones de lluvia de ideas y convertirlas en publicaciones de blog estructuradas
    • Generar ilustraciones correspondientes para conceptos clave
    • Crear paquetes de contenido para redes sociales con visuales coincidentes
  • Un asistente de profesor (grabar voz ➝ resumir ➝ ilustrar)
    • Transformar planes de lección en materiales de aprendizaje visual
    • Crear contenido educativo atractivo con ilustraciones correspondientes
    • Generar ayudas visuales para conceptos complejos
  • Una herramienta de diario (registrar entradas de voz ➝ resumir + visualizar)
    • Convertir memorandos de voz diarios en entradas escritas organizadas
    • Crear tableros de estado de ánimo basados en el contenido del diario
    • Rastrear patrones emocionales a través de representaciones visuales

Resumen

En esta sección, has elevado tu asistente multimodal a un panel de control de nivel profesional. Esto es lo que has logrado:

  • Desglosar tu lógica en utilidades reutilizables
    • Creada estructura de código modular y mantenible
    • Implementada clara separación de responsabilidades
  • Aceptar entrada de audio y procesarla a través de modelos
    • Integración perfecta de múltiples tecnologías de IA
    • Pipeline de procesamiento eficiente
  • Presentar todo claramente en una IU cohesiva
    • Diseño de interfaz amigable para el usuario
    • Jerarquía de información intuitiva
  • Pasar de "demo" a herramienta
    • Implementación lista para producción
    • Arquitectura escalable

Este panel representa una interfaz de nivel profesional que ofrece valor real a los usuarios. Con su arquitectura robusta y diseño intuitivo, está listo para transformarse en un producto completo con un desarrollo adicional mínimo.

6.2 Construyendo un Panel de Control para Creadores

Aquí es donde todas las capacidades que has desarrollado hasta ahora se unen para crear un sistema potente y unificado. Al integrar múltiples tecnologías de IA, podemos crear aplicaciones que son más que la suma de sus partes. Exploremos estas capacidades fundamentales en detalle:

  • La transcripción convierte las palabras habladas en texto escritoUsando modelos avanzados de reconocimiento de voz como Whisper, podemos convertir con precisión grabaciones de audio en texto, preservando la intención y el contexto del hablante. Esto forma la base para el procesamiento posterior.
  • La generación de contenido crea material nuevo y contextualmente relevanteLos modelos de lenguaje grandes pueden analizar el texto transcrito y generar nuevo contenido que mantiene la consistencia con el mensaje original mientras añade ideas valiosas o expande puntos clave.
  • La ingeniería de prompts elabora instrucciones precisas para modelos de IAA través de una construcción cuidadosa de prompts, podemos guiar a los modelos de IA para producir resultados más precisos y relevantes. Esto implica entender tanto las capacidades técnicas de los modelos como las formas matizadas de comunicarse con ellos.
  • La creación de imágenes transforma descripciones textuales en arte visualLos modelos como DALL·E pueden interpretar descripciones textuales y crear imágenes correspondientes, añadiendo una dimensión visual a nuestras aplicaciones y haciendo más tangibles los conceptos abstractos.

Estos componentes no solo existen uno al lado del otro - forman un pipeline interconectado donde cada paso mejora el siguiente. La salida de la transcripción alimenta la generación de contenido, que informa la ingeniería de prompts, llevando finalmente a la creación de imágenes. Esta integración perfecta crea un flujo de trabajo fluido donde los usuarios pueden comenzar con una simple grabación de voz y terminar con una salida multimedia rica, todo dentro de un sistema único y cohesivo. Al eliminar la necesidad de cambiar entre diferentes herramientas o interfaces, los usuarios pueden concentrarse en su proceso creativo en lugar de los detalles de implementación técnica.

6.2.1 Lo que vas a construir

En esta sección, diseñarás e implementarás un Panel de Control para Creadores - una interfaz web sofisticada que transforma la forma en que los creadores trabajan con la IA. Esta plataforma integral sirve como centro neurálgico para la creación de contenido, combinando múltiples tecnologías de IA en una experiencia fluida. Exploremos las características clave que hacen poderoso este panel:

  • Subir una grabación de voz
    Los creadores pueden subir fácilmente archivos de audio en varios formatos, simplificando el proceso de iniciar su proceso creativo con ideas o narraciones habladas.
  • Transcribir la grabación de voz a texto usando IA
    Utilizando tecnología avanzada de reconocimiento de voz por IA, el sistema convierte con precisión las palabras habladas en texto escrito, manteniendo los matices y el contexto de la grabación original.
  • Convertir esa transcripción en un prompt editable
    El sistema procesa de manera inteligente el texto transcrito para crear prompts estructurados y preparados para IA que pueden personalizarse para lograr el resultado creativo deseado.
  • Generar imágenes usando DALL·E basadas en el prompt
    Aprovechando las potentes capacidades de generación de imágenes de DALL·E, el sistema crea representaciones visuales que coinciden con los prompts especificados, dando vida a las ideas a través de obras de arte generadas por IA.
  • Resumir la transcripción
    El panel utiliza IA para destilar transcripciones largas en resúmenes concisos y significativos, ayudando a los creadores a captar rápidamente los conceptos y temas principales.
  • Mostrar todos los resultados para revisión y uso posterior en la producción de contenido
    Todo el contenido generado - desde transcripciones hasta imágenes - se presenta en un formato organizado y fácil de revisar, permitiendo a los creadores gestionar y utilizar sus recursos de manera eficiente.

Para construir este sistema robusto, implementarás un stack tecnológico moderno usando Flask para las operaciones del backend y una combinación limpia y adaptable de HTML y CSS para la interfaz del frontend. Esta arquitectura garantiza tanto la modularidad como la mantenibilidad, facilitando la actualización y escalabilidad del panel según sea necesario.

6.2.2 Implementación Paso a Paso

Paso 1: Configuración del Proyecto

Descarga el ejemplo de audio: https://files.cuantum.tech/audio/dashboard-project.mp3

Crea un nuevo directorio para tu proyecto y navega hacia él:

mkdir creator_dashboard
cd creator_dashboard

Se recomienda configurar un entorno virtual:

python -m venv venv
source venv/bin/activate  # En macOS/Linux
venv\\Scripts\\activate  # En Windows

Instala los paquetes de Python necesarios:

pip install flask openai python-dotenv

Organiza los archivos de tu proyecto de la siguiente manera:

/creator_dashboard

├── app.py
├── .env
└── templates/
    └── dashboard.html
└── utils/
    ├── __init__.py
    ├── transcribe.py
    ├── summarize.py
    ├── generate_prompt.py
    └── generate_image.py
  • app.py: El archivo principal de la aplicación Flask.
  • .env: Un archivo para almacenar tu clave de API de OpenAI.
  • templates/: Un directorio para plantillas HTML.
  • templates/dashboard.html: La plantilla HTML para la interfaz de usuario.
  • utils/: Un directorio para módulos Python que contienen funciones reutilizables.
    • __init__.py: Convierte el directorio utils en un paquete Python.
    • transcribe.py: Contiene la función para transcribir audio usando Whisper.
    • summarize.py: Contiene la función para resumir la transcripción usando un Modelo de Lenguaje Grande.
    • generate_prompt.py: Contiene la función para generar un prompt de imagen a partir del resumen usando un Modelo de Lenguaje Grande.
    • generate_image.py: Contiene la función para generar una imagen con DALL·E 3.

Paso 2: Crear los Módulos de Utilidades

Crea los siguientes archivos Python en el directorio utils/:

utils/transcribe.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def transcribe_audio(file_path: str) -> Optional[str]:
    """
    Transcribes an audio file using OpenAI's Whisper API.

    Args:
        file_path (str): The path to the audio file.

    Returns:
        Optional[str]: The transcribed text, or None on error.
    """
    try:
        logger.info(f"Transcribing audio: {file_path}")
        audio_file = open(file_path, "rb")
        response = openai.Audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
        )
        transcript = response.text
        audio_file.close()
        return transcript
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error during transcription: {e}")
        return None
  • Este módulo define la función transcribe_audio, que toma como entrada la ruta de un archivo de audio y utiliza la API Whisper de OpenAI para generar una transcripción de texto.
  • La función abre el archivo de audio en modo lectura binaria ("rb").
  • Llama a openai.Audio.transcriptions.create() para realizar la transcripción, especificando el modelo "whisper-1".
  • Extrae el texto transcrito de la respuesta de la API.
  • Incluye manejo de errores usando un bloque try...except para capturar posibles excepciones openai.error.OpenAIError (específicas de OpenAI) y Exception general para otros errores. Si ocurre un error, registra el error y devuelve None.
  • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
  • El archivo de audio se cierra después de la transcripción.

utils/summarize.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def summarize_transcript(text: str) -> Optional[str]:
    """
    Summarizes a text transcript using OpenAI's Chat Completion API.

    Args:
        text (str): The text transcript to summarize.

    Returns:
        Optional[str]: The summarized text, or None on error.
    """
    try:
        logger.info("Summarizing transcript")
        response = openai.chat.completions.create(
            model="gpt-4o",
            messages=[
                {"role": "system",
                 "content": "You are a helpful assistant.  Provide a concise summary of the text, suitable for generating a visual representation."},
                {"role": "user", "content": text}
            ],
        )
        summary = response.choices[0].message.content
        logger.info(f"Summary: {summary}")
        return summary
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating summary: {e}")
        return None
  • Este módulo define la función summarize_transcript, que toma como entrada una transcripción de texto y utiliza la API de Chat Completion de OpenAI para generar un resumen conciso.
  • El mensaje del sistema indica al modelo que actúe como un asistente útil y proporcione un resumen conciso del texto, adecuado para generar una representación visual.
  • El mensaje del usuario proporciona la transcripción como contenido para que el modelo lo resuma.
  • La función extrae el resumen de la respuesta de la API.
  • Incluye manejo de errores.

utils/generate_prompt.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def create_image_prompt(transcription: str) -> Optional[str]:
    """
    Generates a detailed image prompt from a text transcription using OpenAI's Chat Completion API.

    Args:
        transcription (str): The text transcription of the audio.

    Returns:
        Optional[str]: A detailed text prompt suitable for image generation, or None on error.
    """
    try:
        logger.info("Generating image prompt from transcription")
        response = openai.chat.completions.create(
            model="gpt-4o",  # Use a powerful chat model
            messages=[
                {
                    "role": "system",
                    "content": "You are a creative assistant. Your task is to create a vivid and detailed text description of a scene that could be used to generate an image with an AI image generation model. Focus on capturing the essence and key visual elements of the audio content.  Do not include any phrases like 'based on the audio' or 'from the user audio'.  Incorporate scene lighting, time of day, weather, and camera angle into the description.  Limit the description to 200 words.",
                },
                {"role": "user", "content": transcription},
            ],
        )
        prompt = response.choices[0].message.content
        prompt = prompt.strip()  # Remove leading/trailing spaces
        logger.info(f"Generated prompt: {prompt}")
        return prompt
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image prompt: {e}")
        return None
  • Este módulo define la función create_image_prompt, que toma como entrada el texto transcrito y utiliza la API de Chat Completion de OpenAI para generar un texto detallado que sirva como prompt para la generación de imágenes.
  • El mensaje del sistema instruye al modelo para actuar como un asistente creativo y generar una descripción vívida de una escena. Este prompt del sistema es clave para guiar al modelo de lenguaje a producir un prompt de alta calidad. Se le indica al modelo que se enfoque en elementos visuales e incorpore detalles como la iluminación, la hora del día, el clima y el ángulo de cámara. También se limita la longitud de la descripción a 200 palabras.
  • El mensaje del usuario proporciona el texto transcrito como contenido con el que el modelo debe trabajar.
  • La función extrae el prompt generado de la respuesta de la API.
  • Elimina los espacios en blanco al inicio y al final del prompt generado.
  • Incluye manejo de errores.

utils/generate_image.py:

import openai
import logging
from typing import Optional, Dict

logger = logging.getLogger(__name__)


def generate_dalle_image(prompt: str, model: str = "dall-e-3", size: str = "1024x1024",
                       response_format: str = "url", quality: str = "standard") -> Optional[str]:
    """
    Generates an image using OpenAI's DALL·E API.

    Args:
        prompt (str): The text prompt to generate the image from.
        model (str, optional): The DALL·E model to use. Defaults to "dall-e-3".
        size (str, optional): The size of the generated image. Defaults to "1024x1024".
        response_format (str, optional): The format of the response. Defaults to "url".
        quality (str, optional): The quality of the image. Defaults to "standard".

    Returns:
        Optional[str]: The URL of the generated image, or None on error.
    """
    try:
        logger.info(f"Generating image with prompt: {prompt}, model: {model}, size: {size}, format: {response_format}, quality: {quality}")
        response = openai.images.generate(
            prompt=prompt,
            model=model,
            size=size,
            response_format=response_format,
            quality=quality
        )
        image_url = response.data[0].url
        logger.info(f"Image URL: {image_url}")
        return image_url
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image: {e}")
        return None
  • Este módulo define la función generate_dalle_image, que toma un texto descriptivo como entrada y utiliza la API DALL·E de OpenAI para generar una imagen.
  • Llama al método openai.images.generate() para generar la imagen.
  • Acepta parámetros opcionales de modelsizeresponse_format, y quality, permitiendo al usuario configurar la generación de la imagen.
  • Extrae la URL de la imagen generada desde la respuesta de la API.
  • Incluye manejo de errores.

Paso 5: Crear la Aplicación Principal (app.py)

Crea un archivo Python llamado app.py en el directorio raíz de tu proyecto y agrega el siguiente código:

from flask import Flask, request, render_template, jsonify, make_response, redirect, url_for
import os
from dotenv import load_dotenv
import logging
from typing import Optional, Dict
from werkzeug.utils import secure_filename
from werkzeug.datastructures import FileStorage

# Import the utility functions from the utils directory
from utils.transcribe import transcribe_audio
from utils.generate_prompt import create_image_prompt
from utils.generate_image import generate_dalle_image
from utils.summarize import summarize_transcript

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'  # Store uploaded files
app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024  # 25MB max file size - increased for larger audio files
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)  # Create the upload folder if it doesn't exist

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

ALLOWED_EXTENSIONS = {'mp3', 'mp4', 'wav', 'm4a'}  # Allowed audio file extensions


def allowed_file(filename: str) -> bool:
    """
    Checks if the uploaded file has an allowed extension.

    Args:
        filename (str): The name of the file.

    Returns:
        bool: True if the file has an allowed extension, False otherwise.
    """
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route("/", methods=["GET", "POST"])
def index():
    """
    Handles the main route for the web application.
    Processes audio uploads, transcribes them, generates image prompts, and displays images.
    """
    transcript = None
    image_url = None
    prompt_summary = None
    error_message = None
    summary = None # Initialize summary

    if request.method == "POST":
        if 'audio_file' not in request.files:
            error_message = "No file part"
            logger.warning(error_message)
            return render_template("index.html", error_message=error_message)

        file: FileStorage = request.files['audio_file']  # Use type hinting
        if file.filename == '':
            error_message = "No file selected"
            logger.warning(request)
            return render_template("index.html", error_message=error_message)

        if file and allowed_file(file.filename):
            try:
                # Secure the filename and construct a safe path
                filename = secure_filename(file.filename)
                file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
                file.save(file_path)  # Save the uploaded file

                transcript = transcribe_audio(file_path)  # Transcribe audio
                if not transcript:
                    error_message = "Audio transcription failed. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                summary = summarize_transcript(transcript) # Summarize the transcript
                if not summary:
                    error_message = "Audio summary failed. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                prompt_summary = generate_image_prompt(transcript)  # Generate prompt
                if not prompt_summary:
                    error_message = "Failed to generate image prompt. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                image_url = generate_dalle_image(prompt_summary, model=request.form.get('model', 'dall-e-3'),
                                                size=request.form.get('size', '1024x1024'),
                                                response_format=request.form.get('format', 'url'),
                                                quality=request.form.get('quality', 'standard'))  # Generate image
                if not image_url:
                    error_message = "Failed to generate image. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                # Optionally, delete the uploaded file after processing
                os.remove(file_path)
                logger.info(f"Successfully processed audio file and generated image.")
                return render_template("index.html", transcript=transcript, image_url=image_url, prompt=prompt_summary, summary=summary)

            except Exception as e:
                error_message = f"An error occurred: {e}"
                logger.error(error_message)
                return render_template("index.html",  error_message=error_message)
        else:
            error_message = "Invalid file type. Please upload a valid audio file (MP3, MP4, WAV, M4A)."
            logger.warning(request)
            return render_template("index.html",  error_message=error_message)

    return render_template("index.html", transcript=transcript, image_url=image_url, prompt=prompt_summary,
                           error=error_message, summary=summary)



@app.errorhandler(500)
def internal_server_error(e):
    """Handles internal server errors."""
    logger.error(f"Internal Server Error: {e}")
    return render_template("error.html", error="Internal Server Error"), 500


if __name__ == "__main__":
    app.run(debug=True)

Desglose del Código:

  • Declaraciones de Importación: Importa los módulos necesarios de Flask, la biblioteca OpenAI, osdotenvloggingOptional y Dict para la tipificación, y secure_filename y FileStorage de Werkzeug.
  • Variables de Entorno: Carga la clave API de OpenAI desde el archivo .env.
  • Aplicación Flask:
    • Crea una instancia de la aplicación Flask.
    • Configura una carpeta de carga y tamaño máximo de archivo. El UPLOAD_FOLDER se establece como 'uploads', y MAX_CONTENT_LENGTH se establece en 25MB. La carpeta de carga se crea si no existe.
  • Configuración de Registro: Configura el sistema de registro.
  • Función allowed_file: Verifica si el archivo cargado tiene una extensión de audio permitida.
  • Función transcribe_audio:
    • Toma la ruta del archivo de audio como entrada.
    • Abre el archivo de audio en modo de lectura binaria ("rb").
    • Llama al método openai.Audio.transcriptions.create() de la API de OpenAI para transcribir el audio.
    • Extrae el texto transcrito de la respuesta de la API.
    • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
    • Incluye manejo de errores para errores de la API de OpenAI y otras excepciones. El archivo de audio se cierra después de la transcripción.
  • Función generate_image_prompt:
    • Toma el texto transcrito como entrada.
    • Utiliza la API de Chat Completion de OpenAI (openai.chat.completions.create()) con el modelo gpt-4o para generar un prompt de texto detallado adecuado para la generación de imágenes.
    • El mensaje del sistema instruye al modelo para que actúe como un asistente creativo y proporcione una descripción vívida y detallada de una escena que podría usarse para generar una imagen con un modelo de generación de imágenes de IA. El prompt del sistema es crucial para guiar al LLM en la generación de un prompt de alta calidad. Instruimos al LLM para que se centre en elementos visuales e incorpore detalles como iluminación, hora del día, clima y ángulo de la cámara. También limitamos la longitud de la descripción a 200 palabras.
    • Extrae el prompt generado de la respuesta de la API.
    • Elimina los espacios iniciales/finales del prompt generado.
    • Incluye manejo de errores.
  • Función generate_image:
    • Toma el prompt de imagen como entrada.
    • Llama al método openai.Image.create() de la API de OpenAI para generar una imagen usando DALL·E 3.
    • Acepta parámetros opcionales de modelsizeresponse_format, y quality, permitiendo al usuario configurar la generación de la imagen.
    • Extrae la URL de la imagen generada de la respuesta de la API.
    • Incluye manejo de errores.
  • Ruta index:
    • Maneja solicitudes GET y POST.
    • Para solicitudes GET, renderiza la página HTML inicial.
    • Para solicitudes POST (cuando el usuario carga un archivo de audio):
      • Valida el archivo cargado:
        • Verifica si existe la parte del archivo en la solicitud.
        • Verifica si se seleccionó un archivo.
        • Verifica si el tipo de archivo está permitido usando la función allowed_file.
      • Guarda el archivo cargado en una ubicación temporal usando un nombre de archivo seguro.
      • Llama a las funciones de utilidad para:
        • Transcribir el audio usando transcribe_audio().
        • Generar un prompt de imagen a partir de la transcripción usando create_image_prompt().
        • Generar una imagen a partir del prompt usando generate_dalle_image().
      • Resume la transcripción usando la api de chat completions de openai.
      • Maneja los errores que puedan ocurrir durante cualquiera de estos pasos, registrando el error y renderizando la plantilla index.html con un mensaje de error apropiado. El archivo temporal se elimina antes de renderizar la página de error.
      • Si todos los pasos son exitosos, renderiza la plantilla index.html, pasando el texto de transcripción, URL de la imagen y prompt generado para ser mostrados.
  • @app.errorhandler(500): Maneja errores HTTP 500 (Error Interno del Servidor) registrando el error y renderizando una página de error amigable para el usuario.
  • if __name__ == "__main__":: Inicia el servidor de desarrollo Flask si el script se ejecuta directamente.

Paso 6: Crear la Plantilla HTML (templates/dashboard.html)

Crea una carpeta llamada templates en el mismo directorio que app.py. Dentro de la carpeta templates, crea un archivo llamado dashboard.html con el siguiente código HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Creator Dashboard</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
    <style>
        /* --- General Styles --- */
        body {
            font-family: 'Inter', sans-serif;
            padding: 40px;
            background-color: #f9fafb; /* Tailwind's gray-50 */
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            color: #374151; /* Tailwind's gray-700 */
        }
        .container {
            max-width: 800px; /* Increased max-width */
            width: 95%; /* Take up most of the viewport */
            background-color: #fff;
            padding: 2rem;
            border-radius: 0.75rem; /* Tailwind's rounded-lg */
            box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.05); /* Tailwind's shadow-xl */
            text-align: center;
        }
        h2 {
            font-size: 2.25rem; /* Tailwind's text-3xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1.5rem; /* Tailwind's mb-6 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        p{
            color: #6b7280; /* Tailwind's gray-500 */
            margin-bottom: 1rem;
        }

        /* --- Form Styles --- */
        form {
            margin-top: 1rem; /* Tailwind's mt-4 */
            margin-bottom: 1.5rem;
            display: flex;
            flex-direction: column;
            align-items: center; /* Center form elements */
            gap: 0.5rem; /* Tailwind's gap-2 */
        }
        label {
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600;  /* Tailwind's font-semibold */
            color: #4b5563; /* Tailwind's gray-600 */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            display: block; /* Ensure label takes full width */
            text-align: left;
            width: 100%;
            max-width: 400px; /* Added max-width for label */
            margin-left: auto;
            margin-right: auto;
        }
        input[type="file"] {
            width: 100%;
            max-width: 400px; /* Added max-width for file input */
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            font-size: 1rem; /* Tailwind's text-base */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            margin-left: auto;
            margin-right: auto;
        }
        input[type="submit"] {
            padding: 0.75rem 1.5rem; /* Tailwind's px-6 py-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #4f46e5; /* Tailwind's bg-indigo-500 */
            color: #fff;
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600; /* Tailwind's font-semibold */
            cursor: pointer;
            transition: background-color 0.3s ease; /* Smooth transition */
            border: none;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Subtle shadow */
            margin-top: 1rem;
        }
        input[type="submit"]:hover {
            background-color: #4338ca; /* Tailwind's bg-indigo-700 on hover */
        }
        input[type="submit"]:focus {
            outline: none;
            box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3); /* Tailwind's ring-indigo-500 */
        }

        /* --- Result Styles --- */
        .result-container {
            margin-top: 2rem; /* Tailwind's mt-8 */
            padding: 1.5rem; /* Tailwind's p-6 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #f8fafc; /* Tailwind's bg-gray-50 */
            border: 1px solid #e2e8f0; /* Tailwind's border-gray-200 */
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */
            text-align: left;
        }
        h3 {
            font-size: 1.5rem; /* Tailwind's text-2xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1rem; /* Tailwind's mb-4 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        textarea {
            width: 100%;
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            resize: none;
            font-size: 1rem; /* Tailwind's text-base */
            line-height: 1.5rem; /* Tailwind's leading-relaxed */
            margin-top: 0.5rem; /* Tailwind's mt-2 */
            margin-bottom: 0;
            box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); /* Inner shadow */
            min-height: 100px;
        }
        textarea:focus {
            outline: none;
            border-color: #3b82f6; /* Tailwind's border-blue-500 */
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); /* Tailwind's ring-blue-500 */
        }
        img {
            max-width: 100%;
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            margin-top: 1.5rem; /* Tailwind's mt-6 */
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* Tailwind's shadow-md */
        }

        /* --- Error Styles --- */
        .error-message {
            color: #dc2626; /* Tailwind's text-red-600 */
            margin-top: 1rem; /* Tailwind's mt-4 */
            padding: 0.75rem;
            background-color: #fee2e2; /* Tailwind's bg-red-100 */
            border-radius: 0.375rem; /* Tailwind's rounded-md */
            border: 1px solid #fecaca; /* Tailwind's border-red-300 */
            text-align: center;
        }

        .prompt-select {
            margin-top: 1rem; /* Tailwind's mt-4 */
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 0.5rem;
            width: 100%;
        }

        .prompt-select label {
            font-size: 1rem;
            font-weight: 600;
            color: #4b5563;
            margin-bottom: 0.25rem;
            display: block; /* Ensure label takes full width */
            text-align: left;
            width: 100%;
            max-width: 400px; /* Added max-width for label */
            margin-left: auto;
            margin-right: auto;
        }

        .prompt-select select {
            width: 100%;
            max-width: 400px;
            padding: 0.75rem;
            border-radius: 0.5rem;
            border: 1px solid #d1d5db;
            font-size: 1rem;
            margin-bottom: 0.25rem;
            margin-left: auto;
            margin-right: auto;
            appearance: none;  /* Remove default arrow */
            background-image: url("data:image/svg+xml,%3Csvgxmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l4 4 4-4'%3E%3C/path%3E%3C/svg%3E"); /* Add custom arrow */
            background-repeat: no-repeat;
            background-position: right 0.75rem center;
            background-size: 1rem;
            padding-right: 2.5rem; /* Make space for the arrow */
        }

        .prompt-select select:focus {
            outline: none;
            border-color: #3b82f6;
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
        }


    </style>
</head>
<body>
    <div class="container">
        <h2>🎤🧠🎨 Multimodal Assistant</h2>
        <p> Upload an audio file to transcribe and generate a corresponding image. </p>
        <form method="POST" enctype="multipart/form-data">
            <label for="audio_file">Upload your voice note:</label><br>
            <input type="file" name="audio_file" accept="audio/*" required><br><br>

            <div class = "prompt-select">
                <label for="prompt_mode">Image Prompt Mode:</label>
                <select id="prompt_mode" name="prompt_mode">
                    <option value="detailed">Detailed Scene Description</option>
                    <option value="keywords">Keywords</option>
                    <option value="creative">Creative Interpretation</option>
                </select>
            </div>

            <input type="submit" value="Generate Visual Response">
        </form>

        {% if transcript %}
            <div class="result-container">
                <h3>📝 Transcript:</h3>
                <textarea readonly>{{ transcript }}</textarea>
            </div>
        {% endif %}

        {% if summary %}
            <div class="result-container">
                <h3>🔎 Summary:</h3>
                <p>{{ summary }}</p>
            </div>
        {% endif %}

        {% if prompt %}
            <div class="result-container">
                <h3>🎯 Scene Prompt:</h3>
                <p>{{ prompt }}</p>
            </div>
        {% endif %}

        {% if image_url %}
            <div class="result-container">
                <h3>🖼️ Generated Image:</h3>
                <img src="{{ image_url }}" alt="Generated image">
            </div>
        {% endif %}
        {% if error %}
            <div class="error-message">{{ error }}</div>
        {% endif %}
    </div>
</body>
</html>

6.2.3 ¿Qué hace que esto sea un Panel de Control?

Este diseño combina varios elementos clave que lo transforman de una interfaz simple a un panel de control integral:

  • Múltiples zonas de salida (texto, resumen, instrucción, imagen)La interfaz está dividida en secciones distintas, cada una dedicada a mostrar diferentes tipos de datos procesados. Esta organización permite a los usuarios seguir fácilmente la progresión desde la entrada de voz hasta la salida visual.
  • Interacción simple del usuario (procesamiento con un solo clic)A pesar del complejo procesamiento que ocurre tras bambalinas, los usuarios solo necesitan realizar una acción para iniciar todo el flujo de trabajo. Esta simplicidad hace que la herramienta sea accesible para usuarios de todos los niveles técnicos.
  • Formato limpio y legibleLa interfaz utiliza espaciado, tipografía y jerarquía visual consistentes para asegurar que la información sea fácilmente digerible. Cada sección está claramente etiquetada y visualmente separada de las demás.
  • Retroalimentación visual para reforzar la salida del modeloEl panel proporciona confirmación visual inmediata en cada paso del proceso, ayudando a los usuarios a entender cómo su entrada se está transformando a través de diferentes modelos de IA.
  • Arquitectura reutilizable, gracias a la estructura utils/El diseño modular separa la funcionalidad central en funciones de utilidad, haciendo que el código sea más fácil de mantener y adaptar para diferentes casos de uso.

6.2.4 Ideas de Casos de Uso

Este versátil panel tiene numerosas aplicaciones potenciales. Exploremos algunos casos de uso clave en detalle:

  • Un kit de herramientas de IA para creadores de contenido (convertir pensamientos en blogs + visuales)
    • Grabar sesiones de lluvia de ideas y convertirlas en publicaciones de blog estructuradas
    • Generar ilustraciones correspondientes para conceptos clave
    • Crear paquetes de contenido para redes sociales con visuales coincidentes
  • Un asistente de profesor (grabar voz ➝ resumir ➝ ilustrar)
    • Transformar planes de lección en materiales de aprendizaje visual
    • Crear contenido educativo atractivo con ilustraciones correspondientes
    • Generar ayudas visuales para conceptos complejos
  • Una herramienta de diario (registrar entradas de voz ➝ resumir + visualizar)
    • Convertir memorandos de voz diarios en entradas escritas organizadas
    • Crear tableros de estado de ánimo basados en el contenido del diario
    • Rastrear patrones emocionales a través de representaciones visuales

Resumen

En esta sección, has elevado tu asistente multimodal a un panel de control de nivel profesional. Esto es lo que has logrado:

  • Desglosar tu lógica en utilidades reutilizables
    • Creada estructura de código modular y mantenible
    • Implementada clara separación de responsabilidades
  • Aceptar entrada de audio y procesarla a través de modelos
    • Integración perfecta de múltiples tecnologías de IA
    • Pipeline de procesamiento eficiente
  • Presentar todo claramente en una IU cohesiva
    • Diseño de interfaz amigable para el usuario
    • Jerarquía de información intuitiva
  • Pasar de "demo" a herramienta
    • Implementación lista para producción
    • Arquitectura escalable

Este panel representa una interfaz de nivel profesional que ofrece valor real a los usuarios. Con su arquitectura robusta y diseño intuitivo, está listo para transformarse en un producto completo con un desarrollo adicional mínimo.

6.2 Construyendo un Panel de Control para Creadores

Aquí es donde todas las capacidades que has desarrollado hasta ahora se unen para crear un sistema potente y unificado. Al integrar múltiples tecnologías de IA, podemos crear aplicaciones que son más que la suma de sus partes. Exploremos estas capacidades fundamentales en detalle:

  • La transcripción convierte las palabras habladas en texto escritoUsando modelos avanzados de reconocimiento de voz como Whisper, podemos convertir con precisión grabaciones de audio en texto, preservando la intención y el contexto del hablante. Esto forma la base para el procesamiento posterior.
  • La generación de contenido crea material nuevo y contextualmente relevanteLos modelos de lenguaje grandes pueden analizar el texto transcrito y generar nuevo contenido que mantiene la consistencia con el mensaje original mientras añade ideas valiosas o expande puntos clave.
  • La ingeniería de prompts elabora instrucciones precisas para modelos de IAA través de una construcción cuidadosa de prompts, podemos guiar a los modelos de IA para producir resultados más precisos y relevantes. Esto implica entender tanto las capacidades técnicas de los modelos como las formas matizadas de comunicarse con ellos.
  • La creación de imágenes transforma descripciones textuales en arte visualLos modelos como DALL·E pueden interpretar descripciones textuales y crear imágenes correspondientes, añadiendo una dimensión visual a nuestras aplicaciones y haciendo más tangibles los conceptos abstractos.

Estos componentes no solo existen uno al lado del otro - forman un pipeline interconectado donde cada paso mejora el siguiente. La salida de la transcripción alimenta la generación de contenido, que informa la ingeniería de prompts, llevando finalmente a la creación de imágenes. Esta integración perfecta crea un flujo de trabajo fluido donde los usuarios pueden comenzar con una simple grabación de voz y terminar con una salida multimedia rica, todo dentro de un sistema único y cohesivo. Al eliminar la necesidad de cambiar entre diferentes herramientas o interfaces, los usuarios pueden concentrarse en su proceso creativo en lugar de los detalles de implementación técnica.

6.2.1 Lo que vas a construir

En esta sección, diseñarás e implementarás un Panel de Control para Creadores - una interfaz web sofisticada que transforma la forma en que los creadores trabajan con la IA. Esta plataforma integral sirve como centro neurálgico para la creación de contenido, combinando múltiples tecnologías de IA en una experiencia fluida. Exploremos las características clave que hacen poderoso este panel:

  • Subir una grabación de voz
    Los creadores pueden subir fácilmente archivos de audio en varios formatos, simplificando el proceso de iniciar su proceso creativo con ideas o narraciones habladas.
  • Transcribir la grabación de voz a texto usando IA
    Utilizando tecnología avanzada de reconocimiento de voz por IA, el sistema convierte con precisión las palabras habladas en texto escrito, manteniendo los matices y el contexto de la grabación original.
  • Convertir esa transcripción en un prompt editable
    El sistema procesa de manera inteligente el texto transcrito para crear prompts estructurados y preparados para IA que pueden personalizarse para lograr el resultado creativo deseado.
  • Generar imágenes usando DALL·E basadas en el prompt
    Aprovechando las potentes capacidades de generación de imágenes de DALL·E, el sistema crea representaciones visuales que coinciden con los prompts especificados, dando vida a las ideas a través de obras de arte generadas por IA.
  • Resumir la transcripción
    El panel utiliza IA para destilar transcripciones largas en resúmenes concisos y significativos, ayudando a los creadores a captar rápidamente los conceptos y temas principales.
  • Mostrar todos los resultados para revisión y uso posterior en la producción de contenido
    Todo el contenido generado - desde transcripciones hasta imágenes - se presenta en un formato organizado y fácil de revisar, permitiendo a los creadores gestionar y utilizar sus recursos de manera eficiente.

Para construir este sistema robusto, implementarás un stack tecnológico moderno usando Flask para las operaciones del backend y una combinación limpia y adaptable de HTML y CSS para la interfaz del frontend. Esta arquitectura garantiza tanto la modularidad como la mantenibilidad, facilitando la actualización y escalabilidad del panel según sea necesario.

6.2.2 Implementación Paso a Paso

Paso 1: Configuración del Proyecto

Descarga el ejemplo de audio: https://files.cuantum.tech/audio/dashboard-project.mp3

Crea un nuevo directorio para tu proyecto y navega hacia él:

mkdir creator_dashboard
cd creator_dashboard

Se recomienda configurar un entorno virtual:

python -m venv venv
source venv/bin/activate  # En macOS/Linux
venv\\Scripts\\activate  # En Windows

Instala los paquetes de Python necesarios:

pip install flask openai python-dotenv

Organiza los archivos de tu proyecto de la siguiente manera:

/creator_dashboard

├── app.py
├── .env
└── templates/
    └── dashboard.html
└── utils/
    ├── __init__.py
    ├── transcribe.py
    ├── summarize.py
    ├── generate_prompt.py
    └── generate_image.py
  • app.py: El archivo principal de la aplicación Flask.
  • .env: Un archivo para almacenar tu clave de API de OpenAI.
  • templates/: Un directorio para plantillas HTML.
  • templates/dashboard.html: La plantilla HTML para la interfaz de usuario.
  • utils/: Un directorio para módulos Python que contienen funciones reutilizables.
    • __init__.py: Convierte el directorio utils en un paquete Python.
    • transcribe.py: Contiene la función para transcribir audio usando Whisper.
    • summarize.py: Contiene la función para resumir la transcripción usando un Modelo de Lenguaje Grande.
    • generate_prompt.py: Contiene la función para generar un prompt de imagen a partir del resumen usando un Modelo de Lenguaje Grande.
    • generate_image.py: Contiene la función para generar una imagen con DALL·E 3.

Paso 2: Crear los Módulos de Utilidades

Crea los siguientes archivos Python en el directorio utils/:

utils/transcribe.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def transcribe_audio(file_path: str) -> Optional[str]:
    """
    Transcribes an audio file using OpenAI's Whisper API.

    Args:
        file_path (str): The path to the audio file.

    Returns:
        Optional[str]: The transcribed text, or None on error.
    """
    try:
        logger.info(f"Transcribing audio: {file_path}")
        audio_file = open(file_path, "rb")
        response = openai.Audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
        )
        transcript = response.text
        audio_file.close()
        return transcript
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error during transcription: {e}")
        return None
  • Este módulo define la función transcribe_audio, que toma como entrada la ruta de un archivo de audio y utiliza la API Whisper de OpenAI para generar una transcripción de texto.
  • La función abre el archivo de audio en modo lectura binaria ("rb").
  • Llama a openai.Audio.transcriptions.create() para realizar la transcripción, especificando el modelo "whisper-1".
  • Extrae el texto transcrito de la respuesta de la API.
  • Incluye manejo de errores usando un bloque try...except para capturar posibles excepciones openai.error.OpenAIError (específicas de OpenAI) y Exception general para otros errores. Si ocurre un error, registra el error y devuelve None.
  • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
  • El archivo de audio se cierra después de la transcripción.

utils/summarize.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def summarize_transcript(text: str) -> Optional[str]:
    """
    Summarizes a text transcript using OpenAI's Chat Completion API.

    Args:
        text (str): The text transcript to summarize.

    Returns:
        Optional[str]: The summarized text, or None on error.
    """
    try:
        logger.info("Summarizing transcript")
        response = openai.chat.completions.create(
            model="gpt-4o",
            messages=[
                {"role": "system",
                 "content": "You are a helpful assistant.  Provide a concise summary of the text, suitable for generating a visual representation."},
                {"role": "user", "content": text}
            ],
        )
        summary = response.choices[0].message.content
        logger.info(f"Summary: {summary}")
        return summary
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating summary: {e}")
        return None
  • Este módulo define la función summarize_transcript, que toma como entrada una transcripción de texto y utiliza la API de Chat Completion de OpenAI para generar un resumen conciso.
  • El mensaje del sistema indica al modelo que actúe como un asistente útil y proporcione un resumen conciso del texto, adecuado para generar una representación visual.
  • El mensaje del usuario proporciona la transcripción como contenido para que el modelo lo resuma.
  • La función extrae el resumen de la respuesta de la API.
  • Incluye manejo de errores.

utils/generate_prompt.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def create_image_prompt(transcription: str) -> Optional[str]:
    """
    Generates a detailed image prompt from a text transcription using OpenAI's Chat Completion API.

    Args:
        transcription (str): The text transcription of the audio.

    Returns:
        Optional[str]: A detailed text prompt suitable for image generation, or None on error.
    """
    try:
        logger.info("Generating image prompt from transcription")
        response = openai.chat.completions.create(
            model="gpt-4o",  # Use a powerful chat model
            messages=[
                {
                    "role": "system",
                    "content": "You are a creative assistant. Your task is to create a vivid and detailed text description of a scene that could be used to generate an image with an AI image generation model. Focus on capturing the essence and key visual elements of the audio content.  Do not include any phrases like 'based on the audio' or 'from the user audio'.  Incorporate scene lighting, time of day, weather, and camera angle into the description.  Limit the description to 200 words.",
                },
                {"role": "user", "content": transcription},
            ],
        )
        prompt = response.choices[0].message.content
        prompt = prompt.strip()  # Remove leading/trailing spaces
        logger.info(f"Generated prompt: {prompt}")
        return prompt
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image prompt: {e}")
        return None
  • Este módulo define la función create_image_prompt, que toma como entrada el texto transcrito y utiliza la API de Chat Completion de OpenAI para generar un texto detallado que sirva como prompt para la generación de imágenes.
  • El mensaje del sistema instruye al modelo para actuar como un asistente creativo y generar una descripción vívida de una escena. Este prompt del sistema es clave para guiar al modelo de lenguaje a producir un prompt de alta calidad. Se le indica al modelo que se enfoque en elementos visuales e incorpore detalles como la iluminación, la hora del día, el clima y el ángulo de cámara. También se limita la longitud de la descripción a 200 palabras.
  • El mensaje del usuario proporciona el texto transcrito como contenido con el que el modelo debe trabajar.
  • La función extrae el prompt generado de la respuesta de la API.
  • Elimina los espacios en blanco al inicio y al final del prompt generado.
  • Incluye manejo de errores.

utils/generate_image.py:

import openai
import logging
from typing import Optional, Dict

logger = logging.getLogger(__name__)


def generate_dalle_image(prompt: str, model: str = "dall-e-3", size: str = "1024x1024",
                       response_format: str = "url", quality: str = "standard") -> Optional[str]:
    """
    Generates an image using OpenAI's DALL·E API.

    Args:
        prompt (str): The text prompt to generate the image from.
        model (str, optional): The DALL·E model to use. Defaults to "dall-e-3".
        size (str, optional): The size of the generated image. Defaults to "1024x1024".
        response_format (str, optional): The format of the response. Defaults to "url".
        quality (str, optional): The quality of the image. Defaults to "standard".

    Returns:
        Optional[str]: The URL of the generated image, or None on error.
    """
    try:
        logger.info(f"Generating image with prompt: {prompt}, model: {model}, size: {size}, format: {response_format}, quality: {quality}")
        response = openai.images.generate(
            prompt=prompt,
            model=model,
            size=size,
            response_format=response_format,
            quality=quality
        )
        image_url = response.data[0].url
        logger.info(f"Image URL: {image_url}")
        return image_url
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image: {e}")
        return None
  • Este módulo define la función generate_dalle_image, que toma un texto descriptivo como entrada y utiliza la API DALL·E de OpenAI para generar una imagen.
  • Llama al método openai.images.generate() para generar la imagen.
  • Acepta parámetros opcionales de modelsizeresponse_format, y quality, permitiendo al usuario configurar la generación de la imagen.
  • Extrae la URL de la imagen generada desde la respuesta de la API.
  • Incluye manejo de errores.

Paso 5: Crear la Aplicación Principal (app.py)

Crea un archivo Python llamado app.py en el directorio raíz de tu proyecto y agrega el siguiente código:

from flask import Flask, request, render_template, jsonify, make_response, redirect, url_for
import os
from dotenv import load_dotenv
import logging
from typing import Optional, Dict
from werkzeug.utils import secure_filename
from werkzeug.datastructures import FileStorage

# Import the utility functions from the utils directory
from utils.transcribe import transcribe_audio
from utils.generate_prompt import create_image_prompt
from utils.generate_image import generate_dalle_image
from utils.summarize import summarize_transcript

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'  # Store uploaded files
app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024  # 25MB max file size - increased for larger audio files
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)  # Create the upload folder if it doesn't exist

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

ALLOWED_EXTENSIONS = {'mp3', 'mp4', 'wav', 'm4a'}  # Allowed audio file extensions


def allowed_file(filename: str) -> bool:
    """
    Checks if the uploaded file has an allowed extension.

    Args:
        filename (str): The name of the file.

    Returns:
        bool: True if the file has an allowed extension, False otherwise.
    """
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route("/", methods=["GET", "POST"])
def index():
    """
    Handles the main route for the web application.
    Processes audio uploads, transcribes them, generates image prompts, and displays images.
    """
    transcript = None
    image_url = None
    prompt_summary = None
    error_message = None
    summary = None # Initialize summary

    if request.method == "POST":
        if 'audio_file' not in request.files:
            error_message = "No file part"
            logger.warning(error_message)
            return render_template("index.html", error_message=error_message)

        file: FileStorage = request.files['audio_file']  # Use type hinting
        if file.filename == '':
            error_message = "No file selected"
            logger.warning(request)
            return render_template("index.html", error_message=error_message)

        if file and allowed_file(file.filename):
            try:
                # Secure the filename and construct a safe path
                filename = secure_filename(file.filename)
                file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
                file.save(file_path)  # Save the uploaded file

                transcript = transcribe_audio(file_path)  # Transcribe audio
                if not transcript:
                    error_message = "Audio transcription failed. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                summary = summarize_transcript(transcript) # Summarize the transcript
                if not summary:
                    error_message = "Audio summary failed. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                prompt_summary = generate_image_prompt(transcript)  # Generate prompt
                if not prompt_summary:
                    error_message = "Failed to generate image prompt. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                image_url = generate_dalle_image(prompt_summary, model=request.form.get('model', 'dall-e-3'),
                                                size=request.form.get('size', '1024x1024'),
                                                response_format=request.form.get('format', 'url'),
                                                quality=request.form.get('quality', 'standard'))  # Generate image
                if not image_url:
                    error_message = "Failed to generate image. Please try again."
                    os.remove(file_path)
                    return render_template("index.html", error_message=error_message)

                # Optionally, delete the uploaded file after processing
                os.remove(file_path)
                logger.info(f"Successfully processed audio file and generated image.")
                return render_template("index.html", transcript=transcript, image_url=image_url, prompt=prompt_summary, summary=summary)

            except Exception as e:
                error_message = f"An error occurred: {e}"
                logger.error(error_message)
                return render_template("index.html",  error_message=error_message)
        else:
            error_message = "Invalid file type. Please upload a valid audio file (MP3, MP4, WAV, M4A)."
            logger.warning(request)
            return render_template("index.html",  error_message=error_message)

    return render_template("index.html", transcript=transcript, image_url=image_url, prompt=prompt_summary,
                           error=error_message, summary=summary)



@app.errorhandler(500)
def internal_server_error(e):
    """Handles internal server errors."""
    logger.error(f"Internal Server Error: {e}")
    return render_template("error.html", error="Internal Server Error"), 500


if __name__ == "__main__":
    app.run(debug=True)

Desglose del Código:

  • Declaraciones de Importación: Importa los módulos necesarios de Flask, la biblioteca OpenAI, osdotenvloggingOptional y Dict para la tipificación, y secure_filename y FileStorage de Werkzeug.
  • Variables de Entorno: Carga la clave API de OpenAI desde el archivo .env.
  • Aplicación Flask:
    • Crea una instancia de la aplicación Flask.
    • Configura una carpeta de carga y tamaño máximo de archivo. El UPLOAD_FOLDER se establece como 'uploads', y MAX_CONTENT_LENGTH se establece en 25MB. La carpeta de carga se crea si no existe.
  • Configuración de Registro: Configura el sistema de registro.
  • Función allowed_file: Verifica si el archivo cargado tiene una extensión de audio permitida.
  • Función transcribe_audio:
    • Toma la ruta del archivo de audio como entrada.
    • Abre el archivo de audio en modo de lectura binaria ("rb").
    • Llama al método openai.Audio.transcriptions.create() de la API de OpenAI para transcribir el audio.
    • Extrae el texto transcrito de la respuesta de la API.
    • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
    • Incluye manejo de errores para errores de la API de OpenAI y otras excepciones. El archivo de audio se cierra después de la transcripción.
  • Función generate_image_prompt:
    • Toma el texto transcrito como entrada.
    • Utiliza la API de Chat Completion de OpenAI (openai.chat.completions.create()) con el modelo gpt-4o para generar un prompt de texto detallado adecuado para la generación de imágenes.
    • El mensaje del sistema instruye al modelo para que actúe como un asistente creativo y proporcione una descripción vívida y detallada de una escena que podría usarse para generar una imagen con un modelo de generación de imágenes de IA. El prompt del sistema es crucial para guiar al LLM en la generación de un prompt de alta calidad. Instruimos al LLM para que se centre en elementos visuales e incorpore detalles como iluminación, hora del día, clima y ángulo de la cámara. También limitamos la longitud de la descripción a 200 palabras.
    • Extrae el prompt generado de la respuesta de la API.
    • Elimina los espacios iniciales/finales del prompt generado.
    • Incluye manejo de errores.
  • Función generate_image:
    • Toma el prompt de imagen como entrada.
    • Llama al método openai.Image.create() de la API de OpenAI para generar una imagen usando DALL·E 3.
    • Acepta parámetros opcionales de modelsizeresponse_format, y quality, permitiendo al usuario configurar la generación de la imagen.
    • Extrae la URL de la imagen generada de la respuesta de la API.
    • Incluye manejo de errores.
  • Ruta index:
    • Maneja solicitudes GET y POST.
    • Para solicitudes GET, renderiza la página HTML inicial.
    • Para solicitudes POST (cuando el usuario carga un archivo de audio):
      • Valida el archivo cargado:
        • Verifica si existe la parte del archivo en la solicitud.
        • Verifica si se seleccionó un archivo.
        • Verifica si el tipo de archivo está permitido usando la función allowed_file.
      • Guarda el archivo cargado en una ubicación temporal usando un nombre de archivo seguro.
      • Llama a las funciones de utilidad para:
        • Transcribir el audio usando transcribe_audio().
        • Generar un prompt de imagen a partir de la transcripción usando create_image_prompt().
        • Generar una imagen a partir del prompt usando generate_dalle_image().
      • Resume la transcripción usando la api de chat completions de openai.
      • Maneja los errores que puedan ocurrir durante cualquiera de estos pasos, registrando el error y renderizando la plantilla index.html con un mensaje de error apropiado. El archivo temporal se elimina antes de renderizar la página de error.
      • Si todos los pasos son exitosos, renderiza la plantilla index.html, pasando el texto de transcripción, URL de la imagen y prompt generado para ser mostrados.
  • @app.errorhandler(500): Maneja errores HTTP 500 (Error Interno del Servidor) registrando el error y renderizando una página de error amigable para el usuario.
  • if __name__ == "__main__":: Inicia el servidor de desarrollo Flask si el script se ejecuta directamente.

Paso 6: Crear la Plantilla HTML (templates/dashboard.html)

Crea una carpeta llamada templates en el mismo directorio que app.py. Dentro de la carpeta templates, crea un archivo llamado dashboard.html con el siguiente código HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Creator Dashboard</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
    <style>
        /* --- General Styles --- */
        body {
            font-family: 'Inter', sans-serif;
            padding: 40px;
            background-color: #f9fafb; /* Tailwind's gray-50 */
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            color: #374151; /* Tailwind's gray-700 */
        }
        .container {
            max-width: 800px; /* Increased max-width */
            width: 95%; /* Take up most of the viewport */
            background-color: #fff;
            padding: 2rem;
            border-radius: 0.75rem; /* Tailwind's rounded-lg */
            box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.05); /* Tailwind's shadow-xl */
            text-align: center;
        }
        h2 {
            font-size: 2.25rem; /* Tailwind's text-3xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1.5rem; /* Tailwind's mb-6 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        p{
            color: #6b7280; /* Tailwind's gray-500 */
            margin-bottom: 1rem;
        }

        /* --- Form Styles --- */
        form {
            margin-top: 1rem; /* Tailwind's mt-4 */
            margin-bottom: 1.5rem;
            display: flex;
            flex-direction: column;
            align-items: center; /* Center form elements */
            gap: 0.5rem; /* Tailwind's gap-2 */
        }
        label {
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600;  /* Tailwind's font-semibold */
            color: #4b5563; /* Tailwind's gray-600 */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            display: block; /* Ensure label takes full width */
            text-align: left;
            width: 100%;
            max-width: 400px; /* Added max-width for label */
            margin-left: auto;
            margin-right: auto;
        }
        input[type="file"] {
            width: 100%;
            max-width: 400px; /* Added max-width for file input */
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            font-size: 1rem; /* Tailwind's text-base */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            margin-left: auto;
            margin-right: auto;
        }
        input[type="submit"] {
            padding: 0.75rem 1.5rem; /* Tailwind's px-6 py-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #4f46e5; /* Tailwind's bg-indigo-500 */
            color: #fff;
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600; /* Tailwind's font-semibold */
            cursor: pointer;
            transition: background-color 0.3s ease; /* Smooth transition */
            border: none;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Subtle shadow */
            margin-top: 1rem;
        }
        input[type="submit"]:hover {
            background-color: #4338ca; /* Tailwind's bg-indigo-700 on hover */
        }
        input[type="submit"]:focus {
            outline: none;
            box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3); /* Tailwind's ring-indigo-500 */
        }

        /* --- Result Styles --- */
        .result-container {
            margin-top: 2rem; /* Tailwind's mt-8 */
            padding: 1.5rem; /* Tailwind's p-6 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #f8fafc; /* Tailwind's bg-gray-50 */
            border: 1px solid #e2e8f0; /* Tailwind's border-gray-200 */
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */
            text-align: left;
        }
        h3 {
            font-size: 1.5rem; /* Tailwind's text-2xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1rem; /* Tailwind's mb-4 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        textarea {
            width: 100%;
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            resize: none;
            font-size: 1rem; /* Tailwind's text-base */
            line-height: 1.5rem; /* Tailwind's leading-relaxed */
            margin-top: 0.5rem; /* Tailwind's mt-2 */
            margin-bottom: 0;
            box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); /* Inner shadow */
            min-height: 100px;
        }
        textarea:focus {
            outline: none;
            border-color: #3b82f6; /* Tailwind's border-blue-500 */
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); /* Tailwind's ring-blue-500 */
        }
        img {
            max-width: 100%;
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            margin-top: 1.5rem; /* Tailwind's mt-6 */
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* Tailwind's shadow-md */
        }

        /* --- Error Styles --- */
        .error-message {
            color: #dc2626; /* Tailwind's text-red-600 */
            margin-top: 1rem; /* Tailwind's mt-4 */
            padding: 0.75rem;
            background-color: #fee2e2; /* Tailwind's bg-red-100 */
            border-radius: 0.375rem; /* Tailwind's rounded-md */
            border: 1px solid #fecaca; /* Tailwind's border-red-300 */
            text-align: center;
        }

        .prompt-select {
            margin-top: 1rem; /* Tailwind's mt-4 */
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 0.5rem;
            width: 100%;
        }

        .prompt-select label {
            font-size: 1rem;
            font-weight: 600;
            color: #4b5563;
            margin-bottom: 0.25rem;
            display: block; /* Ensure label takes full width */
            text-align: left;
            width: 100%;
            max-width: 400px; /* Added max-width for label */
            margin-left: auto;
            margin-right: auto;
        }

        .prompt-select select {
            width: 100%;
            max-width: 400px;
            padding: 0.75rem;
            border-radius: 0.5rem;
            border: 1px solid #d1d5db;
            font-size: 1rem;
            margin-bottom: 0.25rem;
            margin-left: auto;
            margin-right: auto;
            appearance: none;  /* Remove default arrow */
            background-image: url("data:image/svg+xml,%3Csvgxmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l4 4 4-4'%3E%3C/path%3E%3C/svg%3E"); /* Add custom arrow */
            background-repeat: no-repeat;
            background-position: right 0.75rem center;
            background-size: 1rem;
            padding-right: 2.5rem; /* Make space for the arrow */
        }

        .prompt-select select:focus {
            outline: none;
            border-color: #3b82f6;
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
        }


    </style>
</head>
<body>
    <div class="container">
        <h2>🎤🧠🎨 Multimodal Assistant</h2>
        <p> Upload an audio file to transcribe and generate a corresponding image. </p>
        <form method="POST" enctype="multipart/form-data">
            <label for="audio_file">Upload your voice note:</label><br>
            <input type="file" name="audio_file" accept="audio/*" required><br><br>

            <div class = "prompt-select">
                <label for="prompt_mode">Image Prompt Mode:</label>
                <select id="prompt_mode" name="prompt_mode">
                    <option value="detailed">Detailed Scene Description</option>
                    <option value="keywords">Keywords</option>
                    <option value="creative">Creative Interpretation</option>
                </select>
            </div>

            <input type="submit" value="Generate Visual Response">
        </form>

        {% if transcript %}
            <div class="result-container">
                <h3>📝 Transcript:</h3>
                <textarea readonly>{{ transcript }}</textarea>
            </div>
        {% endif %}

        {% if summary %}
            <div class="result-container">
                <h3>🔎 Summary:</h3>
                <p>{{ summary }}</p>
            </div>
        {% endif %}

        {% if prompt %}
            <div class="result-container">
                <h3>🎯 Scene Prompt:</h3>
                <p>{{ prompt }}</p>
            </div>
        {% endif %}

        {% if image_url %}
            <div class="result-container">
                <h3>🖼️ Generated Image:</h3>
                <img src="{{ image_url }}" alt="Generated image">
            </div>
        {% endif %}
        {% if error %}
            <div class="error-message">{{ error }}</div>
        {% endif %}
    </div>
</body>
</html>

6.2.3 ¿Qué hace que esto sea un Panel de Control?

Este diseño combina varios elementos clave que lo transforman de una interfaz simple a un panel de control integral:

  • Múltiples zonas de salida (texto, resumen, instrucción, imagen)La interfaz está dividida en secciones distintas, cada una dedicada a mostrar diferentes tipos de datos procesados. Esta organización permite a los usuarios seguir fácilmente la progresión desde la entrada de voz hasta la salida visual.
  • Interacción simple del usuario (procesamiento con un solo clic)A pesar del complejo procesamiento que ocurre tras bambalinas, los usuarios solo necesitan realizar una acción para iniciar todo el flujo de trabajo. Esta simplicidad hace que la herramienta sea accesible para usuarios de todos los niveles técnicos.
  • Formato limpio y legibleLa interfaz utiliza espaciado, tipografía y jerarquía visual consistentes para asegurar que la información sea fácilmente digerible. Cada sección está claramente etiquetada y visualmente separada de las demás.
  • Retroalimentación visual para reforzar la salida del modeloEl panel proporciona confirmación visual inmediata en cada paso del proceso, ayudando a los usuarios a entender cómo su entrada se está transformando a través de diferentes modelos de IA.
  • Arquitectura reutilizable, gracias a la estructura utils/El diseño modular separa la funcionalidad central en funciones de utilidad, haciendo que el código sea más fácil de mantener y adaptar para diferentes casos de uso.

6.2.4 Ideas de Casos de Uso

Este versátil panel tiene numerosas aplicaciones potenciales. Exploremos algunos casos de uso clave en detalle:

  • Un kit de herramientas de IA para creadores de contenido (convertir pensamientos en blogs + visuales)
    • Grabar sesiones de lluvia de ideas y convertirlas en publicaciones de blog estructuradas
    • Generar ilustraciones correspondientes para conceptos clave
    • Crear paquetes de contenido para redes sociales con visuales coincidentes
  • Un asistente de profesor (grabar voz ➝ resumir ➝ ilustrar)
    • Transformar planes de lección en materiales de aprendizaje visual
    • Crear contenido educativo atractivo con ilustraciones correspondientes
    • Generar ayudas visuales para conceptos complejos
  • Una herramienta de diario (registrar entradas de voz ➝ resumir + visualizar)
    • Convertir memorandos de voz diarios en entradas escritas organizadas
    • Crear tableros de estado de ánimo basados en el contenido del diario
    • Rastrear patrones emocionales a través de representaciones visuales

Resumen

En esta sección, has elevado tu asistente multimodal a un panel de control de nivel profesional. Esto es lo que has logrado:

  • Desglosar tu lógica en utilidades reutilizables
    • Creada estructura de código modular y mantenible
    • Implementada clara separación de responsabilidades
  • Aceptar entrada de audio y procesarla a través de modelos
    • Integración perfecta de múltiples tecnologías de IA
    • Pipeline de procesamiento eficiente
  • Presentar todo claramente en una IU cohesiva
    • Diseño de interfaz amigable para el usuario
    • Jerarquía de información intuitiva
  • Pasar de "demo" a herramienta
    • Implementación lista para producción
    • Arquitectura escalable

Este panel representa una interfaz de nivel profesional que ofrece valor real a los usuarios. Con su arquitectura robusta y diseño intuitivo, está listo para transformarse en un producto completo con un desarrollo adicional mínimo.