tejasashinde commited on
Commit
a514b2a
·
1 Parent(s): 750f12f

Added Gemini support and refactored code

Browse files
Files changed (2) hide show
  1. app.py +151 -83
  2. requirements.txt +2 -1
app.py CHANGED
@@ -1,140 +1,208 @@
1
- import gradio as gr
2
  import base64
3
- from io import BytesIO
 
4
  import re
 
5
  from PIL import Image
 
6
  from openai import OpenAI
 
 
 
 
 
 
7
 
8
- # Helper to convert image to base64
9
  def image_to_base64(image: Image.Image) -> str:
 
10
  buffered = BytesIO()
11
  image.save(buffered, format="PNG")
12
  return base64.b64encode(buffered.getvalue()).decode()
13
 
14
- # 🧠 Main logic function
15
- def recyclo_advisor(item_description, item_image, location, api_key):
16
- if not api_key or api_key.strip() == "":
17
- return "❗ Please enter a valid Nebius API key.", "", "", ""
18
-
 
 
 
 
 
 
 
 
 
 
 
19
  try:
20
- client = OpenAI(
21
- base_url="https://api.studio.nebius.com/v1/",
22
- api_key=api_key
23
- )
24
- except Exception as e:
25
- return " API key error", "", "", str(e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- try:
28
- messages = [
29
- {
30
- "role": "system",
31
- "content": (
32
- "You are a recycling and waste management expert. "
33
- "Your job is to help users determine whether an item is recyclable, and if not, guide them on responsible disposal based on their location. "
34
- "Be specific, practical, and locally relevant.\n\n"
35
- "Always format your response as follows:\n"
36
- "Item Name: <clear name>\n"
37
- "1. ♻️ Recyclability Status: Recyclable / Not Recyclable / Depends\n"
38
- "2. 🧾 Instructions: What should the user do with the item?\n"
39
- "3. ℹ️ Reasoning: Why is this the right action in the selected location?"
40
- )
41
- }
42
- ]
43
 
44
- user_content = []
45
- description_text = f"The user is located in: {location}.\n"
46
- if item_description:
47
- description_text += f'They described the item as: "{item_description}".'
48
- else:
49
- description_text += "No description provided."
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- user_content.append({"type": "text", "text": description_text})
 
 
52
 
53
  if item_image:
54
- image_b64 = image_to_base64(item_image)
55
  user_content.append({
56
  "type": "image_url",
57
- "image_url": {"url": f"data:image/png;base64,{image_b64}"}
58
  })
59
 
60
- messages.append({"role": "user", "content": user_content})
61
-
62
- response = client.chat.completions.create(
63
- model="google/gemma-3-27b-it",
64
- messages=messages,
65
- max_tokens=2048,
66
- temperature=0.6,
67
- top_p=0.9
68
- )
69
-
70
- full_response = response.choices[0].message.content.strip()
71
-
72
- # Extract Item Name, Status, Instructions, Reasoning
73
- item_match = re.search(r"Item Name:\s*(.*)", full_response)
74
- item_name = item_match.group(1).strip() if item_match else "This item"
75
-
76
- status_match = re.search(r"1\.\s*♻️ Recyclability Status:\s*(.*)", full_response)
77
- status_raw = status_match.group(1).strip() if status_match else "Unknown"
78
 
79
- instructions_match = re.search(r"2\.\s*🧾 Instructions:\s*(.*?)(?:3\.|$)", full_response, re.DOTALL)
80
- instructions = instructions_match.group(1).strip() if instructions_match else ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
- reasoning_match = re.search(r"3\.\s*ℹ️ Reasoning:\s*(.*)", full_response, re.DOTALL)
83
- reasoning = reasoning_match.group(1).strip() if reasoning_match else ""
 
84
 
85
- # Label summary with item name included
86
- if "Not Recyclable" in status_raw:
87
  label = f"❌ {item_name} is Not Recyclable"
88
- elif "Depends" in status_raw:
89
  label = f"⚠️ {item_name} recyclability Depends"
90
  else:
91
  label = f"✅ {item_name} is Recyclable"
92
 
93
- # Basic summary report
94
  markdown_summary = f"""
95
  ### ♻️ **Recyclability Report for `{item_name}`**
96
  **Recyclability Status:**
97
- {status_raw}
98
  """
99
 
100
- return label, markdown_summary, instructions, reasoning
101
 
102
  except Exception as e:
103
- return "Error calling Nebius LLM", "", "", str(e)
 
 
 
 
 
104
 
105
- # 🎨 Gradio UI
106
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
107
  gr.Markdown("## ♻️ RecycloBot – AI-Powered Recycling Advisor")
108
  gr.Markdown("Upload an item photo or describe it to get location-aware recycling guidance.")
109
 
110
  with gr.Row():
111
  with gr.Column(scale=1):
 
 
 
 
 
 
112
  api_key = gr.Textbox(
113
- label="🔐 Nebius API Key",
114
- placeholder="Paste your Nebius API key here",
115
  type="password"
116
  )
117
 
 
 
 
 
 
118
  item_image = gr.Image(label="📷 Upload Image (Optional)", type="pil")
119
-
120
  item_description = gr.Textbox(
121
- label="📝 Describe the Item",
122
  placeholder="e.g., 'USB cable', 'Greasy pizza box'"
123
  )
124
 
125
  location = gr.Textbox(
 
126
  placeholder="Please input your location. e.g., Country and/or state name",
127
- value="USA",
128
- label="🌍 Your Region"
129
  )
130
 
131
  submit_btn = gr.Button("🚀 Analyze Item")
132
-
133
  examples = gr.Examples(
134
- examples=[
135
- ["broken phone", "img/broken_phone.jpg", "India"],
136
- ["charger", "img/charger.jpg", "London, England"]
137
- ],
138
  inputs=[item_description, item_image, location],
139
  label="🧪 Try an Example"
140
  )
@@ -142,14 +210,14 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
142
  with gr.Column(scale=1):
143
  status_output = gr.Label(label="♻️ Item Recyclability Summary")
144
  summary_output = gr.Markdown(label="📋 Recyclability Report")
145
- with gr.Accordion("📦 What To Do With It", open=False):
146
  instructions_output = gr.Markdown()
147
- with gr.Accordion("ℹ️ Why This Matters", open=False):
148
  reasoning_output = gr.Markdown()
149
 
150
  submit_btn.click(
151
  fn=recyclo_advisor,
152
- inputs=[item_description, item_image, location, api_key],
153
  outputs=[status_output, summary_output, instructions_output, reasoning_output]
154
  )
155
 
 
 
1
  import base64
2
+ import json
3
+ import io
4
  import re
5
+ from io import BytesIO
6
  from PIL import Image
7
+ import gradio as gr
8
  from openai import OpenAI
9
+ from google import genai
10
+ from google.genai import types
11
+
12
+ # ------------------ #
13
+ # Utility Functions
14
+ # ------------------ #
15
 
 
16
  def image_to_base64(image: Image.Image) -> str:
17
+ """Convert a PIL image to base64 string."""
18
  buffered = BytesIO()
19
  image.save(buffered, format="PNG")
20
  return base64.b64encode(buffered.getvalue()).decode()
21
 
22
+ def parse_nebius_response(content: str):
23
+ """Parse the response content from Nebius."""
24
+ item_name = re.search(r"Item Name:\s*(.*)", content)
25
+ status = re.search(r"1\.\s*Recyclability Status:\s*(.*)", content)
26
+ instructions = re.search(r"2\.\s*Instructions:\s*(.*?)(?:3\.|$)", content, re.DOTALL)
27
+ reasoning = re.search(r"3\.\s*Reasoning:\s*(.*)", content, re.DOTALL)
28
+
29
+ return {
30
+ "item_name": item_name.group(1).strip() if item_name else "This item",
31
+ "status": status.group(1).strip() if status else "Unknown",
32
+ "instructions": instructions.group(1).strip() if instructions else "",
33
+ "reasoning": reasoning.group(1).strip() if reasoning else ""
34
+ }
35
+
36
+ def parse_gemini_response(response_text: str):
37
+ """Parse the JSON string response from Gemini provider."""
38
  try:
39
+ data = json.loads(response_text)
40
+ except json.JSONDecodeError:
41
+ data = {}
42
+ return {
43
+ "item_name": data.get("Item Name", "This item").strip(),
44
+ "status": data.get("1. Recyclability Status", "").strip(),
45
+ "instructions": data.get("2. Instructions", "").strip(),
46
+ "reasoning": data.get("3. Reasoning", "").strip()
47
+ }
48
+
49
+ def build_prompt(item_description: str, location: str):
50
+ """Build system and user prompts."""
51
+ system_prompt = (
52
+ "You are a recycling and waste management expert. "
53
+ "Your job is to help users determine whether an item is recyclable, and if not, guide them on responsible disposal based on their location. "
54
+ "Be specific, practical, and locally relevant.\n\n"
55
+ "Always format your response as follows:\n"
56
+ "Item Name: <clear name>\n"
57
+ "1. Recyclability Status: Recyclable / Not Recyclable / Depends\n"
58
+ "2. Instructions: What should the user do with the item?\n"
59
+ "3. Reasoning: Why is this the right action in the selected location?"
60
+ )
61
 
62
+ description = f"The user is located in: {location}.\n"
63
+ description += f'They described the item as: "{item_description}".' if item_description else "No description provided."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
+ return system_prompt, description
66
+
67
+ def validate_inputs(api_key, item_image, location):
68
+ """Validate required inputs."""
69
+ if not api_key or api_key.strip() == "":
70
+ raise gr.Error("🔐 API Key is required.")
71
+ if not item_image:
72
+ raise gr.Error("📷 Please upload image of the item.")
73
+ if not location or location.strip() == "":
74
+ raise gr.Error("🌍 Please enter your region.")
75
+
76
+ # ------------------------- #
77
+ # Main Processing Function
78
+ # ------------------------- #
79
+
80
+ def recyclo_advisor(item_description, item_image, location, api_key, provider):
81
+ """Main advisor logic: processes the image and description using the chosen provider."""
82
+ validate_inputs(api_key, item_image, location)
83
 
84
+ try:
85
+ system_prompt, user_text = build_prompt(item_description, location)
86
+ user_content = [{"type": "text", "text": user_text}]
87
 
88
  if item_image:
89
+ img_b64 = image_to_base64(item_image)
90
  user_content.append({
91
  "type": "image_url",
92
+ "image_url": {"url": f"data:image/png;base64,{img_b64}"}
93
  })
94
 
95
+ messages = [
96
+ {"role": "system", "content": system_prompt},
97
+ {"role": "user", "content": user_content}
98
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
+ if provider == "Nebius":
101
+ client = OpenAI(
102
+ base_url="https://api.studio.nebius.com/v1/",
103
+ api_key=api_key
104
+ )
105
+ response = client.chat.completions.create(
106
+ model="google/gemma-3-27b-it",
107
+ messages=messages,
108
+ max_tokens=2048,
109
+ temperature=0.6,
110
+ top_p=0.9
111
+ )
112
+ full_response = response.choices[0].message.content.strip()
113
+ result = parse_nebius_response(full_response)
114
+
115
+ else: # Gemini
116
+ client = genai.Client(api_key=api_key)
117
+ prompt = system_prompt + "\n" + user_text
118
+
119
+ image_obj = None
120
+ for part in user_content:
121
+ if part["type"] == "image_url":
122
+ b64_data = part["image_url"]["url"].split(",")[1]
123
+ image_bytes = base64.b64decode(b64_data)
124
+ image_obj = Image.open(io.BytesIO(image_bytes))
125
+ break
126
+
127
+ if not image_obj:
128
+ raise ValueError("No image provided for Gemini provider")
129
+
130
+ config = types.GenerateContentConfig(response_mime_type="application/json")
131
+ response = client.models.generate_content(
132
+ model="gemini-2.5-flash",
133
+ contents=[image_obj, prompt],
134
+ config=config
135
+ )
136
+ result = parse_gemini_response(response.text.strip())
137
 
138
+ # Label formatting
139
+ status = result["status"]
140
+ item_name = result["item_name"]
141
 
142
+ if "Not Recyclable" in status:
 
143
  label = f"❌ {item_name} is Not Recyclable"
144
+ elif "Depends" in status:
145
  label = f"⚠️ {item_name} recyclability Depends"
146
  else:
147
  label = f"✅ {item_name} is Recyclable"
148
 
 
149
  markdown_summary = f"""
150
  ### ♻️ **Recyclability Report for `{item_name}`**
151
  **Recyclability Status:**
152
+ {status}
153
  """
154
 
155
+ return label, markdown_summary.strip(), result["instructions"], result["reasoning"]
156
 
157
  except Exception as e:
158
+ print(f"[Error] {e}")
159
+ return "❌ Error calling API Endpoint", "", "", str(e)
160
+
161
+ # ----------- #
162
+ # Gradio UI
163
+ # ----------- #
164
 
 
165
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
166
  gr.Markdown("## ♻️ RecycloBot – AI-Powered Recycling Advisor")
167
  gr.Markdown("Upload an item photo or describe it to get location-aware recycling guidance.")
168
 
169
  with gr.Row():
170
  with gr.Column(scale=1):
171
+ provider_dropdown = gr.Dropdown(
172
+ choices=["Nebius", "Gemini"],
173
+ value="Nebius",
174
+ label="🧠 Select Provider"
175
+ )
176
+
177
  api_key = gr.Textbox(
178
+ label="🔐 API Key (Nebius)",
179
+ placeholder="Paste your API key here",
180
  type="password"
181
  )
182
 
183
+ def update_label(provider):
184
+ return gr.update(label=f"🔐 API Key ({provider})")
185
+
186
+ provider_dropdown.change(fn=update_label, inputs=provider_dropdown, outputs=api_key)
187
+
188
  item_image = gr.Image(label="📷 Upload Image (Optional)", type="pil")
189
+
190
  item_description = gr.Textbox(
191
+ label="📝 Describe the Item (OPTIONAL)",
192
  placeholder="e.g., 'USB cable', 'Greasy pizza box'"
193
  )
194
 
195
  location = gr.Textbox(
196
+ label="🌍 Your Region",
197
  placeholder="Please input your location. e.g., Country and/or state name",
198
+ value="USA"
 
199
  )
200
 
201
  submit_btn = gr.Button("🚀 Analyze Item")
202
+
203
  examples = gr.Examples(
204
+ examples=[["broken phone", "img/broken_phone.jpg", "India"],
205
+ ["charger", "img/charger.jpg", "London, England"]],
 
 
206
  inputs=[item_description, item_image, location],
207
  label="🧪 Try an Example"
208
  )
 
210
  with gr.Column(scale=1):
211
  status_output = gr.Label(label="♻️ Item Recyclability Summary")
212
  summary_output = gr.Markdown(label="📋 Recyclability Report")
213
+ with gr.Accordion("ℹ️ Instructions on What Exactly to Do with It", open=False):
214
  instructions_output = gr.Markdown()
215
+ with gr.Accordion("📦 Why This Matters", open=False):
216
  reasoning_output = gr.Markdown()
217
 
218
  submit_btn.click(
219
  fn=recyclo_advisor,
220
+ inputs=[item_description, item_image, location, api_key, provider_dropdown],
221
  outputs=[status_output, summary_output, instructions_output, reasoning_output]
222
  )
223
 
requirements.txt CHANGED
@@ -1,3 +1,4 @@
1
  gradio
2
  pillow
3
- openai
 
 
1
  gradio
2
  pillow
3
+ openai
4
+ google-genai