Spaces:
Running
Running
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() |