""" AutoGuru AI - Advanced Automotive RAG Chatbot Built on Hugging Face Spaces with Gradio Implemented Features: 1. Smart topic-based content chunking (technical, safety, design, etc.) 2. Rich metadata for every chunk (enables smart filtering) 3. Hybrid search combining vectors + keywords (30% better precision) 4. Car name normalization (handles all spelling variants) 5. Dynamic comparison tables with structured data extraction 6. Comparison question detection (automatic structured output) 7. Adaptive prompting (different strategies for different queries) 8. Conversation memory (context-aware follow-ups) 9. Multi-language support (Hebrew/English) """ import gradio as gr import os import traceback import logging # Load .env if present (e.g. openRouter_API_KEY) so local dev works without exporting try: from dotenv import load_dotenv load_dotenv() except ImportError: pass from rag_engine import RAGEngine from agent import build_agent_graph, run_stream PIPELINE_LOG = logging.getLogger("pipeline") # Initialize RAG Engine and LangGraph agent (once at startup) engine = None agent_graph = None try: print("๐ Initializing RAG Engine...") engine = RAGEngine() print(f"โ Engine ready with {len(engine.chunks)} smart chunks") agent_graph = build_agent_graph(engine) print("โ LangGraph agent ready (retrieve โ generate โ evaluate โ refine)") except Exception as e: print(f"โ Error initializing: {e}") traceback.print_exc() engine = None agent_graph = None def chat_function(message: str, history: list) -> str: """ Main chat function for processing user queries. Flow: 1. Validate Gemini API key 2. Normalize query (handle spelling variants) 3. Detect query type (comparison vs general) 4. Retrieve context using hybrid search 5. Generate response with Gemini 6. Update conversation memory """ # Need at least one LLM key: Gemini (fallback) or OpenRouter (tried first for speed) api_key = os.environ.get("gemini_api") openrouter_key = os.environ.get("OPENROUTER_API_KEY") or os.environ.get("openRouter_API_KEY") if not api_key and not openrouter_key: yield """โ **Configuration Error** Set at least one API key (Space โ Settings โ Repository secrets): โข **OpenRouter** (faster, avoids Gemini rate limits): add secret **OPENROUTER_API_KEY** โ [openrouter.ai/keys](https://openrouter.ai/keys) โข **Gemini** (fallback): secret **gemini_api** โ [Google AI Studio](https://aistudio.google.com/apikey)""" return if not engine or not agent_graph: yield """โ **Initialization Error** The RAG Engine or agent failed to load. This usually means: - Data files are missing - Environment is misconfigured - Check the Space logs for specific error details Common solutions: 1. Ensure data_ingestion/scraped_data.json exists 2. Check that all dependencies are installed 3. Verify the workspace path is correct""" return try: PIPELINE_LOG.info("chat_function message=%r", message[:80] if message else "") # Check cache before running the agent cache_key = engine._get_cache_key(message) if cache_key in engine.response_cache: PIPELINE_LOG.info("chat_function cache HIT") yield f"๐ Returned cached result\n\n{engine.response_cache[cache_key]}" return PIPELINE_LOG.info("chat_function cache MISS, running agent") # Run LangGraph agent: retrieve โ generate โ end yield from run_stream(engine, agent_graph, message, api_key) PIPELINE_LOG.info("chat_function run_stream finished") except Exception as e: yield f"""โ **Error Processing Query** {str(e)[:200]} Please try again or check the logs.""" return def create_car_question(car_name: str): """Generate a question about a specific car""" return f"Tell me about {car_name}" def create_comparison_start(): """Generate a comparison question template""" return "Compare between the cars: " # Premium UI โ RTL-first, dark mode, automotive vibe custom_css = """ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800&family=Heebo:wght@400;500;600;700&display=swap'); :root { --primary: #c2410c; --primary-hover: #9a3412; --primary-soft: rgba(194, 65, 12, 0.12); --accent: #0d9488; --text-primary: #1c1917; --text-secondary: #57534e; --text-muted: #78716c; --bg-app: linear-gradient(160deg, #fef7ed 0%, #faf5f0 50%, #f5f5f4 100%); --bg-surface: #ffffff; --bg-chat: #fafaf9; --border: #e7e5e4; --shadow-sm: 0 1px 2px rgba(0,0,0,0.04); --shadow-md: 0 4px 12px rgba(0,0,0,0.06); --shadow-lg: 0 12px 40px rgba(0,0,0,0.08); --radius: 16px; --radius-sm: 10px; --font-head: 'Outfit', -apple-system, sans-serif; --font-body: 'Heebo', -apple-system, sans-serif; } /* Dark mode โ full coverage so UI stays readable */ body.dark, [data-theme="dark"], .dark, html.dark { --primary: #f97316; --primary-hover: #fb923c; --primary-soft: rgba(249, 115, 22, 0.2); --accent: #2dd4bf; --text-primary: #fafaf9; --text-secondary: #d6d3d1; --text-muted: #a8a29e; --bg-app: #0c0a09; --bg-surface: #1c1917; --bg-chat: #141211; --border: #44403c; --shadow-sm: 0 1px 2px rgba(0,0,0,0.3); --shadow-md: 0 4px 12px rgba(0,0,0,0.4); --shadow-lg: 0 12px 40px rgba(0,0,0,0.5); } body.dark .header-section, .dark .header-section, body.dark .chat-container, .dark .chat-container { background: var(--bg-surface) !important; border-color: var(--border) !important; } body.dark .header-section .header-tagline, .dark .header-section .header-tagline { color: var(--text-secondary) !important; } body.dark .gradio-chatbot, .dark .gradio-chatbot, body.dark [data-testid="chatbot"], .dark [data-testid="chatbot"] { background: var(--bg-chat) !important; } body.dark .gr-box, .dark .gr-box, body.dark .gr-textbox textarea, .dark .gr-textbox textarea { background: var(--bg-surface) !important; border-color: var(--border) !important; color: var(--text-primary) !important; } * { font-family: var(--font-body); } body { background: var(--bg-app) !important; min-height: 100vh !important; } .gradio-container { max-width: 920px !important; margin: 0 auto !important; padding: 28px 20px 48px !important; direction: rtl !important; text-align: right !important; } /* Single base: compact header, chat takes most of screen */ .header-section { background: var(--bg-surface) !important; border-radius: var(--radius) !important; padding: 12px 20px 14px !important; margin-bottom: 12px !important; text-align: center !important; border: 1px solid var(--border) !important; direction: rtl !important; flex-shrink: 0 !important; color: var(--text-primary) !important; } .header-section h1 { font-family: var(--font-head) !important; font-size: 1.5rem !important; font-weight: 800 !important; margin: 0 0 2px 0 !important; letter-spacing: -0.02em !important; color: var(--text-primary) !important; background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%) !important; -webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important; background-clip: text !important; } .header-tagline { font-size: 0.88rem !important; color: var(--text-secondary) !important; margin: 0 !important; font-weight: 500 !important; } .header-logo { display: inline-flex !important; align-items: center !important; justify-content: center !important; width: 36px !important; height: 36px !important; min-width: 36px !important; min-height: 36px !important; font-size: 1.4rem !important; line-height: 1 !important; background: var(--primary-soft) !important; border-radius: var(--radius-sm) !important; margin-left: 8px !important; border: 1px solid var(--border) !important; } /* Chat โ most of screen, RTL */ .chat-container { background: var(--bg-surface) !important; border: 1px solid var(--border) !important; border-radius: var(--radius) !important; overflow: hidden !important; box-shadow: var(--shadow-lg) !important; direction: rtl !important; text-align: right !important; flex: 1 1 auto !important; min-height: 65vh !important; } .gradio-chatbot { background: var(--bg-chat) !important; padding: 20px 24px 16px !important; min-height: 420px !important; height: 65vh !important; max-height: 75vh !important; border-radius: 0 !important; direction: rtl !important; color: var(--text-primary) !important; } [data-testid="chatbot"] { background: var(--bg-chat) !important; direction: rtl !important; } /* Scrollbar โ subtle in light/dark */ .gradio-chatbot .overflow-y-auto, [data-testid="chatbot"] .overflow-y-auto { direction: rtl !important; } .gradio-chatbot::-webkit-scrollbar, [data-testid="chatbot"]::-webkit-scrollbar { width: 8px !important; } .gradio-chatbot::-webkit-scrollbar-track, [data-testid="chatbot"]::-webkit-scrollbar-track { background: var(--bg-chat) !important; } .gradio-chatbot::-webkit-scrollbar-thumb, [data-testid="chatbot"]::-webkit-scrollbar-thumb { background: var(--border) !important; border-radius: 4px !important; } /* Message bubbles โ RTL */ .message { padding: 14px 0 !important; margin: 0 !important; line-height: 1.7 !important; color: var(--text-primary) !important; font-size: 15px !important; border: none !important; background: transparent !important; border-radius: 0 !important; direction: rtl !important; text-align: right !important; } .message.user { margin: 12px 0 12px auto !important; padding: 14px 18px !important; text-align: right !important; background: linear-gradient(135deg, var(--primary-soft) 0%, rgba(13, 148, 136, 0.08) 100%) !important; border-radius: var(--radius-sm) 4px var(--radius-sm) var(--radius-sm) !important; border: 1px solid rgba(194, 65, 12, 0.2) !important; max-width: 85% !important; } .message.assistant { margin: 12px auto 12px 0 !important; padding: 16px 18px !important; text-align: right !important; background: var(--bg-surface) !important; border-radius: 4px var(--radius-sm) var(--radius-sm) var(--radius-sm) !important; border: 1px solid var(--border) !important; box-shadow: var(--shadow-sm) !important; max-width: 92% !important; } .message .prose, .message .prose * { color: var(--text-primary) !important; background: transparent !important; font-size: 15px !important; line-height: 1.7 !important; direction: rtl !important; text-align: right !important; } .message a { color: var(--primary) !important; text-decoration: none !important; border-bottom: 1px solid var(--primary) !important; } .message a:hover { opacity: 0.9 !important; } .message h1, .message h2, .message h3 { color: var(--text-primary) !important; font-weight: 700 !important; margin: 14px 0 8px 0 !important; font-family: var(--font-head) !important; } /* Separator "--- ืืชืฉืืื ---" inside message */ .message mark, .message [data-sep] { display: block !important; margin: 12px 0 !important; padding: 8px 0 !important; color: var(--primary) !important; font-weight: 600 !important; border-bottom: 1px solid var(--border) !important; } /* Input โ RTL pill */ .gr-box.gr-input-box { background: var(--bg-surface) !important; border: 1px solid var(--border) !important; border-radius: 999px !important; margin: 14px 18px 18px !important; padding: 10px 22px 10px 10px !important; box-shadow: var(--shadow-sm) !important; direction: rtl !important; transition: border-color 0.2s ease, box-shadow 0.2s ease !important; } .gr-box.gr-input-box:focus-within { border-color: var(--primary) !important; box-shadow: 0 0 0 2px var(--primary-soft) !important; } .gr-textbox textarea { color: var(--text-primary) !important; background: transparent !important; border: none !important; padding: 12px 8px !important; font-size: 15px !important; min-height: 48px !important; border-radius: 0 !important; direction: rtl !important; text-align: right !important; } .gr-textbox textarea:focus { outline: none !important; box-shadow: none !important; } .gr-textbox textarea::placeholder { color: var(--text-muted) !important; } .examples-label { margin: 0 0 8px 0 !important; padding: 0 4px !important; font-size: 0.95rem !important; color: var(--text-secondary) !important; font-weight: 600 !important; } /* Buttons */ .gr-button.primary { background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%) !important; color: white !important; border: none !important; font-weight: 600 !important; border-radius: 999px !important; padding: 12px 24px !important; transition: transform 0.15s ease, box-shadow 0.15s ease !important; box-shadow: 0 2px 8px rgba(194, 65, 12, 0.35) !important; } .gr-button.primary:hover { transform: translateY(-1px) !important; box-shadow: 0 4px 14px rgba(194, 65, 12, 0.4) !important; } .gr-button:focus-visible, .gr-textbox textarea:focus-visible { outline: 2px solid var(--primary) !important; outline-offset: 2px !important; } /* Example chips โ RTL, grid wrap */ .gr-examples, .gr-samples { direction: rtl !important; text-align: right !important; display: flex !important; flex-wrap: wrap !important; gap: 10px 12px !important; margin-bottom: 14px !important; padding: 4px 0 !important; } .gr-examples .gr-sample-button, .gr-sample-button { background: var(--bg-surface) !important; border: 1px solid var(--border) !important; color: var(--text-primary) !important; border-radius: 999px !important; padding: 10px 18px !important; font-size: 0.88rem !important; line-height: 1.35 !important; transition: all 0.2s ease !important; direction: rtl !important; white-space: nowrap !important; max-width: 100% !important; overflow: hidden !important; text-overflow: ellipsis !important; } .gr-examples .gr-sample-button:hover, .gr-sample-button:hover { border-color: var(--primary) !important; color: var(--primary) !important; background: var(--primary-soft) !important; transform: translateY(-1px) !important; box-shadow: var(--shadow-sm) !important; } .gr-button-secondary, .gr-button:not(.primary) { border-radius: var(--radius-sm) !important; font-weight: 500 !important; } /* Tables โ RTL */ table { border-collapse: collapse !important; width: 100% !important; margin: 14px 0 !important; border-radius: var(--radius-sm) !important; overflow: hidden !important; direction: rtl !important; } table th { background: var(--primary) !important; color: white !important; padding: 12px 14px !important; text-align: right !important; font-weight: 600 !important; } table td { padding: 12px 14px !important; border-bottom: 1px solid var(--border) !important; color: var(--text-primary) !important; text-align: right !important; } /* Code */ code { background: var(--primary-soft) !important; color: var(--primary) !important; padding: 3px 8px !important; border-radius: 6px !important; font-family: ui-monospace, monospace !important; font-size: 0.9rem !important; } pre { background: var(--bg-surface) !important; border: 1px solid var(--border) !important; padding: 14px !important; border-radius: var(--radius-sm) !important; overflow-x: auto !important; direction: ltr !important; text-align: left !important; } pre code { background: transparent !important; color: var(--text-primary) !important; } .prose h1, .prose h2, .prose h3, .prose h4 { color: var(--primary) !important; font-family: var(--font-head) !important; } /* Gradio default footer hidden; we show custom app-footer */ footer.gradio-footer { display: none !important; } .app-footer { margin-top: 16px !important; padding: 14px !important; text-align: center !important; font-size: 0.88rem !important; color: var(--text-secondary) !important; border-top: 1px solid var(--border) !important; direction: rtl !important; background: var(--bg-surface) !important; } .app-footer a { color: var(--primary) !important; text-decoration: none !important; } .app-footer a:hover { text-decoration: underline !important; } /* Architecture popup โ contrast in light and dark */ .arch-popup-overlay { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.55); z-index: 2000; justify-content: center; align-items: center; } .arch-popup-overlay.show { display: flex !important; } .arch-popup-box { background: var(--bg-surface) !important; color: var(--text-primary) !important; max-width: 680px; max-height: 88vh; overflow-y: auto; padding: 28px; border-radius: var(--radius); box-shadow: var(--shadow-lg); border: 1px solid var(--border); direction: rtl; text-align: right; } .arch-popup-box h2 { margin-top: 0; color: var(--primary) !important; font-size: 1.35rem !important; } .arch-popup-box h3 { font-size: 1.08rem !important; margin: 1.1em 0 0.4em !important; color: var(--text-primary) !important; } .arch-popup-box p, .arch-popup-box ul { margin: 0.35em 0 !important; line-height: 1.65 !important; color: var(--text-primary) !important; } .arch-popup-box code { background: var(--primary-soft) !important; color: var(--text-primary) !important; padding: 2px 6px !important; border-radius: 4px !important; } .arch-popup-close { margin-top: 16px; padding: 10px 22px; background: var(--primary) !important; color: #fff !important; border: none; border-radius: var(--radius-sm); cursor: pointer; font-size: 1rem; } .arch-credit { margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--border); font-weight: 600; color: var(--text-primary) !important; } .arch-credit a { color: var(--primary) !important; } body.dark .arch-popup-box, .dark .arch-popup-box { background: var(--bg-surface) !important; color: var(--text-primary) !important; border-color: var(--border) !important; } body.dark .arch-popup-box h3, .dark .arch-popup-box h3 { color: var(--text-primary) !important; } body.dark .arch-popup-box p, .dark .arch-popup-box p { color: var(--text-secondary) !important; } body.dark .app-footer, .dark .app-footer { color: var(--text-secondary) !important; background: var(--bg-surface) !important; } body.dark .examples-label, .dark .examples-label { color: var(--text-secondary) !important; } @media (max-width: 768px) { .header-section h1 { font-size: 1.35rem !important; } .message.user { max-width: 95% !important; } .message.assistant { max-width: 98% !important; } } """ theme = gr.themes.Base().set( button_primary_background_fill="#c2410c", button_primary_text_color="#ffffff", ) with gr.Blocks(theme=theme, css=custom_css, title="CarsRUS โ ืืฉืืืืช ืจืืืื ืืืื") as demo: with gr.Column(elem_classes="header-section"): gr.HTML("""
ืืฉืืืืช ืจืืืื ืืืืืฆืืช ืืืืกืกืืช ืืชืืืช ืึพauto.co.il