BinduRP commited on
Commit
333f068
·
verified ·
1 Parent(s): 4df1a98

Upload 6 files

Browse files
Files changed (6) hide show
  1. app.py +154 -0
  2. app_logic.py +91 -0
  3. database.py +41 -0
  4. requirements.txt.txt +5 -0
  5. sentiment.py +70 -0
  6. 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