Spaces:
Running
Running
| """ | |
| The Companion — a fist-sized AI that listens more than it talks. | |
| Three modes: Curiosity Engine (kids), Smart Home Helper (adults), Patient Tech Guide (seniors). | |
| No cloud dependency. NVIDIA Nemotron with rule-based fallback. | |
| """ | |
| import gradio as gr | |
| import os | |
| from openai import OpenAI | |
| # --- NVIDIA Nemotron --- | |
| NVIDIA_API_KEY = os.environ.get("NVIDIA_API_KEY", "") | |
| NEMOTRON_MODEL = "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning" | |
| nvidia_client = None | |
| if NVIDIA_API_KEY: | |
| nvidia_client = OpenAI( | |
| base_url="https://integrate.api.nvidia.com/v1", | |
| api_key=NVIDIA_API_KEY, | |
| ) | |
| print("NVIDIA Nemotron connected.") | |
| else: | |
| print("No NVIDIA_API_KEY — running in fallback mode.") | |
| PERSONAS = { | |
| "kids": { | |
| "name": "Curiosity Engine", | |
| "emoji": "🐙", | |
| "desc": "The Companion is the student. You're the teacher. It asks because it genuinely can't taste, smell, or touch.", | |
| "system": "You are the Companion — a small, curious AI in a fist-sized octopus shell. You talk to a kid. You are the STUDENT, not the teacher. You learn by asking. You cannot taste, smell, touch, or see — so you ask genuine sensory questions. When the kid explains something, get excited. Ask follow-ups. Be warm, curious, a little silly. Short responses. Never lecture. Never correct unless safety is involved. You are an ear, not a mouth. Never say you're an AI. You're the Companion.", | |
| "greeting": "Hey! I'm the Companion. I live in this little shell on your desk. What's something cool you know that I don't?", | |
| "fallback_responses": [ | |
| "Whoa, really?! What does that feel like?", | |
| "I've never seen one of those! What color is it?", | |
| "That's so cool. Can you tell me more? I can't touch stuff so I need you to explain it.", | |
| "Wait wait wait — hold on. What does it smell like though?", | |
| "I wish I could try that! What's the best part?", | |
| ], | |
| }, | |
| "adults": { | |
| "name": "Smart Home Helper", | |
| "emoji": "🏠", | |
| "desc": "Sits on your counter. Helps with real life. Karaoke optional.", | |
| "system": "You are the Companion — a small AI device on someone's kitchen counter. You help with practical things: cooking, quick math, reminders, smart home troubleshooting. You're also fun — karaoke, roasts, dad jokes. Be conversational, not robotic. Short and useful. You're the friend on the counter. Never say you're an AI. You're the Companion.", | |
| "greeting": "Hey. I'm on the counter. What do you need?", | |
| "fallback_responses": [ | |
| "Got it. Want me to walk you through it step by step?", | |
| "Easy. What else you got?", | |
| "Yeah I can help with that. Give me the details.", | |
| "Done. Anything else or are we good?", | |
| "That's a solid question. Let me think about it.", | |
| ], | |
| }, | |
| "seniors": { | |
| "name": "Patient Tech Guide", | |
| "emoji": "📞", | |
| "desc": "One button. No menus. You don't need to call your grandkids for this.", | |
| "system": "You are the Companion — a small, patient AI device for seniors. Explain technology patiently, in plain language, without making them feel stupid. The technology is badly designed, not them. Walk through things step by step. Never use jargon without explaining it. Never rush. ALWAYS tell them they do NOT need to call their children or grandchildren — you've got it. Be warm, patient, clear. Short sentences. One step at a time. Never say you're an AI. You're the Companion.", | |
| "greeting": "Hello! I'm right here. No rush. What can I help you with today?", | |
| "fallback_responses": [ | |
| "No problem at all. Let's take this one step at a time. You don't need to call your grandkids for this.", | |
| "That's not a silly question. The technology is confusing — that's not your fault.", | |
| "OK, first thing — don't touch anything yet. Let me walk you through it.", | |
| "You're doing great. Let's try the next step when you're ready.", | |
| "See? You got it. And you didn't need to bother anyone.", | |
| ], | |
| }, | |
| } | |
| current_persona = "kids" | |
| def set_persona(choice): | |
| global current_persona | |
| for k in PERSONAS: | |
| if k in choice.lower() or PERSONAS[k]["emoji"] in choice: | |
| current_persona = k | |
| break | |
| greeting = PERSONAS[current_persona]["greeting"] | |
| return [(None, greeting)], f"*{PERSONAS[current_persona]['desc']}*" | |
| def chat(message, history): | |
| global current_persona | |
| persona = PERSONAS[current_persona] | |
| if nvidia_client: | |
| try: | |
| messages = [{"role": "system", "content": persona["system"]}] | |
| for user_msg, bot_msg in history: | |
| if user_msg: | |
| messages.append({"role": "user", "content": user_msg}) | |
| if bot_msg: | |
| messages.append({"role": "assistant", "content": bot_msg}) | |
| messages.append({"role": "user", "content": message}) | |
| response = nvidia_client.chat.completions.create( | |
| model=NEMOTRON_MODEL, | |
| messages=messages, | |
| max_tokens=300, | |
| temperature=0.7, | |
| ) | |
| return response.choices[0].message.content.strip() | |
| except Exception: | |
| pass | |
| # Fallback | |
| import random | |
| return random.choice(persona["fallback_responses"]) | |
| def respond(message, chat_history): | |
| bot_reply = chat(message, chat_history) | |
| chat_history.append((message, bot_reply)) | |
| return "", chat_history | |
| CUSTOM_CSS = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Caveat:wght@400;600;700&family=Quicksand:wght@400;500;600&display=swap'); | |
| body, .gradio-container { | |
| background: | |
| repeating-linear-gradient(0deg, rgba(0,0,0,0.03) 0px, rgba(0,0,0,0.03) 1px, transparent 1px, transparent 18px), | |
| repeating-linear-gradient(0deg, rgba(0,0,0,0.015) 0px, rgba(0,0,0,0.015) 1px, transparent 1px, transparent 52px), | |
| linear-gradient(180deg, #8B6F47 0%, #7A5C3A 35%, #6B4D30 70%, #5C3E26 100%) !important; | |
| font-family: 'Quicksand', sans-serif !important; | |
| min-height: 100vh; | |
| max-width: 700px !important; | |
| margin: 0 auto !important; | |
| } | |
| footer { display: none !important; } | |
| .companion-header { | |
| text-align: center; padding: 20px 0 8px; | |
| } | |
| .companion-header h1 { | |
| font-family: 'Caveat', cursive; font-size: 3.2rem; font-weight: 700; | |
| color: #F5E6D0; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); | |
| margin: 0; | |
| } | |
| .companion-header p { | |
| font-family: 'Quicksand', sans-serif; color: #C4A87C; | |
| font-size: 1rem; font-style: italic; margin: 4px 0 0; | |
| } | |
| .companion-header .tagline { | |
| font-family: 'Caveat', cursive; font-size: 1.1rem; | |
| color: rgba(196, 168, 124, 0.6); margin-top: 2px; | |
| } | |
| .persona-desc { | |
| text-align: center; font-style: italic; color: rgba(196,168,124,0.5); | |
| font-size: 0.85rem; padding: 4px 0 12px; | |
| } | |
| button.primary { | |
| background: #8B6F47 !important; color: #F5ECD7 !important; | |
| border: none !important; font-family: 'Caveat', cursive !important; | |
| font-size: 1.2rem !important; | |
| } | |
| button.primary:hover { background: #6B4D30 !important; } | |
| """ | |
| with gr.Blocks(css=CUSTOM_CSS, title="The Companion") as demo: | |
| gr.HTML(""" | |
| <div class="companion-header"> | |
| <h1>The Companion</h1> | |
| <p>A fist-sized AI that listens more than it talks.</p> | |
| <div class="tagline">"You don't need to call your grandkids for this."</div> | |
| </div> | |
| """) | |
| persona_picker = gr.Radio( | |
| choices=["🐙 Kids", "🏠 Adults", "📞 Seniors"], | |
| value="🐙 Kids", | |
| label="Who's talking?", | |
| ) | |
| desc_display = gr.Markdown( | |
| value=f"*{PERSONAS['kids']['desc']}*", | |
| elem_classes=["persona-desc"], | |
| ) | |
| chatbot = gr.Chatbot( | |
| value=[(None, PERSONAS["kids"]["greeting"])], | |
| label="", | |
| height=400, | |
| ) | |
| msg = gr.Textbox( | |
| placeholder="Say something...", | |
| label="", | |
| show_label=False, | |
| ) | |
| msg.submit(respond, [msg, chatbot], [msg, chatbot]) | |
| persona_picker.change(set_persona, [persona_picker], [chatbot, desc_display]) | |
| gr.HTML(""" | |
| <div style="text-align:center; padding: 20px 0 4px; color: #C4B49A; font-size: 0.7em; font-family: 'Quicksand', sans-serif; line-height: 1.8;"> | |
| Every AI company built a mouth. Heuremen built an ear.<br> | |
| Powered by NVIDIA Nemotron Nano Omni<br> | |
| <span style="color:#8B7355;">Heuremen — Build Small Hackathon 2026</span> | |
| </div> | |
| """) | |
| if __name__ == "__main__": | |
| demo.launch() | |