Spaces:
Running
Running
# 2) The actual app | |
import os | |
from getpass import getpass | |
from openai import OpenAI | |
import gradio as gr | |
import requests | |
import json | |
from datetime import datetime | |
# βββ Configure your OpenRouter key βββ | |
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY") | |
BSEARCH_API = os.getenv("BSEARCH_API") | |
# Check if the API key was retrieved | |
if not OPENROUTER_API_KEY: | |
print("Error: OPENROUTER_API_KEY not found in environment.") | |
print("Please set your API key in the environment as 'OPENROUTER_API_KEY'.") | |
else: | |
client = OpenAI( | |
base_url="https://openrouter.ai/api/v1", | |
api_key=OPENROUTER_API_KEY, | |
) | |
# Brave Search function | |
def brave_search(query): | |
"""Perform a web search using Brave Search API.""" | |
if not BSEARCH_API: | |
return "Error: BSEARCH_API not found in environment. Please set your Brave Search API key." | |
try: | |
headers = { | |
"Accept": "application/json", | |
"X-Subscription-Token": BSEARCH_API | |
} | |
# Brave Search API endpoint | |
url = "https://api.search.brave.com/res/v1/web/search" | |
params = { | |
"q": query, | |
"count": 5 # Number of results to return | |
} | |
response = requests.get(url, headers=headers, params=params) | |
response.raise_for_status() | |
data = response.json() | |
# Format the search results | |
results = [] | |
if "web" in data and "results" in data["web"]: | |
for idx, result in enumerate(data["web"]["results"][:5], 1): | |
title = result.get("title", "No title") | |
url = result.get("url", "") | |
description = result.get("description", "No description") | |
results.append(f"{idx}. **{title}**\n URL: {url}\n {description}\n") | |
if results: | |
return "π **Web Search Results:**\n\n" + "\n".join(results) | |
else: | |
return "No search results found." | |
except Exception as e: | |
return f"Search error: {str(e)}" | |
def openrouter_chat(user_message, history, use_web_search): | |
"""Send user_message and history to mistralai/devstral-small:free and append to history.""" | |
history = history or [] | |
# If web search is enabled, perform search first | |
search_context = "" | |
if use_web_search and user_message.strip(): | |
search_results = brave_search(user_message) | |
search_context = f"\n\n{search_results}\n\nBased on the above search results, please answer the following question:\n" | |
# Add search results to history as a system message | |
history.append(("π Web Search Query", user_message)) | |
history.append(("π Search Results", search_results)) | |
# Build the messages list from the history and the current user message | |
messages_for_api = [] | |
# Add system message if web search was used | |
if search_context: | |
messages_for_api.append({ | |
"role": "system", | |
"content": "You are a helpful assistant. When web search results are provided, incorporate them into your response to give accurate and up-to-date information." | |
}) | |
for human_message, ai_message in history[:-2] if use_web_search else history: # Exclude search entries from API messages | |
if not human_message.startswith("π") and not human_message.startswith("π"): | |
messages_for_api.append({"role": "user", "content": human_message}) | |
if ai_message is not None: | |
messages_for_api.append({"role": "assistant", "content": ai_message}) | |
# Add the current user message with search context if applicable | |
current_message = search_context + user_message if search_context else user_message | |
messages_for_api.append({"role": "user", "content": current_message}) | |
try: | |
# Call the model with the mistralai/Devstral-Small-2505 for full conversation history | |
resp = client.chat.completions.create( | |
model="mistralai/devstral-small:free", | |
messages=messages_for_api, | |
# you can tweak max_tokens, temperature, etc. here | |
) | |
bot_reply = resp.choices[0].message.content | |
# Append the user message and bot reply to the history for Gradio display | |
history.append((user_message, bot_reply)) | |
except Exception as e: | |
# Handle potential errors and append an error message to the history | |
history.append((user_message, f"Error: {e}")) | |
return history, "" | |
# Clean and professional CSS | |
custom_css = """ | |
/* Clean gradient background */ | |
.gradio-container { | |
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
min-height: 100vh; | |
padding: 20px; | |
} | |
/* Main container styling */ | |
.container { | |
max-width: 1200px; | |
margin: 0 auto; | |
} | |
/* Chat container with subtle glassmorphism */ | |
#component-0 { | |
background: rgba(255, 255, 255, 0.9); | |
backdrop-filter: blur(10px); | |
border-radius: 15px; | |
border: 1px solid rgba(255, 255, 255, 0.3); | |
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1); | |
padding: 20px; | |
} | |
/* Chatbot styling */ | |
.chatbot { | |
background: rgba(255, 255, 255, 0.95) !important; | |
border-radius: 12px !important; | |
border: 1px solid #e0e0e0 !important; | |
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05) !important; | |
} | |
/* Message bubbles */ | |
.message { | |
border-radius: 10px !important; | |
padding: 12px 16px !important; | |
margin: 8px 0 !important; | |
} | |
.user { | |
background-color: #4a5568 !important; | |
color: white !important; | |
margin-left: 20% !important; | |
} | |
.bot { | |
background-color: #f7fafc !important; | |
color: #2d3748 !important; | |
margin-right: 20% !important; | |
border: 1px solid #e2e8f0 !important; | |
} | |
/* Input styling */ | |
.textbox { | |
border-radius: 8px !important; | |
border: 2px solid #e2e8f0 !important; | |
background: white !important; | |
font-size: 16px !important; | |
} | |
.textbox:focus { | |
border-color: #4a5568 !important; | |
box-shadow: 0 0 0 3px rgba(74, 85, 104, 0.1) !important; | |
} | |
/* Checkbox styling */ | |
.checkbox-group { | |
background: rgba(255, 255, 255, 0.8) !important; | |
border-radius: 8px !important; | |
padding: 10px !important; | |
margin: 10px 0 !important; | |
} | |
/* Button styling */ | |
button { | |
background: #4a5568 !important; | |
color: white !important; | |
border: none !important; | |
border-radius: 8px !important; | |
padding: 10px 20px !important; | |
font-weight: 600 !important; | |
transition: all 0.2s ease !important; | |
} | |
button:hover { | |
background: #2d3748 !important; | |
transform: translateY(-1px) !important; | |
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important; | |
} | |
/* Title styling */ | |
h1, h2 { | |
color: #2d3748 !important; | |
} | |
/* Remove excessive shadows and effects */ | |
* { | |
text-shadow: none !important; | |
} | |
/* Responsive design */ | |
@media (max-width: 768px) { | |
.user { | |
margin-left: 10% !important; | |
} | |
.bot { | |
margin-right: 10% !important; | |
} | |
} | |
""" | |
with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo: | |
gr.HTML( | |
""" | |
<div style="text-align: center; padding: 20px;"> | |
<h1 style="font-size: 2.5em; margin-bottom: 10px;">π€ AI Chat Assistant : Mistral-Devstral</h1> | |
<p style="font-size: 1.1em; color: #718096;">with Web Search</p> | |
<!-- λ°°μ§ μΉμ --> | |
<div style="margin-top: 20px; display: flex; justify-content: center; align-items: center; gap: 15px;"> | |
<a href="https://huggingface.co/spaces/aiqcamp/deepseek-r1-0528" target="_blank"> | |
<img src="https://img.shields.io/static/v1?label=Commercial%20API&message=deepseek-r1-0528&color=%230000ff&labelColor=%23800080&logo=huggingface&logoColor=%23ffa500&style=for-the-badge" alt="badge"> | |
</a> | |
<a href="https://huggingface.co/spaces/aiqcamp/deepseek-r1-0528-qwen3-8b" target="_blank"> | |
<img src="https://img.shields.io/static/v1?label=Commercial%20API&message=deepseek-r1-0528-qwen3-8b&color=%230000ff&labelColor=%23800080&logo=huggingface&logoColor=%23ffa500&style=for-the-badge" alt="badge"> | |
</a> | |
<a href="https://huggingface.co/spaces/aiqcamp/Mistral-Devstral-API" target="_blank"> | |
<img src="https://img.shields.io/static/v1?label=Free%20API&message=Mistral-Devstral&color=%230000ff&labelColor=%23800080&logo=huggingface&logoColor=%23ffa500&style=for-the-badge" alt="badge"> | |
</a> | |
</div> | |
</div> | |
""" | |
) | |
chatbot = gr.Chatbot( | |
label="Chat History", | |
height=500, | |
elem_classes=["chatbot"], | |
bubble_full_width=False | |
) | |
with gr.Row(): | |
msg_in = gr.Textbox( | |
placeholder="Type your message here...", | |
label="Message", | |
scale=4, | |
lines=1 | |
) | |
submit_btn = gr.Button("Send", scale=1, variant="primary") | |
with gr.Row(): | |
use_web_search = gr.Checkbox( | |
label="π Enable Web Search", | |
value=True, | |
scale=2 | |
) | |
clear_btn = gr.Button("Clear Chat", scale=1) | |
# Event handlers | |
def submit_message(msg, history, search): | |
return openrouter_chat(msg, history, search) | |
msg_in.submit( | |
submit_message, | |
inputs=[msg_in, chatbot, use_web_search], | |
outputs=[chatbot, msg_in] | |
) | |
submit_btn.click( | |
submit_message, | |
inputs=[msg_in, chatbot, use_web_search], | |
outputs=[chatbot, msg_in] | |
) | |
clear_btn.click(lambda: ([], ""), outputs=[chatbot, msg_in]) | |
# Add example queries | |
gr.Examples( | |
examples=[ | |
["What's the latest news about AI?", True], | |
["Explain quantum computing in simple terms", False], | |
["What's the weather like today?", True], | |
["Write a Python function to sort a list", False], | |
["What are the current stock market trends?", True] | |
], | |
inputs=[msg_in, use_web_search], | |
label="Example Queries" | |
) | |
demo.launch() |