File size: 11,290 Bytes
63f176f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import gradio as gr
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel, PeftConfig
import torch
import re

class MoroccanStudentChatbot:
    def __init__(self, adapter_model_id="echarif/llama_alpaca_lora_adapter"):
        """Initialize the chatbot with the fine-tuned PEFT/LoRA model"""
        print("Loading PEFT adapter config...")
        self.config = PeftConfig.from_pretrained(adapter_model_id)

        print("Loading base model...")
        self.base_model = AutoModelForCausalLM.from_pretrained(
            self.config.base_model_name_or_path,
            return_dict=True,
            device_map="auto",
            torch_dtype=torch.float16
        )

        print("Loading LoRA adapter...")
        self.model = PeftModel.from_pretrained(self.base_model, adapter_model_id)

        print("Loading tokenizer...")
        self.tokenizer = AutoTokenizer.from_pretrained(self.config.base_model_name_or_path)

        # Add padding token if it doesn't exist
        if self.tokenizer.pad_token is None:
            self.tokenizer.pad_token = self.tokenizer.eos_token

        self.alpaca_prompt = """Ci-dessous se trouve une instruction décrivant une tâche, accompagnée éventuellement d'un contexte supplémentaire. Rédige une réponse qui complète correctement la demande.
### Instruction :
{}
### input :
{}
### output :
{}"""

        print("Model loaded successfully!")

    def clean_output(self, raw_output):
        """Clean the model output to remove unwanted tokens and formatting"""
        # Decode the output
        if isinstance(raw_output, torch.Tensor):
            text = self.tokenizer.decode(raw_output[0], skip_special_tokens=True)
        else:
            text = raw_output

        # Remove the prompt part and keep only the actual response
        # Look for the "### output :" pattern and extract what comes after
        output_pattern = r"### output :\s*(.*?)(?:<\|end_of_text\|>|$)"
        match = re.search(output_pattern, text, re.DOTALL)

        if match:
            response = match.group(1).strip()
        else:
            # Fallback: try to extract text after "### output :"
            if "### output :" in text:
                response = text.split("### output :")[1].strip()
            else:
                response = text

        # Clean up any remaining special tokens
        response = re.sub(r'<\|.*?\|>', '', response)
        response = re.sub(r'<.*?>', '', response)

        # Remove extra whitespace and newlines
        response = re.sub(r'\n+', '\n', response)
        response = response.strip()

        return response if response else "Je suis désolé, je n'ai pas pu générer une réponse appropriée."

    def generate_response(self, user_input, history):
        """Generate response for the chatbot"""
        if not user_input.strip():
            return history, ""

        # Format the prompt
        prompt = self.alpaca_prompt.format(user_input.strip(), "", "")

        # Tokenize
        inputs = self.tokenizer(prompt, return_tensors="pt", padding=True, truncation=True, max_length=512)
        inputs = {k: v.to(self.model.device) for k, v in inputs.items()}

        # Generate response
        with torch.no_grad():
            outputs = self.model.generate(
                **inputs,
                max_new_tokens=256,
                do_sample=True,
                temperature=0.7,
                top_p=0.9,
                pad_token_id=self.tokenizer.eos_token_id,
                repetition_penalty=1.1,
            )

        # Clean the output
        response = self.clean_output(outputs)

        # Update history
        history.append([user_input, response])

        return history, ""

def create_interface():
    """Create the Gradio interface"""

    # Initialize the chatbot
    chatbot = MoroccanStudentChatbot()

    # Custom CSS for professional styling and responsiveness
    custom_css = """
    .header-container {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 20px;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        border-radius: 10px;
        margin-bottom: 20px;
        color: white;
    }

    .logo-container {
        display: flex;
        align-items: center;
        gap: 15px;
    }

    .logo-placeholder {
        width: 60px;
        height: 60px;
        background: rgba(255, 255, 255, 0.2);
        border-radius: 10px;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 24px;
        border: 2px solid rgba(255, 255, 255, 0.3);
    }

    .title-section h1 {
        margin: 0;
        font-size: 24px;
        font-weight: bold;
    }

    .title-section p {
        margin: 5px 0 0 0;
        font-size: 14px;
        opacity: 0.9;
    }

    .university-info {
        text-align: right;
        font-size: 12px;
        opacity: 0.8;
    }

    /* Responsive design */
    @media (max-width: 768px) {
        .header-container {
            flex-direction: column;
            text-align: center;
            gap: 15px;
        }

        .university-info {
            text-align: center;
        }

        .title-section h1 {
            font-size: 20px;
        }

        .logo-placeholder {
            width: 50px;
            height: 50px;
            font-size: 20px;
        }
    }

    .chatbot-container {
        max-width: 800px;
        margin: 0 auto;
    }

    .footer-info {
        text-align: center;
        margin-top: 20px;
        padding: 15px;
        background: #f8f9fa;
        border-radius: 8px;
        font-size: 12px;
        color: #666;
    }

    /* Custom chatbot styling */
    .gradio-container {
        max-width: 1200px !important;
    }

    /* Improve mobile responsiveness */
    @media (max-width: 480px) {
        .header-container {
            padding: 15px;
        }

        .title-section h1 {
            font-size: 18px;
        }

        .logo-placeholder {
            width: 40px;
            height: 40px;
            font-size: 16px;
        }
    }
    """

    # Create the interface
    with gr.Blocks(css=custom_css, title="Assistant Étudiant Marocain", theme=gr.themes.Soft()) as interface:

        # Header with logos and title
        gr.HTML("""
        <div class="header-container">
            <div class="logo-container">
                <div class="logo-placeholder">🎓</div>
                <div class="title-section">
                    <h1>Assistant Étudiant Marocain</h1>
                    <p>Votre guide pour l'enseignement supérieur au Maroc</p>
                </div>
            </div>
            <div class="university-info">
                <div class="logo-placeholder">🏛️</div>
                <div style="margin-top: 5px;">
                    <strong>Université [Nom de votre université]</strong><br>
                    Master [Votre spécialité]<br>
                    Projet de fin d'études
                </div>
            </div>
        </div>
        """)

        with gr.Row():
            with gr.Column(scale=1):
                # Main chatbot interface
                chatbot_interface = gr.Chatbot(
                    height=500,
                    placeholder="👋 Bonjour! Je suis votre assistant pour l'enseignement supérieur au Maroc. Posez-moi vos questions sur les universités, les inscriptions, les bourses, etc.",
                    container=True,
                    bubble_full_width=False,
                    show_label=False,
                    elem_classes="chatbot-container"
                )

                with gr.Row():
                    msg = gr.Textbox(
                        placeholder="Tapez votre question ici...",
                        container=False,
                        scale=7,
                        min_width=0,
                    )
                    send_btn = gr.Button("Envoyer", variant="primary", scale=1, min_width=0)

                # Clear button
                clear_btn = gr.Button("Nouvelle conversation", variant="secondary", size="sm")

        # Example questions
        gr.HTML("""
        <div style="margin-top: 20px;">
            <h3>💡 Questions d'exemple :</h3>
            <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 10px; margin-top: 10px;">
                <button onclick="document.querySelector('textarea').value='Quelles sont les meilleures universités pour étudier l\\'informatique au Maroc?'; document.querySelector('textarea').focus();"
                        style="padding: 10px; border: 1px solid #ddd; border-radius: 5px; background: #f8f9fa; cursor: pointer;">
                    🏫 Universités d'informatique
                </button>
                <button onclick="document.querySelector('textarea').value='Comment obtenir une bourse d\\'études au Maroc?'; document.querySelector('textarea').focus();"
                        style="padding: 10px; border: 1px solid #ddd; border-radius: 5px; background: #f8f9fa; cursor: pointer;">
                    💰 Bourses d'études
                </button>
                <button onclick="document.querySelector('textarea').value='Quelles sont les procédures d\\'inscription dans les universités publiques?'; document.querySelector('textarea').focus();"
                        style="padding: 10px; border: 1px solid #ddd; border-radius: 5px; background: #f8f9fa; cursor: pointer;">
                    📝 Procédures d'inscription
                </button>
                <button onclick="document.querySelector('textarea').value='Comment trouver un logement étudiant au Maroc?'; document.querySelector('textarea').focus();"
                        style="padding: 10px; border: 1px solid #ddd; border-radius: 5px; background: #f8f9fa; cursor: pointer;">
                    🏠 Logement étudiant
                </button>
            </div>
        </div>
        """)

        # Footer
        gr.HTML("""
        <div class="footer-info">
            <p><strong>Projet de fin d'études</strong> - Développé par [Votre nom]</p>
            <p>Ce chatbot utilise un modèle LLaMA 3.1 fine-tuné pour assister les étudiants marocains</p>
            <p><em>Pour toute question technique, contactez [[email protected]]</em></p>
        </div>
        """)

        # Event handlers
        def respond(message, history):
            return chatbot.generate_response(message, history)

        def clear_conversation():
            return [], ""

        # Connect the events
        msg.submit(respond, [msg, chatbot_interface], [chatbot_interface, msg])
        send_btn.click(respond, [msg, chatbot_interface], [chatbot_interface, msg])
        clear_btn.click(clear_conversation, None, [chatbot_interface, msg])

    return interface

if __name__ == "__main__":
    # Launch the interface
    interface = create_interface()
    interface.launch(
        server_name="0.0.0.0",  # Makes it accessible from other devices on the network
        server_port=7860,
        share=True,  # Creates a public link
        debug=True,
        show_error=True,
        inbrowser=True,  # Opens automatically in browser
    )