rkihacker commited on
Commit
e0f5861
Β·
verified Β·
1 Parent(s): aa5e579

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +98 -132
app.py CHANGED
@@ -1,8 +1,9 @@
1
- # === Gradio Demo App: gradio_app.py ===
2
  # This script creates a user-friendly web interface to demonstrate the
3
  # multimodal moderation capabilities of the main FastAPI server.
4
  #
5
  # It interacts with the /v3/moderations endpoint.
 
6
  # --------------------------------------------------------------------
7
 
8
  import base64
@@ -19,11 +20,9 @@ from dotenv import load_dotenv
19
  logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
20
  load_dotenv()
21
 
22
- # The URL of your running FastAPI server.
23
- # It's crucial to set this in your .env file for deployment.
24
- API_BASE_URL = os.environ.get("API_BASE_URL", "")
25
  MODERATION_ENDPOINT = f"{API_BASE_URL}/v3/moderations"
26
-
27
  # --- Full list of Whisper V3 supported languages ---
28
  # Mapping user-friendly names to ISO 639-1 codes
29
  WHISPER_LANGUAGES = {
@@ -48,33 +47,27 @@ WHISPER_LANGUAGES = {
48
  "Tagalog": "tl", "Malagasy": "mg", "Assamese": "as", "Tatar": "tt", "Hawaiian": "haw",
49
  "Lingala": "ln", "Hausa": "ha", "Bashkir": "ba", "Javanese": "jw", "Sundanese": "su",
50
  }
51
- # Sort languages alphabetically for the dropdown
52
  SORTED_LANGUAGES = dict(sorted(WHISPER_LANGUAGES.items()))
53
-
54
-
55
- # --- Helper Function ---
56
  def file_to_base64(filepath: str) -> str:
57
- """Reads a file and converts it to a base64 encoded string."""
58
- if not filepath:
59
- return None
60
  try:
61
  with open(filepath, "rb") as f:
62
- encoded_string = base64.b64encode(f.read()).decode("utf-8")
63
- return encoded_string
64
  except Exception as e:
65
  logging.error(f"Failed to convert file {filepath} to base64: {e}")
66
  return None
67
-
68
- # --- Core Logic ---
 
 
 
 
 
69
  def moderate_content(text_input, image_input, video_input, audio_input, language_full_name):
70
- """
71
- Prepares the payload, calls the moderation API, and formats the response.
72
- """
73
  if not any([text_input, image_input, video_input, audio_input]):
74
- return "Please provide at least one input (text, image, video, or audio).", None
75
-
76
  logging.info("Preparing payload for moderation API...")
77
- payload = { "model": "nai-moderation-latest" }
78
  if text_input: payload["input"] = text_input
79
  if image_b64 := file_to_base64(image_input): payload["image"] = image_b64
80
  if video_b64 := file_to_base64(video_input): payload["video"] = video_b64
@@ -83,154 +76,127 @@ def moderate_content(text_input, image_input, video_input, audio_input, language
83
  language_code = SORTED_LANGUAGES.get(language_full_name, "en")
84
  payload["language"] = language_code
85
  logging.info(f"Audio detected. Using language: {language_full_name} ({language_code})")
86
-
87
  logging.info(f"Sending request to {MODERATION_ENDPOINT} with inputs: {list(payload.keys())}")
88
-
89
- summary_output = "An error occurred. Please check the logs."
90
- full_response_output = {}
91
  latency_ms = None
92
-
93
  try:
94
  with httpx.Client(timeout=180.0) as client:
95
- start_time = time.monotonic()
96
  response = client.post(MODERATION_ENDPOINT, json=payload)
97
  latency_ms = (time.monotonic() - start_time) * 1000
98
  logging.info(f"API response received in {latency_ms:.2f} ms with status code {response.status_code}")
99
-
100
  response.raise_for_status()
101
-
102
  data = response.json()
103
- full_response_output = data # <-- MODIFIED: Assign raw data, without adding latency
104
-
105
  if not data.get("results"):
106
- summary_output = "API returned an empty result. This might happen if media processing fails (e.g., a video with no frames)."
107
- return summary_output, full_response_output
108
-
109
  result = data["results"][0]
110
-
111
- status = "🚨 FLAGGED 🚨" if result["flagged"] else "βœ… SAFE βœ…"
112
- reason = result.get("reason") or "N/A"
113
- transcribed = result.get("transcribed_text") or "N/A"
114
  flagged_categories = [cat for cat, flagged in result.get("categories", {}).items() if flagged]
115
  categories_str = ", ".join(flagged_categories) if flagged_categories else "None"
116
-
117
- summary_output = f"""
118
- **API Latency:** {latency_ms:.2f} ms
119
- ---
120
- **Moderation Status:** {status}
121
- ---
122
- **Reason:** {reason}
123
- ---
124
- **Flagged Categories:** {categories_str}
125
- ---
126
- **Transcribed Text (from audio):**
127
- {transcribed}
128
- """
129
  logging.info("Successfully parsed moderation response.")
130
-
131
  except httpx.HTTPStatusError as e:
132
- user_message = "The moderation service returned an error."
133
- error_details = ""
134
- latency_str = f"**API Latency:** {latency_ms:.2f} ms" if latency_ms is not None else ""
135
-
136
  try:
137
  error_json = e.response.json()
138
  detail = error_json.get("detail", "No specific error detail provided.")
139
- error_details = f"**Reason:** {detail}"
140
- # <-- MODIFIED: Latency removed from this dictionary
141
- full_response_output = {"error": "Backend API Error", "status_code": e.response.status_code, "details": error_json}
142
  except (json.JSONDecodeError, AttributeError):
143
- error_details = f"**Raw Server Response:**\n```\n{e.response.text}\n```"
144
- # <-- MODIFIED: Latency removed from this dictionary
145
- full_response_output = {"error": "Backend API Error", "status_code": e.response.status_code, "details": e.response.text}
146
-
147
- summary_output = f"""
148
- **🚫 Error from Moderation Service (HTTP {e.response.status_code})**
149
- ---
150
- {latency_str}
151
-
152
- {user_message}
153
-
154
- {error_details}
155
- """
156
  logging.error(f"HTTP Status Error: {e.response.status_code} - Response: {e.response.text}")
157
-
158
  except httpx.RequestError as e:
159
- if latency_ms is None:
160
- latency_ms = (time.monotonic() - start_time) * 1000 if 'start_time' in locals() else 0
161
-
162
- summary_output = f"""
163
- **πŸ”Œ Connection Error**
164
- ---
165
- Could not connect to the API server at `{API_BASE_URL}`. The request failed after {latency_ms:.0f} ms.
166
-
167
- Please ensure the backend server is running and the URL is configured correctly in your `.env` file.
168
- """
169
- # <-- MODIFIED: Latency removed from this dictionary
170
- full_response_output = {"error": "Connection Error", "url": API_BASE_URL, "details": str(e)}
171
  logging.error(f"Request Error: Could not connect to {API_BASE_URL}. Details: {e}")
172
-
173
  except Exception as e:
174
- summary_output = f"""
175
- **πŸ’₯ An Unexpected Application Error Occurred**
176
- ---
177
- An error happened within the Gradio application itself.
178
- Please check the application logs for more details.
179
-
180
- **Error Type:** `{type(e).__name__}`
181
- """
182
- full_response_output = {"error": "Gradio App Internal Error", "type": type(e).__name__, "details": str(e)}
183
  logging.error(f"Unexpected Error in Gradio App: {e}", exc_info=True)
184
-
185
- return summary_output, full_response_output
186
 
187
  # --- Gradio Interface ---
188
- with gr.Blocks(theme=gr.themes.Soft(), css="footer {display: none !important}") as demo:
189
  gr.Markdown(
190
  """
191
  # πŸ€– Multimodal Content Moderation Demo
192
- This demo uses a custom API server to perform advanced content moderation.
193
- You can provide any combination of text, image, video, and audio. The system will analyze all inputs together.
194
  """
195
  )
196
-
197
- with gr.Row():
198
- with gr.Column(scale=1):
199
- gr.Markdown("### 1. Provide Your Content")
200
- text_input = gr.Textbox(label="Text Input", lines=4, placeholder="Enter any text here...")
201
- image_input = gr.Image(label="Image Input", type="filepath")
202
- video_input = gr.Video(label="Video Input")
203
- audio_input = gr.Audio(label="Voice/Audio Input", type="filepath")
204
-
205
- language_input = gr.Dropdown(
206
- label="Audio Language (if providing audio)",
207
- choices=list(SORTED_LANGUAGES.keys()),
208
- value="English",
209
- interactive=True
210
- )
211
-
212
- submit_button = gr.Button("Moderate Content", variant="primary")
213
-
214
  with gr.Column(scale=2):
215
- gr.Markdown("### 2. See the Results")
216
- result_output = gr.Markdown(label="Moderation Summary")
217
- full_response_output = gr.JSON(label="Full API Response")
218
-
219
- submit_button.click(
220
- fn=moderate_content,
221
- inputs=[text_input, image_input, video_input, audio_input, language_input],
222
- outputs=[result_output, full_response_output]
223
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  gr.Examples(
226
  examples=[
227
- ["This is a test of the system with safe text.", None, None, None, "English"],
228
- ["I am going to kill the process on my computer.", None, None, None, "English"],
 
 
 
 
 
229
  ],
230
  inputs=[text_input, image_input, video_input, audio_input, language_input],
231
- outputs=[result_output, full_response_output],
232
- fn=moderate_content
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  )
 
234
 
235
  if __name__ == "__main__":
236
  logging.info(f"Connecting to API server at: {API_BASE_URL}")
 
1
+ # === Gradio Demo App: gradio_app.py (Backward-Compatible Version) ===
2
  # This script creates a user-friendly web interface to demonstrate the
3
  # multimodal moderation capabilities of the main FastAPI server.
4
  #
5
  # It interacts with the /v3/moderations endpoint.
6
+ # NOTE: This version removes the "Copy" button for compatibility with older Gradio versions.
7
  # --------------------------------------------------------------------
8
 
9
  import base64
 
20
  logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
21
  load_dotenv()
22
 
23
+ API_BASE_URL = os.environ.get("API_BASE_URL", "http://127.0.0.1:8000")
 
 
24
  MODERATION_ENDPOINT = f"{API_BASE_URL}/v3/moderations"
25
+ # ... (rest of the configuration and helper functions remain the same) ...
26
  # --- Full list of Whisper V3 supported languages ---
27
  # Mapping user-friendly names to ISO 639-1 codes
28
  WHISPER_LANGUAGES = {
 
47
  "Tagalog": "tl", "Malagasy": "mg", "Assamese": "as", "Tatar": "tt", "Hawaiian": "haw",
48
  "Lingala": "ln", "Hausa": "ha", "Bashkir": "ba", "Javanese": "jw", "Sundanese": "su",
49
  }
 
50
  SORTED_LANGUAGES = dict(sorted(WHISPER_LANGUAGES.items()))
 
 
 
51
  def file_to_base64(filepath: str) -> str:
52
+ if not filepath: return None
 
 
53
  try:
54
  with open(filepath, "rb") as f:
55
+ return base64.b64encode(f.read()).decode("utf-8")
 
56
  except Exception as e:
57
  logging.error(f"Failed to convert file {filepath} to base64: {e}")
58
  return None
59
+ def create_status_banner(status_type, text):
60
+ colors = {"safe": ("#DFF2BF", "#4F8A10"),"flagged": ("#FFD2D2", "#D8000C"),"error": ("#FEEFB3", "#9F6000"),"info": ("#BDE5F8", "#00529B"),}
61
+ bg_color, text_color = colors.get(status_type, ("#E0E0E0", "#000000"))
62
+ return f"<div style='background-color:{bg_color}; padding: 1rem; border-radius: 8px; margin-bottom: 1rem; border: 1px solid {text_color};'><h2 style='color:{text_color}; text-align:center; margin:0; font-size: 1.5rem;'>{text}</h2></div>"
63
+ def clear_outputs():
64
+ initial_text = "Results will appear here after submission."
65
+ return (create_status_banner("info", "SUBMIT CONTENT FOR MODERATION"),"N/A",initial_text,initial_text,initial_text,None,)
66
  def moderate_content(text_input, image_input, video_input, audio_input, language_full_name):
 
 
 
67
  if not any([text_input, image_input, video_input, audio_input]):
68
+ return (create_status_banner("error", "🚫 NO INPUT PROVIDED 🚫"),"N/A","Please provide at least one input (text, image, video, or audio) before submitting.","N/A", "N/A", None)
 
69
  logging.info("Preparing payload for moderation API...")
70
+ payload = {"model": "nai-moderation-latest"}
71
  if text_input: payload["input"] = text_input
72
  if image_b64 := file_to_base64(image_input): payload["image"] = image_b64
73
  if video_b64 := file_to_base64(video_input): payload["video"] = video_b64
 
76
  language_code = SORTED_LANGUAGES.get(language_full_name, "en")
77
  payload["language"] = language_code
78
  logging.info(f"Audio detected. Using language: {language_full_name} ({language_code})")
 
79
  logging.info(f"Sending request to {MODERATION_ENDPOINT} with inputs: {list(payload.keys())}")
 
 
 
80
  latency_ms = None
81
+ start_time = time.monotonic()
82
  try:
83
  with httpx.Client(timeout=180.0) as client:
 
84
  response = client.post(MODERATION_ENDPOINT, json=payload)
85
  latency_ms = (time.monotonic() - start_time) * 1000
86
  logging.info(f"API response received in {latency_ms:.2f} ms with status code {response.status_code}")
 
87
  response.raise_for_status()
 
88
  data = response.json()
 
 
89
  if not data.get("results"):
90
+ return (create_status_banner("error", "EMPTY API RESPONSE"), f"{latency_ms:.2f} ms", "The API returned an empty result. This can happen if media processing fails (e.g., a video with no valid frames).", "N/A", "N/A", data)
 
 
91
  result = data["results"][0]
92
+ status_text, status_type = ("🚨 FLAGGED 🚨", "flagged") if result["flagged"] else ("βœ… SAFE βœ…", "safe")
93
+ status_banner = create_status_banner(status_type, status_text)
94
+ reason = result.get("reason") or "No specific reason provided."
95
+ transcribed = result.get("transcribed_text") or "No audio was provided or transcription was not applicable."
96
  flagged_categories = [cat for cat, flagged in result.get("categories", {}).items() if flagged]
97
  categories_str = ", ".join(flagged_categories) if flagged_categories else "None"
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  logging.info("Successfully parsed moderation response.")
99
+ return (status_banner,f"{latency_ms:.2f} ms",reason,categories_str,transcribed,data)
100
  except httpx.HTTPStatusError as e:
101
+ latency_str = f"{latency_ms:.2f} ms" if latency_ms is not None else "N/A"
102
+ full_response, error_details = {}, ""
 
 
103
  try:
104
  error_json = e.response.json()
105
  detail = error_json.get("detail", "No specific error detail provided.")
106
+ error_details = f"Server responded with error: {detail}"
107
+ full_response = {"error": "Backend API Error", "status_code": e.response.status_code, "details": error_json}
 
108
  except (json.JSONDecodeError, AttributeError):
109
+ error_details = f"Could not decode the server's error response:\n{e.response.text}"
110
+ full_response = {"error": "Backend API Error", "status_code": e.response.status_code, "details": e.response.text}
 
 
 
 
 
 
 
 
 
 
 
111
  logging.error(f"HTTP Status Error: {e.response.status_code} - Response: {e.response.text}")
112
+ return (create_status_banner("error", f"🚫 API ERROR (HTTP {e.response.status_code}) 🚫"), latency_str, error_details, "N/A", "N/A", full_response)
113
  except httpx.RequestError as e:
114
+ latency_ms = (time.monotonic() - start_time) * 1000
115
+ error_msg = f"Could not connect to the API server at `{API_BASE_URL}`. Please ensure the backend server is running and the URL is correctly configured."
 
 
 
 
 
 
 
 
 
 
116
  logging.error(f"Request Error: Could not connect to {API_BASE_URL}. Details: {e}")
117
+ return (create_status_banner("error", "πŸ”Œ CONNECTION ERROR πŸ”Œ"), f"{latency_ms:.0f} ms", error_msg, "N/A", "N/A", {"error": "Connection Error", "url": API_BASE_URL, "details": str(e)})
118
  except Exception as e:
 
 
 
 
 
 
 
 
 
119
  logging.error(f"Unexpected Error in Gradio App: {e}", exc_info=True)
120
+ return (create_status_banner("error", "πŸ’₯ UNEXPECTED APP ERROR πŸ’₯"),"N/A",f"An unexpected error occurred within the Gradio application itself: {type(e).__name__}","N/A", "N/A",{"error": "Gradio App Internal Error", "type": type(e).__name__, "details": str(e)})
 
121
 
122
  # --- Gradio Interface ---
123
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"), css="footer {display: none !important}") as demo:
124
  gr.Markdown(
125
  """
126
  # πŸ€– Multimodal Content Moderation Demo
127
+ This interface demonstrates a powerful, multi-input moderation API.
128
+ Provide any combination of text, image, video, and audio. The system will analyze all inputs together for a comprehensive result.
129
  """
130
  )
131
+ with gr.Row(variant="panel"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  with gr.Column(scale=2):
133
+ gr.Markdown("### 1. Provide Your Content")
134
+ with gr.Tabs():
135
+ with gr.TabItem("πŸ“ Text"):
136
+ text_input = gr.Textbox(label="Text Input", lines=8, placeholder="Enter any text here...")
137
+ with gr.TabItem("πŸ–ΌοΈ Image"):
138
+ image_input = gr.Image(label="Image Input", type="filepath")
139
+ with gr.TabItem("🎬 Video"):
140
+ video_input = gr.Video(label="Video Input")
141
+ with gr.TabItem("🎀 Audio"):
142
+ audio_input = gr.Audio(label="Voice/Audio Input", type="filepath")
143
+ language_input = gr.Dropdown(label="Audio Language", choices=list(SORTED_LANGUAGES.keys()), value="English", interactive=True)
144
+ with gr.Row():
145
+ clear_button = gr.Button("Clear All")
146
+ submit_button = gr.Button("▢️ Moderate Content", variant="primary")
147
+ with gr.Column(scale=3):
148
+ gr.Markdown("### 2. Moderation Results")
149
+ status_output = gr.Markdown(value=create_status_banner("info", "AWAITING SUBMISSION"))
150
+ with gr.Group():
151
+ with gr.Row():
152
+ latency_output = gr.Textbox(label="⏱️ API Latency", interactive=False)
153
+ categories_output = gr.Textbox(label="🏷️ Flagged Categories", interactive=False)
154
+ reason_output = gr.Textbox(label="βš–οΈ Reason", interactive=False, lines=2)
155
+ # MODIFICATION: The Copy button and its surrounding Row have been removed.
156
+ transcription_output = gr.Textbox(label="🎀 Transcribed Text (from audio)", interactive=False, lines=4)
157
+ with gr.Accordion("Full API Response (JSON)", open=False):
158
+ full_response_output = gr.JSON(label="Raw JSON Response")
159
 
160
+ demo.load(fn=clear_outputs, inputs=None, outputs=[status_output, latency_output, reason_output, categories_output, transcription_output, full_response_output])
161
+
162
+ gr.Markdown("---")
163
+ gr.Markdown(
164
+ """
165
+ ### πŸ’‘ Quick Examples
166
+ <p style='color: #666; font-size: 0.9rem;'>
167
+ <b>⚠️ Content Warning:</b> The examples below include text that may be offensive or disturbing (e.g., hate speech, violence, sexual content).
168
+ They are provided solely to demonstrate the capabilities of the moderation model.
169
+ </p>
170
+ """
171
+ )
172
  gr.Examples(
173
  examples=[
174
+ ["The sun is shining and the birds are singing. It's a beautiful day for a walk in the park.", None, None, None, "English"],
175
+ ["I'm going to kill the process on my computer because it's using too much memory.", None, None, None, "English"],
176
+ ["If you don't give me what I want, I will hunt you down and hurt you.", None, None, None, "English"],
177
+ ["I can't stand people from that country, they are all lazy and untrustworthy.", None, None, None, "English"],
178
+ ["I feel so hopeless and alone. I don't see the point in going on anymore.", None, None, None, "English"],
179
+ ["Looking for a partner for some wild, no-strings-attached fun tonight. Must be over 18.", None, None, None, "English"],
180
+ ["She looks so young and innocent in that picture, I love it.", None, None, None, "English"],
181
  ],
182
  inputs=[text_input, image_input, video_input, audio_input, language_input],
183
+ fn=moderate_content,
184
+ outputs=[status_output, latency_output, reason_output, categories_output, transcription_output, full_response_output],
185
+ cache_examples=False,
186
+ )
187
+
188
+ # --- Event Handlers (Backward-Compatible) ---
189
+ all_inputs = [text_input, image_input, video_input, audio_input, language_input]
190
+ all_outputs = [status_output, latency_output, reason_output, categories_output, transcription_output, full_response_output]
191
+
192
+ submit_button.click(fn=moderate_content, inputs=all_inputs, outputs=all_outputs)
193
+ clear_button.click(
194
+ fn=lambda: (None, None, None, None, *clear_outputs()),
195
+ inputs=None,
196
+ outputs=[text_input, image_input, video_input, audio_input, *all_outputs],
197
+ queue=False
198
  )
199
+ # MODIFICATION: The copy_button.click() handler has been removed entirely.
200
 
201
  if __name__ == "__main__":
202
  logging.info(f"Connecting to API server at: {API_BASE_URL}")