sdafd commited on
Commit
c23b561
·
verified ·
1 Parent(s): e59f7e5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +179 -597
app.py CHANGED
@@ -1,621 +1,203 @@
1
  import gradio as gr
2
- import google.generativeai as genai
3
- import os
4
- import mimetypes
5
  from PIL import Image
6
- import io
7
- import magic # python-magic library
8
- from dotenv import load_dotenv
9
-
10
- # (Optional) Load environment variables for local testing if you have a .env file
11
- # load_dotenv()
12
- # TEST_API_KEY = os.getenv("GEMINI_API_KEY") # Use this ONLY for your local testing
13
-
14
- # --- Constants ---
15
- # Define available models (expand this list as needed)
16
- # Include models supporting different modalities and versions
17
- AVAILABLE_MODELS = [
18
- "gemini-1.5-flash-latest",
19
- "gemini-1.5-pro-latest",
20
- "gemini-1.0-pro",
21
- "gemini-pro-vision", # Example vision model
22
- # "gemini-experimental", # Add other relevant models
23
  ]
24
 
25
- # Define parameters for each model (Example structure)
26
- # This needs meticulous mapping based on official Gemini documentation
27
- MODEL_PARAMS = {
28
- "gemini-1.5-flash-latest": {
29
- "temperature": {"type": "slider", "min": 0.0, "max": 2.0, "step": 0.1, "default": 1.0},
30
- "top_p": {"type": "slider", "min": 0.0, "max": 1.0, "step": 0.01, "default": 0.95},
31
- "top_k": {"type": "slider", "min": 1, "max": 100, "step": 1, "default": 40},
32
- "max_output_tokens": {"type": "number", "min": 1, "step": 1, "default": 8192},
33
- "stop_sequences": {"type": "textbox", "lines": 1, "placeholder": "e.g., END,STOP", "default": ""},
34
- # Safety settings could be added here too (as dropdowns or checkboxes)
35
- },
36
- "gemini-1.5-pro-latest": {
37
- # Similar params, possibly different defaults or ranges
38
- "temperature": {"type": "slider", "min": 0.0, "max": 2.0, "step": 0.1, "default": 1.0},
39
- "top_p": {"type": "slider", "min": 0.0, "max": 1.0, "step": 0.01, "default": 0.95},
40
- "top_k": {"type": "slider", "min": 1, "max": 100, "step": 1, "default": 40},
41
- "max_output_tokens": {"type": "number", "min": 1, "step": 1, "default": 8192},
42
- "stop_sequences": {"type": "textbox", "lines": 1, "placeholder": "e.g., END,STOP", "default": ""},
43
- },
44
- "gemini-1.0-pro": {
45
- # Params for older model might differ slightly
46
- "temperature": {"type": "slider", "min": 0.0, "max": 1.0, "step": 0.1, "default": 0.9}, # Different max/default maybe
47
- "top_p": {"type": "slider", "min": 0.0, "max": 1.0, "step": 0.01, "default": 0.95},
48
- "top_k": {"type": "slider", "min": 1, "max": 100, "step": 1, "default": 40},
49
- "max_output_tokens": {"type": "number", "min": 1, "step": 1, "default": 2048}, # Different default
50
- "stop_sequences": {"type": "textbox", "lines": 1, "placeholder": "e.g., END,STOP", "default": ""},
51
- },
52
- "gemini-pro-vision": {
53
- # Vision models might have fewer text-generation params or different ones
54
- "temperature": {"type": "slider", "min": 0.0, "max": 1.0, "step": 0.1, "default": 0.4},
55
- "top_p": {"type": "slider", "min": 0.0, "max": 1.0, "step": 0.01, "default": 0.95},
56
- "top_k": {"type": "slider", "min": 1, "max": 100, "step": 1, "default": 32},
57
- "max_output_tokens": {"type": "number", "min": 1, "step": 1, "default": 2048},
58
- # No stop sequences typically needed here? Check docs.
59
- }
60
  }
61
 
62
- # --- Helper Functions ---
63
-
64
- def get_mime_type(file_path):
65
- """Get MIME type using python-magic for reliability."""
66
- try:
67
- mime = magic.Magic(mime=True)
68
- return mime.from_file(file_path)
69
- except Exception:
70
- # Fallback to mimetypes if magic fails
71
- return mimetypes.guess_type(file_path)[0]
72
-
73
- def convert_file_to_text(file_obj):
74
- """
75
- Attempts to convert various file types to text.
76
- Returns (text_content, original_filename) or (None, original_filename) if conversion fails.
77
- """
78
- file_path = file_obj.name
79
- filename = os.path.basename(file_path)
80
- mime_type = get_mime_type(file_path)
81
- print(f"Processing file: {filename}, MIME type: {mime_type}") # Debugging
82
-
83
- try:
84
- if mime_type is None:
85
- # If MIME type is unknown, try reading as text
86
- print(f"Warning: Unknown MIME type for {filename}. Attempting to read as text.")
87
- with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
88
- return f.read(), filename
89
- elif mime_type.startswith("text/"):
90
- with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
91
- return f.read(), filename
92
- elif mime_type == "application/pdf":
93
- # Placeholder for PDF conversion (requires pypdf or similar)
94
- print(f"PDF conversion not implemented yet for {filename}.")
95
- # from pypdf import PdfReader # Example
96
- # reader = PdfReader(file_path)
97
- # text = ""
98
- # for page in reader.pages:
99
- # text += page.extract_text() + "\n"
100
- # return text, filename
101
- return f"[Unsupported PDF: {filename} - Conversion not implemented]", filename # Temporary
102
- elif mime_type in ["application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"]:
103
- # Placeholder for DOCX conversion (requires python-docx or similar)
104
- print(f"DOCX conversion not implemented yet for {filename}.")
105
- # import docx # Example
106
- # doc = docx.Document(file_path)
107
- # text = "\n".join([para.text for para in doc.paragraphs])
108
- # return text, filename
109
- return f"[Unsupported Word Doc: {filename} - Conversion not implemented]", filename # Temporary
110
- else:
111
- # For other unsupported types, return a marker
112
- print(f"Unsupported file type: {mime_type} for {filename}. Skipping content.")
113
- return f"[Unsupported file type: {mime_type} - {filename}]", filename
114
-
115
- except Exception as e:
116
- print(f"Error converting file {filename}: {e}")
117
- return f"[Error converting file: {filename}]", filename
118
-
119
- def prepare_gemini_input(prompt, files):
120
- """Prepares the input list for Gemini, handling text and images."""
121
- gemini_parts = []
122
- if prompt:
123
- gemini_parts.append(prompt)
124
-
125
- if files:
126
- for file_obj in files:
127
- file_path = file_obj.name
128
- mime_type = get_mime_type(file_path)
129
- filename = os.path.basename(file_path)
130
-
131
- print(f"Preparing file for Gemini: {filename}, MIME: {mime_type}")
132
-
133
- if mime_type and mime_type.startswith("image/"):
134
- try:
135
- img = Image.open(file_path)
136
- # Convert image to bytes (e.g., PNG or JPEG)
137
- # Gemini API directly accepts PIL Images usually
138
- gemini_parts.append(img)
139
- print(f"Added image: {filename}")
140
- except Exception as e:
141
- print(f"Error processing image {filename}: {e}")
142
- gemini_parts.append(f"[Error processing image: {filename}]")
143
- elif mime_type and mime_type.startswith("video/"): # Gemini 1.5 Pro can handle video
144
- # Upload file via File API first (more complex, needs google.ai.generativelanguage)
145
- # For simplicity here, we'll just note it's a video
146
- # or provide a basic text representation if conversion isn't implemented
147
- print(f"Video file detected: {filename}. Full video processing requires File API.")
148
- gemini_parts.append(f"[Video file: {filename} - Requires File API upload]")
149
- # Placeholder: Add text conversion if feasible for your use case
150
- # text_content, _ = convert_file_to_text(file_obj)
151
- # if text_content:
152
- # gemini_parts.append(f"--- Content of video file {filename} (extracted as text) ---\n{text_content}")
153
-
154
- elif mime_type and mime_type.startswith("audio/"): # Gemini 1.5 Pro can handle audio
155
- print(f"Audio file detected: {filename}. Full audio processing requires File API.")
156
- gemini_parts.append(f"[Audio file: {filename} - Requires File API upload]")
157
- # Placeholder: Add text conversion if feasible (e.g. transcript)
158
- # text_content, _ = convert_file_to_text(file_obj) # Needs specific audio-to-text logic
159
- # if text_content:
160
- # gemini_parts.append(f"--- Content of audio file {filename} (extracted as text) ---\n{text_content}")
161
-
162
- else: # Assume text or convertible to text
163
- text_content, original_filename = convert_file_to_text(file_obj)
164
- if text_content:
165
- # Add context marker
166
- gemini_parts.append(f"\n--- Content from file: {original_filename} ---\n{text_content}\n--- End of file: {original_filename} ---")
167
- else:
168
- gemini_parts.append(f"[Could not process file: {original_filename}]")
169
-
170
- # Ensure there's at least one part (maybe an empty string if only files were given?)
171
- if not gemini_parts:
172
- gemini_parts.append("") # Avoid sending empty list
173
-
174
- return gemini_parts
175
-
176
-
177
- # --- Gradio UI Functions ---
178
-
179
  def validate_api_key(api_key):
180
- """Checks if the API key is potentially valid by trying to list models."""
181
- if not api_key:
182
- return "<p style='color: orange;'>Please enter an API Key.</p>"
183
  try:
184
- genai.configure(api_key=api_key)
185
- models = genai.list_models()
186
- # Check if at least one desired model is available with this key
187
- available_core_models = [m.name for m in models if 'generateContent' in m.supported_generation_methods]
188
- if any(model_name.split('/')[-1] in AVAILABLE_MODELS for model_name in available_core_models):
189
- return "<p style='color: green;'>API Key seems valid (can list models).</p>"
190
- else:
191
- return "<p style='color: orange;'>API Key is valid but might not have access to the required Gemini models.</p>"
192
-
193
  except Exception as e:
194
- print(f"API Key validation error: {e}")
195
- # Be careful not to leak too much error detail
196
- if "API key not valid" in str(e):
197
- return "<p style='color: red;'>API Key is invalid.</p>"
 
 
 
 
 
198
  else:
199
- return f"<p style='color: red;'>API Key validation failed. Error: {str(e)}</p>"
200
-
201
-
202
- def update_parameter_visibility(model_name):
203
- """Updates visibility and values of parameter controls based on selected model."""
204
- updates = {}
205
- params_for_model = MODEL_PARAMS.get(model_name, {})
206
-
207
- # Define ALL possible parameter components used across models
208
- all_param_keys = set(k for params in MODEL_PARAMS.values() for k in params)
209
-
210
- for key in all_param_keys:
211
- param_config = params_for_model.get(key)
212
- if param_config:
213
- # Parameter exists for this model: make visible and set defaults
214
- updates[param_elements[key]] = gr.update(
215
- visible=True,
216
- label=key.replace("_", " ").title(), # Nicer label
217
- value=param_config.get("default") # Set default value
218
- # Add specific updates for slider ranges etc. if needed
219
- # minimum=param_config.get("min"),
220
- # maximum=param_config.get("max"),
221
- # step=param_config.get("step")
222
- )
223
- else:
224
- # Parameter does NOT exist for this model: hide it
225
- updates[param_elements[key]] = gr.update(visible=False, value=None) # Reset value when hiding
226
-
227
- return updates
228
-
229
-
230
- def handle_chat(api_key, model_name, history, message, files, *params_tuple):
231
- """Handles the chat interaction."""
232
- # 1. Basic Validation
233
- if not api_key:
234
- gr.Warning("Gemini API Key is missing!")
235
- return history, "" # Return unchanged history and empty textbox
236
- if not message and not files:
237
- gr.Warning("Please enter a message or upload files.")
238
- return history, ""
239
-
240
- # 2. Configure API Key
241
- try:
242
- genai.configure(api_key=api_key)
243
- except Exception as e:
244
- gr.Error(f"Failed to configure API Key: {e}")
245
- return history, message # Keep message in textbox for retry
246
-
247
- # 3. Prepare Generation Config from *params_tuple
248
- param_keys = [key for key, config in MODEL_PARAMS.get(model_name, {}).items()]
249
- generation_config_dict = {}
250
- if len(params_tuple) == len(param_keys):
251
- generation_config_dict = {key: val for key, val in zip(param_keys, params_tuple) if val is not None}
252
- # Handle stop sequences (expecting comma-separated string)
253
- if 'stop_sequences' in generation_config_dict and isinstance(generation_config_dict['stop_sequences'], str):
254
- sequences = [s.strip() for s in generation_config_dict['stop_sequences'].split(',') if s.strip()]
255
- if sequences:
256
- generation_config_dict['stop_sequences'] = sequences
257
- else:
258
- del generation_config_dict['stop_sequences'] # Remove if empty/invalid
259
- print(f"Using Generation Config: {generation_config_dict}") # Debug
260
  else:
261
- print(f"Warning: Mismatch between expected params ({len(param_keys)}) and received params ({len(params_tuple)})")
262
-
263
-
264
- # 4. Prepare Model Input
265
- gemini_input_parts = prepare_gemini_input(message, files)
266
- print(f"Prepared Gemini Input Parts: {gemini_input_parts}") # Debugging
267
-
268
- # 5. Initialize Model and Chat
269
- try:
270
- # Add safety settings if needed/configured
271
- # safety_settings = {...}
272
- model = genai.GenerativeModel(model_name)#, safety_settings=safety_settings)
273
-
274
- # Convert Gradio history (list of lists) to Gemini format (list of Content objects)
275
- gemini_history = []
276
- for user_msg, model_msg in history:
277
- # Simple text history for now. Need enhancement for multimodal history.
278
- if user_msg: gemini_history.append({'role': 'user', 'parts': [user_msg]})
279
- if model_msg: gemini_history.append({'role': 'model', 'parts': [model_msg]})
280
-
281
- chat = model.start_chat(history=gemini_history)
282
- print(f"Starting chat with history (simplified): {gemini_history}") # Debugging
283
-
284
- except Exception as e:
285
- gr.Error(f"Failed to initialize model or chat: {e}")
286
- return history, message # Keep message in textbox
287
-
288
- # 6. Send Message and Get Response
289
- response_text = ""
290
- try:
291
- # Use streaming for better UX in chat
292
- response = chat.send_message(gemini_input_parts,
293
- generation_config=genai.types.GenerationConfig(**generation_config_dict),
294
- stream=True)
295
-
296
- full_response_content = ""
297
- for chunk in response:
298
- # Check if the chunk has text content
299
- if hasattr(chunk, 'text'):
300
- chunk_text = chunk.text
301
- print(f"Stream chunk: {chunk_text}") # Debug stream
302
- full_response_content += chunk_text
303
- # Yield intermediate updates to the chatbot
304
- current_history = history + [[message or "[Input files only]", full_response_content]]
305
- yield current_history, "" # Update chatbot, clear input
306
- # Check for image data if model supports it (more complex parsing needed)
307
- # elif chunk.parts and chunk.parts[0].inline_data:
308
- # # Handle potential image output - requires modification
309
- # pass
310
-
311
- response_text = full_response_content # Final text response
312
-
313
- # Check for blocked prompts or safety issues
314
- if not response_text and response.prompt_feedback.block_reason:
315
- block_reason = response.prompt_feedback.block_reason
316
- safety_ratings = response.prompt_feedback.safety_ratings
317
- gr.Warning(f"Request blocked. Reason: {block_reason}. Ratings: {safety_ratings}")
318
- # Append a notice to history instead of an empty response
319
- history.append([message or "[Input files only]", f"[Request blocked due to: {block_reason}]"])
320
- return history, "" # Clear input box
321
-
322
-
323
- except Exception as e:
324
- gr.Error(f"Error during generation: {e}")
325
- # Optionally add the error to history for context
326
- history.append([message or "[Input files only]", f"[Error during generation: {str(e)}]"])
327
- return history, "" # Clear input box
328
-
329
- # 7. Update History and Clear Input
330
- # The yielding above handles intermediate updates. This is the final state.
331
- final_history = history + [[message or "[Input files only]", response_text or "[No text content received]"]]
332
- return final_history, "" # Final update, clear input
333
-
334
-
335
- def handle_single_response(api_key, model_name, prompt, files, *params_tuple):
336
- """Handles the single response interaction."""
337
- # 1. Validations
338
- if not api_key:
339
- gr.Warning("Gemini API Key is missing!")
340
- return "[Error: API Key Missing]", None # Text output, Image output
341
- if not prompt and not files:
342
- gr.Warning("Please enter a prompt or upload files.")
343
- return "[Error: No input provided]", None
344
-
345
- # 2. Configure API Key
346
  try:
347
- genai.configure(api_key=api_key)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  except Exception as e:
349
- gr.Error(f"Failed to configure API Key: {e}")
350
- return f"[Error: API Key Config Failed: {e}]", None
351
-
352
- # 3. Prepare Generation Config
353
- param_keys = [key for key, config in MODEL_PARAMS.get(model_name, {}).items()]
354
- generation_config_dict = {}
355
- if len(params_tuple) == len(param_keys):
356
- generation_config_dict = {key: val for key, val in zip(param_keys, params_tuple) if val is not None}
357
- # Handle stop sequences
358
- if 'stop_sequences' in generation_config_dict and isinstance(generation_config_dict['stop_sequences'], str):
359
- sequences = [s.strip() for s in generation_config_dict['stop_sequences'].split(',') if s.strip()]
360
- if sequences:
361
- generation_config_dict['stop_sequences'] = sequences
362
- else:
363
- del generation_config_dict['stop_sequences']
364
- print(f"Using Generation Config: {generation_config_dict}") # Debug
365
  else:
366
- print(f"Warning: Mismatch between expected params ({len(param_keys)}) and received params ({len(params_tuple)})")
367
-
368
-
369
- # 4. Prepare Model Input
370
- gemini_input_parts = prepare_gemini_input(prompt, files)
371
- print(f"Prepared Gemini Input Parts: {gemini_input_parts}") # Debugging
372
-
373
-
374
- # 5. Initialize Model
375
- try:
376
- # Add safety settings if needed/configured
377
- model = genai.GenerativeModel(model_name)
378
- except Exception as e:
379
- gr.Error(f"Failed to initialize model: {e}")
380
- return f"[Error: Model Initialization Failed: {e}]", None
381
-
382
- # 6. Generate Content (Non-streaming for single response usually)
383
- output_text = "[No text content generated]"
384
- output_image = None # Placeholder for image output
385
  try:
386
- response = model.generate_content(
387
- gemini_input_parts,
388
- generation_config=genai.types.GenerationConfig(**generation_config_dict),
389
- stream=False # Simpler for single turn unless very long output expected
390
- )
391
-
392
- # Check for blocked prompts or safety issues
393
- if response.prompt_feedback.block_reason:
394
- block_reason = response.prompt_feedback.block_reason
395
- safety_ratings = response.prompt_feedback.safety_ratings
396
- gr.Warning(f"Request blocked. Reason: {block_reason}. Ratings: {safety_ratings}")
397
- return f"[Request blocked due to: {block_reason}]", None
398
-
399
- # Process response parts (could contain text and/or images)
400
- # This part needs refinement based on how Gemini API returns mixed content
401
- # For now, prioritize text and assume first image part if present
402
- response_text_parts = []
403
- for part in response.parts:
404
- if hasattr(part, 'text'):
405
- response_text_parts.append(part.text)
406
- elif hasattr(part, 'inline_data') and part.inline_data.mime_type.startswith('image/'):
407
- if output_image is None: # Display the first image found
408
- try:
409
- image_data = part.inline_data.data
410
- img = Image.open(io.BytesIO(image_data))
411
- output_image = img
412
- print("Image received in response.")
413
- except Exception as img_err:
414
- print(f"Error decoding image from response: {img_err}")
415
- response_text_parts.append("[Error decoding image in response]")
416
-
417
- if response_text_parts:
418
- output_text = "\n".join(response_text_parts)
419
- elif hasattr(response, 'text'): # Fallback if parts parsing fails but text attribute exists
420
- output_text = response.text
421
-
422
- # Check if only an image was returned (or intended)
423
- if not response_text_parts and output_image is not None:
424
- output_text = "[Image generated - see output below]"
425
-
426
-
427
  except Exception as e:
428
- gr.Error(f"Error during generation: {e}")
429
- output_text = f"[Error during generation: {str(e)}]"
430
-
431
- # 7. Return results
432
- return output_text, output_image
433
-
434
-
435
- # --- Build Gradio Interface ---
436
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
437
- gr.Markdown("# Gemini API Interface")
438
- gr.Markdown("Interact with Google Gemini models using your own API key. Supports chat, single responses, file uploads, and model-specific parameters.")
439
 
 
 
440
  # API Key Section
441
- with gr.Row():
442
- api_key_input = gr.Textbox(
443
- label="Gemini API Key",
444
- placeholder="Enter your Gemini API Key here",
445
- type="password",
446
- scale=3
447
- )
448
- validate_button = gr.Button("Validate Key", scale=1)
449
- api_key_status = gr.Markdown("<p style='color: gray;'>Enter your key and click Validate.</p>")
450
-
451
- # Model Selection
452
- model_dropdown = gr.Dropdown(
453
- label="Select Gemini Model",
454
- choices=AVAILABLE_MODELS,
455
- value=AVAILABLE_MODELS[0], # Default model
456
- )
457
-
458
- # Dynamic Parameters Section (Initially hidden, updated by model selection)
459
- param_elements = {} # Dictionary to hold parameter UI components
460
- with gr.Accordion("Model Parameters", open=False) as params_accordion:
461
- # Create UI elements for ALL possible parameters defined in MODEL_PARAMS
462
- # They will be shown/hidden by the update_parameter_visibility function
463
- all_possible_params = set(k for params in MODEL_PARAMS.values() for k in params)
464
- for param_name in sorted(list(all_possible_params)): # Sort for consistent order
465
- # Determine control type based on the first model that defines it (can be refined)
466
- control_type = "textbox" # Default
467
- config = {}
468
- for model_cfg in MODEL_PARAMS.values():
469
- if param_name in model_cfg:
470
- config = model_cfg[param_name]
471
- control_type = config.get("type", "textbox")
472
- break # Found config for this param
473
-
474
- if control_type == "slider":
475
- param_elements[param_name] = gr.Slider(
476
- label=param_name.replace("_", " ").title(),
477
- minimum=config.get("min", 0),
478
- maximum=config.get("max", 1),
479
- step=config.get("step", 0.1),
480
- value=config.get("default"),
481
- visible=False, # Initially hidden
482
- interactive=True
483
- )
484
- elif control_type == "number":
485
- param_elements[param_name] = gr.Number(
486
- label=param_name.replace("_", " ").title(),
487
- minimum=config.get("min", 1),
488
- step=config.get("step", 1),
489
- value=config.get("default"),
490
- visible=False,
491
- interactive=True
492
- )
493
- else: # Default to Textbox for stop_sequences etc.
494
- param_elements[param_name] = gr.Textbox(
495
- label=param_name.replace("_", " ").title(),
496
- lines=config.get("lines", 1),
497
- placeholder=config.get("placeholder", ""),
498
- value=config.get("default", ""),
499
- visible=False,
500
- interactive=True
501
- )
502
-
503
- # Pack the parameter components into a list for function inputs/outputs
504
- # IMPORTANT: The order here MUST match the order expected by handle_chat/handle_single_response
505
- ordered_param_components = [param_elements[key] for key in sorted(param_elements.keys())]
506
-
507
-
508
- # Main Interaction Area (Tabs)
509
- with gr.Tabs():
510
- # --- Chat Interface Tab ---
511
- with gr.TabItem("Chat Interface"):
512
- gr.Markdown("Have a conversation with the selected model. Upload files to include their content.")
513
- chat_history_state = gr.State([]) # Holds the conversation history
514
- chatbot_display = gr.Chatbot(label="Conversation", height=500)
515
- with gr.Row():
516
- chat_file_upload = gr.File(label="Upload Files (Text, Images, etc.)", file_count="multiple")
517
- with gr.Row():
518
- chat_message_input = gr.Textbox(label="Your Message", placeholder="Type your message here...", scale=4, lines=3)
519
- chat_submit_button = gr.Button("Send", variant="primary", scale=1)
520
- clear_chat_button = gr.Button("Clear Chat History")
521
-
522
-
523
- # --- Single Response Tab ---
524
  with gr.TabItem("Single Response"):
525
- gr.Markdown("Send a prompt (and optionally files) to get a single response from the model.")
526
- with gr.Row():
527
- with gr.Column(scale=2):
528
- single_prompt_input = gr.Textbox(label="Your Prompt", placeholder="Enter your prompt...", lines=5)
529
- single_file_upload = gr.File(label="Upload Files (Text, Images, etc.)", file_count="multiple")
530
- single_submit_button = gr.Button("Generate Response", variant="primary")
531
- with gr.Column(scale=2):
532
- gr.Markdown("**Output:**")
533
- single_output_text = gr.Textbox(label="Text Response", lines=10, interactive=False)
534
- single_output_image = gr.Image(label="Image Response", type="pil", interactive=False) # Display PIL images
535
-
536
-
537
- # --- Event Wiring ---
538
-
539
- # 1. API Key Validation
540
- validate_button.click(
541
- fn=validate_api_key,
542
  inputs=[api_key_input],
543
- outputs=[api_key_status]
544
- )
545
-
546
- # 2. Update Parameters UI when Model Changes
547
- model_dropdown.change(
548
- fn=update_parameter_visibility,
549
- inputs=[model_dropdown],
550
- outputs=list(param_elements.values()) # Pass the actual components
551
- )
552
-
553
- # Trigger initial parameter visibility update on load
554
- demo.load(
555
- fn=update_parameter_visibility,
556
- inputs=[model_dropdown],
557
- outputs=list(param_elements.values())
558
- )
559
-
560
- # 3. Chat Submission Logic (using .then() for streaming if possible, or standard submit)
561
- # Note: Gradio streaming with gr.Chatbot often uses yields
562
- chat_submit_button.click(
563
- fn=handle_chat,
564
- inputs=[
565
- api_key_input,
566
- model_dropdown,
567
- chat_history_state,
568
- chat_message_input,
569
- chat_file_upload
570
- ] + ordered_param_components, # Add dynamic params
571
- outputs=[chatbot_display, chat_message_input] # Update chatbot, clear input box
572
- ).then(
573
- # Update the state *after* the response is fully generated
574
- lambda history: history, # Simple pass-through to get final history
575
- inputs=chatbot_display,
576
- outputs=chat_history_state
577
  )
578
- # Allow submitting chat by pressing Enter in the textbox
579
- chat_message_input.submit(
580
- fn=handle_chat,
581
- inputs=[
582
- api_key_input,
583
- model_dropdown,
584
- chat_history_state,
585
- chat_message_input,
586
- chat_file_upload
587
- ] + ordered_param_components,
588
- outputs=[chatbot_display, chat_message_input]
589
- ).then(
590
- lambda history: history,
591
- inputs=chatbot_display,
592
- outputs=chat_history_state
593
- )
594
-
595
-
596
- # 4. Clear Chat Logic
597
- def clear_chat_history_func():
598
- return [], [] # Clears chatbot display and history state
599
-
600
- clear_chat_button.click(
601
- fn=clear_chat_history_func,
602
- inputs=[],
603
- outputs=[chatbot_display, chat_history_state]
604
  )
605
-
606
- # 5. Single Response Submission Logic
607
- single_submit_button.click(
608
- fn=handle_single_response,
609
- inputs=[
610
- api_key_input,
611
- model_dropdown,
612
- single_prompt_input,
613
- single_file_upload
614
- ] + ordered_param_components, # Add dynamic params
615
- outputs=[single_output_text, single_output_image]
616
  )
617
 
618
-
619
- # Launch the Gradio app
620
- if __name__ == "__main__":
621
- demo.launch(debug=True) # Set debug=False for deployment
 
1
  import gradio as gr
2
+ import google.genai as genai
 
 
3
  from PIL import Image
4
+ import os
5
+ import textract
6
+
7
+ # List of available models (including experimental and recent ones)
8
+ models = [
9
+ "gemini-2.5-flash-preview-04-17",
10
+ "gemini-2.5-pro-preview-03-25",
11
+ "gemini-2.0-flash",
12
+ "gemini-2.0-flash-lite",
13
+ "gemini-2.0-flash-thinking-exp-01-21",
14
+ "gemini-1.5-pro",
15
+ "gemini-2.0-flash-exp-image-generation"
 
 
 
 
 
16
  ]
17
 
18
+ # Model types for handling inputs
19
+ model_types = {
20
+ "gemini-2.5-flash-preview-04-17": "text",
21
+ "gemini-2.5-pro-preview-03-25": "text",
22
+ "gemini-2.0-flash": "text",
23
+ "gemini-2.0-flash-lite": "text",
24
+ "gemini-2.0-flash-thinking-exp-01-21": "text",
25
+ "gemini-1.5-pro": "text",
26
+ "gemini-2.0-flash-exp-image-generationn": "multimodal"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
 
29
+ # Function to validate API key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def validate_api_key(api_key):
 
 
 
31
  try:
32
+ client = genai.Client(api_key=api_key)
33
+ client.models.list() # Validate by attempting to list models
34
+ return True, "API Key is valid."
 
 
 
 
 
 
35
  except Exception as e:
36
+ return False, f"Invalid API Key: {str(e)}"
37
+
38
+ # Function to process uploaded files
39
+ def process_files(files, model_type):
40
+ inputs = []
41
+ for file_path in files:
42
+ if model_type == "multimodal" and file_path.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp')):
43
+ img = Image.open(file_path)
44
+ inputs.append(img)
45
  else:
46
+ try:
47
+ text = textract.process(file_path).decode('utf-8')
48
+ inputs.append(text)
49
+ except Exception as e:
50
+ inputs.append(f"Error extracting text from {os.path.basename(file_path)}: {str(e)}")
51
+ return inputs
52
+
53
+ # Chat submit function
54
+ def chat_submit_func(message, files, chat_history, model, temperature, top_p, max_tokens, api_key):
55
+ client = genai.Client(api_key=api_key)
56
+ gen_model = client.models.get(model)
57
+
58
+ # Prepare inputs
59
+ if model_types[model] == "text" and files:
60
+ chat_history.append((message, "Warning: Files are not supported for text-only models. Converting to text where possible."))
61
+ processed_inputs = process_files(files, "text")
62
+ inputs = [message] + processed_inputs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  else:
64
+ processed_inputs = process_files(files, model_types[model]) if files else []
65
+ inputs = [message] + processed_inputs
66
+
67
+ # Generation configuration
68
+ generation_config = {
69
+ "temperature": temperature,
70
+ "top_p": top_p,
71
+ "max_output_tokens": max_tokens,
72
+ }
73
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  try:
75
+ response = gen_model.generate_content(inputs, generation_config=generation_config)
76
+ response_text = ""
77
+ response_images = []
78
+
79
+ # Parse response
80
+ for candidate in response.candidates:
81
+ for part in candidate.content.parts:
82
+ if hasattr(part, 'text') and part.text:
83
+ response_text += part.text
84
+ elif hasattr(part, 'file_data') and part.file_data:
85
+ # Assuming file_data provides a URL; adjust if base64 or other format
86
+ image_url = part.file_data.url
87
+ response_images.append(image_url)
88
+
89
+ # Update chat history
90
+ user_message = message
91
+ if files:
92
+ user_message += "\nFiles: " + ", ".join([os.path.basename(f) for f in files])
93
+ chat_history.append((user_message, None))
94
+
95
+ bot_message = response_text
96
+ if response_images:
97
+ bot_message += "\n" + "\n".join([f"![image]({img})" for img in response_images])
98
+ chat_history.append((None, bot_message))
99
+
100
+ return chat_history, ""
101
  except Exception as e:
102
+ chat_history.append((message, f"Error: {str(e)}"))
103
+ return chat_history, ""
104
+
105
+ # Single response submit function
106
+ def single_submit_func(prompt, files, model, temperature, top_p, max_tokens, api_key):
107
+ client = genai.Client(api_key=api_key)
108
+ gen_model = client.models.get(model)
109
+
110
+ # Prepare inputs
111
+ if model_types[model] == "text" and files:
112
+ processed_inputs = process_files(files, "text")
113
+ inputs = [prompt] + processed_inputs
114
+ warning = "Warning: Files converted to text for text-only model."
 
 
 
115
  else:
116
+ processed_inputs = process_files(files, model_types[model]) if files else []
117
+ inputs = [prompt] + processed_inputs
118
+ warning = ""
119
+
120
+ # Generation configuration
121
+ generation_config = {
122
+ "temperature": temperature,
123
+ "top_p": top_p,
124
+ "max_output_tokens": max_tokens,
125
+ }
126
+
 
 
 
 
 
 
 
 
127
  try:
128
+ response = gen_model.generate_content(inputs, generation_config=generation_config)
129
+ response_text = warning
130
+ response_images = []
131
+
132
+ # Parse response
133
+ for candidate in response.candidates:
134
+ for part in candidate.content.parts:
135
+ if hasattr(part, 'text') and part.text:
136
+ response_text += part.text
137
+ elif hasattr(part, 'file_data') and part.file_data:
138
+ image_url = part.file_data.url
139
+ response_images.append(image_url)
140
+
141
+ return response_text, response_images
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  except Exception as e:
143
+ return f"Error: {str(e)}", []
 
 
 
 
 
 
 
 
 
 
144
 
145
+ # Gradio interface
146
+ with gr.Blocks(title="Gemini API Interface") as app:
147
  # API Key Section
148
+ api_key_input = gr.Textbox(label="Gemini API Key", type="password", placeholder="Enter your Gemini API Key")
149
+ validate_btn = gr.Button("Validate API Key")
150
+ key_status = gr.Textbox(label="API Key Status", interactive=False)
151
+ key_validated = gr.State(False)
152
+
153
+ # Model and Parameters Section (hidden until key is validated)
154
+ with gr.Group(visible=False) as config_group:
155
+ model_selector = gr.Dropdown(choices=models, label="Select Model", value=models[0])
156
+ temperature = gr.Slider(0, 1, value=0.7, label="Temperature", step=0.01)
157
+ top_p = gr.Slider(0, 1, value=0.9, label="Top P", step=0.01)
158
+ max_tokens = gr.Number(value=512, label="Max Tokens", minimum=1)
159
+
160
+ # Tabs for Chat and Single Response (hidden until key is validated)
161
+ with gr.Tabs(visible=False) as tabs:
162
+ with gr.TabItem("Chat"):
163
+ chat_display = gr.Chatbot(label="Chat History")
164
+ chat_input = gr.Textbox(label="Your Message", placeholder="Type your message here...")
165
+ chat_files = gr.File(label="Upload Files", file_count="multiple")
166
+ chat_submit_btn = gr.Button("Send")
167
+ chat_status = gr.Textbox(label="Status", interactive=False)
168
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  with gr.TabItem("Single Response"):
170
+ single_input = gr.Textbox(label="Prompt", placeholder="Enter your prompt here...")
171
+ single_files = gr.File(label="Upload Files", file_count="multiple")
172
+ single_submit_btn = gr.Button("Generate")
173
+ single_text_output = gr.Textbox(label="Response Text", interactive=False)
174
+ single_image_output = gr.Gallery(label="Response Images")
175
+
176
+ # Validation logic
177
+ def on_validate_key(api_key):
178
+ is_valid, status = validate_api_key(api_key)
179
+ if is_valid:
180
+ return status, True, gr.update(visible=True), gr.update(visible=True)
181
+ return status, False, gr.update(visible=False), gr.update(visible=False)
182
+
183
+ validate_btn.click(
184
+ on_validate_key,
 
 
185
  inputs=[api_key_input],
186
+ outputs=[key_status, key_validated, config_group, tabs]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  )
188
+
189
+ # Chat submission
190
+ chat_submit_btn.click(
191
+ chat_submit_func,
192
+ inputs=[chat_input, chat_files, chat_display, model_selector, temperature, top_p, max_tokens, api_key_input],
193
+ outputs=[chat_display, chat_status]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  )
195
+
196
+ # Single response submission
197
+ single_submit_btn.click(
198
+ single_submit_func,
199
+ inputs=[single_input, single_files, model_selector, temperature, top_p, max_tokens, api_key_input],
200
+ outputs=[single_text_output, single_image_output]
 
 
 
 
 
201
  )
202
 
203
+ app.launch()