import mysql.connector import requests import json import time import sys # --- CONFIGURACIÓN --- DB_CONFIG = { 'user': 'ghghgh', 'password': 'Kq093~oo5', 'database': 'para_modificar', 'unix_socket': '/run/mysqld/mysqld.sock' } PREFIX = "9ENkutm_" OLLAMA_URL = "http://localhost:11434/api" MODELO_TEXTO = "mistral" MODELO_VECTOR = "all-minilm" # Mapeo de Idiomas LANG_NAMES = { "en": "English", "es": "Spanish", "fr": "French", "de": "German", "it": "Italian", "ro": "Romanian", "pt-br": "Brazilian Portuguese", "pt-pt": "European Portuguese", "pl": "Polish", "tr": "Turkish", "ru": "Russian", "vi": "Vietnamese", "da": "Danish", "sv": "Swedish", "no": "Norwegian", "ko": "Korean", "ja": "Japanese", "zh-hans": "Simplified Chinese", "zh-hant": "Traditional Chinese", "hi": "Hindi" } def log(msg): timestamp = time.strftime("%H:%M:%S") print(f"[{timestamp}] {msg}", flush=True) def generar_embedding(texto): """Genera vector matemático para 'Selena'""" try: res = requests.post(f"{OLLAMA_URL}/embeddings", json={ "model": MODELO_VECTOR, "prompt": texto[:1000], "options": {"num_thread": 2} }, timeout=10) if res.status_code == 200: return res.json().get('embedding') except: return None def obtener_lote_pendiente(cursor, tamano=50): """ CAMBIO CLAVE: Buscamos artículos que NO tengan VECTOR (embedding). Esto asegura que pasamos por todos, tengan o no subtítulo previo. """ sql = f""" SELECT p.ID, p.post_title, p.post_content, t.language_code FROM {PREFIX}posts p JOIN {PREFIX}icl_translations t ON p.ID = t.element_id AND t.element_type = 'post_post' LEFT JOIN {PREFIX}mcm_embeddings me ON p.ID = me.post_id WHERE p.post_status = 'publish' AND p.post_type = 'post' AND me.post_id IS NULL ORDER BY p.ID DESC LIMIT %s """ cursor.execute(sql, (tamano,)) return cursor.fetchall() def check_existing_subtitle(cursor, post_id): """Comprueba si YA existe una bajada en la DB para no machacarla""" sql = f"SELECT meta_value FROM {PREFIX}postmeta WHERE post_id = %s AND meta_key = '_mcm_subtitle'" cursor.execute(sql, (post_id,)) res = cursor.fetchone() return res['meta_value'] if res else None def procesar_inteligente_v21(): log("🚀 INICIANDO V21: RESPETO DE DATOS + RELLENADO DE SEO/VECTORES") conn = None try: conn = mysql.connector.connect(**DB_CONFIG) while True: # 1. CARGAR LOTE (Basado en falta de Vectores) cursor_read = conn.cursor(dictionary=True) lote = obtener_lote_pendiente(cursor_read, tamano=50) cursor_read.close() if not lote: log("🎉 ¡Misión cumplida! Todos los artículos tienen vectores y datos.") break total = len(lote) log(f"📦 Lote de {total} artículos (Sin vector)...") cursor_write = conn.cursor() for i, item in enumerate(lote): p_id = item['ID'] titulo = item['post_title'] contenido = item['post_content'] code = item['language_code'] lang_name = LANG_NAMES.get(code, "English") # --- PASO 0: ¿YA TIENE SUBTÍTULO? --- # Hacemos una consulta rápida para respetar el trabajo previo cursor_check = conn.cursor(dictionary=True) subtitulo_existente = check_existing_subtitle(cursor_check, p_id) cursor_check.close() estado_sub = "✅ MANTENIDO" if subtitulo_existente else "✨ GENERANDO" print(f"\r ⚙️ [{i+1}/{total}] ID {p_id} ({code}) | Sub: {estado_sub} | SEO: 🔨...", end="", flush=True) # --- PASO A: GENERAR TEXTOS (JSON) --- bajada_final = subtitulo_existente seo_desc = "" keyword = "" # Construimos el Prompt dependiendo de si necesitamos bajada o no if subtitulo_existente: # SOLO SEO prompt = ( f"Task: Analyze title '{titulo}'. Language: {lang_name}. " f"Output JSON keys: 'seo_desc' (meta description), 'keyword' (focus keyword)." ) else: # TODO COMPLETO prompt = ( f"Task: Analyze title '{titulo}'. Language: {lang_name}. " f"Output JSON keys: 'summary' (subtitle max 20 words), 'seo_desc' (meta description), 'keyword' (focus keyword)." ) try: res = requests.post(f"{OLLAMA_URL}/generate", json={ "model": MODELO_TEXTO, "prompt": prompt, "format": "json", "stream": False, "options": {"num_thread": 2, "num_ctx": 1024} }, timeout=45) if res.status_code == 200: data = json.loads(res.json().get('response', '{}')) if not subtitulo_existente: bajada_final = data.get('summary', titulo) seo_desc = data.get('seo_desc', '') keyword = data.get('keyword', '') except: # Si falla y no había subtítulo, usamos título como fallback if not bajada_final: bajada_final = titulo # --- PASO B: GENERAR VECTOR --- texto_vector = titulo + " " + contenido[:500] vector = generar_embedding(texto_vector) # --- PASO C: GUARDADO --- try: # 1. Subtítulo (SOLO SI NO EXISTÍA) if not subtitulo_existente: cursor_write.execute(f"INSERT INTO {PREFIX}postmeta (post_id, meta_key, meta_value) VALUES (%s, '_mcm_subtitle', %s)", (p_id, bajada_final)) # 2. Rank Math (SIEMPRE LO ACTUALIZAMOS/CREAMOS) if seo_desc: sql_seo = f"INSERT INTO {PREFIX}postmeta (post_id, meta_key, meta_value) VALUES (%s, 'rank_math_description', %s) ON DUPLICATE KEY UPDATE meta_value=%s" cursor_write.execute(sql_seo, (p_id, seo_desc, seo_desc)) if keyword: sql_kw = f"INSERT INTO {PREFIX}postmeta (post_id, meta_key, meta_value) VALUES (%s, 'rank_math_focus_keyword', %s) ON DUPLICATE KEY UPDATE meta_value=%s" cursor_write.execute(sql_kw, (p_id, keyword, keyword)) # 3. Vector (Marca el post como completado para la Query principal) if vector: sql_vec = f"REPLACE INTO {PREFIX}mcm_embeddings (post_id, embedding, updated_at) VALUES (%s, %s, NOW())" cursor_write.execute(sql_vec, (p_id, json.dumps(vector))) conn.commit() except Exception as e: log(f"\n❌ Error guardando ID {p_id}: {e}") cursor_write.close() log(f"\n✅ Lote guardado. Vamos al siguiente...") except Exception as e: log(f"🔥 Error Fatal: {e}") finally: if conn: conn.close() if __name__ == "__main__": procesar_inteligente_v21()