import gradio as gr
import torch
import torch._dynamo
import pandas as pd
import datetime
import json
import os
from peft import PeftModel
from transformers import AutoTokenizer, AutoModelForCausalLM
from huggingface_hub import login
import spaces
hf_token = os.getenv("gemma_access")
if hf_token:
login(hf_token)
else:
raise RuntimeError("Missing access token. Add it under Space Settings > Secrets.")
torch._dynamo.config.suppress_errors = True
torch._dynamo.config.disable = True
base_model = AutoModelForCausalLM.from_pretrained("google/gemma-2-2b-it")
model = PeftModel.from_pretrained(base_model, "Nourivex/noura-2b-it-lora")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
tokenizer = AutoTokenizer.from_pretrained("Nourivex/noura-2b-it-lora")
"""
This part is for the setting of the character of the chatbot.
"""
def save_character(name, personality, background):
character_data = {
"name": name,
"personality": personality,
"background": background
}
# Save to a JSON file
with open("character_data.json", "w") as f:
json.dump(character_data, f)
return character_data
def load_character():
try:
with open("character_data.json", "r") as f:
return json.load(f)
except FileNotFoundError:
return {
"name": "Wanting",
"personality": "friendly and helpful",
"background": "Wanting is a software engineer who loves to code and build new things."
}
def save_rating(rating, chat_history):
rating_data = {
"rating": rating,
"timestamp": datetime.datetime.now().isoformat(),
"chat_history": chat_history
}
# Save to a JSON file
with open("ratings.json", "a") as f:
json.dump(rating_data, f)
f.write("\n")
return rating_data
def generate_system_message(character):
return f"""You are roleplaying as {character['name']}.
Your personality: {character['personality']}
Your background: {character['background']}
Important guidelines:
1. Stay in character at all times
2. Respond naturally and consistently with your personality
3. Use appropriate emotional expressions based on your character
4. Maintain your character's background and experiences in your responses
5. If asked about something your character wouldn't know, respond appropriately in character
6. You can add some reasonable settings to make yourself more vivid
Now, begin the conversation as {character['name']}."""
@spaces.GPU(duration=120)
def run(message, history, system_message):
try:
prompt = ""
if not history:
prompt += f"<|system|>\n{system_message}\n"
for user_msg, assistant_msg in history:
if user_msg:
prompt += f"<|user|>\n{user_msg}\n"
if assistant_msg:
prompt += f"<|assistant|>\n{assistant_msg}\n"
prompt += f"<|user|>\n{message}\n<|assistant|>\n"
inputs = tokenizer(prompt, return_tensors="pt", padding=True).to(model.device)
outputs = model.generate(
**inputs,
max_new_tokens=64,
temperature=0.6,
top_p=0.95,
do_sample=True,
pad_token_id=tokenizer.pad_token_id,
eos_token_id=tokenizer.eos_token_id,
repetition_penalty=1.1,
num_return_sequences=1,
no_repeat_ngram_size=3
)
# Step 3: Post-process output
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
if "<|assistant|>" in response:
response = response.split("<|assistant|>")[-1].strip()
else:
response = response.strip()
response = (response.replace("print(", "")
.replace(")", "")
.replace("<|system|>", "")
.replace("<|user|>", "")
.replace("```", "")
.replace("`", "")
)
response = " ".join(response.split())
return response
except Exception as e:
print(f"❌ Error in run function: {str(e)}")
return "I apologize, but I'm having trouble generating a response right now. Please try again."
def save_session(character, history, rating, feedback, filename="dataset.jsonl"):
if not os.path.exists(filename):
open(filename, "w").close()
formatted = {
"system": f"You are roleplaying as {character['name']}. Personality: {character['personality']} Background: {character['background']}",
"conversation": [
{"user": u, "assistant": a} for u, a in history if u and a
],
"rating": rating,
"feedback": feedback
}
with open(filename, "a") as f:
f.write(json.dumps(formatted) + "\n")
def preview_character(name, personality, background):
preview_text = f"""
👤
{name}
Personality:
{personality}
Background:
{background}
"""
return preview_text
custom_css = """
/* Modern blue and white color scheme */
:root {
--primary: #2563eb;
--primary-light: #3b82f6;
--primary-dark: #1d4ed8;
--secondary: #f8fafc;
--accent: #e0f2fe;
--text: #1e293b;
--text-light: #64748b;
--border: #e2e8f0;
--success: #10b981;
}
/* Global styling */
.gradio-container {
background: white;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
color: var(--text);
line-height: 1.5;
}
/* Main header */
#header-title {
text-align: center;
color: var(--primary);
font-size: 2.5rem;
font-weight: 700;
margin: 20px 0 10px 0;
background: linear-gradient(90deg, var(--primary), var(--primary-light));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
#subtitle {
text-align: center;
color: var(--text-light);
font-size: 1.1rem;
margin-bottom: 30px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
/* Character creation section */
.gr-group {
background: white;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
border: 1px solid var(--border);
margin: 20px auto;
max-width: 900px;
transition: all 0.2s ease;
}
.gr-group:hover {
box-shadow: 0 10px 15px rgba(0,0,0,0.1);
}
.gr-form {
padding: 30px;
}
/* Input fields */
.gr-textbox, .gr-textbox textarea {
border: 1px solid var(--border);
border-radius: 8px;
background: white;
font-family: inherit;
padding: 12px 16px;
transition: all 0.2s ease;
margin-bottom: 16px;
}
.gr-textbox:focus, .gr-textbox textarea:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
outline: none;
}
/* Button styling */
#preview_btn, #save_btn, #start_btn {
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
padding: 12px 24px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
margin: 8px 0;
}
#preview_btn:hover, #save_btn:hover, #start_btn:hover {
background: var(--primary-dark);
transform: translateY(-1px);
}
#start_btn {
background: var(--primary-dark);
font-size: 1.1rem;
padding: 14px 28px;
margin-top: 16px;
}
/* Character preview card */
.character-preview {
padding: 20px;
}
.character-card {
background: white;
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
}
.character-avatar {
font-size: 3rem;
margin-bottom: 16px;
color: var(--primary);
}
.character-name {
color: var(--primary);
font-size: 1.5rem;
margin-bottom: 16px;
font-weight: 600;
}
.detail-section {
margin: 12px 0;
text-align: left;
padding: 12px;
background: var(--accent);
border-radius: 8px;
}
.detail-label {
font-weight: 600;
color: var(--primary);
display: block;
margin-bottom: 4px;
font-size: 0.9rem;
}
.detail-text {
color: var(--text);
font-size: 0.95rem;
}
.preview-footer {
margin-top: 16px;
color: var(--primary);
font-weight: 500;
font-size: 0.95rem;
text-align: center;
padding-top: 12px;
border-top: 1px solid var(--border);
}
/* Chat interface styling */
.chat-header {
background: var(--primary);
color: white;
padding: 16px;
border-radius: 12px 12px 0 0;
font-weight: 500;
font-size: 1.1rem;
}
#chatbox {
background: white;
border: 1px solid var(--border);
border-radius: 0 0 12px 12px;
min-height: 500px;
}
/* Chat messages */
.message.user {
background: var(--accent) !important;
border-radius: 12px 12px 0 12px !important;
border: 1px solid var(--border) !important;
color: var(--text) !important;
padding: 12px 16px !important;
}
.message.bot {
background: white !important;
border-radius: 12px 12px 12px 0 !important;
border: 1px solid var(--border) !important;
color: var(--text) !important;
padding: 12px 16px !important;
}
/* Finish button */
#finish_btn {
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
padding: 12px 24px;
font-size: 1rem;
font-weight: 500;
margin-top: 20px;
cursor: pointer;
transition: all 0.2s ease;
}
#finish_btn:hover {
background: var(--primary-dark);
transform: translateY(-1px);
}
/* Rating section */
.rating-header {
text-align: center;
font-size: 1.5rem;
color: var(--primary);
font-weight: 600;
margin-bottom: 24px;
}
#rating-slider {
margin: 20px 0;
}
#feedback-box textarea {
border: 1px solid var(--border);
border-radius: 8px;
background: white;
padding: 12px 16px;
min-height: 120px;
}
#submit-rating-btn {
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
padding: 12px 24px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
margin: 16px auto;
display: block;
}
#submit-rating-btn:hover {
background: var(--primary-dark);
transform: translateY(-1px);
}
/* Responsive design */
@media (max-width: 768px) {
#header-title {
font-size: 2rem !important;
}
.gr-group {
margin: 10px;
}
.gr-form {
padding: 20px;
}
}
/* Animations */
.fade-in {
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Loading animation */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
"""
with gr.Blocks(title="Roleplay Chatbot", css=custom_css) as demo:
state = gr.State(load_character())
gr.HTML("""
Create and chat with custom AI characters. Powered by fine-tuned Gemma 2B model.
""")
with gr.Group(visible=True, elem_classes=["fade-in"]) as tab_setup:
gr.HTML("""
Character Settings
Define your character's personality and background
""")
with gr.Row():
with gr.Column(scale=1):
name = gr.Textbox(
label="Character Name",
placeholder="Enter your character's name",
elem_classes=["character-input"]
)
personality = gr.Textbox(
label="Personality Traits",
placeholder="Describe personality traits (e.g., friendly, witty, mysterious)",
elem_classes=["character-input"]
)
background = gr.Textbox(
label="Background Story",
lines=4,
placeholder="Describe your character's background and history",
elem_classes=["character-input"]
)
with gr.Row():
btn_preview = gr.Button("Preview Character", elem_id="preview_btn")
btn_save = gr.Button("Save Character", elem_id="save_btn")
btn_start = gr.Button("Start Chatting", elem_id="start_btn", size="lg")
with gr.Column(scale=1):
preview = gr.HTML(
"""
👤
Preview will appear here
""",
elem_classes=["preview-placeholder"]
)
btn_preview.click(preview_character, [name, personality, background], preview)
btn_save.click(save_character, [name, personality, background], state)
with gr.Group(visible=False, elem_classes=["fade-in"]) as tab_chat:
gr.HTML('')
chatbot = gr.ChatInterface(
run,
additional_inputs=[
gr.Textbox(value="", label="System message", visible=False),
],
chatbot=gr.Chatbot(
elem_id="chatbox",
height=500,
show_label=False,
container=True
),
)
btn_finish = gr.Button("Finish Chat", elem_id="finish_btn")
with gr.Group(visible=False, elem_classes=["fade-in"]) as tab_rating:
gr.HTML("""
""")
with gr.Row():
with gr.Column():
rating = gr.Slider(
minimum=1,
maximum=5,
step=1,
label="Rating (1-5)",
elem_id="rating-slider",
value=3
)
feedback = gr.Textbox(
label="Additional Feedback (optional)",
placeholder="Share your thoughts about the experience...",
lines=4,
elem_id="feedback-box"
)
btn_submit_rating = gr.Button("Submit Feedback", elem_id="submit-rating-btn")
gr.HTML("""
Thank you for your feedback!
""")
# Connect all the event handlers
def start_chat_with_character(character):
system_msg = generate_system_message(character)
return (system_msg, gr.update(visible=False), gr.update(visible=True), gr.update(visible=False))
btn_start.click(
fn=start_chat_with_character,
inputs=state,
outputs=[chatbot.additional_inputs[0], tab_setup, tab_chat, tab_rating]
)
btn_finish.click(
lambda: [gr.update(visible=False), gr.update(visible=False),gr.update(visible=True)],
inputs=[],
outputs=[tab_setup, tab_chat, tab_rating]
)
btn_submit_rating.click(
fn=lambda r, f, h, c:(
save_rating(r, h),
save_session(generate_system_message(c), h, r, f),
gr.update(visible=True),
gr.update(visible=False),
gr.update(visible=False)
),
inputs=[rating, feedback, chatbot.chatbot_state, state],
outputs=[gr.State(), tab_setup, tab_chat, tab_rating]
)
if __name__ == "__main__":
demo.launch()