first commit
This commit is contained in:
600
src/lib/prompts.ts
Normal file
600
src/lib/prompts.ts
Normal file
@@ -0,0 +1,600 @@
|
||||
import type { IdeaMotriz, Personaje, GuionConductor, ActoGuion, Escaleta, CharacterAsset } from '@/types/project';
|
||||
|
||||
// =====================
|
||||
// IDEA MOTRIZ
|
||||
// =====================
|
||||
|
||||
export function getIdeaMotrizGeneratePrompt(): string {
|
||||
return `Eres un director creativo experto en series animadas.
|
||||
Tu tarea es crear la "Idea Motriz" completa de una serie animada basándote en la descripción del usuario.
|
||||
|
||||
Debes generar un JSON con esta estructura exacta:
|
||||
{
|
||||
"concepto": "Resumen narrativo del concepto",
|
||||
"ideaMotrizCentral": "La premisa central en una frase",
|
||||
"formato": {
|
||||
"capitulos": número de capítulos,
|
||||
"duracionMinutos": duración por capítulo
|
||||
},
|
||||
"narrativa": "Descripción del estilo narrativo (con/sin diálogos, etc.)",
|
||||
"expansion": "Cómo puede expandirse la serie",
|
||||
"valores": ["valor1", "valor2", ...],
|
||||
"personajesSecundarios": "Descripción del rol de personajes secundarios",
|
||||
"protagonistas": [
|
||||
{
|
||||
"nombre": "Nombre",
|
||||
"rol": "Rol principal",
|
||||
"utileria": "Objeto distintivo",
|
||||
"mecanica": "Mecánica de habilidad"
|
||||
}
|
||||
],
|
||||
"antagonista": {
|
||||
"nombre": "Nombre del antagonista",
|
||||
"descripcion": "Descripción",
|
||||
"representacionVisual": "Cómo se ve",
|
||||
"mision": "Qué busca o representa"
|
||||
},
|
||||
"estructuraGuion": [
|
||||
{
|
||||
"numero": 1,
|
||||
"nombre": "Nombre del acto",
|
||||
"duracionMin": minutos mínimos,
|
||||
"duracionMax": minutos máximos,
|
||||
"descripcion": "Qué ocurre en este acto"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Responde SOLO con el JSON, sin texto adicional.`;
|
||||
}
|
||||
|
||||
export function getIdeaMotrizRefinePrompt(current: IdeaMotriz): string {
|
||||
return `Eres un director creativo. Tienes la siguiente Idea Motriz de una serie:
|
||||
|
||||
${JSON.stringify(current, null, 2)}
|
||||
|
||||
El usuario quiere hacer ajustes. Modifica la idea según su feedback manteniendo la estructura JSON.
|
||||
Responde SOLO con el JSON completo actualizado.`;
|
||||
}
|
||||
|
||||
// =====================
|
||||
// PERSONAJES
|
||||
// =====================
|
||||
|
||||
export function getPersonajesGeneratePrompt(ideaMotriz: IdeaMotriz): string {
|
||||
return `Eres un diseñador de personajes experto. Basándote en esta Idea Motriz:
|
||||
|
||||
${JSON.stringify(ideaMotriz, null, 2)}
|
||||
|
||||
Genera personajes detallados. Para cada personaje incluye:
|
||||
- id: UUID único
|
||||
- nombre
|
||||
- tipo: "protagonista" | "aliado" | "antagonista"
|
||||
- especie: qué animal o criatura es (ej: "gato", "búho", "zorro")
|
||||
- aparienciaFisica: descripción visual detallada (color de pelaje/plumas, tamaño, rasgos distintivos, expresión)
|
||||
- rolPrincipal: su función en la historia
|
||||
- personalidad: rasgos de carácter
|
||||
- habilidadInnata: qué sabe hacer naturalmente
|
||||
- utileriaVisual: objetos distintivos que lleva
|
||||
- mecanicaNarrativa: cómo funciona en la historia
|
||||
- comunicacion: cómo se expresa (sonidos, gestos)
|
||||
- avanceHabilidad: cómo progresa
|
||||
|
||||
Responde con un JSON: { "personajes": [...] }`;
|
||||
}
|
||||
|
||||
export function getPersonajeRefinePrompt(personaje: Personaje): string {
|
||||
return `Tienes este personaje:
|
||||
|
||||
${JSON.stringify(personaje, null, 2)}
|
||||
|
||||
Modifícalo según el feedback del usuario. Responde SOLO con el JSON del personaje actualizado.`;
|
||||
}
|
||||
|
||||
export function getPersonajesImprovePrompt(personajes: Personaje[]): string {
|
||||
return `Tienes esta lista de personajes:
|
||||
|
||||
${JSON.stringify(personajes, null, 2)}
|
||||
|
||||
El usuario quiere mejorarlos según su feedback. Modifica los personajes manteniendo la estructura.
|
||||
Responde con JSON: { "personajes": [...] }`;
|
||||
}
|
||||
|
||||
export function getNewPersonajePrompt(
|
||||
datos: { nombre: string; tipo: string; descripcion: string },
|
||||
ideaMotriz: IdeaMotriz,
|
||||
personajesExistentes: Personaje[]
|
||||
): string {
|
||||
return `Eres un diseñador de personajes. Crea un personaje completo para una serie animada.
|
||||
|
||||
CONTEXTO DE LA SERIE:
|
||||
${JSON.stringify(ideaMotriz, null, 2)}
|
||||
|
||||
PERSONAJES YA EXISTENTES (para evitar duplicados y mantener coherencia):
|
||||
${JSON.stringify(personajesExistentes.map(p => ({ nombre: p.nombre, tipo: p.tipo, rol: p.rolPrincipal })), null, 2)}
|
||||
|
||||
NUEVO PERSONAJE A CREAR:
|
||||
- Nombre: ${datos.nombre}
|
||||
- Tipo: ${datos.tipo}
|
||||
- Descripción del usuario: ${datos.descripcion || 'Sin descripción adicional'}
|
||||
|
||||
Genera un JSON completo con esta estructura:
|
||||
{
|
||||
"id": "id-unico",
|
||||
"nombre": "${datos.nombre}",
|
||||
"tipo": "${datos.tipo}",
|
||||
"especie": "qué animal o criatura es",
|
||||
"aparienciaFisica": "descripción visual detallada (color, tamaño, rasgos distintivos)",
|
||||
"rolPrincipal": "descripción del rol",
|
||||
"personalidad": "rasgos de carácter",
|
||||
"habilidadInnata": "qué sabe hacer",
|
||||
"utileriaVisual": "objetos distintivos",
|
||||
"mecanicaNarrativa": "cómo funciona en la historia",
|
||||
"comunicacion": "cómo se expresa",
|
||||
"avanceHabilidad": "cómo progresa"
|
||||
}
|
||||
|
||||
Responde SOLO con el JSON.`;
|
||||
}
|
||||
|
||||
// =====================
|
||||
// GUIÓN CONDUCTOR
|
||||
// =====================
|
||||
|
||||
export function getGuionConductorPrompt(
|
||||
ideaMotriz: IdeaMotriz,
|
||||
personajes: Personaje[],
|
||||
capituloNumero: number,
|
||||
virtudSugerida?: string
|
||||
): string {
|
||||
return `Eres un guionista experto. Genera un guión conductor para el capítulo ${capituloNumero}.
|
||||
|
||||
CONTEXTO DE LA SERIE:
|
||||
${JSON.stringify(ideaMotriz, null, 2)}
|
||||
|
||||
PERSONAJES:
|
||||
${JSON.stringify(personajes, null, 2)}
|
||||
|
||||
${virtudSugerida ? `VIRTUD SUGERIDA: ${virtudSugerida}` : 'Elige una virtud apropiada para este capítulo.'}
|
||||
|
||||
Genera un JSON con esta estructura:
|
||||
{
|
||||
"titulo": "Título del capítulo",
|
||||
"virtudGanada": "La virtud que se gana",
|
||||
"duracionTotal": "20:00",
|
||||
"nudoDelMundo": "El problema/conflicto del capítulo",
|
||||
"aliado": {
|
||||
"nombre": "Nombre del aliado (animal personificado)",
|
||||
"descripcion": "Descripción visual y rol"
|
||||
},
|
||||
"gemaGanada": "Nombre de la gema",
|
||||
"actos": [
|
||||
{
|
||||
"numero": 1,
|
||||
"nombre": "Nombre del acto",
|
||||
"tiempoInicio": "0:00",
|
||||
"tiempoFin": "4:00",
|
||||
"segmentos": [
|
||||
{
|
||||
"id": "I.1",
|
||||
"nombre": "Nombre del segmento",
|
||||
"tiempo": "0:00-1:00",
|
||||
"accionVisualClave": "Descripción detallada de lo que ocurre visualmente",
|
||||
"disenoFondo": "Descripción del escenario/ambiente: lugar, iluminación, hora del día, elementos del entorno, atmósfera",
|
||||
"disenoSonido": "Música, efectos, vocalizaciones"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Sigue la estructura de 4 actos según la idea motriz. Responde SOLO con el JSON.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt para regenerar SOLO los actos del guión, manteniendo la metadata fija.
|
||||
* El LLM recibe titulo, duracionTotal, aliado, virtudGanada, gemaGanada, nudoDelMundo
|
||||
* como restricciones fijas y genera únicamente el array de actos con sus segmentos.
|
||||
*/
|
||||
export function getRegenerateActosPrompt(
|
||||
guionMetadata: {
|
||||
titulo: string;
|
||||
duracionTotal: string;
|
||||
virtudGanada: string;
|
||||
nudoDelMundo: string;
|
||||
aliado: { nombre: string; descripcion: string };
|
||||
gemaGanada: string;
|
||||
},
|
||||
ideaMotriz: IdeaMotriz,
|
||||
personajes: Personaje[],
|
||||
capituloNumero: number
|
||||
): string {
|
||||
return `Eres un guionista experto. Genera SOLO los actos y segmentos para el capítulo ${capituloNumero}.
|
||||
|
||||
Los siguientes datos del capítulo ya están definidos y son FIJOS (NO los modifiques ni los incluyas en tu respuesta):
|
||||
- Título: ${guionMetadata.titulo}
|
||||
- Duración total: ${guionMetadata.duracionTotal}
|
||||
- Virtud ganada: ${guionMetadata.virtudGanada}
|
||||
- Nudo del mundo: ${guionMetadata.nudoDelMundo}
|
||||
- Aliado: ${guionMetadata.aliado.nombre} — ${guionMetadata.aliado.descripcion}
|
||||
- Gema ganada: ${guionMetadata.gemaGanada}
|
||||
|
||||
CONTEXTO DE LA SERIE:
|
||||
${JSON.stringify(ideaMotriz, null, 2)}
|
||||
|
||||
PERSONAJES:
|
||||
${JSON.stringify(personajes, null, 2)}
|
||||
|
||||
Genera un JSON con SOLO los actos. Los actos deben encajar dentro de la duración total de ${guionMetadata.duracionTotal} y desarrollar el nudo "${guionMetadata.nudoDelMundo}" involucrando al aliado "${guionMetadata.aliado.nombre}".
|
||||
|
||||
Sigue la estructura de actos según la idea motriz (típicamente 4 actos).
|
||||
|
||||
{
|
||||
"actos": [
|
||||
{
|
||||
"numero": 1,
|
||||
"nombre": "Nombre del acto",
|
||||
"tiempoInicio": "0:00",
|
||||
"tiempoFin": "4:00",
|
||||
"segmentos": [
|
||||
{
|
||||
"id": "I.1",
|
||||
"nombre": "Nombre del segmento",
|
||||
"tiempo": "0:00-1:00",
|
||||
"accionVisualClave": "Descripción detallada de lo que ocurre visualmente",
|
||||
"disenoFondo": "Descripción del escenario/ambiente: lugar, iluminación, hora del día, elementos del entorno, atmósfera",
|
||||
"disenoSonido": "Música, efectos, vocalizaciones"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Responde SOLO con el JSON.`;
|
||||
}
|
||||
|
||||
// =====================
|
||||
// ESCALETA
|
||||
// =====================
|
||||
|
||||
/** Parse "M:SS" or "MM:SS" to seconds */
|
||||
export function parseTimeToSeconds(t: string): number {
|
||||
const parts = t.split(':').map(Number);
|
||||
if (parts.length === 2) return parts[0] * 60 + parts[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Parse "M:SS-M:SS" range and return duration in seconds */
|
||||
export function parseTimeRange(range: string): number {
|
||||
const parts = range.split('-').map(s => s.trim());
|
||||
if (parts.length !== 2) return 0;
|
||||
return parseTimeToSeconds(parts[1]) - parseTimeToSeconds(parts[0]);
|
||||
}
|
||||
|
||||
export function getEscaletaPrompt(
|
||||
guion: GuionConductor,
|
||||
ideaMotriz: IdeaMotriz,
|
||||
personajes: Personaje[]
|
||||
): string {
|
||||
const personajesRef = personajes.map(p =>
|
||||
`- ID: "${p.id}", Nombre: "${p.nombre}" (${p.especie}, ${p.tipo}). Utilería: ${p.utileriaVisual}`
|
||||
).join('\n');
|
||||
|
||||
// Build per-segment time budget from guión
|
||||
let segmentBudgets = '';
|
||||
let totalBudget = 0;
|
||||
if (guion.actos) {
|
||||
for (const acto of guion.actos) {
|
||||
for (const seg of acto.segmentos) {
|
||||
const duracion = parseTimeRange(seg.tiempo);
|
||||
const numPlanos = Math.max(2, Math.round(duracion / 5));
|
||||
totalBudget += duracion;
|
||||
segmentBudgets += `- ${seg.id} "${seg.nombre}" (${seg.tiempo}): ${duracion}s → genera ~${numPlanos} planos\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!totalBudget) {
|
||||
totalBudget = parseTimeToSeconds(guion.duracionTotal || '20:00');
|
||||
}
|
||||
|
||||
return `Eres un director de animación. Convierte este guión conductor en una escaleta detallada plano por plano.
|
||||
|
||||
GUIÓN CONDUCTOR:
|
||||
${JSON.stringify(guion, null, 2)}
|
||||
|
||||
CONTEXTO:
|
||||
- Serie: ${ideaMotriz.ideaMotrizCentral}
|
||||
- Narrativa: ${ideaMotriz.narrativa}
|
||||
|
||||
PERSONAJES DISPONIBLES (usa estos IDs exactos):
|
||||
${personajesRef}
|
||||
|
||||
EMOCIONES VÁLIDAS: alegria | tristeza | sorpresa | miedo | enfado | neutral
|
||||
|
||||
PRESUPUESTO DE TIEMPO POR SEGMENTO (OBLIGATORIO):
|
||||
${segmentBudgets}
|
||||
TOTAL: ${totalBudget}s
|
||||
|
||||
REGLA CRÍTICA: La suma de tiempoSegundos de los planos de CADA segmento DEBE coincidir con la duración del segmento en el guión. Si un segmento dura 90s, la suma de sus planos debe ser exactamente 90s. NO generes planos que sumen menos del 90% del tiempo asignado.
|
||||
|
||||
Para cada segmento, genera suficientes planos (de 3-8 segundos cada uno) para cubrir TODO su tiempo:
|
||||
- Un segmento de 60s necesita ~10-12 planos
|
||||
- Un segmento de 90s necesita ~15-18 planos
|
||||
- Un segmento de 120s necesita ~20-24 planos
|
||||
- Un segmento de 150s necesita ~25-30 planos
|
||||
|
||||
Cada plano debe tener:
|
||||
- id: identificador único (ej: "plano-001")
|
||||
- numeroPlano: formato "acto.segmento.plano" (ej: "1.1.1")
|
||||
- segmentoGuion: referencia al segmento (ej: "I.1 Inicio Tranquilo")
|
||||
- tiempoSegundos: duración (3-8 seg)
|
||||
- accionDescripcion: descripción técnica detallada (tipo de plano, ángulo de cámara, iluminación, acción específica del personaje, elementos visuales). Varía los tipos de plano: wide shot, medium shot, close-up, over-the-shoulder, POV, etc.
|
||||
- indicacionesSonido: música, efectos, vocalizaciones
|
||||
- personajesDetalle: array de personajes que aparecen, cada uno con:
|
||||
- personajeId: ID exacto (ej: "coco-001")
|
||||
- nombre: nombre del personaje
|
||||
- emocion: una emoción válida que refleje su estado en ese plano
|
||||
|
||||
IMPORTANTE: Desglosa cada acción del guión en múltiples planos cinematográficos. Un personaje "corriendo hacia un lugar" puede ser: plano de pies corriendo, plano medio de expresión, plano general del recorrido, plano de llegada. Cada emoción visual merece su propio plano.
|
||||
|
||||
Responde con JSON:
|
||||
{
|
||||
"capitulo": "título",
|
||||
"virtudGanada": "virtud",
|
||||
"duracionTotalSegundos": ${totalBudget},
|
||||
"planos": [...]
|
||||
}`;
|
||||
}
|
||||
|
||||
export function getEscaletaActoPrompt(
|
||||
acto: ActoGuion,
|
||||
guion: GuionConductor,
|
||||
ideaMotriz: IdeaMotriz,
|
||||
personajes: Personaje[]
|
||||
): string {
|
||||
const personajesRef = personajes.map(p =>
|
||||
`- ID: "${p.id}", Nombre: "${p.nombre}" (${p.especie}, ${p.tipo}). Utilería: ${p.utileriaVisual}`
|
||||
).join('\n');
|
||||
|
||||
let segmentBudgets = '';
|
||||
let actoBudget = 0;
|
||||
for (const seg of acto.segmentos) {
|
||||
const duracion = parseTimeRange(seg.tiempo);
|
||||
const numPlanos = Math.max(2, Math.round(duracion / 5));
|
||||
actoBudget += duracion;
|
||||
segmentBudgets += `- ${seg.id} "${seg.nombre}" (${seg.tiempo}): ${duracion}s → genera ~${numPlanos} planos\n`;
|
||||
}
|
||||
|
||||
return `Eres un director de animación. Genera la escaleta detallada (plano por plano) para el ACTO ${acto.numero} "${acto.nombre}" (${acto.tiempoInicio}-${acto.tiempoFin}).
|
||||
|
||||
CONTEXTO DE LA SERIE:
|
||||
- Serie: ${ideaMotriz.ideaMotrizCentral}
|
||||
- Narrativa: ${ideaMotriz.narrativa}
|
||||
- Capítulo: ${guion.titulo}
|
||||
|
||||
ACTO ${acto.numero} - SEGMENTOS:
|
||||
${JSON.stringify(acto.segmentos, null, 2)}
|
||||
|
||||
PERSONAJES DISPONIBLES (usa estos IDs exactos):
|
||||
${personajesRef}
|
||||
|
||||
EMOCIONES VÁLIDAS: alegria | tristeza | sorpresa | miedo | enfado | neutral
|
||||
|
||||
PRESUPUESTO DE TIEMPO POR SEGMENTO (OBLIGATORIO):
|
||||
${segmentBudgets}
|
||||
TOTAL ACTO: ${actoBudget}s
|
||||
|
||||
REGLA CRÍTICA: La suma de tiempoSegundos de los planos de CADA segmento DEBE coincidir con la duración del segmento. NO generes planos que sumen menos del 90% del tiempo asignado.
|
||||
|
||||
Guías de cantidad:
|
||||
- Un segmento de 60s necesita ~10-12 planos
|
||||
- Un segmento de 90s necesita ~15-18 planos
|
||||
- Un segmento de 120s necesita ~20-24 planos
|
||||
- Un segmento de 150s necesita ~25-30 planos
|
||||
|
||||
Cada plano debe tener:
|
||||
- id: identificador único (ej: "plano-001")
|
||||
- numeroPlano: formato "acto.segmento.plano" (ej: "${acto.numero}.1.1")
|
||||
- segmentoGuion: referencia al segmento (ej: "${acto.segmentos[0]?.id} ${acto.segmentos[0]?.nombre}")
|
||||
- tiempoSegundos: duración (3-8 seg)
|
||||
- accionDescripcion: descripción técnica detallada (tipo de plano, ángulo de cámara, iluminación, acción, elementos visuales). Varía: wide shot, medium shot, close-up, over-the-shoulder, POV, etc.
|
||||
- indicacionesSonido: música, efectos, vocalizaciones
|
||||
- personajesDetalle: array de personajes que aparecen, cada uno con:
|
||||
- personajeId: ID exacto (ej: "coco-001")
|
||||
- nombre: nombre del personaje
|
||||
- emocion: una emoción válida que refleje su estado en ese plano
|
||||
|
||||
IMPORTANTE: Desglosa cada acción en múltiples planos cinematográficos. "Un personaje corriendo" = plano de pies, plano medio de expresión, plano general del recorrido, plano de llegada. Cada emoción merece su propio plano.
|
||||
|
||||
Responde con JSON:
|
||||
{
|
||||
"planos": [...]
|
||||
}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-segment prompt: generates planos for a SINGLE segment (escena) of the guión.
|
||||
* This is the most granular approach — one LLM call per segment — which ensures
|
||||
* the LLM generates the exact number of planos needed for that segment's duration.
|
||||
*/
|
||||
export function getEscaletaSegmentoPrompt(
|
||||
segmento: { id: string; nombre: string; tiempo: string; accionVisualClave: string; disenoSonido: string },
|
||||
actoNumero: number,
|
||||
segmentoIndex: number,
|
||||
guion: GuionConductor,
|
||||
ideaMotriz: IdeaMotriz,
|
||||
personajes: Personaje[],
|
||||
targetPlanos: number,
|
||||
targetSeconds: number,
|
||||
subBatchInfo?: { batchIndex: number; totalBatches: number; planosInThisBatch: number; startPlano: number }
|
||||
): string {
|
||||
const personajesRef = personajes.map(p =>
|
||||
`- ID: "${p.id}", Nombre: "${p.nombre}" (${p.especie}, ${p.tipo}). Utilería: ${p.utileriaVisual}`
|
||||
).join('\n');
|
||||
|
||||
const actualPlanos = subBatchInfo ? subBatchInfo.planosInThisBatch : targetPlanos;
|
||||
const startPlano = subBatchInfo ? subBatchInfo.startPlano : 1;
|
||||
const batchNote = subBatchInfo
|
||||
? `\nNOTA: Este es el lote ${subBatchInfo.batchIndex + 1} de ${subBatchInfo.totalBatches} para este segmento. Genera los planos ${startPlano} a ${startPlano + actualPlanos - 1} de ${targetPlanos} totales.`
|
||||
: '';
|
||||
|
||||
return `Eres un director de animación. Genera EXACTAMENTE ${actualPlanos} planos para el segmento "${segmento.id} ${segmento.nombre}" del capítulo "${guion.titulo}".
|
||||
${batchNote}
|
||||
CONTEXTO:
|
||||
- Serie: ${ideaMotriz.ideaMotrizCentral}
|
||||
- Narrativa: ${ideaMotriz.narrativa}
|
||||
- Capítulo: ${guion.titulo}
|
||||
- Virtud: ${guion.virtudGanada}
|
||||
|
||||
SEGMENTO A DESGLOSAR:
|
||||
- ID: ${segmento.id}
|
||||
- Nombre: ${segmento.nombre}
|
||||
- Tiempo: ${segmento.tiempo} (${targetSeconds} segundos total)
|
||||
- Acción visual: ${segmento.accionVisualClave}
|
||||
- Diseño de sonido: ${segmento.disenoSonido}
|
||||
|
||||
PERSONAJES DISPONIBLES (usa estos IDs exactos):
|
||||
${personajesRef}
|
||||
|
||||
EMOCIONES VÁLIDAS: alegria | tristeza | sorpresa | miedo | enfado | neutral
|
||||
|
||||
═══════════════════════════════════════════════
|
||||
INSTRUCCIÓN OBLIGATORIA: Genera EXACTAMENTE ${actualPlanos} planos.
|
||||
Cada plano debe durar 5 segundos (tiempoSegundos: 5).
|
||||
Total: ${actualPlanos} planos × 5s = ${actualPlanos * 5}s
|
||||
═══════════════════════════════════════════════
|
||||
|
||||
Cada plano debe variar en tipo de encuadre y ángulo de cámara para crear ritmo visual:
|
||||
- Wide/establishing shot: para mostrar escenario completo
|
||||
- Medium shot: personaje de cintura para arriba, acción general
|
||||
- Close-up: rostro, expresión emocional
|
||||
- Extreme close-up: detalle de ojos, patas, objeto
|
||||
- Over-the-shoulder: perspectiva de un personaje mirando a otro
|
||||
- Low angle: cámara baja mirando hacia arriba (da poder/grandeza)
|
||||
- High angle: cámara alta mirando hacia abajo (da vulnerabilidad)
|
||||
- POV: lo que ve el personaje
|
||||
- Tracking shot: siguiendo movimiento del personaje
|
||||
- Pan/tilt: barrido horizontal o vertical del escenario
|
||||
|
||||
Distribuye la acción del segmento a lo largo de los ${actualPlanos} planos. NO comprimas todo en pocos planos. Cada micro-momento merece su propio plano: una mirada, un gesto, un detalle del entorno, una reacción.
|
||||
|
||||
Formato de cada plano:
|
||||
{
|
||||
"id": "plano-XXX",
|
||||
"numeroPlano": "${actoNumero}.${segmentoIndex + 1}.N",
|
||||
"segmentoGuion": "${segmento.id} ${segmento.nombre}",
|
||||
"tiempoSegundos": 5,
|
||||
"accionDescripcion": "TIPO DE PLANO. Descripción técnica detallada...",
|
||||
"indicacionesSonido": "música, efectos, vocalizaciones",
|
||||
"personajesDetalle": [
|
||||
{ "personajeId": "id-exacto", "nombre": "Nombre", "emocion": "emocion_valida" }
|
||||
]
|
||||
}
|
||||
|
||||
Responde con JSON: { "planos": [...] } con EXACTAMENTE ${actualPlanos} planos.`;
|
||||
}
|
||||
|
||||
// =====================
|
||||
// PROMPTS PARA IMÁGENES
|
||||
// =====================
|
||||
|
||||
export function getImagePromptsPrompt(
|
||||
personajes: Personaje[],
|
||||
): string {
|
||||
const characterDescriptions = personajes.map(p =>
|
||||
`- ID: "${p.id}", Nombre: "${p.nombre}" (${p.especie}, ${p.tipo}): ${p.aparienciaFisica}. Utilería: ${p.utileriaVisual}`
|
||||
).join('\n');
|
||||
|
||||
return `Eres un prompt engineer especializado en generación de imágenes para animación con IA.
|
||||
|
||||
PERSONAJES (referencia visual completa):
|
||||
${characterDescriptions}
|
||||
|
||||
CONTEXTO: Las imágenes se generarán con Gemini imagen, que recibirá imágenes de referencia: T-poses del personaje (frontal, lateral, trasera), su emoción, e imagen de referencia del fondo/escenario. El fondo ya se envía como imagen de referencia visual, así que NO necesitas describir el escenario en el prompt. Tu prompt compuesto complementa esas referencias visuales.
|
||||
|
||||
Para cada plano de la escaleta, genera un objeto "promptComposition" con estos sub-campos:
|
||||
|
||||
1. "personajes": Una CADENA DE TEXTO (string, NO array ni objeto) describiendo todos los personajes en escena. Para cada uno: "Nombre: descripción física breve, emoción visible, posición en el encuadre, pose corporal". Si hay varios, sepáralos con punto y coma. Ejemplo: "Coco: gato naranja con gafas de piloto, expresión curiosa, centro del encuadre, caminando hacia la derecha; Miko: búho gris, sorprendido, fondo izquierda, posado en rama".
|
||||
|
||||
2. "accion": Movimiento principal, poses dinámicas, qué ocurre en la escena.
|
||||
|
||||
3. "luz": Tipo de iluminación (difusa, directa, contraluz), dirección, temperatura de color (cálida/fría), atmósfera lumínica.
|
||||
|
||||
4. "camara": Ángulo de cámara (contrapicado, cenital, frontal, tres cuartos, over-the-shoulder, POV, etc.).
|
||||
|
||||
5. "optica": Tipo de lente (gran angular, teleobjetivo, normal, macro), profundidad de campo.
|
||||
|
||||
6. "tipoPlano": Tipo de encuadre (plano general, plano medio, primer plano, plano detalle, plano americano, etc.).
|
||||
|
||||
7. "custom": Siempre cadena vacía "".
|
||||
|
||||
NO generes los campos "estilo" ni "fondo" — se inyectan automáticamente desde las referencias visuales.
|
||||
|
||||
IMPORTANTE: Cada sub-campo debe ser específico y unívoco. Si alguien lee la composición, debe poder imaginar exactamente la misma escena.
|
||||
|
||||
Responde con JSON: { "planos": [...] } donde cada plano incluye todos los campos originales más "promptComposition".`;
|
||||
}
|
||||
|
||||
// =====================
|
||||
// CHARACTER ASSET PROMPTS (Nano Banana Pro)
|
||||
// =====================
|
||||
|
||||
const TPOSE_VARIANTS = [
|
||||
{ variant: 'front', label: 'T-Pose Frontal' },
|
||||
{ variant: 'side', label: 'T-Pose Lateral' },
|
||||
{ variant: 'back', label: 'T-Pose Trasera' },
|
||||
] as const;
|
||||
|
||||
const EMOTION_VARIANTS = [
|
||||
{ variant: 'alegria', label: 'Alegría' },
|
||||
{ variant: 'tristeza', label: 'Tristeza' },
|
||||
{ variant: 'sorpresa', label: 'Sorpresa' },
|
||||
{ variant: 'miedo', label: 'Miedo' },
|
||||
{ variant: 'enfado', label: 'Enfado' },
|
||||
{ variant: 'neutral', label: 'Neutral' },
|
||||
] as const;
|
||||
|
||||
export { TPOSE_VARIANTS, EMOTION_VARIANTS };
|
||||
|
||||
export function getCharacterAssetPromptsPrompt(personaje: Personaje): string {
|
||||
return `Eres un artista conceptual especializado en character sheets para animación.
|
||||
|
||||
PERSONAJE:
|
||||
- Nombre: ${personaje.nombre}
|
||||
- Especie: ${personaje.especie}
|
||||
- Tipo: ${personaje.tipo}
|
||||
- Apariencia física: ${personaje.aparienciaFisica}
|
||||
- Utilería visual: ${personaje.utileriaVisual}
|
||||
- Personalidad: ${personaje.personalidad}
|
||||
|
||||
Genera prompts de imagen optimizados para crear el character sheet de este personaje.
|
||||
Cada prompt debe producir una imagen con FONDO BLANCO LISO, enfocada exclusivamente en el personaje.
|
||||
|
||||
Genera un JSON con este formato:
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"type": "tpose",
|
||||
"variant": "front" | "side" | "back",
|
||||
"prompt": "prompt detallado para la imagen"
|
||||
},
|
||||
{
|
||||
"type": "emotion",
|
||||
"variant": "alegria" | "tristeza" | "sorpresa" | "miedo" | "enfado" | "neutral",
|
||||
"prompt": "prompt detallado para la expresión"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
REGLAS PARA LOS PROMPTS:
|
||||
- T-Pose frontal: cuerpo completo de frente, brazos extendidos horizontalmente, postura neutra, fondo blanco
|
||||
- T-Pose lateral: cuerpo completo de perfil (lado derecho), brazos extendidos, fondo blanco
|
||||
- T-Pose trasera: cuerpo completo de espaldas, brazos extendidos, fondo blanco
|
||||
- Emociones: primer plano del rostro/cara del personaje mostrando la emoción clara, fondo blanco
|
||||
- Todos los prompts deben incluir la descripción física exacta del personaje (colores, rasgos, utilería)
|
||||
- Estilo: character design sheet, clean lines, flat white background, no shadows on background
|
||||
|
||||
Genera exactamente 3 T-poses y 6 emociones (9 assets total).
|
||||
Responde SOLO con el JSON.`;
|
||||
}
|
||||
Reference in New Issue
Block a user