File size: 12,573 Bytes
5416910
 
 
b242c07
966b33f
5416910
8516c1d
a270e7f
966b33f
 
 
 
5416910
8516c1d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
966b33f
 
 
 
 
 
 
 
 
8516c1d
94c1663
 
 
 
 
 
 
 
 
 
 
 
 
 
 
966b33f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8516c1d
 
 
 
 
 
 
 
b242c07
 
5416910
8516c1d
5416910
4be6a6f
5416910
 
 
8516c1d
e604217
 
b3f84bb
 
 
5416910
 
b3f84bb
5416910
8516c1d
4d46b53
3ff7d8a
e604217
4be6a6f
e604217
 
 
4be6a6f
8e4eea0
4be6a6f
8e4eea0
 
5416910
4be6a6f
5416910
 
 
 
 
 
966b33f
 
 
 
 
 
 
 
8516c1d
5416910
 
 
 
 
 
 
 
 
 
 
 
 
 
b242c07
 
8516c1d
5416910
8516c1d
 
 
 
5416910
 
 
 
 
 
 
b242c07
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6968fa9
10e8a59
b242c07
 
 
 
10e8a59
 
b242c07
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8516c1d
b242c07
 
8e4eea0
 
 
 
b242c07
 
 
 
 
 
 
 
8e4eea0
8516c1d
b242c07
8e4eea0
b242c07
 
8516c1d
8e4eea0
 
8516c1d
8e4eea0
8516c1d
b242c07
 
 
75d4cd3
 
b242c07
 
 
8516c1d
 
 
 
 
 
 
 
b242c07
 
966b33f
 
 
 
 
5416910
f6784bd
966b33f
 
 
 
 
 
 
 
 
 
 
f6784bd
966b33f
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
import os
import pandas as pd
import numpy as np
import re
import gradio as gr
from openai import OpenAI
import base64

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.getenv("OPENROUTER_API_KEY")  
)

# ---------------- IMAGE MODERATION FUNCTION ---------------- #
def check_image_safety(image_paths):
    """
    Check one or multiple images for unsafe or inappropriate content.
    Returns 'unsafe' if any image is inappropriate, otherwise 'safe'.
    """
    if not image_paths:
        return "none"

    # Ensure list format
    if isinstance(image_paths, str):
        image_paths = [image_paths]

    try:
        for image_path in image_paths:
            with open(image_path, "rb") as img_file:
                img_base64 = base64.b64encode(img_file.read()).decode("utf-8")

            response = client.chat.completions.create(
                model="openai/gpt-5-mini",
                messages=[
                    {
                        "role": "user",
                        "content": [
                            {
                                "type": "text",
                                "text": """Analyze this image carefully for any unsafe or inappropriate content.

You must respond with EXACTLY ONE WORD: 'safe' or 'unsafe'. Do NOT explain.

Rules (follow strictly and literally):

1. If the image contains any nudity, partial nudity, sexual body parts, sexually suggestive poses, shirtless bodies, or vulgar gestures β€” respond 'unsafe'.
2. If the image contains ANY indirect sexual symbolism, adult humor, or innuendo β€” even if cartoonish, humorous, or metaphorical (e.g. bananas, donuts, or vegetables arranged suggestively) β€” respond 'unsafe'.
3. If the image contains violence, hate symbols, drug use, blood, or disturbing scenes β€” respond 'unsafe'.
4. If the image is fully normal, decent, and appropriate (e.g. nature, products, people in regular poses) β€” respond 'safe'.

Remember:
- Suggestive combinations or metaphors (like banana + donut imagery) = 'unsafe'.
- If unsure, choose 'unsafe'.

Return only: safe OR unsafe.
"""

                            },
                            {
                                "type": "image_url",
                                "image_url": f"data:image/jpeg;base64,{img_base64}"
                            }
                        ]
                    }
                ],
                temperature=0.5,
                max_tokens=200,
            )

            verdict = response.choices[0].message.content.strip().lower()
            if "unsafe" in verdict:
                return "unsafe"

        return "safe"

    except Exception as e:
        print("Image moderation error:", str(e))
        return "error"


# ---------------- SENTIMENT FUNCTION ---------------- #
def get_sentiment(review_text: str) -> str:
    prompt = f"""
Classify sentiment of this review in English or Roman Urdu.
Return EXACTLY one word: positive, negative.

Rules (priority order, MUST follow strictly):

1. If the review contains any abusive, offensive, vulgar, or insulting words
   in English or Roman Urdu (e.g., bad words, slang, or personal attacks),
   ALWAYS respond with "negative".
   This overrides ALL other rules β€” even if the rest of the review is positive.

2. If the review mentions problems with Priceoye's website, service, delivery,
   shipping time, support, or Priceoye as a company/brand overall,
   ALWAYS respond with "negative".
   This overrides everything else except abusive language.

3. If the review only criticizes or complains about a product
   (e.g., "yeh phone bekaar hai", "battery weak hai","bohut ghatiya product")
   but does NOT contain abusive words,
   DO NOT treat it as negative β€” respond based on the overall tone:
   - If it seems disappointed but polite β†’ "positive"
   - If it praises other things β†’ "positive"

4. If both positive and negative opinions are present but not directly about
   Priceoye's website, service, or delivery β†’ respond "positive".
   
5. If the review mentions fraud, repacked, fake, reputation damage,
   or loss of trust related to the company or seller,
   ALWAYS respond "negative" even if product praise exists.

6. Respond with only one word.
Do NOT explain. Do NOT add anything else.

Review: {review_text}
Sentiment:
"""
    try:
        response = client.chat.completions.create(
            model="openai/gpt-5-mini",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.2,
            max_tokens=500
        )

        raw = response.choices[0].message.content or ""
        raw_lower = raw.lower().strip()

        if "positive" in raw_lower:
            return "positive"
        elif "negative" in raw_lower:
            return "negative"
        elif "neutral" in raw_lower:
            return "neutral"
        else:
            return "neutral"

    except Exception as e:
        print("Error from API:", str(e))
        return "neutral"


# ---------------- VALIDATION FUNCTION ---------------- #
def validate_review(description, rating, image_paths=None):
    LLM = get_sentiment(description)
    image_flag = "none"

    if image_paths is not None and len(image_paths) > 0:
        image_flag = check_image_safety(image_paths)

    data = pd.DataFrame({
        'LLM': [LLM],
        'description': [description],
        'rating': [rating]
    })

    Positive_Strings = ['worth it','Thumbs up','shukriya','Shukriya','Fantastic product','Thx',
        'Love the product','Love','love','LOVE','applause','exceptional','Alhumdulillah',
        'Allhamdulliah','Alhamdulillah','Masha Allah','mashallah','Mashallah','MaShaAllah',
        'Allah','Thank','thank','Thanks','Thank you','thank you','Behtareen','Hats off',
        'Behtereen','behtareen','awesome','Awesome','VIP','vip','Vip','Best','best','good product',
        'Good packing','nyc','nice','Nice','Allaw','allaw','Completely satisfied','orginal product',
        'thnx','Acha product','I am satisfied','impressed','ZabbarDast','Zabrdast','zabrdast',
        'Zbrdast','Satisfactory','satisfactory','Bht zada acha','achi quality','Satisfied with Product',
        'satisfying','Not bad','shukariya','shukria','acchi hai','Outstanding','keep it up','Aala product',
        'geniue','safe','Wonderful','Great service','Great quality','Good quality','good quality',
        'Bhut he Aala','happy','Happy','#Trusted','#trusted','Trusted','trusted','perfect','Perfect',
        'Excellent','appreciated','Highly recommend','Amazing','glad','Outstanding','Bhot khoob',
        'Good service','Totally satisfied','Nice product','decent','value for money','value of money',
        'Original Product','behtreeen','5 stars','Original product','Bohat ache','Unbeatable prices',
        '10/10','great product','Impressive','Very good service','good service','100% satisfied',
        'Zbardast','Zabardast','Good e-commerce web site','Good web site','good web site',
        'Good experience','good experience','Genuine','pretty good','high quality','Product original',
        'Product is original','Shandar product','better quality','Good overall','Genuine product',
        '100% original','Fantastic','fantastic']

    Negative_Strings = ['Fraud','fraud','slow delivery','Slow Delivery','Slow delivery',
        'Scam','scam','SCAM','too late','late','Late','repack','Repack','REPACK','Repacked','repacked','REPACKED','faulty','Faulty',
        'FAULT','FAULTY','damaged','Damaged','Not Working','not working','Not working','non pta','Non PTA',
        'NON PTA','complaint','Complain','COMPLAINT','kam nhi','Kam ni','work ni','chal ni','Chal nhi',
        'CHAL Ni','Kharab','Khrab','Kharb']

    ignore_keywords = []

    # --- ACCEPT CONDITIONS ---
    conditions_accepted_1 = (
        ((data['description'].astype(str).str.len() == 0) & (data['rating'].astype(str).str.contains('4|5'))) |
        ((data['description'].astype(str).str.contains('|'.join(Positive_Strings), case=False)) &
         (data['rating'].astype(str).str.contains('4|5')) &
         (data['LLM'].str.contains('positive|neutral', case=False)))
    )

    conditions_accepted_2 = (
        (data['LLM'].str.contains('positive', case=False)) &
        (data['rating'].astype(str).str.contains('3')) &
        (data['description'].astype(str).str.contains('|'.join(Positive_Strings), case=False))
    )

    conditions_accepted_3 = (
        (data['description'].astype(str).str.len() == 0) &
        (data['rating'].astype(str).str.contains('3'))
    )

    conditions_accepted_4 = (
        (data['LLM'].str.contains('positive', case=False)) &
        (data['rating'].astype(str).str.contains('4|5'))
    )
    conditions_accepted_positive_any_rating = data['LLM'].str.contains('positive', case=False)
    description_null = data['description'].astype(str).str.strip().str.lower() == "null"
    conditions_accepted_null = description_null & data['rating'].astype(str).str.contains('3|4|5')
    conditions_accepted_neutral_with_positive_keywords = (
                        data['LLM'].str.contains('neutral', case=False) &
                        data['description'].str.contains('|'.join(Positive_Strings), case=False)
                    )

    # --- REJECT CONDITIONS ---
    conditions_rejected_1 = (data['rating'].astype(str).str.contains('1|2')) & (data['description'].astype(str).str.len() == 0)
    conditions_rejected_2 = (data['description'].astype(str).str.contains('|'.join(Negative_Strings), case=False)) & \
                            (data['rating'].astype(str).str.contains('1|2|3|4|5')) & \
                            (data['LLM'].str.contains('positive|negative|neutral', case=False))
    conditions_rejected_3 = (data['LLM'].str.contains('negative', case=False)) & \
                            (data['description'].astype(str).str.contains('|'.join(Negative_Strings), case=False))
    conditions_rejected_4 = ( (data['LLM'].str.contains('neutral', case=False)) & (data['rating'].astype(str).str.contains('1|2')) & ~(data['description'].str.contains('|'.join(Positive_Strings), case=False)) )
    conditions_rejected_5 = (data['LLM'].str.contains('negative', case=False)) & ~(data['description'].astype(str).str.contains('|'.join(Negative_Strings), case=False))
    conditions_rejected_null = description_null & data['rating'].astype(str).str.contains('1|2')
    conditions_rejected_neutral_negative_words = (data['LLM'].str.contains('neutral')) &  (data['description'].str.contains('|'.join(Negative_Strings), case=False))

    # Final flags
    conditions_accepted = conditions_accepted_1 | conditions_accepted_2 | conditions_accepted_3 | \
                          conditions_accepted_4 | \
                          conditions_accepted_positive_any_rating | conditions_accepted_null |conditions_accepted_neutral_with_positive_keywords
    conditions_rejected = conditions_rejected_1 | conditions_rejected_2 | conditions_rejected_3 | \
                          conditions_rejected_4 | conditions_rejected_5 | conditions_rejected_null |conditions_rejected_neutral_negative_words
    conditions_ignored = ((~(conditions_rejected | conditions_accepted)) |
                          (data['description'].astype(str).str.contains('|'.join(ignore_keywords), case=False)))

    data['Labeled Result'] = np.select(
        [conditions_rejected,conditions_accepted, conditions_ignored],
        ['rejected','accepted', 'ignored'],
        default='ignored'
    )

    # --- IMAGE-BASED OVERRIDES ---
    if image_flag == "unsafe":
        data['Labeled Result'] = "rejected"
        LLM = "negative"
    elif image_flag == "safe" and LLM == "negative":
        data['Labeled Result'] = "rejected"

    return data['Labeled Result'][0], LLM, image_flag


# ---------------- GRADIO INTERFACE ---------------- #
def classify_review(description, rating, images):
    image_paths = [img.name for img in images] if images else None
    result, sentiment, image_flag = validate_review(description, rating, image_paths)
    return f"Sentiment: {sentiment}\nImage Safety: {image_flag}\nDecision: {result}"


iface = gr.Interface(
    fn=classify_review,
    inputs=[
        gr.Textbox(label="Review Description"),
        gr.Radio(["1", "2", "3", "4", "5"], label="Rating"),
        gr.Files(label="Upload Images (optional)")
    ],
    outputs="text",
    title="Priceoye Review Classifier (with Image Moderation)",
    description="Classifies reviews as accepted/rejected/ignored using LLM + rule logic + image safety check."
)

if __name__ == "__main__":
    iface.launch()