From 3f1dbbf396c63e6bfbb68d02e0fe1aff8f7d7e61 Mon Sep 17 00:00:00 2001 From: Michele Date: Sun, 8 Mar 2026 01:27:25 +0100 Subject: [PATCH] docs(01): create phase plan Phase 01: Core Generation Pipeline - 4 plan(s) in 3 wave(s) - Wave 1: 01-01 (infra) + 01-02 (core services) parallel - Wave 2: 01-03 (LLM pipeline + API routers) - Wave 3: 01-04 (Web UI) with human-verify checkpoint - Ready for execution --- .planning/ROADMAP.md | 12 +- .../01-core-generation-pipeline/01-01-PLAN.md | 255 ++++++++++++++ .../01-core-generation-pipeline/01-02-PLAN.md | 311 +++++++++++++++++ .../01-core-generation-pipeline/01-03-PLAN.md | 265 ++++++++++++++ .../01-core-generation-pipeline/01-04-PLAN.md | 328 ++++++++++++++++++ 5 files changed, 1165 insertions(+), 6 deletions(-) create mode 100644 .planning/phases/01-core-generation-pipeline/01-01-PLAN.md create mode 100644 .planning/phases/01-core-generation-pipeline/01-02-PLAN.md create mode 100644 .planning/phases/01-core-generation-pipeline/01-03-PLAN.md create mode 100644 .planning/phases/01-core-generation-pipeline/01-04-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 30c5d34..4d036f5 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -29,13 +29,13 @@ Decimal phases appear between their surrounding integers in numeric order. 3. Al termine della generazione, l'utente puo' scaricare un file CSV che si apre correttamente in Excel con caratteri italiani intatti e header che corrispondono esattamente ai placeholder del template Canva 4. Il CSV contiene esattamente 13 righe di contenuto con la distribuzione Persuasion Nurturing corretta (4 valore, 2 storytelling, 2 news, 3 riprova, 1 coinvolgimento, 1 promo) e i livelli Schwartz assegnati 5. Se la generazione di un singolo post fallisce (errore API), gli altri post del batch sono salvati e scaricabili; il post fallito e' marcato come errore senza bloccare il resto -**Plans**: TBD +**Plans**: 4 plans Plans: -- [ ] 01-01: Infrastructure setup — FastAPI + React SPA single container, Docker multi-stage build, subpath /postgenerator/ configurato correttamente (root_path via Uvicorn, Vite base, nginx lab-router) -- [ ] 01-02: Core services — CalendarService (distribuzione 13 post, Schwartz, nicchie, date), FormatSelector (mapping tipo x Schwartz → formato), PromptService base (carica .txt, compile variabili), file-based storage layout -- [ ] 01-03: LLM pipeline — LLMService (Claude API, retry, backoff, rate limit, JSON validation), CSVBuilder (header Canva locked, utf-8-sig encoding), per-item error isolation -- [ ] 01-04: Web UI base — Dashboard, form Genera Calendario, form Genera Singolo Post, Output Review (anteprima slide-by-slide), Impostazioni, progress indicator bulk +- [ ] 01-01-PLAN.md — Infrastructure setup: FastAPI skeleton + React SPA + Docker multi-stage build + subpath /postgenerator/ (Wave 1) +- [ ] 01-02-PLAN.md — Core services: CalendarService + FormatSelector + PromptService + costanti dominio + 5 prompt italiani (Wave 1) +- [ ] 01-03-PLAN.md — LLM pipeline: LLMService + CSVBuilder + GenerationPipeline + API routers (Wave 2) +- [ ] 01-04-PLAN.md — Web UI: Dashboard + Genera Calendario + Output Review + Genera Singolo + Settings (Wave 3) --- @@ -93,7 +93,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Core Generation Pipeline | 0/4 | Not started | - | +| 1. Core Generation Pipeline | 0/4 | Planned (3 waves) | - | | 2. Prompt Control + Output Review | 0/2 | Not started | - | | 3. Organization Layer | 0/2 | Not started | - | | 4. Enrichment | 0/1 | Not started | - | diff --git a/.planning/phases/01-core-generation-pipeline/01-01-PLAN.md b/.planning/phases/01-core-generation-pipeline/01-01-PLAN.md new file mode 100644 index 0000000..5b14471 --- /dev/null +++ b/.planning/phases/01-core-generation-pipeline/01-01-PLAN.md @@ -0,0 +1,255 @@ +--- +phase: 01-core-generation-pipeline +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - backend/main.py + - backend/config.py + - backend/__init__.py + - backend/routers/__init__.py + - frontend/vite.config.ts + - frontend/package.json + - frontend/tsconfig.json + - frontend/tsconfig.app.json + - frontend/src/main.tsx + - frontend/src/App.tsx + - frontend/src/index.css + - frontend/src/api/client.ts + - frontend/index.html + - Dockerfile + - docker-compose.yml + - requirements.txt + - .env.example + - .gitignore +autonomous: true + +must_haves: + truths: + - "FastAPI app starts on port 8000 and responds to GET /api/health with 200" + - "React SPA builds with Vite and base path /postgenerator/" + - "Docker multi-stage build produces a working container that serves both API and SPA" + - "SPA catch-all returns index.html for non-API routes without breaking /api/ routes" + artifacts: + - path: "backend/main.py" + provides: "FastAPI app with SPAStaticFiles catch-all, health endpoint, CORS-free single origin" + contains: "SPAStaticFiles" + - path: "backend/config.py" + provides: "Centralized path constants from env vars (DATA_PATH, PROMPTS_PATH, OUTPUTS_PATH)" + contains: "DATA_PATH" + - path: "frontend/vite.config.ts" + provides: "Vite config with base /postgenerator/, Tailwind v4 plugin, dev proxy to :8000" + contains: "base: '/postgenerator/'" + - path: "frontend/src/api/client.ts" + provides: "API client with base URL /postgenerator/api for production" + contains: "/postgenerator/api" + - path: "Dockerfile" + provides: "Multi-stage build: Node builds React, Python serves everything" + contains: "frontend-builder" + - path: "docker-compose.yml" + provides: "Single service with volume mount for data persistence" + contains: "lab-postgenerator-app" + - path: "requirements.txt" + provides: "Python dependencies pinned" + contains: "fastapi" + key_links: + - from: "frontend/vite.config.ts" + to: "backend/main.py" + via: "Vite dev proxy /api -> localhost:8000" + pattern: "proxy.*api.*8000" + - from: "Dockerfile" + to: "backend/main.py" + via: "CMD uvicorn with --root-path /postgenerator" + pattern: "root-path" + - from: "backend/main.py" + to: "frontend/dist" + via: "SPAStaticFiles mount" + pattern: "SPAStaticFiles.*static" +--- + + +Creare lo scheletro infrastrutturale completo: FastAPI app con SPA catch-all, React + Vite + Tailwind v4 project, Docker multi-stage build, e tutte le configurazioni per il subpath /postgenerator/. + +Purpose: Stabilire il plumbing funzionante prima di qualsiasi business logic. Verificare che il container Docker serve API e SPA correttamente, evitando i pitfall 4 (root_path double-path) e 9 (React API URL subpath). + +Output: Container Docker buildabile che serve una pagina React vuota su / e risponde a /api/health, pronto per ricevere routers e servizi nei piani successivi. + + + +@C:\Users\miche\.claude/get-shit-done/workflows/execute-plan.md +@C:\Users\miche\.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/research/STACK.md +@.planning/research/ARCHITECTURE.md +@.planning/research/PITFALLS.md +@.planning/phases/01-core-generation-pipeline/01-CONTEXT.md + + + + + + Task 1: Backend FastAPI skeleton + config + Docker build + + backend/__init__.py + backend/main.py + backend/config.py + backend/routers/__init__.py + requirements.txt + .env.example + .gitignore + Dockerfile + docker-compose.yml + + + 1. Aggiornare .gitignore con: __pycache__/, .venv/, *.pyc, node_modules/, frontend/dist/, .env, backend/data/outputs/ + + 2. Creare backend/__init__.py (vuoto). + + 3. Creare backend/config.py: + - DATA_PATH = Path(os.getenv("DATA_PATH", "./data")) + - PROMPTS_PATH = DATA_PATH / "prompts" + - OUTPUTS_PATH = DATA_PATH / "outputs" + - CAMPAIGNS_PATH = DATA_PATH / "campaigns" + - CONFIG_PATH = DATA_PATH / "config" + - Funzione get_settings() che legge ANTHROPIC_API_KEY e LLM_MODEL da env + + 4. Creare backend/routers/__init__.py (vuoto). + + 5. Creare backend/main.py: + - FastAPI() senza root_path nel costruttore (CRITICO: root_path solo via Uvicorn --root-path) + - Classe SPAStaticFiles che estende StaticFiles con fallback a index.html + - Health endpoint: GET /api/health -> {"status": "ok"} + - Mount SPAStaticFiles su "/" come ULTIMA operazione (dopo tutti i router) + - Startup event che crea le directory data/ se non esistono (prompts, outputs, campaigns, config) + + 6. Creare requirements.txt: + - fastapi[standard]==0.135.1 + - anthropic==0.84.0 + - httpx==0.28.1 + - python-dotenv==1.2.2 + - aiofiles + + 7. Creare .env.example: + - ANTHROPIC_API_KEY=your-key-here + - LLM_MODEL=claude-sonnet-4-5 + - DATA_PATH=/app/data + + 8. Creare Dockerfile multi-stage: + - Stage 1 (frontend-builder): FROM node:22-slim, WORKDIR /app/frontend, COPY package*.json, npm ci, COPY tutto, npm run build + - Stage 2 (runtime): FROM python:3.12-slim, WORKDIR /app, COPY requirements.txt, pip install --no-cache-dir, COPY backend/ ./backend/, COPY --from=frontend-builder /app/frontend/dist ./static, CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000", "--root-path", "/postgenerator"] + + 9. Creare docker-compose.yml: + - service "app" con container_name "lab-postgenerator-app" + - build context "." con dockerfile "Dockerfile" + - env_file: .env + - volumes: postgenerator-data:/app/data (named volume per persistenza) + - networks: proxy_net (external: true) + - deploy.resources.limits: memory 1024M, cpus '1.0' (Next.js-level per build React) + - NO porte esposte pubblicamente + + + - File backend/main.py contiene SPAStaticFiles e NON contiene root_path nel costruttore FastAPI() + - File Dockerfile contiene --root-path /postgenerator nel CMD + - File docker-compose.yml contiene proxy_net e container_name lab-postgenerator-app + - requirements.txt contiene fastapi[standard]==0.135.1 e anthropic==0.84.0 + + + Backend skeleton e Docker config pronti. FastAPI app definita senza root_path nel costruttore. Dockerfile multi-stage con --root-path solo in Uvicorn CMD. docker-compose.yml con volume per dati e rete proxy_net. + + + + + Task 2: React + Vite + Tailwind v4 SPA scaffold con API client + + frontend/package.json + frontend/vite.config.ts + frontend/tsconfig.json + frontend/tsconfig.app.json + frontend/index.html + frontend/src/main.tsx + frontend/src/App.tsx + frontend/src/index.css + frontend/src/api/client.ts + + + 1. Creare il progetto React + TypeScript con Vite: + - cd al progetto, eseguire: npm create vite@latest frontend -- --template react-ts + - Questo genera la struttura base + + 2. Installare dipendenze frontend: + - cd frontend + - npm install tailwindcss @tailwindcss/vite + - npm install react-router-dom @tanstack/react-query + - npm install lucide-react + + 3. Configurare vite.config.ts: + - import react from '@vitejs/plugin-react' + - import tailwindcss from '@tailwindcss/vite' + - plugins: [react(), tailwindcss()] + - base: '/postgenerator/' + - server.proxy: { '/postgenerator/api': { target: 'http://localhost:8000', changeOrigin: true, rewrite: path => path.replace('/postgenerator', '') } } + + 4. Configurare frontend/src/index.css: + - Rimuovere contenuto default Vite + - Aggiungere: @import "tailwindcss"; + + 5. Creare frontend/src/api/client.ts: + - const API_BASE = '/postgenerator/api' + - Funzione generica apiFetch(endpoint: string, options?: RequestInit): Promise + - Gestione errori con throw su status non-ok + - Export API_BASE e apiFetch + + 6. Aggiornare frontend/src/App.tsx: + - Import BrowserRouter con basename="/postgenerator" + - Import QueryClientProvider da @tanstack/react-query + - Struttura base con Routes placeholder + - Pagina placeholder con titolo "PostGenerator" e messaggio "Setup completo" + + 7. Aggiornare frontend/src/main.tsx: + - StrictMode + render App + + 8. Aggiornare frontend/index.html: + - title: "PostGenerator" + - Rimuovere favicon Vite default + + ATTENZIONE Pitfall 9: L'API client DEVE usare '/postgenerator/api' come base, NON '/api'. Questo e' l'absolute path che funziona dietro nginx lab-router. + + + - cd frontend && npm run build completa senza errori + - vite.config.ts contiene base: '/postgenerator/' + - frontend/src/api/client.ts contiene '/postgenerator/api' + - frontend/src/App.tsx contiene basename="/postgenerator" + - index.css contiene @import "tailwindcss" + + + React SPA scaffold completo con Tailwind v4, react-router con basename corretto, TanStack Query configurato, API client con path /postgenerator/api. Build Vite produce output in frontend/dist/ pronto per essere copiato nel container Docker. + + + + + + +1. `cd frontend && npm run build` produce output in dist/ senza errori +2. FastAPI main.py: SPAStaticFiles registrato DOPO health endpoint +3. Dockerfile: --root-path /postgenerator nel CMD, NON nel costruttore FastAPI() +4. Nessun hardcoded `/api/` senza prefisso /postgenerator/ nel frontend +5. docker-compose.yml: NO porte esposte, usa proxy_net + + + +- Backend FastAPI app definita con health endpoint e SPA catch-all +- Frontend React builds con base path /postgenerator/ +- Docker multi-stage build configurato +- API client usa path corretto /postgenerator/api +- Tutti i pitfall infrastrutturali (4, 9) indirizzati nella configurazione + + + +After completion, create `.planning/phases/01-core-generation-pipeline/01-01-SUMMARY.md` + diff --git a/.planning/phases/01-core-generation-pipeline/01-02-PLAN.md b/.planning/phases/01-core-generation-pipeline/01-02-PLAN.md new file mode 100644 index 0000000..bd6fad6 --- /dev/null +++ b/.planning/phases/01-core-generation-pipeline/01-02-PLAN.md @@ -0,0 +1,311 @@ +--- +phase: 01-core-generation-pipeline +plan: 02 +type: execute +wave: 1 +depends_on: [] +files_modified: + - backend/services/__init__.py + - backend/services/calendar_service.py + - backend/services/format_selector.py + - backend/services/prompt_service.py + - backend/schemas/__init__.py + - backend/schemas/calendar.py + - backend/schemas/generate.py + - backend/data/format_mapping.json + - backend/data/prompts/system_prompt.txt + - backend/data/prompts/pas_valore.txt + - backend/data/prompts/listicle_valore.txt + - backend/data/prompts/bab_storytelling.txt + - backend/data/prompts/aida_promozione.txt + - backend/data/prompts/dato_news.txt + - backend/constants.py +autonomous: true + +must_haves: + truths: + - "CalendarService genera esattamente 13 slot con distribuzione PN corretta (4 valore, 2 storytelling, 2 news, 3 riprova, 1 coinvolgimento, 1 promo)" + - "Ogni slot ha livello Schwartz assegnato con distribuzione corretta (L5+L4=6, L3=4, L2=2, L1=1)" + - "FormatSelector mappa ogni combinazione tipo_contenuto x livello_schwartz a un formato narrativo" + - "PromptService carica, lista e compila prompt .txt con sostituzione variabili" + - "CANVA_FIELDS e' definito come costante locked e contiene tutti i nomi colonna CSV" + - "Almeno 5 prompt base esistono come file .txt scritti IN italiano" + artifacts: + - path: "backend/services/calendar_service.py" + provides: "CalendarService con generate_calendar() che produce 13 slot PN + Schwartz + nicchie + date" + contains: "class CalendarService" + - path: "backend/services/format_selector.py" + provides: "FormatSelector con select_format(tipo, livello) -> formato narrativo" + contains: "class FormatSelector" + - path: "backend/services/prompt_service.py" + provides: "PromptService con load, list, compile con variabili" + contains: "class PromptService" + - path: "backend/constants.py" + provides: "CANVA_FIELDS, PERSUASION_DISTRIBUTION, SCHWARTZ_DISTRIBUTION costanti locked" + contains: "CANVA_FIELDS" + - path: "backend/schemas/calendar.py" + provides: "CalendarSlot, CalendarRequest, CalendarResponse Pydantic models" + contains: "class CalendarSlot" + - path: "backend/schemas/generate.py" + provides: "SlideContent, GeneratedPost Pydantic models per output LLM e CSV" + contains: "class GeneratedPost" + - path: "backend/data/format_mapping.json" + provides: "Tabella mapping tipo_contenuto x livello_schwartz -> formato narrativo" + - path: "backend/data/prompts/system_prompt.txt" + provides: "System prompt scritto IN italiano per generazione caroselli" + key_links: + - from: "backend/services/calendar_service.py" + to: "backend/constants.py" + via: "Importa PERSUASION_DISTRIBUTION e SCHWARTZ_DISTRIBUTION" + pattern: "from.*constants.*import" + - from: "backend/services/format_selector.py" + to: "backend/data/format_mapping.json" + via: "Carica mapping da file JSON" + pattern: "format_mapping" + - from: "backend/services/prompt_service.py" + to: "backend/config.py" + via: "Usa PROMPTS_PATH per localizzare file .txt" + pattern: "PROMPTS_PATH" +--- + + +Creare i servizi core del dominio: CalendarService (distribuzione 13 post PN + Schwartz + nicchie + date), FormatSelector (mapping tipo x livello -> formato), PromptService (carica/compila prompt .txt), e definire le costanti fondamentali (CANVA_FIELDS, distribuzioni). + +Purpose: Costruire la logica di dominio pura (zero dipendenza LLM) che orchestra il calendario editoriale e prepara i prompt per la generazione. Queste sono le fondamenta su cui LLMService e CSVBuilder si appoggeranno. + +Output: Servizi Python testabili indipendentemente, 5 prompt .txt in italiano, schema Pydantic per slot calendario e post generato, costante CANVA_FIELDS locked. + + + +@C:\Users\miche\.claude/get-shit-done/workflows/execute-plan.md +@C:\Users\miche\.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/research/ARCHITECTURE.md +@.planning/research/PITFALLS.md +@.planning/phases/01-core-generation-pipeline/01-CONTEXT.md + + + + + + Task 1: Costanti di dominio, Pydantic schemas, FormatSelector + + backend/constants.py + backend/schemas/__init__.py + backend/schemas/calendar.py + backend/schemas/generate.py + backend/services/__init__.py + backend/services/format_selector.py + backend/data/format_mapping.json + + + 1. Creare backend/constants.py con le costanti LOCKED del progetto: + + PERSUASION_DISTRIBUTION — distribuzione per ciclo di 13 post: + - "valore": 4 + - "storytelling": 2 + - "news": 2 + - "riprova_sociale": 3 + - "coinvolgimento": 1 + - "promozione": 1 + + SCHWARTZ_DISTRIBUTION — distribuzione livelli per 13 post: + - L5: 3 post (storytelling + news) + - L4: 3 post (valore + storytelling) + - L3: 4 post (valore + riprova) + - L2: 2 post (riprova + coinvolgimento) + - L1: 1 post (promozione) + Nota: L5+L4=6, L3=4, L2=2, L1=1 come da requirement CAL-02. + + CANVA_FIELDS — lista ORDINATA dei nomi colonna CSV per Canva Bulk Create: + Metadati: campagna, fase_campagna, tipo_contenuto, formato_narrativo, funzione, livello_schwartz, target_nicchia, data_pub_suggerita + Slide (8 slide x 3 campi = 24): + cover_title, cover_subtitle, cover_image_keyword + s2_headline, s2_body, s2_image_keyword + s3_headline, s3_body, s3_image_keyword + s4_headline, s4_body, s4_image_keyword + s5_headline, s5_body, s5_image_keyword + s6_headline, s6_body, s6_image_keyword + s7_headline, s7_body, s7_image_keyword + cta_text, cta_subtext, cta_image_keyword + Extra: caption_instagram + Totale: 8 metadati + 24 slide + 1 caption = 33 colonne + + FORMATI_NARRATIVI — lista dei 7 formati: PAS, AIDA, BAB, Listicle, Storytelling, Dato_Implicazione, Obiezione_Risposta + + FUNZIONI_CONTENUTO — le 4 funzioni: Intrattenere, Educare, Persuadere, Convertire + + FASI_CAMPAGNA — le 4 fasi: Attira, Cattura, Coinvolgi, Converti + + NICCHIE_DEFAULT — lista default: ["generico", "dentisti", "avvocati", "ecommerce", "local_business", "agenzie"] + + POST_PER_CICLO = 13 + + Nota: usare _image_keyword (non _image_url) per le colonne immagine — l'URL verra' solo con Unsplash in Phase 4. Per ora il CSV contiene keyword testuali. + + 2. Creare backend/schemas/__init__.py (vuoto). + + 3. Creare backend/schemas/calendar.py con Pydantic models: + - CalendarSlot: indice (int), tipo_contenuto (str), livello_schwartz (str), formato_narrativo (str), funzione (str), fase_campagna (str), target_nicchia (str), data_pub_suggerita (str, formato YYYY-MM-DD), topic (Optional[str], default None — verra' generato dall'LLM o overridden dall'utente) + - CalendarRequest: obiettivo_campagna (str), settimane (int, default 2), nicchie (Optional[list[str]]), frequenza_post (int, default 3 — post a settimana), data_inizio (Optional[str]) + - CalendarResponse: campagna (str), slots (list[CalendarSlot]), totale_post (int) + + 4. Creare backend/schemas/generate.py con Pydantic models: + - SlideContent: headline (str), body (str), image_keyword (str) + - GeneratedPost: cover_title (str), cover_subtitle (str), cover_image_keyword (str), slides (list[SlideContent] — 6 slide centrali s2-s7), cta_text (str), cta_subtext (str), cta_image_keyword (str), caption_instagram (str) + - GenerateRequest: slot (CalendarSlot), obiettivo_campagna (str), brand_name (Optional[str]), tono (Optional[str]) + - PostResult: slot_index (int), status (Literal["success", "failed", "pending"]), post (Optional[GeneratedPost]), error (Optional[str]) + - GenerateResponse: campagna (str), results (list[PostResult]), total (int), success_count (int), failed_count (int) + + 5. Creare backend/data/format_mapping.json: + Matrice tipo_contenuto x livello_schwartz -> formato_narrativo. + Struttura: { "valore": { "L5": "Listicle", "L4": "PAS", "L3": "PAS", "L2": "Obiezione_Risposta", "L1": "AIDA" }, ... } + Coprire tutte le 6 combinazioni tipo x 5 livelli con i 7 formati disponibili, scegliendo il formato piu' efficace per ogni combinazione. + + 6. Creare backend/services/__init__.py (vuoto). + + 7. Creare backend/services/format_selector.py: + - class FormatSelector con __init__ che carica format_mapping.json + - Metodo select_format(tipo_contenuto: str, livello_schwartz: str) -> str che ritorna il formato narrativo + - Fallback a "PAS" se combinazione non trovata + - Metodo get_mapping() -> dict per esporre la tabella completa + + + - backend/constants.py: CANVA_FIELDS ha esattamente 33 elementi, PERSUASION_DISTRIBUTION somma a 13, SCHWARTZ_DISTRIBUTION somma a 13 + - backend/schemas/calendar.py: CalendarSlot importabile, CalendarRequest ha campo obiettivo_campagna + - backend/schemas/generate.py: GeneratedPost ha slides come list[SlideContent], PostResult ha campo status + - backend/data/format_mapping.json: contiene tutte le 6 chiavi tipo_contenuto, ciascuna con 5 livelli + - backend/services/format_selector.py: FormatSelector ha metodo select_format + + + Costanti di dominio locked (CANVA_FIELDS, PERSUASION_DISTRIBUTION, SCHWARTZ_DISTRIBUTION). Pydantic schemas per calendario e generazione definiti. FormatSelector carica mapping da JSON e mappa tipo x livello -> formato. Tutto il dominio ha tipi espliciti, nessuna stringa magica. + + + + + Task 2: CalendarService, PromptService, e prompt .txt in italiano + + backend/services/calendar_service.py + backend/services/prompt_service.py + backend/data/prompts/system_prompt.txt + backend/data/prompts/pas_valore.txt + backend/data/prompts/listicle_valore.txt + backend/data/prompts/bab_storytelling.txt + backend/data/prompts/aida_promozione.txt + backend/data/prompts/dato_news.txt + + + 1. Creare backend/services/calendar_service.py: + - class CalendarService + - Metodo generate_calendar(request: CalendarRequest) -> CalendarResponse: + a. Genera 13 slot con distribuzione PERSUASION_DISTRIBUTION (import da constants) + b. Assegna livelli Schwartz secondo la logica naturale: + - valore: L4 (2), L3 (2) — educare chi e' consapevole del problema/soluzioni + - storytelling: L5 (2) — attirare inconsapevoli con storie + - news: L5 (1), L4 (1) — intrattenere/educare + - riprova_sociale: L3 (2), L2 (1) — persuadere chi conosce soluzioni/prodotto + - coinvolgimento: L2 (1) — interagire con chi conosce il prodotto + - promozione: L1 (1) — convertire chi e' pronto + Verifica che totali siano: L5=3, L4=3, L3=4, L2=2, L1=1 + c. Assegna funzioni contenuto: Intrattenere (storytelling, news, coinvolgimento), Educare (valore), Persuadere (riprova_sociale), Convertire (promozione) + d. Distribuisce nelle 4 fasi campagna: Attira (L5), Cattura (L4+L3), Coinvolgi (L3+L2), Converti (L1+L2) + e. Ordina gli slot nella sequenza campagna (Attira -> Cattura -> Coinvolgi -> Converti) + f. Calcola date_pub_suggerita dalla data_inizio con la frequenza specificata (default: 3 post/settimana, lun-mer-ven) + g. Assegna nicchie con rotazione: 50% generico, 50% verticali in rotazione dalla lista nicchie + - Usa FormatSelector internamente per assegnare formato_narrativo a ogni slot + - Metodo statico _distribute_niches(slots, nicchie) per la logica rotazione + + 2. Creare backend/services/prompt_service.py: + - class PromptService(__init__ riceve prompts_dir: Path) + - list_prompts() -> list[str]: elenca tutti i .txt nella directory + - load_prompt(name: str) -> str: carica contenuto file .txt + - compile_prompt(name: str, variables: dict[str, str]) -> str: carica e sostituisce {{variabile}} con valori dal dict + - Usa doppia graffa {{variabile}} come delimitatore (Pitfall 7) + - Solleva ValueError se una variabile nel template non ha corrispondenza nel dict + - save_prompt(name: str, content: str) -> None: salva contenuto (per Phase 2 editor) + - get_required_variables(name: str) -> list[str]: parse il template e ritorna lista variabili {{...}} trovate + + 3. Creare i 5 prompt base + system prompt, TUTTI scritti IN italiano (Pitfall 8): + + backend/data/prompts/system_prompt.txt: + - Ruolo: esperto di content marketing B2B per PMI italiane + - Tono: diretto, provocatorio ma costruttivo, usa il "tu" + - Target: imprenditori e manager italiani + - Regole: "cosa fare" mai "come farlo", benefici concreti, evitare jargon + - Lingua: italiano naturale, NON tradotto dall'inglese + - Output: JSON strutturato con i campi specificati nello schema + + backend/data/prompts/pas_valore.txt (formato PAS per post valore): + - Sezioni: SYSTEM (ref system_prompt), USER, OUTPUT_SCHEMA + - Variabili: {{obiettivo_campagna}}, {{target_nicchia}}, {{livello_schwartz}}, {{topic}}, {{brand_name}} + - Istruzioni PAS: Problema -> Agitazione -> Soluzione + - Output schema JSON esplicito con campi GeneratedPost + + backend/data/prompts/listicle_valore.txt (formato Listicle per post valore): + - Variabili: stesse di PAS + - Istruzioni Listicle: lista numerata di consigli pratici + - Enfasi su valore educativo + + backend/data/prompts/bab_storytelling.txt (formato BAB per storytelling): + - Variabili: stesse + enfasi emotiva + - Istruzioni BAB: Before -> After -> Bridge + - Tono narrativo, storia di trasformazione + + backend/data/prompts/aida_promozione.txt (formato AIDA per promo): + - Variabili: stesse + {{call_to_action}} + - Istruzioni AIDA: Attenzione -> Interesse -> Desiderio -> Azione + - Focus su conversione + + backend/data/prompts/dato_news.txt (formato Dato+Implicazione per news): + - Variabili: stesse + - Istruzioni: dato/statistica -> cosa significa -> come agire + - Focus su urgenza informata + + TUTTI i prompt devono: + - Essere scritti interamente in italiano (istruzioni E esempi) + - Usare {{variabile}} per tutti i parametri dinamici + - Specificare l'output JSON schema esplicito con i campi di GeneratedPost + - Includere istruzioni sul tono (tu, diretto, concreto) + - Specificare il numero di slide e la struttura (cover, 6 slide centrali, CTA) + + + - CalendarService.generate_calendar() con CalendarRequest(obiettivo_campagna="test", settimane=2) produce CalendarResponse con esattamente 13 slot + - Distribuzione PN: contare tipi -> 4 valore, 2 storytelling, 2 news, 3 riprova, 1 coinvolgimento, 1 promo + - Distribuzione Schwartz: contare livelli -> L5=3, L4=3, L3=4, L2=2, L1=1 + - PromptService.list_prompts() ritorna almeno 6 file (system + 5 base) + - PromptService.compile_prompt("pas_valore", {"obiettivo_campagna": "test", ...}) sostituisce tutte le variabili senza errori + - Tutti i prompt .txt contengono SOLO testo italiano, nessuna istruzione in inglese + - Nessun prompt contiene numeri hardcoded per slide count — usano {{num_slides}} o la struttura e' definita nell'output schema + + + CalendarService genera 13 slot con distribuzione PN e Schwartz corretta, assegna fasi campagna, calcola date, ruota nicchie. PromptService carica e compila prompt con variabili {{...}}. 5 prompt base + system prompt scritti IN italiano, con output JSON schema esplicito. Nessun valore hardcoded nei template. + + + + + + +1. CANVA_FIELDS ha 33 elementi e corrisponde allo schema in PROJECT.md (+ caption_instagram) +2. CalendarService produce 13 slot con distribuzione verificabile contando tipi e livelli +3. Ogni prompt .txt usa solo {{variabile}} e non contiene numeri/strutture hardcoded +4. FormatSelector mappa tutte le 30 combinazioni (6 tipi x 5 livelli) +5. Pydantic schemas validano correttamente un esempio di GeneratedPost + + + +- CalendarService genera calendario con distribuzione PN e Schwartz corretta +- FormatSelector mappa ogni combinazione tipo x livello a un formato +- PromptService carica, lista, compila prompt con sostituzione variabili +- 5 prompt base + system prompt esistono come file .txt in italiano +- CANVA_FIELDS locked come costante con tutti i nomi colonna +- Pydantic schemas per calendario, generazione, e risultati definiti + + + +After completion, create `.planning/phases/01-core-generation-pipeline/01-02-SUMMARY.md` + diff --git a/.planning/phases/01-core-generation-pipeline/01-03-PLAN.md b/.planning/phases/01-core-generation-pipeline/01-03-PLAN.md new file mode 100644 index 0000000..0b4625a --- /dev/null +++ b/.planning/phases/01-core-generation-pipeline/01-03-PLAN.md @@ -0,0 +1,265 @@ +--- +phase: 01-core-generation-pipeline +plan: 03 +type: execute +wave: 2 +depends_on: ["01-01", "01-02"] +files_modified: + - backend/services/llm_service.py + - backend/services/csv_builder.py + - backend/services/generation_pipeline.py + - backend/routers/calendar.py + - backend/routers/generate.py + - backend/routers/export.py + - backend/routers/settings.py + - backend/schemas/settings.py + - backend/main.py +autonomous: true + +must_haves: + truths: + - "LLMService chiama Claude API con retry e backoff, gestisce 429 leggendo retry-after header" + - "LLMService valida il JSON output con Pydantic GeneratedPost e rigetta output malformato" + - "CSVBuilder produce CSV con encoding utf-8-sig, header CANVA_FIELDS, e caratteri italiani intatti" + - "GenerationPipeline genera 13 post con per-item error isolation: un fallimento non blocca il batch" + - "API endpoint POST /api/calendar/generate ritorna CalendarResponse con 13 slot" + - "API endpoint POST /api/generate/bulk ritorna GenerateResponse con risultati per-item (success/failed)" + - "API endpoint GET /api/export/{job_id}/csv scarica file CSV con Content-Disposition attachment" + - "API endpoint GET /api/settings ritorna configurazione corrente, PUT /api/settings salva" + artifacts: + - path: "backend/services/llm_service.py" + provides: "LLMService con retry, backoff, rate limit, JSON validation via Pydantic" + contains: "class LLMService" + - path: "backend/services/csv_builder.py" + provides: "CSVBuilder con CANVA_FIELDS header locked, utf-8-sig encoding, write to disk" + contains: "class CSVBuilder" + - path: "backend/services/generation_pipeline.py" + provides: "GenerationPipeline che orchestra calendario -> LLM -> CSV con per-item isolation" + contains: "class GenerationPipeline" + - path: "backend/routers/generate.py" + provides: "POST /api/generate/bulk e POST /api/generate/single endpoints" + contains: "router = APIRouter" + - path: "backend/routers/calendar.py" + provides: "POST /api/calendar/generate endpoint" + contains: "router = APIRouter" + - path: "backend/routers/export.py" + provides: "GET /api/export/{job_id}/csv endpoint con FileResponse" + contains: "router = APIRouter" + - path: "backend/routers/settings.py" + provides: "GET/PUT /api/settings endpoint per API key e configurazione" + contains: "router = APIRouter" + key_links: + - from: "backend/services/llm_service.py" + to: "Claude API" + via: "anthropic.Anthropic client con retry loop" + pattern: "client\\.messages\\.create" + - from: "backend/services/csv_builder.py" + to: "backend/constants.py" + via: "Importa CANVA_FIELDS per header CSV" + pattern: "CANVA_FIELDS" + - from: "backend/services/generation_pipeline.py" + to: "backend/services/llm_service.py" + via: "Chiama generate() per ogni slot con try/except per-item" + pattern: "llm_service\\.generate" + - from: "backend/routers/generate.py" + to: "backend/services/generation_pipeline.py" + via: "Chiama pipeline.generate_bulk()" + pattern: "pipeline\\.generate" + - from: "backend/main.py" + to: "backend/routers/" + via: "include_router per tutti i routers" + pattern: "include_router" +--- + + +Creare la pipeline LLM completa: LLMService (Claude API con retry/backoff/rate limit), CSVBuilder (CSV Canva-compatibile con utf-8-sig), GenerationPipeline (orchestrazione con per-item error isolation), e tutti gli API routers (calendar, generate, export, settings). + +Purpose: Connettere i servizi di dominio (Plan 02) alla Claude API e al CSV export, creando gli endpoint REST che il frontend (Plan 04) consumera'. Indirizzare i pitfall 1 (soft failures), 3 (CSV encoding), 5 (all-or-nothing batch), e 6 (rate limit). + +Output: API backend completa che accetta una richiesta di generazione calendario, chiama Claude per ogni post con error isolation, produce un CSV scaricabile con encoding corretto. + + + +@C:\Users\miche\.claude/get-shit-done/workflows/execute-plan.md +@C:\Users\miche\.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/research/STACK.md +@.planning/research/ARCHITECTURE.md +@.planning/research/PITFALLS.md +@.planning/phases/01-core-generation-pipeline/01-CONTEXT.md +@.planning/phases/01-core-generation-pipeline/01-01-SUMMARY.md +@.planning/phases/01-core-generation-pipeline/01-02-SUMMARY.md + + + + + + Task 1: LLMService, CSVBuilder, GenerationPipeline + + backend/services/llm_service.py + backend/services/csv_builder.py + backend/services/generation_pipeline.py + + + 1. Creare backend/services/llm_service.py: + - class LLMService(__init__ riceve api_key: str, model: str = "claude-sonnet-4-5", max_retries: int = 3, inter_request_delay: float = 2.0) + - Usa anthropic.Anthropic(api_key=api_key) per il client + - Metodo generate(system_prompt: str, user_prompt: str, response_schema: Type[BaseModel]) -> BaseModel: + a. Loop retry con max_retries tentativi + b. Chiama client.messages.create con model, max_tokens=4096, system=system_prompt, messages user + c. Parse response.content[0].text come JSON + d. Valida con response_schema.model_validate_json(raw_text) + e. Gestione errori SPECIFICA: + - anthropic.RateLimitError (429): leggi response header retry-after, attendi quel tempo esatto, poi riprova + - anthropic.APIStatusError (5xx): exponential backoff con jitter (base_delay * 2^attempt + random 0-1s) + - Pydantic ValidationError: riprova UNA volta con istruzione correttiva appesa al prompt ("Il tuo output precedente non era JSON valido. Rispondi SOLO con JSON valido secondo lo schema.") + - Qualsiasi altra eccezione: non ritentare, solleva + f. Dopo ogni chiamata riuscita, applica inter_request_delay (time.sleep) per rispettare OTPM Tier 1 + - Metodo generate_topic(system_prompt: str, obiettivo: str, tipo_contenuto: str, nicchia: str) -> str: + Genera un topic specifico per lo slot dato l'obiettivo campagna. Ritorna una stringa topic. + - Log strutturato: ogni chiamata logga model, tokens in/out, tempo risposta, tentativo N/max + + 2. Creare backend/services/csv_builder.py: + - class CSVBuilder + - Importa CANVA_FIELDS da backend.constants + - Metodo build_csv(posts: list[PostResult], calendar: CalendarResponse, job_id: str) -> Path: + a. Filtra solo PostResult con status="success" + b. Per ogni post success, mappa GeneratedPost + CalendarSlot -> dict con chiavi CANVA_FIELDS + - Metadati: campagna, fase_campagna, tipo_contenuto, formato_narrativo, funzione, livello_schwartz, target_nicchia, data_pub_suggerita (da CalendarSlot) + - Cover: cover_title, cover_subtitle, cover_image_keyword (da GeneratedPost) + - Slide s2-s7: headline, body, image_keyword (da GeneratedPost.slides[0..5]) + - CTA: cta_text, cta_subtext, cta_image_keyword (da GeneratedPost) + - caption_instagram (da GeneratedPost) + c. Scrive CSV su disco in OUTPUTS_PATH / f"{job_id}.csv" + d. Encoding: utf-8-sig (BOM) — CRITICO per Excel + caratteri italiani (Pitfall 3) + e. Usa csv.DictWriter con fieldnames=CANVA_FIELDS + f. Ritorna il Path del file scritto + - Metodo build_csv_content(posts, calendar, job_id) -> str: come sopra ma ritorna stringa CSV (per preview) + + 3. Creare backend/services/generation_pipeline.py: + - class GenerationPipeline(__init__ riceve llm_service: LLMService, prompt_service: PromptService, calendar_service: CalendarService, format_selector: FormatSelector, csv_builder: CSVBuilder) + - Metodo generate_bulk(request: CalendarRequest, api_key: str) -> GenerateResponse: + a. Genera calendario via calendar_service.generate_calendar(request) + b. Per ogni slot del calendario: + - Genera topic via llm_service.generate_topic() se slot.topic e' None + - Seleziona il prompt template corretto in base a formato_narrativo (es. "pas_valore" per PAS + valore) + - Compila il prompt con variabili (obiettivo, nicchia, livello, topic, brand) + - Chiama llm_service.generate(system_prompt, user_prompt, GeneratedPost) + - Se successo: PostResult(status="success", post=risultato) + - Se fallimento: PostResult(status="failed", error=str(e)) + - CRITICO Pitfall 5: ogni slot in try/except INDIVIDUALE. Un fallimento NON blocca il loop. + c. Genera job_id (UUID) + d. Chiama csv_builder.build_csv() con i risultati + e. Salva job metadata in OUTPUTS_PATH / f"{job_id}.json" (per ricaricamento) + f. Ritorna GenerateResponse con risultati per-item + - Metodo generate_single(slot: CalendarSlot, obiettivo: str, api_key: str) -> PostResult: + Genera un singolo post. Utile per rigenerazione di post falliti. + - Metodo _select_prompt_template(formato: str, tipo: str) -> str: + Mappa formato_narrativo + tipo_contenuto al nome del file prompt (es. "PAS" + "valore" -> "pas_valore") + Fallback a "pas_valore" se template specifico non esiste + + + - LLMService ha gestione specifica per RateLimitError con lettura retry-after + - LLMService ha inter_request_delay dopo ogni chiamata riuscita + - CSVBuilder importa CANVA_FIELDS e usa encoding='utf-8-sig' + - GenerationPipeline ha try/except dentro il loop per-slot (non attorno al loop intero) + - GenerationPipeline salva job metadata JSON per ricaricamento + + + LLMService chiama Claude con retry, backoff specifico per 429, e validation Pydantic. CSVBuilder produce CSV con encoding utf-8-sig e header CANVA_FIELDS locked. GenerationPipeline orchestra il flusso completo con per-item error isolation. + + + + + Task 2: API routers e wiring in main.py + + backend/routers/calendar.py + backend/routers/generate.py + backend/routers/export.py + backend/routers/settings.py + backend/schemas/settings.py + backend/main.py + + + 1. Creare backend/schemas/settings.py: + - class Settings(BaseModel): api_key (Optional[str]), llm_model (str, default "claude-sonnet-4-5"), nicchie_attive (list[str], default NICCHIE_DEFAULT), lingua (str, default "italiano"), frequenza_post (int, default 3), brand_name (Optional[str]), tono (Optional[str], default "diretto e concreto") + - Settings salvate in CONFIG_PATH / "settings.json" + + 2. Creare backend/routers/calendar.py: + - router = APIRouter(prefix="/api/calendar", tags=["calendar"]) + - POST /generate: riceve CalendarRequest, usa CalendarService, ritorna CalendarResponse + - GET /formats: ritorna il mapping formati da FormatSelector + + 3. Creare backend/routers/generate.py: + - router = APIRouter(prefix="/api/generate", tags=["generate"]) + - POST /bulk: riceve CalendarRequest (+ eventuali topic overrides), usa GenerationPipeline.generate_bulk(), ritorna GenerateResponse + - Prima verifica che API key sia configurata (da settings), ritorna 400 se mancante + - Ritorna 200 anche con risultati parziali (alcuni failed) — il frontend gestisce lo stato per-item + - POST /single: riceve GenerateRequest (singolo slot), usa GenerationPipeline.generate_single(), ritorna PostResult + - GET /job/{job_id}: ritorna i risultati salvati di un job precedente (carica da OUTPUTS_PATH/{job_id}.json) + + 4. Creare backend/routers/export.py: + - router = APIRouter(prefix="/api/export", tags=["export"]) + - GET /{job_id}/csv: trova file CSV in OUTPUTS_PATH/{job_id}.csv + - Ritorna FileResponse con media_type="text/csv; charset=utf-8" + - Headers: Content-Disposition: attachment; filename="postgenerator_{job_id}.csv" + - Ritorna 404 se file non esiste + + 5. Creare backend/routers/settings.py: + - router = APIRouter(prefix="/api/settings", tags=["settings"]) + - GET /: carica settings da settings.json, ritorna Settings (con api_key mascherata: mostra solo ultimi 4 caratteri) + - PUT /: riceve Settings, salva in settings.json, ritorna Settings aggiornate + - GET /status: ritorna {"api_key_configured": bool, "llm_model": str} — usato dal frontend per abilitare/disabilitare pulsante genera + + 6. Aggiornare backend/main.py: + - Importare tutti i router: calendar, generate, export, settings + - app.include_router() per ciascuno, PRIMA del mount SPAStaticFiles + - Aggiungere lifespan/startup che: + a. Crea directory dati se non esistono + b. Copia prompts default in DATA_PATH/prompts/ se la directory e' vuota (primo avvio) + - Ordine mount: health -> routers -> SPAStaticFiles (ULTIMO) + + NOTA: I routers sono thin — validano input, chiamano service, ritornano output. Nessuna logica di business nei routers. + + + - backend/main.py include tutti e 4 i router PRIMA del mount SPAStaticFiles + - POST /api/calendar/generate accetta CalendarRequest body + - POST /api/generate/bulk verifica API key prima di procedere + - GET /api/export/{job_id}/csv ha Content-Disposition header + - GET /api/settings/status ritorna api_key_configured boolean + - Nessun router contiene logica di business (solo validazione + chiamata service + return) + + + 4 routers API (calendar, generate, export, settings) creati e montati in main.py. Ogni endpoint ha schema request/response Pydantic. Generate verifica API key. Export serve CSV con header corretti. Settings gestisce configurazione persistente. + + + + + + +1. `python -c "from backend.services.llm_service import LLMService; print('OK')"` — importa senza errori +2. `python -c "from backend.services.csv_builder import CSVBuilder; print('OK')"` — importa senza errori +3. `python -c "from backend.main import app; print(app.routes)"` — mostra tutti i routes registrati +4. CSVBuilder usa encoding='utf-8-sig' nel codice (grep) +5. GenerationPipeline ha try/except PER SINGOLO slot, non attorno al loop +6. LLMService gestisce RateLimitError separatamente dalle altre eccezioni +7. Nessun import circolare tra moduli + + + +- LLMService chiama Claude con retry specifico per 429 e validation Pydantic +- CSVBuilder produce CSV con utf-8-sig encoding e CANVA_FIELDS header +- GenerationPipeline ha per-item error isolation +- 4 API routers montati e funzionali +- Settings endpoint gestisce API key +- Job results salvati su disco per ricaricamento + + + +After completion, create `.planning/phases/01-core-generation-pipeline/01-03-SUMMARY.md` + diff --git a/.planning/phases/01-core-generation-pipeline/01-04-PLAN.md b/.planning/phases/01-core-generation-pipeline/01-04-PLAN.md new file mode 100644 index 0000000..54b8dbf --- /dev/null +++ b/.planning/phases/01-core-generation-pipeline/01-04-PLAN.md @@ -0,0 +1,328 @@ +--- +phase: 01-core-generation-pipeline +plan: 04 +type: execute +wave: 3 +depends_on: ["01-01", "01-02", "01-03"] +files_modified: + - frontend/src/App.tsx + - frontend/src/pages/Dashboard.tsx + - frontend/src/pages/GenerateCalendar.tsx + - frontend/src/pages/GenerateSingle.tsx + - frontend/src/pages/OutputReview.tsx + - frontend/src/pages/Settings.tsx + - frontend/src/components/Layout.tsx + - frontend/src/components/Sidebar.tsx + - frontend/src/components/PostCard.tsx + - frontend/src/components/SlideViewer.tsx + - frontend/src/components/ProgressIndicator.tsx + - frontend/src/components/BadgePN.tsx + - frontend/src/components/BadgeSchwartz.tsx + - frontend/src/api/client.ts + - frontend/src/api/hooks.ts + - frontend/src/types.ts +autonomous: false + +must_haves: + truths: + - "L'utente vede una Dashboard con link a Genera Calendario, Genera Singolo Post, e Impostazioni" + - "L'utente compila il form Genera Calendario (obiettivo + settimane) e clicca Genera — vede progress indicator per ogni post" + - "L'utente vede i 13 post generati come griglia di card con badge colorati PN e Schwartz" + - "L'utente clicca su una card e vede le slide con navigazione frecce laterali + caption Instagram" + - "L'utente puo' modificare il testo di una slide inline (click to edit) e le modifiche si riflettono nel CSV" + - "L'utente scarica il CSV cliccando un pulsante Download CSV" + - "Post falliti appaiono come card errore con pulsante Riprova" + - "Il pulsante Genera e' disabilitato se API key non configurata, con messaggio che rimanda a Impostazioni" + - "La pagina Impostazioni permette di configurare API key, modello LLM, nicchie, frequenza" + artifacts: + - path: "frontend/src/pages/Dashboard.tsx" + provides: "Dashboard con stato e navigazione" + min_lines: 30 + - path: "frontend/src/pages/GenerateCalendar.tsx" + provides: "Form generazione calendario con progress indicator" + min_lines: 80 + - path: "frontend/src/pages/OutputReview.tsx" + provides: "Griglia card post con espansione slide, edit inline, download CSV" + min_lines: 100 + - path: "frontend/src/pages/Settings.tsx" + provides: "Form configurazione con API key, modello, nicchie" + min_lines: 50 + - path: "frontend/src/components/PostCard.tsx" + provides: "Card singolo post con badge PN e Schwartz" + min_lines: 40 + - path: "frontend/src/components/SlideViewer.tsx" + provides: "Navigazione slide con frecce laterali" + min_lines: 50 + - path: "frontend/src/api/hooks.ts" + provides: "TanStack Query hooks per tutte le API calls" + contains: "useQuery" + key_links: + - from: "frontend/src/api/hooks.ts" + to: "/postgenerator/api" + via: "apiFetch con endpoint completo" + pattern: "apiFetch" + - from: "frontend/src/pages/GenerateCalendar.tsx" + to: "frontend/src/api/hooks.ts" + via: "useMutation per POST /api/generate/bulk" + pattern: "useMutation" + - from: "frontend/src/pages/OutputReview.tsx" + to: "frontend/src/components/PostCard.tsx" + via: "Render griglia di PostCard" + pattern: "PostCard" + - from: "frontend/src/components/PostCard.tsx" + to: "frontend/src/components/SlideViewer.tsx" + via: "Espansione card mostra SlideViewer" + pattern: "SlideViewer" +--- + + +Creare l'intera Web UI: Dashboard, form Genera Calendario con progress indicator, Output Review con griglia card + navigazione slide + edit inline + download CSV, form Genera Singolo Post, e pagina Impostazioni. + +Purpose: Dare all'utente l'interfaccia per interagire con la pipeline di generazione. Questa e' l'unica via di accesso al sistema — senza UI il backend non e' utilizzabile. + +Output: SPA React completa con tutte le pagine e componenti per il workflow: configura -> genera -> rivedi -> scarica. + + + +@C:\Users\miche\.claude/get-shit-done/workflows/execute-plan.md +@C:\Users\miche\.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-core-generation-pipeline/01-CONTEXT.md +@.planning/phases/01-core-generation-pipeline/01-01-SUMMARY.md +@.planning/phases/01-core-generation-pipeline/01-02-SUMMARY.md +@.planning/phases/01-core-generation-pipeline/01-03-SUMMARY.md + + + + + + Task 1: Layout, routing, API hooks, tipi TypeScript, pagine Settings e Dashboard + + frontend/src/types.ts + frontend/src/api/client.ts + frontend/src/api/hooks.ts + frontend/src/components/Layout.tsx + frontend/src/components/Sidebar.tsx + frontend/src/App.tsx + frontend/src/pages/Dashboard.tsx + frontend/src/pages/Settings.tsx + + + 1. Creare frontend/src/types.ts con i tipi TypeScript che rispecchiano gli schemas Pydantic del backend: + - CalendarSlot, CalendarRequest, CalendarResponse + - SlideContent, GeneratedPost, PostResult, GenerateResponse + - Settings (api_key, llm_model, nicchie_attive, lingua, frequenza_post, brand_name, tono) + - SettingsStatus (api_key_configured: boolean, llm_model: string) + + 2. Aggiornare frontend/src/api/client.ts (gia' creato in Plan 01): + - Aggiungere metodi specifici: apiGet, apiPost, apiPut + - Gestione download file: apiDownload(endpoint) -> Blob + + 3. Creare frontend/src/api/hooks.ts con TanStack Query hooks: + - useSettings(): useQuery per GET /api/settings + - useSettingsStatus(): useQuery per GET /api/settings/status + - useUpdateSettings(): useMutation per PUT /api/settings + - useGenerateCalendar(): useMutation per POST /api/generate/bulk — ritorna GenerateResponse + - useGenerateSingle(): useMutation per POST /api/generate/single + - useJobResults(jobId): useQuery per GET /api/generate/job/{jobId} + - useDownloadCsv(): funzione che chiama apiDownload e triggera download browser + - useFormats(): useQuery per GET /api/calendar/formats + + 4. Creare frontend/src/components/Layout.tsx: + - Layout wrapper con Sidebar a sinistra e contenuto a destra + - Responsive: sidebar collassabile su mobile + - Tailwind CSS per styling + + 5. Creare frontend/src/components/Sidebar.tsx: + - Logo/titolo "PostGenerator" in alto + - Navigazione con link: Dashboard, Genera Calendario, Genera Singolo Post, Impostazioni + - Usa react-router-dom NavLink con activeClassName + - Icone da lucide-react + + 6. Aggiornare frontend/src/App.tsx: + - BrowserRouter con basename="/postgenerator" + - QueryClientProvider con QueryClient + - Layout wrapping + - Routes: / -> Dashboard, /genera -> GenerateCalendar, /genera-singolo -> GenerateSingle, /risultati/:jobId -> OutputReview, /impostazioni -> Settings + + 7. Creare frontend/src/pages/Dashboard.tsx: + - Card di benvenuto con nome progetto e descrizione breve + - Quick actions: "Genera Calendario" (link), "Impostazioni" (link) + - Se API key non configurata: banner prominente "Configura la tua API key Claude nelle Impostazioni" + - Se ci sono job recenti (check /api/generate/job/latest): mostra ultimo job con link a risultati + - Usa useSettingsStatus() per verificare API key + + 8. Creare frontend/src/pages/Settings.tsx: + - Form con campi: + - API Key Claude (input password con toggle visibilita', mostra solo ultimi 4 char se gia' configurata) + - Modello LLM (select: claude-sonnet-4-5, claude-haiku-3-5) + - Brand Name (input text, opzionale) + - Tono di voce (input text, default "diretto e concreto") + - Nicchie attive (lista checkbox con le nicchie default + possibilita' di aggiungerne) + - Frequenza post settimanale (number input, 1-7, default 3) + - Pulsante Salva con feedback (successo/errore) + - Usa useSettings() e useUpdateSettings() + + NOTA DESIGN: Claude ha discrezione sul design UI. Scegliere uno stile pulito e professionale, NON il solito template generico con gradienti viola. Colori suggeriti: palette terrosa o industriale adatta a B2B. + + + - frontend/src/App.tsx ha BrowserRouter con basename="/postgenerator" + - frontend/src/api/hooks.ts ha almeno 7 hooks (settings, settingsStatus, updateSettings, generateCalendar, generateSingle, jobResults, downloadCsv) + - frontend/src/types.ts ha CalendarSlot, GeneratedPost, PostResult, Settings + - Sidebar ha 4 link di navigazione + - Settings ha campo API key con tipo password + - Dashboard mostra banner se API key non configurata + - npm run build completa senza errori TypeScript + + + Layout con sidebar, routing completo, API hooks TanStack Query per tutti gli endpoint, tipi TypeScript, Dashboard con stato API key, Settings form funzionale. + + + + + Task 2: Genera Calendario, Output Review con card/slide/edit, Genera Singolo Post + + frontend/src/pages/GenerateCalendar.tsx + frontend/src/pages/GenerateSingle.tsx + frontend/src/pages/OutputReview.tsx + frontend/src/components/PostCard.tsx + frontend/src/components/SlideViewer.tsx + frontend/src/components/ProgressIndicator.tsx + frontend/src/components/BadgePN.tsx + frontend/src/components/BadgeSchwartz.tsx + + + 1. Creare frontend/src/components/BadgePN.tsx: + - Badge colorato per tipo Persuasion Nurturing + - Colori distinti per ogni tipo: valore (blu), storytelling (viola), news (verde), riprova_sociale (arancione), coinvolgimento (giallo), promozione (rosso) + - Mostra label (es. "Valore", "Storytelling") + + 2. Creare frontend/src/components/BadgeSchwartz.tsx: + - Badge per livello Schwartz (L1-L5) + - Colori progressivi (L5 chiaro -> L1 scuro) per indicare vicinanza all'acquisto + - Tooltip con descrizione livello + + 3. Creare frontend/src/components/ProgressIndicator.tsx: + - Mostra progresso generazione bulk: "Post 3/13 in generazione..." + - Barra di progresso visuale + - Lista dei post con stato: pending (grigio), processing (spinner), success (verde check), failed (rosso X) + - Animazione per il post attualmente in generazione + + 4. Creare frontend/src/components/PostCard.tsx: + - Card per singolo post nel risultato + - Mostra: indice, tipo PN (badge), livello Schwartz (badge), formato narrativo, nicchia, data + - Se status=success: mostra cover_title come titolo card, click per espandere + - Se status=failed: card con sfondo rosso chiaro, icona errore, messaggio errore, pulsante "Riprova" + - Pulsante Riprova chiama useGenerateSingle() per rigenerare quel slot + - Click su card success -> espande per mostrare SlideViewer + + 5. Creare frontend/src/components/SlideViewer.tsx: + - Visualizzazione slide-by-slide con navigazione frecce laterali (stile Instagram stories) + - Mostra: slide corrente N/8, headline, body, image_keyword + - Freccia sinistra/destra per navigare + - Ogni campo testo e' EDITABILE inline: click per trasformare in input/textarea + - Le modifiche aggiornano lo stato locale (PostResult) che verra' usato per il CSV download + - Sotto le slide: caption Instagram in textarea editabile + - Keyboard navigation: frecce sinistra/destra per cambiare slide + + 6. Creare frontend/src/pages/GenerateCalendar.tsx: + - Form con campi: + - Obiettivo campagna (textarea, obbligatorio, placeholder "Es: Aumentare awareness sull'AI per PMI italiane") + - Settimane (number, default 2, range 1-4) + - Brand name (input, opzionale — prende default da Settings) + - Tono di voce (input, opzionale — prende default da Settings) + - Nicchie (multi-select o checkbox, prende default da Settings) + - Pulsante "Genera Calendario" con stati: + - Se API key non configurata: disabilitato, messaggio "Configura API key nelle Impostazioni" + - Se configurata: abilitato, al click mostra ProgressIndicator + - Usa useSettingsStatus() per controllare API key + - Usa useGenerateCalendar() mutation + - Al completamento (successo o parziale): redirect a OutputReview con jobId + + 7. Creare frontend/src/pages/OutputReview.tsx: + - Riceve jobId da route params + - Carica risultati con useJobResults(jobId) + - Header con: nome campagna, conteggio successi/falliti, pulsante "Download CSV" + - Griglia di PostCard (3 colonne desktop, 2 tablet, 1 mobile) + - PostCard espandibile con SlideViewer + - Pulsante "Download CSV": + - Chiama useDownloadCsv(jobId) + - Se ci sono post falliti: mostra nota "Il CSV contiene solo i N post generati con successo" + - Se tutti i post sono falliti: messaggio "Nessun post generato con successo. Riprova." + + 8. Creare frontend/src/pages/GenerateSingle.tsx: + - Form per generare un singolo post manualmente: + - Topic (textarea, obbligatorio) + - Tipo contenuto (select: valore, storytelling, news, riprova_sociale, coinvolgimento, promozione) + - Livello Schwartz (select: L1-L5) + - Nicchia (select dalla lista) + - Formato narrativo (select, auto-compilato in base a tipo+livello ma override possibile) + - Al submit: chiama useGenerateSingle() + - Mostra risultato con SlideViewer direttamente nella pagina + - Pulsante download CSV per singolo post + + GESTIONE STATO EDIT INLINE (importante): + - OutputReview mantiene stato locale dei post (copia di GenerateResponse) + - Quando utente edita una slide in SlideViewer, aggiorna lo stato locale + - Il pulsante Download CSV usa lo stato locale aggiornato (non l'originale dal server) + - Questo significa che il CSV riflette le modifiche dell'utente + + + - GenerateCalendar.tsx ha form con obiettivo e settimane, pulsante disabilitato senza API key + - OutputReview.tsx mostra griglia di PostCard con badge PN e Schwartz + - SlideViewer.tsx ha navigazione frecce e campi editabili inline + - PostCard.tsx ha stati distinti per success e failed, con pulsante Riprova + - ProgressIndicator.tsx mostra progresso per-item + - GenerateSingle.tsx ha form con select per tipo, livello, nicchia, formato + - npm run build completa senza errori TypeScript + + + Web UI completa: form Genera Calendario con progress, griglia risultati con card/badge, SlideViewer con navigazione e edit inline, download CSV con modifiche utente, Genera Singolo Post, gestione errori per-item con Riprova. + + + + + + Web UI completa con tutte le pagine: Dashboard, Genera Calendario, Output Review, Genera Singolo Post, Impostazioni. Inclusi progress indicator, griglia card con badge, navigazione slide, edit inline. + + + 1. Verificare che `cd frontend && npm run build` completa senza errori + 2. Verificare che la struttura delle pagine e componenti sia coerente + 3. Controllare che il routing in App.tsx abbia tutte le 5 route + 4. Verificare che l'API client usi /postgenerator/api come base + 5. Opzionale: se deployato, navigare su https://lab.mlhub.it/postgenerator/ e verificare che la UI si carichi + + Digita "approved" se la UI e' accettabile, oppure descrivi i problemi da correggere + + + + + +1. `cd frontend && npm run build` produce dist/ senza errori +2. App.tsx ha 5 route definite con BrowserRouter basename="/postgenerator" +3. Tutti i componenti importano tipi da types.ts (non definiscono tipi inline) +4. API hooks usano /postgenerator/api come base URL +5. PostCard ha due varianti visive: success (espandibile) e failed (errore + riprova) +6. SlideViewer supporta edit inline e navigazione frecce +7. GenerateCalendar disabilita pulsante se API key non configurata +8. OutputReview fa download CSV con le modifiche inline dell'utente + + + +- Dashboard mostra stato API key e quick actions +- Settings permette configurazione API key, modello, nicchie, frequenza +- Genera Calendario ha form, progress indicator, redirect a risultati +- Output Review mostra griglia card con badge, slide viewer con edit, download CSV +- Post falliti mostrano errore e pulsante Riprova +- Genera Singolo Post ha form completo con anteprima risultato +- Build frontend completa senza errori + + + +After completion, create `.planning/phases/01-core-generation-pipeline/01-04-SUMMARY.md` +