File size: 17,724 Bytes
58486c5
43ac004
72e3c06
 
43ac004
 
 
 
 
 
 
 
 
 
5435b87
43ac004
 
c6add89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1013dbf
c6add89
 
 
 
 
 
 
 
 
 
afd06a9
 
c6add89
 
 
 
 
 
 
 
26a30cd
 
c6add89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1013dbf
43ac004
 
 
 
 
 
 
 
ba31095
43ac004
263d6ed
 
 
 
 
26a30cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ba31095
43ac004
 
 
1013dbf
 
 
 
 
 
 
ca0ae1f
 
1013dbf
 
28f8836
 
 
 
 
1013dbf
 
 
 
 
ca0ae1f
1013dbf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ca0ae1f
1013dbf
 
 
 
 
 
 
 
 
 
ca0ae1f
28f8836
 
 
 
 
1013dbf
 
28f8836
1013dbf
 
 
ca0ae1f
28f8836
1013dbf
 
 
 
 
28f8836
 
 
 
1013dbf
 
 
28f8836
ca0ae1f
1013dbf
 
 
28f8836
 
1013dbf
 
 
28f8836
ca0ae1f
1013dbf
 
 
 
28f8836
ca0ae1f
28f8836
1013dbf
 
 
 
28f8836
 
 
 
 
1013dbf
 
 
 
ca0ae1f
28f8836
1013dbf
 
 
 
28f8836
1013dbf
 
 
 
28f8836
ca0ae1f
28f8836
 
 
1013dbf
 
 
28f8836
ca0ae1f
28f8836
 
 
1013dbf
 
 
28f8836
ca0ae1f
28f8836
 
 
1013dbf
 
 
 
 
 
 
 
 
afd06a9
1013dbf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72e3c06
1013dbf
afd06a9
43ac004
 
1013dbf
 
 
 
 
 
 
28f8836
1013dbf
 
 
 
 
28f8836
1013dbf
 
 
 
 
 
 
 
 
 
 
28f8836
1013dbf
 
 
 
 
 
 
 
 
 
afd06a9
1013dbf
 
28f8836
1013dbf
 
 
 
 
 
 
 
 
 
 
 
 
 
28f8836
1013dbf
 
28f8836
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72e3c06
 
ba31095
 
 
 
74f5196
c6add89
ba31095
 
 
cb8f33a
ba31095
cb8f33a
43ac004
 
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
import gradio as gr
import os
from functools import partial

api_token = os.getenv("HF_TOKEN")

from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import ConversationalRetrievalChain
from langchain_community.embeddings import HuggingFaceEmbeddings 
from langchain.memory import ConversationBufferMemory
from langchain_community.llms import HuggingFaceEndpoint

list_llm = ["meta-llama/Meta-Llama-3-8B-Instruct", "mistralai/Mistral-7B-Instruct-v0.2", "deepseek-ai/DeepSeek-R1"]  
list_llm_simple = [os.path.basename(llm) for llm in list_llm]

# Load and split PDF document
def load_doc(list_file_path):
    loaders = [PyPDFLoader(x) for x in list_file_path]
    pages = []
    for loader in loaders:
        pages.extend(loader.load())
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=64)
    doc_splits = text_splitter.split_documents(pages)
    return doc_splits

# Create vector database
def create_db(splits):
    embeddings = HuggingFaceEmbeddings()
    vectordb = FAISS.from_documents(splits, embeddings)
    return vectordb

# Initialize database
def initialize_database(list_file_obj, progress=gr.Progress()):
    list_file_path = [x.name for x in list_file_obj if x is not None]
    doc_splits = load_doc(list_file_path)
    vector_db = create_db(doc_splits)
    return vector_db, "Database created successfully! ✅"

# Initialize langchain LLM chain
def initialize_llmchain(llm_model, temperature, max_tokens, top_k, vector_db, progress=gr.Progress()):
    if llm_model == "meta-llama/Meta-Llama-3-8B-Instruct":
        llm = HuggingFaceEndpoint(
            repo_id=llm_model,
            huggingfacehub_api_token=api_token,
            temperature=temperature,
            max_new_tokens=max_tokens,
            top_k=top_k,
            timeout=120,
            max_retries=3
        )
    else:
        llm = HuggingFaceEndpoint(
            huggingfacehub_api_token=api_token,
            repo_id=llm_model, 
            temperature=temperature,
            max_new_tokens=max_tokens,
            top_k=top_k,
            timeout=120,
            max_retries=3
        )
    memory = ConversationBufferMemory(memory_key="chat_history", output_key='answer', return_messages=True)
    retriever = vector_db.as_retriever()
    qa_chain = ConversationalRetrievalChain.from_llm(
        llm,
        retriever=retriever,
        chain_type="stuff", 
        memory=memory,
        return_source_documents=True,
        verbose=False,
    )
    return qa_chain

# Initialize LLM
def initialize_LLM(llm_option, llm_temperature, max_tokens, top_k, vector_db, progress=gr.Progress()):
    llm_name = list_llm[llm_option]
    qa_chain = initialize_llmchain(llm_name, llm_temperature, max_tokens, top_k, vector_db, progress)
    return qa_chain, "QA chain initialized. Chatbot is ready! 🚀"

def format_chat_history(message, chat_history):
    formatted_chat_history = []
    for user_message, bot_message in chat_history:
        formatted_chat_history.append(f"User: {user_message}")
        formatted_chat_history.append(f"Assistant: {bot_message}")
    return formatted_chat_history

def conversation(qa_chain, message, history, language):
    formatted_chat_history = format_chat_history(message, history)
    if language == "Português":
        prompt = f"Responda em português: {message}"
    else:
        prompt = f"Answer in English: {message}"
    
    try:
        response = qa_chain.invoke({"question": prompt, "chat_history": formatted_chat_history})
        response_answer = response["answer"]
        if response_answer.find("Helpful Answer:") != -1:
            response_answer = response_answer.split("Helpful Answer:")[-1]
    except Exception as e:
        if language == "Português":
            response_answer = f"Erro: Não foi possível obter resposta do modelo devido a problemas no servidor. Tente novamente mais tarde. ({str(e)})"
        else:
            response_answer = f"Error: Could not get a response from the model due to server issues. Please try again later. ({str(e)})"
    
    try:
        response_sources = response["source_documents"]
        response_source1 = response_sources[0].page_content.strip()
        response_source1_page = response_sources[0].metadata["page"] + 1
        response_source2 = response_sources[1].page_content.strip()
        response_source2_page = response_sources[1].metadata["page"] + 1
        response_source3 = response_sources[2].page_content.strip()
        response_source3_page = response_sources[2].metadata["page"] + 1
    except:
        response_source1 = response_source2 = response_source3 = "N/A"
        response_source1_page = response_source2_page = response_source3_page = 0
    
    new_history = history + [(message, response_answer)]
    return qa_chain, gr.update(value=""), new_history, response_source1, response_source1_page, response_source2, response_source2_page, response_source3, response_source3_page

# Main demo with enhanced UI
def demo():
    # Custom CSS
    custom_css = """
    /* Global styles */
    body {
        font-family: 'Inter', sans-serif;
        color: #333333; /* Dark Gray Text */
        background-color: #f7f7f7; /* Light Gray Background */
    }
    
    .container {
        max-width: 1200px;
        margin: 0 auto;
    }
    
    /* Header styles */
    .header {
        text-align: center;
        padding: 20px 0;
        margin-bottom: 20px;
        background: linear-gradient(90deg, #3171c7, #24599b); /* Primary & Secondary Blue */
        color: white;
        border-radius: 10px;
        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    }
    
    .header h1 {
        font-size: 2.5rem;
        margin: 0;
        padding: 0;
    }
    
    .header p {
        font-size: 1.1rem;
        margin: 10px 0 0;
        opacity: 0.9;
    }
    
    /* Card styles */
    .card {
        background-color: white;
        border-radius: 10px;
        padding: 20px;
        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
        margin-bottom: 20px;
    }
    
    /* Section titles */
    .section-title {
        font-size: 1.25rem;
        font-weight: 600;
        margin-bottom: 15px;
        color: #3171c7; /* Primary Blue */
        display: flex;
        align-items: center;
    }
    
    .section-title svg {
        margin-right: 8px;
    }
    
    /* Buttons */
    .primary-button {
        background: linear-gradient(90deg, #3171c7, #24599b); /* Primary & Secondary Blue */
        color: white !important;
        border: none !important;
        padding: 10px 20px !important;
        border-radius: 8px !important;
        font-weight: 500 !important;
        cursor: pointer;
        transition: all 0.2s ease;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
    }
    
    .primary-button:hover {
        background: linear-gradient(90deg, #24599b, #1d4a83); /* Darker Blue on Hover */
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15) !important;
        transform: translateY(-1px);
    }
    
    /* Status indicators */
    .status {
        padding: 8px 12px !important;
        border-radius: 6px !important;
        font-size: 0.9rem !important;
        font-weight: 500 !important;
    }
    
    .status-success {
        background-color: #d1fae5 !important;
        color: #065f46 !important; /* Teal */
    }
    
    .status-waiting {
        background-color: #fef3c7 !important;
        color: #92400e !important;
    }
    
    .status-error {
        background-color: #fee2e2 !important;
        color: #d9534f !important; /* Red */
    }
    
    /* Chat container */
    .chat-container {
        border-radius: 10px !important;
        border: 1px solid #e0e0e0 !important; /* Medium Gray Border */
        overflow: hidden !important;
    }
    
    /* Document upload area */
    .upload-area {
        border: 2px dashed #d1d5db !important;
        border-radius: 8px !important;
        padding: 20px !important;
        text-align: center !important;
        background-color: #f9fafb !important;
        transition: all 0.2s ease;
    }
    
    .upload-area:hover {
        border-color: #3171c7 !important; /* Primary Blue on Hover */
        background-color: #eff6ff !important;
    }
    
    /* Parameter sliders */
    .parameter-slider {
        margin-bottom: 15px !important;
    }
    
    /* Reference boxes */
    .reference-box {
        background-color: #f3f4f6 !important;
        border-left: 4px solid #3171c7 !important; /* Primary Blue */
        padding: 10px 15px !important;
        margin-bottom: 10px !important;
        border-radius: 4px !important;
    }
    
    .reference-box-title {
        font-weight: 600 !important;
        color: #3171c7 !important; /* Primary Blue */
        margin-bottom: 5px !important;
        display: flex !important;
        justify-content: space-between !important;
    }
    
    .page-number {
        background-color: #dbeafe !important;
        color: #3171c7 !important; /* Primary Blue */
        padding: 2px 8px !important;
        border-radius: 12px !important;
        font-size: 0.8rem !important;
    }
    
    /* Responsive adjustments */
    @media (max-width: 768px) {
        .header h1 {
            font-size: 1.8rem;
        }
    }
    """

    # HTML Components
    header_html = """
    <div class="header">
        <h1>📚 RAG PDF Chatbot</h1>
        <p>Query your documents with AI-powered search and generation</p>
    </div>
    """
    
    upload_html = """
    <div class="section-title">
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
            <polyline points="17 8 12 3 7 8"></polyline>
            <line x1="12" y1="3" x2="12" y2="15"></line>
        </svg>
        Upload your PDF documents
    </div>
    <p>Select one or more PDF files to analyze and chat with.</p>
    """
    
    model_html = """
    <div class="section-title">
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M12 2L2 7l10 5 10-5-10-5z"></path>
            <path d="M2 17l10 5 10-5"></path>
            <path d="M2 12l10 5 10-5"></path>
        </svg>
        Select AI Model
    </div>
    <p>Choose the language model that will process your questions.</p>
    """
    
    chat_html = """
    <div class="section-title">
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
        </svg>
        Chat with your Documents
    </div>
    <p>Ask questions about your uploaded documents to get AI-powered answers.</p>
    """
    
    reference_html = """
    <div class="section-title">
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
            <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
        </svg>
        Document References
    </div>
    <p>These are the relevant sections from your documents that the AI used to generate its response.</p>
    """

    with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="blue", neutral_hue="slate"), css=custom_css) as demo:
        # State variables
        vector_db = gr.State()
        qa_chain = gr.State()
        
        # Header
        gr.HTML(header_html)
        
        with gr.Row():
            # Left column - Setup
            with gr.Column(scale=1):
                with gr.Group(elem_classes="card"):
                    gr.HTML(upload_html)
                    document = gr.Files(height=200, file_count="multiple", file_types=["pdf"], interactive=True)
                    db_btn = gr.Button("Create Vector Database", elem_classes="primary-button")
                    db_progress = gr.Textbox(value="Not initialized", show_label=False, elem_classes="status status-waiting")
                
                with gr.Group(elem_classes="card"):
                    gr.HTML(model_html)
                    llm_btn = gr.Radio(list_llm_simple, label="", value=list_llm_simple[0], type="index")
                    
                    with gr.Accordion("Advanced Parameters", open=False):
                        slider_temperature = gr.Slider(minimum=0.01, maximum=1.0, value=0.5, step=0.1, label="Temperature", interactive=True, elem_classes="parameter-slider")
                        slider_maxtokens = gr.Slider(minimum=128, maximum=9192, value=4096, step=128, label="Max Tokens", interactive=True, elem_classes="parameter-slider")
                        slider_topk = gr.Slider(minimum=1, maximum=10, value=3, step=1, label="Top-K", interactive=True, elem_classes="parameter-slider")
                    
                    qachain_btn = gr.Button("Initialize Chatbot", elem_classes="primary-button")
                    llm_progress = gr.Textbox(value="Not initialized", show_label=False, elem_classes="status status-waiting")
                
                with gr.Group(elem_classes="card"):
                    gr.Markdown("### Usage Instructions")
                    gr.Markdown("""
                    1. Upload one or more PDF documents
                    2. Click "Create Vector Database"
                    3. Select your preferred AI model
                    4. Click "Initialize Chatbot"
                    5. Start asking questions about your documents
                    
                    **Note:** The system will analyze your documents and use AI to answer questions based on their content.
                    """)
            
            # Right column - Chat
            with gr.Column(scale=1.5):
                with gr.Group(elem_classes="card"):
                    gr.HTML(chat_html)
                    language_selector = gr.Radio(["English", "Português"], label="Response Language", value="English")
                    
                    chatbot = gr.Chatbot(height=400, elem_classes="chat-container")
                    
                    with gr.Row():
                        with gr.Column(scale=4):
                            msg = gr.Textbox(placeholder="Ask a question about your documents...", show_label=False)
                        with gr.Column(scale=1):
                            submit_btn = gr.Button("Send", elem_classes="primary-button")
                    
                    with gr.Row():
                        clear_btn = gr.Button("Clear Chat", scale=1)
                
                with gr.Group(elem_classes="card"):
                    gr.HTML(reference_html)
                    with gr.Accordion("Document References", open=True):
                        # Reference 1
                        gr.Markdown("**Reference 1**", elem_classes="reference-box-title")
                        with gr.Row():
                            doc_source1 = gr.Textbox(show_label=False, lines=2, elem_classes="reference-box")
                            source1_page = gr.Number(label="Page", show_label=True, elem_classes="page-number")
                        
                        # Reference 2
                        gr.Markdown("**Reference 2**", elem_classes="reference-box-title")
                        with gr.Row():
                            doc_source2 = gr.Textbox(show_label=False, lines=2, elem_classes="reference-box")
                            source2_page = gr.Number(label="Page", show_label=True, elem_classes="page-number")
                        
                        # Reference 3
                        gr.Markdown("**Reference 3**", elem_classes="reference-box-title")
                        with gr.Row():
                            doc_source3 = gr.Textbox(show_label=False, lines=2, elem_classes="reference-box")
                            source3_page = gr.Number(label="Page", show_label=True, elem_classes="page-number")

        # Preprocessing events
        db_btn.click(initialize_database, inputs=[document], outputs=[vector_db, db_progress])
        qachain_btn.click(initialize_LLM, inputs=[llm_btn, slider_temperature, slider_maxtokens, slider_topk, vector_db], outputs=[qa_chain, llm_progress]).then(
            lambda: [None, "", 0, "", 0, "", 0], inputs=None, outputs=[chatbot, doc_source1, source1_page, doc_source2, source2_page, doc_source3, source3_page], queue=False
        )

        # Chatbot events
        msg.submit(conversation, inputs=[qa_chain, msg, chatbot, language_selector], outputs=[qa_chain, msg, chatbot, doc_source1, source1_page, doc_source2, source2_page, doc_source3, source3_page], queue=False)
        submit_btn.click(conversation, inputs=[qa_chain, msg, chatbot, language_selector], outputs=[qa_chain, msg, chatbot, doc_source1, source1_page, doc_source2, source2_page, doc_source3, source3_page], queue=False)
        clear_btn.click(lambda: [None, "", 0, "", 0, "", 0], inputs=None, outputs=[chatbot, doc_source1, source1_page, doc_source2, source2_page, doc_source3, source3_page], queue=False)

    demo.queue().launch(debug=True)

if __name__ == "__main__":
    demo()