Notes
Tres fases desplegades en un dia: assistent, veu i seccions
Tres hitos en una tarda: l''assistent de creació, l''àudio amb comentaris
3 min de lectura
Avui ha estat el dia més intens del projecte: tres fases desplegades en una sola tarda, amb els seus ajusts i més d'un fix sobre la marxa. Ho apunt tot aquí perquè demà no me'n recordi de cap.
Hito 1: l'assistent "De la idea al publicat"
El primer desplegament ha estat l'assistent unificat de creació a /admin/editor/. Tres orígens possibles: cerca amb Tavily, URLs concretes o contingut base meu. Revisió en línia amb millora SEO i generació d'imatge destacada amb l'endpoint que ja tenia.
Sense canvis d'esquema ni backend: només frontend. npm run check amb 0 errors, build correcte i desplegament a fxserver amb docker compose up -d --build. La validació E2E per HTTP va tornar HTML 200 del wizard i l'asset de l'editor amb el text esperat.
El primer bug va arribar prompt: crear un document amb Keyword + Tavily acabava en 504 perquè el POST síncron triga més que el proxy. La solució ha estat moure el flux a un job curt amb polling des de l'assistent, sense mantenir una petició oberta.
Hito 2: àudio, comentaris i chat amb IA
El segon desplegament afegeix la capa de veu i comunitat: ús d'embeddings configurable, ingesta d'articles publicats al coneixement, endpoint públic /api/public/ask amb cites, TTS cap a audio_url, comentaris públics amb moderació per IA i el seu panel. Migració d'Alembic inclosa per a articles.audio_url i la taula comments.
Verificació: python -m unittest amb 13 tests en verd, /api/health i /api/articles amb 200, i un article públic sencer carregat. /api/public/ask respon 400 explícit perquè encara no hi ha ús d'embeddings assignat a Ajustes → IA: la ruta funciona, falta configurar-lo.
Aquí ha vingut la sèrie de fixes més llarga de la tarda. La veu MiniMax rebutjava textos llargs (text too long), així que ara es parteixen en fragments de 4.000 caràcters i es concatenen els àudios. També rebutjava la veu Spanish_Female (voice id not exist): ara hi ha una llista de veus de recanvi i el backend prova la següent si falla. I si no hi ha cap proveïdor configurat, els articles públics ofereixen "Escoltar l'article" amb la Web Speech API del navegador: zero cost al servidor.
El chat públic també s'ha redissenyat: bombolles tipus missatgeria, avís visible que parles amb una IA, markdown mínim amb escape d'HTML (negreta, cursiva, codi, enllaços) i neteja dels blocs <think> i <thought> que alguns models deixen escapar. A més, chat i veu tenen configuració separada: ús chat propi i voice_id als ajustos, sense tocar codi.
Hito 3: seccions configurables
El tercer desplegament porta seccions: migració sections + articles.section_id, CRUD a Ajustes → Seccions, selector al editor, filtre a la taula d'articles, navegació pública dinàmica i pàgina /<seccio> que reutilitza el llistat del blog. Tests en verd i validació E2E completa: crear secció, veure-la pública, esborrar-la i comprovar el 404.
Després, dos ajustos d'UX des dels commits: "Articles" queda com a vista de blog sense secció, cada secció té el seu item propi al menú de l'admin, i un parell de fixes al router (import que faltava i llistat per a editors).
Hito 4, primera tanda
Per acabar, les bases de la fase de versions: autoguardat amb debounce de 10 segons i indicador, taula article_versions amb instantànies i restauració des de l'editor, columna publish_at amb consulta pública filtrant publish_at <= now(), selector de data al editor, badge "Programat" i cua editorial real al rail.
La segona tanda afegeix rols editor/admin verificats al backend, gestió d'usuaris, cerca global Cmd+K, copiloto al editor i clonat traduït via ús translation.
SEO que vaig descobrir trencat
Revisant la fase de revisió vaig trobar que robots.txt apuntava a un sitemap generat amb localhost i que el sitemap estàtic incloïa rutes d'admin. Ara /sitemap.xml es genera dinàmic amb home, blog, seccions públiques i articles publicats; robots.txt apunta allà i bloca /admin/, i SITE_URL usa el domini públic per a canonical i sitemap. Coses que només veus quan les mires.