# 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.Markdown( """
Powered by Devstral with Web Search