File size: 15,171 Bytes
0d05da0
3949185
0d05da0
 
 
 
 
 
8a6fb22
0d05da0
 
1b52886
0d05da0
 
8a6fb22
0d05da0
8a6fb22
 
0d05da0
a643b29
8a6fb22
0d05da0
8a6fb22
0d05da0
 
8a6fb22
9ec04c6
 
 
 
0d05da0
 
8a6fb22
1b52886
8a6fb22
20094e4
 
 
 
 
 
 
 
 
8a6fb22
 
20094e4
8a6fb22
20094e4
 
0d05da0
 
 
cad0242
8a6fb22
1b52886
cad0242
8a6fb22
1b52886
cad0242
9ec04c6
66274bb
9ec04c6
 
8a6fb22
 
9ec04c6
 
8a6fb22
 
9ec04c6
8a6fb22
 
9ec04c6
 
 
8a6fb22
1b52886
8a6fb22
 
9ec04c6
 
8a6fb22
0d05da0
1b52886
 
9ec04c6
8a6fb22
9ec04c6
1b52886
 
 
8a6fb22
66274bb
 
 
 
 
 
 
 
8a6fb22
 
 
66274bb
8a6fb22
1b52886
9ec04c6
 
 
 
8a6fb22
9ec04c6
 
1b52886
9ec04c6
 
0d05da0
cad0242
8a6fb22
3949185
 
 
 
 
 
 
 
 
 
 
 
 
 
20094e4
3949185
 
9ec04c6
 
 
 
 
0d05da0
8a6fb22
cad0242
 
 
deadc30
8a6fb22
 
 
 
 
 
 
 
1b52886
0d05da0
 
9ec04c6
cad0242
66274bb
0d05da0
9ec04c6
 
8a6fb22
 
3949185
 
66274bb
 
 
 
9ec04c6
8a6fb22
 
 
9ec04c6
 
0d05da0
9ec04c6
 
0d05da0
deadc30
8a6fb22
 
9ec04c6
 
 
 
 
 
 
 
8a6fb22
cad0242
9ec04c6
 
0d05da0
8a6fb22
0d05da0
8a6fb22
 
1b52886
9ec04c6
 
3949185
9ec04c6
 
0d05da0
8a6fb22
cad0242
8a6fb22
 
 
 
66274bb
8a6fb22
9ec04c6
 
 
 
8a6fb22
 
3949185
9ec04c6
8a6fb22
 
9ec04c6
8a6fb22
3949185
9ec04c6
8a6fb22
8ef55f5
3949185
66274bb
8a6fb22
 
9ec04c6
 
 
8a6fb22
 
66274bb
8a6fb22
 
 
 
 
 
9ec04c6
8a6fb22
 
9ec04c6
 
 
 
 
8a6fb22
66274bb
8a6fb22
9ec04c6
 
 
 
8a6fb22
 
 
 
 
 
9ec04c6
8a6fb22
 
66274bb
8a6fb22
 
3949185
9ec04c6
8a6fb22
 
9ec04c6
cad0242
8a6fb22
 
 
3949185
9ec04c6
0d05da0
8a6fb22
 
 
3949185
 
9ec04c6
8a6fb22
9ec04c6
8a6fb22
 
66274bb
8a6fb22
cad0242
0d05da0
1b52886
0d05da0
 
8a6fb22
cad0242
8a6fb22
 
 
 
0d05da0
cad0242
0d05da0
8a6fb22
 
 
 
 
0d05da0
8a6fb22
0d05da0
 
cad0242
1b52886
8a6fb22
 
 
 
0d05da0
cad0242
 
8a6fb22
 
 
0d05da0
cad0242
 
8a6fb22
0d05da0
9ec04c6
 
 
 
 
 
1b52886
9ec04c6
8a6fb22
 
1b52886
8a6fb22
0d05da0
 
cad0242
 
8a6fb22
 
cad0242
 
 
 
8a6fb22
cad0242
8a6fb22
 
9ec04c6
 
 
 
 
 
 
 
8a6fb22
 
cad0242
8a6fb22
cad0242
 
8a6fb22
 
 
 
9ec04c6
 
 
 
 
 
 
 
 
 
8a6fb22
 
0d05da0
8a6fb22
 
 
0d05da0
8a6fb22
0d05da0
 
8a6fb22
0d05da0
1b52886
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
# ╔════════════════════════════════════════════════════════════════════════════╗
# ║  PIPELINE v43: FRAG + VISÃO PAGINADA + CONFEXT_UPLOAD + PARSE ROBUSTO     ║
# ╚════════════════════════════════════════════════════════════════════════════╝

import os
import json
import time
from datetime import datetime

import gradio as gr
import google.generativeai as genai
import pypdf  # pip install pypdf

# ==================== 1. CONFIGURAÇÃO ====================

api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
if api_key:
    genai.configure(api_key=api_key)

model_flash = genai.GenerativeModel("gemini-flash-latest")
model_pro   = genai.GenerativeModel("gemini-pro-latest")

ARQUIVO_CONFIG = "protocolo_fragmentacao_visao-3.json"

# ==================== 2. UTILIDADES ====================

def log_point(msg, logs):
    ts = datetime.now().strftime("%H:%M:%S")
    return logs + f"[{ts}] {msg}\n"

def carregar_protocolo():
    try:
        with open(ARQUIVO_CONFIG, "r", encoding="utf-8") as f:
            return f.read()
    except:
        # fallback mínimo válido
        proto = [
            {
                "nome": "PAGINADOR_VISUAL",
                "missao": (
                    "Você recebe o texto bruto de um conjunto de páginas de um PDF. "
                    "Separe por página e devolva uma lista JSON com objetos "
                    "{'pagina','transcricao_fiel','descricao_visual'}."
                    "Retorne APENAS essa lista JSON, sem texto extra."
                ),
                "tipo_saida": "json",
                "modelo": "flash",
            }
        ]
        return json.dumps(proto, ensure_ascii=False, indent=2)

def salvar_protocolo(conteudo):
    try:
        json.loads(conteudo)
        with open(ARQUIVO_CONFIG, "w", encoding="utf-8") as f:
            f.write(conteudo)
        return "✅ Salvo"
    except:
        return "❌ Erro JSON"

# --------- DIVISÃO BURRA COM TEXTO REAL + LOGS ---------

def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=5, logs=""):
    logs = log_point("ler_anexo_e_fragmentar() chamado", logs)

    if arquivo is None:
        logs = log_point("Nenhum arquivo recebido", logs)
        return [], "", logs

    filename = getattr(arquivo, "name", arquivo)
    logs = log_point(f"Arquivo recebido: {filename}", logs)

    if not os.path.exists(filename):
        msg = f"Arquivo não encontrado: {filename}"
        logs = log_point(msg, logs)
        return [], f"[ERRO: {msg}]", logs

    anexo_info = f"[PDF: {os.path.basename(filename)}]"

    if not filename.lower().endswith(".pdf"):
        logs = log_point("Arquivo não é PDF; tratado como texto simples", logs)
        return [f"[ARQUIVO_TEXTO: {os.path.basename(filename)}]"], anexo_info, logs

    try:
        reader = pypdf.PdfReader(filename)
        total_pages = len(reader.pages)
        logs = log_point(f"PDF com {total_pages} páginas", logs)

        fragments = []
        for i in range(0, total_pages, paginas_por_fragmento):
            start = i + 1
            end = min(i + paginas_por_fragmento, total_pages)

            bloco_texto = ""
            for p in range(i, end):
                try:
                    t = reader.pages[p].extract_text() or ""
                except Exception as e:
                    t = f"\n[ERRO_EXTRACT_PAG_{p+1}: {e}]\n"
                bloco_texto += f"\n=== PAGINA {p+1}/{total_pages} ===\n{t}\n"

            fragment = (
                f"=== FRAG {i//paginas_por_fragmento + 1} "
                f"(PÁGS {start}-{end}/{total_pages}) ===\n"
                f"{bloco_texto.strip()}"
            )
            fragments.append(fragment)
            logs = log_point(
                f"Fragmento {i//paginas_por_fragmento + 1} criado (pags {start}-{end})",
                logs,
            )

        logs = log_point(f"Total de fragmentos: {len(fragments)}", logs)
        return fragments, anexo_info, logs
    except Exception as e:
        logs = log_point(f"ERRO PDF: {e}", logs)
        return [f"[ERRO PDF: {str(e)}]"], anexo_info, logs

# ==================== 3. ENGINE DE EXECUÇÃO ====================

def _extrair_json_possivel(out_raw: str) -> str:
    """
    Tenta isolar só o bloco JSON de uma resposta que pode ter texto extra.
    Procura o primeiro 'json.
    """
    cleaned = out_raw.strip()
    idx_abre_col = cleaned.find("

    # menor índice válido
    candidatos = [i for i in [idx_abre_col, idx_abre_obj] if i != -1]
    if candidatos:
        start = min(candidatos)
        cleaned = cleaned[start:]

    cleaned = cleaned.replace("```json", "").replace("```")
    return cleaned

def executar_no(timeline, config, fragmento_input=None, logs=""):
    logs = log_point(f"executar_no({config['nome']}) chamado", logs)
    modo = "input_fragmento" if fragmento_input is not None else "timeline"
    logs = log_point(f"Modo de entrada: {modo}", logs)

    modelo = model_pro if config.get("modelo") == "pro" else model_flash

    if fragmento_input is not None:
        input_para_prompt = fragmento_input
    else:
        input_para_prompt = json.dumps(timeline, ensure_ascii=False, indent=2)

    prompt = (
        "--- INPUT PARA O AGENTE ---\n"
        f"{input_para_prompt}\n"
        "----------------\n"
        f"AGENTE: {config['nome']}\n"
        f"MISSÃO: {config['missao']}"
    )

    try:
        inicio = time.time()
        logs = log_point("Chamando modelo.generate_content()", logs)
        resp = modelo.generate_content(prompt)
        out = resp.text or ""
        tempo = time.time() - inicio
        logs = log_point(f"Tempo de geração: {tempo:.2f}s", logs)
        logs = log_point(f"Saída bruta (120 chars): {out[:120]!r}", logs)

        if config["tipo_saida"] == "json":
            cleaned = _extrair_json_possivel(out)
            logs = log_point(f"Trecho candidato a JSON (120): {cleaned[:120]!r}", logs)
            try:
                content = json.loads(cleaned)
            except Exception as e:
                content = []
                logs = log_point(f"ERRO JSON parse: {e}", logs)
        else:
            content = out

        logs = log_point("executar_no() concluído com sucesso", logs)
        return {"role": "assistant", "agent": config["nome"], "content": content}, logs, out
    except Exception as e:
        logs = log_point(f"ERRO em executar_no: {e}", logs)
        return {"role": "system", "error": str(e)}, logs, str(e)

# ==================== 4. ORQUESTRADOR ====================

def orquestrador(texto, arquivo, history, json_config, confext_state):
    logs = f"🚀 START: {datetime.now().strftime('%H:%M:%S')}\n"
    logs = log_point("orquestrador() iniciado", logs)
    logs = log_point(f"Texto len={len(texto or '')}", logs)

    fragmentos, anexo_info, logs = ler_anexo_e_fragmentar(
        arquivo, paginas_por_fragmento=5, logs=logs
    )
    logs = log_point(f"Qtd fragmentos após leitura: {len(fragmentos)}", logs)

    if not texto and not fragmentos:
        logs = log_point("Sem texto e sem fragmentos; encerrando", logs)
        yield history, {}, logs, confext_state
        return

    history = history + [[texto + (" 📎" if arquivo else ""), None]]

    try:
        protocolo = json.loads(json_config)
        logs = log_point("Protocolo JSON carregado", logs)
    except Exception as e:
        history[-1] = "❌ Erro no JSON de Configuração."[3]
        logs = log_point(f"ERRO carregando protocolo: {e}", logs)
        yield history, {}, logs, confext_state
        return

    timeline = [{"role": "user", "content": texto}]
    confext_upload = {
        "arquivo": os.path.basename(getattr(arquivo, "name", "sem_arquivo"))
        if arquivo else None,
        "meta": anexo_info,
        "paginas": []
    }
    logs = log_point(
        f"confext_upload inicializado para arquivo={confext_upload['arquivo']}",
        logs,
    )

    if fragmentos:
        history[-1] = "⏳ Fragmentando + visão paginada..."[3]
        logs = log_point("Fragmentos disponíveis; iniciando visão paginada", logs)
        yield history, timeline, logs, confext_upload

    # PASSO PAGINADOR_VISUAL (primeiro agente, se existir)
    if protocolo and fragmentos:
        cfg_visao = protocolo
        logs = log_point(f"Agente de visão selecionado: {cfg_visao['nome']}", logs)

        for i, fragmento in enumerate(fragmentos):
            history[-1] = f"👁️ {cfg_visao['nome']} frag {i+1}/{len(fragmentos)}..."[3]
            logs = log_point(f"Enviando frag {i+1}", logs)
            yield history, timeline, logs, confext_upload

            res, logs, raw = executar_no(
                timeline, cfg_visao, fragmento_input=fragmento, logs=logs
            )

            if "error" in res:
                logs = log_point(f"Erro no frag {i+1}: {res['error']}", logs)
                continue

            try:
                paginas_res = res["content"]
                if isinstance(paginas_res, dict):
                    paginas_res = [paginas_res]
                antes = len(confext_upload["paginas"])
                for p in paginas_res:
                    confext_upload["paginas"].append(p)
                depois = len(confext_upload["paginas"])
                logs = log_point(
                    f"Frag {i+1} adicionou {depois-antes} páginas; total={depois}",
                    logs,
                )
            except Exception as e:
                logs = log_point(f"Falha ao anexar páginas do frag {i+1}: {e}", logs)

        logs = log_point(
            f"Visão paginada concluída; paginas={len(confext_upload['paginas'])}",
            logs,
        )

    timeline.append({
        "role": "system",
        "agent": "CONFEXT_UPLOAD",
        "content": confext_upload
    })
    logs = log_point("CONFEXT_UPLOAD injetado na timeline", logs)

    restante = protocolo[1:] if protocolo else []
    final_response = ""

    for cfg in restante:
        history[-1] = f"⚙️ {cfg['nome']}..."[3]
        logs = log_point(f"Iniciando passo adicional: {cfg['nome']}", logs)
        yield history, timeline, logs, confext_upload

        res, logs, raw = executar_no(timeline, cfg, fragmento_input=None, logs=logs)
        timeline.append(res)

        if cfg["tipo_saida"] == "texto":
            final_response = res["content"]
            history[-1] = final_response[3]
            logs = log_point(f"Passo {cfg['nome']} produziu texto final", logs)

        yield history, timeline, logs, confext_upload

    if not restante and not texto:
        history[-1] = "✅ PDF processado. Pronto para perguntas usando confext_upload."[3]
        final_response = history[-1][3]
        logs = log_point("Nenhum passo adicional; apenas pré-processamento", logs)

    logs = log_point("FIM orquestrador()", logs)
    yield history, timeline, logs, confext_upload

# ==================== 5. UI ====================

def ui_clean():
    css = """
    footer {display: none !important;}
    .contain {border: none !important;}
    """

    config_init = carregar_protocolo()

    with gr.Blocks(title="AI Forensics – Visão Paginada", css=css, theme=gr.themes.Soft()) as app:
        confext_state = gr.State(value=None)

        with gr.Tabs():
            with gr.Tab("💬 Investigador"):
                chatbot = gr.Chatbot(
                    label="",
                    show_label=False,
                    height=600,
                    show_copy_button=True,
                    render_markdown=True,
                )

                with gr.Row():
                    with gr.Column(scale=10):
                        txt_in = gr.Textbox(
                            show_label=False,
                            placeholder="Descreva o caso ou faça perguntas (opcional após upload)...",
                            lines=1,
                            max_lines=5,
                            container=False,
                        )
                    with gr.Column(scale=1, min_width=50):
                        file_in = gr.UploadButton(
                            "📎",
                            file_types=[".txt", ".md", ".csv", ".json", ".pdf"],
                            size="sm",
                        )
                    with gr.Column(scale=1, min_width=80):
                        btn_send = gr.Button("Enviar", variant="primary", size="sm")

                file_status = gr.Markdown("", visible=True)

                def _on_upload(x):
                    nome = os.path.basename(getattr(x, "name", x))
                    print("[DEBUG] upload arquivo:", nome)
                    return f"📎 Anexo recebido: {nome}"

                file_in.upload(
                    _on_upload,
                    inputs=file_in,
                    outputs=file_status,
                )

            with gr.Tab("🕵️ Depuração"):
                with gr.Row():
                    out_dna = gr.JSON(label="DNA (Timeline)")
                    out_logs = gr.Textbox(label="Logs do Sistema", lines=20)
                confext_view = gr.JSON(label="confext_upload")

            with gr.Tab("⚙️ Config"):
                with gr.Row():
                    btn_save = gr.Button("Salvar Config")
                    lbl_save = gr.Label(show_label=False)
                code_json = gr.Code(value=config_init, language="json", label=ARQUIVO_CONFIG)
                btn_save.click(salvar_protocolo, code_json, lbl_save)

        def _orq_wrapper(texto, arquivo, history, json_cfg, confext_old):
            print(
                "[DEBUG] _orq_wrapper disparado",
                "len_texto=", len(texto or ""),
                "arquivo=", getattr(arquivo, "name", None),
            )
            for h, dna, logs, confext_new in orquestrador(
                texto, arquivo, history, json_cfg, confext_old
            ):
                yield h, dna, logs, confext_new

        triggers = [btn_send.click, txt_in.submit]

        for trig in triggers:
            trig(
                _orq_wrapper,
                inputs=[txt_in, file_in, chatbot, code_json, confext_state],
                outputs=[chatbot, out_dna, out_logs, confext_state],
            ).then(
                lambda c: (
                    print(
                        "[DEBUG] pós-envio; paginas_confext=",
                        0 if not c else len(c.get("paginas", [])),
                    ),
                    None,
                    None,
                    "",
                    c,
                )[1:],
                inputs=confext_state,
                outputs=[txt_in, file_in, file_status, confext_state],
            ).then(
                lambda c: c,
                inputs=confext_state,
                outputs=confext_view,
            )

    return app


if __name__ == "__main__":
    ui_clean().launch()