CTk Sentiment Analyzer – Python + AI + CustomTkinter GUI | FuzzuTech

 Demo :


Click Video πŸ‘‡πŸ‘‡πŸ‘‡































Features :

  • Python + CTkinter GUI

  • Real-time Sentiment Detection (VADER + Naive Bayes)

  • Dark/Light Themes

  • Emoji-powered results

  • Export CSV History

  • Train/Load ML Models


Code :


import os

import json

import time

import tkinter as tk

from tkinter import filedialog, messagebox

import customtkinter as ctk


# Data / ML

import nltk

from nltk.sentiment import SentimentIntensityAnalyzer

import pandas as pd

import numpy as np

from sklearn.feature_extraction.text import TfidfVectorizer

from sklearn.naive_bayes import MultinomialNB

from sklearn.pipeline import Pipeline

from sklearn.preprocessing import LabelEncoder

from sklearn.model_selection import train_test_split

from sklearn.metrics import classification_report

from joblib import dump, load


APP_TITLE = "CTk Sentiment Analyzer — FuzzuTech"

ctk.set_appearance_mode("Dark")

ctk.set_default_color_theme("blue")


# Ensure data/model dirs

os.makedirs("data", exist_ok=True)

os.makedirs("assets", exist_ok=True)

os.makedirs("models", exist_ok=True)


VADER_READY = False

SIA = None


HISTORY = []  # list of dicts {ts, text, model, label, score}

ACCENT_COLORS = ["blue","green","dark-blue","sweetkind","sunburst","emerald"]


# ----------------------- Helpers -----------------------


def ensure_vader():

    global VADER_READY, SIA

    try:

        nltk.data.find('sentiment/vader_lexicon.zip')

    except LookupError:

        try:

            nltk.download('vader_lexicon')

        except Exception as e:

            messagebox.showerror("NLTK Error", f"Failed to download VADER: {e}")

            return False

    try:

        SIA = SentimentIntensityAnalyzer()

        VADER_READY = True

        return True

    except Exception as e:

        messagebox.showerror("VADER Init", str(e))

        return False


NB_PATH = os.path.join("models","nb_model.pkl")

LE_PATH = os.path.join("models","label_encoder.npy")



def load_or_none(path):

    try:

        return load(path)

    except Exception:

        return None



def label_from_vader_scores(scores):

    # compound in [-1,1]

    c = scores.get('compound', 0)

    if c >= 0.2:

        return 'pos', c

    elif c <= -0.2:

        return 'neg', abs(c)

    else:

        return 'neu', 1-abs(c)



class GlassCard(ctk.CTkFrame):

    def __init__(self, master, **kwargs):

        super().__init__(master, corner_radius=20, **kwargs)

        self.configure(fg_color=("#111418","#0b0e12"))



class App(ctk.CTk):

    def __init__(self):

        super().__init__()

        self.title(APP_TITLE)

        self.geometry("1080x700")

        self.minsize(960, 620)


        # Top bar

        top = GlassCard(self)

        top.pack(fill="x", padx=14, pady=(14,8))


        self.model_var = tk.StringVar(value="VADER (Rule-based)")

        self.realtime_var = tk.BooleanVar(value=False)


        ctk.CTkLabel(top, text=APP_TITLE, font=("Segoe UI Semibold", 20)).pack(side="left", padx=12)


        self.model_menu = ctk.CTkOptionMenu(top, values=["VADER (Rule-based)", "Naive Bayes (Train Quick)"], variable=self.model_var, width=240)

        self.model_menu.pack(side="left", padx=8)


        self.theme_switch = ctk.CTkSwitch(top, text="Realtime", variable=self.realtime_var)

        self.theme_switch.pack(side="left", padx=8)


        self.appearance = ctk.CTkOptionMenu(top, values=["Dark","Light"], command=lambda m: ctk.set_appearance_mode(m), width=110)

        self.appearance.pack(side="right", padx=8)


        self.accent = ctk.CTkOptionMenu(top, values=ACCENT_COLORS, command=lambda c: ctk.set_default_color_theme(c) if c in ["blue","green","dark-blue"] else None, width=140)

        self.accent.set("blue")

        self.accent.pack(side="right", padx=8)


        # Main layout

        body = ctk.CTkFrame(self, fg_color="transparent")

        body.pack(fill="both", expand=True, padx=14, pady=(0,14))

        body.grid_columnconfigure(0, weight=3)

        body.grid_columnconfigure(1, weight=2)

        body.grid_rowconfigure(0, weight=1)


        # Left card: input / actions

        left = GlassCard(body)

        left.grid(row=0, column=0, sticky="nsew", padx=(0,8), pady=0)


        self.input = ctk.CTkTextbox(left, corner_radius=16, height=220, font=("Inter", 14))

        self.input.pack(fill="both", expand=True, padx=16, pady=(16,8))

        self.input.bind("<Control-Return>", lambda e: self.analyze())

        self.input.bind("<KeyRelease>", lambda e: self.analyze() if self.realtime_var.get() else None)


        btns = ctk.CTkFrame(left, fg_color="transparent")

        btns.pack(fill="x", padx=16, pady=(0,12))

        ctk.CTkButton(btns, text="Analyze (Ctrl+Enter)", command=self.analyze).pack(side="left", padx=6)

        ctk.CTkButton(btns, text="Clear (Ctrl+L)", command=self.clear).pack(side="left", padx=6)

        self.bind("<Control-l>", lambda e: self.clear())


        ctk.CTkButton(btns, text="Export CSV (Ctrl+S)", command=self.export_csv).pack(side="right", padx=6)

        self.bind("<Control-s>", lambda e: self.export_csv())


        # Right card: result / history / training

        right = GlassCard(body)

        right.grid(row=0, column=1, sticky="nsew", padx=(8,0), pady=0)

        right.grid_rowconfigure(3, weight=1)


        self.result_label = ctk.CTkLabel(right, text="Result: —", font=("Segoe UI", 18))

        self.result_label.grid(row=0, column=0, sticky="w", padx=16, pady=(16,6))


        self.emoji_label = ctk.CTkLabel(right, text="πŸ™‚", font=("Segoe UI Emoji", 42))

        self.emoji_label.grid(row=0, column=1, sticky="e", padx=16, pady=(16,6))


        self.bar = ctk.CTkProgressBar(right)

        self.bar.set(0)

        self.bar.grid(row=1, column=0, columnspan=2, sticky="ew", padx=16)


        # Train/Load

        tools = ctk.CTkFrame(right, fg_color="transparent")

        tools.grid(row=2, column=0, columnspan=2, sticky="ew", padx=12, pady=(8,8))

        ctk.CTkButton(tools, text="Download/Init VADER", command=self.init_vader).pack(side="left", padx=6)

        ctk.CTkButton(tools, text="Train NB", command=self.train_nb).pack(side="left", padx=6)

        ctk.CTkButton(tools, text="Load NB", command=self.load_nb).pack(side="left", padx=6)


        # History table

        self.table = ctk.CTkTextbox(right, corner_radius=14, height=280)

        self.table.grid(row=3, column=0, columnspan=2, sticky="nsew", padx=12, pady=(0,12))

        self.table.insert("end", "Time, Model, Label, Score, Text\n")

        self.table.configure(state="disabled")


        # State for NB model

        self.nb_pipe = None

        self.le = None


        # Prepare VADER on startup

        self.after(200, self.init_vader)


    # ---- actions ----

    def init_vader(self):

        ok = ensure_vader()

        if ok:

            messagebox.showinfo("VADER", "VADER is ready ✅")


    def analyze(self):

        text = self.input.get("1.0", "end").strip()

        if not text:

            return


        model = self.model_var.get()

        label, conf = "neu", 0.0


        if model.startswith("VADER"):

            if not VADER_READY:

                if not ensure_vader():

                    return

            scores = SIA.polarity_scores(text)

            label, conf = label_from_vader_scores(scores)


        else:

            if self.nb_pipe is None:

                messagebox.showwarning("Naive Bayes", "Train or load the NB model first.")

                return

            proba = self.nb_pipe.predict_proba([text])[0]

            idx = int(np.argmax(proba))

            label = self.le.inverse_transform([idx])[0]

            conf = float(np.max(proba))


        # Update UI

        self.result_label.configure(text=f"Result: {label.upper()}  ({conf:.2f})")

        self.bar.set(conf)

        emoji = {"pos":"πŸ˜„","neg":"😠","neu":"😐"}.get(label, "πŸ™‚")

        self.emoji_label.configure(text=emoji)


        # History

        row = {

            "ts": time.strftime("%Y-%m-%d %H:%M:%S"),

            "model": model.split(" ")[0],

            "label": label,

            "score": round(conf, 3),

            "text": text.replace("\n"," ")[:1200]

        }

        HISTORY.append(row)

        self._append_table(row)


    def _append_table(self, row):

        self.table.configure(state="normal")

        line = f"{row['ts']}, {row['model']}, {row['label']}, {row['score']}, {row['text']}\n"

        self.table.insert("end", line)

        self.table.see("end")

        self.table.configure(state="disabled")


    def clear(self):

        self.input.delete("1.0", "end")

        self.result_label.configure(text="Result: —")

        self.bar.set(0)

        self.emoji_label.configure(text="πŸ™‚")


    def export_csv(self):

        if not HISTORY:

            messagebox.showinfo("Export", "No history to export.")

            return

        df = pd.DataFrame(HISTORY)

        path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV","*.csv")])

        if path:

            df.to_csv(path, index=False)

            messagebox.showinfo("Export", f"Saved to {path}")


    def train_nb(self):

        # choose CSV

        path = filedialog.askopenfilename(initialdir="data", filetypes=[("CSV","*.csv")])

        if not path:

            return

        try:

            df = pd.read_csv(path)

            if not set(["text","label"]).issubset(df.columns):

                messagebox.showerror("CSV", "CSV must have 'text' and 'label' columns")

                return

            X = df["text"].astype(str).values

            y = df["label"].astype(str).values

            self.le = LabelEncoder()

            y_enc = self.le.fit_transform(y)


            Xtr, Xte, ytr, yte = train_test_split(X, y_enc, test_size=0.2, random_state=42)

            self.nb_pipe = Pipeline([

                ("tfidf", TfidfVectorizer(max_features=5000, ngram_range=(1,2))),

                ("nb", MultinomialNB())

            ])

            self.nb_pipe.fit(Xtr, ytr)

            dump(self.nb_pipe, NB_PATH)

            np.save(LE_PATH, self.le.classes_)


            # quick report

            ypred = self.nb_pipe.predict(Xte)

            rep = classification_report(yte, ypred, target_names=list(self.le.classes_), zero_division=0)

            messagebox.showinfo("Training done", rep)

        except Exception as e:

            messagebox.showerror("Train NB", str(e))


    def load_nb(self):

        try:

            self.nb_pipe = load(NB_PATH)

            classes = np.load(LE_PATH, allow_pickle=True)

            self.le = LabelEncoder()

            self.le.classes_ = classes

            messagebox.showinfo("Load NB", "Loaded models/nb_model.pkl ✅")

        except Exception as e:

            messagebox.showerror("Load NB", f"Failed: {e}\nTrain the model first.")



if __name__ == "__main__":

    app = App()

    app.mainloop()

Comments

Popular posts from this blog

Is This News Real or Fake? πŸ€– AI Exposes the Truth | FuzzuTech Python App Demo

🚨 Python Intrusion Detection System (IDS) – Real-Time ML + Tkinter GUI Project | FuzzuTech

πŸš€ Simple Login & Registration System in Python Tkinter πŸ“±