Dark Light

Chat GPT5 vs Claude Sonnet4: A python script example 4

Hi guys,

After a while not posting, now I’ve found more time to be active once again. I’ve been working closely on image generation with Flux1. So I’ve been doing some productivity scripts that will help me on this.

So I came up with the idea of comparing ChatGPT5, latest OpenAI model, with Claude’s Sonnet4. It is a very simple script. This is the prompt I used:

Prompt: Create a python script with a tkinter GUI that obtains multiple txt (positive) prompt files that include a prompt text. Process all text and create a new .txt in the following format (as an example, below) you have the “——-” as a marker between prompt files. Negative prompts will go blank unless specified in a separate text box in the GUI :

positive: candid street photo of carl0s, a young man walking through a rainy Tokyo street at night, neon reflections on wet pavement, cinematic mood, 35mm lens

negative: lowres, bad, bad anatomy, bad hands, extra digits

———————–

positive: carl0s, a young man wearing a tailored black tuxedo, standing at a gala event, champagne in hand, dramatic lighting, high-resolution professional photography negative: lowres, bad, bad anatomy, bad hands, extra digits

———————–

positive: close-up portrait of carl0s, a young man in natural sunlight, freckles and skin texture visible, 85mm lens, bokeh background, hyperrealistic detail

negative: lowres, bad, bad anatomy, bad hands, extra digits


This was the code written by ChatGPT5:

import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk
import os
import sys

SEPARATOR_LINE = "-----------------------"  # exactly as requested

class PromptBatcher(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Prompt Batcher — Positive/Negative Formatter")
        self.geometry("760x520")
        self.minsize(720, 480)

        # State
        self.selected_files = []
        self.output_path = tk.StringVar(value="")
        self.collapse_newlines = tk.BooleanVar(value=True)

        # UI
        self.create_widgets()

    def create_widgets(self):
        # Top controls (file actions)
        top_frame = ttk.Frame(self, padding=(10, 10, 10, 0))
        top_frame.pack(fill="x")

        ttk.Label(top_frame, text="Selected positive prompt files:").grid(row=0, column=0, sticky="w")

        btn_frame = ttk.Frame(top_frame)
        btn_frame.grid(row=0, column=1, sticky="e")

        ttk.Button(btn_frame, text="Add Files…", command=self.add_files).grid(row=0, column=0, padx=(0, 5))
        ttk.Button(btn_frame, text="Remove Selected", command=self.remove_selected).grid(row=0, column=1, padx=(0, 5))
        ttk.Button(btn_frame, text="Clear List", command=self.clear_list).grid(row=0, column=2)

        # Listbox for files
        list_frame = ttk.Frame(self, padding=10)
        list_frame.pack(fill="both", expand=True)

        self.file_list = tk.Listbox(list_frame, selectmode=tk.EXTENDED, activestyle="dotbox")
        self.file_list_scroll = ttk.Scrollbar(list_frame, orient="vertical", command=self.file_list.yview)
        self.file_list.config(yscrollcommand=self.file_list_scroll.set)

        self.file_list.pack(side="left", fill="both", expand=True)
        self.file_list_scroll.pack(side="right", fill="y")

        # Middle: Negative prompt and options
        middle = ttk.LabelFrame(self, text="Negative prompt (optional — leave blank for empty)", padding=10)
        middle.pack(fill="x", padx=10, pady=(0, 10))

        self.negative_entry = tk.Text(middle, height=4, wrap="word")
        self.negative_entry.pack(fill="x")

        opts = ttk.Frame(self, padding=(10, 0, 10, 10))
        opts.pack(fill="x")

        ttk.Checkbutton(opts, text="Collapse newlines inside each positive prompt into spaces",
                        variable=self.collapse_newlines).pack(side="left")

        # Output path chooser
        out_frame = ttk.Frame(self, padding=(10, 0, 10, 10))
        out_frame.pack(fill="x")
        ttk.Label(out_frame, text="Output file:").pack(side="left")
        self.out_entry = ttk.Entry(out_frame, textvariable=self.output_path)
        self.out_entry.pack(side="left", fill="x", expand=True, padx=6)
        ttk.Button(out_frame, text="Save As…", command=self.choose_output).pack(side="left")

        # Generate button
        action_frame = ttk.Frame(self, padding=(10, 0, 10, 10))
        action_frame.pack(fill="x")
        ttk.Button(action_frame, text="Generate Combined .txt", command=self.generate_output).pack(side="right")

        # Status
        self.status = tk.StringVar(value="Ready.")
        status_bar = ttk.Label(self, textvariable=self.status, anchor="w", relief="sunken")
        status_bar.pack(fill="x", side="bottom")

        # Styling (optional nicer padding)
        for child in top_frame.winfo_children():
            child.grid_configure(pady=4)
        self.bind_shortcuts()

    def bind_shortcuts(self):
        self.bind("<Control-o>", lambda e: self.add_files())
        self.bind("<Control-s>", lambda e: self.choose_output())
        self.bind("<F5>", lambda e: self.generate_output())
        self.bind("<Delete>", lambda e: self.remove_selected())

    def add_files(self):
        paths = filedialog.askopenfilenames(
            title="Choose positive prompt .txt files",
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )
        if not paths:
            return
        added = 0
        for p in paths:
            p = os.path.abspath(p)
            if p not in self.selected_files and os.path.isfile(p):
                self.selected_files.append(p)
                self.file_list.insert("end", p)
                added += 1
        self.set_status(f"Added {added} file(s). Total: {len(self.selected_files)}")

    def remove_selected(self):
        sel = list(self.file_list.curselection())
        if not sel:
            self.set_status("No selection to remove.")
            return
        # Remove from bottom to top to keep indices valid
        for idx in reversed(sel):
            path = self.file_list.get(idx)
            self.file_list.delete(idx)
            try:
                self.selected_files.remove(path)
            except ValueError:
                pass
        self.set_status(f"Removed {len(sel)} file(s). Total: {len(self.selected_files)}")

    def clear_list(self):
        self.file_list.delete(0, "end")
        self.selected_files.clear()
        self.set_status("List cleared.")

    def choose_output(self):
        default_name = "combined_prompts.txt"
        path = filedialog.asksaveasfilename(
            title="Save output as",
            defaultextension=".txt",
            initialfile=default_name,
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )
        if path:
            self.output_path.set(path)
            self.set_status(f"Output set to: {path}")

    def generate_output(self):
        if not self.selected_files:
            messagebox.showwarning("No files", "Please add at least one positive prompt file.")
            return

        out_path = self.output_path.get().strip()
        if not out_path:
            # If no explicit path, suggest a default next to first file
            first_dir = os.path.dirname(self.selected_files[0]) if self.selected_files else os.getcwd()
            out_path = os.path.join(first_dir, "combined_prompts.txt")
            self.output_path.set(out_path)

        neg_text = self.negative_entry.get("1.0", "end").strip()

        try:
            blocks = []
            for fpath in self.selected_files:
                with open(fpath, "r", encoding="utf-8", errors="replace") as fh:
                    content = fh.read().strip()

                if self.collapse_newlines.get():
                    # turn any internal newlines/tabs into single spaces
                    one_line = " ".join(content.split())
                else:
                    # preserve internal newlines exactly
                    one_line = content

                # Build the block
                block_lines = []
                block_lines.append(f"positive: {one_line}")
                # Negative: blank unless provided
                if neg_text:
                    block_lines.append(f"negative: {neg_text}")
                else:
                    block_lines.append("negative:")
                blocks.append("\n".join(block_lines))

            final_text = f"\n{SEPARATOR_LINE}\n".join(blocks) + "\n"

            # Ensure directory exists
            os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
            with open(out_path, "w", encoding="utf-8", errors="ignore") as out:
                out.write(final_text)

            self.set_status(f"Done. Wrote {len(blocks)} block(s) to: {out_path}")
            messagebox.showinfo("Success", f"Generated file:\n{out_path}")
        except Exception as e:
            self.set_status("Error during generation.")
            messagebox.showerror("Error", f"Something went wrong:\n{e}")

    def set_status(self, msg):
        self.status.set(msg)


def main():
    # High-DPI scaling on Windows for sharper UI
    if sys.platform.startswith("win"):
        try:
            import ctypes
            ctypes.windll.shcore.SetProcessDpiAwareness(1)  # SYSTEM_AWARE
        except Exception:
            pass
    app = PromptBatcher()
    app.mainloop()

if __name__ == "__main__":
    main()

And this is the result:

After a few tests, it worked like a charm.

Noe let’s see what’s the result for the same prompt on Claude:

This was the code written by Sonnet 4:

import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import os


class PromptProcessor:
    def __init__(self, root):
        self.root = root
        self.root.title("Prompt File Processor")
        self.root.geometry("800x600")

        # Store selected files
        self.selected_files = []

        # Create main frame
        main_frame = ttk.Frame(root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        # Configure grid weights
        root.columnconfigure(0, weight=1)
        root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        main_frame.rowconfigure(2, weight=1)

        # File selection section
        ttk.Label(main_frame, text="Select Positive Prompt Files:", font=("Arial", 12, "bold")).grid(
            row=0, column=0, columnspan=2, sticky=tk.W, pady=(0, 5)
        )

        file_frame = ttk.Frame(main_frame)
        file_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
        file_frame.columnconfigure(0, weight=1)

        ttk.Button(file_frame, text="Browse Files", command=self.browse_files).grid(
            row=0, column=0, sticky=tk.W, padx=(0, 10)
        )

        ttk.Button(file_frame, text="Clear Selection", command=self.clear_files).grid(
            row=0, column=1, sticky=tk.W
        )

        # File list display
        self.file_listbox = tk.Listbox(file_frame, height=6)
        self.file_listbox.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(5, 0))

        # Scrollbar for file list
        file_scrollbar = ttk.Scrollbar(file_frame, orient="vertical", command=self.file_listbox.yview)
        file_scrollbar.grid(row=1, column=2, sticky=(tk.N, tk.S), pady=(5, 0))
        self.file_listbox.configure(yscrollcommand=file_scrollbar.set)

        # Negative prompt section
        ttk.Label(main_frame, text="Negative Prompt (optional):", font=("Arial", 12, "bold")).grid(
            row=2, column=0, sticky=(tk.W, tk.N), pady=(10, 5)
        )

        # Text area for negative prompt
        self.negative_text = scrolledtext.ScrolledText(main_frame, height=4, width=60)
        self.negative_text.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))

        # Default negative prompt
        default_negative = "lowres, bad, bad anatomy, bad hands, extra digits"
        self.negative_text.insert("1.0", default_negative)

        # Process button
        process_frame = ttk.Frame(main_frame)
        process_frame.grid(row=4, column=0, columnspan=2, pady=(10, 0))

        ttk.Button(process_frame, text="Process Files", command=self.process_files,
                   style="Accent.TButton").pack(side=tk.LEFT, padx=(0, 10))

        ttk.Button(process_frame, text="Save Output", command=self.save_output).pack(side=tk.LEFT)

        # Output preview section
        ttk.Label(main_frame, text="Output Preview:", font=("Arial", 12, "bold")).grid(
            row=5, column=0, sticky=(tk.W, tk.N), pady=(20, 5)
        )

        # Output text area
        self.output_text = scrolledtext.ScrolledText(main_frame, height=15, width=80, state=tk.DISABLED)
        self.output_text.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))

        # Configure grid weights for resizing
        main_frame.rowconfigure(6, weight=1)

        # Status bar
        self.status_var = tk.StringVar()
        self.status_var.set("Ready - Select prompt files to begin")
        status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.grid(row=7, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(5, 0))

        # Store processed output
        self.processed_output = ""

    def browse_files(self):
        """Open file dialog to select multiple txt files"""
        files = filedialog.askopenfilenames(
            title="Select Positive Prompt Files",
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )

        if files:
            self.selected_files = list(files)
            self.update_file_list()
            self.status_var.set(f"Selected {len(self.selected_files)} file(s)")

    def clear_files(self):
        """Clear the selected files"""
        self.selected_files = []
        self.update_file_list()
        self.status_var.set("Files cleared - Select new files")

    def update_file_list(self):
        """Update the file listbox display"""
        self.file_listbox.delete(0, tk.END)
        for file_path in self.selected_files:
            filename = os.path.basename(file_path)
            self.file_listbox.insert(tk.END, filename)

    def process_files(self):
        """Process the selected files and generate formatted output"""
        if not self.selected_files:
            messagebox.showwarning("No Files", "Please select at least one prompt file first.")
            return

        try:
            # Get negative prompt text
            negative_prompt = self.negative_text.get("1.0", tk.END).strip()

            # Process each file
            output_lines = []

            for i, file_path in enumerate(self.selected_files):
                try:
                    # Read the file content
                    with open(file_path, 'r', encoding='utf-8') as f:
                        positive_content = f.read().strip()

                    # Skip empty files
                    if not positive_content:
                        continue

                    # Add positive prompt line
                    output_lines.append(f"positive: {positive_content}")

                    # Add negative prompt line
                    if negative_prompt:
                        output_lines.append(f"negative: {negative_prompt}")
                    else:
                        output_lines.append("negative: ")

                    # Add separator (except for the last file)
                    if i < len(self.selected_files) - 1:
                        output_lines.append("-" * 23)

                except Exception as e:
                    messagebox.showerror("File Error", f"Error reading file {os.path.basename(file_path)}: {str(e)}")
                    return

            # Join all lines
            self.processed_output = "\n".join(output_lines)

            # Display in output text area
            self.output_text.config(state=tk.NORMAL)
            self.output_text.delete("1.0", tk.END)
            self.output_text.insert("1.0", self.processed_output)
            self.output_text.config(state=tk.DISABLED)

            self.status_var.set(f"Successfully processed {len(self.selected_files)} file(s)")

        except Exception as e:
            messagebox.showerror("Processing Error", f"An error occurred while processing: {str(e)}")

    def save_output(self):
        """Save the processed output to a new file"""
        if not self.processed_output:
            messagebox.showwarning("No Output", "Please process files first before saving.")
            return

        # Ask user where to save
        save_path = filedialog.asksaveasfilename(
            title="Save Processed Output",
            defaultextension=".txt",
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )

        if save_path:
            try:
                with open(save_path, 'w', encoding='utf-8') as f:
                    f.write(self.processed_output)

                messagebox.showinfo("Success", f"Output saved successfully to:\n{save_path}")
                self.status_var.set(f"Output saved to {os.path.basename(save_path)}")

            except Exception as e:
                messagebox.showerror("Save Error", f"Error saving file: {str(e)}")


def main():
    root = tk.Tk()
    app = PromptProcessor(root)
    root.mainloop()


if __name__ == "__main__":
    main()

And this is the result:

As you can see, GUI’s window is fairly similar. Results were also positive.

So for this simple script, both of them work flawlessly!

However, where I appreciate an edge on favor of ChatGPT over Sonnet is other areas beside coding (where Claude might be slightly better). For instance, ChatGPT offer image generation (with many limitations compared to other Stable Diffusion models), whereas Claude does’t offer this. Claude offers Claude Code, it’s agentic solution, which came out months before ChatGPT5’s agentic solution. However, I feel like ChatGPT is smart on everyday usage for the average public, and its response is usually faster. So at similar prices, I’d definitely pick ChatGPT over Claude as a basic/intermediate user.

4 Comments

  1. HondroLife para qué sirve: alivio articular natural y efectivo.

    sintomas artrosis rodilla

  2. Votre guide parifoot rdc: picks quotidiens, cotes comparees, tickets securises, gestion de mise, cash-out et promos. Depots via mobile money, retraits rapides, support francophone. LINAFOOT, CAF, ligues europeennes. Pariez avec moderation.

Leave a Reply

Your email address will not be published. Required fields are marked *

0