#4910 Python Tkinter GUI for NASA Chat
#4910 Python Tkinter GUI for NASA Chat
#4910 Provide a Python application applying the Google Gemini API and Tkinter to take as input: 1) one or more reference files, 2) one or more files to be reviewed, and 3) one or more AI prompt scripts. The application will upload the files to the LLM, then sequence through each prompt script.
A complete, modular Tkinter desktop application using the official google-genai SDK is provided below.
The application uses background threading to keep the UI responsive, uploads reference and review files via the Gemini Files API, and iterates sequentially through each loaded prompt script.
Prerequisites
Install the official Google GenAI Python SDK:
pip install google-genai
Python Application (gemini_reviewer_app.py)
import os
import threading
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from google import genai
from google.genai import types
class GeminiReviewerApp:
def __init__(self, root):
self.root = root
self.root.title("Gemini Document Review & Batch Prompting Suite")
self.root.geometry("960x780")
self.root.minsize(800, 600)
# File tracking lists
self.reference_files = []
self.review_files = []
self.prompt_files = []
self._build_ui()
def _build_ui(self):
# 1. API Configuration Frame
api_frame = ttk.LabelFrame(self.root, text="API Configuration", padding=10)
api_frame.pack(fill=tk.X, padx=10, pady=5)
ttk.Label(api_frame, text="Gemini API Key:").pack(side=tk.LEFT, padx=5)
self.api_key_entry = ttk.Entry(api_frame, show="*", width=40)
self.api_key_entry.pack(side=tk.LEFT, padx=5)
# Pre-populate if environment variable exists
env_key = os.environ.get("GEMINI_API_KEY", "")
if env_key:
self.api_key_entry.insert(0, env_key)
ttk.Label(api_frame, text="Model:").pack(side=tk.LEFT, padx=(15, 5))
self.model_var = tk.StringVar(value="gemini-2.5-flash")
model_dropdown = ttk.Combobox(
api_frame,
textvariable=self.model_var,
values=["gemini-2.5-flash", "gemini-2.5-pro", "gemini-2.0-flash"],
width=18,
state="readonly",
)
model_dropdown.pack(side=tk.LEFT, padx=5)
# 2. File Selection Frame (3 columns)
files_frame = ttk.Frame(self.root, padding=5)
files_frame.pack(fill=tk.X, padx=5, pady=5)
files_frame.columnconfigure((0, 1, 2), weight=1)
# Reference Files Column
self.ref_listbox = self._create_file_selector(
files_frame,
title="1. Reference Documents",
col=0,
add_command=lambda: self._add_files(self.ref_listbox, self.reference_files),
clear_command=lambda: self._clear_files(self.ref_listbox, self.reference_files),
)
# Review Files Column
self.rev_listbox = self._create_file_selector(
files_frame,
title="2. Files to Review",
col=1,
add_command=lambda: self._add_files(self.rev_listbox, self.review_files),
clear_command=lambda: self._clear_files(self.rev_listbox, self.review_files),
)
# Prompt Scripts Column
self.prompt_listbox = self._create_file_selector(
files_frame,
title="3. Prompt Scripts (.txt, .md)",
col=2,
add_command=lambda: self._add_files(
self.prompt_listbox,
self.prompt_files,
filetypes=[("Prompt Scripts", "*.txt *.md *.prompt"), ("All Files", "*.*")],
),
clear_command=lambda: self._clear_files(self.prompt_listbox, self.prompt_files),
)
# 3. Action / Execution Bar
action_frame = ttk.Frame(self.root, padding=5)
action_frame.pack(fill=tk.X, padx=10, pady=5)
self.run_button = ttk.Button(
action_frame,
text="Upload Files & Execute Prompt Sequence",
command=self._start_processing_thread,
)
self.run_button.pack(side=tk.LEFT, padx=5, ipady=4)
self.save_button = ttk.Button(
action_frame,
text="Save Output to File",
command=self._save_output,
state=tk.DISABLED,
)
self.save_button.pack(side=tk.RIGHT, padx=5)
self.status_label = ttk.Label(action_frame, text="Ready", font=("Segoe UI", 9, "italic"))
self.status_label.pack(side=tk.LEFT, padx=15)
# 4. Output / Console Frame
output_frame = ttk.LabelFrame(self.root, text="Execution Log & Results", padding=10)
output_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.output_text = tk.Text(output_frame, wrap=tk.WORD, font=("Consolas", 10))
self.output_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar = ttk.Scrollbar(output_frame, command=self.output_text.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.output_text.config(yscrollcommand=scrollbar.set)
def _create_file_selector(self, parent, title, col, add_command, clear_command):
frame = ttk.LabelFrame(parent, text=title, padding=5)
frame.grid(row=0, column=col, sticky="nsew", padx=5)
listbox = tk.Listbox(frame, height=6, selectmode=tk.EXTENDED)
listbox.pack(fill=tk.BOTH, expand=True, pady=2)
btn_box = ttk.Frame(frame)
btn_box.pack(fill=tk.X, pady=2)
ttk.Button(btn_box, text="+ Add", command=add_command).pack(side=tk.LEFT, expand=True, fill=tk.X, padx=1)
ttk.Button(btn_box, text="Clear", command=clear_command).pack(side=tk.RIGHT, expand=True, fill=tk.X, padx=1)
return listbox
def _add_files(self, listbox, target_list, filetypes=None):
if filetypes is None:
filetypes = [
("Supported Files", "*.pdf *.txt *.md *.csv *.docx *.png *.jpg *.jpeg"),
("All Files", "*.*"),
]
paths = filedialog.askopenfilenames(title="Select Files", filetypes=filetypes)
for path in paths:
if path not in target_list:
target_list.append(path)
listbox.insert(tk.END, os.path.basename(path))
def _clear_files(self, listbox, target_list):
target_list.clear()
listbox.delete(0, tk.END)
def _log(self, text):
self.output_text.insert(tk.END, text + "\n")
self.output_text.see(tk.END)
def _start_processing_thread(self):
api_key = self.api_key_entry.get().strip()
if not api_key:
messagebox.showerror("Error", "Please provide a valid Gemini API Key.")
return
if not self.review_files:
messagebox.showwarning("Warning", "Please add at least one file to be reviewed.")
return
if not self.prompt_files:
messagebox.showwarning("Warning", "Please add at least one prompt script.")
return
# Disable trigger button to prevent race conditions
self.run_button.config(state=tk.DISABLED)
self.save_button.config(state=tk.DISABLED)
self.output_text.delete("1.0", tk.END)
worker = threading.Thread(target=self._process_workflow, args=(api_key,), daemon=True)
worker.start()
def _process_workflow(self, api_key):
uploaded_refs = []
uploaded_revs = []
try:
client = genai.Client(api_key=api_key)
model_name = self.model_var.get()
# Step 1: Upload Reference Files
self._update_status("Uploading Reference Files...")
self._log("=== [1/3] UPLOADING REFERENCE FILES ===")
for path in self.reference_files:
self._log(f" Uploading reference: {os.path.basename(path)}...")
f_obj = client.files.upload(file=path)
uploaded_refs.append((os.path.basename(path), f_obj))
self._log(f" ✓ Uploaded as URI: {f_obj.uri}")
# Step 2: Upload Files to Review
self._update_status("Uploading Review Files...")
self._log("\n=== [2/3] UPLOADING REVIEW FILES ===")
for path in self.review_files:
self._log(f" Uploading review target: {os.path.basename(path)}...")
f_obj = client.files.upload(file=path)
uploaded_revs.append((os.path.basename(path), f_obj))
self._log(f" ✓ Uploaded as URI: {f_obj.uri}")
# Step 3: Sequence Through Prompts
self._log("\n=== [3/3] EXECUTING PROMPT SEQUENCE ===")
total_prompts = len(self.prompt_files)
for idx, p_path in enumerate(self.prompt_files, start=1):
p_name = os.path.basename(p_path)
self._update_status(f"Executing Prompt {idx}/{total_prompts}: {p_name}...")
self._log(f"\n" + "=" * 70)
self._log(f"▶ PROMPT {idx}/{total_prompts}: {p_name}")
self._log("=" * 70)
with open(p_path, "r", encoding="utf-8", errors="replace") as pf:
prompt_instruction = pf.read()
# Build context payload with structural labels
contents = []
# Attach Reference files
if uploaded_refs:
contents.append("--- REFERENCE DOCUMENTS (Ground Truth / Standards / Rules) ---")
for name, f_obj in uploaded_refs:
contents.append(f"Reference File: {name}")
contents.append(f_obj)
# Attach Review files
contents.append("\n--- FILES TO BE REVIEWED (Artifacts under evaluation) ---")
for name, f_obj in uploaded_revs:
contents.append(f"Review File: {name}")
contents.append(f_obj)
# Attach current prompt script instructions
contents.append(f"\n--- INSTRUCTIONS / PROMPT SCRIPT ({p_name}) ---\n{prompt_instruction}")
# Call Gemini API
response = client.models.generate_content(
model=model_name,
contents=contents,
)
self._log("\n[MODEL EVALUATION]:\n")
self._log(response.text if response.text else "[Empty response received]")
self._log("\n" + "=" * 70)
self._log("✓ All prompt scripts successfully executed!")
self._update_status("Execution Completed.")
except Exception as exc:
self._log(f"\n[ERROR]: An error occurred during execution:\n{str(exc)}")
self._update_status("Error encountered.")
messagebox.showerror("Execution Error", f"Operation failed:\n{str(exc)}")
finally:
self.root.after(0, lambda: self.run_button.config(state=tk.NORMAL))
self.root.after(0, lambda: self.save_button.config(state=tk.NORMAL))
def _update_status(self, message):
self.root.after(0, lambda: self.status_label.config(text=message))
def _save_output(self):
content = self.output_text.get("1.0", tk.END).strip()
if not content:
messagebox.showwarning("Warning", "No evaluation content available to save.")
return
save_path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("Text file", "*.txt"), ("Markdown file", "*.md"), ("All Files", "*.*")],
title="Save Review Output",
)
if save_path:
with open(save_path, "w", encoding="utf-8") as f:
f.write(content)
messagebox.showinfo("Saved", f"Results successfully saved to:\n{save_path}")
if __name__ == "__main__":
root = tk.Tk()
app = GeminiReviewerApp(root)
root.mainloop()
Key Capabilities
Multimodal Uploads via Files API: Uses
client.files.upload(...)to upload any supported document format (PDF, images, text, source code, data tables) without needing manual file parsing.Labeled Prompt Contexts: Clearly separates
REFERENCE DOCUMENTS,FILES TO BE REVIEWED, and the respectivePROMPT SCRIPTin the multimodal payload so the model understands which documents are references versus targets.Sequential Batching: Reads each selected
.txtor.mdprompt file in sequence, sending the uploaded documents and instructions in separate round-trips.Responsive Threading: All API interactions run inside a background worker thread (
threading.Thread), preventing the Tkinter interface from freezing during upload and inference.Exportable Results: Includes a built-in log viewer and export utility to save the complete evaluation trail to a local
.txtor.mdfile.
From <https://gemini.google.com/app/2d20a58b64e0018f> Google Gemini (3.7 Flash)
Comments
Post a Comment