File size: 11,281 Bytes
3949424
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f98e1a1
 
3949424
 
f98e1a1
 
 
 
3949424
 
 
f98e1a1
 
 
 
3949424
f98e1a1
 
3949424
 
 
 
847c173
3949424
 
 
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
import os
import gradio as gr
import shutil
from typing import List

from src.file_processor import chunk_pdfs, chunk_all_documents
from src.chroma_db import save_to_chroma_db, get_chroma_client
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama import OllamaEmbeddings
from langchain_ollama import ChatOllama

# Initialize components - procesamiento condicional
def initialize_system(process_documents=True):
    """
    Inicializa el sistema RAG con opción de procesar documentos
    """
    if process_documents:
        print("Procesando documentos...")
        processed_documents = chunk_pdfs()

        print("Inicializando modelo de embeddings...")
        embedding_model = OllamaEmbeddings(
            model="nomic-embed-text"
        )

        print("Guardando documentos en la base de datos...")
        db = save_to_chroma_db(processed_documents, embedding_model)
        return db, embedding_model
    else:
        print("Saltando procesamiento de documentos...")
        print("Inicializando modelo de embeddings...")
        embedding_model = OllamaEmbeddings(
            model="nomic-embed-text"
        )

        # Intentar conectar con base de datos existente
        try:
            db = get_chroma_client()
            print("Conectado a base de datos existente")
            return db, embedding_model
        except Exception as e:
            print(f"Error conectando a base de datos existente: {e}")
            return None, embedding_model

# Estado global para controlar si los documentos están procesados
documents_processed = False
db = None
embedding_model = None

# Define the prompt template
PROMPT_TEMPLATE = """
Tienes que responder la siguiente pregunta basada en el contexto proporcionado:
{context}

Responde la siguiente pregunta: {question}

Proporciona una respuesta con un enfoque de análisis histórico, considerando las causas, consecuencias y evolución de los hechos descritos.
Sitúa los eventos en su marco temporal y geopolítico, y explica los factores sociales, económicos y políticos relevantes.
Evita opiniones o juicios de valor y no incluyas información que no esté sustentada en el contexto.
"""


prompt_template = ChatPromptTemplate.from_template(PROMPT_TEMPLATE)

# Initialize Ollama LLM model
model = ChatOllama(model="hf.co/unsloth/granite-4.0-h-small-GGUF:Q2_K_L")



def answer_question(question):
    """
    Función que responde preguntas basadas en el contexto de los documentos usando ChromaDB Docker
    """
    global documents_processed, db

    if not question.strip():
        return "Por favor ingresa una pregunta válida."

    if not documents_processed or db is None:
        return "❌ No hay documentos procesados disponibles. Por favor, procesa algunos documentos primero usando la opción de arriba."

    try:
        # Perform similarity search with the query
        docs = db.similarity_search_with_score(question, k=3)

        if not docs:
            return "No se encontraron documentos relevantes para responder tu pregunta."

        context = "\n\n---\n\n".join([doc.page_content for doc, _score in docs])

        # Generate the prompt
        prompt = prompt_template.format(context=context, question=question)

        # Get response from model
        response = model.invoke(prompt)

        return response.content if hasattr(response, 'content') else str(response)

    except Exception as e:
        return f"Error al procesar la pregunta: {str(e)}. Verifica que ChromaDB Docker esté funcionando en el puerto 8000."

# Definir constante para la carpeta de aportaciones
APORTACIONES_PATH = 'aportaciones'

def handle_file_upload(files) -> str:
    """
    Función que maneja la subida de archivos de los usuarios
    """
    if not files:
        return "😅 ¡Ups! No has seleccionado ningún archivo. ¡Inténtalo de nuevo!"

    success_count = 0
    error_count = 0
    error_messages = []

    # Crear carpeta aportaciones si no existe
    os.makedirs(APORTACIONES_PATH, exist_ok=True)

    for file_obj in files:
        try:
            # Obtener el nombre del archivo
            filename = os.path.basename(file_obj.name)

            # Crear ruta de destino
            destination_path = os.path.join(APORTACIONES_PATH, filename)

            # Copiar el archivo a la carpeta aportaciones
            shutil.copy2(file_obj.name, destination_path)

            print(f"✅ Archivo {filename} subido exitosamente a {APORTACIONES_PATH}")
            success_count += 1

        except Exception as e:
            error_message = f"❌ Error al subir {filename}: {str(e)}"
            print(error_message)
            error_messages.append(error_message)
            error_count += 1

    # Crear mensaje de respuesta jovial
    if success_count > 0 and error_count == 0:
        return f"🎉 ¡Genial! Has subido {success_count} archivo(s) exitosamente a la carpeta 'aportaciones'. ¡Tu conocimiento ahora forma parte del sistema! 🚀"
    elif success_count > 0 and error_count > 0:
        return f"⚠️ {success_count} archivo(s) subido(s) correctamente, pero {error_count} archivo(s) tuvieron problemas:\n" + "\n".join(error_messages)
    else:
        return f"😞 ¡Vaya! Hubo problemas al subir los archivos:\n" + "\n".join(error_messages)

def process_user_documents():
    """
    Función que procesa los documentos subidos por usuarios
    """
    global documents_processed, db, embedding_model

    try:
        print("🔄 Procesando documentos de usuarios...")

        # Procesar documentos de ambas carpetas
        processed_documents = chunk_all_documents()

        if not processed_documents:
            return "😅 No se encontraron documentos para procesar. ¡Sube algunos archivos primero!"

        print("🔗 Inicializando modelo de embeddings...")
        embedding_model = OllamaEmbeddings(
            model="nomic-embed-text"
        )

        print("💾 Guardando documentos en la base de datos...")
        db = save_to_chroma_db(processed_documents, embedding_model)

        documents_processed = True

        return f"🎊 ¡Perfecto! Se procesaron {len(processed_documents)} documentos exitosamente. ¡Ya puedes hacer preguntas sobre tu nuevo contenido! 📚✨"

    except Exception as e:
        return f"❌ Error al procesar documentos: {str(e)}. Asegúrate de que todos los servicios estén funcionando correctamente."

# Create Gradio interface
with gr.Blocks(
    title="Sistema RAG - Consulta de Documentos",
    theme=gr.themes.Soft(),
    css="""
    .gradio-container {
        max-width: 800px;
        margin: auto;
    }
    .title {
        text-align: center;
        color: #2563eb;
        font-size: 2.5em;
        margin-bottom: 1em;
    }
    .subtitle {
        text-align: center;
        color: #64748b;
        font-size: 1.1em;
        margin-bottom: 2em;
    }
    """
) as demo:
    gr.HTML("<h1 class='title'>🤖 Sistema RAG - Consulta de Documentos</h1>")
    gr.HTML("<p class='subtitle'>Haz preguntas sobre el contenido de tus documentos usando IA con ChromaDB Docker</p>")

    gr.HTML("""
    <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
               padding: 20px;
               border-radius: 15px;
               margin: 20px 0;
               text-align: center;
               color: white;">
        <h3 style="margin: 0 0 10px 0;">🚀 ¡Comparte tu conocimiento!</h3>
        <p style="margin: 0; font-size: 1.1em;">
            ¿Tienes documentos interesantes que quieres añadir al sistema?
            ¡Súbelos aquí y forma parte de esta aventura del conocimiento! 📚✨
        </p>
    </div>
    """)

    with gr.Row():
        with gr.Column(scale=2):
            file_upload = gr.File(
                label="📎 Subir documentos",
                file_count="multiple",
                file_types=[".pdf", ".txt", ".md"],
                elem_id="file_upload"
            )

        with gr.Column(scale=1):
            upload_btn = gr.Button(
                "⬆️ Subir archivos",
                variant="secondary",
                size="lg"
            )

    upload_output = gr.Markdown(
        label="Estado de subida",
        elem_id="upload_status"
    )

    with gr.Row():
        process_btn = gr.Button(
            "🔄 Procesar documentos",
            variant="primary",
            size="lg"
        )

    process_output = gr.Markdown(
        label="Estado de procesamiento",
        elem_id="process_status"
    )

    question_input = gr.Textbox(
        label="Tu pregunta",
        placeholder="Ej: ¿Cuáles son los pasos recomendados para fertilizar un jardín de vegetales?",
        lines=3,
        max_lines=10
    )

    submit_btn = gr.Button(
        "🔍 Consultar",
        variant="primary",
        size="lg"
    )

    answer_output = gr.Markdown(
        label="Respuesta",
        show_copy_button=True
    )

    # Examples
    gr.Examples(
        examples=[
            "¿Cuál es el orgigen étnico de los habitantes de Gaza?",
            "¿Qué documentos históricos están disponibles?",
            "¿Qué ocurrió el 7 de octubre de 2023?",
        ],
        inputs=question_input,
        label="Ejemplos de preguntas"
    )

    # Event handlers
    submit_btn.click(
        fn=answer_question,
        inputs=[question_input],
        outputs=[answer_output]
    )

    question_input.submit(
        fn=answer_question,
        inputs=[question_input],
        outputs=[answer_output]
    )

    # Event handlers para subida de archivos
    upload_btn.click(
        fn=handle_file_upload,
        inputs=[file_upload],
        outputs=[upload_output]
    )

    process_btn.click(
        fn=process_user_documents,
        inputs=[],
        outputs=[process_output]
    )

    gr.HTML("""
    <div style="text-align: center; margin-top: 2em; color: #64748b; font-size: 0.9em;">
        <p>Sistema RAG con LangChain, Ollama y ChromaDB Docker</p>
        <p style="font-size: 0.8em; margin-top: 0.5em;">🌐 ChromaDB corriendo en contenedor Docker (puerto 8000)</p>
    </div>
    """)

if __name__ == "__main__":
    print("🚀 Sistema RAG - Consulta de Documentos")
    print("=" * 50)

    # Usar siempre la opción de base de datos existente
    print("💡 ¡Novedad! Los usuarios ahora pueden subir documentos a la carpeta 'aportaciones' desde la interfaz web")
    print("   ¡Comparte tu conocimiento y enriquecer el sistema! 📚✨")

    print("\n🚀 Usando base de datos existente directamente...")
    process_documents = False

    # Inicializar sistema usando base de datos existente
    print("\nInicializando sistema...")
    db, embedding_model = initialize_system(process_documents)

    # Verificar estado de inicialización
    documents_processed = (db is not None)
    if documents_processed:
        print("✅ Sistema inicializado con documentos existentes")
    else:
        print("⚠️ No se pudo conectar a documentos existentes")
        print("💡 Asegúrate de que la base de datos ChromaDB esté disponible en 'chroma/'")

    print("\n🚀 Iniciando interfaz web...")
    demo.launch(
        server_name="0.0.0.0",
        server_port=7862,
        share=True,
        debug=False
    )