File size: 5,480 Bytes
74390ec
 
 
9ef5861
74390ec
 
 
 
 
225b022
74390ec
 
 
6402d0a
 
 
 
 
 
b85c2f0
6402d0a
 
 
b85c2f0
6402d0a
b85c2f0
6402d0a
450b116
6402d0a
 
2cb9f23
74390ec
6402d0a
74390ec
 
9ef5861
27cafa5
74390ec
 
 
6402d0a
 
450b116
27cafa5
 
450b116
27cafa5
 
450b116
27cafa5
 
6402d0a
 
9ef5861
6402d0a
 
 
 
0dd06b3
 
6402d0a
0dd06b3
6402d0a
 
0dd06b3
6402d0a
 
0dd06b3
6402d0a
 
 
 
2cb9f23
0dd06b3
 
6402d0a
81b3fa7
6402d0a
 
3813ca8
6402d0a
 
3813ca8
6402d0a
 
 
 
 
 
 
 
 
 
 
3813ca8
6402d0a
74390ec
6b186dd
7633bea
6402d0a
 
 
 
 
7633bea
6402d0a
 
 
 
 
 
 
0dd06b3
6402d0a
0dd06b3
 
 
 
6402d0a
 
0dd06b3
6402d0a
 
0dd06b3
6402d0a
 
 
 
0dd06b3
6402d0a
 
 
 
 
0dd06b3
3813ca8
6402d0a
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
import streamlit as st
from PyPDF2 import PdfReader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS
from langchain_groq import ChatGroq
from langchain.chains.question_answering import load_qa_chain
from langchain.prompts import PromptTemplate
from dotenv import load_dotenv
import re

load_dotenv()

# Configuración inicial
st.set_page_config(page_title="PDF Consultor 🔍", page_icon="🔍", layout="wide")
GROQ_API_KEY = os.getenv("GROQ_API_KEY")

# CSS personalizado
st.markdown("""
<style>
    .response-box { padding: 20px; background-color: #f8f9fa; border-radius: 10px; border-left: 5px solid #252850; margin: 20px 0; }
    .metadata-box { padding: 20px; background-color: #f0f2f6; border-radius: 10px; margin-bottom: 20px; }
    .step-number { font-size: 24px; font-weight: bold; }
</style>
""", unsafe_allow_html=True)

# Funciones auxiliares
def eliminar_proceso_pensamiento(texto):
    limpio = re.sub(r'<think>.*?</think>', '', texto, flags=re.DOTALL)
    return limpio.strip(), re.search(r'<think>(.*?)</think>', texto, re.DOTALL).group(1) if "<think>" in texto else "No disponible"

def get_pdf_text(pdf_docs):
    return "".join([page.extract_text() for pdf in pdf_docs for page in PdfReader(pdf).pages])

def get_vector_store(text_chunks):
    embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
    return FAISS.from_texts(text_chunks, embedding=embeddings)

def get_conversational_chain():
    prompt_template = """
    Responde en español exclusivamente con la información solicitada usando el contexto.
    Si no hay información, di "No disponible".
    
    Contexto:
    {context}
    
    Pregunta:
    {question}
    
    Respuesta:
    """
    model = ChatGroq(temperature=0.2, model_name="deepseek-r1-distill-llama-70b", groq_api_key=GROQ_API_KEY)
    return load_qa_chain(model, chain_type="stuff", prompt=PromptTemplate(template=prompt_template, input_variables=["context", "question"]))

def procesar_consulta(pregunta):
    if 'vector_store' not in st.session_state:
        st.error("Por favor carga un documento primero")
        return
    
    chain = get_conversational_chain()
    docs = st.session_state.vector_store.similarity_search(pregunta)
    
    with st.spinner("Analizando documento..."):
        response = chain({"input_documents": docs, "question": pregunta}, return_only_outputs=True)
    
    respuesta_final, pensamiento = eliminar_proceso_pensamiento(response['output_text'])
    mostrar_respuesta(respuesta_final, pensamiento)

def mostrar_respuesta(respuesta, pensamiento):
    st.markdown(f'<div class="response-box">{respuesta}</div>', unsafe_allow_html=True)
    with st.expander("💭 Pensamiento del modelo"):
        st.write(pensamiento)

def generar_sugerencias():
    if 'vector_store' not in st.session_state:
        return []
    
    docs = st.session_state.vector_store.similarity_search("", k=3)
    context = "\n".join([doc.page_content for doc in docs])
    
    prompt_template = """
    Genera exactamente 3 preguntas simples en español basadas en este contexto.
    
    Contexto:
    {context}
    
    Preguntas sugeridas:
    """
    
    model = ChatGroq(temperature=0.4, model_name="deepseek-r1-distill-llama-70b", groq_api_key=GROQ_API_KEY)
    response = model.invoke(prompt_template.format(context=context))
    
    preguntas = [line.split('. ', 1)[1] for line in response.content.split("\n") if line.strip() and line[0].isdigit()]
    return preguntas[:3]

# Aplicación principal
def main():
    st.title("PDF Consultor 🔍")

    # Estados de sesión
    if 'documento_cargado' not in st.session_state:
        st.session_state.documento_cargado = False
        st.session_state.sugerencias = []
        st.session_state.pregunta_actual = ""

    # Sidebar de carga de documentos
    with st.sidebar:
        st.markdown('<p class="step-number">1. Subir archivos</p>', unsafe_allow_html=True)
        pdf_docs = st.file_uploader("Subir PDF(s)", accept_multiple_files=True, type=["pdf"])
    
        if pdf_docs and not st.session_state.documento_cargado:
            with st.spinner("Procesando documento..."):
                raw_text = get_pdf_text(pdf_docs)
                text_chunks = RecursiveCharacterTextSplitter(chunk_size=5000, chunk_overlap=500).split_text(raw_text)
                vector_store = get_vector_store(text_chunks)
                st.session_state.vector_store = vector_store
                st.session_state.documento_cargado = True
                st.session_state.sugerencias = generar_sugerencias()
                st.success("Documento procesado exitosamente.")
                st.experimental_rerun()

    # Mostrar sugerencias y formulario principal
    if st.session_state.documento_cargado:
        if st.session_state.sugerencias:
            st.subheader("💡 Preguntas sugeridas:")
            for pregunta in st.session_state.sugerencias:
                if st.button(pregunta):
                    procesar_consulta(pregunta)

        with st.form("consulta_form"):
            pregunta_usuario = st.text_input("Escribe tu pregunta:", placeholder="Ej: ¿Qué normativa regula este proceso?")
            enviar = st.form_submit_button("Enviar ▶")
            if enviar and pregunta_usuario:
                procesar_consulta(pregunta_usuario)

if __name__ == "__main__":
    main()