henrywch2huggingface Claude Fable 5 commited on
Commit
65be679
·
1 Parent(s): 7b97603

Single-stage layout: one image/video block, chat/raw response view, fixed equal heights.

Browse files

- One media block (gr.File + scaled-to-fit previews) replaces the two tabs;
examples merged into one table (3 image rows + 1 video row).
- Right response block mirrors the live demo: segmented Chat/{ } Raw toggle,
chatbot + raw request/response JSON, pinned to a fixed 680px height that
matches the left column exactly; long text scrolls inside with transparent
hover-only scrollbars.
- analyze_image / analyze_video stay exposed as API/MCP tools; a MOCK mode
(MOSS_DEMO_MOCK=1) allows local UI work without torch/spaces.
- sdk bumped to gradio 6.15.1 (same as the realtime Space).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files changed (2) hide show
  1. README.md +1 -1
  2. app.py +286 -101
README.md CHANGED
@@ -4,7 +4,7 @@ emoji: 🧠
4
  colorFrom: gray
5
  colorTo: blue
6
  sdk: gradio
7
- sdk_version: 5.50.0
8
  app_file: app.py
9
  short_description: Image and video understanding with MOSS-VL multimodal model
10
  python_version: "3.12"
 
4
  colorFrom: gray
5
  colorTo: blue
6
  sdk: gradio
7
+ sdk_version: 6.15.1
8
  app_file: app.py
9
  short_description: Image and video understanding with MOSS-VL multimodal model
10
  python_version: "3.12"
app.py CHANGED
@@ -1,30 +1,56 @@
 
 
 
 
 
 
 
 
 
 
1
  import os
 
2
 
3
  os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
4
 
5
- import spaces
6
- import torch
7
  import gradio as gr
8
- from transformers import AutoModelForCausalLM, AutoProcessor
9
 
10
  MODEL_ID = "OpenMOSS-Team/MOSS-VL-Instruct-0708"
11
 
12
- processor = AutoProcessor.from_pretrained(
13
- MODEL_ID,
14
- trust_remote_code=True,
15
- frame_extract_num_threads=1,
16
- )
17
- model = AutoModelForCausalLM.from_pretrained(
18
- MODEL_ID,
19
- trust_remote_code=True,
20
- torch_dtype=torch.bfloat16,
21
- attn_implementation="sdpa",
22
- ).to("cuda")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
 
25
  @spaces.GPU(duration=120)
26
  def analyze_image(
27
- image,
28
  prompt: str,
29
  max_new_tokens: int,
30
  temperature: float,
@@ -34,7 +60,7 @@ def analyze_image(
34
  """Analyze an image with a text prompt.
35
 
36
  Args:
37
- image: The input image to analyze.
38
  prompt: Text instruction or question about the image.
39
  max_new_tokens: Maximum number of tokens to generate.
40
  temperature: Sampling temperature (1.0 = greedy when do_sample=False).
@@ -45,8 +71,14 @@ def analyze_image(
45
  return "Please upload an image."
46
  if not prompt.strip():
47
  prompt = "Describe this image in detail."
 
 
 
 
 
 
48
 
49
- result = model.offline_image_generate(
50
  processor,
51
  prompt=prompt,
52
  image=image,
@@ -55,12 +87,11 @@ def analyze_image(
55
  top_p=float(top_p),
56
  do_sample=do_sample,
57
  )
58
- return result
59
 
60
 
61
  @spaces.GPU(duration=180)
62
  def analyze_video(
63
- video,
64
  prompt: str,
65
  max_new_tokens: int,
66
  temperature: float,
@@ -72,7 +103,7 @@ def analyze_video(
72
  """Analyze a video with a text prompt.
73
 
74
  Args:
75
- video: The input video file to analyze.
76
  prompt: Text instruction or question about the video.
77
  max_new_tokens: Maximum number of tokens to generate.
78
  temperature: Sampling temperature (1.0 = greedy when do_sample=False).
@@ -85,8 +116,14 @@ def analyze_video(
85
  return "Please upload a video."
86
  if not prompt.strip():
87
  prompt = "Describe this video in detail."
 
 
 
 
 
 
88
 
89
- result = model.offline_video_generate(
90
  processor,
91
  prompt=prompt,
92
  video=video,
@@ -97,105 +134,253 @@ def analyze_video(
97
  max_frames=int(max_frames),
98
  do_sample=do_sample,
99
  )
100
- return result
101
 
102
 
103
- CSS = """
104
- #col-container { max-width: 1100px; margin: 0 auto; }
105
- .dark .gradio-container { color: var(--body-text-color); }
106
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
- with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo:
109
- gr.Markdown(
110
- """
111
- # MOSS-VL-Instruct-0708
112
- An 11B multimodal vision-language model for image and video understanding.
113
- Upload an image or video and ask any question about it.
114
- [Model Card](https://huggingface.co/OpenMOSS-Team/MOSS-VL-Instruct-0708) |
115
- [GitHub](https://github.com/OpenMOSS/MOSS-VL)
116
- """
117
  )
118
 
119
- with gr.Tab("Image Understanding"):
120
- with gr.Row():
121
- with gr.Column(scale=1):
122
- img_input = gr.Image(type="filepath", label="Input Image")
123
- img_prompt = gr.Textbox(
124
- label="Prompt",
125
- placeholder="Describe this image in detail.",
126
- value="Describe this image in detail.",
127
- lines=2,
128
- )
129
- img_btn = gr.Button("Analyze Image", variant="primary")
130
- with gr.Column(scale=1):
131
- img_output = gr.Textbox(label="Response", lines=12, show_copy_button=True)
132
 
133
- with gr.Accordion("Advanced Settings", open=False):
134
- with gr.Row():
135
- img_max_tokens = gr.Slider(64, 2048, value=512, step=64, label="Max New Tokens")
136
- img_temperature = gr.Slider(0.1, 2.0, value=1.0, step=0.1, label="Temperature")
137
- img_top_p = gr.Slider(0.1, 1.0, value=1.0, step=0.05, label="Top-p")
138
- img_do_sample = gr.Checkbox(label="Do Sample", value=False)
 
 
 
 
 
139
 
140
- gr.Examples(
141
- examples=[
142
- ["example_bill.png", "Extract the store name, waiter name, bill number, number of people, items purchased with their quantities and amounts, total amount, and print time from this receipt. Output in JSON format.", 512, 1.0, 1.0, False],
143
- ["astronaut.jpg", "Describe this image in detail.", 256, 1.0, 1.0, False],
144
- ["bird_kingfisher.jpg", "What species of bird is this? Describe its appearance and habitat.", 256, 1.0, 1.0, False],
145
- ],
146
- inputs=[img_input, img_prompt, img_max_tokens, img_temperature, img_top_p, img_do_sample],
147
- outputs=img_output,
148
- fn=analyze_image,
149
- cache_examples=True,
150
- cache_mode="lazy",
151
- )
152
 
153
- img_btn.click(
154
- fn=analyze_image,
155
- inputs=[img_input, img_prompt, img_max_tokens, img_temperature, img_top_p, img_do_sample],
156
- outputs=img_output,
157
- api_name="analyze_image",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  )
159
 
160
- with gr.Tab("Video Understanding"):
161
- with gr.Row():
162
- with gr.Column(scale=1):
163
- vid_input = gr.Video(label="Input Video")
164
- vid_prompt = gr.Textbox(
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  label="Prompt",
166
- placeholder="Describe this video in detail.",
167
- value="Describe this video in detail.",
168
- lines=2,
169
  )
170
- vid_btn = gr.Button("Analyze Video", variant="primary")
171
- with gr.Column(scale=1):
172
- vid_output = gr.Textbox(label="Response", lines=12, show_copy_button=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
  with gr.Accordion("Advanced Settings", open=False):
175
  with gr.Row():
176
- vid_max_tokens = gr.Slider(64, 2048, value=512, step=64, label="Max New Tokens")
177
- vid_temperature = gr.Slider(0.1, 2.0, value=1.0, step=0.1, label="Temperature")
178
- vid_top_p = gr.Slider(0.1, 1.0, value=1.0, step=0.05, label="Top-p")
179
- vid_fps = gr.Slider(0.5, 4.0, value=1.0, step=0.5, label="Video FPS")
180
- vid_max_frames = gr.Slider(8, 256, value=64, step=8, label="Max Frames")
181
- vid_do_sample = gr.Checkbox(label="Do Sample", value=False)
 
182
 
183
  gr.Examples(
184
  examples=[
185
- ["example_video.mp4", "Describe what happens in this video.", 512, 1.0, 1.0, 1.0, 64, False],
 
 
 
186
  ],
187
- inputs=[vid_input, vid_prompt, vid_max_tokens, vid_temperature, vid_top_p, vid_fps, vid_max_frames, vid_do_sample],
188
- outputs=vid_output,
189
- fn=analyze_video,
190
- cache_examples=True,
191
- cache_mode="lazy",
192
  )
193
 
194
- vid_btn.click(
195
- fn=analyze_video,
196
- inputs=[vid_input, vid_prompt, vid_max_tokens, vid_temperature, vid_top_p, vid_fps, vid_max_frames, vid_do_sample],
197
- outputs=vid_output,
198
- api_name="analyze_video",
199
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
- demo.launch(mcp_server=True)
 
 
 
 
1
+ """MOSS-VL-Instruct Space — single-stage media QA demo.
2
+
3
+ ONE media stage (image OR video upload) + prompt on the left; the response log
4
+ with a chat/raw toggle on the right — mirroring the MOSS-VL-Realtime live demo
5
+ styling. analyze_image / analyze_video stay exposed as API/MCP tools.
6
+
7
+ MOCK mode (MOSS_DEMO_MOCK=1): no torch / spaces imports; canned responses for
8
+ local UI work.
9
+ """
10
+
11
  import os
12
+ import time
13
 
14
  os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
15
 
16
+ MOCK = os.getenv("MOSS_DEMO_MOCK") == "1"
17
+
18
  import gradio as gr
 
19
 
20
  MODEL_ID = "OpenMOSS-Team/MOSS-VL-Instruct-0708"
21
 
22
+ if not MOCK:
23
+ import spaces
24
+ import torch
25
+ from transformers import AutoModelForCausalLM, AutoProcessor
26
+
27
+ processor = AutoProcessor.from_pretrained(
28
+ MODEL_ID,
29
+ trust_remote_code=True,
30
+ frame_extract_num_threads=1,
31
+ )
32
+ model = AutoModelForCausalLM.from_pretrained(
33
+ MODEL_ID,
34
+ trust_remote_code=True,
35
+ torch_dtype=torch.bfloat16,
36
+ attn_implementation="sdpa",
37
+ ).to("cuda")
38
+ else:
39
+ class _MockSpaces:
40
+ """Effect-free stand-in for the spaces module in MOCK mode."""
41
+
42
+ @staticmethod
43
+ def GPU(*args, **kwargs):
44
+ if args and callable(args[0]):
45
+ return args[0]
46
+ return lambda fn: fn
47
+
48
+ spaces = _MockSpaces()
49
 
50
 
51
  @spaces.GPU(duration=120)
52
  def analyze_image(
53
+ image: str,
54
  prompt: str,
55
  max_new_tokens: int,
56
  temperature: float,
 
60
  """Analyze an image with a text prompt.
61
 
62
  Args:
63
+ image: Path of the input image to analyze.
64
  prompt: Text instruction or question about the image.
65
  max_new_tokens: Maximum number of tokens to generate.
66
  temperature: Sampling temperature (1.0 = greedy when do_sample=False).
 
71
  return "Please upload an image."
72
  if not prompt.strip():
73
  prompt = "Describe this image in detail."
74
+ if MOCK:
75
+ time.sleep(1.2)
76
+ return (
77
+ f"[MOCK] Scripted image analysis for {os.path.basename(image)} — prompt: {prompt!r}.\n\n"
78
+ + "This is a long mock paragraph to exercise scrolling in the response view. " * 12
79
+ )
80
 
81
+ return model.offline_image_generate(
82
  processor,
83
  prompt=prompt,
84
  image=image,
 
87
  top_p=float(top_p),
88
  do_sample=do_sample,
89
  )
 
90
 
91
 
92
  @spaces.GPU(duration=180)
93
  def analyze_video(
94
+ video: str,
95
  prompt: str,
96
  max_new_tokens: int,
97
  temperature: float,
 
103
  """Analyze a video with a text prompt.
104
 
105
  Args:
106
+ video: Path of the input video file to analyze.
107
  prompt: Text instruction or question about the video.
108
  max_new_tokens: Maximum number of tokens to generate.
109
  temperature: Sampling temperature (1.0 = greedy when do_sample=False).
 
116
  return "Please upload a video."
117
  if not prompt.strip():
118
  prompt = "Describe this video in detail."
119
+ if MOCK:
120
+ time.sleep(1.5)
121
+ return (
122
+ f"[MOCK] Scripted video analysis for {os.path.basename(video)} — prompt: {prompt!r}.\n\n"
123
+ + "This is a long mock paragraph to exercise scrolling in the response view. " * 12
124
+ )
125
 
126
+ return model.offline_video_generate(
127
  processor,
128
  prompt=prompt,
129
  video=video,
 
134
  max_frames=int(max_frames),
135
  do_sample=do_sample,
136
  )
 
137
 
138
 
139
+ # --- UI glue ---
140
+
141
+ IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif"}
142
+
143
+
144
+ def classify_media(path):
145
+ return "image" if os.path.splitext(path)[1].lower() in IMAGE_EXTS else "video"
146
+
147
+
148
+ def _file_path(value):
149
+ """Normalize a gr.File value (str | list | dict | None) to a path or None."""
150
+ if isinstance(value, list):
151
+ value = value[0] if value else None
152
+ if isinstance(value, dict):
153
+ value = value.get("path") or value.get("name")
154
+ return value or None
155
+
156
+
157
+ def run_analyze(media, prompt_text, max_new_tokens, temperature, top_p, do_sample, video_fps, max_frames):
158
+ path = _file_path(media)
159
+ if not path:
160
+ gr.Warning("Upload an image or video first · 请先上传图片或视频")
161
+ yield gr.skip(), gr.skip()
162
+ return
163
+ kind = classify_media(path)
164
+ prompt_text = (prompt_text or "").strip() or f"Describe this {kind} in detail."
165
+
166
+ history = [
167
+ {"role": "user", "content": {"path": path}},
168
+ {"role": "user", "content": prompt_text},
169
+ {"role": "assistant", "content": "⏳ Analyzing · 分析中…"},
170
+ ]
171
+ params = {
172
+ "max_new_tokens": int(max_new_tokens),
173
+ "temperature": float(temperature),
174
+ "top_p": float(top_p),
175
+ "do_sample": bool(do_sample),
176
+ }
177
+ if kind == "video":
178
+ params.update(video_fps=float(video_fps), max_frames=int(max_frames))
179
+ events = [{"event": "request", "media": os.path.basename(path), "kind": kind, "prompt": prompt_text, "params": params}]
180
+ yield history, events
181
+
182
+ t0 = time.monotonic()
183
+ try:
184
+ if kind == "image":
185
+ result = analyze_image(path, prompt_text, max_new_tokens, temperature, top_p, do_sample)
186
+ else:
187
+ result = analyze_video(path, prompt_text, max_new_tokens, temperature, top_p, video_fps, max_frames, do_sample)
188
+ except Exception as exc:
189
+ history[-1] = {"role": "assistant", "content": f"⚠️ {type(exc).__name__}: {exc}"}
190
+ events.append({"event": "error", "message": f"{type(exc).__name__}: {exc}"})
191
+ yield history, events
192
+ return
193
+ history[-1] = {"role": "assistant", "content": result}
194
+ events.append({"event": "response", "elapsed_s": round(time.monotonic() - t0, 2), "text": result})
195
+ yield history, events
196
+
197
 
198
+ def on_media_change(value):
199
+ path = _file_path(value)
200
+ if not path:
201
+ return gr.update(visible=True), gr.update(visible=False, value=None), gr.update(visible=False, value=None)
202
+ kind = classify_media(path)
203
+ return (
204
+ gr.update(visible=False),
205
+ gr.update(visible=kind == "video", value=path if kind == "video" else None),
206
+ gr.update(visible=kind == "image", value=path if kind == "image" else None),
207
  )
208
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
 
210
+ def clear_media():
211
+ return (
212
+ gr.update(visible=True, value=None),
213
+ gr.update(visible=False, value=None),
214
+ gr.update(visible=False, value=None),
215
+ )
216
+
217
+
218
+ def toggle_view(choice):
219
+ chat = choice == "chat"
220
+ return gr.update(visible=chat), gr.update(visible=not chat)
221
 
 
 
 
 
 
 
 
 
 
 
 
 
222
 
223
+ CSS = """
224
+ #col-container { max-width: 1250px; margin: 0 auto; --console-h: 46px; --stage-h: 680px; }
225
+ /* FIXED overall height: the right response block is pinned to --stage-h and its
226
+ content scrolls inside; the left column stretches to match, with the media
227
+ widget absorbing the spare height above the prompt + action row */
228
+ #message-stage {
229
+ flex-grow: 0 !important; height: var(--stage-h); min-height: var(--stage-h); max-height: var(--stage-h);
230
+ display: flex; flex-direction: column;
231
+ }
232
+ #media-stage { flex-grow: 1 !important; display: flex; flex-direction: column; min-height: 0; }
233
+ #media-stage > *, #media-stage .styler,
234
+ #message-stage > *, #message-stage .styler {
235
+ flex-grow: 1 !important; display: flex; flex-direction: column; min-height: 0;
236
+ }
237
+ #stage-file, #preview-video, #preview-image { flex-grow: 1 !important; min-height: 0; }
238
+ /* chat/raw fill the fixed stage and scroll internally — never grow with content */
239
+ #chatbot, #raw-json {
240
+ flex-grow: 1 !important; min-height: 0 !important; height: auto !important; max-height: none !important;
241
+ }
242
+ #stage-file .boundedheight, #preview-video .boundedheight, #preview-image .boundedheight,
243
+ #stage-file [data-testid="file"], #preview-video video, #preview-image img {
244
+ height: 100% !important; max-height: none !important;
245
+ }
246
+ /* media scales to fit its fixed frame */
247
+ #preview-image img, #preview-video video { object-fit: contain; width: 100%; }
248
+ /* segmented chat/raw toggle: two half-width buttons, centered text, no radio
249
+ dot — black = off, accent yellow = on (same as the live demo) */
250
+ #view-toggle { flex-grow: 0 !important; flex-shrink: 0 !important; min-height: fit-content; }
251
+ #view-toggle .wrap { display: flex; flex-direction: row; gap: 0; width: 100%; }
252
+ #view-toggle label {
253
+ flex: 1 1 50%; justify-content: center; text-align: center; margin: 0;
254
+ padding: var(--spacing-sm) 0; cursor: pointer; border-radius: 0;
255
+ background: #141414; color: #9aa0a6; border: 1px solid var(--border-color-primary);
256
+ transition: background 0.15s, color 0.15s;
257
+ }
258
+ #view-toggle label:first-child { border-radius: var(--radius-md) 0 0 var(--radius-md); }
259
+ #view-toggle label:last-child { border-radius: 0 var(--radius-md) var(--radius-md) 0; }
260
+ #view-toggle label.selected { background: var(--color-accent); color: #111; font-weight: 600; }
261
+ #view-toggle input[type="radio"] { display: none; }
262
+ /* prompt + action row stay natural-sized at the column bottom — including the
263
+ .form element gradio wraps around the textbox, which is flex-grow:1 by
264
+ default and would otherwise eat the column's spare height */
265
+ #prompt-block, #action-row { flex-grow: 0 !important; }
266
+ #col-container .form:has(#prompt-block) { flex-grow: 0 !important; }
267
+ #action-row { margin-top: auto; }
268
+ #action-row button { height: var(--console-h); min-height: var(--console-h); white-space: nowrap; }
269
+ /* scrollbars: invisible until the pointer is over the thin scrollbar strip;
270
+ no track rail, no arrow buttons. (Firefox has no thumb-hover selector, so
271
+ it falls back to a thin transparent bar revealed on container hover.) */
272
+ @supports not selector(::-webkit-scrollbar) {
273
+ * { scrollbar-width: thin; scrollbar-color: transparent transparent; }
274
+ *:hover { scrollbar-color: rgba(128, 128, 128, 0.45) transparent; }
275
+ }
276
+ ::-webkit-scrollbar { width: 8px; height: 8px; background: transparent !important; }
277
+ ::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent !important; }
278
+ ::-webkit-scrollbar-button { display: none !important; width: 0 !important; height: 0 !important; }
279
+ ::-webkit-scrollbar-thumb { background: transparent !important; border-radius: 4px; }
280
+ ::-webkit-scrollbar-thumb:hover, ::-webkit-scrollbar-thumb:active { background: rgba(128, 128, 128, 0.55) !important; }
281
+ """
282
+
283
+ with gr.Blocks(title="MOSS-VL-Instruct Demo") as demo:
284
+ with gr.Column(elem_id="col-container"):
285
+ gr.Markdown(
286
+ "# MOSS-VL-Instruct-0708\n\n"
287
+ "An 11B multimodal vision-language model for image and video understanding. "
288
+ "Upload an image or video and ask any question about it.\n\n"
289
+ "[Model Card](https://huggingface.co/OpenMOSS-Team/MOSS-VL-Instruct-0708) | "
290
+ "[GitHub](https://github.com/OpenMOSS/MOSS-VL)"
291
+ + ("\n\n`MOCK mode — scripted model responses`" if MOCK else "")
292
  )
293
 
294
+ with gr.Row(equal_height=True):
295
+ with gr.Column(scale=5):
296
+ with gr.Group(elem_id="media-stage"):
297
+ media_file = gr.File(
298
+ file_types=["image", "video"],
299
+ label="Image or video · 图片或视频",
300
+ height=430,
301
+ elem_id="stage-file",
302
+ )
303
+ preview_video = gr.Video(
304
+ visible=False, interactive=False, height=430, show_label=False,
305
+ autoplay=False, elem_id="preview-video",
306
+ )
307
+ preview_image = gr.Image(
308
+ visible=False, interactive=False, height=430, show_label=False,
309
+ elem_id="preview-image",
310
+ )
311
+ prompt = gr.Textbox(
312
  label="Prompt",
313
+ lines=3,
314
+ placeholder="Describe this image/video in detail.",
315
+ elem_id="prompt-block",
316
  )
317
+ with gr.Row(elem_id="action-row"):
318
+ clear_btn = gr.Button("Clear media · 清除媒体", variant="secondary")
319
+ analyze_btn = gr.Button("▶ Analyze · 分析", variant="primary")
320
+
321
+ with gr.Column(scale=6):
322
+ with gr.Group(elem_id="message-stage"):
323
+ view_toggle = gr.Radio(
324
+ [("💬 Chat", "chat"), ("{ } Raw", "raw")],
325
+ value="chat",
326
+ show_label=False,
327
+ container=False,
328
+ elem_id="view-toggle",
329
+ )
330
+ chatbot = gr.Chatbot(
331
+ height=560,
332
+ show_label=False,
333
+ buttons=["copy"],
334
+ autoscroll=True,
335
+ elem_id="chatbot",
336
+ )
337
+ raw_json = gr.JSON(
338
+ value=[], show_label=False, visible=False, height=560, elem_id="raw-json"
339
+ )
340
 
341
  with gr.Accordion("Advanced Settings", open=False):
342
  with gr.Row():
343
+ max_new_tokens = gr.Slider(64, 2048, value=512, step=64, label="Max New Tokens")
344
+ temperature = gr.Slider(0.1, 2.0, value=1.0, step=0.1, label="Temperature")
345
+ top_p = gr.Slider(0.1, 1.0, value=1.0, step=0.05, label="Top-p")
346
+ do_sample = gr.Checkbox(label="Do Sample", value=False)
347
+ with gr.Row():
348
+ video_fps = gr.Slider(0.5, 4.0, value=1.0, step=0.5, label="Video FPS (video only)")
349
+ max_frames = gr.Slider(8, 256, value=64, step=8, label="Max Frames (video only)")
350
 
351
  gr.Examples(
352
  examples=[
353
+ ["example_bill.png", "Extract the store name, waiter name, bill number, number of people, items purchased with their quantities and amounts, total amount, and print time from this receipt. Output in JSON format."],
354
+ ["astronaut.jpg", "Describe this image in detail."],
355
+ ["bird_kingfisher.jpg", "What species of bird is this? Describe its appearance and habitat."],
356
+ ["example_video.mp4", "Describe what happens in this video."],
357
  ],
358
+ inputs=[media_file, prompt],
359
+ label="Examples",
 
 
 
360
  )
361
 
362
+ media_file.change(
363
+ on_media_change, [media_file],
364
+ [media_file, preview_video, preview_image], api_name=False,
365
+ )
366
+ clear_btn.click(
367
+ clear_media, None,
368
+ [media_file, preview_video, preview_image], api_name=False,
369
+ )
370
+ view_toggle.change(toggle_view, [view_toggle], [chatbot, raw_json], api_name=False)
371
+ analyze_btn.click(
372
+ run_analyze,
373
+ inputs=[media_file, prompt, max_new_tokens, temperature, top_p, do_sample, video_fps, max_frames],
374
+ outputs=[chatbot, raw_json],
375
+ api_name=False,
376
+ show_progress="hidden",
377
+ )
378
+
379
+ gr.api(analyze_image, api_name="analyze_image")
380
+ gr.api(analyze_video, api_name="analyze_video")
381
+
382
 
383
+ if __name__ == "__main__":
384
+ # gradio 6.x: theme/css/mcp_server are launch() parameters
385
+ demo.queue(max_size=32)
386
+ demo.launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)