""" ui/app.py โ€” Gradio layout. This file is pure layout โ€” no inference logic lives here. All intelligence is in core/api_client.py. """ import gradio as gr from core import stream_description from ui.components import ( image_upload, prompt_box, model_selector, generation_settings, api_key_field, prompt_examples, output_box, info_panel, ) CSS = """ #title { text-align: center; } #submit { background: #6366f1; color: white; } #submit:hover { background: #4f46e5; } #model-hint { font-size: 0.85em; opacity: 0.75; } footer { display: none !important; } """ def build_ui() -> gr.Blocks: with gr.Blocks( title="MiniCPM-V ยท API Image Describer", theme=gr.themes.Soft(), css=CSS, ) as demo: gr.Markdown( "# ๐Ÿ” MiniCPM-V 4.6 ยท Image Description via API\n" "**No downloads. No GPU needed.** Calls OpenBMB's hosted API โ€” " "results stream back in real time.\n\n" "> Models: `MiniCPM-V-4.6-Instruct` & `MiniCPM-V-4.6-Thinking` ยท " "1.3 B params each ยท well under the 32 B cap ยท Apache-2.0", elem_id="title", ) with gr.Row(): # โ”€โ”€ Left column โ€” inputs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ with gr.Column(scale=1): image = image_upload() prompt = prompt_box() model = model_selector() gr.Markdown( "**Instruct** โ€” direct, concise answer. \n" "**Thinking** โ€” reasons step-by-step first (slower, more thorough).", elem_id="model-hint", ) max_tokens, temperature = generation_settings() api_key = api_key_field() submit = gr.Button( "โ–ถ Describe Image", variant="primary", elem_id="submit" ) prompt_examples(prompt) # โ”€โ”€ Right column โ€” output โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ with gr.Column(scale=1): output = output_box() info_panel() # โ”€โ”€ Event wiring โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ inputs = [image, prompt, model, max_tokens, temperature, api_key] outputs = [output] submit.click(fn=stream_description, inputs=inputs, outputs=outputs) prompt.submit(fn=stream_description, inputs=inputs, outputs=outputs) return demo