File size: 26,915 Bytes
08141f1
5413358
 
9edfd4c
 
52b8580
8907d55
9edfd4c
37f68ba
eb7cbda
866e087
eb7cbda
 
9edfd4c
 
5413358
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52b8580
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8907d55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52b8580
 
 
 
 
 
 
 
 
 
95064ac
52b8580
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5413358
 
 
 
 
 
 
 
 
52b8580
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9edfd4c
 
 
52b8580
9edfd4c
 
6977462
37f68ba
8907d55
 
9edfd4c
 
 
 
 
6977462
 
 
c67c91c
6977462
 
c67c91c
5413358
 
 
 
 
6977462
 
 
 
5413358
 
 
 
 
 
6977462
 
52b8580
 
 
 
 
 
 
 
 
 
 
6977462
 
52b8580
 
6977462
52b8580
6977462
eb7cbda
 
 
 
 
 
 
 
 
4ec8a50
52b8580
4ec8a50
 
 
 
 
 
 
 
eb7cbda
4ec8a50
 
 
eb7cbda
4ec8a50
 
 
 
 
 
eb7cbda
 
4ec8a50
 
 
 
 
 
 
 
 
eb7cbda
 
 
 
 
 
866e087
eb7cbda
 
 
866e087
eb7cbda
866e087
 
 
eb7cbda
866e087
 
 
 
 
 
 
eb7cbda
 
52b8580
 
 
 
 
 
 
 
 
eb7cbda
 
52b8580
 
eb7cbda
52b8580
eb7cbda
6977462
 
 
 
 
 
 
37f68ba
8907d55
37f68ba
 
 
 
 
fa68783
37f68ba
 
 
 
fa68783
37f68ba
 
 
fa68783
37f68ba
 
 
 
 
 
 
 
 
 
 
 
 
8907d55
fa68783
8907d55
 
 
fa68783
8907d55
 
 
fa68783
8907d55
 
 
 
 
 
 
 
 
 
 
9edfd4c
 
 
 
37f68ba
 
8907d55
9edfd4c
8907d55
37f68ba
fa68783
37f68ba
9edfd4c
 
37f68ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9edfd4c
37f68ba
 
8907d55
 
 
 
 
37f68ba
 
 
 
 
 
 
 
 
 
 
 
8907d55
9edfd4c
 
 
 
 
 
 
 
37f68ba
 
9edfd4c
37f68ba
 
 
 
 
 
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
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
import logging
import re

import streamlit as st
from modules.config import client_anthropic
from modules.utils import sanitizar, extrair_texto_arquivo, aprimorar_prompt
from modules.docgen import criar_docx, _gerar_com_continuacao
from modules.memoria import ler_arquivo_memoria
from modules.video_presentation import gerar_video_apresentacao
from modules.pptx_generator import (
    gerar_pptx,
    salvar_apresentacao_drive, listar_apresentacoes_drive, carregar_apresentacao_drive,
)


# Detecta URLs do Drive/Docs/Sheets/Slides em texto livre. Captura o ID
# em qualquer um dos 5 formatos comuns. Usado pra permitir que o usuario
# cole links no campo de instrucoes e o agente busque o conteudo automatico.
_RE_DRIVE_URLS = re.compile(
    r'https?://(?:'
    r'docs\.google\.com/document/d/([a-zA-Z0-9_-]+)'
    r'|docs\.google\.com/spreadsheets/d/([a-zA-Z0-9_-]+)'
    r'|docs\.google\.com/presentation/d/([a-zA-Z0-9_-]+)'
    r'|drive\.google\.com/file/d/([a-zA-Z0-9_-]+)'
    r'|drive\.google\.com/open\?id=([a-zA-Z0-9_-]+)'
    r')'
)


def _carregar_arquivos_drive_de_urls(texto: str) -> tuple[str, list[str]]:
    """Detecta URLs de Drive/Docs/Sheets/Slides em texto livre, busca o
    conteudo de cada arquivo via Drive API, e retorna (conteudo_concatenado,
    lista_de_nomes). Limita cada arquivo a 15k chars pra nao estourar contexto.
    """
    if not texto:
        return "", []

    ids_vistos: set[str] = set()
    ids: list[str] = []
    for match in _RE_DRIVE_URLS.finditer(texto):
        for g in match.groups():
            if g and g not in ids_vistos:
                ids_vistos.add(g)
                ids.append(g)

    if not ids:
        return "", []

    from modules.drive_utils import conectar_drive, _ler_conteudo_drive_arquivo
    svc = conectar_drive()
    if not svc:
        return "", []

    partes: list[str] = []
    nomes: list[str] = []
    for file_id in ids:
        try:
            meta = svc.files().get(fileId=file_id, fields="id,name,mimeType").execute()
            nome = meta.get("name", file_id)
            mime = meta.get("mimeType", "")
            conteudo = _ler_conteudo_drive_arquivo(svc, file_id, mime, nome)
            if conteudo and conteudo.strip():
                if len(conteudo) > 15000:
                    conteudo = conteudo[:15000] + "\n[...TRUNCADO...]"
                partes.append(f"### Arquivo do Drive: {nome}\n{conteudo}")
                nomes.append(nome)
            else:
                nomes.append(f"{nome} (sem conteudo extraivel)")
        except Exception as e:
            logging.warning(f"[Docs] Erro ao ler arquivo Drive {file_id}: {e}")
            nomes.append(f"{file_id} (erro de acesso)")

    return "\n\n".join(partes), nomes


# --------------- Enhancer UI helper ---------------

def _enhancer_ui(key_prefix: str) -> str | None:
    """Show enhancer approval UI. Returns final prompt or None if still waiting."""
    estado_key = f"_enh_{key_prefix}_state"
    original_key = f"_enh_{key_prefix}_orig"
    aprimorado_key = f"_enh_{key_prefix}_enh"

    estado = st.session_state.get(estado_key)
    if estado != "awaiting":
        return None

    original = st.session_state.get(original_key, "")
    aprimorado = st.session_state.get(aprimorado_key, "")

    st.markdown("---")
    with st.expander("Seu prompt original", expanded=False):
        st.markdown(original)
    st.markdown("**Prompt aprimorado:**")
    st.info(aprimorado)

    col1, col2, col3 = st.columns(3)
    with col1:
        if st.button("Usar aprimorado", type="primary", key=f"btn_enh_usar_{key_prefix}"):
            st.session_state[estado_key] = None
            st.session_state[f"_enh_{key_prefix}_run"] = aprimorado
            st.rerun()
    with col2:
        if st.button("Regenerar", key=f"btn_enh_regen_{key_prefix}"):
            with st.spinner("Regenerando..."):
                st.session_state[aprimorado_key] = aprimorar_prompt(original)
            st.rerun()
    with col3:
        if st.button("Usar original", key=f"btn_enh_orig_{key_prefix}"):
            st.session_state[estado_key] = None
            st.session_state[f"_enh_{key_prefix}_run"] = original
            st.rerun()
    return None


def _trigger_enhancer(texto: str, key_prefix: str):
    """Start the enhancer flow: enhance + set awaiting state."""
    st.session_state[f"_enh_{key_prefix}_orig"] = texto
    with st.spinner("Aprimorando seu prompt..."):
        st.session_state[f"_enh_{key_prefix}_enh"] = aprimorar_prompt(texto)
    st.session_state[f"_enh_{key_prefix}_state"] = "awaiting"
    st.rerun()


def _pop_enhancer_result(key_prefix: str) -> str | None:
    """Pop and return the enhancer result if available."""
    return st.session_state.pop(f"_enh_{key_prefix}_run", None)


# --------------- Gamma prompt ---------------

_GAMMA_PROMPT = """Voce e um especialista em criar apresentacoes executivas interativas para a plataforma Gamma.app.

Recebeu o conteudo extraido de uma apresentacao PowerPoint. Sua tarefa e transformar esse conteudo em um ROTEIRO COMPLETO para uma apresentacao Gamma.app animada e narrada.

CONTEUDO DO POWERPOINT:
{conteudo_ppt}

INSTRUCOES ADICIONAIS DO USUARIO:
{instrucoes}

CONTEXTO ADICIONAL:
{contexto}

Gere o roteiro seguindo EXATAMENTE este formato para cada slide/card do Gamma:

---

## Card [N]: [Titulo do Card]

**Visual:** [Descreva o layout ideal: hero image, split layout, grid, quote, etc.]

**Icones/Ilustracoes:** [Sugira icones ou ilustracoes no estilo sketch/hand-drawn que combinam com o tema]

**Texto principal:**
[Texto que aparece no card — curto, impactante, com palavras-chave em **destaque**]

**Narração (texto que o apresentador fala):**
[Texto mais longo e explicativo que complementa o que esta no card. Deve soar natural, como alguem apresentando para a diretoria. Use linguagem executiva mas acessivel.]

**Animacao sugerida:** [fade-in, slide-left, zoom, typewriter, etc.]

**Duracao estimada:** [X segundos]

---

REGRAS:
1. Cada slide do PPT pode virar 1 ou mais cards no Gamma (quebre slides densos em multiplos cards)
2. O primeiro card deve ser uma capa impactante com titulo e subtitulo
3. O ultimo card deve ser um encerramento com call-to-action ou agradecimento
4. Use frases curtas e impactantes nos cards — a narração complementa
5. Sugira destaques visuais (palavras com background colorido, como no exemplo)
6. Para tabelas/dados, sugira visualizacoes (graficos, progress bars, KPIs side-by-side)
7. Mantenha o tom profissional mas envolvente
8. Toda narração em portugues brasileiro
9. Inclua transicoes suaves entre cards
10. Para dados numericos, destaque os numeros grandes e as variacoes (%, +/-)

Ao final, inclua uma secao:

## Resumo para Gamma.app
- Total de cards: [N]
- Duracao estimada: [X minutos]
- Estilo visual sugerido: [ex: Sketch/Hand-drawn, Minimalista, Corporativo]
- Paleta de cores sugerida: [baseada no conteudo]
- Instrucoes para importar no Gamma: [passo a passo rapido]

Gere o roteiro COMPLETO agora."""


# --------------- PPTX generation logic ---------------

def _executar_geracao_pptx(instrucao_pptx: str, auto_ctx: bool):
    """Executa a geracao do PPTX (separado da UI para reuso)."""
    contexto_auto = ""
    if auto_ctx:
        with st.spinner("Carregando contexto completo (OKRs, roadmap, metricas, iniciativas, PM OS)..."):
            partes = []
            try:
                from modules.contexto import carregar_contexto_sob_demanda
                ctx_base, _ctx_log = carregar_contexto_sob_demanda(
                    "okrs roadmap metricas iniciativas squad cora estrategia"
                )
                if ctx_base:
                    partes.append(f"## Contexto Base (Roadmap, Squad, Empresa)\n{ctx_base}")
            except Exception as e:
                logging.warning(f"[Docs] Erro ao carregar contexto base: {e}")

            try:
                from modules.pm_os import pm_os_carregar_contexto_outputs
                ctx_pm_os = pm_os_carregar_contexto_outputs()
                if ctx_pm_os:
                    partes.append(f"## Contexto PM OS (OKRs, PRDs, Outputs)\n{ctx_pm_os}")
            except Exception as e:
                logging.warning(f"[Docs] Erro ao carregar PM OS: {e}")

            try:
                from modules.pm_os import PM_OS_DRIVE, _pm_os_listar_com_svc
                from modules.drive_utils import conectar_drive, _ler_conteudo_drive_arquivo
                svc = conectar_drive()
                if svc:
                    prds = _pm_os_listar_com_svc(svc, PM_OS_DRIVE.get("outputs_prds", ""))
                    prd_parts = []
                    for arq in prds:
                        if arq.get("mimeType") == "application/vnd.google-apps.folder":
                            continue
                        conteudo = _ler_conteudo_drive_arquivo(svc, arq["id"], arq["mimeType"], arq["name"])
                        if conteudo.strip():
                            prd_parts.append(f"### PRD: {arq['name']}\n{conteudo[:5000]}")
                    if prd_parts:
                        partes.append(f"## PRDs das Iniciativas\n" + "\n\n".join(prd_parts))
            except Exception as e:
                logging.warning(f"[Docs] Erro ao carregar PRDs: {e}")

            try:
                from modules.contexto_fraude import carregar_inteligencia_fraude
                ctx_fraude = carregar_inteligencia_fraude()
                if ctx_fraude:
                    partes.append(f"## Inteligencia de Fraude\n{ctx_fraude[:15000]}")
            except Exception as e:
                logging.warning(f"[Docs] Erro ao carregar contexto fraude: {e}")

            contexto_auto = "\n\n---\n\n".join(partes)

    # Contexto de documentos selecionados
    mem_docs = st.session_state.get("memoria_arquivos") or []
    docs_selecionados = st.session_state.get("pptx_docs_ctx", [])
    for doc in mem_docs:
        if doc["name"] in docs_selecionados:
            contexto_auto += f"\n--- {doc['name']} ---\n{ler_arquivo_memoria(doc['id'])}\n"

    # Carrega arquivos linkados no campo de instrucoes (Docs, Sheets, Slides,
    # Drive files). Permite o usuario colar URL e o agente puxa o conteudo.
    with st.spinner("Verificando links do Drive na instrucao..."):
        drive_conteudo, drive_nomes = _carregar_arquivos_drive_de_urls(instrucao_pptx)
    if drive_conteudo:
        contexto_auto += "\n\n---\n\n## Arquivos do Drive linkados na instrucao\n\n" + drive_conteudo
    if drive_nomes:
        st.info("Arquivos do Drive carregados: " + ", ".join(drive_nomes))

    progress_bar = st.progress(0)
    status_text = st.empty()

    def update_progress(step, total, msg):
        progress_bar.progress(step / total)
        status_text.info(f"**Passo {step}/{total}:** {msg}")

    try:
        pptx_bytes, n_slides, slides_html = gerar_pptx(
            tema=instrucao_pptx,
            contexto=contexto_auto,
            progress_callback=update_progress,
        )
        progress_bar.empty()
        status_text.empty()

        st.session_state["_pptx_slides"] = slides_html
        st.session_state["_pptx_bytes"] = pptx_bytes

        nome_apres = instrucao_pptx.strip()[:50] or "apresentacao"
        fid = salvar_apresentacao_drive(slides_html, nome_apres)
        if fid:
            st.session_state["_pptx_drive_id"] = fid

        st.success(f"Apresentacao gerada com sucesso! {n_slides} slides."
                   + (" Salva no Drive." if fid else ""))
        st.download_button(
            f"Baixar PPTX ({n_slides} slides)",
            data=pptx_bytes,
            file_name="apresentacao_cora.pptx",
            mime="application/vnd.openxmlformats-officedocument.presentationml.presentation",
            key="dl_pptx",
        )
    except Exception as e:
        progress_bar.empty()
        status_text.empty()
        st.error(f"Erro ao gerar apresentacao: {e}")


def _executar_edicao_slides(edit_instrucao: str, saved_slides: list):
    """Executa a edicao dos slides."""
    from modules.pptx_generator import editar_slides_lote
    progress_bar = st.progress(0)
    status_text = st.empty()

    def update_edit_progress(step, total, msg):
        progress_bar.progress(step / total)
        status_text.info(f"**Passo {step}/{total}:** {msg}")

    try:
        pptx_bytes, n_slides, slides_atualizados = editar_slides_lote(
            slides=saved_slides,
            instrucao=edit_instrucao,
            progress_callback=update_edit_progress,
        )
        progress_bar.empty()
        status_text.empty()

        st.session_state["_pptx_slides"] = slides_atualizados
        st.session_state["_pptx_bytes"] = pptx_bytes

        salvar_apresentacao_drive(slides_atualizados,
            st.session_state.get("pptx_instrucao", "apresentacao")[:50])

        st.success(f"Edicao aplicada com sucesso! {n_slides} slides.")
        st.download_button(
            f"Baixar PPTX editado ({n_slides} slides)",
            data=pptx_bytes,
            file_name="apresentacao_cora.pptx",
            mime="application/vnd.openxmlformats-officedocument.presentationml.presentation",
            key="dl_pptx_edited",
        )
    except Exception as e:
        progress_bar.empty()
        status_text.empty()
        st.error(f"Erro ao editar slides: {e}")


# --------------- Main render ---------------

def render_docs_tab():
    """Renderiza a aba Documentos."""
    st.header("Gerador de Documentos")
    enhancer_on = st.session_state.get("prompt_enhancer_ativo", False)

    tipo_doc = st.selectbox("Tipo de documento:", [
        "Apresentacao Executiva (PPTX)",
        "Video Executivo (MP4)",
        "Apresentacao Interativa (Gamma.app)",
        "Roteiro de Apresentacao",
        "Analise de Risco",
        "Email Executivo",
        "Documento Livre",
    ])

    # --- Apresentacao Executiva PPTX ---
    if tipo_doc == "Apresentacao Executiva (PPTX)":
        st.markdown("""
Gera uma **apresentacao PowerPoint com visual rico** — igual ao video executivo.

**Como funciona:**
1. Descreva o tema (ex: "Racional de priorizacao das iniciativas por KR")
2. **Cole links do Drive/Docs/Sheets/Slides** direto no campo abaixo — o agente extrai o conteudo automaticamente
3. O agente carrega contexto automaticamente (OKRs, metricas, iniciativas) se a opcao estiver marcada
4. Claude Opus cria slides HTML com design visual rico (gradientes, cards, icones)
5. Playwright renderiza cada slide em imagem Full HD (1920x1080)
6. Monta o PPTX com imagens — visual identico ao video executivo
""")
        auto_ctx = st.checkbox("Carregar contexto automaticamente (OKRs, roadmap, metricas)", value=True, key="pptx_auto_ctx")

        instrucao_pptx = st.text_area("Tema e instrucoes da apresentacao:", height=150,
            placeholder=(
                "Ex: Racional de atingimento dos OKRs — como as iniciativas do roadmap impactam cada KR. "
                "Tom executivo para o CPO.\n\n"
                "Voce pode colar links do Drive aqui (Docs, Sheets, Slides, PDFs, DOCX...) que o agente "
                "le o conteudo automaticamente. Ex: https://docs.google.com/document/d/ABC123/edit"
            ),
            key="pptx_instrucao")

        # Check if enhancer returned a result
        enh_result = _pop_enhancer_result("pptx_gen")
        if enh_result:
            _executar_geracao_pptx(enh_result, auto_ctx)

        # Show enhancer UI if awaiting
        elif st.session_state.get("_enh_pptx_gen_state") == "awaiting":
            _enhancer_ui("pptx_gen")

        # Button
        elif st.button("Gerar Apresentacao PPTX", type="primary", key="btn_pptx"):
            if not instrucao_pptx.strip():
                st.warning("Descreva o tema da apresentacao.")
            elif enhancer_on:
                _trigger_enhancer(instrucao_pptx, "pptx_gen")
            else:
                _executar_geracao_pptx(instrucao_pptx, auto_ctx)

        # --- Carregar apresentacao salva do Drive ---
        st.divider()
        with st.expander("Carregar apresentacao salva"):
            if st.button("Listar apresentacoes", key="btn_list_pptx"):
                with st.spinner("Buscando no Drive..."):
                    st.session_state["_pptx_lista_drive"] = listar_apresentacoes_drive()

            lista_drive = st.session_state.get("_pptx_lista_drive", [])
            if lista_drive:
                opcoes_drive = {}
                opcoes_meta = {}
                for a in lista_drive:
                    name = a["name"]
                    display = name.replace("pptx_", "").replace(".json", "")
                    is_json = name.endswith(".json")
                    tipo = "editavel" if is_json else "importavel"
                    label = f"{display} ({a.get('modifiedTime', '')[:10]}) [{tipo}]"
                    opcoes_drive[label] = a["id"]
                    opcoes_meta[label] = name
                selecionada = st.selectbox("Apresentacao:", list(opcoes_drive.keys()), key="pptx_load_select")
                is_pptx_import = not opcoes_meta.get(selecionada, "").endswith(".json")
                if is_pptx_import:
                    st.caption("Este arquivo sera importado: Claude vai recriar os slides como HTML editavel. Pode levar ~1 min por slide.")
                if st.button("Carregar", key="btn_load_pptx"):
                    spinner_msg = "Importando PPTX (Claude recriando slides)..." if is_pptx_import else "Carregando apresentacao..."
                    with st.spinner(spinner_msg):
                        slides_carregados = carregar_apresentacao_drive(
                            opcoes_drive[selecionada],
                            file_name=opcoes_meta.get(selecionada, ""),
                        )
                    if slides_carregados:
                        st.session_state["_pptx_slides"] = slides_carregados
                        if is_pptx_import:
                            nome_save = opcoes_meta.get(selecionada, "import").rsplit(".", 1)[0]
                            fid = salvar_apresentacao_drive(slides_carregados, nome_save)
                            if fid:
                                st.success(f"Importado e salvo! {len(slides_carregados)} slides. Edite abaixo.")
                            else:
                                st.success(f"Importado: {len(slides_carregados)} slides. Edite abaixo.")
                        else:
                            st.success(f"Apresentacao carregada: {len(slides_carregados)} slides. Edite abaixo.")
                        st.rerun()
                    else:
                        st.error("Erro ao carregar apresentacao.")
            elif st.session_state.get("_pptx_lista_drive") is not None:
                st.caption("Nenhuma apresentacao salva encontrada.")

        # --- Edicao de slides ---
        saved_slides = st.session_state.get("_pptx_slides")
        if saved_slides:
            st.divider()
            st.subheader("Editar slides")

            with st.expander(f"Ver slides atuais ({len(saved_slides)})"):
                for i, s in enumerate(saved_slides):
                    st.caption(f"**Slide {i+1}:** {s.get('titulo', 'Sem titulo')}")

            st.caption("Descreva as alteracoes referenciando os slides por numero. "
                       "Slides nao mencionados serao mantidos identicos.")

            edit_instrucao = st.text_area("Alteracoes:", height=150,
                placeholder="Ex:\nSlide 1: Trocar titulo para 'Novo Titulo'\n"
                            "Slide 3: Remover a parte sobre migração\n"
                            "Slide 5: Adicionar bullet sobre modelo de ML",
                key="pptx_edit_instrucao")

            # Check if enhancer returned a result for edit
            enh_edit_result = _pop_enhancer_result("pptx_edit")
            if enh_edit_result:
                _executar_edicao_slides(enh_edit_result, saved_slides)

            elif st.session_state.get("_enh_pptx_edit_state") == "awaiting":
                _enhancer_ui("pptx_edit")

            elif st.button("Aplicar edicoes", type="primary", key="btn_pptx_edit"):
                if not edit_instrucao.strip():
                    st.warning("Descreva o que deseja alterar.")
                elif enhancer_on:
                    _trigger_enhancer(edit_instrucao, "pptx_edit")
                else:
                    _executar_edicao_slides(edit_instrucao, saved_slides)

        # Documentos adicionais de contexto
        mem_docs = st.session_state.get("memoria_arquivos") or []
        if mem_docs:
            st.multiselect("Documentos adicionais de contexto (opcional):",
                          [d["name"] for d in mem_docs], key="pptx_docs_ctx")
        return  # Nao renderizar o resto da aba

    # --- Upload de PPT (para Video ou Gamma) ---
    ppt_conteudo = ""
    ppt_file_ref = None
    needs_ppt = tipo_doc in ("Video Executivo (MP4)", "Apresentacao Interativa (Gamma.app)")

    if tipo_doc == "Video Executivo (MP4)":
        st.markdown("""
Transforme sua apresentacao (PowerPoint ou PDF) em um **video profissional narrado** —
com slides redesenhados na identidade visual Cora, narracao feminina em portugues,
animacoes e transicoes suaves.

**Como funciona:**
1. Faca upload do seu arquivo abaixo (PPTX ou PDF)
2. Adicione instrucoes especificas (opcional)
3. O agente redesenha os slides, gera narracao e monta o video MP4
""")
        ppt_file = st.file_uploader("Upload da apresentação (.pptx ou .pdf):", type=["pptx", "pdf"], key="ppt_video")
        if ppt_file:
            ppt_file_ref = ppt_file
            with st.spinner("Extraindo conteudo do PowerPoint..."):
                ppt_conteudo = extrair_texto_arquivo(ppt_file)
            if ppt_conteudo:
                n_slides = ppt_conteudo.count("--- SLIDE")
                st.success(f"PowerPoint carregado: {n_slides} slides extraidos")
                with st.expander("Ver conteudo extraido"):
                    st.text(ppt_conteudo[:3000] + ("..." if len(ppt_conteudo) > 3000 else ""))
            else:
                st.warning("Nao foi possivel extrair conteudo do PPT.")

    elif tipo_doc == "Apresentacao Interativa (Gamma.app)":
        st.markdown("""
Transforme sua apresentacao (PowerPoint ou PDF) em um **roteiro completo para o Gamma.app** —
com narração, animações, e sugestões visuais para criar uma apresentação interativa e animada.

**Como funciona:**
1. Faca upload do seu arquivo abaixo (PPTX ou PDF)
2. O agente gera o roteiro completo com narração para cada card
3. Copie o conteudo no [Gamma.app](https://gamma.app) e ele cria a apresentação animada
""")
        ppt_file = st.file_uploader("Upload da apresentação (.pptx ou .pdf):", type=["pptx", "pdf"], key="ppt_gamma")
        if ppt_file:
            with st.spinner("Extraindo conteudo do PowerPoint..."):
                ppt_conteudo = extrair_texto_arquivo(ppt_file)
            if ppt_conteudo:
                n_slides = ppt_conteudo.count("--- SLIDE")
                st.success(f"PowerPoint carregado: {n_slides} slides extraidos")
                with st.expander("Ver conteudo extraido"):
                    st.text(ppt_conteudo[:3000] + ("..." if len(ppt_conteudo) > 3000 else ""))
            else:
                st.warning("Nao foi possivel extrair conteudo do PPT.")

    mem_docs_tab = st.session_state.memoria_arquivos or []
    docs_ctx = st.multiselect("Documentos de contexto (memoria):", [d["name"] for d in mem_docs_tab], key="docs_ctx")

    instrucao = st.text_area("Descreva o que precisa:", height=150,
                             placeholder="Ex: Foco nos resultados, tom executivo para diretoria, destacar KPIs..."
                             if needs_ppt
                             else "Ex: Roteiro de apresentacao sobre os KPIs do trimestre...")

    if st.button("Gerar Documento", type="primary"):
        if needs_ppt and not ppt_conteudo:
            st.warning("Faca upload do arquivo (PPTX ou PDF) primeiro.")
        elif not instrucao.strip() and not needs_ppt:
            st.warning("Descreva o documento que precisa.")
        else:
            contexto_docs = ""
            for doc in mem_docs_tab:
                if doc["name"] in docs_ctx:
                    contexto_docs += f"\n--- {doc['name']} ---\n{ler_arquivo_memoria(doc['id'])}\n"

            if tipo_doc == "Video Executivo (MP4)":
                placeholder = st.empty()
                ppt_file_ref.seek(0)
                video_bytes = gerar_video_apresentacao(
                    ppt_file_ref, instrucao, contexto_docs, placeholder
                )
                if video_bytes:
                    video_bytes.seek(0)
                    st.video(video_bytes, format="video/mp4")
                    video_bytes.seek(0)
                    st.download_button(
                        "Baixar Video (MP4)",
                        data=video_bytes,
                        file_name="apresentacao_executiva.mp4",
                        mime="video/mp4",
                        key="dl_video",
                    )

            elif tipo_doc == "Apresentacao Interativa (Gamma.app)":
                with st.spinner("Gerando apresentacao interativa..."):
                    prompt_doc = _GAMMA_PROMPT.format(
                        conteudo_ppt=ppt_conteudo,
                        instrucoes=instrucao or "Nenhuma instrucao adicional.",
                        contexto=contexto_docs or "Nenhum contexto adicional.",
                    )
                    placeholder = st.empty()
                    resposta = _gerar_com_continuacao(client_anthropic, st.session_state.modelo, prompt_doc, placeholder)

                    st.download_button(
                        "Baixar em Word (.docx)", data=criar_docx(resposta),
                        file_name=f"{sanitizar(tipo_doc)}.docx",
                        mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                        key="dl_doc"
                    )

            else:
                with st.spinner("Gerando..."):
                    prompt_doc = f"""Voce e um PM Senior. Gere um {tipo_doc} completo e profissional em portugues.

SOLICITACAO: {instrucao}

CONTEXTO ADICIONAL:
{contexto_docs or "Nenhum contexto adicional fornecido."}

Use formatacao Markdown com titulos, subtitulos e listas bem estruturadas."""

                    placeholder = st.empty()
                    resposta = _gerar_com_continuacao(client_anthropic, st.session_state.modelo, prompt_doc, placeholder)

                    st.download_button(
                        "Baixar em Word (.docx)", data=criar_docx(resposta),
                        file_name=f"{sanitizar(tipo_doc)}.docx",
                        mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                        key="dl_doc_other"
                    )