File size: 8,653 Bytes
f123c00
 
 
8677714
f123c00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8677714
f123c00
 
8677714
f123c00
 
 
 
 
 
 
 
 
 
 
8677714
f123c00
 
 
8677714
 
f123c00
 
 
 
 
 
 
 
 
 
 
 
8677714
 
 
 
f123c00
 
8677714
 
f123c00
 
 
 
 
 
 
 
 
 
8677714
f123c00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8677714
 
 
 
 
 
 
f123c00
 
 
 
 
 
 
 
 
8677714
 
 
 
 
 
 
 
f123c00
 
 
 
 
 
 
8677714
f123c00
8677714
 
f123c00
 
 
 
 
 
 
 
 
 
 
8677714
 
f123c00
 
 
 
8677714
 
f123c00
 
 
8677714
 
f123c00
 
8677714
f123c00
8677714
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
"""
Personality Chatbot - Multi-personality LLM with LoRA adapters
Deployed on Hugging Face Spaces
FIXED: Removed gr.Chatbot() to avoid Gradio 4.x schema bug
"""

import gradio as gr
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel
import os

# Configuration
BASE_MODEL = "Qwen/Qwen2-0.5B-Instruct"
ADAPTERS = {
    "🧠 Brainrot": "qwen-brainrot-lora-stage1-final",
    "πŸ΄β€β˜ οΈ Pirate": "pirate-lora-adapter",
    "πŸ§™ Yoda": "yoda-lora-adapter",
    "πŸ€“ Nerd": "nerd-lora-adapter",
}

# Global state
base_model = None
tokenizer = None
current_adapter = None
current_personality = None
device = None

def load_base_model():
    """Load base model and tokenizer once at startup"""
    global base_model, tokenizer, device
    
    print("πŸ”„ Loading base model...")
    device = "cuda" if torch.cuda.is_available() else "cpu"
    
    tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, trust_remote_code=True)
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token
    
    base_model = AutoModelForCausalLM.from_pretrained(
        BASE_MODEL,
        torch_dtype=torch.float16 if device == "cuda" else torch.float32,
        device_map="auto" if device == "cuda" else None,
        trust_remote_code=True,
    )
    
    if device == "cpu":
        base_model = base_model.to(device)
    
    print(f"βœ… Base model loaded on {device}")
    return f"Base model loaded on {device}"

def switch_personality(personality_name):
    """Switch to a different personality adapter"""
    global current_adapter, current_personality
    
    if personality_name == current_personality:
        return f"Already using {personality_name}"
    
    adapter_path = ADAPTERS.get(personality_name)
    if not adapter_path:
        return f"❌ Personality '{personality_name}' not found"
    
    if not os.path.exists(adapter_path):
        return f"❌ Adapter folder '{adapter_path}' not found. Make sure adapters are uploaded."
    
    try:
        print(f"πŸ”„ Loading {personality_name} adapter from {adapter_path}...")
        
        # Load adapter on top of base model
        current_adapter = PeftModel.from_pretrained(
            base_model,
            adapter_path,
            torch_dtype=torch.float16 if device == "cuda" else torch.float32,
        )
        current_adapter.eval()
        
        current_personality = personality_name
        print(f"βœ… Switched to {personality_name}")
        return f"βœ… Switched to {personality_name}"
    
    except Exception as e:
        return f"❌ Error loading adapter: {str(e)}"

def generate_response(message, chat_history, temperature=0.7, max_tokens=256):
    """Generate response using current personality"""
    if current_adapter is None:
        return "⚠️ Please select a personality first!", chat_history
    
    try:
        # Format with chat template
        prompt = f"<|im_start|>user\n{message}<|im_end|>\n<|im_start|>assistant\n"
        
        inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=512)
        inputs = {k: v.to(device) for k, v in inputs.items()}
        
        with torch.no_grad():
            outputs = current_adapter.generate(
                **inputs,
                max_new_tokens=max_tokens,
                temperature=temperature,
                do_sample=True,
                top_p=0.9,
                top_k=50,
                repetition_penalty=1.1,
                pad_token_id=tokenizer.pad_token_id,
                eos_token_id=tokenizer.eos_token_id,
            )
        
        # Decode and extract only the new tokens (the response)
        response_tokens = outputs[0][inputs['input_ids'].shape[1]:]
        response = tokenizer.decode(response_tokens, skip_special_tokens=True).strip()
        
        # Remove any leftover special tokens or formatting
        response = response.replace("<|im_start|>", "").replace("<|im_end|>", "")
        response = response.replace("assistant\n", "").strip()
        
        # Format chat history for display
        new_history = chat_history + f"\n\n**You:** {message}\n\n**Bot ({current_personality}):** {response}\n\n---"
        
        return response, new_history
    
    except Exception as e:
        error_msg = f"❌ Error generating response: {str(e)}"
        return error_msg, chat_history

def handle_personality_change(personality_name):
    """Handle personality dropdown change"""
    status = switch_personality(personality_name)
    return status

# Load base model on startup
print("πŸš€ Starting application...")
load_base_model()

# Create Gradio interface WITHOUT gr.Chatbot() to avoid schema bug
with gr.Blocks(theme=gr.themes.Soft(), title="Personality Chatbot") as demo:
    gr.Markdown(
        """
        # 🎭 Multi-Personality Chatbot
        Chat with AI personalities powered by LoRA adapters on Qwen2-0.5B-Instruct
        
        **Select a personality** and start chatting!
        """
    )
    
    with gr.Row():
        with gr.Column(scale=1):
            personality_dropdown = gr.Dropdown(
                choices=list(ADAPTERS.keys()),
                label="🎭 Select Personality",
                value=list(ADAPTERS.keys())[0],
                interactive=True,
            )
            
            status_box = gr.Textbox(
                label="Status",
                value="Select a personality to begin",
                interactive=False,
                lines=2,
            )
            
            with gr.Accordion("βš™οΈ Generation Settings", open=False):
                temperature_slider = gr.Slider(
                    minimum=0.1,
                    maximum=2.0,
                    value=0.8,
                    step=0.1,
                    label="Temperature (creativity)",
                )
                max_tokens_slider = gr.Slider(
                    minimum=30,
                    maximum=256,
                    value=100,
                    step=10,
                    label="Max tokens (response length)",
                )
            
            gr.Markdown(
                """
                ### πŸ“ Personality Descriptions
                - **🧠 Brainrot**: Internet slang and Gen-Z speak
                - **πŸ΄β€β˜ οΈ Pirate**: Arr matey, talks like a pirate!
                - **πŸ§™ Yoda**: Wise Jedi master, speaks in reverse
                - **πŸ€“ Nerd**: Intellectual, loves facts and science
                """
            )
        
        with gr.Column(scale=2):
            # FIXED: Use Textbox instead of Chatbot to avoid schema bug
            chat_display = gr.Textbox(
                label="πŸ’¬ Chat History",
                value="",
                lines=20,
                max_lines=30,
                interactive=False,
                show_label=True,
            )
            
            msg_box = gr.Textbox(
                label="Your message",
                placeholder="Type your message here...",
                lines=2,
            )
            
            last_response = gr.Textbox(
                label="πŸ€– Last Response",
                value="",
                lines=5,
                interactive=False,
                show_label=True,
            )
            
            with gr.Row():
                submit_btn = gr.Button("Send πŸ’¬", variant="primary")
                clear_btn = gr.Button("Clear πŸ—‘οΈ", variant="secondary")
    
    # Event handlers
    def respond(message, chat_history, temperature, max_tokens):
        if not message.strip():
            return "", chat_history, ""
        
        bot_response, new_history = generate_response(message, chat_history, temperature, max_tokens)
        return "", new_history, bot_response
    
    # Personality change handler
    personality_dropdown.change(
        fn=handle_personality_change,
        inputs=[personality_dropdown],
        outputs=[status_box],
    )
    
    # Chat handlers
    submit_btn.click(
        fn=respond,
        inputs=[msg_box, chat_display, temperature_slider, max_tokens_slider],
        outputs=[msg_box, chat_display, last_response],
    )
    
    msg_box.submit(
        fn=respond,
        inputs=[msg_box, chat_display, temperature_slider, max_tokens_slider],
        outputs=[msg_box, chat_display, last_response],
    )
    
    clear_btn.click(
        fn=lambda: ("", "", ""),
        outputs=[chat_display, msg_box, last_response],
    )

# Launch with share=True for HF Spaces
if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860, share=False)