File size: 13,134 Bytes
2c34bfb
 
 
e98ddaa
2c34bfb
67bb883
bbcc1c1
2c34bfb
 
8b5b3e2
2c34bfb
 
 
 
 
 
 
 
 
 
bbcc1c1
2c34bfb
085878e
 
2c34bfb
 
 
 
 
bbcc1c1
 
2c34bfb
 
 
 
 
bbcc1c1
2c34bfb
 
 
 
bbcc1c1
578c38e
 
2c34bfb
 
e98ddaa
 
bbcc1c1
 
e98ddaa
 
 
bbcc1c1
2c34bfb
bbcc1c1
 
2c34bfb
 
 
bbcc1c1
2c34bfb
bbcc1c1
2c34bfb
 
bbcc1c1
2c34bfb
bbcc1c1
2c34bfb
 
 
 
 
 
bbcc1c1
 
 
2c34bfb
1c9b9af
578c38e
1c9b9af
 
 
 
 
 
2c34bfb
 
 
bbcc1c1
 
2c34bfb
bbcc1c1
2c34bfb
 
 
bbcc1c1
2c34bfb
 
 
 
 
1c9b9af
2c34bfb
 
 
 
1c9b9af
 
2c34bfb
 
5916f4a
ac2905b
bbcc1c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5916f4a
78bb813
bbcc1c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c34bfb
b09f05e
2c34bfb
bbcc1c1
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
#================imports==============

import uuid
import requests
import os
os.environ["USER_AGENT"] = "RAG-App/1.0"
from typing import Dict, List, Any, Generator
from dotenv import load_dotenv

from bs4 import BeautifulSoup
from langchain_core.globals import set_llm_cache
from langchain_core.caches import InMemoryCache

from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import Weaviate
from langchain_community.vectorstores import FAISS
from langchain_groq import ChatGroq
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

from langchain_classic.chains.combine_documents import create_stuff_documents_chain
from langchain_classic.chains import create_retrieval_chain

from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory

import gradio as gr

#================== CONFIG==================

load_dotenv()

set_llm_cache(InMemoryCache())
api_key = os.environ["GROQ_API_KEY"]
print("api chargée:" if api_key else "y'a probleme!!")

#========== charger et decouper documents=================

urls = [
    "https://fr.wikipedia.org/wiki/%C3%89levage",
    "https://fr.wikipedia.org/wiki/La_P%C3%AAche"
]

loader = WebBaseLoader(urls,
                      requests_kwargs={
                          "headers": {
                              "User-Agent": "RAG-App/1.0"
                          }
                      }
)
docs = loader.load()

splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = splitter.split_documents(docs)

#============embeding et indexation vers faiss_db================

embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")

faiss_db = FAISS.from_documents(
    documents=chunks,
    embedding=embeddings
)

retriever = faiss_db.as_retriever(search_type="similarity", search_kwargs={"k": 3})

#=============== LLM et Prompt=================

llm = ChatGroq(
    model="llama-3.3-70b-versatile",
    temperature=0.0,
    max_tokens=1200,
    streaming=True  # Activer le streaming pour une réponse en temps réel
)

prompt = ChatPromptTemplate.from_messages([
    ("system", """Tu es un assistant expert en dans le domaine de l'elevage et la pêche. Réponds clairement.
Si tu ne connais pas, n'invente pas. Garde un ton amical.
Contexte :
{context}"""),
    MessagesPlaceholder(variable_name="chat_history"),
    ("human", "{input}"),
])

#============= CHAINE DE RECUPERATION=======

stuff_chain = create_stuff_documents_chain(llm, prompt)
rag_chain = create_retrieval_chain(retriever, stuff_chain)

# ====== GESTION DE L'HISTORIQUE ======

store = {}

def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

# ====== CHAIN AVEC MÉMOIRE ======

convers_chain = RunnableWithMessageHistory(
    rag_chain,
    get_session_history,
    input_messages_key="input",
    history_messages_key="chat_history",
    output_messages_key="answer"
)

SESSION_ID = str(uuid.uuid4())  # session globale

# ================= CSS PERSONNALISÉ POUR LE STYLE CHATGPT ====================

custom_css = """
/* Style global */
.gradio-container {
    max-width: 100% !important;
    margin: 0 !important;
    padding: 0 !important;
}

/* Style de la sidebar */
.sidebar {
    background: #202123;
    color: white;
    height: 100vh;
    overflow-y: auto;
}

.sidebar-header {
    padding: 12px 16px;
    border-bottom: 1px solid #4d4d4f;
    margin-bottom: 8px;
}

.new-chat-btn {
    width: 100%;
    padding: 8px 16px;
    background: transparent;
    border: 1px solid #4d4d4f;
    color: white;
    border-radius: 8px;
    cursor: pointer;
    font-size: 14px;
    text-align: left;
    transition: background 0.3s;
}

.new-chat-btn:hover {
    background: #2b2c2f;
}

.history-item {
    padding: 12px 16px;
    cursor: pointer;
    border-radius: 8px;
    margin: 4px 8px;
    transition: background 0.3s;
    word-wrap: break-word;
    color: #ececec;
}

.history-item:hover {
    background: #2b2c2f;
}

.history-item.active {
    background: #343541;
}

.main-chat-area {
    background: #343541;
    height: 100vh;
}

.chatbot-container {
    height: calc(100vh - 180px) !important;
}

.chatbot-container > div {
    border: none !important;
    background: #343541 !important;
}
"""

# ================= INTERFACE GRADIO STYLE CHATGPT ====================

with gr.Blocks(css=custom_css, theme="soft") as demo:
    with gr.Row(equal_height=True):
        # ==================== SIDEBAR GAUCHE (Style ChatGPT) ====================
        with gr.Column(scale=1, min_width=260, elem_classes="sidebar"):
            gr.HTML("""
                <div class="sidebar-header">
                    <h2 style="color: white; font-size: 16px; margin: 0;">🐟 RAG Assistant</h2>
                    <p style="color: #8e8ea0; font-size: 12px; margin: 4px 0 0 0;">Élevage & Pêche</p>
                </div>
            """)
            
            # Bouton Nouvelle conversation
            new_chat_btn = gr.Button("➕ Nouvelle conversation", elem_classes="new-chat-btn")
            
            gr.HTML('<div style="padding: 8px 16px; color: #8e8ea0; font-size: 12px; margin-top: 16px;">HISTORIQUE</div>')
            
            # Liste des conversations précédentes
            history_radio = gr.Radio(
                choices=[],
                label=None,
                interactive=True,
                elem_classes="history-radio",
                container=False
            )
            
            # Variables d'état pour stocker les conversations
            conversations_state = gr.State([])
            current_conversation_id = gr.State("")
            
            # Message pour confirmer les actions
            status_msg = gr.HTML(visible=False)
        
        # ==================== ZONE PRINCIPALE DE CHAT (Style ChatGPT) ====================
        with gr.Column(scale=4, elem_classes="main-chat-area"):
            # Header
            gr.HTML("""
                <div style="padding: 16px; border-bottom: 1px solid #4d4d4f; background: #343541;">
                    <h2 style="color: white; font-size: 18px; margin: 0; text-align: center;">
                        Assistant RAG - Élevage & Pêche
                    </h2>
                </div>
            """)
            
            # Chatbot
            chatbot = gr.Chatbot(
                label=None,
                height=500,
                elem_classes="chatbot-container",
                show_label=False,
                bubble_full_width=False,
                avatar_images=(None, "🐟")
            )
            
            # Zone de saisie style ChatGPT
            with gr.Row():
                msg_input = gr.Textbox(
                    show_label=False,
                    placeholder="Envoyez un message...",
                    scale=9,
                    container=False,
                    lines=1,
                    max_lines=5
                )
                send_btn = gr.Button("↑", variant="primary", scale=1, min_width=40)
            
            # Texte de copyright en bas
            gr.HTML("""
                <div style="text-align: center; padding: 8px; color: #8e8ea0; font-size: 11px;">
                    RAG Assistant peut faire des erreurs. Vérifiez les informations importantes.
                </div>
            """)
    
    # ==================== FONCTIONS DE GESTION ====================
    
    def create_new_conversation(conversations):
        """Crée une nouvelle conversation et retourne l'ID."""
        conversation_id = str(uuid.uuid4())
        title = "Nouvelle conversation"
        conversations.append({"id": conversation_id, "title": title, "messages": []})
        return conversations, conversation_id, title, gr.update(choices=[c["title"] for c in conversations], value=None)
    
    def load_conversation(selected_title, conversations):
        """Charge une conversation existante."""
        if not selected_title:
            return [], "", ""
        
        for conv in conversations:
            if conv["title"] == selected_title:
                chat_history = []
                for msg in conv["messages"]:
                    if msg["role"] == "user":
                        chat_history.append((msg["content"], None))
                    else:
                        if chat_history:
                            chat_history[-1] = (chat_history[-1][0], msg["content"])
                        else:
                            chat_history.append((None, msg["content"]))
                return chat_history, conv["id"], conv["title"]
        
        return [], "", ""
    
    def send_message(message, chat_history, conversations, current_conv_id, chat_state):
        """Envoie un message et met à jour la conversation."""
        if not message or not message.strip():
            return "", chat_history, conversations, current_conv_id, chat_state
        
        # Créer une nouvelle conversation si nécessaire
        if not current_conv_id:
            current_conv_id = str(uuid.uuid4())
            # Utiliser les premiers mots comme titre
            title = message[:50] + "..." if len(message) > 50 else message
            conversations.append({
                "id": current_conv_id, 
                "title": title, 
                "messages": [{"role": "user", "content": message}]
            })
        else:
            # Ajouter le message à la conversation existante
            for conv in conversations:
                if conv["id"] == current_conv_id:
                    conv["messages"].append({"role": "user", "content": message})
                    if conv["title"] == "Nouvelle conversation":
                        conv["title"] = message[:50] + "..." if len(message) > 50 else message
                    break
        
        # Ajouter le message utilisateur au chatbot
        chat_history.append((message, None))
        
        # Obtenir la réponse de l'assistant
        result = convers_chain.invoke(
            {"input": message},
            config={"configurable": {"session_id": SESSION_ID}}
        )
        
        response = result.get("answer", str(result))
        
        # Ajouter la réponse à la conversation
        for conv in conversations:
            if conv["id"] == current_conv_id:
                conv["messages"].append({"role": "assistant", "content": response})
                break
        
        # Mettre à jour le chatbot
        chat_history[-1] = (message, response)
        
        # Mettre à jour les choix du radio
        choices = [conv["title"] for conv in conversations]
        
        return "", chat_history, conversations, current_conv_id, gr.update(choices=choices, value=conversations[-1]["title"] if conversations else None)
    
    def clear_chat():
        """Efface le chat et commence une nouvelle conversation."""
        return [], "", None, gr.update(choices=[], value=None)
    
    # ==================== GESTIONNAIRES D'ÉVÉNEMENTS ====================
    
    # Envoi de message
    msg_input.submit(
        send_message,
        inputs=[msg_input, chatbot, conversations_state, current_conversation_id, gr.State([])],
        outputs=[msg_input, chatbot, conversations_state, current_conversation_id, history_radio]
    ).then(
        lambda: gr.update(value=""),
        outputs=[msg_input]
    )
    
    send_btn.click(
        send_message,
        inputs=[msg_input, chatbot, conversations_state, current_conversation_id, gr.State([])],
        outputs=[msg_input, chatbot, conversations_state, current_conversation_id, history_radio]
    ).then(
        lambda: gr.update(value=""),
        outputs=[msg_input]
    )
    
    # Nouvelle conversation
    new_chat_btn.click(
        create_new_conversation,
        inputs=[conversations_state],
        outputs=[conversations_state, current_conversation_id, gr.State(""), history_radio]
    ).then(
        clear_chat,
        outputs=[chatbot, current_conversation_id, history_radio, history_radio]
    )
    
    # Charger une conversation depuis l'historique
    history_radio.change(
        load_conversation,
        inputs=[history_radio, conversations_state],
        outputs=[chatbot, current_conversation_id, gr.State("")]
    )
    
    # Support de la touche Entrée (Shift+Entrée pour nouvelle ligne)
    def handle_enter_key(text, event: gr.EventData):
        if event.key == "Enter" and not event.shift:
            return text, gr.update()
        return text, gr.update()
    
    msg_input.key_up(
        handle_enter_key,
        inputs=[msg_input],
        outputs=[msg_input, chatbot]
    )

# ===================LANCEMENT ================

demo.launch(share=False)