| | import gradio as gr |
| | import google.generativeai as genai |
| | import os |
| | import re |
| |
|
| | GEMINI_KEY = os.getenv("GEMINI") |
| | genai.configure(api_key=GEMINI_KEY) |
| | model = genai.GenerativeModel('gemini-pro') |
| |
|
| | CSS = """ |
| | :root { |
| | --primary: #2A5C82; |
| | --secondary: #5BA4E6; |
| | --accent: #FF7F50; |
| | --background: #F8F9FA; |
| | --text: #2C3E50; |
| | } |
| | |
| | body { |
| | background: var(--background); |
| | font-family: 'Segoe UI', system-ui, sans-serif; |
| | min-height: 100vh; |
| | margin: 0; |
| | padding: 20px; |
| | } |
| | |
| | .gradio-container { |
| | max-width: 800px; |
| | margin: 0 auto; |
| | background: white; |
| | border-radius: 20px; |
| | box-shadow: 0 10px 30px rgba(0,0,0,0.1); |
| | overflow: hidden; |
| | } |
| | |
| | .header { |
| | background: var(--primary); |
| | padding: 40px 20px; |
| | text-align: center; |
| | color: white; |
| | } |
| | |
| | .header h1 { |
| | margin: 0; |
| | font-size: 2.2em; |
| | font-weight: 700; |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | gap: 10px; |
| | color:#fff; |
| | |
| | } |
| | |
| | .header h1 svg{ |
| | color:#fff; |
| | } |
| | |
| | .header h1 p { |
| | color: #fff; |
| | } |
| | |
| | .chat-container { |
| | padding: 20px; |
| | height: 60vh; |
| | overflow-y: auto; |
| | } |
| | |
| | .msg-user { |
| | background: var(--secondary); |
| | color: white; |
| | padding: 15px 20px; |
| | border-radius: 15px 15px 0 15px; |
| | margin: 10px 0 10px 30%; |
| | position: relative; |
| | animation: slideIn 0.3s ease; |
| | } |
| | |
| | .msg-bot { |
| | background: white; |
| | padding: 20px; |
| | border-radius: 15px 15px 15px 0; |
| | margin: 10px 30% 10px 0; |
| | border: 1px solid #E0E0E0; |
| | position: relative; |
| | animation: fadeIn 0.5s ease; |
| | } |
| | |
| | .msg-bot::before { |
| | content: "✈️"; |
| | position: absolute; |
| | left: -35px; |
| | top: 15px; |
| | font-size: 1.5em; |
| | } |
| | |
| | .card { |
| | background: white; |
| | padding: 20px; |
| | border-radius: 12px; |
| | margin: 15px 0; |
| | border-left: 4px solid var(--accent); |
| | box-shadow: 0 3px 10px rgba(0,0,0,0.05); |
| | transition: transform 0.2s; |
| | } |
| | |
| | .card:hover { |
| | transform: translateY(-3px); |
| | } |
| | |
| | .price { |
| | color: var(--primary); |
| | font-weight: 600; |
| | margin: 8px 0; |
| | } |
| | |
| | .link { |
| | color: var(--secondary) !important; |
| | text-decoration: none; |
| | font-weight: 500; |
| | display: inline-flex; |
| | align-items: center; |
| | gap: 5px; |
| | } |
| | |
| | .link:hover { |
| | text-decoration: underline; |
| | } |
| | |
| | .controls { |
| | padding: 20px; |
| | background: #F8FAFD; |
| | border-top: 1px solid #E0E0E0; |
| | } |
| | |
| | input[type="text"] { |
| | border: 2px solid var(--secondary) !important; |
| | border-radius: 12px !important; |
| | padding: 15px !important; |
| | font-size: 1em; |
| | } |
| | |
| | button { |
| | background: var(--primary) !important; |
| | color: white !important; |
| | border: none !important; |
| | padding: 12px 25px !important; |
| | border-radius: 10px !important; |
| | transition: all 0.2s !important; |
| | } |
| | |
| | button:hover { |
| | transform: translateY(-2px); |
| | box-shadow: 0 5px 15px rgba(42,92,130,0.2) !important; |
| | } |
| | |
| | @keyframes slideIn { |
| | from { transform: translateX(20px); opacity: 0; } |
| | to { transform: translateX(0); opacity: 1; } |
| | } |
| | |
| | @keyframes fadeIn { |
| | from { opacity: 0; transform: translateY(10px); } |
| | to { opacity: 1; transform: translateY(0); } |
| | } |
| | |
| | @media (max-width: 600px) { |
| | .gradio-container { |
| | border-radius: 0; |
| | } |
| | |
| | .msg-user, .msg-bot { |
| | margin-left: 10%; |
| | margin-right: 10%; |
| | } |
| | } |
| | """ |
| |
|
| | class ChatManager: |
| | def __init__(self): |
| | self.context = {"city": None} |
| | |
| | def extract_city(self, text): |
| | cities = re.findall(r'\b(?:a|in|su|per)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)\b', text, re.IGNORECASE) |
| | return cities[0] if cities else self.context["city"] |
| | |
| | def format_response(self, response): |
| | structured = [] |
| | current_item = {} |
| | |
| | lines = response.split('\n') |
| | for line in lines: |
| | if line.startswith('- **'): |
| | parts = re.split(r'\*\*:?|\*', line) |
| | if len(parts) > 2: |
| | key = parts[1].strip().lower() |
| | value = parts[2].strip() |
| | current_item[key] = value |
| | elif line.strip() == '' and current_item: |
| | structured.append(current_item) |
| | current_item = {} |
| | |
| | if current_item: |
| | structured.append(current_item) |
| | |
| | html = "" |
| | for item in structured: |
| | html += f""" |
| | <div class='card'> |
| | <h3 style="margin:0;color:var(--primary)">📍 {item.get('nome', '')}</h3> |
| | <p style="margin:10px 0;color:var(--text)">{item.get('descrizione', '')}</p> |
| | <div style="display:flex;gap:15px;align-items:center"> |
| | {f"<div class='price'>💰 {item.get('prezzo', '')}</div>" if item.get('prezzo') else ''} |
| | {f"<a href='{item['link']}' class='link' target='_blank'><svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-external-link'><path d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'></path><polyline points='15 3 21 3 21 9'></polyline><line x1='10' y1='14' x2='21' y2='3'></line></svg> Maggiori info</a>" if 'link' in item else ''} |
| | </div> |
| | </div> |
| | """ |
| | return html if html else response |
| |
|
| | chat_manager = ChatManager() |
| |
|
| | def respond(message, chat_history): |
| | chat_history.append((message, "")) |
| | |
| | city = chat_manager.extract_city(message) |
| | if city: |
| | chat_manager.context["city"] = city |
| | |
| | if not chat_manager.context["city"]: |
| | bot_response = "🌍 Per favore dimmi prima di quale città vuoi informazioni!" |
| | chat_history[-1] = (message, bot_response) |
| | return "", chat_history |
| | |
| | prompt = f""" |
| | Sei un assistente turistico esperto di {chat_manager.context["city"]}. |
| | Fornisci informazioni strutturate in italiano con: |
| | - Nome luogo |
| | - Breve descrizione (max 30 parole) |
| | - Prezzo (se applicabile) |
| | - Link ufficiale (se disponibile) |
| | |
| | Richiesta: {message} |
| | """ |
| | |
| | try: |
| | response = model.generate_content(prompt).text |
| | bot_response = chat_manager.format_response(response) |
| | except Exception as e: |
| | bot_response = f"⚠️ Errore: {str(e)}" |
| | |
| | chat_history[-1] = (message, bot_response) |
| | return "", chat_history |
| |
|
| | def reset_context(chat_history): |
| | chat_manager.context = {"city": None} |
| | new_history = chat_history + [[None, "🔄 Contesto resettato! Di quale città vuoi parlare ora?"]] |
| | return new_history |
| |
|
| | with gr.Blocks(css=CSS) as app: |
| | with gr.Column(): |
| | gr.HTML(""" |
| | <div class="header"> |
| | <h1> |
| | <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| | <path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/> |
| | <circle cx="12" cy="10" r="3"/> |
| | </svg> |
| | Travel Assistant AI |
| | </h1> |
| | <p>La tua guida personale per scoprire il mondo</p> |
| | </div> |
| | """) |
| | |
| | chatbot = gr.Chatbot( |
| | label="Chat", |
| | elem_classes="chat-container", |
| | show_label=False, |
| | avatar_images=( |
| | None, |
| | "https://i.ibb.co/8XJp0dN/bot-avatar.png" |
| | ), |
| | value=[[None, "Ciao sono la tua guida turistica, come posso aiutarti? 🌍"]] |
| | ) |
| | |
| | with gr.Row(elem_classes="controls"): |
| | msg = gr.Textbox( |
| | label="", |
| | placeholder="Scrivi qui la tua richiesta...", |
| | show_label=False, |
| | container=False |
| | ) |
| | with gr.Column(min_width=100): |
| | send_btn = gr.Button("Invia ➔", variant="primary") |
| | reset_btn = gr.Button("Nuova Ricerca", variant="secondary") |
| |
|
| | send_btn.click( |
| | respond, |
| | [msg, chatbot], |
| | [msg, chatbot] |
| | ) |
| | reset_btn.click( |
| | reset_context, |
| | [chatbot], |
| | [chatbot] |
| | ) |
| | msg.submit( |
| | respond, |
| | [msg, chatbot], |
| | [msg, chatbot] |
| | ) |
| |
|
| | app.launch() |