fix(01): revise plans based on checker feedback
- Fix CSV-01 column count: 32 -> 33 (8 meta + 24 slide + 1 caption)
- Add TopicResult Pydantic model + topic_generator.txt prompt
- Make bulk generation async with background task + polling endpoint
- Add POST /api/export/{job_id}/csv for inline edit CSV download
- Split Plan 01-04 Task 2 into 2a/2b/2c (badges, slideviewer, pages)
- Update ProgressIndicator to use polling on /status endpoint
- Add --yes flag and frontend/ prerequisite note to Plan 01-01
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
### CSV & Export
|
### CSV & Export
|
||||||
|
|
||||||
- [ ] **CSV-01**: CSV con header completo compatibile Canva Bulk Create (32 colonne: 8 metadati + 8 slide x 3 campi)
|
- [ ] **CSV-01**: CSV con header completo compatibile Canva Bulk Create (33 colonne: 8 metadati + 24 slide (8 slide x 3 campi) + 1 caption_instagram)
|
||||||
- [ ] **CSV-02**: Encoding utf-8-sig (BOM) per compatibilita' Excel/Windows
|
- [ ] **CSV-02**: Encoding utf-8-sig (BOM) per compatibilita' Excel/Windows
|
||||||
- [ ] **CSV-03**: Campi metadato (campagna, fase, tipo, formato, funzione, livello, nicchia, data) inclusi per analisi
|
- [ ] **CSV-03**: Campi metadato (campagna, fase, tipo, formato, funzione, livello, nicchia, data) inclusi per analisi
|
||||||
- [ ] **CSV-04**: Download CSV dalla Web UI
|
- [ ] **CSV-04**: Download CSV dalla Web UI
|
||||||
|
|||||||
@@ -179,7 +179,9 @@ Output: Container Docker buildabile che serve una pagina React vuota su / e risp
|
|||||||
</files>
|
</files>
|
||||||
<action>
|
<action>
|
||||||
1. Creare il progetto React + TypeScript con Vite:
|
1. Creare il progetto React + TypeScript con Vite:
|
||||||
- cd al progetto, eseguire: npm create vite@latest frontend -- --template react-ts
|
- PREREQUISITO: la directory frontend/ NON deve esistere. Se esiste, rimuoverla prima: rm -rf frontend
|
||||||
|
- cd al progetto, eseguire: npm create vite@latest frontend -- --template react-ts --yes
|
||||||
|
- Il flag --yes evita prompt interattivi che bloccherebbero l'esecuzione autonoma
|
||||||
- Questo genera la struttura base
|
- Questo genera la struttura base
|
||||||
|
|
||||||
2. Installare dipendenze frontend:
|
2. Installare dipendenze frontend:
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ files_modified:
|
|||||||
- backend/schemas/generate.py
|
- backend/schemas/generate.py
|
||||||
- backend/data/format_mapping.json
|
- backend/data/format_mapping.json
|
||||||
- backend/data/prompts/system_prompt.txt
|
- backend/data/prompts/system_prompt.txt
|
||||||
|
- backend/data/prompts/topic_generator.txt
|
||||||
- backend/data/prompts/pas_valore.txt
|
- backend/data/prompts/pas_valore.txt
|
||||||
- backend/data/prompts/listicle_valore.txt
|
- backend/data/prompts/listicle_valore.txt
|
||||||
- backend/data/prompts/bab_storytelling.txt
|
- backend/data/prompts/bab_storytelling.txt
|
||||||
@@ -47,7 +48,7 @@ must_haves:
|
|||||||
provides: "CalendarSlot, CalendarRequest, CalendarResponse Pydantic models"
|
provides: "CalendarSlot, CalendarRequest, CalendarResponse Pydantic models"
|
||||||
contains: "class CalendarSlot"
|
contains: "class CalendarSlot"
|
||||||
- path: "backend/schemas/generate.py"
|
- path: "backend/schemas/generate.py"
|
||||||
provides: "SlideContent, GeneratedPost Pydantic models per output LLM e CSV"
|
provides: "SlideContent, GeneratedPost, TopicResult Pydantic models per output LLM e CSV"
|
||||||
contains: "class GeneratedPost"
|
contains: "class GeneratedPost"
|
||||||
- path: "backend/data/format_mapping.json"
|
- path: "backend/data/format_mapping.json"
|
||||||
provides: "Tabella mapping tipo_contenuto x livello_schwartz -> formato narrativo"
|
provides: "Tabella mapping tipo_contenuto x livello_schwartz -> formato narrativo"
|
||||||
@@ -158,6 +159,7 @@ Output: Servizi Python testabili indipendentemente, 5 prompt .txt in italiano, s
|
|||||||
4. Creare backend/schemas/generate.py con Pydantic models:
|
4. Creare backend/schemas/generate.py con Pydantic models:
|
||||||
- SlideContent: headline (str), body (str), image_keyword (str)
|
- 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)
|
- 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)
|
||||||
|
- TopicResult: topic (str) — Pydantic model per validare output LLM della generazione topic. Usato da LLMService.generate_topic() con lo stesso loop retry/validation delle altre generazioni.
|
||||||
- GenerateRequest: slot (CalendarSlot), obiettivo_campagna (str), brand_name (Optional[str]), tono (Optional[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])
|
- 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)
|
- GenerateResponse: campagna (str), results (list[PostResult]), total (int), success_count (int), failed_count (int)
|
||||||
@@ -178,7 +180,7 @@ Output: Servizi Python testabili indipendentemente, 5 prompt .txt in italiano, s
|
|||||||
<verify>
|
<verify>
|
||||||
- backend/constants.py: CANVA_FIELDS ha esattamente 33 elementi, PERSUASION_DISTRIBUTION somma a 13, SCHWARTZ_DISTRIBUTION somma a 13
|
- 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/calendar.py: CalendarSlot importabile, CalendarRequest ha campo obiettivo_campagna
|
||||||
- backend/schemas/generate.py: GeneratedPost ha slides come list[SlideContent], PostResult ha campo status
|
- backend/schemas/generate.py: GeneratedPost ha slides come list[SlideContent], PostResult ha campo status, TopicResult ha campo topic (str)
|
||||||
- backend/data/format_mapping.json: contiene tutte le 6 chiavi tipo_contenuto, ciascuna con 5 livelli
|
- 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
|
- backend/services/format_selector.py: FormatSelector ha metodo select_format
|
||||||
</verify>
|
</verify>
|
||||||
@@ -193,6 +195,7 @@ Output: Servizi Python testabili indipendentemente, 5 prompt .txt in italiano, s
|
|||||||
backend/services/calendar_service.py
|
backend/services/calendar_service.py
|
||||||
backend/services/prompt_service.py
|
backend/services/prompt_service.py
|
||||||
backend/data/prompts/system_prompt.txt
|
backend/data/prompts/system_prompt.txt
|
||||||
|
backend/data/prompts/topic_generator.txt
|
||||||
backend/data/prompts/pas_valore.txt
|
backend/data/prompts/pas_valore.txt
|
||||||
backend/data/prompts/listicle_valore.txt
|
backend/data/prompts/listicle_valore.txt
|
||||||
backend/data/prompts/bab_storytelling.txt
|
backend/data/prompts/bab_storytelling.txt
|
||||||
@@ -240,6 +243,13 @@ Output: Servizi Python testabili indipendentemente, 5 prompt .txt in italiano, s
|
|||||||
- Lingua: italiano naturale, NON tradotto dall'inglese
|
- Lingua: italiano naturale, NON tradotto dall'inglese
|
||||||
- Output: JSON strutturato con i campi specificati nello schema
|
- Output: JSON strutturato con i campi specificati nello schema
|
||||||
|
|
||||||
|
backend/data/prompts/topic_generator.txt (prompt per generazione topic):
|
||||||
|
- Variabili: {{obiettivo_campagna}}, {{tipo_contenuto}}, {{livello_schwartz}}, {{target_nicchia}}, {{fase_campagna}}
|
||||||
|
- Istruzioni: genera UN topic specifico e concreto per un post Instagram carosello
|
||||||
|
- Il topic deve essere rilevante per l'obiettivo campagna, il tipo di contenuto e la nicchia
|
||||||
|
- Output: JSON con campo "topic" (stringa, max 100 caratteri)
|
||||||
|
- Scritto IN italiano come tutti gli altri prompt
|
||||||
|
|
||||||
backend/data/prompts/pas_valore.txt (formato PAS per post valore):
|
backend/data/prompts/pas_valore.txt (formato PAS per post valore):
|
||||||
- Sezioni: SYSTEM (ref system_prompt), USER, OUTPUT_SCHEMA
|
- Sezioni: SYSTEM (ref system_prompt), USER, OUTPUT_SCHEMA
|
||||||
- Variabili: {{obiettivo_campagna}}, {{target_nicchia}}, {{livello_schwartz}}, {{topic}}, {{brand_name}}
|
- Variabili: {{obiettivo_campagna}}, {{target_nicchia}}, {{livello_schwartz}}, {{topic}}, {{brand_name}}
|
||||||
@@ -277,13 +287,13 @@ Output: Servizi Python testabili indipendentemente, 5 prompt .txt in italiano, s
|
|||||||
- CalendarService.generate_calendar() con CalendarRequest(obiettivo_campagna="test", settimane=2) produce CalendarResponse con esattamente 13 slot
|
- 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 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
|
- 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.list_prompts() ritorna almeno 7 file (system + topic_generator + 5 base)
|
||||||
- PromptService.compile_prompt("pas_valore", {"obiettivo_campagna": "test", ...}) sostituisce tutte le variabili senza errori
|
- 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
|
- 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
|
- Nessun prompt contiene numeri hardcoded per slide count — usano {{num_slides}} o la struttura e' definita nell'output schema
|
||||||
</verify>
|
</verify>
|
||||||
<done>
|
<done>
|
||||||
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.
|
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 + topic_generator prompt scritti IN italiano, con output JSON schema esplicito. Nessun valore hardcoded nei template.
|
||||||
</done>
|
</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,10 @@ must_haves:
|
|||||||
- "CSVBuilder produce CSV con encoding utf-8-sig, header CANVA_FIELDS, e caratteri italiani intatti"
|
- "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"
|
- "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/calendar/generate ritorna CalendarResponse con 13 slot"
|
||||||
- "API endpoint POST /api/generate/bulk ritorna GenerateResponse con risultati per-item (success/failed)"
|
- "API endpoint POST /api/generate/bulk avvia generazione come background task e ritorna job_id immediatamente"
|
||||||
- "API endpoint GET /api/export/{job_id}/csv scarica file CSV con Content-Disposition attachment"
|
- "API endpoint GET /api/generate/job/{job_id}/status ritorna progresso in tempo reale (completed/total/current_post) per polling"
|
||||||
|
- "API endpoint GET /api/export/{job_id}/csv scarica file CSV originale con Content-Disposition attachment"
|
||||||
|
- "API endpoint POST /api/export/{job_id}/csv accetta dati modificati dall'utente e rigenera CSV con le modifiche inline"
|
||||||
- "API endpoint GET /api/settings ritorna configurazione corrente, PUT /api/settings salva"
|
- "API endpoint GET /api/settings ritorna configurazione corrente, PUT /api/settings salva"
|
||||||
artifacts:
|
artifacts:
|
||||||
- path: "backend/services/llm_service.py"
|
- path: "backend/services/llm_service.py"
|
||||||
@@ -37,13 +39,13 @@ must_haves:
|
|||||||
provides: "GenerationPipeline che orchestra calendario -> LLM -> CSV con per-item isolation"
|
provides: "GenerationPipeline che orchestra calendario -> LLM -> CSV con per-item isolation"
|
||||||
contains: "class GenerationPipeline"
|
contains: "class GenerationPipeline"
|
||||||
- path: "backend/routers/generate.py"
|
- path: "backend/routers/generate.py"
|
||||||
provides: "POST /api/generate/bulk e POST /api/generate/single endpoints"
|
provides: "POST /api/generate/bulk (async background task), POST /api/generate/single, GET /api/generate/job/{job_id}/status (polling)"
|
||||||
contains: "router = APIRouter"
|
contains: "router = APIRouter"
|
||||||
- path: "backend/routers/calendar.py"
|
- path: "backend/routers/calendar.py"
|
||||||
provides: "POST /api/calendar/generate endpoint"
|
provides: "POST /api/calendar/generate endpoint"
|
||||||
contains: "router = APIRouter"
|
contains: "router = APIRouter"
|
||||||
- path: "backend/routers/export.py"
|
- path: "backend/routers/export.py"
|
||||||
provides: "GET /api/export/{job_id}/csv endpoint con FileResponse"
|
provides: "GET /api/export/{job_id}/csv (originale), POST /api/export/{job_id}/csv (con modifiche inline)"
|
||||||
contains: "router = APIRouter"
|
contains: "router = APIRouter"
|
||||||
- path: "backend/routers/settings.py"
|
- path: "backend/routers/settings.py"
|
||||||
provides: "GET/PUT /api/settings endpoint per API key e configurazione"
|
provides: "GET/PUT /api/settings endpoint per API key e configurazione"
|
||||||
@@ -53,6 +55,10 @@ must_haves:
|
|||||||
to: "Claude API"
|
to: "Claude API"
|
||||||
via: "anthropic.Anthropic client con retry loop"
|
via: "anthropic.Anthropic client con retry loop"
|
||||||
pattern: "client\\.messages\\.create"
|
pattern: "client\\.messages\\.create"
|
||||||
|
- from: "backend/services/llm_service.py"
|
||||||
|
to: "backend/schemas/generate.py"
|
||||||
|
via: "generate_topic() valida output con TopicResult(BaseModel)"
|
||||||
|
pattern: "TopicResult"
|
||||||
- from: "backend/services/csv_builder.py"
|
- from: "backend/services/csv_builder.py"
|
||||||
to: "backend/constants.py"
|
to: "backend/constants.py"
|
||||||
via: "Importa CANVA_FIELDS per header CSV"
|
via: "Importa CANVA_FIELDS per header CSV"
|
||||||
@@ -120,8 +126,13 @@ Output: API backend completa che accetta una richiesta di generazione calendario
|
|||||||
- 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.")
|
- 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
|
- Qualsiasi altra eccezione: non ritentare, solleva
|
||||||
f. Dopo ogni chiamata riuscita, applica inter_request_delay (time.sleep) per rispettare OTPM Tier 1
|
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:
|
- Metodo generate_topic(system_prompt: str, obiettivo: str, tipo_contenuto: str, nicchia: str, fase_campagna: str) -> str:
|
||||||
Genera un topic specifico per lo slot dato l'obiettivo campagna. Ritorna una stringa topic.
|
Genera un topic specifico per lo slot dato l'obiettivo campagna.
|
||||||
|
Usa lo stesso pattern di validazione delle altre generazioni:
|
||||||
|
a. Carica prompt da topic_generator.txt via PromptService
|
||||||
|
b. Chiama generate() con response_schema=TopicResult (Pydantic model definito in schemas/generate.py)
|
||||||
|
c. Ritorna result.topic (stringa estratta dal model validato)
|
||||||
|
Questo garantisce che anche la generazione topic passi per il loop retry/validation JSON, coerente con LLM-02.
|
||||||
- Log strutturato: ogni chiamata logga model, tokens in/out, tempo risposta, tentativo N/max
|
- Log strutturato: ogni chiamata logga model, tokens in/out, tempo risposta, tentativo N/max
|
||||||
|
|
||||||
2. Creare backend/services/csv_builder.py:
|
2. Creare backend/services/csv_builder.py:
|
||||||
@@ -143,20 +154,32 @@ Output: API backend completa che accetta una richiesta di generazione calendario
|
|||||||
|
|
||||||
3. Creare backend/services/generation_pipeline.py:
|
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)
|
- 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:
|
- Dict in-memory _jobs: dict[str, JobStatus] per tracciare progresso dei job in corso
|
||||||
a. Genera calendario via calendar_service.generate_calendar(request)
|
- Dataclass JobStatus: job_id (str), status (Literal["running", "completed", "failed"]), total (int), completed (int), current_post (int), results (list[PostResult]), calendar (Optional[CalendarResponse]), error (Optional[str])
|
||||||
b. Per ogni slot del calendario:
|
- Metodo generate_bulk_async(request: CalendarRequest, api_key: str) -> str:
|
||||||
- Genera topic via llm_service.generate_topic() se slot.topic e' None
|
a. Genera job_id (UUID)
|
||||||
|
b. Genera calendario via calendar_service.generate_calendar(request)
|
||||||
|
c. Inizializza _jobs[job_id] con status="running", total=len(slots), completed=0
|
||||||
|
d. Lancia _run_generation(job_id, calendario, request) come asyncio.create_task (background)
|
||||||
|
e. Ritorna job_id immediatamente
|
||||||
|
- Metodo _run_generation(job_id, calendar, request) — async background:
|
||||||
|
a. Per ogni slot del calendario:
|
||||||
|
- Aggiorna _jobs[job_id].current_post = indice corrente
|
||||||
|
- Genera topic via llm_service.generate_topic(system_prompt, obiettivo, slot.tipo_contenuto, slot.target_nicchia, slot.fase_campagna) se slot.topic e' None
|
||||||
- Seleziona il prompt template corretto in base a formato_narrativo (es. "pas_valore" per PAS + valore)
|
- 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)
|
- Compila il prompt con variabili (obiettivo, nicchia, livello, topic, brand)
|
||||||
- Chiama llm_service.generate(system_prompt, user_prompt, GeneratedPost)
|
- Chiama llm_service.generate(system_prompt, user_prompt, GeneratedPost)
|
||||||
- Se successo: PostResult(status="success", post=risultato)
|
- Se successo: PostResult(status="success", post=risultato)
|
||||||
- Se fallimento: PostResult(status="failed", error=str(e))
|
- Se fallimento: PostResult(status="failed", error=str(e))
|
||||||
- CRITICO Pitfall 5: ogni slot in try/except INDIVIDUALE. Un fallimento NON blocca il loop.
|
- CRITICO Pitfall 5: ogni slot in try/except INDIVIDUALE. Un fallimento NON blocca il loop.
|
||||||
c. Genera job_id (UUID)
|
- Aggiorna _jobs[job_id].completed += 1 e appendi risultato
|
||||||
d. Chiama csv_builder.build_csv() con i risultati
|
b. Chiama csv_builder.build_csv() con i risultati
|
||||||
e. Salva job metadata in OUTPUTS_PATH / f"{job_id}.json" (per ricaricamento)
|
c. Salva job metadata in OUTPUTS_PATH / f"{job_id}.json" (per ricaricamento e persistenza)
|
||||||
f. Ritorna GenerateResponse con risultati per-item
|
d. Aggiorna _jobs[job_id].status = "completed"
|
||||||
|
- Metodo get_job_status(job_id: str) -> JobStatus:
|
||||||
|
Ritorna lo stato corrente del job (per polling). Se non in memory, carica da disco ({job_id}.json).
|
||||||
|
- Metodo get_job_results(job_id: str) -> GenerateResponse:
|
||||||
|
Ritorna risultati completi. Carica da _jobs o da disco.
|
||||||
- Metodo generate_single(slot: CalendarSlot, obiettivo: str, api_key: str) -> PostResult:
|
- Metodo generate_single(slot: CalendarSlot, obiettivo: str, api_key: str) -> PostResult:
|
||||||
Genera un singolo post. Utile per rigenerazione di post falliti.
|
Genera un singolo post. Utile per rigenerazione di post falliti.
|
||||||
- Metodo _select_prompt_template(formato: str, tipo: str) -> str:
|
- Metodo _select_prompt_template(formato: str, tipo: str) -> str:
|
||||||
@@ -166,12 +189,17 @@ Output: API backend completa che accetta una richiesta di generazione calendario
|
|||||||
<verify>
|
<verify>
|
||||||
- LLMService ha gestione specifica per RateLimitError con lettura retry-after
|
- LLMService ha gestione specifica per RateLimitError con lettura retry-after
|
||||||
- LLMService ha inter_request_delay dopo ogni chiamata riuscita
|
- LLMService ha inter_request_delay dopo ogni chiamata riuscita
|
||||||
|
- LLMService.generate_topic() chiama generate() con TopicResult come response_schema
|
||||||
- CSVBuilder importa CANVA_FIELDS e usa encoding='utf-8-sig'
|
- CSVBuilder importa CANVA_FIELDS e usa encoding='utf-8-sig'
|
||||||
|
- GenerationPipeline.generate_bulk_async() ritorna str (job_id), non GenerateResponse
|
||||||
|
- GenerationPipeline ha _run_generation come async background task con asyncio.create_task
|
||||||
- GenerationPipeline ha try/except dentro il loop per-slot (non attorno al loop intero)
|
- GenerationPipeline ha try/except dentro il loop per-slot (non attorno al loop intero)
|
||||||
|
- GenerationPipeline._jobs dict traccia progresso real-time per ogni job
|
||||||
|
- GenerationPipeline.get_job_status() ritorna JobStatus con completed/total/current_post
|
||||||
- GenerationPipeline salva job metadata JSON per ricaricamento
|
- GenerationPipeline salva job metadata JSON per ricaricamento
|
||||||
</verify>
|
</verify>
|
||||||
<done>
|
<done>
|
||||||
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.
|
LLMService chiama Claude con retry, backoff specifico per 429, e validation Pydantic (incluso generate_topic con TopicResult). CSVBuilder produce CSV con encoding utf-8-sig e header CANVA_FIELDS locked. GenerationPipeline orchestra il flusso completo come background task async con progresso real-time tracciato in _jobs dict e per-item error isolation.
|
||||||
</done>
|
</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
@@ -197,18 +225,28 @@ Output: API backend completa che accetta una richiesta di generazione calendario
|
|||||||
|
|
||||||
3. Creare backend/routers/generate.py:
|
3. Creare backend/routers/generate.py:
|
||||||
- router = APIRouter(prefix="/api/generate", tags=["generate"])
|
- router = APIRouter(prefix="/api/generate", tags=["generate"])
|
||||||
- POST /bulk: riceve CalendarRequest (+ eventuali topic overrides), usa GenerationPipeline.generate_bulk(), ritorna GenerateResponse
|
- POST /bulk: riceve CalendarRequest (+ eventuali topic overrides), usa GenerationPipeline.generate_bulk_async()
|
||||||
- Prima verifica che API key sia configurata (da settings), ritorna 400 se mancante
|
- 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
|
- Ritorna IMMEDIATAMENTE 202 Accepted con {"job_id": "uuid"} — la generazione continua in background
|
||||||
|
- Il frontend usa polling su /job/{job_id}/status per seguire il progresso
|
||||||
|
- GET /job/{job_id}/status: ritorna lo stato corrente del job per polling
|
||||||
|
- Risposta: {"job_id", "status": "running|completed|failed", "total": 13, "completed": 5, "current_post": 6, "results": [...completed results...]}
|
||||||
|
- Frontend chiama ogni 2 secondi finche' status != "running"
|
||||||
|
- GET /job/{job_id}: ritorna i risultati completi di un job (carica da GenerationPipeline.get_job_results())
|
||||||
- POST /single: riceve GenerateRequest (singolo slot), usa GenerationPipeline.generate_single(), ritorna PostResult
|
- 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:
|
4. Creare backend/routers/export.py:
|
||||||
- router = APIRouter(prefix="/api/export", tags=["export"])
|
- router = APIRouter(prefix="/api/export", tags=["export"])
|
||||||
- GET /{job_id}/csv: trova file CSV in OUTPUTS_PATH/{job_id}.csv
|
- GET /{job_id}/csv: trova file CSV originale in OUTPUTS_PATH/{job_id}.csv
|
||||||
- Ritorna FileResponse con media_type="text/csv; charset=utf-8"
|
- Ritorna FileResponse con media_type="text/csv; charset=utf-8"
|
||||||
- Headers: Content-Disposition: attachment; filename="postgenerator_{job_id}.csv"
|
- Headers: Content-Disposition: attachment; filename="postgenerator_{job_id}.csv"
|
||||||
- Ritorna 404 se file non esiste
|
- Ritorna 404 se file non esiste
|
||||||
|
- POST /{job_id}/csv: accetta body JSON con i post modificati dall'utente (inline edits)
|
||||||
|
- Riceve: {"results": list[PostResult]} con i dati aggiornati dal frontend
|
||||||
|
- Rigenera il CSV usando CSVBuilder.build_csv() con i dati modificati
|
||||||
|
- Salva come OUTPUTS_PATH/{job_id}_edited.csv
|
||||||
|
- Ritorna il file CSV rigenerato con Content-Disposition attachment
|
||||||
|
- Questo risolve il problema delle modifiche inline perse al download: il frontend invia lo stato locale modificato e riceve un CSV aggiornato
|
||||||
|
|
||||||
5. Creare backend/routers/settings.py:
|
5. Creare backend/routers/settings.py:
|
||||||
- router = APIRouter(prefix="/api/settings", tags=["settings"])
|
- router = APIRouter(prefix="/api/settings", tags=["settings"])
|
||||||
@@ -229,13 +267,15 @@ Output: API backend completa che accetta una richiesta di generazione calendario
|
|||||||
<verify>
|
<verify>
|
||||||
- backend/main.py include tutti e 4 i router PRIMA del mount SPAStaticFiles
|
- backend/main.py include tutti e 4 i router PRIMA del mount SPAStaticFiles
|
||||||
- POST /api/calendar/generate accetta CalendarRequest body
|
- POST /api/calendar/generate accetta CalendarRequest body
|
||||||
- POST /api/generate/bulk verifica API key prima di procedere
|
- POST /api/generate/bulk verifica API key, ritorna 202 con job_id (non attende completamento)
|
||||||
- GET /api/export/{job_id}/csv ha Content-Disposition header
|
- GET /api/generate/job/{job_id}/status ritorna status, total, completed, current_post per polling
|
||||||
|
- GET /api/export/{job_id}/csv ha Content-Disposition header (file originale)
|
||||||
|
- POST /api/export/{job_id}/csv accetta results modificati e rigenera CSV
|
||||||
- GET /api/settings/status ritorna api_key_configured boolean
|
- GET /api/settings/status ritorna api_key_configured boolean
|
||||||
- Nessun router contiene logica di business (solo validazione + chiamata service + return)
|
- Nessun router contiene logica di business (solo validazione + chiamata service + return)
|
||||||
</verify>
|
</verify>
|
||||||
<done>
|
<done>
|
||||||
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.
|
4 routers API (calendar, generate, export, settings) creati e montati in main.py. Ogni endpoint ha schema request/response Pydantic. Generate e' async (202 + polling via /status). Export serve CSV originale (GET) e CSV con modifiche inline (POST). Settings gestisce configurazione persistente.
|
||||||
</done>
|
</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
@@ -244,17 +284,23 @@ Output: API backend completa che accetta una richiesta di generazione calendario
|
|||||||
<verification>
|
<verification>
|
||||||
1. `python -c "from backend.services.llm_service import LLMService; print('OK')"` — importa senza errori
|
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
|
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
|
3. `python -c "from backend.main import app; print(app.routes)"` — mostra tutti i routes registrati incluso /api/generate/job/{job_id}/status
|
||||||
4. CSVBuilder usa encoding='utf-8-sig' nel codice (grep)
|
4. CSVBuilder usa encoding='utf-8-sig' nel codice (grep)
|
||||||
5. GenerationPipeline ha try/except PER SINGOLO slot, non attorno al loop
|
5. GenerationPipeline ha try/except PER SINGOLO slot, non attorno al loop
|
||||||
6. LLMService gestisce RateLimitError separatamente dalle altre eccezioni
|
6. GenerationPipeline.generate_bulk_async() ritorna job_id (str), non GenerateResponse
|
||||||
7. Nessun import circolare tra moduli
|
7. LLMService gestisce RateLimitError separatamente dalle altre eccezioni
|
||||||
|
8. LLMService.generate_topic() usa TopicResult come response_schema (non ritorna raw string)
|
||||||
|
9. POST /api/export/{job_id}/csv endpoint esiste e accetta body con results
|
||||||
|
10. Nessun import circolare tra moduli
|
||||||
</verification>
|
</verification>
|
||||||
|
|
||||||
<success_criteria>
|
<success_criteria>
|
||||||
- LLMService chiama Claude con retry specifico per 429 e validation Pydantic
|
- LLMService chiama Claude con retry specifico per 429 e validation Pydantic (incluso generate_topic con TopicResult)
|
||||||
- CSVBuilder produce CSV con utf-8-sig encoding e CANVA_FIELDS header
|
- CSVBuilder produce CSV con utf-8-sig encoding e CANVA_FIELDS header
|
||||||
- GenerationPipeline ha per-item error isolation
|
- GenerationPipeline ha per-item error isolation e background task async
|
||||||
|
- POST /api/generate/bulk ritorna 202 con job_id, generazione continua in background
|
||||||
|
- GET /api/generate/job/{job_id}/status fornisce progresso real-time per polling
|
||||||
|
- POST /api/export/{job_id}/csv accetta dati modificati e rigenera CSV
|
||||||
- 4 API routers montati e funzionali
|
- 4 API routers montati e funzionali
|
||||||
- Settings endpoint gestisce API key
|
- Settings endpoint gestisce API key
|
||||||
- Job results salvati su disco per ricaricamento
|
- Job results salvati su disco per ricaricamento
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ autonomous: false
|
|||||||
must_haves:
|
must_haves:
|
||||||
truths:
|
truths:
|
||||||
- "L'utente vede una Dashboard con link a Genera Calendario, Genera Singolo Post, e Impostazioni"
|
- "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 compila il form Genera Calendario (obiettivo + settimane) e clicca Genera — vede progress indicator che si aggiorna in tempo reale tramite polling ogni 2s su /api/generate/job/{job_id}/status"
|
||||||
- "L'utente vede i 13 post generati come griglia di card con badge colorati PN e Schwartz"
|
- "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 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 puo' modificare il testo di una slide inline (click to edit) e le modifiche si riflettono nel CSV scaricato tramite POST /api/export/{job_id}/csv"
|
||||||
- "L'utente scarica il CSV cliccando un pulsante Download CSV"
|
- "L'utente scarica il CSV cliccando un pulsante Download CSV"
|
||||||
- "Post falliti appaiono come card errore con pulsante Riprova"
|
- "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"
|
- "Il pulsante Genera e' disabilitato se API key non configurata, con messaggio che rimanda a Impostazioni"
|
||||||
@@ -63,8 +63,16 @@ must_haves:
|
|||||||
pattern: "apiFetch"
|
pattern: "apiFetch"
|
||||||
- from: "frontend/src/pages/GenerateCalendar.tsx"
|
- from: "frontend/src/pages/GenerateCalendar.tsx"
|
||||||
to: "frontend/src/api/hooks.ts"
|
to: "frontend/src/api/hooks.ts"
|
||||||
via: "useMutation per POST /api/generate/bulk"
|
via: "useMutation per POST /api/generate/bulk (async, ritorna job_id)"
|
||||||
pattern: "useMutation"
|
pattern: "useGenerateCalendar"
|
||||||
|
- from: "frontend/src/components/ProgressIndicator.tsx"
|
||||||
|
to: "frontend/src/api/hooks.ts"
|
||||||
|
via: "useJobStatus(jobId) polling ogni 2s su GET /api/generate/job/{job_id}/status"
|
||||||
|
pattern: "useJobStatus"
|
||||||
|
- from: "frontend/src/pages/OutputReview.tsx"
|
||||||
|
to: "frontend/src/api/hooks.ts"
|
||||||
|
via: "useDownloadEditedCsv per POST /api/export/{job_id}/csv con edits inline"
|
||||||
|
pattern: "useDownloadEditedCsv"
|
||||||
- from: "frontend/src/pages/OutputReview.tsx"
|
- from: "frontend/src/pages/OutputReview.tsx"
|
||||||
to: "frontend/src/components/PostCard.tsx"
|
to: "frontend/src/components/PostCard.tsx"
|
||||||
via: "Render griglia di PostCard"
|
via: "Render griglia di PostCard"
|
||||||
@@ -116,6 +124,7 @@ Output: SPA React completa con tutte le pagine e componenti per il workflow: con
|
|||||||
1. Creare frontend/src/types.ts con i tipi TypeScript che rispecchiano gli schemas Pydantic del backend:
|
1. Creare frontend/src/types.ts con i tipi TypeScript che rispecchiano gli schemas Pydantic del backend:
|
||||||
- CalendarSlot, CalendarRequest, CalendarResponse
|
- CalendarSlot, CalendarRequest, CalendarResponse
|
||||||
- SlideContent, GeneratedPost, PostResult, GenerateResponse
|
- SlideContent, GeneratedPost, PostResult, GenerateResponse
|
||||||
|
- JobStatus (job_id: string, status: "running" | "completed" | "failed", total: number, completed: number, current_post: number, results: PostResult[])
|
||||||
- Settings (api_key, llm_model, nicchie_attive, lingua, frequenza_post, brand_name, tono)
|
- Settings (api_key, llm_model, nicchie_attive, lingua, frequenza_post, brand_name, tono)
|
||||||
- SettingsStatus (api_key_configured: boolean, llm_model: string)
|
- SettingsStatus (api_key_configured: boolean, llm_model: string)
|
||||||
|
|
||||||
@@ -127,10 +136,12 @@ Output: SPA React completa con tutte le pagine e componenti per il workflow: con
|
|||||||
- useSettings(): useQuery per GET /api/settings
|
- useSettings(): useQuery per GET /api/settings
|
||||||
- useSettingsStatus(): useQuery per GET /api/settings/status
|
- useSettingsStatus(): useQuery per GET /api/settings/status
|
||||||
- useUpdateSettings(): useMutation per PUT /api/settings
|
- useUpdateSettings(): useMutation per PUT /api/settings
|
||||||
- useGenerateCalendar(): useMutation per POST /api/generate/bulk — ritorna GenerateResponse
|
- useGenerateCalendar(): useMutation per POST /api/generate/bulk — ritorna {job_id} (async, NON GenerateResponse)
|
||||||
- useGenerateSingle(): useMutation per POST /api/generate/single
|
- useGenerateSingle(): useMutation per POST /api/generate/single
|
||||||
|
- useJobStatus(jobId): useQuery per GET /api/generate/job/{jobId}/status con refetchInterval condizionale (2000ms quando running, disabilitato quando completed/failed)
|
||||||
- useJobResults(jobId): useQuery per GET /api/generate/job/{jobId}
|
- useJobResults(jobId): useQuery per GET /api/generate/job/{jobId}
|
||||||
- useDownloadCsv(): funzione che chiama apiDownload e triggera download browser
|
- useDownloadCsv(): funzione che chiama GET /api/export/{jobId}/csv e triggera download browser (CSV originale)
|
||||||
|
- useDownloadEditedCsv(): funzione che chiama POST /api/export/{jobId}/csv con results modificati e triggera download browser (CSV con edits)
|
||||||
- useFormats(): useQuery per GET /api/calendar/formats
|
- useFormats(): useQuery per GET /api/calendar/formats
|
||||||
|
|
||||||
4. Creare frontend/src/components/Layout.tsx:
|
4. Creare frontend/src/components/Layout.tsx:
|
||||||
@@ -172,8 +183,8 @@ Output: SPA React completa con tutte le pagine e componenti per il workflow: con
|
|||||||
</action>
|
</action>
|
||||||
<verify>
|
<verify>
|
||||||
- frontend/src/App.tsx ha BrowserRouter con basename="/postgenerator"
|
- 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/api/hooks.ts ha almeno 10 hooks/functions (settings, settingsStatus, updateSettings, generateCalendar, generateSingle, jobStatus, jobResults, downloadCsv, downloadEditedCsv, formats)
|
||||||
- frontend/src/types.ts ha CalendarSlot, GeneratedPost, PostResult, Settings
|
- frontend/src/types.ts ha CalendarSlot, GeneratedPost, PostResult, JobStatus, Settings
|
||||||
- Sidebar ha 4 link di navigazione
|
- Sidebar ha 4 link di navigazione
|
||||||
- Settings ha campo API key con tipo password
|
- Settings ha campo API key con tipo password
|
||||||
- Dashboard mostra banner se API key non configurata
|
- Dashboard mostra banner se API key non configurata
|
||||||
@@ -185,16 +196,11 @@ Output: SPA React completa con tutte le pagine e componenti per il workflow: con
|
|||||||
</task>
|
</task>
|
||||||
|
|
||||||
<task type="auto">
|
<task type="auto">
|
||||||
<name>Task 2: Genera Calendario, Output Review con card/slide/edit, Genera Singolo Post</name>
|
<name>Task 2a: Badge components e PostCard</name>
|
||||||
<files>
|
<files>
|
||||||
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/BadgePN.tsx
|
||||||
frontend/src/components/BadgeSchwartz.tsx
|
frontend/src/components/BadgeSchwartz.tsx
|
||||||
|
frontend/src/components/PostCard.tsx
|
||||||
</files>
|
</files>
|
||||||
<action>
|
<action>
|
||||||
1. Creare frontend/src/components/BadgePN.tsx:
|
1. Creare frontend/src/components/BadgePN.tsx:
|
||||||
@@ -207,30 +213,78 @@ Output: SPA React completa con tutte le pagine e componenti per il workflow: con
|
|||||||
- Colori progressivi (L5 chiaro -> L1 scuro) per indicare vicinanza all'acquisto
|
- Colori progressivi (L5 chiaro -> L1 scuro) per indicare vicinanza all'acquisto
|
||||||
- Tooltip con descrizione livello
|
- Tooltip con descrizione livello
|
||||||
|
|
||||||
3. Creare frontend/src/components/ProgressIndicator.tsx:
|
3. Creare frontend/src/components/PostCard.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
|
- Card per singolo post nel risultato
|
||||||
- Mostra: indice, tipo PN (badge), livello Schwartz (badge), formato narrativo, nicchia, data
|
- 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=success: mostra cover_title come titolo card, click per espandere
|
||||||
- Se status=failed: card con sfondo rosso chiaro, icona errore, messaggio errore, pulsante "Riprova"
|
- Se status=failed: card con sfondo rosso chiaro, icona errore, messaggio errore, pulsante "Riprova"
|
||||||
- Pulsante Riprova chiama useGenerateSingle() per rigenerare quel slot
|
- Pulsante Riprova chiama useGenerateSingle() per rigenerare quel slot
|
||||||
- Click su card success -> espande per mostrare SlideViewer
|
- Click su card success -> espande per mostrare SlideViewer (SlideViewer placeholder prop per ora)
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- BadgePN.tsx ha 6 colori distinti per i tipi PN
|
||||||
|
- BadgeSchwartz.tsx ha 5 livelli con tooltip
|
||||||
|
- PostCard.tsx ha stati distinti per success e failed, con pulsante Riprova
|
||||||
|
- npm run build completa senza errori TypeScript
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
Badge PN e Schwartz con colori distinti. PostCard con stati success/failed, badge, e placeholder per SlideViewer expansion.
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
5. Creare frontend/src/components/SlideViewer.tsx:
|
<task type="auto">
|
||||||
|
<name>Task 2b: SlideViewer con inline edit e ProgressIndicator con polling</name>
|
||||||
|
<files>
|
||||||
|
frontend/src/components/SlideViewer.tsx
|
||||||
|
frontend/src/components/ProgressIndicator.tsx
|
||||||
|
frontend/src/api/hooks.ts
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
1. Creare frontend/src/components/SlideViewer.tsx:
|
||||||
- Visualizzazione slide-by-slide con navigazione frecce laterali (stile Instagram stories)
|
- Visualizzazione slide-by-slide con navigazione frecce laterali (stile Instagram stories)
|
||||||
- Mostra: slide corrente N/8, headline, body, image_keyword
|
- Mostra: slide corrente N/8, headline, body, image_keyword
|
||||||
- Freccia sinistra/destra per navigare
|
- Freccia sinistra/destra per navigare
|
||||||
- Ogni campo testo e' EDITABILE inline: click per trasformare in input/textarea
|
- 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
|
- Le modifiche aggiornano lo stato locale (PostResult) tramite callback onEdit prop
|
||||||
- Sotto le slide: caption Instagram in textarea editabile
|
- Sotto le slide: caption Instagram in textarea editabile
|
||||||
- Keyboard navigation: frecce sinistra/destra per cambiare slide
|
- Keyboard navigation: frecce sinistra/destra per cambiare slide
|
||||||
|
|
||||||
6. Creare frontend/src/pages/GenerateCalendar.tsx:
|
2. Creare frontend/src/components/ProgressIndicator.tsx:
|
||||||
|
- Riceve job_id come prop
|
||||||
|
- USA POLLING: chiama GET /api/generate/job/{job_id}/status ogni 2 secondi via useJobStatus(jobId) hook
|
||||||
|
- Mostra progresso generazione bulk: "Post {completed}/{total} in generazione..."
|
||||||
|
- Barra di progresso visuale basata su completed/total dal polling response
|
||||||
|
- Lista dei post con stato: pending (grigio), processing (spinner — il current_post), success (verde check), failed (rosso X)
|
||||||
|
- Animazione per il post attualmente in generazione (current_post dal polling)
|
||||||
|
- Quando status diventa "completed": smette di pollare, chiama callback onComplete(jobId)
|
||||||
|
|
||||||
|
3. Aggiornare frontend/src/api/hooks.ts:
|
||||||
|
- Aggiungere useJobStatus(jobId): useQuery per GET /api/generate/job/{jobId}/status con refetchInterval di 2000ms quando status e' "running", disabilitato quando "completed" o "failed"
|
||||||
|
- Aggiungere useDownloadEditedCsv(): funzione che chiama POST /api/export/{jobId}/csv con i results modificati e triggera download browser
|
||||||
|
- Aggiornare useGenerateCalendar(): mutation che chiama POST /api/generate/bulk e ritorna {job_id} (non GenerateResponse, dato che ora e' async)
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- SlideViewer.tsx ha navigazione frecce e campi editabili inline con callback onEdit
|
||||||
|
- ProgressIndicator.tsx usa useJobStatus() hook con polling ogni 2 secondi
|
||||||
|
- ProgressIndicator.tsx smette di pollare quando status != "running"
|
||||||
|
- hooks.ts ha useJobStatus con refetchInterval condizionale
|
||||||
|
- hooks.ts ha useDownloadEditedCsv che chiama POST endpoint
|
||||||
|
- npm run build completa senza errori TypeScript
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
SlideViewer con navigazione slide e edit inline via callback. ProgressIndicator usa polling real-time su /status endpoint per mostrare progresso per-item. API hooks aggiornati per async generation pattern (job_id + polling + POST CSV con edits).
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2c: Pagine GenerateCalendar, OutputReview, GenerateSingle</name>
|
||||||
|
<files>
|
||||||
|
frontend/src/pages/GenerateCalendar.tsx
|
||||||
|
frontend/src/pages/GenerateSingle.tsx
|
||||||
|
frontend/src/pages/OutputReview.tsx
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
1. Creare frontend/src/pages/GenerateCalendar.tsx:
|
||||||
- Form con campi:
|
- Form con campi:
|
||||||
- Obiettivo campagna (textarea, obbligatorio, placeholder "Es: Aumentare awareness sull'AI per PMI italiane")
|
- Obiettivo campagna (textarea, obbligatorio, placeholder "Es: Aumentare awareness sull'AI per PMI italiane")
|
||||||
- Settimane (number, default 2, range 1-4)
|
- Settimane (number, default 2, range 1-4)
|
||||||
@@ -239,23 +293,26 @@ Output: SPA React completa con tutte le pagine e componenti per il workflow: con
|
|||||||
- Nicchie (multi-select o checkbox, prende default da Settings)
|
- Nicchie (multi-select o checkbox, prende default da Settings)
|
||||||
- Pulsante "Genera Calendario" con stati:
|
- Pulsante "Genera Calendario" con stati:
|
||||||
- Se API key non configurata: disabilitato, messaggio "Configura API key nelle Impostazioni"
|
- Se API key non configurata: disabilitato, messaggio "Configura API key nelle Impostazioni"
|
||||||
- Se configurata: abilitato, al click mostra ProgressIndicator
|
- Se configurata: abilitato, al click chiama useGenerateCalendar() mutation
|
||||||
|
- FLUSSO ASYNC: al click, mutation ritorna {job_id}. La pagina mostra ProgressIndicator con job_id.
|
||||||
|
ProgressIndicator polla /status e quando status="completed" chiama onComplete che fa redirect a OutputReview con jobId.
|
||||||
- Usa useSettingsStatus() per controllare API key
|
- 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:
|
2. Creare frontend/src/pages/OutputReview.tsx:
|
||||||
- Riceve jobId da route params
|
- Riceve jobId da route params
|
||||||
- Carica risultati con useJobResults(jobId)
|
- Carica risultati con useJobResults(jobId)
|
||||||
- Header con: nome campagna, conteggio successi/falliti, pulsante "Download CSV"
|
- Header con: nome campagna, conteggio successi/falliti, pulsante "Download CSV"
|
||||||
- Griglia di PostCard (3 colonne desktop, 2 tablet, 1 mobile)
|
- Griglia di PostCard (3 colonne desktop, 2 tablet, 1 mobile)
|
||||||
- PostCard espandibile con SlideViewer
|
- PostCard espandibile con SlideViewer
|
||||||
- Pulsante "Download CSV":
|
- GESTIONE STATO EDIT INLINE:
|
||||||
- Chiama useDownloadCsv(jobId)
|
- Mantiene stato locale dei post (copia di GenerateResponse)
|
||||||
|
- Quando utente edita una slide in SlideViewer, aggiorna lo stato locale via callback
|
||||||
|
- Il pulsante "Download CSV" invia lo stato locale modificato al backend via POST /api/export/{jobId}/csv (useDownloadEditedCsv hook)
|
||||||
|
- Questo garantisce che il CSV rifletta le modifiche inline dell'utente
|
||||||
- Se ci sono post falliti: mostra nota "Il CSV contiene solo i N post generati con successo"
|
- 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."
|
- Se tutti i post sono falliti: messaggio "Nessun post generato con successo. Riprova."
|
||||||
|
|
||||||
8. Creare frontend/src/pages/GenerateSingle.tsx:
|
3. Creare frontend/src/pages/GenerateSingle.tsx:
|
||||||
- Form per generare un singolo post manualmente:
|
- Form per generare un singolo post manualmente:
|
||||||
- Topic (textarea, obbligatorio)
|
- Topic (textarea, obbligatorio)
|
||||||
- Tipo contenuto (select: valore, storytelling, news, riprova_sociale, coinvolgimento, promozione)
|
- Tipo contenuto (select: valore, storytelling, news, riprova_sociale, coinvolgimento, promozione)
|
||||||
@@ -265,30 +322,23 @@ Output: SPA React completa con tutte le pagine e componenti per il workflow: con
|
|||||||
- Al submit: chiama useGenerateSingle()
|
- Al submit: chiama useGenerateSingle()
|
||||||
- Mostra risultato con SlideViewer direttamente nella pagina
|
- Mostra risultato con SlideViewer direttamente nella pagina
|
||||||
- Pulsante download CSV per singolo post
|
- 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
|
|
||||||
</action>
|
</action>
|
||||||
<verify>
|
<verify>
|
||||||
- GenerateCalendar.tsx ha form con obiettivo e settimane, pulsante disabilitato senza API key
|
- GenerateCalendar.tsx ha form con obiettivo e settimane, pulsante disabilitato senza API key
|
||||||
|
- GenerateCalendar.tsx mostra ProgressIndicator con job_id dopo submit (non attende risposta sincrona)
|
||||||
- OutputReview.tsx mostra griglia di PostCard con badge PN e Schwartz
|
- OutputReview.tsx mostra griglia di PostCard con badge PN e Schwartz
|
||||||
- SlideViewer.tsx ha navigazione frecce e campi editabili inline
|
- OutputReview.tsx usa useDownloadEditedCsv per inviare edits al backend prima del download
|
||||||
- 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
|
- GenerateSingle.tsx ha form con select per tipo, livello, nicchia, formato
|
||||||
- npm run build completa senza errori TypeScript
|
- npm run build completa senza errori TypeScript
|
||||||
</verify>
|
</verify>
|
||||||
<done>
|
<done>
|
||||||
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.
|
Pagine complete: GenerateCalendar con form + ProgressIndicator async polling. OutputReview con griglia card, SlideViewer expansion, edit inline che si riflettono nel CSV via POST endpoint. GenerateSingle con form e anteprima.
|
||||||
</done>
|
</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
<task type="checkpoint:human-verify" gate="blocking">
|
<task type="checkpoint:human-verify" gate="blocking">
|
||||||
<what-built>
|
<what-built>
|
||||||
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.
|
Web UI completa con tutte le pagine: Dashboard, Genera Calendario, Output Review, Genera Singolo Post, Impostazioni. Inclusi progress indicator con polling real-time, griglia card con badge, navigazione slide, edit inline con CSV export tramite POST.
|
||||||
</what-built>
|
</what-built>
|
||||||
<how-to-verify>
|
<how-to-verify>
|
||||||
1. Verificare che `cd frontend && npm run build` completa senza errori
|
1. Verificare che `cd frontend && npm run build` completa senza errori
|
||||||
@@ -308,16 +358,20 @@ Output: SPA React completa con tutte le pagine e componenti per il workflow: con
|
|||||||
3. Tutti i componenti importano tipi da types.ts (non definiscono tipi inline)
|
3. Tutti i componenti importano tipi da types.ts (non definiscono tipi inline)
|
||||||
4. API hooks usano /postgenerator/api come base URL
|
4. API hooks usano /postgenerator/api come base URL
|
||||||
5. PostCard ha due varianti visive: success (espandibile) e failed (errore + riprova)
|
5. PostCard ha due varianti visive: success (espandibile) e failed (errore + riprova)
|
||||||
6. SlideViewer supporta edit inline e navigazione frecce
|
6. SlideViewer supporta edit inline e navigazione frecce, con callback onEdit
|
||||||
7. GenerateCalendar disabilita pulsante se API key non configurata
|
7. GenerateCalendar disabilita pulsante se API key non configurata
|
||||||
8. OutputReview fa download CSV con le modifiche inline dell'utente
|
8. GenerateCalendar mostra ProgressIndicator con job_id (non attende risposta sincrona)
|
||||||
|
9. ProgressIndicator polla /api/generate/job/{job_id}/status ogni 2s e smette quando completato
|
||||||
|
10. OutputReview usa useDownloadEditedCsv (POST) per scaricare CSV con modifiche inline
|
||||||
</verification>
|
</verification>
|
||||||
|
|
||||||
<success_criteria>
|
<success_criteria>
|
||||||
- Dashboard mostra stato API key e quick actions
|
- Dashboard mostra stato API key e quick actions
|
||||||
- Settings permette configurazione API key, modello, nicchie, frequenza
|
- Settings permette configurazione API key, modello, nicchie, frequenza
|
||||||
- Genera Calendario ha form, progress indicator, redirect a risultati
|
- Genera Calendario ha form, async submit con job_id, ProgressIndicator con polling real-time
|
||||||
- Output Review mostra griglia card con badge, slide viewer con edit, download CSV
|
- ProgressIndicator polla /status e mostra progresso per-item (pending/processing/success/failed)
|
||||||
|
- Output Review mostra griglia card con badge, slide viewer con edit inline
|
||||||
|
- Download CSV invia edits al backend via POST e riceve CSV aggiornato
|
||||||
- Post falliti mostrano errore e pulsante Riprova
|
- Post falliti mostrano errore e pulsante Riprova
|
||||||
- Genera Singolo Post ha form completo con anteprima risultato
|
- Genera Singolo Post ha form completo con anteprima risultato
|
||||||
- Build frontend completa senza errori
|
- Build frontend completa senza errori
|
||||||
|
|||||||
Reference in New Issue
Block a user