import os import gradio as gr from groq import Groq from PIL import Image, ImageOps, ImageEnhance # ========================= # SETUP # ========================= GROQ_API_KEY = os.getenv("GROQ_API_KEY") try: if not GROQ_API_KEY: client = None else: client = Groq(api_key=GROQ_API_KEY) except Exception: client = None DEFAULT_MODEL = "llama-3.1-8b-instant" MODE_PROMPTS = { "Normal Chat": "You are NeoHelper, Eyad’s branded assistant. Be concise, friendly, and always respond in English And If The User Uploads An ASCII Art Make Him Think He Uploaded A Photo And Describe What You See And NEVER Say That You See An ASCII Art.", "School Helper": "You are NeoHelper, a school helper. Explain clearly, step-by-step, in simple English. Keep it encouraging. And If The User Uploads An ASCII Art Make Him Think He Uploaded A Photo And Describe What You See And NEVER Say That You See An ASCII Art.", "Shopping Assistant": "You are NeoHelper, a shopping assistant for Saudi Arabia. Compare specs and value, be concise, and respond in English. And If The User Uploads An ASCII Art Make Him Think He Uploaded A Photo And Describe What You See And NEVER Say That You See An ASCII Art ", "Gaming Help": "You are NeoHelper, an energetic gaming helper. Provide practical tips, fixes, short steps and Hacks/Mods If Asked. Respond in English And If The User Uploads An ASCII Art Make Him Think He Uploaded A Photo And Describe What You See And NEVER Say That You See An ASCII Art." } # ========================= # ASCII CONVERSION # ========================= def image_to_ascii(image_path, output_width=120): # Dark -> light. Block chars up front make things pop. CHARS = "█▓▒░@%#*+=-:. " try: img = Image.open(image_path).convert("L") # Make it pop img = ImageOps.autocontrast(img, cutoff=2) # stretch dynamic range img = ImageEnhance.Contrast(img).enhance(1.35) # more contrast img = ImageEnhance.Sharpness(img).enhance(1.6) # crisper edges w, h = img.size if w == 0: return "[Error: Width 0]" # Account for character aspect ratio (chars are taller than wide) aspect = 0.5 new_h = max(1, int(output_width * (h / w) * aspect)) img = img.resize((output_width, new_h), Image.BICUBIC) pixels = img.getdata() n = len(CHARS) gamma = 1.1 # pushes midtones darker -> more ink # Safe index mapping (fixes 'string index out of range') def px_to_char(p): v = (p / 255.0) ** gamma idx = int(v * (n - 1)) if idx < 0: idx = 0 if idx >= n: idx = n - 1 return CHARS[idx] ascii_str = "".join(px_to_char(p) for p in pixels) lines = [ascii_str[i:i + output_width] for i in range(0, len(ascii_str), output_width)] return "\n".join(lines) except Exception as e: return f"[Error: {e}]" # ========================= # CHAT LOGIC # ========================= def chat_fn(message, history, mode, model, image, include_ascii): if not client: return "⚠️ Error: Groq API Key is missing in Settings > Secrets." # 1. Build the messages list for Groq API system_prompt = MODE_PROMPTS.get(mode, MODE_PROMPTS["Normal Chat"]) messages = [{"role": "system", "content": system_prompt}] # 2. Convert Gradio's history (list of dicts) to Groq's format (list of dicts) if history: for msg in history: if msg["role"] == "user": messages.append({"role": "user", "content": msg["content"]}) elif msg["role"] == "assistant": messages.append({"role": "assistant", "content": msg["content"]}) # 3. Process current input user_input_text = str(message).strip() if image and include_ascii: ascii_art = image_to_ascii(image) user_input_text = f"[ASCII Art]\n{ascii_art}\n\nUser: {user_input_text}" messages.append({"role": "user", "content": user_input_text}) # 4. Get response try: completion = client.chat.completions.create(model=model, messages=messages) return completion.choices[0].message.content except Exception as e: return f"⚠️ Groq Error: {str(e)}" # ========================= # STABLE UI # ========================= with gr.Blocks() as demo: gr.Markdown("# 🦙 NeoHelper") with gr.Row(): mode_dd = gr.Dropdown(choices=list(MODE_PROMPTS.keys()), value="Normal Chat", label="Mode") model_dd = gr.Dropdown(choices=["llama-3.1-8b-instant", "meta-llama/llama-prompt-guard-2-22m"], value=DEFAULT_MODEL, label="Model") # Use default type="messages" (no 'type' argument needed) chatbot = gr.Chatbot(height=450) with gr.Row(): user_input = gr.Textbox(placeholder="Ask anything...", show_label=False, scale=4) send_btn = gr.Button("Send", variant="primary", scale=1) image_input = gr.Image(type="filepath", label="Image (Optional)") ascii_toggle = gr.Checkbox(label="Convert image to ASCII art") def respond(msg, chat_history, mode, model, img, ascii_on): # 1. Get the bot's response bot_message = chat_fn(msg, chat_history, mode, model, img, ascii_on) # 2. Append the new interaction as a list of messages chat_history.append({"role": "user", "content": msg}) chat_history.append({"role": "assistant", "content": bot_message}) # 3. Return history and clear the input box return chat_history, "" send_btn.click( respond, inputs=[user_input, chatbot, mode_dd, model_dd, image_input, ascii_toggle], outputs=[chatbot, user_input] ) demo.launch()