Spaces:
Running
Running
| 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" | |
| ) | |