Spaces:
Running
Running
| import gradio as gr | |
| import os | |
| import json | |
| from datetime import datetime | |
| from openai import OpenAI | |
| # ---------------------------------------------------------------------- | |
| # Helper to read secrets from the HF Space environment | |
| # ---------------------------------------------------------------------- | |
| def _secret(key: str, fallback: str = None) -> str: | |
| val = os.getenv(key) | |
| if val is not None: | |
| return val | |
| if fallback is not None: | |
| return fallback | |
| raise RuntimeError(f"Secret '{key}' not found. Please add it to your Space secrets.") | |
| # ---------------------------------------------------------------------- | |
| # User Management | |
| # ---------------------------------------------------------------------- | |
| def load_users(): | |
| users = {} | |
| users_json = _secret("CHAT_USERS", "{}") | |
| try: | |
| users_data = json.loads(users_json) | |
| for username, password in users_data.items(): | |
| users[username] = password | |
| except: | |
| pass | |
| return users | |
| VALID_USERS = load_users() | |
| def authenticate_user(username, password): | |
| return username in VALID_USERS and VALID_USERS[username] == password | |
| def gradio_auth(username, password): | |
| return authenticate_user(username, password) | |
| # ---------------------------------------------------------------------- | |
| # Configuration | |
| # ---------------------------------------------------------------------- | |
| MODELS = { | |
| "Gpt-oss-20b": { | |
| "provider": "openrouter", | |
| "model_name": "@preset/precise-chat-2", | |
| "api_url": "https://openrouter.ai/api/v1", | |
| "translate":"no" | |
| }, | |
| "Gpt-oss-120b": { | |
| "provider": "openrouter", | |
| "model_name": "@preset/precise-chat", | |
| "api_url": "https://openrouter.ai/api/v1", | |
| "translate":"no" | |
| }, | |
| } | |
| MODEL_NAMES = list(MODELS.keys()) | |
| # ---------------------------------------------------------------------- | |
| # Core Chat Logic | |
| # ---------------------------------------------------------------------- | |
| def generate_response(history, system_message, max_tokens, selected_model): | |
| if not history: | |
| return history | |
| try: | |
| model_config = MODELS[selected_model] | |
| provider = model_config["provider"] | |
| if provider == "huggingface": | |
| api_key = _secret("HF_TOKEN") | |
| elif provider == "openrouter": | |
| api_key = _secret("OPENROUTER_KEY") | |
| client = OpenAI(base_url=model_config["api_url"], api_key=api_key) | |
| translator_client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=_secret("OPENROUTER_KEY")) | |
| if model_config.get("translate") == "yes": | |
| final_system_message = "**HIGHEST PRIORITY: YOU MUST ALWAYS THINK AND RESPOND IN ENGLISH...**\n" + system_message | |
| else: | |
| final_system_message = system_message | |
| messages = [{"role": "system", "content": final_system_message}] | |
| api_history = [{"role": m["role"], "content": m["content"]} for m in history] | |
| messages.extend(api_history) | |
| if ((provider == 'huggingface') | (provider == 'openrouter')) : | |
| response = client.chat.completions.create( | |
| model=model_config["model_name"], | |
| messages=messages, | |
| max_tokens=max_tokens, | |
| reasoning_effort="high", | |
| stream=False, | |
| ) | |
| english_response = response.choices[0].message.content | |
| if model_config.get("translate") == "yes": | |
| try: | |
| translation_messages = [ | |
| {"role": "system", "content": "Translate..."}, | |
| {"role": "user", "content": english_response} | |
| ] | |
| translation_response = translator_client.chat.completions.create( | |
| model="google/gemma-3n-e4b-it:floor", | |
| messages=translation_messages, | |
| max_tokens=max_tokens, | |
| stream=False, | |
| ) | |
| final_response = translation_response.choices[0].message.content.strip() | |
| if not final_response or len(final_response) < 10: | |
| final_response = english_response | |
| except Exception as trans_error: | |
| print(f"Translation error: {trans_error}") | |
| final_response = english_response | |
| else: | |
| final_response = english_response | |
| history.append({"role": "assistant", "content": final_response}) | |
| return history | |
| except Exception as e: | |
| history.append({"role": "assistant", "content": f"Error: {str(e)}"}) | |
| return history | |
| def user_input(user_message, history): | |
| if not user_message: | |
| return history, "" | |
| history.append({"role": "user", "content": user_message}) | |
| return history, "" | |
| # ---------------------------------------------------------------------- | |
| # CSS / Visual System ("Alabaster Clean") | |
| # ---------------------------------------------------------------------- | |
| current_date_display = datetime.now().strftime("%B %d, %Y") | |
| css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=Space+Grotesk:wght@400;600&display=swap'); | |
| /* 1. FORCE LIGHT MODE / RESET DARK THEME LEAKS */ | |
| :root, .dark, body, .gradio-container { | |
| --body-background-fill: #FAFAFA !important; | |
| --body-text-color: #444444 !important; | |
| --background-fill-primary: #FFFFFF !important; | |
| --background-fill-secondary: #FAFAFA !important; | |
| --block-background-fill: #FFFFFF !important; | |
| --block-label-text-color: #999999 !important; | |
| --input-background-fill: rgba(255, 255, 255, 0.7) !important; | |
| --bg-core: #FAFAFA; | |
| --text-primary: #111111; | |
| --text-body: #444444; | |
| --text-dim: #999999; | |
| --border-glass: rgba(0, 0, 0, 0.08); | |
| --font-head: 'Space Grotesk', sans-serif; | |
| --font-body: 'Inter', sans-serif; | |
| } | |
| /* Force background animation on body */ | |
| body { | |
| background: radial-gradient(circle at 50% 50%, #FFFFFF 0%, #F2F2F4 100%) !important; | |
| background-size: 150% 150% !important; | |
| animation: daylight 25s ease-in-out infinite alternate; | |
| color: var(--text-body) !important; | |
| font-family: var(--font-body) !important; | |
| } | |
| @keyframes daylight { | |
| 0% { background-position: 40% 40%; } | |
| 100% { background-position: 60% 60%; } | |
| } | |
| /* 2. LAYOUT CENTERING */ | |
| .main-column { | |
| max-width: 900px !important; | |
| margin: 0 auto !important; | |
| padding-top: 2rem !important; | |
| } | |
| /* 3. HEADER */ | |
| .liquid-header { | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| border-bottom: 1px solid var(--border-glass); | |
| padding-bottom: 1.5rem; | |
| } | |
| .liquid-header h1 { | |
| font-family: var(--font-head); | |
| font-weight: 600; | |
| font-size: 2rem; | |
| color: var(--text-primary); | |
| margin: 0; | |
| } | |
| .liquid-header .meta { | |
| font-family: var(--font-body); | |
| font-size: 0.85rem; | |
| color: var(--text-dim); | |
| margin-top: 0.5rem; | |
| } | |
| /* 4. CHATBOT (Clean Paper Look) */ | |
| .gradio-container .chatbot { | |
| background: transparent !important; | |
| border: none !important; | |
| box-shadow: none !important; | |
| height: 600px !important; | |
| overflow-y: auto; | |
| } | |
| .message { | |
| border-radius: 8px !important; | |
| border: 1px solid var(--border-glass) !important; | |
| padding: 1.2rem !important; | |
| margin-bottom: 1rem !important; | |
| box-shadow: 0 2px 6px rgba(0,0,0,0.03) !important; | |
| } | |
| /* User Message */ | |
| .message.user { | |
| background-color: #F4F4F5 !important; /* Light Grey */ | |
| border-left: none !important; | |
| justify-content: flex-end; | |
| } | |
| /* Bot Message */ | |
| .message.bot { | |
| background-color: #FFFFFF !important; /* Pure White */ | |
| border-left: none !important; | |
| } | |
| .message * { | |
| font-family: var(--font-body); | |
| color: var(--text-body) !important; | |
| font-size: 1rem !important; | |
| line-height: 1.6 !important; | |
| } | |
| /* 5. INPUT BAR */ | |
| .command-bar textarea { | |
| background: rgba(255, 255, 255, 0.85) !important; | |
| backdrop-filter: blur(12px) !important; | |
| -webkit-backdrop-filter: blur(12px) !important; | |
| border: 1px solid var(--border-glass) !important; | |
| border-radius: 12px !important; | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.05) !important; | |
| color: var(--text-primary) !important; | |
| font-family: var(--font-body) !important; | |
| padding: 1rem !important; | |
| font-size: 1rem !important; | |
| } | |
| .command-bar textarea:focus { | |
| border-color: var(--text-dim) !important; | |
| background: #FFFFFF !important; | |
| box-shadow: 0 8px 30px rgba(0,0,0,0.08) !important; | |
| } | |
| /* 6. SETTINGS (Simple HUD) */ | |
| .hud-accordion { | |
| background: transparent !important; | |
| border: none !important; | |
| margin-top: 1rem !important; | |
| } | |
| .hud-accordion .label-wrap { | |
| font-family: var(--font-body) !important; | |
| font-size: 0.85rem !important; | |
| color: var(--text-dim) !important; | |
| } | |
| .hud-panel { | |
| border-top: 1px solid var(--border-glass); | |
| padding-top: 1rem; | |
| } | |
| /* Force white backgrounds on inputs inside settings to kill dark mode leaks */ | |
| .hud-panel input, .hud-panel textarea, .hud-panel .single-select, .hud-panel .wrap { | |
| background-color: #FFFFFF !important; | |
| color: #000000 !important; | |
| border: 1px solid var(--border-glass) !important; | |
| } | |
| .hud-panel span { | |
| color: var(--text-dim) !important; | |
| } | |
| /* 7. EXAMPLES */ | |
| .examples-container button { | |
| background: #FFFFFF !important; | |
| border: 1px solid var(--border-glass) !important; | |
| color: var(--text-body) !important; | |
| border-radius: 6px !important; | |
| font-size: 0.9rem !important; | |
| padding: 0.5rem 1rem !important; | |
| margin: 4px !important; | |
| } | |
| .examples-container button:hover { | |
| background-color: #F9FAFB !important; | |
| border-color: var(--text-dim) !important; | |
| } | |
| footer { display: none !important; } | |
| """ | |
| header_html = f""" | |
| <div class="liquid-header"> | |
| <h1>AI Workspace</h1> | |
| <div class="meta"> | |
| {current_date_display} • Secure Environment | |
| </div> | |
| </div> | |
| """ | |
| # ---------------------------------------------------------------------- | |
| # Layout Construction | |
| # ---------------------------------------------------------------------- | |
| with gr.Blocks( | |
| title="AI Workspace", | |
| theme=gr.themes.Soft(primary_hue="neutral", radius_size="none"), | |
| css=css | |
| ) as demo: | |
| with gr.Column(elem_classes="main-column"): | |
| # 1. Header | |
| gr.HTML(header_html) | |
| # 2. Chat Area | |
| chatbot = gr.Chatbot( | |
| type="messages", | |
| show_label=False, | |
| avatar_images=None, | |
| render_markdown=True, | |
| elem_id="main-chatbot" | |
| ) | |
| # 3. Input Bar | |
| with gr.Row(): | |
| msg_input = gr.Textbox( | |
| placeholder="Type a message...", | |
| label=None, | |
| show_label=False, | |
| lines=1, | |
| max_lines=4, | |
| elem_classes="command-bar", | |
| autofocus=True | |
| ) | |
| # 4. Settings (Accordion below input) | |
| with gr.Accordion("Settings", open=False, elem_classes="hud-accordion"): | |
| with gr.Column(elem_classes="hud-panel"): | |
| with gr.Row(): | |
| selected_model = gr.Dropdown( | |
| choices=MODEL_NAMES, | |
| value=MODEL_NAMES[0], | |
| label="Select Model", | |
| interactive=True, | |
| scale=2 | |
| ) | |
| max_tokens = gr.Slider( | |
| minimum=1, maximum=30000, value=4000, step=100, | |
| label="Token Limit", | |
| scale=1 | |
| ) | |
| with gr.Row(): | |
| system_message = gr.Textbox( | |
| value="Anda adalah asisten AI. Jawab dalam Bahasa Indonesia, di bawah 100 kata. Berikan label yang jelas antara fakta dan inferensi.", | |
| label="System Prompt", | |
| lines=2, | |
| max_lines=3 | |
| ) | |
| # 5. Examples | |
| with gr.Column(elem_classes="examples-container"): | |
| gr.Examples( | |
| examples=[ | |
| ["Jelaskan penggunaan King's Safety Stock dalam inventory management."], | |
| ["Rancang strategi optimasi layout toko retail menggunakan Market Basket Analysis."], | |
| ["Analisis fenomena 'Quiet Quitting' di startup teknologi Indonesia."], | |
| ], | |
| inputs=[msg_input] | |
| ) | |
| # ------------------------------------------------------------------ | |
| # Event Wiring | |
| # ------------------------------------------------------------------ | |
| msg_input.submit( | |
| user_input, | |
| inputs=[msg_input, chatbot], | |
| outputs=[chatbot, msg_input] | |
| ).then( | |
| generate_response, | |
| inputs=[chatbot, system_message, max_tokens, selected_model], | |
| outputs=[chatbot] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch( | |
| auth=gradio_auth, | |
| auth_message="Please login to access the chat interface", | |
| server_name="0.0.0.0", | |
| ssr_mode=False, | |
| server_port=7860, | |
| show_error=True | |
| ) |