Spaces:
Sleeping
Sleeping
Upload 6 files
Browse files- app.py +154 -0
- app_logic.py +91 -0
- database.py +41 -0
- requirements.txt.txt +5 -0
- sentiment.py +70 -0
- visualization.py +40 -0
app.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import database
|
| 3 |
+
import app_logic
|
| 4 |
+
import visualization
|
| 5 |
+
|
| 6 |
+
# 1. Initialize Database Schema and Sample Data on startup
|
| 7 |
+
database.init_db()
|
| 8 |
+
|
| 9 |
+
# --- Helper Functions for UI Logic ---
|
| 10 |
+
|
| 11 |
+
def update_dropdown(category):
|
| 12 |
+
"""
|
| 13 |
+
Triggered when Category changes.
|
| 14 |
+
Updates the Product list and clears the review textbox.
|
| 15 |
+
"""
|
| 16 |
+
names = app_logic.get_products_by_category(category)
|
| 17 |
+
return gr.Dropdown(choices=names, value=names[0] if names else None), ""
|
| 18 |
+
|
| 19 |
+
def refresh_owner_dashboard():
|
| 20 |
+
"""
|
| 21 |
+
Fetches the latest data for the Table and the latest Plotly Chart.
|
| 22 |
+
Used by the Refresh button and automatic updates.
|
| 23 |
+
"""
|
| 24 |
+
table_data = app_logic.get_all_reviews()
|
| 25 |
+
chart_figure = visualization.generate_sentiment_pie_chart()
|
| 26 |
+
return table_data, chart_figure
|
| 27 |
+
|
| 28 |
+
def validate_input(text):
|
| 29 |
+
"""
|
| 30 |
+
Real-time 'Sense Check'.
|
| 31 |
+
Disables the Submit button unless at least 3 words are typed.
|
| 32 |
+
"""
|
| 33 |
+
word_count = len(text.strip().split())
|
| 34 |
+
if word_count >= 3:
|
| 35 |
+
# Enable button and make it primary (colored)
|
| 36 |
+
return gr.update(interactive=True, variant="primary")
|
| 37 |
+
else:
|
| 38 |
+
# Disable button and make it secondary (gray)
|
| 39 |
+
return gr.update(interactive=False, variant="secondary")
|
| 40 |
+
|
| 41 |
+
# --- UI Layout using Gradio Blocks ---
|
| 42 |
+
|
| 43 |
+
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 44 |
+
gr.Markdown("# 👗 Women's Clothing Portal")
|
| 45 |
+
gr.Markdown("### AI-Powered Customer Feedback & Business Intelligence")
|
| 46 |
+
|
| 47 |
+
with gr.Tabs():
|
| 48 |
+
|
| 49 |
+
# TAB 1: CUSTOMER INTERFACE
|
| 50 |
+
with gr.TabItem("Customer"):
|
| 51 |
+
gr.Markdown("#### Submit your Review")
|
| 52 |
+
with gr.Row():
|
| 53 |
+
with gr.Column():
|
| 54 |
+
category_input = gr.Dropdown(
|
| 55 |
+
choices=["Jeans", "Tops", "Kurti", "Leggings"],
|
| 56 |
+
value="Jeans",
|
| 57 |
+
label="Category"
|
| 58 |
+
)
|
| 59 |
+
product_input = gr.Dropdown(choices=[], label="Product Name")
|
| 60 |
+
rating_input = gr.Slider(1, 5, step=1, value=5, label="Rating (1-5)")
|
| 61 |
+
|
| 62 |
+
review_input = gr.Textbox(
|
| 63 |
+
label="Review Text",
|
| 64 |
+
placeholder="Please write at least 3 words about the fit or quality...",
|
| 65 |
+
lines=4
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Submit button starts as DISABLED
|
| 69 |
+
submit_btn = gr.Button("Submit Review", variant="secondary", interactive=False)
|
| 70 |
+
status_output = gr.Textbox(label="Submission Status")
|
| 71 |
+
|
| 72 |
+
# TAB 2: OWNER DASHBOARD
|
| 73 |
+
with gr.TabItem("Owner Dashboard"):
|
| 74 |
+
gr.Markdown("#### Live Sentiment Analytics")
|
| 75 |
+
with gr.Row():
|
| 76 |
+
with gr.Column(scale=2):
|
| 77 |
+
# Plotly Chart from visualization.py
|
| 78 |
+
sentiment_plot = gr.Plot(label="Sentiment Distribution")
|
| 79 |
+
|
| 80 |
+
with gr.Column(scale=1):
|
| 81 |
+
refresh_btn = gr.Button("🔄 Refresh Database", variant="secondary")
|
| 82 |
+
gr.Markdown("Updates the chart and table with the latest customer entries.")
|
| 83 |
+
|
| 84 |
+
data_table = gr.Dataframe(label="Detailed Customer Feedback Log", interactive=False)
|
| 85 |
+
|
| 86 |
+
# TAB 3: AI BUSINESS INSIGHTS
|
| 87 |
+
with gr.TabItem("📈 AI Insights"):
|
| 88 |
+
gr.Markdown("#### Strategic Business Analysis")
|
| 89 |
+
gr.Markdown("Generate a deep-dive report based on all current customer feedback using Generative AI.")
|
| 90 |
+
|
| 91 |
+
generate_report_btn = gr.Button("Generate AI Strategy Report", variant="primary")
|
| 92 |
+
report_output = gr.Markdown(value="*Report will appear here after clicking the button...*")
|
| 93 |
+
|
| 94 |
+
# --- UI Event Listeners ---
|
| 95 |
+
|
| 96 |
+
# 1. Real-time Word Count Validation
|
| 97 |
+
review_input.change(
|
| 98 |
+
fn=validate_input,
|
| 99 |
+
inputs=review_input,
|
| 100 |
+
outputs=submit_btn
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
# 2. Category -> Product Dropdown Sync
|
| 104 |
+
category_input.change(
|
| 105 |
+
fn=update_dropdown,
|
| 106 |
+
inputs=category_input,
|
| 107 |
+
outputs=[product_input, review_input]
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
# 3. Clear review text when product changes
|
| 111 |
+
product_input.change(lambda: "", outputs=review_input)
|
| 112 |
+
|
| 113 |
+
# 4. Main Submission Logic (Saves to DB, Updates Chart, Resets UI)
|
| 114 |
+
submit_btn.click(
|
| 115 |
+
fn=app_logic.save_review,
|
| 116 |
+
inputs=[product_input, rating_input, review_input],
|
| 117 |
+
outputs=status_output
|
| 118 |
+
).then(
|
| 119 |
+
# Auto-update the dashboard
|
| 120 |
+
fn=refresh_owner_dashboard,
|
| 121 |
+
outputs=[data_table, sentiment_plot]
|
| 122 |
+
).then(
|
| 123 |
+
# Clear the text box
|
| 124 |
+
fn=lambda: "", outputs=review_input
|
| 125 |
+
).then(
|
| 126 |
+
# Re-disable the button
|
| 127 |
+
fn=lambda: gr.update(interactive=False, variant="secondary"),
|
| 128 |
+
outputs=submit_btn
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
# 5. Manual Dashboard Refresh
|
| 132 |
+
refresh_btn.click(
|
| 133 |
+
fn=refresh_owner_dashboard,
|
| 134 |
+
outputs=[data_table, sentiment_plot]
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
# 6. AI Report Generation
|
| 138 |
+
generate_report_btn.click(
|
| 139 |
+
fn=app_logic.generate_business_report,
|
| 140 |
+
outputs=report_output
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
# 7. Initial Load (Ensures data is visible when the page first opens)
|
| 144 |
+
demo.load(refresh_owner_dashboard, outputs=[data_table, sentiment_plot])
|
| 145 |
+
demo.load(
|
| 146 |
+
fn=update_dropdown,
|
| 147 |
+
inputs=category_input,
|
| 148 |
+
outputs=[product_input, review_input]
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
# --- Launch the Application ---
|
| 152 |
+
if __name__ == "__main__":
|
| 153 |
+
# Use share=True if you want to create a public link
|
| 154 |
+
demo.launch()
|
app_logic.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
import pandas as pd
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
from database import query_db, DB_NAME
|
| 5 |
+
# UPDATED: Changed from sentiment2 to sentiment
|
| 6 |
+
from sentiment import analyze_sentiment, client
|
| 7 |
+
|
| 8 |
+
def get_products_by_category(category):
|
| 9 |
+
results = query_db("SELECT name FROM products WHERE category = ?", (category,), fetch=True)
|
| 10 |
+
return [row[0] for row in results]
|
| 11 |
+
|
| 12 |
+
def save_review(product_name, rating, review_text):
|
| 13 |
+
"""
|
| 14 |
+
Validates the input, runs sentiment analysis, and saves to SQLite.
|
| 15 |
+
"""
|
| 16 |
+
# 1. Validation: No empty strings
|
| 17 |
+
if not review_text or not review_text.strip():
|
| 18 |
+
return "⚠️ Error: Review text cannot be empty!"
|
| 19 |
+
|
| 20 |
+
# 2. Validation: Minimum 3 words (Matches UI logic)
|
| 21 |
+
word_count = len(review_text.strip().split())
|
| 22 |
+
if word_count < 3:
|
| 23 |
+
return "⚠️ Error: Please write at least 3 words."
|
| 24 |
+
|
| 25 |
+
# 3. Database lookup for product ID
|
| 26 |
+
result = query_db("SELECT id FROM products WHERE name = ?", (product_name,), fetch=True)
|
| 27 |
+
|
| 28 |
+
if result:
|
| 29 |
+
product_id = result[0][0]
|
| 30 |
+
|
| 31 |
+
# 4. Sentiment Analysis (using logic from sentiment.py)
|
| 32 |
+
sentiment = analyze_sentiment(review_text, rating=int(rating))
|
| 33 |
+
|
| 34 |
+
# 5. Save to Database
|
| 35 |
+
date_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 36 |
+
query_db("""INSERT INTO reviews (product_id, rating, review_text, sentiment, date)
|
| 37 |
+
VALUES (?, ?, ?, ?, ?)""",
|
| 38 |
+
(product_id, int(rating), review_text, sentiment, date_str))
|
| 39 |
+
|
| 40 |
+
return f"✅ Saved! Sentiment detected: {sentiment}"
|
| 41 |
+
|
| 42 |
+
return "❌ Error: Product not found."
|
| 43 |
+
|
| 44 |
+
def generate_business_report():
|
| 45 |
+
"""Fetches all reviews and sends them to Groq for a summary report."""
|
| 46 |
+
conn = sqlite3.connect(DB_NAME)
|
| 47 |
+
query = """
|
| 48 |
+
SELECT p.name, r.rating, r.sentiment, r.review_text
|
| 49 |
+
FROM products p JOIN reviews r ON p.id = r.product_id
|
| 50 |
+
"""
|
| 51 |
+
df = pd.read_sql_query(query, conn)
|
| 52 |
+
conn.close()
|
| 53 |
+
|
| 54 |
+
if df.empty:
|
| 55 |
+
return "No data found. Collect some reviews first!"
|
| 56 |
+
|
| 57 |
+
# Create a string of all reviews for the AI to read
|
| 58 |
+
review_summary = ""
|
| 59 |
+
for _, row in df.iterrows():
|
| 60 |
+
review_summary += f"- {row['name']} ({row['rating']} stars, {row['sentiment']}): {row['review_text']}\n"
|
| 61 |
+
|
| 62 |
+
prompt = f"Analyze these clothing reviews and provide a summary of satisfaction, top issues, and an action plan:\n\n{review_summary}"
|
| 63 |
+
|
| 64 |
+
try:
|
| 65 |
+
# Uses the client imported from sentiment.py
|
| 66 |
+
completion = client.chat.completions.create(
|
| 67 |
+
messages=[
|
| 68 |
+
{"role": "system", "content": "You are a concise business consultant."},
|
| 69 |
+
{"role": "user", "content": prompt}
|
| 70 |
+
],
|
| 71 |
+
model="llama-3.1-8b-instant",
|
| 72 |
+
temperature=0.5,
|
| 73 |
+
max_tokens=500
|
| 74 |
+
)
|
| 75 |
+
return completion.choices[0].message.content
|
| 76 |
+
except Exception as e:
|
| 77 |
+
return f"Error: {str(e)}"
|
| 78 |
+
|
| 79 |
+
def get_all_reviews():
|
| 80 |
+
"""Returns a DataFrame for the Owner's table."""
|
| 81 |
+
conn = sqlite3.connect(DB_NAME)
|
| 82 |
+
try:
|
| 83 |
+
query = """
|
| 84 |
+
SELECT p.category, p.name as product_name, r.rating, r.sentiment, r.review_text, r.date
|
| 85 |
+
FROM products p
|
| 86 |
+
JOIN reviews r ON p.id = r.product_id
|
| 87 |
+
ORDER BY r.date DESC
|
| 88 |
+
"""
|
| 89 |
+
return pd.read_sql_query(query, conn)
|
| 90 |
+
finally:
|
| 91 |
+
conn.close()
|
database.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
|
| 3 |
+
DB_NAME = "clothing_reviews.db"
|
| 4 |
+
|
| 5 |
+
def init_db():
|
| 6 |
+
conn = sqlite3.connect(DB_NAME)
|
| 7 |
+
cursor = conn.cursor()
|
| 8 |
+
cursor.execute('''CREATE TABLE IF NOT EXISTS products
|
| 9 |
+
(id INTEGER PRIMARY KEY, category TEXT, name TEXT)''')
|
| 10 |
+
cursor.execute('''CREATE TABLE IF NOT EXISTS reviews
|
| 11 |
+
(id INTEGER PRIMARY KEY, product_id INTEGER,
|
| 12 |
+
rating INTEGER, review_text TEXT,
|
| 13 |
+
sentiment TEXT, date TEXT)''')
|
| 14 |
+
|
| 15 |
+
cursor.execute("SELECT COUNT(*) FROM products")
|
| 16 |
+
if cursor.fetchone()[0] == 0:
|
| 17 |
+
sample_products = [
|
| 18 |
+
('Kurti', 'Embroidered Cotton Chikankari Kurti'),
|
| 19 |
+
('Jeans', 'High-Waisted Distressed Skinny Jeans'),
|
| 20 |
+
('Tops', 'Floral Print Peplum Blouse'),
|
| 21 |
+
('Leggings', 'High-Performance Seamless Yoga Leggings'),
|
| 22 |
+
('Kurti', 'Anarkali Style Floor-Length Kurti'),
|
| 23 |
+
('Tops', 'Oversized Linen Button-Down Shirt'),
|
| 24 |
+
('Jeans', 'Classic Straight-Leg Denim'),
|
| 25 |
+
('Leggings', 'Fleece-Lined Winter Thermal Leggings'),
|
| 26 |
+
('Kurti', 'Straight-Cut Rayon Daily Wear Kurti'),
|
| 27 |
+
('Tops', 'Silk Camisole with Lace Trim')
|
| 28 |
+
]
|
| 29 |
+
cursor.executemany("INSERT INTO products (category, name) VALUES (?, ?)", sample_products)
|
| 30 |
+
|
| 31 |
+
conn.commit()
|
| 32 |
+
conn.close()
|
| 33 |
+
|
| 34 |
+
def query_db(query, params=(), fetch=False):
|
| 35 |
+
conn = sqlite3.connect(DB_NAME)
|
| 36 |
+
cursor = conn.cursor()
|
| 37 |
+
cursor.execute(query, params)
|
| 38 |
+
result = cursor.fetchall() if fetch else None
|
| 39 |
+
conn.commit()
|
| 40 |
+
conn.close()
|
| 41 |
+
return result
|
requirements.txt.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
groq
|
| 3 |
+
python-dotenv
|
| 4 |
+
pandas
|
| 5 |
+
plotly
|
sentiment.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
from groq import Groq
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
client = Groq(api_key=os.environ.get("GROQ_API_KEY"))
|
| 8 |
+
|
| 9 |
+
def is_meaningful(text):
|
| 10 |
+
"""Checks if the text contains actual words or just gibberish."""
|
| 11 |
+
text_stripped = text.strip()
|
| 12 |
+
|
| 13 |
+
# 1. Basic length check (already enforced by UI, but good for safety)
|
| 14 |
+
if len(text_stripped) < 3:
|
| 15 |
+
return False
|
| 16 |
+
|
| 17 |
+
# 2. Check for vowel ratio (catches 'bdfghj')
|
| 18 |
+
vowels = len(re.findall(r'[aeiouAEIOU]', text_stripped))
|
| 19 |
+
total_chars = len(re.findall(r'[a-zA-Z]', text_stripped))
|
| 20 |
+
if total_chars > 0 and (vowels / total_chars) < 0.1:
|
| 21 |
+
return False
|
| 22 |
+
|
| 23 |
+
# 3. Check for repetitive characters (catches 'aaaaaa')
|
| 24 |
+
if re.search(r'(.)\1{4,}', text_stripped):
|
| 25 |
+
return False
|
| 26 |
+
|
| 27 |
+
return True
|
| 28 |
+
|
| 29 |
+
def analyze_sentiment(review_text, rating=3):
|
| 30 |
+
"""
|
| 31 |
+
Core sentiment logic. Gibberish is filtered locally;
|
| 32 |
+
meaningful text is analyzed by Llama 3.1.
|
| 33 |
+
"""
|
| 34 |
+
text_clean = str(review_text).strip()
|
| 35 |
+
|
| 36 |
+
# --- STEP 1: SENSE CHECK (GIBBERISH FILTER) ---
|
| 37 |
+
if not is_meaningful(text_clean):
|
| 38 |
+
return "Neutral"
|
| 39 |
+
|
| 40 |
+
# --- STEP 2: AI LOGIC ---
|
| 41 |
+
# Enhanced prompt to ensure 'bad product' is caught as Negative
|
| 42 |
+
system_msg = (
|
| 43 |
+
"You are a strict sentiment classifier. Output ONLY one word: Positive, Negative, or Neutral. "
|
| 44 |
+
"Priority Rule: If the text contains negative words (bad, poor, hate, worst), "
|
| 45 |
+
"the output MUST be Negative, regardless of the star rating."
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
user_prompt = f"Review Text: '{text_clean}'\nStar Rating: {rating}/5\nSentiment:"
|
| 49 |
+
|
| 50 |
+
try:
|
| 51 |
+
chat_completion = client.chat.completions.create(
|
| 52 |
+
messages=[
|
| 53 |
+
{"role": "system", "content": system_msg},
|
| 54 |
+
{"role": "user", "content": user_prompt}
|
| 55 |
+
],
|
| 56 |
+
model="llama-3.1-8b-instant",
|
| 57 |
+
temperature=0, # Zero temperature ensures the most predictable result
|
| 58 |
+
max_tokens=3
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
# Clean the output from the AI
|
| 62 |
+
result = chat_completion.choices[0].message.content.strip().replace(".", "")
|
| 63 |
+
|
| 64 |
+
if result in ["Positive", "Negative", "Neutral"]:
|
| 65 |
+
return result
|
| 66 |
+
return "Neutral"
|
| 67 |
+
|
| 68 |
+
except Exception as e:
|
| 69 |
+
print(f"Error calling Groq API: {e}")
|
| 70 |
+
return "Neutral"
|
visualization.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import plotly.express as px
|
| 4 |
+
from database import DB_NAME
|
| 5 |
+
|
| 6 |
+
def generate_sentiment_pie_chart():
|
| 7 |
+
"""Fetches data and returns a Plotly Pie Chart figure."""
|
| 8 |
+
conn = sqlite3.connect(DB_NAME)
|
| 9 |
+
# Fetch only the sentiment column
|
| 10 |
+
df = pd.read_sql_query("SELECT sentiment FROM reviews", conn)
|
| 11 |
+
conn.close()
|
| 12 |
+
|
| 13 |
+
if df.empty:
|
| 14 |
+
# Return a simple figure with a message if no reviews exist
|
| 15 |
+
fig = px.pie(title="No reviews collected yet.")
|
| 16 |
+
return fig
|
| 17 |
+
|
| 18 |
+
# Process data for the chart
|
| 19 |
+
counts = df['sentiment'].value_counts().reset_index()
|
| 20 |
+
counts.columns = ['Sentiment', 'Total']
|
| 21 |
+
|
| 22 |
+
# Create the chart
|
| 23 |
+
fig = px.pie(
|
| 24 |
+
counts,
|
| 25 |
+
values='Total',
|
| 26 |
+
names='Sentiment',
|
| 27 |
+
title='Customer Sentiment Distribution',
|
| 28 |
+
# Professional color mapping
|
| 29 |
+
color='Sentiment',
|
| 30 |
+
color_discrete_map={
|
| 31 |
+
'Positive': '#2ecc71', # Emerald Green
|
| 32 |
+
'Negative': '#e74c3c', # Alizarin Red
|
| 33 |
+
'Neutral': '#f1c40f' # Sunflower Yellow
|
| 34 |
+
}
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
# Update layout for a cleaner look
|
| 38 |
+
fig.update_layout(showlegend=True, margin=dict(t=50, b=20, l=20, r=20))
|
| 39 |
+
|
| 40 |
+
return fig
|