#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 respective PROMPT SCRIPT in the multimodal payload so the model understands which documents are references versus targets.

  • Sequential Batching: Reads each selected .txt or .md prompt 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 .txt or .md file.


From <https://gemini.google.com/app/2d20a58b64e0018f>  Google Gemini (3.7 Flash)

Comments

Popular posts from this blog

#4297 ISS Astronaut Unable to Speak

#761 New Ways to Access Online Content, particularly Virtual Reality Content

#3679 Google Gemini 2.5 Pro: Create Python Application to View and Query Neo4j KG