Spaces:
Running
Running
| """ | |
| 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(""" | |
| <div dir="rtl" style="text-align: center; display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap;"> | |
| <span class="header-logo" aria-hidden="true">๐</span> | |
| <h1 style="margin: 0;">CarsRUS</h1> | |
| <p class="header-tagline" style="margin: 0;">ืืฉืืืืช ืจืืืื ืืืืืฆืืช ืืืืกืกืืช ืืชืืืช ืึพauto.co.il</p> | |
| </div> | |
| """) | |
| with gr.Column(elem_classes="chat-container"): | |
| gr.Markdown("#### ืืืจ ืืืืื ืื ืืงืื ืฉืืื", elem_classes="examples-label") | |
| chat_interface = gr.ChatInterface( | |
| fn=chat_function, | |
| examples=[ | |
| "ืกืคืจ ืื ืขื ืืืื RS3", | |
| "ืืฉืืืื ืืื RS3 ืืืื ืืจื N", | |
| "ืื ืืืืื ืืงืื EV9?", | |
| "Tell me about the Audi RS3", | |
| "Compare Audi RS3 vs Hyundai Elantra N", | |
| "ืกืคืจ ืื ืขื ืกืืืจืืื C3", | |
| "ืื ืืชืจืื ืืช ืืืกืจืื ืืช ืฉื ื'ื ืกืืก GV80?", | |
| "ืืฉืืืื ืืื ืงืื EV9 ืึพMG S6", | |
| "ืื ืืืืืืืช ืืืื ืืจื N?", | |
| "Tell me about Genesis GV80", | |
| "Compare Kia EV9 vs MG S6", | |
| "ืื ืืื ืืข ืืืืืฆืืขืื ืฉื ืืืื RS3?", | |
| "ืืื EV9 ืืืฉืืืื ืืืื ืืจื N?", | |
| "ืกืคืจ ืขื ืืื ืง ืื ื ืงื 01", | |
| "What are the pros and cons of Elantra N?", | |
| "ืืฉืืืื ืืื ืืืื HT ืึพืงืื EV9", | |
| ], | |
| cache_examples=False, | |
| submit_btn="ืฉืื", | |
| stop_btn="ืขืฆืืจ", | |
| api_name="chat", # exposes /chat endpoint for gradio_client tests | |
| ) | |
| with gr.Column(elem_classes="app-footer"): | |
| gr.HTML(""" | |
| <div class="app-footer" dir="rtl"> | |
| <p style="margin: 0 0 6px 0;"><strong>ืืืฉื ืืฆ'ืื ืืจื API:</strong> <a href="https://huggingface.co/spaces/galbendavids/CarsRUS" target="_blank" rel="noopener">ืืืืืง ืืช ืืฆ'ืื ืึพAPI (ืืืฉื ืึพSpace + gradio_client / HTTP)</a></p> | |
| <p style="margin: 0 0 6px 0; font-size: 0.85rem;"><strong>ืืืืื ื ืชืืืื:</strong> Citroen C3 ยท Audi RS3 ยท Kia EV9 ยท MG S6 ยท Hyundai Elantra N ยท Aion HT ยท Genesis GV80 ยท Link & Co 01</p> | |
| <p style="margin: 0;"><a href="#" onclick="document.getElementById('arch-popup').classList.add('show'); return false;">๐ ืืจืืืืงืืืจื ืืืื ืืืจื ื ืื</a></p> | |
| </div> | |
| <div id="arch-popup" class="arch-popup-overlay" onclick="if(event.target===this) this.classList.remove('show');"> | |
| <div class="arch-popup-box" onclick="event.stopPropagation();"> | |
| <h2>ืืจืืืืงืืืจืช CarsRUS</h2> | |
| <p>ืืืขืจืืช ืื ืืื ืึพRAG (Retrieval-Augmented Generation) ืขื ืกืืื LangGraph, ืืื ืืชืช ืชืฉืืืืช ืืืืกืกืืช ืจืง ืขื ืืชืืืช ืึพauto.co.il. ืืืื ืืจืืืืื ืืืื ืืืจื ื ืืื.</p> | |
| <h3>ืืคืืืคืืืื โ ืฆืขื ืืืจ ืฆืขื</h3> | |
| <p>ืฉืืืืชืช ืืืฉืชืืฉ ื ืื ืกืช ืืฉืื <strong>ื ืจืืื ืฉืืืช</strong> (ืืืืื ืืื: RS3, ืืื ืง ืื ื ืงื 01 ืืื'). ืืืจ ืื <strong>ืืืืื ืกืื ืฉืืื</strong> โ ืืืื ืื ืืฉืืืื. ืืฉืื <strong>ืฉืืืคื</strong> ืืชืืฆืข ืืืคืืฉ ืืืืจืืื (ืืงืืืจืื + ืืืืืช ืืคืชื) ืืืชืงืืืื chunks ืจืืืื ืืืื. ืืื ืืื ืื <strong>ืคืจืืืคื</strong> (system + user) ืขื ืืืงืฉืจ, ืืืกืืืจืืืช ืฉืืื ืืืืืื. ืืืกืืฃ <strong>ืงืจืืื ืึพLLM</strong> (OpenRouter ืื Gemini) โ ืคืขื ืืืช โ ืืืชืฉืืื ืืืฆืืช ืืืฉืชืืฉ: ืงืืื "ืขืืงืจ ืืคืืืคืืืื" (ืฉืืื ืขืืืื) ืืื "ืืชืฉืืื" ืืื ืืคืืืืช.</p> | |
| <h3>1. ื ืชืื ืื ืืืืืงื ืื ืืฉืืื</h3> | |
| <p><strong>ืื:</strong> ืงืืืฅ <code>scraped_data.json</code> ืขื ืืชืืืช ืืืื ื ืืจืืื. ืื ืืชืื ืืืืืงืช ืึพchunks ืืคื ื ืืฉืืื (ืืคืจื ืืื ื, ืืืืืืช, ืขืืฆืื, ื ืืืืช, ืฆืจืืื, ืืื ืืืงื).</p> | |
| <p><strong>ืืื:</strong> ืืืืงื ืืคื ื ืืฉื ืืืคืฉืจืช ืืืคืืฉ ืืืืงื ืืืฉืืืฃ ืจืง ืงืืขืื ืจืืืื ืืืื ืืฉืืื, ืืืงืื ืืืขืืืจ ืืืืื ืืงืกื ืืจืื ืฉืื. ืื ืื ืืืืืง ืขืืื ืืื ืืื ืืชืืืื ืืืจื.</p> | |
| <h3>2. ืืืึพืืืื ืื ืจืืื ืฉืืืช ืจืืืื</h3> | |
| <p><strong>ืื:</strong> ืืื chunk ืืฉ ืืืึพืืืื (ืืืชืจืช, URL, ื ืืฉื, ืืืืืช ืืคืชื). ืืืืื ื ืจืืื + ืจืืงืก ืืืื ืืจืืืฆืืืช ืฉืืืช (ืืืื RS3, RS3, Link & Co 01, ืืื ืง ืื ื ืงื 01 ืืื').</p> | |
| <p><strong>ืืื:</strong> ืื ืืืฉืชืืฉ ืืืื ืืฉืืื ืืื ื ืืกืื ืืืืขืจืืช ืชืืื ืืช ืืืื ืืืืคืฉ ืจืง ืืจืฉืืืช ืืืืืื ืื ืชืืืื. ืืื ื ืจืืื, ืฉืืืืช ืืื "ืืื ืง ืื ื ืงื" ืื "Link and Co 01" ืืื ื ืืืืช ืืืจืืช ืฉืืฉ ืื ื ืืกืื ืืชืืื.</p> | |
| <h3>3. ืืืคืืฉ ืืืืจืืื (ืืงืืืจืื + ืืืืืช ืืคืชื)</h3> | |
| <p><strong>ืื:</strong> Embeddings ืขื <code>paraphrase-multilingual-MiniLM-L12-v2</code>, ืืืคืืฉ ืืืืื ืกืื ืื + ืืื ืืก ืึพchunks ืฉืืืืืื ืืืืืช ืืคืชื ืืืฉืืื (ืืื ืกืืก, ืงื"ืฉ, ืืืืืืช ืืื').</p> | |
| <p><strong>ืืื:</strong> ืฉืืืื ืกืื ืื ืืืืืืช ืืคืชื ืืฉืคืจ ืืืืง (ืึพ30% ืืืฉืืืื ืืืงืืืจื ืืืื) ืืืชืืื ืืขืืจืืช ืืื ืืืืช. ืืืืื ืืจืึพืืฉืื ื ืชืืื ืืฉืชื ืืฉืคืืช ืืืืชื ืืื ืืงืก.</p> | |
| <h3>4. ืกืืื LangGraph (retrieve โ generate)</h3> | |
| <p><strong>ืื:</strong> ืฆืขื ืจืืฉืื โ <code>prepare_generation</code> (ื ืจืืื, ืืืืื ืืฉืืืื/ืืืื, ืฉืืืคื, ืื ืืืช ืคืจืืืคื). ืฆืขื ืฉื ื โ ืงืจืืื ืึพLLM ืขื ืืืงืฉืจ ืฉื ืฉืืืฃ. ืืื ืืืืืช refine โ ืชืฉืืื ืืืช.</p> | |
| <p><strong>ืืื:</strong> ืืจืืื ืืืช ืืจืืจื ืืงืื ืขื ืชืืืืงื ืืืืืื. ืืืืื ื ืขื ืชืฉืืื ืืืช ืืกืืืจืช (ืงืืื "ืขืืงืจ ืืคืืืคืืืื" ืืื "ืืชืฉืืื") ืืื ืฉืืืฉืชืืฉ ืืจืื ืืชืงืืืืช ืืื ืืคืืืืช ืฉื ืืืชื ืชืฉืืื ืคืขืืืื.</p> | |
| <h3>5. ืืืื ืฉืคื (OpenRouter / Gemini)</h3> | |
| <p><strong>ืื:</strong> ืื ืืืืืจ <code>OPENROUTER_API_KEY</code> โ ืืฉืชืืฉืื ืึพOpenRouter (ืืืื ืืจืืจืช ืืืื Gemini Flash). ืืืจืช โ Google Gemini ืขื retry ืขื rate limit.</p> | |
| <p><strong>ืืื:</strong> OpenRouter ืืคืืืช ืืกืืืืช rate limit ืืืืคืฉืจ ืืืืฉืืช ืืืืื. Gemini ืืืืืื ืืฉืืืคืชื ืฉื OpenRouter ืื ืืืืืจ. ืืืืื ืืืืืจ ืชืฉืืื ืืืืืืืช ืืืช ืฉืืืืืช ืืช ืืืงืฉืจ ืฉื ืฉืืืฃ.</p> | |
| <h3>6. ืืืืจืื ืฉืืื ืืืืืื</h3> | |
| <p><strong>ืื:</strong> ืืืกืืืจืืืช ืฉืืื (Q+A) ืึพ3 ืชืืจืืช ืืืจืื ืืช ื ืืืืช ืืคืจืืืคื. ืืืืื ืืคื hash ืฉื ืืฉืืื โ ืชืฉืืืืช ืืืืช ืืืืจืืช ืืืืืืื ืืื ืงืจืืื ืึพLLM.</p> | |
| <p><strong>ืืื:</strong> ืืขื ื ืืฉืืืืช ืืืฉื ("ืืื ืืืื ืืืืืืืช?") ืืืจืฉ ืืงืฉืจ โ ืืื ืืืกืืืจืื ืืืืื ืื ืืืืข ืขื ืื ืืืืจืื. ืืืืืื ืืืกื ืืื ืืืกืฃ ืขื ืฉืืืืช ืืืืจืืช ืืืฉืคืจ ืืืืืืช ืืฉืชืืฉ.</p> | |
| <p class="arch-credit">ืื ืฉืืื ืื ืืื ยท <a href="mailto:galbendavids@gmail.com">galbendavids@gmail.com</a></p> | |
| <button type="button" class="arch-popup-close" onclick="document.getElementById('arch-popup').classList.remove('show');">ืกืืืจ</button> | |
| </div> | |
| </div> | |
| """) | |
| if __name__ == "__main__": | |
| # Enable queue + open API so tests (gradio_client) can call the same chat pipeline | |
| demo.queue(api_open=True).launch() | |