Sync: guardar en el historial el trabajo de las ultimas semanas que solo vivia en el disco
Este repo local tenia origin apuntando al Gitea local (localhost:3000), que Rafa
declaro archivado el 2026-06-28 (commit 962f33a, desarrollo movido a
gitea.feadulta.com). Ese memo nunca llego a este checkout: main quedo congelado
y todo el trabajo real de las ultimas 3+ semanas se fue commiteando solo en la
rama fix/multiidioma-portada-132 (ya fusionada a main sin perdida, commit
2504666), mientras que ademas se acumulaban 78 cambios sin commitear en el
working tree que nunca llegaron a NINGUN historial de git.
Este commit consolida esos cambios sueltos: TTS multi-voz (tts_*.py), scripts
de traduccion (translate_haiku.py, pretranslate_en_haiku.py, sync_translations_to_prod.py),
mu-plugins nuevos desplegados a prod (fea-beta-feedback, fea-cloudflare-realip,
fea-legacy-redirect, fea-gsc-verification, fea-support-campaign, fea-ui, etc.),
scripts de mantenimiento de enlaces/cartas, capturas E2E (tools/e2e/shot_*.cjs)
y documentacion de sesiones recientes.
Excluido deliberadamente (no es codigo versionable): tts-voices/ (3.3GB de
muestras de audio para clonacion de voz, anadido a .gitignore), logs/ (logs de
ejecucion, anadido a .gitignore), y 2 ficheros vacios accidentales + 2 copias
duplicadas sueltas en la raiz que ya existen en su ubicacion correcta.
This commit is contained in:
@@ -22,6 +22,7 @@ Pensado para que Codex lo lance en lote sobre la cola priorizada (cartas/destaca
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@@ -47,6 +48,12 @@ if ENGINE == "haiku":
|
||||
MODEL = "claude-haiku-4-5"
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import translate_haiku # carga la API key de portfolio-tracker/.env
|
||||
elif ENGINE == "minimax":
|
||||
MODEL = os.environ.get("LOCAL_MODEL", "MiniMax-Text-01")
|
||||
MINIMAX_URL = os.environ.get("MINIMAX_URL", "https://api.minimax.io/v1/text/chatcompletion_v2")
|
||||
_kf = Path(os.environ.get("MINIMAX_KEY_FILE", "/home/rafa/Feadulta/minimax.txt"))
|
||||
_keys = [l.strip() for l in _kf.read_text().splitlines() if l.strip().startswith("sk-")]
|
||||
MINIMAX_KEY = _keys[-1] if _keys else ""
|
||||
|
||||
HELPER_SRC = Path(__file__).resolve().parent / "fea_translate_helper.php"
|
||||
HELPER_DST = "/tmp/fea_translate_helper.php"
|
||||
@@ -114,6 +121,24 @@ def gemma(messages: list[dict], *, max_tokens: int) -> str:
|
||||
return data["choices"][0]["message"]["content"]
|
||||
|
||||
|
||||
def minimax(messages: list[dict], *, max_tokens: int) -> str:
|
||||
import urllib.request
|
||||
|
||||
body = json.dumps({
|
||||
"model": MODEL,
|
||||
"messages": messages,
|
||||
"temperature": 0.2,
|
||||
"max_tokens": max_tokens,
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
MINIMAX_URL, data=body,
|
||||
headers={"Content-Type": "application/json", "Authorization": f"Bearer {MINIMAX_KEY}"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=300) as resp:
|
||||
data = json.loads(resp.read().decode("utf-8"))
|
||||
return data["choices"][0]["message"]["content"]
|
||||
|
||||
|
||||
def _extract(text: str) -> str:
|
||||
"""Extrae la traducción del ÚLTIMO bloque <<<INI>>>…<<<FIN>>>.
|
||||
|
||||
@@ -175,7 +200,8 @@ def translate_text(text: str, lang: str, *, is_title: bool = False) -> str:
|
||||
{"role": "user", "content": task},
|
||||
]
|
||||
max_tokens = max(800, int(len(text) * 1.6))
|
||||
raw = gemma(messages, max_tokens=max_tokens)
|
||||
engine_fn = minimax if ENGINE == "minimax" else gemma
|
||||
raw = engine_fn(messages, max_tokens=max_tokens)
|
||||
return _extract(raw)
|
||||
|
||||
|
||||
@@ -208,9 +234,19 @@ def translation_exists(es_id: int, lang: str) -> int:
|
||||
return int(php_helper("exists", str(es_id), lang).strip() or "0")
|
||||
|
||||
|
||||
WP_LOCK_FILE = Path(os.environ.get("FEA_TR_LOCK", "/tmp/feadulta-translate.lock"))
|
||||
|
||||
|
||||
def create_translation(es_id: int, lang: str, title: str, content: str, status: str) -> int:
|
||||
payload = json.dumps({"title": title, "content": content, "model": MODEL})
|
||||
return int(php_helper("create", str(es_id), lang, status, stdin=payload).strip())
|
||||
# Lock entre procesos: serializa SOLO la escritura/enlace Polylang (rápido), no la
|
||||
# traducción LLM (lenta), para que 4 streams por idioma no pisen el grupo de traducciones.
|
||||
with WP_LOCK_FILE.open("w") as lk:
|
||||
fcntl.flock(lk, fcntl.LOCK_EX)
|
||||
try:
|
||||
return int(php_helper("create", str(es_id), lang, status, stdin=payload).strip())
|
||||
finally:
|
||||
fcntl.flock(lk, fcntl.LOCK_UN)
|
||||
|
||||
|
||||
def carta_article_ids(carta_id: int) -> list[int]:
|
||||
@@ -272,6 +308,7 @@ def main() -> int:
|
||||
g = ap.add_mutually_exclusive_group(required=True)
|
||||
g.add_argument("--post-id", type=int, help="ID de un post ES a traducir.")
|
||||
g.add_argument("--carta", type=int, help="ID de carta: traduce la carta y todos sus artículos (_carta_id).")
|
||||
g.add_argument("--ids-file", help="Fichero con un ID de post ES por línea.")
|
||||
ap.add_argument("--langs", default="en,fr,it,pt", help="Idiomas destino separados por coma.")
|
||||
ap.add_argument("--status", default="draft", choices=["draft", "publish"], help="Estado de la traducción.")
|
||||
ap.add_argument("--force", action="store_true", help="Regenera aunque ya exista la traducción.")
|
||||
@@ -283,6 +320,9 @@ def main() -> int:
|
||||
|
||||
if args.post_id:
|
||||
ids = [args.post_id]
|
||||
elif args.ids_file:
|
||||
ids = [int(x) for x in Path(args.ids_file).read_text().split() if x.strip().isdigit()]
|
||||
log(f"ids-file {args.ids_file}: {len(ids)} posts")
|
||||
else:
|
||||
ids = [args.carta] + carta_article_ids(args.carta)
|
||||
log(f"Carta {args.carta}: {len(ids)} posts (carta + {len(ids)-1} artículos)")
|
||||
|
||||
Reference in New Issue
Block a user