File size: 29,329 Bytes
d60dac5
c146fd5
 
 
293ffb8
12f17b8
c146fd5
 
 
 
e402ae5
c146fd5
 
e402ae5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c146fd5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12f17b8
c146fd5
 
 
 
 
 
12f17b8
c146fd5
 
12f17b8
c146fd5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12f17b8
c146fd5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12f17b8
c146fd5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b0a649
c146fd5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12f17b8
c146fd5
 
 
 
 
 
 
 
 
 
293ffb8
c146fd5
 
 
293ffb8
 
c146fd5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
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()