import streamlit as st from openai import OpenAI import openpyxl from openpyxl.styles import Font, PatternFill, Border, Side, Alignment import os import io import traceback import sys from dotenv import load_dotenv import json load_dotenv() st.set_page_config(layout="wide", page_title="Generatore Excel AI") st.title("📊 Generatore Excel AI") if "logged" not in st.session_state: st.session_state.logged = False if st.session_state.logged == False: login_placeholder = st.empty() with login_placeholder.container(): container = st.container(border=True) username = container.text_input('Username') password = container.text_input('Passowrd', type='password') login = container.button(' Login ', type='primary') if not login or username != os.getenv("LOGIN_USER") or password != os.getenv("LOGIN_PASSWORD"): if login: st.error('Password Errata') st.stop() st.session_state.logged = True login_placeholder.empty() API_KEY = os.getenv("API_HUGGINGFACE") BASE_URL = "https://matteoscript-ai.hf.space/v1/" MODEL_NAME = "gemini-2.5-flash-preview-05-20" # Sostituisci con il tuo modello preferito if not API_KEY: st.error("API Key Hugging Face non trovata. Assicurati che il file .env sia configurato correttamente con API_HUGGINGFACE.") st.stop() client = OpenAI(api_key=API_KEY, base_url=BASE_URL) DEFAULT_EXCEL_FILENAME = "excel_ai.xlsx" MAX_CORRECTION_ATTEMPTS = 5 PROMPT_FOR_SHEET_PLANNER = """ Sei un assistente AI specializzato nella pianificazione di documenti Excel multi-foglio. Data una richiesta generale dell'utente e un numero di fogli desiderato, il tuo compito è proporre: 1. Un nome univoco e descrittivo per ciascun foglio (massimo 30 caratteri, evita caratteri speciali non ammessi nei nomi dei fogli Excel come / ? * [ ]). 2. Una breve descrizione (1-3 frasi) dello scopo, del contenuto principale di ciascun foglio e delle possibili interazioni/collegamenti con altri fogli. Questa descrizione verrà poi usata come base per generare il contenuto dettagliato del foglio. La tua risposta DEVE essere un array JSON di oggetti. Ogni oggetto rappresenta un foglio e DEVE avere i campi "sheet_name" (stringa) e "sheet_purpose" (stringa). Il numero di oggetti nell'array DEVE corrispondere esattamente al numero di fogli specificato dall'utente. Esempio di richiesta utente: "Report vendite trimestrale (3 fogli): 1. Input dati grezzi vendite. 2. Riepilogo vendite per prodotto, che legga da input. 3. Riepilogo vendite per regione, che legga anch'esso da input." Esempio di TUA risposta JSON: [ {"sheet_name": "Input Vendite", "sheet_purpose": "Foglio per l'inserimento manuale o l'importazione dei dati grezzi di vendita, includendo data, ID prodotto, quantità, prezzo, regione."}, {"sheet_name": "Riep Prodotti", "sheet_purpose": "Analizza i dati dal foglio 'Input Vendite' per mostrare un riepilogo delle vendite per prodotto. Include totali, medie e forse grafici. Utilizza formule per aggregare i dati da 'Input Vendite'."}, {"sheet_name": "Riep Regioni", "sheet_purpose": "Analizza i dati dal foglio 'Input Vendite' per visualizzare le performance di vendita per regione. Potrebbe includere somme condizionali basate sui dati di 'Input Vendite'."} ] Non includere ```json o ``` all'inizio o alla fine della tua risposta. Fornisci solo l'array JSON. """ BASE_PROMPT_NO_BACKTICKS = """ Sei un esperto generatore di SCRIPT PYTHON per MODIFICARE un oggetto Workbook openpyxl esistente (fornito in una variabile chiamata wb). Il tuo compito è aggiungere e popolare UN SINGOLO FOGLIO in questo Workbook, usando la libreria openpyxl. IL TUO SCRIPT NON DEVE ASSOLUTAMENTE: - Creare un nuovo Workbook (NON usare openpyxl.Workbook()). L'oggetto Workbook ti viene fornito e si chiama wb. - Salvare il Workbook (NON usare wb.save()). Il salvataggio sarà gestito esternamente. - Includere istruzioni di import globali. Le funzioni e classi necessarie (wb, Font, PatternFill, Border, Side, Alignment) saranno già disponibili nello scope. IL TUO SCRIPT PYTHON DEVE: - Aspettarsi che un oggetto openpyxl.Workbook (wb) sia già definito. - Creare un nuovo foglio o accedere a uno esistente usando il TITOLO_FOGLIO_RICHIESTO: sheet_title = "TITOLO_FOGLIO_RICHIESTO" # Questo sarà il nome fornito if sheet_title in wb.sheetnames: sheet = wb[sheet_title] # Pulisci il foglio se deve essere sovrascritto (valori e stili) for row_idx in range(1, sheet.max_row + 1): for col_idx in range(1, sheet.max_column + 1): cell = sheet.cell(row=row_idx, column=col_idx) cell.value = None cell.style = openpyxl.styles.Style() # Reset stile base else: sheet = wb.create_sheet(title=sheet_title) - Popolare l'oggetto sheet con dati, header, formule e formattazione come da richiesta. - Usare stili Material Design e font moderni. - Includere FORMULE Excel, specialmente se la richiesta implica calcoli o aggregazioni. CONTESTO DEI FOGLI PRECEDENTI (SE FORNITO): Se nella richiesta utente qui sotto trovi una sezione "CONTESTO DEI FOGLI PRECEDENTI", essa conterrà gli script Python usati per generare i fogli precedenti. Usa questo per creare FORMULE EXCEL CHE COLLEGANO IL FOGLIO CORRENTE AI FOGLI PRECEDENTI. Esempio: sheet['B1'].value = "='DatiVendite'!E10" Analizza gli script precedenti per nomi dei fogli e struttura dati. I nomi dei fogli nelle formule DEVONO CORRISPONDERE ESATTAMENTE. REQUISITI CODICE GENERATO: - SENZA commenti python (#). - Massimo un ritorno a capo vuoto consecutivo. - Gestione errori interni al try/except (usare 'pass', non 'raise e'). - NON impostare sola lettura. - NON usare Alignment se non strettamente necessario. - Genera dati di esempio realistici (5-10 righe) se non diversamente specificato. - Applica formattazione ad header e celle chiave. """ PROMPT_FOR_SCRIPT_CORRECTION = """ Sei un esperto programmatore Python specializzato nella libreria openpyxl e nel debugging di script che manipolano file Excel. Ti è stato fornito uno script Python che ha fallito durante l'esecuzione mentre tentava di aggiungere e popolare un foglio in un workbook openpyxl esistente ('wb'). Ti sono stati forniti anche l'errore esatto e il traceback. IL TUO COMPITO È: 1. Analizzare attentamente lo script originale, l'errore e il traceback. 2. Identificare la causa dell'errore. 3. Correggere lo script Python. Lo script corretto deve ancora rispettare TUTTE le seguenti regole originali: - DEVE operare su un oggetto Workbook openpyxl esistente chiamato 'wb'. - NON DEVE creare un nuovo Workbook (NON usare openpyxl.Workbook()). - NON DEVE salvare il Workbook (NON usare wb.save()). - NON DEVE includere istruzioni di import globali (es. 'import openpyxl'). Le classi necessarie (wb, Font, PatternFill, Border, Side, Alignment) sono già nello scope. - DEVE creare/accedere al foglio specificato da TITOLO_FOGLIO_RICHIESTO. Il nome del foglio è critico, non alterarlo a meno che l'errore non sia specificamente legato ad esso e la correzione sia ovvia. - DEVE popolare il foglio come descritto in DESCRIZIONE_ORIGINALE_FOGLIO. - Se era presente un CONTESTO_FOGLI_PRECEDENTI, lo script corretto deve ancora poter utilizzare quel contesto per eventuali formule inter-foglio. - DEVE essere SENZA commenti python (#). - DEVE avere al massimo un ritorno a capo vuoto consecutivo. - DEVE gestire gli errori specifici del foglio internamente con un blocco try/except che termina con 'pass' (non 'raise e'). L'intero script dovrebbe essere avvolto in un try/except. - NON DEVE usare la classe Alignment esplicitamente a meno che non sia fondamentale e parte della descrizione originale. - DEVE mantenere l'obiettivo originale del foglio. Non rimuovere funzionalità a meno che non siano la causa diretta dell'errore e non ci sia una correzione ovvia. 4. Restituisci SOLO lo script Python CORRETTO E COMPLETO, senza alcuna spiegazione aggiuntiva, commenti o ```python. INFORMAZIONI FORNITE: - TITOLO_FOGLIO_RICHIESTO: "{sheet_name}" - DESCRIZIONE_ORIGINALE_FOGLIO: "{sheet_purpose}" - CONTESTO_FOGLI_PRECEDENTI (se applicabile): {previous_scripts_context} - SCRIPT_ORIGINALE_FALLITO: --- {original_script} --- - ERRORE_E_TRACEBACK: --- {error_traceback} --- Restituisci solo il codice Python corretto. Non aggiungere spiegazioni prima o dopo il codice. Assicurati che lo script sia completo e pronto per essere eseguito. """ def initialize_session_state(): defaults = { "initial_excel_request": "Business Plan di lancio prodotto per un'azienda Loyalty specializzata nella GDO", "num_sheets_requested": 1, "ai_sheet_plan": None, "workbook_object": None, "generated_scripts_for_sheets": [], "final_excel_file_path": None, "final_excel_filename": DEFAULT_EXCEL_FILENAME, "process_log": [], "error_message": "", "warning_message": "", "generation_started": False } for key, value in defaults.items(): if key not in st.session_state: st.session_state[key] = value def log_message(message, level="info"): log_entry = f"[{level.upper()}] {message}\n" st.session_state.process_log.append(log_entry) if level == "error": st.session_state.error_message += message + "\n" elif level == "warning": st.session_state.warning_message += message + "\n" def call_openai_for_sheet_plan(overall_request, num_sheets): st.session_state.error_message = "" st.session_state.warning_message = "" try: user_content = f"Richiesta generale dell'utente: \"{overall_request}\"\nNumero di fogli desiderato: {num_sheets}" completion = client.chat.completions.create( model=MODEL_NAME, messages=[ {"role": "system", "content": PROMPT_FOR_SHEET_PLANNER}, {"role": "user", "content": user_content} ], temperature=0.3, response_format={"type": "json_object"} ) response_content = completion.choices[0].message.content.strip() log_message(f"Risposta grezza AI (piano fogli): {response_content}") planned_sheets = json.loads(response_content) if isinstance(planned_sheets, list) and all(isinstance(item, dict) and "sheet_name" in item and "sheet_purpose" in item for item in planned_sheets): if len(planned_sheets) == num_sheets: return planned_sheets else: msg = f"AI ha proposto {len(planned_sheets)} fogli, richiesti {num_sheets}. Verifica." log_message(msg, level="warning"); st.session_state.warning_message = msg return planned_sheets else: msg = "Struttura JSON piano fogli AI non valida." log_message(msg, level="error"); st.session_state.error_message = msg return None except json.JSONDecodeError as e: msg = f"Errore decodifica JSON piano fogli: {e}. Risposta: {response_content}" log_message(msg, level="error"); st.session_state.error_message = msg return None except Exception as e: log_message(f"Errore OpenAI piano fogli: {e}", level="error"); st.session_state.error_message = f"Errore OpenAI pianificazione: {e}" return None def call_openai_for_sheet_script(sheet_purpose, sheet_name, prev_scripts_ctx): st.session_state.error_message = "" try: user_content = ( f"TITOLO_FOGLIO_RICHIESTO: '{sheet_name}'.\n" f"Descrizione per '{sheet_name}':\n{sheet_purpose}\n\n" ) if prev_scripts_ctx: user_content += f"CONTESTO_FOGLI_PRECEDENTI:\n{prev_scripts_ctx}\n" completion = client.chat.completions.create( model=MODEL_NAME, messages=[ {"role": "system", "content": BASE_PROMPT_NO_BACKTICKS}, {"role": "user", "content": user_content} ], temperature=0.1, ) script = completion.choices[0].message.content.strip() if script.startswith("```python"): script = script[9:] if script.endswith("```"): script = script[:-3] return script.strip() except Exception as e: st.session_state.error_message = f"Errore OpenAI script foglio '{sheet_name}': {e}" log_message(st.session_state.error_message, level="error") return None def call_openai_for_script_correction(original_script, error_traceback, sheet_name, sheet_purpose, prev_scripts_ctx): st.session_state.error_message = "" # Resetta per la chiamata di correzione log_message(f"Tentativo di correzione AI per foglio '{sheet_name}'.") try: prompt = PROMPT_FOR_SCRIPT_CORRECTION.format( sheet_name=sheet_name, sheet_purpose=sheet_purpose, previous_scripts_context=prev_scripts_ctx if prev_scripts_ctx else "Nessuno.", original_script=original_script, error_traceback=error_traceback ) completion = client.chat.completions.create( model=MODEL_NAME, messages=[{"role": "user", "content": prompt}], # Il system prompt è inglobato per semplicità qui temperature=0.2, # Leggermente più creativo per trovare soluzioni ) corrected_script = completion.choices[0].message.content.strip() if corrected_script.startswith("```python"): corrected_script = corrected_script[9:] if corrected_script.endswith("```"): corrected_script = corrected_script[:-3] log_message(f"Script corretto proposto dall'AI per '{sheet_name}'.") return corrected_script.strip() except Exception as e: st.session_state.error_message = f"Errore OpenAI correzione script per '{sheet_name}': {e}" log_message(st.session_state.error_message, level="error") return None def execute_single_sheet_script(script_code, workbook_obj, sheet_name_being_processed): #st.session_state.error_message = "" # Non resettare qui, l'errore può venire da OpenAI current_error_message_for_exec = "" capture_out = io.StringIO() original_stdout = sys.stdout sys.stdout = capture_out execution_success = False script_globals = { 'wb': workbook_obj, 'openpyxl': openpyxl, 'Font': Font, 'PatternFill': PatternFill, 'Border': Border, 'Side': Side, 'Alignment': Alignment } detailed_error_for_correction = "" try: exec(script_code, script_globals) execution_success = True except Exception as e: tb_str = traceback.format_exc() current_error_message_for_exec = f"Errore esecuzione script per foglio '{sheet_name_being_processed}':\n{type(e).__name__}: {e}\nTraceback:\n{tb_str}" detailed_error_for_correction = f"{type(e).__name__}: {e}\n{tb_str}" # Non loggare qui come errore fatale subito, potrebbe essere corretto finally: sys.stdout = original_stdout execution_log = capture_out.getvalue() capture_out.close() if not execution_success: # Solo se fallisce, aggiorna l'errore di session_state st.session_state.error_message += current_error_message_for_exec + "\n" return execution_success, execution_log, detailed_error_for_correction def render_sidebar(): with st.sidebar: st.header("🤖 Input per l'AI") st.session_state.initial_excel_request = st.text_area( "📝 Descrivi l'Excel", value=st.session_state.initial_excel_request, height=200, help="Sii descrittivo. Specifica se i fogli devono leggere dati da altri." ) st.session_state.num_sheets_requested = st.number_input( "🔢 Numero di fogli:", min_value=1, max_value=20, value=st.session_state.num_sheets_requested, step=1 ) def render_main_content(): st.markdown("Definisci richiesta e numero Fogli, poi l'AI **pianificherà, genererà, collegherà** e tenterà di auto-correggere gli script dei fogli") if st.sidebar.button("💡 Pianifica Fogli con AI", type="primary", use_container_width=True): st.session_state.ai_sheet_plan = None st.session_state.generated_scripts_for_sheets = [] st.session_state.process_log = [] st.session_state.error_message = "" st.session_state.warning_message = "" st.session_state.final_excel_file_path = None st.session_state.generation_started = False log_message("Avvio pianificazione fogli...") with st.spinner("L'AI sta pianificando i fogli..."): plan = call_openai_for_sheet_plan(st.session_state.initial_excel_request, st.session_state.num_sheets_requested) if plan: st.session_state.ai_sheet_plan = plan log_message(f"Piano fogli ricevuto: {len(plan)} fogli.") st.success(f"✅ L'AI ha pianificato {len(plan)} fogli! Rivedi/modifica descrizioni.") else: log_message("Fallimento pianificazione.", level="error") st.error(f"Impossibile pianificare. {st.session_state.error_message}") st.rerun() if st.session_state.ai_sheet_plan: st.markdown("---") st.subheader("📋 Piano Fogli Proposto dall'AI (Modificabile)") st.caption("Modifica descrizioni se necessario, specialmente per chiarire collegamenti.") for i, sheet_def in enumerate(st.session_state.ai_sheet_plan): with st.expander(f"Foglio {i+1}: **{sheet_def['sheet_name']}**", expanded=True): new_purpose = st.text_area( f"Scopo/Contenuto per '{sheet_def['sheet_name']}':", value=sheet_def['sheet_purpose'], key=f"sheet_purpose_edit_{i}", height=120 ) if new_purpose != sheet_def['sheet_purpose']: st.session_state.ai_sheet_plan[i]['sheet_purpose'] = new_purpose if st.button("✨ Genera, Assembla e Auto-Correggi Excel ✨", type="primary", use_container_width=True, disabled=st.session_state.generation_started): st.session_state.generation_started = True # Conserva log pianificazione, pulisce log di vecchie generazioni planning_logs = [log for log in st.session_state.process_log if "pianificazione" in log.lower() or "piano fogli ricevuto" in log.lower()] st.session_state.process_log = planning_logs st.session_state.error_message = "" st.session_state.warning_message = "" st.session_state.generated_scripts_for_sheets = [] # Visualizzazione finale st.session_state.final_excel_file_path = None st.session_state.workbook_object = openpyxl.Workbook() if st.session_state.workbook_object.sheetnames: st.session_state.workbook_object.remove(st.session_state.workbook_object.active) log_message("Workbook inizializzato per generazione.") generated_scripts_history_for_context = [] # Per passare contesto all'AI all_sheets_successful = True progress_bar = st.progress(0) num_total_sheets = len(st.session_state.ai_sheet_plan) for i, sheet_def in enumerate(st.session_state.ai_sheet_plan): sheet_name = sheet_def["sheet_name"] sheet_purpose = sheet_def["sheet_purpose"] progress_text = f"Foglio {i+1}/{num_total_sheets}: '{sheet_name}'" log_message(f"--- Inizio {progress_text} ---") progress_bar.progress((i + 1) / num_total_sheets, text=f"Generando: {progress_text}") previous_scripts_context_str = "" if generated_scripts_history_for_context: parts = ["'''\nScript fogli precedenti (riferimento/collegamenti):\n"] for idx, prev_info in enumerate(generated_scripts_history_for_context): parts.append(f"--- Script Foglio '{prev_info['name']}' (Indice {idx}) ---\n{prev_info['script']}\n") parts.append("'''\n") previous_scripts_context_str = "\n".join(parts) current_script_for_sheet = None correction_attempts = 0 sheet_successfully_processed = False # Tentativo iniziale di generazione script spinner_msg = f"🤖 AI genera script per '{sheet_name}' (contesto: {len(generated_scripts_history_for_context)} fogli)..." with st.spinner(spinner_msg): current_script_for_sheet = call_openai_for_sheet_script(sheet_purpose, sheet_name, previous_scripts_context_str) if current_script_for_sheet: log_message(f"Script iniziale per '{sheet_name}' generato.") # Ciclo di esecuzione e correzione while correction_attempts <= MAX_CORRECTION_ATTEMPTS and not sheet_successfully_processed: exec_spinner_msg = f"⚙️ Esecuzione script per '{sheet_name}'" if correction_attempts > 0: exec_spinner_msg += f" (tentativo correzione {correction_attempts})" with st.spinner(exec_spinner_msg): success, exec_log, error_details_for_correction = execute_single_sheet_script( current_script_for_sheet, st.session_state.workbook_object, sheet_name ) if exec_log: log_message(f"Log esecuzione per '{sheet_name}':\n{exec_log}") if success: log_message(f"Foglio '{sheet_name}' aggiunto/modificato con successo.") st.session_state.generated_scripts_for_sheets.append({"name": sheet_name, "script": current_script_for_sheet, "status": "Successo"}) generated_scripts_history_for_context.append({"name": sheet_name, "script": current_script_for_sheet}) sheet_successfully_processed = True else: log_message(f"⚠️ Fallimento esecuzione script per '{sheet_name}'. Errore: {error_details_for_correction}", level="warning") correction_attempts += 1 if correction_attempts <= MAX_CORRECTION_ATTEMPTS: log_message(f"Tentativo {correction_attempts}/{MAX_CORRECTION_ATTEMPTS} di auto-correzione AI per '{sheet_name}'.") with st.spinner(f"🤖 AI tenta correzione per '{sheet_name}' (errore: {error_details_for_correction.splitlines()[0]}..."): corrected_script_candidate = call_openai_for_script_correction( current_script_for_sheet, error_details_for_correction, sheet_name, sheet_purpose, previous_scripts_context_str ) if corrected_script_candidate: log_message(f"Nuovo script corretto proposto per '{sheet_name}'.") current_script_for_sheet = corrected_script_candidate # Loop rieseguirà execute_single_sheet_script else: log_message(f"AI non ha fornito script corretto per '{sheet_name}'. Tentativo {correction_attempts} fallito.", level="error") st.session_state.generated_scripts_for_sheets.append({"name": sheet_name, "script": current_script_for_sheet, "status": f"Fallito dopo correzione (AI non ha corretto)", "error": error_details_for_correction}) break # Esce dal ciclo di correzione se AI non dà nulla else: log_message(f"Limite tentativi ({MAX_CORRECTION_ATTEMPTS}) di correzione raggiunto per '{sheet_name}'. Errore finale: {error_details_for_correction}", level="error") st.session_state.generated_scripts_for_sheets.append({"name": sheet_name, "script": current_script_for_sheet, "status": f"Fallito (max tentativi)", "error": error_details_for_correction}) else: # Fallimento generazione script iniziale log_message(f"⚠️ Fallimento generazione script iniziale per '{sheet_name}'. {st.session_state.error_message}", level="error") st.session_state.generated_scripts_for_sheets.append({"name": sheet_name, "script": "# ERRORE GENERAZIONE SCRIPT", "status": "Fallimento Generazione", "error": st.session_state.error_message}) if not sheet_successfully_processed: all_sheets_successful = False break # Interrompe generazione se un foglio fallisce definitivamente log_message(f"--- Fine Foglio {i+1}: '{sheet_name}' ---") progress_bar.empty() if all_sheets_successful and st.session_state.workbook_object: try: if os.path.exists(st.session_state.final_excel_filename): os.remove(st.session_state.final_excel_filename) st.session_state.workbook_object.save(st.session_state.final_excel_filename) st.session_state.final_excel_file_path = st.session_state.final_excel_filename log_message(f"Workbook '{st.session_state.final_excel_filename}' salvato.") st.success(f"✅ Excel '{st.session_state.final_excel_filename}' generato!") except Exception as e: log_message(f"❌ Errore salvataggio Workbook: {e}", level="error"); st.session_state.error_message = f"Errore salvataggio: {e}" elif not all_sheets_successful: st.error(f"Processo interrotto. Excel non salvato. {st.session_state.error_message}") else: st.warning("Nessun foglio elaborato o problema sconosciuto.") st.session_state.generation_started = False st.rerun() if st.session_state.warning_message: st.warning(st.session_state.warning_message) # Mostra errore principale solo se non c'è un file di successo e non siamo in fase di generazione attiva if st.session_state.error_message and not st.session_state.final_excel_file_path and not st.session_state.generation_started: st.error(f"Si sono verificati errori durante l'ultimo processo: {st.session_state.error_message}") if st.session_state.generated_scripts_for_sheets: st.markdown("---") with st.expander("🔍 Vedi Script Generati per i Fogli (e Stato Esecuzione)"): for item in st.session_state.generated_scripts_for_sheets: # Modifica questa riga: sheet_name_display = item.get('name', 'Nome Foglio Mancante') status_display = item.get('status', 'Stato Sconosciuto') script_display = item.get('script', '# Script non disponibile') error_display = item.get('error', None) st.subheader(f"Script per '{sheet_name_display}' - Stato: {status_display}") st.code(script_display, language="python") if error_display: # Controlla se la chiave 'error' esiste e ha un valore st.error(f"Dettaglio Errore Finale per '{sheet_name_display}':\n{error_display}") if st.session_state.process_log: st.markdown("---") with st.expander("📝 Log del Processo Dettagliato", expanded=False): st.text_area("Log:", "".join(st.session_state.process_log), height=300, disabled=True) render_download_section() render_footer() def render_download_section(): if st.session_state.final_excel_file_path and os.path.exists(st.session_state.final_excel_file_path): st.markdown("---") st.header("📥 Download File Excel Finale") try: with open(st.session_state.final_excel_file_path, "rb") as fp: excel_bytes = fp.read() st.download_button( label=f"💾 Scarica {st.session_state.final_excel_filename}", data=excel_bytes, file_name=st.session_state.final_excel_filename, mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", use_container_width=True, type="primary" ) except Exception as e: st.error(f"Impossibile leggere il file per il download: {e}") log_message(f"Errore lettura file per download: {e}", level="error") elif st.session_state.ai_sheet_plan and not st.session_state.generation_started: st.info("Una volta generato l'Excel, il download apparirà qui.") def render_footer(): st.markdown("---") st.markdown("💡 **Nota:** L'AI tenta di collegare i fogli e auto-correggere errori. Verifica sempre i risultati e le formule.") def main(): initialize_session_state() render_sidebar() render_main_content() if __name__ == "__main__": main()