feat(ia): analisis de encuadre inteligente y deteccion de texto para evitar cortes en imagenes
This commit is contained in:
parent
c8fd30e1c8
commit
1f8498e5a7
@ -267,12 +267,18 @@ function isDuplicateMessage(chatId: string, text: string): boolean {
|
||||
|
||||
// Determinar dimensiones según el tipo de publicación
|
||||
// Noticias: 640x360 (16:9) | Clasificados y Eventos: 640x640 (1:1)
|
||||
const isSquare = parsed.tipo === 'evento' || parsed.tipo === 'aviso';
|
||||
// Determinar gravedad de encuadre y dimensiones según análisis de IA
|
||||
const gravity = parsed.crop_focus === 'top' ? 'top'
|
||||
: parsed.crop_focus === 'bottom' ? 'bottom'
|
||||
: 'center';
|
||||
|
||||
// Si es un afiche/flyer con texto integrado o evento/aviso, usar formato cuadrado 640x640 para evitar mutilar texto
|
||||
const isSquare = parsed.tipo === 'evento' || parsed.tipo === 'aviso' || Boolean(parsed.has_text_overlay || parsed.crop_focus === 'contain');
|
||||
const imgHeight = isSquare ? 640 : 360;
|
||||
|
||||
// Notificación de Progreso 2: Estructuración completa y registro (asíncrona)
|
||||
const mediaStatus = candidateMediaList.length > 0
|
||||
? `🖼️ *Imagen:* Optimizando foto destacada (${isSquare ? 'Formato 640x640' : 'Formato 640x360'})`
|
||||
? `🖼️ *Imagen:* Optimizando foto destacada (${isSquare ? 'Formato 640x640' : 'Formato 640x360'} • Gravedad: ${gravity})`
|
||||
: `📄 *Formato:* Publicación en texto`;
|
||||
|
||||
const progressMsg = `⚙️ *Procesando Publicación*\n`
|
||||
@ -331,7 +337,7 @@ function isDuplicateMessage(chatId: string, text: string): boolean {
|
||||
|
||||
let ogImageUrl = undefined;
|
||||
if (storageFileId) {
|
||||
ogImageUrl = `${APPWRITE_ALMEIRA_CONFIG.endpoint}/storage/buckets/${APPWRITE_ALMEIRA_CONFIG.mediaBucketId}/files/${storageFileId}/preview?project=${APPWRITE_ALMEIRA_CONFIG.projectId}&width=640&height=${imgHeight}&quality=85`;
|
||||
ogImageUrl = `${APPWRITE_ALMEIRA_CONFIG.endpoint}/storage/buckets/${APPWRITE_ALMEIRA_CONFIG.mediaBucketId}/files/${storageFileId}/preview?project=${APPWRITE_ALMEIRA_CONFIG.projectId}&width=640&height=${imgHeight}&gravity=${gravity}&quality=85`;
|
||||
}
|
||||
|
||||
// Determinar fecha de vencimiento
|
||||
@ -390,14 +396,20 @@ function isDuplicateMessage(chatId: string, text: string): boolean {
|
||||
// Formatear contenido limpio para WhatsApp (sin etiquetas HTML)
|
||||
const cleanContent = htmlToWhatsAppText(parsed.contenido);
|
||||
|
||||
// Estado de la imagen destacada
|
||||
// Estado de la imagen destacada con análisis de encuadre
|
||||
let imageNotice = 'No se adjuntó imagen';
|
||||
const focusLabel = parsed.crop_focus === 'top' ? 'Enfoque superior (preserva titulares/rostros)'
|
||||
: parsed.crop_focus === 'contain' ? 'Formato completo (afiche/texto sin cortes)'
|
||||
: parsed.crop_focus === 'bottom' ? 'Enfoque inferior'
|
||||
: 'Enfoque centrado equilibrado';
|
||||
|
||||
if (candidateMediaList.length > 1) {
|
||||
const chosenNum = (parsed.selected_image_index ?? 0) + 1;
|
||||
imageNotice = `Foto #${chosenNum} de ${candidateMediaList.length} analizadas`
|
||||
+ (parsed.image_selection_reason ? ` (${parsed.image_selection_reason})` : '');
|
||||
+ (parsed.image_selection_reason ? ` (${parsed.image_selection_reason})` : '')
|
||||
+ `\n 📐 _${focusLabel}_`;
|
||||
} else if (candidateMediaList.length === 1) {
|
||||
imageNotice = `1 foto procesada (${isSquare ? '640x640' : '640x360'})`;
|
||||
imageNotice = `1 foto optimizada (${isSquare ? '640x640' : '640x360'})\n 📐 _${focusLabel}_`;
|
||||
}
|
||||
|
||||
const requestText = `🔔 *PUBLICACIÓN LISTA - SOLICITUD DE APROBACIÓN*\n\n`
|
||||
|
||||
@ -16,6 +16,8 @@ export interface ProcessedPublication {
|
||||
fecha_vencimiento: string | null;
|
||||
selected_image_index?: number;
|
||||
image_selection_reason?: string;
|
||||
crop_focus?: 'top' | 'center' | 'bottom' | 'contain';
|
||||
has_text_overlay?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,7 +54,9 @@ function fallbackRuleBasedParser(text: string, mediaCount: number): ProcessedPub
|
||||
contenido: contenido,
|
||||
fecha_vencimiento: null,
|
||||
selected_image_index: mediaCount > 0 ? 0 : undefined,
|
||||
image_selection_reason: mediaCount > 0 ? 'Foto destacada principal' : undefined
|
||||
image_selection_reason: mediaCount > 0 ? 'Foto destacada principal' : undefined,
|
||||
crop_focus: 'center',
|
||||
has_text_overlay: false
|
||||
};
|
||||
}
|
||||
|
||||
@ -90,6 +94,12 @@ export async function processWhatsAppMessage(
|
||||
5. fecha_vencimiento: Si el texto indica fecha límite del evento o del aviso (Ej. "hasta el 30 de noviembre", o "el próximo sábado"), devuélvela en formato YYYY-MM-DD. Si no aplica o es una noticia general, pasa null.
|
||||
6. selected_image_index: Si se proporcionaron múltiples imágenes candidatas (#1, #2, #3...), selecciona el índice base 0 de la imagen MÁS ADECUADA y de mayor calidad periodística/visual para ser la IMAGEN DESTACADA (la más nítida, bien compuesta, representativa del tema central y sin recortes desfavorables). Si solo hay 1 imagen, devuelve 0. Si no hay imágenes, pasa null.
|
||||
7. image_selection_reason: Breve explicación (máximo 1 línea) del motivo por el cual esa imagen es la más adecuada para portada.
|
||||
8. crop_focus: Analiza el encuadre óptimo de la imagen seleccionada para evitar que se corten textos, titulares o rostros clave:
|
||||
- "top": Si el texto principal, encabezado o rostros clave están en la parte superior.
|
||||
- "center": Si la composición principal está equilibrada en el centro.
|
||||
- "bottom": Si los elementos críticos están en la parte inferior.
|
||||
- "contain": Si la imagen es un afiche, flyer vertical o infografía con texto de borde a borde donde cualquier recorte mutilaría información.
|
||||
9. has_text_overlay: Boolean. true si la imagen es un afiche/poster/flyer con texto integrado; false si es una fotografía estándar.
|
||||
`;
|
||||
|
||||
// Prepara los contenidos
|
||||
@ -131,7 +141,9 @@ export async function processWhatsAppMessage(
|
||||
contenido: { type: Type.STRING, description: "Contenido en HTML" },
|
||||
fecha_vencimiento: { type: Type.STRING, description: "Fecha opcional YYYY-MM-DD", nullable: true },
|
||||
selected_image_index: { type: Type.INTEGER, description: "Índice base 0 de la mejor imagen seleccionada", nullable: true },
|
||||
image_selection_reason: { type: Type.STRING, description: "Motivo de selección de imagen", nullable: true }
|
||||
image_selection_reason: { type: Type.STRING, description: "Motivo de selección de imagen", nullable: true },
|
||||
crop_focus: { type: Type.STRING, enum: ["top", "center", "bottom", "contain"], description: "Punto de encuadre o gravedad óptimo sin cortar textos" },
|
||||
has_text_overlay: { type: Type.BOOLEAN, description: "Indica si contiene texto publicitario o afiche" }
|
||||
},
|
||||
required: ["titulo", "tipo", "seo_descripcion", "contenido"]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user