Kiran5 commited on
Commit
18029e7
·
verified ·
1 Parent(s): 692f700

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +814 -814
src/streamlit_app.py CHANGED
@@ -1,815 +1,815 @@
1
- import streamlit as st
2
- import requests
3
- import json
4
- import os
5
-
6
- #Custom CSS Injection
7
- st.markdown("""
8
- <style>
9
- :root {
10
- --light-purple: rgba(179, 157, 219, 0.6);
11
- --light-purple-border: rgba(179, 157, 219, 0.4);
12
- --main-purple: #6a1b9a; /* A darker purple for accents */
13
- --light-gray-border: #d0cfd0;
14
- --text-color-dark: #555555;
15
- --text-color-medium: #4a4a4a;
16
- }
17
-
18
- /* Import Font Awesome for icons */
19
- @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css');
20
-
21
- .stMainBlockContainer.block-container.st-emotion-cache-zy6yx3.e4man114 {
22
- padding-top: 55px !important;
23
- }
24
-
25
- /* Style for the COLLAPSIBLE details container; the API Description */
26
-
27
- #api-info {
28
- width: 100%;
29
- margin-top: 0 !important;
30
- padding-top: 0 !important;
31
- }
32
-
33
- #api-info details {
34
- width: 100%;
35
- max-width: 100%;
36
- box-sizing: border-box;
37
- border: 2px solid var(--light-gray-border);
38
- border-radius: 6.4px;
39
- padding: 0.4rem 0.8rem;
40
- background-color: #fff;
41
- margin: 0;
42
- transition: border-color 0.3s ease-in-out, box-shadow 0.3s ease-in-out;
43
- }
44
-
45
- /* Purple outline and shadow when OPEN or FOCUSED */
46
- #api-info details:focus-within,
47
- #api-info details[open] {
48
- outline: none;
49
- border-color: rgba(106, 27, 154, 0.2);
50
- box-shadow: 0 0 0 1.5px rgba(106, 27, 154, 0.2);
51
- }
52
-
53
- /* Summary style and hover/focus behavior */
54
- #api-info summary {
55
- font-weight: 600;
56
- font-size: 14.4px;
57
- color: var(--text-color-dark);
58
- cursor: pointer;
59
- list-style: none;
60
- margin: 0;
61
- padding: 0.2rem 0;
62
- transition: color 0.3s;
63
- }
64
-
65
- /* Purple color on hover/focus */
66
- #api-info summary:hover,
67
- #api-info summary:focus {
68
- color: var(--main-purple);
69
- }
70
-
71
- /* Remove default marker */
72
- #api-info summary::-webkit-details-marker {
73
- display: none;
74
- }
75
-
76
- /* Custom arrow */
77
- #api-info summary::after {
78
- content: "›";
79
- font-size: 14.4px;
80
- margin-left: 6.4px;
81
- float: right;
82
- transition: transform 0.3s;
83
- color: var(--main-purple);
84
- }
85
-
86
- #api-info details[open] summary::after {
87
- transform: rotate(90deg);
88
- }
89
-
90
- /* Content styling */
91
- #api-info .expander-content {
92
- font-size: 14px !important;
93
- padding: 0.6rem 0;
94
- color: var(--text-color-medium);
95
- font-family: Arial, sans-serif;
96
- line-height: 1.28;
97
- }
98
-
99
- /* List indentation */
100
- #api-info .expander-content ul {
101
- margin-left: 0.96em;
102
- }
103
-
104
- /* Link appearance */
105
- #api-info .expander-content a {
106
- color: var(--main-purple);
107
- text-decoration: none;
108
- }
109
-
110
- #api-info .expander-content a:hover {
111
- text-decoration: underline;
112
- }
113
-
114
- /* --- Custom styles for Streamlit components --- */
115
- /* Textarea focus & error states */
116
- div[data-baseweb="textarea"] textarea:focus,
117
- div[data-baseweb="textarea"] textarea:focus-visible {
118
- outline: none !important;
119
- box-shadow: 0 0 0 1.5px var(--light-purple) !important;
120
- border: 1.5px solid var(--light-purple-border) !important;
121
- }
122
-
123
- /* Override red border on invalid textarea */
124
- div[data-baseweb="textarea"] textarea:invalid {
125
- box-shadow: 0 0 0 1.5px var(--light-purple) !important;
126
- border-color: var(--light-purple-border) !important;
127
- }
128
-
129
- /* Checkbox focus outline */
130
- div[data-baseweb="checkbox"] input[type="checkbox"]:focus-visible {
131
- outline: none !important;
132
- box-shadow: 0 0 0 1.5px var(--light-purple) !important;
133
- border: 1.5px solid var(--light-purple-border) !important;
134
- }
135
-
136
- /* Checkbox checked fill (override red) */
137
- div[data-baseweb="checkbox"] input[type="checkbox"]:checked {
138
- background-color: var(--light-purple-border) !important;
139
- border-color: var(--light-purple-border) !important;
140
- }
141
-
142
- /* Checkbox hover fill */
143
- div[data-baseweb="checkbox"] input[type="checkbox"]:hover:not(:checked) {
144
- background-color: rgba(179, 157, 219, 0.2) !important;
145
- }
146
-
147
- /* Checkbox checked hover fill */
148
- div[data-baseweb="checkbox"] input[type="checkbox"]:checked:hover {
149
- background-color: rgba(179, 157, 219, 0.5) !important;
150
- }
151
-
152
- /* Ensure Streamlit's checkmark color is visible */
153
- div[data-baseweb="checkbox"] input[type="checkbox"]:checked::before {
154
- color: white !important;
155
- /* Adjust based on your purple background */
156
- }
157
-
158
- /* Style for sliders to match theme */
159
- div[data-baseweb="slider"] div[role="slider"] {
160
- background-color: var(--main-purple) !important;
161
- }
162
- div[data-baseweb="slider"] div[role="slider"]:hover {
163
- background-color: var(--light-purple) !important;
164
- }
165
- div[data-baseweb="slider"] div[role="slider"]:focus {
166
- box-shadow: 0 0 0 1.5px var(--light-purple) !important;
167
- }
168
- div[data-baseweb="slider"] div[data-baseweb="tooltip"] {
169
- background-color: var(--main-purple) !important;
170
- border-color: var(--main-purple) !important;
171
- }
172
- div[data-baseweb="slider"] div[data-baseweb="track"] {
173
- background-color: var(--light-purple-border) !important;
174
- }
175
-
176
- /* Style for multiselect dropdowns */
177
- div[data-baseweb="select"] {
178
- border-radius: 6.4px;
179
- border: 1.5px solid var(--light-purple-border);
180
- }
181
- div[data-baseweb="select"]:focus-within {
182
- box-shadow: 0 0 0 1.5px var(--light-purple) !important;
183
- border-color: var(--light-purple) !important;
184
- }
185
- /* Style for multiselect selected items */
186
- div[data-baseweb="tag"] {
187
- background-color: var(--light-purple) !important;
188
- color: var(--text-color-dark) !important;
189
- border-radius: 4px;
190
- }
191
- div[data-baseweb="tag"] svg { /* Close icon */
192
- color: var(--text-color-dark) !important;
193
- }
194
-
195
- /* Custom button styling for icon buttons (Refresh button) */
196
- .stButton > button[kind="secondary"] { /* Target non-primary buttons for icon styling */
197
- background-color: transparent !important;
198
- border: none !important;
199
- padding: 0.5rem !important;
200
- margin: 0 !important;
201
- box-shadow: none !important;
202
- color: var(--main-purple) !important;
203
- font-size: 1.5rem !important;
204
- /* Adjust icon size */
205
- cursor: pointer;
206
- transition: color 0.2s ease-in-out;
207
- }
208
- .stButton > button[kind="secondary"]:hover {
209
- color: var(--light-purple) !important;
210
- }
211
- .stButton > button[kind="secondary"]:focus,
212
- .stButton > button[kind="secondary"]:focus-visible {
213
- outline: none !important;
214
- box-shadow: 0 0 0 3px rgba(106, 27, 154, 0.4) !important;
215
- /* Purple highlight on focus */
216
- }
217
-
218
- /* Custom button styling for primary buttons (Send button) */
219
- .stButton button.primary {
220
- background-color: var(--main-purple) !important;
221
- color: var(--header-text-color) !important; /* White text */
222
- border: 1.5px solid var(--main-purple) !important;
223
- border-radius: 0.5rem !important;
224
- padding: 0.75rem 1.5rem !important;
225
- font-size: 1rem !important;
226
- font-weight: 600 !important;
227
- box-shadow: 0 2px 5px rgba(0,0,0,0.2) !important;
228
- transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
229
- }
230
-
231
- .stButton button.primary:hover {
232
- background-color: #5a127a !important;
233
- /* Slightly darker purple on hover */
234
- border-color: #5a127a !important;
235
- box-shadow: 0 4px 8px rgba(0,0,0,0.3) !important;
236
- }
237
-
238
- .stButton button.primary:focus,
239
- .stButton button.primary:focus-visible {
240
- outline: none !important;
241
- box-shadow: 0 0 0 3px rgba(106, 27, 154, 0.4) !important;
242
- /* Purple highlight on focus */
243
- }
244
-
245
- /* Adjust text_area to have scroll for long text */
246
- textarea[aria-label="Prompt:"] {
247
- overflow-y: auto !important;
248
- min-height: 38px; /* Approximate single line height */
249
- max-height: 100px;
250
- /* Max height before scrollbar appears, adjust as needed */
251
- resize: vertical;
252
- /* Allow vertical resizing by user */
253
- }
254
-
255
- /* Ensure text area and button align properly */
256
- div[data-testid="stVerticalBlock"] > div > div > div[data-testid="stHorizontalBlock"] {
257
- display: flex;
258
- align-items: flex-end; /* Align items to the bottom */
259
- gap: 10px;
260
- /* Space between textarea and button */
261
- }
262
-
263
- /* Specific styling for the refresh button container to align it */
264
- div[data-testid="stVerticalBlock"] > div > div > div[data-testid="stHorizontalBlock"] > div:last-child {
265
- display: flex;
266
- align-items: flex-end; /* Align the button to the bottom of its flex container */
267
- height: 100%;
268
- /* Ensure it takes full height to align with textarea */
269
- }
270
-
271
- #for send button
272
- .stButton.send-button-container {
273
- display: flex;
274
- justify-content: center;
275
- margin-top: 1.5rem; /* Space above the send button */
276
- }
277
-
278
- /* Hide the label for the prompt text area */
279
- div[data-testid="stTextarea"] label {
280
- display: none !important;
281
- }
282
-
283
- /* Hide scrollbar for the parent container of the text area if it appears */
284
- div[data-baseweb="textarea"] > div:first-child {
285
- overflow: hidden !important;
286
- }
287
-
288
- /* Reduce vertical spacing between checkbox items */
289
- div[data-baseweb="checkbox"] {
290
- margin-bottom: 2px;
291
- /* Adjust to control spacing */
292
- }
293
-
294
- div[data-baseweb="checkbox"] label {
295
- margin: 0 !important;
296
- padding: 0 !important;
297
- }
298
-
299
- </style>
300
- """, unsafe_allow_html=True)
301
-
302
- st.set_page_config(layout="wide")
303
-
304
- #Initialize session state for dynamic elements
305
- if 'custom_words' not in st.session_state:
306
- st.session_state.custom_words = []
307
-
308
- # Initialize session state for output visibility
309
- if 'show_output' not in st.session_state:
310
- st.session_state.show_output = False
311
-
312
- # --- API Description (for Full Width) ---
313
- st.markdown("""
314
- <div id="api-info">
315
- <details>
316
- <summary>API DESCRIPTION</summary>
317
- <div class="expander-content">
318
- <h5 style="margin:0.4em 0;">What does this API do?</h5>
319
- <p>The <strong>Moderation API</strong> evaluates prompts to determine whether they are safe for use with a large language model (LLM). It returns a <strong>status</strong> (passed or failed) with detailed scores.</p>
320
- <h4 style="margin:0.64em 0 0.24em;">Moderation Checks</h4>
321
- <ul>
322
- <li><strong>Prompt Injection</strong>: Detects attempts to hijack or manipulate the LLM behavior.</li>
323
- <li><strong>Jailbreak Attempts</strong>: Identifies prompts trying to bypass guardrails.</li>
324
- <li><strong>Toxicity & Profanity</strong>: Flags harmful, offensive, or explicit content.</li>
325
- <li><strong>Restricted Topics</strong>: Detects categories like cheating, conspiracy, terrorism, etc.</li>
326
- <li><strong>Text Quality</strong>: Measures readability and clarity (informational only).</li>
327
- <li><strong>Customized Theme</strong>: Block prompts using custom keywords (e.g., "atomic weapon").</li>
328
- <li><strong>PII Detection</strong>: Identifies AADHAR, PAN, SSN, Passport, Email, Phone, IP, Credit Card, Medical License, etc.</li>
329
- </ul>
330
- <h4 style="margin:0.64em 0 0.24em;">Full Customization</h4>
331
- <ul>
332
- <li>Enable/disable checks</li>
333
- <li>Set thresholds</li>
334
- <li>Select PII or restricted topics</li>
335
- <li>Define custom block terms</li>
336
- </ul>
337
- <h4 style="margin:0.64em 0 0.24em;">Resources</h4>
338
- <ul>
339
- <li><a href="https://huggingface.co/spaces/InfosysEnterprise/responsible-ai-moderationlayer/tree/main" target="_blank">Hugging Face Repo</a></li>
340
- <li><a href="https://infosysenterprise-responsible-ai-moderationlayer.hf.space/rai/v1/moderations/docs/#" target="_blank">Swagger Docs</a></li>
341
- </ul>
342
- </div>
343
- </details>
344
- </div>
345
- """, unsafe_allow_html=True)
346
-
347
- #Main Layout: Two Columns for Inputs and Outputs
348
- input_col, output_col = st.columns([0.5, 0.5]) # Input on left, Output on right
349
-
350
- with input_col:
351
- st.markdown('<h4 style="font-weight:550; margin-bottom:0.5rem; padding-top:2rem;">INPUT</h4>', unsafe_allow_html=True)
352
-
353
- # Define all checks and their default properties
354
- check_configs = {
355
- "Prompt Injection": {"type": "slider", "default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01},
356
- "Jailbreak": {"type": "slider", "default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01},
357
- "Toxicity": {"type": "slider", "default": 0.6, "min": 0.0, "max": 1.0, "step": 0.01},
358
- "Profanity": {"type": "number_input", "default": 1, "min": 1, "hint": "1"},
359
- "Restricted Topics": {"type": "multiselect", "default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01,
360
- "options": ["terrorism","explosives","nudity","cruelty","cheating","fraud","crime","hacking","immoral","unethical","illegal","robbery","forgery","misinformation"]},
361
- "Text Quality": {"type": "no_threshold"},
362
- "Customized Theme": {"type": "slider_and_input", "default": 0.6, "min": 0.0, "max": 1.0, "step": 0.01},
363
- "PII Detection": {"type": "multiselect_pii",
364
- "options": ["AADHAR_NUMBER", "PAN_Number", "IN_PAN", "US_PASSPORT", "US_SSN"]},
365
- }
366
-
367
- # Initialize session state for all checkboxes (default to checked)
368
- for check_name, config in check_configs.items():
369
- base_key = check_name.replace(' ', '_')
370
-
371
- # Always ensure checkbox is initialized
372
- checkbox_key = f"checkbox_{base_key}"
373
- if checkbox_key not in st.session_state:
374
- st.session_state[checkbox_key] = True # default to checked
375
-
376
- # Set default values for controls
377
- if config["type"] == "slider":
378
- slider_key = f"slider_{base_key}"
379
- if slider_key not in st.session_state:
380
- st.session_state[slider_key] = config["default"]
381
-
382
- elif config["type"] == "number_input":
383
- number_key = f"number_{base_key}"
384
- if number_key not in st.session_state:
385
- st.session_state[number_key] = config["default"]
386
-
387
- elif config["type"] == "multiselect":
388
- multi_key = f"multiselect_{base_key}"
389
- if multi_key not in st.session_state:
390
- st.session_state[multi_key] = config["options"][:2]
391
- slider_key = f"slider_{base_key}"
392
- if slider_key not in st.session_state:
393
- st.session_state[slider_key] = config["default"]
394
-
395
- elif config["type"] == "multiselect_pii":
396
- multi_key = f"multiselect_{base_key}"
397
- if multi_key not in st.session_state:
398
- st.session_state[multi_key] = config["options"]
399
-
400
- elif config["type"] == "slider_and_input":
401
- slider_key = f"slider_{base_key}"
402
- if slider_key not in st.session_state:
403
- st.session_state[slider_key] = config["default"]
404
- if "custom_words" not in st.session_state:
405
- st.session_state["custom_words"] = []
406
-
407
- # Formatting for input box, send and refresh buttons
408
- col1, col2, col3 = st.columns([7,0.5,0.5])
409
- with col1:
410
- user_input = st.text_input(
411
- label="Prompt",
412
- placeholder="Type your prompt here...",
413
- value=st.session_state.get("prompt_input", ""),
414
- key="prompt_input",
415
- label_visibility="collapsed"
416
- )
417
- st.markdown("""
418
- <div class="stButton send-button-container" style="display: flex; gap: 2px;">
419
- """, unsafe_allow_html=True)
420
- with col2:
421
- refresh_clicked = st.button("↻", key="refresh_button_text", help="Reload the page")
422
- with col3:
423
- send_clicked = st.button("→", key="send_request_button", help="Send the request")
424
-
425
- st.markdown("</div>", unsafe_allow_html=True)
426
-
427
- #Handle Refresh Click
428
- if refresh_clicked:
429
-
430
- # Clear all checkboxes, sliders, multiselects, etc.
431
- for check_name, config in check_configs.items():
432
- base_key = check_name.replace(' ', '_')
433
- st.session_state.pop(f"checkbox_{base_key}", None)
434
- st.session_state.pop(f"slider_{base_key}", None)
435
- st.session_state.pop(f"multiselect_{base_key}", None)
436
- st.session_state.pop(f"number_{base_key}", None)
437
-
438
- # Clear custom word logic
439
- st.session_state.pop("custom_words", None)
440
- st.session_state.pop("new_custom_word", None)
441
-
442
- # Clear the input prompt
443
- st.session_state.pop("prompt_input", None)
444
-
445
- # Optionally: hide the output section
446
- st.session_state["show_output"] = False
447
-
448
- st.rerun()
449
-
450
- if send_clicked:
451
- user_input = st.session_state.get("prompt_input", "")
452
- if not user_input.strip():
453
- with output_col:
454
- st.warning("Please enter a prompt before sending the request.")
455
- st.stop()
456
- st.session_state["show_output"] = True
457
-
458
- selected_checks_payload_ui = {}
459
- for check_name, config in check_configs.items():
460
- base_key = check_name.replace(' ', '_')
461
-
462
- enabled = st.session_state.get(f"checkbox_{base_key}", False)
463
-
464
- if enabled:
465
- payload_item = {"enabled": True}
466
-
467
- if config["type"] == "slider":
468
- payload_item["threshold"] = st.session_state.get(f"slider_{base_key}", config["default"])
469
- elif config["type"] == "number_input":
470
- payload_item["threshold"] = st.session_state.get(f"number_{base_key}", config["default"])
471
- elif config["type"] == "multiselect":
472
- payload_item["threshold"] = st.session_state.get(f"slider_{base_key}", config["default"])
473
- payload_item["topics"] = st.session_state.get(f"multiselect_{base_key}", config["options"][:2])
474
- elif config["type"] == "multiselect_pii":
475
- payload_item["entities_to_block"] = st.session_state.get(f"multiselect_{base_key}", config["options"])
476
- elif config["type"] == "slider_and_input":
477
- payload_item["threshold"] = st.session_state.get(f"slider_{base_key}", config["default"])
478
- payload_item["custom_words"] = st.session_state.get("custom_words", [])
479
-
480
- selected_checks_payload_ui[check_name] = payload_item
481
- else:
482
- selected_checks_payload_ui[check_name] = {"enabled": False}
483
-
484
- st.markdown('<h3 style="font-size:20px; font-weight:600; color:var(--text-color-dark);">Select Moderation Checks to apply:</h3>', unsafe_allow_html=True)
485
-
486
- # Mapping from UI check names to API check names
487
- api_check_name_map = {
488
- "Prompt Injection": "PromptInjection",
489
- "Jailbreak": "JailBreak",
490
- "Toxicity": "Toxicity",
491
- "Profanity": "Profanity",
492
- "Restricted Topics": "RestrictTopic",
493
- "Text Quality": "TextQuality",
494
- "Customized Theme": "CustomizedTheme",
495
- "PII Detection": "Piidetct"
496
- }
497
-
498
- # Display checkboxes and conditional UIs
499
- for check_name, config in check_configs.items():
500
- base_key = check_name.replace(' ', '_')
501
- col1, col2 = st.columns([0.3, 0.7])
502
-
503
- with col1:
504
- enabled = st.checkbox(check_name, key=f"checkbox_{base_key}")
505
-
506
- if enabled and config["type"] != "no_threshold":
507
- with col2:
508
- with st.expander(f"Configure {check_name}"):
509
- if config["type"] == "slider":
510
- # CORRECT: Removed 'value=config["default"]'
511
- threshold = st.slider(
512
- f"Set Threshold for {check_name}",
513
- min_value=config["min"],
514
- max_value=config["max"],
515
- step=config["step"],
516
- key=f"slider_{base_key}"
517
- )
518
-
519
- elif config["type"] == "number_input":
520
- profanity_threshold = st.number_input(
521
- f"Set Profanity Count Threshold (whole numbers only)",
522
- min_value=config["min"],
523
- key=f"number_{base_key}",
524
- help=f"Enter a number, e.g., {config['hint']}"
525
- )
526
-
527
- elif config["type"] == "multiselect":
528
-
529
- threshold = st.slider(
530
- f"Set Threshold for {check_name}",
531
- min_value=config["min"],
532
- max_value=config["max"],
533
- step=config["step"],
534
- key=f"slider_{base_key}"
535
- )
536
- if slider_key not in st.session_state:
537
- st.session_state[slider_key] = config["default"]
538
-
539
- selected_topics = st.multiselect(
540
- "Select Restricted Topics:",
541
- options=config["options"],
542
- key=f"multiselect_{base_key}"
543
- )
544
-
545
- elif config["type"] == "multiselect_pii":
546
-
547
- selected_entities = st.multiselect(
548
- "Select PII Entities to Block:",
549
- options=config["options"],
550
- key=f"multiselect_{base_key}"
551
- )
552
-
553
- elif config["type"] == "slider_and_input":
554
-
555
- threshold = st.slider(
556
- f"Set Threshold for {check_name}",
557
- min_value=config["min"],
558
- max_value=config["max"],
559
- step=config["step"],
560
- key=f"slider_{base_key}"
561
- )
562
-
563
- st.markdown("---")
564
- st.subheader("Custom Words for Theme")
565
-
566
- # Using a single, consistent placeholder for messages
567
- message_placeholder = st.empty()
568
-
569
- expander_key = f"custom_words_expander_{base_key}"
570
-
571
- def add_word_callback():
572
- new_word_input = st.session_state.get("new_custom_word", "")
573
- new_word_to_add = new_word_input.strip()
574
- if new_word_to_add and new_word_to_add not in st.session_state.custom_words:
575
- st.session_state.custom_words.append(new_word_to_add)
576
- st.session_state.new_custom_word = ""
577
- st.session_state[expander_key] = True # Keep expander open
578
- message_placeholder.success(f"'{new_word_to_add}' added!")
579
- elif new_word_to_add:
580
- st.session_state[expander_key] = True # Keep expander open
581
- message_placeholder.warning("Word already exists.")
582
- else:
583
- st.session_state[expander_key] = True # Keep expander open
584
- message_placeholder.warning("Word is empty.")
585
-
586
- def delete_word_callback(index):
587
- st.session_state.custom_words.pop(index)
588
- st.session_state[expander_key] = True # Keep expander open
589
- message_placeholder.success("Word removed successfully!")
590
-
591
- st.text_input("Add a new custom word:", key="new_custom_word")
592
- st.button("Add Word", key="add_custom_word_btn", on_click=add_word_callback)
593
-
594
- if st.session_state.custom_words:
595
- st.write("Current Custom Words:")
596
- if expander_key not in st.session_state:
597
- st.session_state[expander_key] = True
598
-
599
- with st.expander("Show/Hide Custom Words", expanded=st.session_state.get(expander_key, False)):
600
- for i, word in enumerate(st.session_state.custom_words):
601
- word_col, btn_col = st.columns([0.8, 0.2])
602
- with word_col:
603
- st.write(f"- {word}")
604
- with btn_col:
605
- # Use the on_click callback for the delete button
606
- st.button("del", key=f"remove_word_{i}", on_click=delete_word_callback, args=(i,))
607
- else:
608
- st.info("No custom words added yet.")
609
-
610
- if st.session_state.show_output:
611
- with output_col:
612
- # Construct the final API payload
613
- final_api_payload = {
614
- "AccountName": "None",
615
- "userid": "None",
616
- "PortfolioName": "None",
617
- "lotNumber": 1,
618
- "translate": "no",
619
- "EmojiModeration": "yes",
620
- "Prompt": user_input,
621
- "ModerationChecks": [],
622
- "ModerationCheckThresholds": {}
623
- }
624
-
625
- for check_name, data in selected_checks_payload_ui.items():
626
-
627
- #customized theme, need to make changes here..
628
- if check_name == "Customized Theme":
629
- final_api_payload["ModerationChecks"].append(api_check_name_map["Customized Theme"])
630
- final_api_payload["ModerationCheckThresholds"]["CustomTheme"] = {
631
- "Themename": "string",
632
- "Themethresold": data.get("threshold", check_configs["Customized Theme"]["default"]),
633
- "ThemeTexts": data.get("custom_words", [])
634
- }
635
-
636
- elif data["enabled"]: # For all other checks, only include if enabled
637
- if check_name in api_check_name_map:
638
- final_api_payload["ModerationChecks"].append(api_check_name_map[check_name])
639
-
640
- # Populate ModerationCheckThresholds for enabled checks from UI
641
- if check_name == "Prompt Injection":
642
- final_api_payload["ModerationCheckThresholds"]["PromptinjectionThreshold"] = data.get("threshold")
643
- elif check_name == "Jailbreak":
644
- final_api_payload["ModerationCheckThresholds"]["JailbreakThreshold"] = data.get("threshold")
645
- elif check_name == "Toxicity":
646
- toxicity_threshold = data.get("threshold")
647
- final_api_payload["ModerationCheckThresholds"]["ToxicityThresholds"] = {
648
- "ToxicityThreshold": toxicity_threshold,
649
- "SevereToxicityThreshold": toxicity_threshold,
650
- "ObsceneThreshold": toxicity_threshold,
651
- "ThreatThreshold": toxicity_threshold,
652
- "InsultThreshold": toxicity_threshold,
653
- "IdentityAttackThreshold": toxicity_threshold,
654
- "SexualExplicitThreshold": toxicity_threshold
655
- }
656
- elif check_name == "Profanity":
657
- final_api_payload["ModerationCheckThresholds"]["ProfanityCountThreshold"] = data.get("threshold")
658
- elif check_name == "Restricted Topics":
659
- final_api_payload["ModerationCheckThresholds"]["RestrictedtopicDetails"] = {
660
- "RestrictedtopicThreshold": data.get("threshold"),
661
- "Restrictedtopics": data.get("topics", [])
662
- }
663
- elif check_name == "PII Detection":
664
- final_api_payload["ModerationCheckThresholds"]["PiientitiesConfiguredToBlock"] = data.get("entities_to_block", [])
665
-
666
- # Ensure unique checks in ModerationChecks list
667
- final_api_payload["ModerationChecks"] = list(set(final_api_payload["ModerationChecks"]))
668
-
669
- with output_col:
670
-
671
- if st.session_state.show_output:
672
- st.markdown('<h4 style="font-weight:550; margin-bottom:0.5rem; padding-top:2rem;">OUTPUT</h4>', unsafe_allow_html=True)
673
- results_placeholder = st.empty() # Placeholder for output results
674
-
675
- with results_placeholder.container(): # Display payload in output column
676
- try:
677
- # Actual API call
678
- resp = requests.post("https://infosysenterprise-responsible-ai-moderationlayer.hf.space/rai/v1/moderations", json=final_api_payload)
679
- response_data = resp.json()
680
-
681
- if not isinstance(response_data, dict):
682
- st.error("API response was not a valid JSON object (dictionary).")
683
- st.stop()
684
-
685
- st.write("Moderation Results")
686
-
687
- overall_status = response_data.get("moderationResults", {}).get("summary", {}).get("status", "N/A")
688
- overall_reason = response_data.get("moderationResults", {}).get("summary", {}).get("reason", [])
689
-
690
- if overall_status.lower() == "passed":
691
- st.success(f"Overall Status: {overall_status.upper()}")
692
- else:
693
- st.error(f"Overall Status: {overall_status.upper()}")
694
- if overall_reason:
695
- failed_checks_str = ", ".join(overall_reason)
696
- st.warning(f"Failed Checks: {failed_checks_str}")
697
-
698
- st.write("Individual Check Details:")
699
-
700
- moderation_results = response_data.get("moderationResults", {})
701
- if not moderation_results:
702
- st.info("No detailed moderation results available.")
703
- else:
704
- # Mapping from UI labels to API keys
705
- ui_to_api_check_map = {
706
- "Prompt Injection": "promptInjectionCheck",
707
- "Jailbreak": "jailbreakCheck",
708
- "Toxicity": "toxicityCheck",
709
- "Profanity": "profanityCheck",
710
- "Restricted Topics": "restrictedtopic",
711
- "Text Quality": "textQuality",
712
- "Customized Theme": "customThemeCheck",
713
- "PII Detection": "privacyCheck",
714
- "Refusal": "refusalCheck"
715
- }
716
-
717
- api_response_name_map = {v: k for k, v in ui_to_api_check_map.items()}
718
-
719
-
720
- moderation_results = response_data.get("moderationResults", {})
721
-
722
- # Filter out 'summary' and other non-check entries like 'text'
723
- individual_results = {
724
- k: v for k, v in moderation_results.items()
725
- if k not in {"summary", "text"} and isinstance(v, dict)
726
- }
727
-
728
- # Determine which checks were enabled in UI and are present in API
729
- enabled_api_checks = []
730
- for ui_check, api_key in ui_to_api_check_map.items():
731
- if selected_checks_payload_ui.get(ui_check, {}).get("enabled"):
732
- if api_key in individual_results:
733
- enabled_api_checks.append(api_key)
734
- elif ui_check == "Customized Theme":
735
- if st.session_state.get("custom_words") or selected_checks_payload_ui.get(ui_check, {}).get("threshold") is not None:
736
- if api_key in individual_results:
737
- enabled_api_checks.append(api_key)
738
-
739
- # Sort by display name
740
- sorted_api_check_names = sorted(
741
- enabled_api_checks,
742
- key=lambda x: api_response_name_map.get(x, x)
743
- )
744
-
745
- # Display checks
746
- if sorted_api_check_names:
747
- for check_api_name in sorted_api_check_names:
748
- details = individual_results.get(check_api_name, {})
749
- display_name = api_response_name_map.get(check_api_name, check_api_name)
750
- status = details.get("result", "N/A")
751
-
752
- expander_style = ""
753
- if status.lower() == "passed":
754
- expander_style = "border-left: 5px solid green; padding-left: 10px;"
755
- elif status.lower() == "failed":
756
- expander_style = "border-left: 5px solid red; padding-left: 10px;"
757
- elif status.lower() == "info":
758
- expander_style = "border-left: 5px solid orange; padding-left: 10px;"
759
-
760
- with st.expander(f"**{display_name}** - Status: **{status.upper()}**"):
761
- st.markdown(f"<div style='{expander_style}'>", unsafe_allow_html=True)
762
-
763
- # Add your detailed logic below
764
- if check_api_name == "promptInjectionCheck":
765
- st.write(f"**Confidence Score:** `{details.get('injectionConfidenceScore', 'N/A')}`")
766
- st.write(f"**Threshold:** `{details.get('injectionThreshold', 'N/A')}`")
767
- elif check_api_name == "jailbreakCheck":
768
- st.write(f"**Similarity Score:** `{details.get('jailbreakSimilarityScore', 'N/A')}`")
769
- st.write(f"**Threshold:** `{details.get('jailbreakThreshold', 'N/A')}`")
770
- elif check_api_name == "toxicityCheck":
771
- st.write(f"**Threshold:** `{details.get('toxicitythreshold', 'N/A')}`")
772
- if details.get("toxicityScore"):
773
- st.write("**Toxicity Scores:**")
774
- for score_obj in details["toxicityScore"]:
775
- for name, score in score_obj.items():
776
- st.write(f"- **{name.title()}**: `{score}`")
777
- elif check_api_name == "profanityCheck":
778
- st.write(f"**Profanity Threshold:** `{details.get('profaneWordsthreshold', 'N/A')}`")
779
- profane_words = details.get("profaneWordsIdentified", [])
780
- st.write(f"**Profane Words Identified:** {', '.join(profane_words) if profane_words else 'None'}")
781
- elif check_api_name == "restrictedtopic":
782
- st.write(f"**Topic Threshold:** `{details.get('topicThreshold', 'N/A')}`")
783
- scores = details.get("topicScores", [])
784
- if scores:
785
- st.write("**Detected Topics Scores:**")
786
- for score_dict in scores:
787
- for topic, score in score_dict.items():
788
- st.write(f"- **{topic}:** `{score}`")
789
- else:
790
- st.write("**Detected Topics:** None")
791
- elif check_api_name == "textQuality":
792
- st.write(f"**Readability Score:** `{details.get('readabilityScore', 'N/A')}`")
793
- st.write(f"**Text Grade:** `{details.get('textGrade', 'N/A')}`")
794
- elif check_api_name == "customThemeCheck":
795
- st.write(f"**Similarity Score:** `{details.get('customSimilarityScore', 'N/A')}`")
796
- st.write(f"**Theme Threshold:** `{details.get('themeThreshold', 'N/A')}`")
797
- elif check_api_name == "privacyCheck":
798
- entities = details.get("entitiesRecognised", [])
799
- blocked = details.get("entitiesConfiguredToBlock", [])
800
- st.write(f"**Entities Recognized:** {', '.join(entities) if entities else 'None'}")
801
- st.write(f"**Entities Configured to Block:** {', '.join(blocked) if blocked else 'None'}")
802
- elif check_api_name == "refusalCheck":
803
- st.write(f"**Similarity Score:** `{details.get('refusalSimilarityScore', 'N/A')}`")
804
- st.write(f"**Threshold:** `{details.get('RefusalThreshold', 'N/A')}`")
805
-
806
- st.markdown("</div>", unsafe_allow_html=True)
807
- else:
808
- st.info("No individual checks to display.")
809
-
810
- except requests.exceptions.RequestException as e:
811
- st.error(f"API Request Error: {e}")
812
- except json.JSONDecodeError:
813
- st.error("Error decoding API response as JSON. Check if the API returned valid JSON.")
814
- except Exception as e:
815
  st.error(f"An unexpected error occurred: {e}.")
 
1
+ import streamlit as st
2
+ import requests
3
+ import json
4
+ import os
5
+
6
+ #Custom CSS Injection
7
+ st.markdown("""
8
+ <style>
9
+ :root {
10
+ --light-purple: rgba(179, 157, 219, 0.6);
11
+ --light-purple-border: rgba(179, 157, 219, 0.4);
12
+ --main-purple: #6a1b9a; /* A darker purple for accents */
13
+ --light-gray-border: #d0cfd0;
14
+ --text-color-dark: #555555;
15
+ --text-color-medium: #4a4a4a;
16
+ }
17
+
18
+ /* Import Font Awesome for icons */
19
+ @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css');
20
+
21
+ .stMainBlockContainer.block-container.st-emotion-cache-zy6yx3.e4man114 {
22
+ padding-top: 55px !important;
23
+ }
24
+
25
+ /* Style for the COLLAPSIBLE details container; the API Description */
26
+
27
+ #api-info {
28
+ width: 100%;
29
+ margin-top: 0 !important;
30
+ padding-top: 0 !important;
31
+ }
32
+
33
+ #api-info details {
34
+ width: 100%;
35
+ max-width: 100%;
36
+ box-sizing: border-box;
37
+ border: 2px solid var(--light-gray-border);
38
+ border-radius: 6.4px;
39
+ padding: 0.4rem 0.8rem;
40
+ background-color: #fff;
41
+ margin: 0;
42
+ transition: border-color 0.3s ease-in-out, box-shadow 0.3s ease-in-out;
43
+ }
44
+
45
+ /* Purple outline and shadow when OPEN or FOCUSED */
46
+ #api-info details:focus-within,
47
+ #api-info details[open] {
48
+ outline: none;
49
+ border-color: rgba(106, 27, 154, 0.2);
50
+ box-shadow: 0 0 0 1.5px rgba(106, 27, 154, 0.2);
51
+ }
52
+
53
+ /* Summary style and hover/focus behavior */
54
+ #api-info summary {
55
+ font-weight: 600;
56
+ font-size: 14.4px;
57
+ color: var(--text-color-dark);
58
+ cursor: pointer;
59
+ list-style: none;
60
+ margin: 0;
61
+ padding: 0.2rem 0;
62
+ transition: color 0.3s;
63
+ }
64
+
65
+ /* Purple color on hover/focus */
66
+ #api-info summary:hover,
67
+ #api-info summary:focus {
68
+ color: var(--main-purple);
69
+ }
70
+
71
+ /* Remove default marker */
72
+ #api-info summary::-webkit-details-marker {
73
+ display: none;
74
+ }
75
+
76
+ /* Custom arrow */
77
+ #api-info summary::after {
78
+ content: "›";
79
+ font-size: 14.4px;
80
+ margin-left: 6.4px;
81
+ float: right;
82
+ transition: transform 0.3s;
83
+ color: var(--main-purple);
84
+ }
85
+
86
+ #api-info details[open] summary::after {
87
+ transform: rotate(90deg);
88
+ }
89
+
90
+ /* Content styling */
91
+ #api-info .expander-content {
92
+ font-size: 14px !important;
93
+ padding: 0.6rem 0;
94
+ color: var(--text-color-medium);
95
+ font-family: Arial, sans-serif;
96
+ line-height: 1.28;
97
+ }
98
+
99
+ /* List indentation */
100
+ #api-info .expander-content ul {
101
+ margin-left: 0.96em;
102
+ }
103
+
104
+ /* Link appearance */
105
+ #api-info .expander-content a {
106
+ color: var(--main-purple);
107
+ text-decoration: none;
108
+ }
109
+
110
+ #api-info .expander-content a:hover {
111
+ text-decoration: underline;
112
+ }
113
+
114
+ /* --- Custom styles for Streamlit components --- */
115
+ /* Textarea focus & error states */
116
+ div[data-baseweb="textarea"] textarea:focus,
117
+ div[data-baseweb="textarea"] textarea:focus-visible {
118
+ outline: none !important;
119
+ box-shadow: 0 0 0 1.5px var(--light-purple) !important;
120
+ border: 1.5px solid var(--light-purple-border) !important;
121
+ }
122
+
123
+ /* Override red border on invalid textarea */
124
+ div[data-baseweb="textarea"] textarea:invalid {
125
+ box-shadow: 0 0 0 1.5px var(--light-purple) !important;
126
+ border-color: var(--light-purple-border) !important;
127
+ }
128
+
129
+ /* Checkbox focus outline */
130
+ div[data-baseweb="checkbox"] input[type="checkbox"]:focus-visible {
131
+ outline: none !important;
132
+ box-shadow: 0 0 0 1.5px var(--light-purple) !important;
133
+ border: 1.5px solid var(--light-purple-border) !important;
134
+ }
135
+
136
+ /* Checkbox checked fill (override red) */
137
+ div[data-baseweb="checkbox"] input[type="checkbox"]:checked {
138
+ background-color: var(--light-purple-border) !important;
139
+ border-color: var(--light-purple-border) !important;
140
+ }
141
+
142
+ /* Checkbox hover fill */
143
+ div[data-baseweb="checkbox"] input[type="checkbox"]:hover:not(:checked) {
144
+ background-color: rgba(179, 157, 219, 0.2) !important;
145
+ }
146
+
147
+ /* Checkbox checked hover fill */
148
+ div[data-baseweb="checkbox"] input[type="checkbox"]:checked:hover {
149
+ background-color: rgba(179, 157, 219, 0.5) !important;
150
+ }
151
+
152
+ /* Ensure Streamlit's checkmark color is visible */
153
+ div[data-baseweb="checkbox"] input[type="checkbox"]:checked::before {
154
+ color: white !important;
155
+ /* Adjust based on your purple background */
156
+ }
157
+
158
+ /* Style for sliders to match theme */
159
+ div[data-baseweb="slider"] div[role="slider"] {
160
+ background-color: var(--main-purple) !important;
161
+ }
162
+ div[data-baseweb="slider"] div[role="slider"]:hover {
163
+ background-color: var(--light-purple) !important;
164
+ }
165
+ div[data-baseweb="slider"] div[role="slider"]:focus {
166
+ box-shadow: 0 0 0 1.5px var(--light-purple) !important;
167
+ }
168
+ div[data-baseweb="slider"] div[data-baseweb="tooltip"] {
169
+ background-color: var(--main-purple) !important;
170
+ border-color: var(--main-purple) !important;
171
+ }
172
+ div[data-baseweb="slider"] div[data-baseweb="track"] {
173
+ background-color: var(--light-purple-border) !important;
174
+ }
175
+
176
+ /* Style for multiselect dropdowns */
177
+ div[data-baseweb="select"] {
178
+ border-radius: 6.4px;
179
+ border: 1.5px solid var(--light-purple-border);
180
+ }
181
+ div[data-baseweb="select"]:focus-within {
182
+ box-shadow: 0 0 0 1.5px var(--light-purple) !important;
183
+ border-color: var(--light-purple) !important;
184
+ }
185
+ /* Style for multiselect selected items */
186
+ div[data-baseweb="tag"] {
187
+ background-color: var(--light-purple) !important;
188
+ color: var(--text-color-dark) !important;
189
+ border-radius: 4px;
190
+ }
191
+ div[data-baseweb="tag"] svg { /* Close icon */
192
+ color: var(--text-color-dark) !important;
193
+ }
194
+
195
+ /* Custom button styling for icon buttons (Refresh button) */
196
+ .stButton > button[kind="secondary"] { /* Target non-primary buttons for icon styling */
197
+ background-color: transparent !important;
198
+ border: none !important;
199
+ padding: 0.5rem !important;
200
+ margin: 0 !important;
201
+ box-shadow: none !important;
202
+ color: var(--main-purple) !important;
203
+ font-size: 1.5rem !important;
204
+ /* Adjust icon size */
205
+ cursor: pointer;
206
+ transition: color 0.2s ease-in-out;
207
+ }
208
+ .stButton > button[kind="secondary"]:hover {
209
+ color: var(--light-purple) !important;
210
+ }
211
+ .stButton > button[kind="secondary"]:focus,
212
+ .stButton > button[kind="secondary"]:focus-visible {
213
+ outline: none !important;
214
+ box-shadow: 0 0 0 3px rgba(106, 27, 154, 0.4) !important;
215
+ /* Purple highlight on focus */
216
+ }
217
+
218
+ /* Custom button styling for primary buttons (Send button) */
219
+ .stButton button.primary {
220
+ background-color: var(--main-purple) !important;
221
+ color: var(--header-text-color) !important; /* White text */
222
+ border: 1.5px solid var(--main-purple) !important;
223
+ border-radius: 0.5rem !important;
224
+ padding: 0.75rem 1.5rem !important;
225
+ font-size: 1rem !important;
226
+ font-weight: 600 !important;
227
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2) !important;
228
+ transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
229
+ }
230
+
231
+ .stButton button.primary:hover {
232
+ background-color: #5a127a !important;
233
+ /* Slightly darker purple on hover */
234
+ border-color: #5a127a !important;
235
+ box-shadow: 0 4px 8px rgba(0,0,0,0.3) !important;
236
+ }
237
+
238
+ .stButton button.primary:focus,
239
+ .stButton button.primary:focus-visible {
240
+ outline: none !important;
241
+ box-shadow: 0 0 0 3px rgba(106, 27, 154, 0.4) !important;
242
+ /* Purple highlight on focus */
243
+ }
244
+
245
+ /* Adjust text_area to have scroll for long text */
246
+ textarea[aria-label="Prompt:"] {
247
+ overflow-y: auto !important;
248
+ min-height: 38px; /* Approximate single line height */
249
+ max-height: 100px;
250
+ /* Max height before scrollbar appears, adjust as needed */
251
+ resize: vertical;
252
+ /* Allow vertical resizing by user */
253
+ }
254
+
255
+ /* Ensure text area and button align properly */
256
+ div[data-testid="stVerticalBlock"] > div > div > div[data-testid="stHorizontalBlock"] {
257
+ display: flex;
258
+ align-items: flex-end; /* Align items to the bottom */
259
+ gap: 10px;
260
+ /* Space between textarea and button */
261
+ }
262
+
263
+ /* Specific styling for the refresh button container to align it */
264
+ div[data-testid="stVerticalBlock"] > div > div > div[data-testid="stHorizontalBlock"] > div:last-child {
265
+ display: flex;
266
+ align-items: flex-end; /* Align the button to the bottom of its flex container */
267
+ height: 100%;
268
+ /* Ensure it takes full height to align with textarea */
269
+ }
270
+
271
+ #for send button
272
+ .stButton.send-button-container {
273
+ display: flex;
274
+ justify-content: center;
275
+ margin-top: 1.5rem; /* Space above the send button */
276
+ }
277
+
278
+ /* Hide the label for the prompt text area */
279
+ div[data-testid="stTextarea"] label {
280
+ display: none !important;
281
+ }
282
+
283
+ /* Hide scrollbar for the parent container of the text area if it appears */
284
+ div[data-baseweb="textarea"] > div:first-child {
285
+ overflow: hidden !important;
286
+ }
287
+
288
+ /* Reduce vertical spacing between checkbox items */
289
+ div[data-baseweb="checkbox"] {
290
+ margin-bottom: 2px;
291
+ /* Adjust to control spacing */
292
+ }
293
+
294
+ div[data-baseweb="checkbox"] label {
295
+ margin: 0 !important;
296
+ padding: 0 !important;
297
+ }
298
+
299
+ </style>
300
+ """, unsafe_allow_html=True)
301
+
302
+ st.set_page_config(layout="wide")
303
+
304
+ #Initialize session state for dynamic elements
305
+ if 'custom_words' not in st.session_state:
306
+ st.session_state.custom_words = []
307
+
308
+ # Initialize session state for output visibility
309
+ if 'show_output' not in st.session_state:
310
+ st.session_state.show_output = False
311
+
312
+ # --- API Description (for Full Width) ---
313
+ st.markdown("""
314
+ <div id="api-info">
315
+ <details>
316
+ <summary>API DESCRIPTION</summary>
317
+ <div class="expander-content">
318
+ <h5 style="margin:0.4em 0;">What does this API do?</h5>
319
+ <p>The <strong>Moderation API</strong> evaluates prompts to determine whether they are safe for use with a large language model (LLM). It returns a <strong>status</strong> (passed or failed) with detailed scores.</p>
320
+ <h4 style="margin:0.64em 0 0.24em;">Moderation Checks</h4>
321
+ <ul>
322
+ <li><strong>Prompt Injection</strong>: Detects attempts to hijack or manipulate the LLM behavior.</li>
323
+ <li><strong>Jailbreak Attempts</strong>: Identifies prompts trying to bypass guardrails.</li>
324
+ <li><strong>Toxicity & Profanity</strong>: Flags harmful, offensive, or explicit content.</li>
325
+ <li><strong>Restricted Topics</strong>: Detects categories like cheating, conspiracy, terrorism, etc.</li>
326
+ <li><strong>Text Quality</strong>: Measures readability and clarity (informational only).</li>
327
+ <li><strong>Customized Theme</strong>: Block prompts using custom keywords (e.g., "atomic weapon").</li>
328
+ <li><strong>PII Detection</strong>: Identifies AADHAR, PAN, SSN, Passport, Email, Phone, IP, Credit Card, Medical License, etc.</li>
329
+ </ul>
330
+ <h4 style="margin:0.64em 0 0.24em;">Full Customization</h4>
331
+ <ul>
332
+ <li>Enable/disable checks</li>
333
+ <li>Set thresholds</li>
334
+ <li>Select PII or restricted topics</li>
335
+ <li>Define custom block terms</li>
336
+ </ul>
337
+ <h4 style="margin:0.64em 0 0.24em;">Resources</h4>
338
+ <ul>
339
+ <li><a href="https://huggingface.co/spaces/InfosysEnterprise/responsible-ai-moderationlayer/tree/main" target="_blank">Hugging Face Repo</a></li>
340
+ <li><a href="https://infosysenterprise-responsible-ai-moderationlayer.hf.space/rai/v1/moderations/docs/#" target="_blank">Swagger Docs</a></li>
341
+ </ul>
342
+ </div>
343
+ </details>
344
+ </div>
345
+ """, unsafe_allow_html=True)
346
+
347
+ #Main Layout: Two Columns for Inputs and Outputs
348
+ input_col, output_col = st.columns([0.5, 0.5]) # Input on left, Output on right
349
+
350
+ with input_col:
351
+ st.markdown('<h4 style="font-weight:550; margin-bottom:0.5rem; padding-top:2rem;">INPUT</h4>', unsafe_allow_html=True)
352
+
353
+ # Define all checks and their default properties
354
+ check_configs = {
355
+ "Prompt Injection": {"type": "slider", "default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01},
356
+ "Jailbreak": {"type": "slider", "default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01},
357
+ "Toxicity": {"type": "slider", "default": 0.6, "min": 0.0, "max": 1.0, "step": 0.01},
358
+ "Profanity": {"type": "number_input", "default": 1, "min": 1, "hint": "1"},
359
+ "Restricted Topics": {"type": "multiselect", "default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01,
360
+ "options": ["terrorism","explosives","nudity","cruelty","cheating","fraud","crime","hacking","immoral","unethical","illegal","robbery","forgery","misinformation"]},
361
+ "Text Quality": {"type": "no_threshold"},
362
+ "Customized Theme": {"type": "slider_and_input", "default": 0.6, "min": 0.0, "max": 1.0, "step": 0.01},
363
+ "PII Detection": {"type": "multiselect_pii",
364
+ "options": ["AADHAR_NUMBER", "PAN_Number", "IN_PAN", "US_PASSPORT", "US_SSN"]},
365
+ }
366
+
367
+ # Initialize session state for all checkboxes (default to checked)
368
+ for check_name, config in check_configs.items():
369
+ base_key = check_name.replace(' ', '_')
370
+
371
+ # Always ensure checkbox is initialized
372
+ checkbox_key = f"checkbox_{base_key}"
373
+ if checkbox_key not in st.session_state:
374
+ st.session_state[checkbox_key] = True # default to checked
375
+
376
+ # Set default values for controls
377
+ if config["type"] == "slider":
378
+ slider_key = f"slider_{base_key}"
379
+ if slider_key not in st.session_state:
380
+ st.session_state[slider_key] = config["default"]
381
+
382
+ elif config["type"] == "number_input":
383
+ number_key = f"number_{base_key}"
384
+ if number_key not in st.session_state:
385
+ st.session_state[number_key] = config["default"]
386
+
387
+ elif config["type"] == "multiselect":
388
+ multi_key = f"multiselect_{base_key}"
389
+ if multi_key not in st.session_state:
390
+ st.session_state[multi_key] = config["options"][:2]
391
+ slider_key = f"slider_{base_key}"
392
+ if slider_key not in st.session_state:
393
+ st.session_state[slider_key] = config["default"]
394
+
395
+ elif config["type"] == "multiselect_pii":
396
+ multi_key = f"multiselect_{base_key}"
397
+ if multi_key not in st.session_state:
398
+ st.session_state[multi_key] = config["options"]
399
+
400
+ elif config["type"] == "slider_and_input":
401
+ slider_key = f"slider_{base_key}"
402
+ if slider_key not in st.session_state:
403
+ st.session_state[slider_key] = config["default"]
404
+ if "custom_words" not in st.session_state:
405
+ st.session_state["custom_words"] = []
406
+
407
+ # Formatting for input box, send and refresh buttons
408
+ col1, col2, col3 = st.columns([7,0.7,0.7])
409
+ with col1:
410
+ user_input = st.text_input(
411
+ label="Prompt",
412
+ placeholder="Type your prompt here...",
413
+ value=st.session_state.get("prompt_input", ""),
414
+ key="prompt_input",
415
+ label_visibility="collapsed"
416
+ )
417
+ st.markdown("""
418
+ <div class="stButton send-button-container" style="display: flex; gap: 2px;">
419
+ """, unsafe_allow_html=True)
420
+ with col2:
421
+ refresh_clicked = st.button("↻", key="refresh_button_text", help="Reload the page")
422
+ with col3:
423
+ send_clicked = st.button("→", key="send_request_button", help="Send the request")
424
+
425
+ st.markdown("</div>", unsafe_allow_html=True)
426
+
427
+ #Handle Refresh Click
428
+ if refresh_clicked:
429
+
430
+ # Clear all checkboxes, sliders, multiselects, etc.
431
+ for check_name, config in check_configs.items():
432
+ base_key = check_name.replace(' ', '_')
433
+ st.session_state.pop(f"checkbox_{base_key}", None)
434
+ st.session_state.pop(f"slider_{base_key}", None)
435
+ st.session_state.pop(f"multiselect_{base_key}", None)
436
+ st.session_state.pop(f"number_{base_key}", None)
437
+
438
+ # Clear custom word logic
439
+ st.session_state.pop("custom_words", None)
440
+ st.session_state.pop("new_custom_word", None)
441
+
442
+ # Clear the input prompt
443
+ st.session_state.pop("prompt_input", None)
444
+
445
+ # Optionally: hide the output section
446
+ st.session_state["show_output"] = False
447
+
448
+ st.rerun()
449
+
450
+ if send_clicked:
451
+ user_input = st.session_state.get("prompt_input", "")
452
+ if not user_input.strip():
453
+ with output_col:
454
+ st.warning("Please enter a prompt before sending the request.")
455
+ st.stop()
456
+ st.session_state["show_output"] = True
457
+
458
+ selected_checks_payload_ui = {}
459
+ for check_name, config in check_configs.items():
460
+ base_key = check_name.replace(' ', '_')
461
+
462
+ enabled = st.session_state.get(f"checkbox_{base_key}", False)
463
+
464
+ if enabled:
465
+ payload_item = {"enabled": True}
466
+
467
+ if config["type"] == "slider":
468
+ payload_item["threshold"] = st.session_state.get(f"slider_{base_key}", config["default"])
469
+ elif config["type"] == "number_input":
470
+ payload_item["threshold"] = st.session_state.get(f"number_{base_key}", config["default"])
471
+ elif config["type"] == "multiselect":
472
+ payload_item["threshold"] = st.session_state.get(f"slider_{base_key}", config["default"])
473
+ payload_item["topics"] = st.session_state.get(f"multiselect_{base_key}", config["options"][:2])
474
+ elif config["type"] == "multiselect_pii":
475
+ payload_item["entities_to_block"] = st.session_state.get(f"multiselect_{base_key}", config["options"])
476
+ elif config["type"] == "slider_and_input":
477
+ payload_item["threshold"] = st.session_state.get(f"slider_{base_key}", config["default"])
478
+ payload_item["custom_words"] = st.session_state.get("custom_words", [])
479
+
480
+ selected_checks_payload_ui[check_name] = payload_item
481
+ else:
482
+ selected_checks_payload_ui[check_name] = {"enabled": False}
483
+
484
+ st.markdown('<h3 style="font-size:20px; font-weight:600; color:var(--text-color-dark);">Select Moderation Checks to apply:</h3>', unsafe_allow_html=True)
485
+
486
+ # Mapping from UI check names to API check names
487
+ api_check_name_map = {
488
+ "Prompt Injection": "PromptInjection",
489
+ "Jailbreak": "JailBreak",
490
+ "Toxicity": "Toxicity",
491
+ "Profanity": "Profanity",
492
+ "Restricted Topics": "RestrictTopic",
493
+ "Text Quality": "TextQuality",
494
+ "Customized Theme": "CustomizedTheme",
495
+ "PII Detection": "Piidetct"
496
+ }
497
+
498
+ # Display checkboxes and conditional UIs
499
+ for check_name, config in check_configs.items():
500
+ base_key = check_name.replace(' ', '_')
501
+ col1, col2 = st.columns([0.3, 0.7])
502
+
503
+ with col1:
504
+ enabled = st.checkbox(check_name, key=f"checkbox_{base_key}")
505
+
506
+ if enabled and config["type"] != "no_threshold":
507
+ with col2:
508
+ with st.expander(f"Configure {check_name}"):
509
+ if config["type"] == "slider":
510
+ # CORRECT: Removed 'value=config["default"]'
511
+ threshold = st.slider(
512
+ f"Set Threshold for {check_name}",
513
+ min_value=config["min"],
514
+ max_value=config["max"],
515
+ step=config["step"],
516
+ key=f"slider_{base_key}"
517
+ )
518
+
519
+ elif config["type"] == "number_input":
520
+ profanity_threshold = st.number_input(
521
+ f"Set Profanity Count Threshold (whole numbers only)",
522
+ min_value=config["min"],
523
+ key=f"number_{base_key}",
524
+ help=f"Enter a number, e.g., {config['hint']}"
525
+ )
526
+
527
+ elif config["type"] == "multiselect":
528
+
529
+ threshold = st.slider(
530
+ f"Set Threshold for {check_name}",
531
+ min_value=config["min"],
532
+ max_value=config["max"],
533
+ step=config["step"],
534
+ key=f"slider_{base_key}"
535
+ )
536
+ if slider_key not in st.session_state:
537
+ st.session_state[slider_key] = config["default"]
538
+
539
+ selected_topics = st.multiselect(
540
+ "Select Restricted Topics:",
541
+ options=config["options"],
542
+ key=f"multiselect_{base_key}"
543
+ )
544
+
545
+ elif config["type"] == "multiselect_pii":
546
+
547
+ selected_entities = st.multiselect(
548
+ "Select PII Entities to Block:",
549
+ options=config["options"],
550
+ key=f"multiselect_{base_key}"
551
+ )
552
+
553
+ elif config["type"] == "slider_and_input":
554
+
555
+ threshold = st.slider(
556
+ f"Set Threshold for {check_name}",
557
+ min_value=config["min"],
558
+ max_value=config["max"],
559
+ step=config["step"],
560
+ key=f"slider_{base_key}"
561
+ )
562
+
563
+ st.markdown("---")
564
+ st.subheader("Custom Words for Theme")
565
+
566
+ # Using a single, consistent placeholder for messages
567
+ message_placeholder = st.empty()
568
+
569
+ expander_key = f"custom_words_expander_{base_key}"
570
+
571
+ def add_word_callback():
572
+ new_word_input = st.session_state.get("new_custom_word", "")
573
+ new_word_to_add = new_word_input.strip()
574
+ if new_word_to_add and new_word_to_add not in st.session_state.custom_words:
575
+ st.session_state.custom_words.append(new_word_to_add)
576
+ st.session_state.new_custom_word = ""
577
+ st.session_state[expander_key] = True # Keep expander open
578
+ message_placeholder.success(f"'{new_word_to_add}' added!")
579
+ elif new_word_to_add:
580
+ st.session_state[expander_key] = True # Keep expander open
581
+ message_placeholder.warning("Word already exists.")
582
+ else:
583
+ st.session_state[expander_key] = True # Keep expander open
584
+ message_placeholder.warning("Word is empty.")
585
+
586
+ def delete_word_callback(index):
587
+ st.session_state.custom_words.pop(index)
588
+ st.session_state[expander_key] = True # Keep expander open
589
+ message_placeholder.success("Word removed successfully!")
590
+
591
+ st.text_input("Add a new custom word:", key="new_custom_word")
592
+ st.button("Add Word", key="add_custom_word_btn", on_click=add_word_callback)
593
+
594
+ if st.session_state.custom_words:
595
+ st.write("Current Custom Words:")
596
+ if expander_key not in st.session_state:
597
+ st.session_state[expander_key] = True
598
+
599
+ with st.expander("Show/Hide Custom Words", expanded=st.session_state.get(expander_key, False)):
600
+ for i, word in enumerate(st.session_state.custom_words):
601
+ word_col, btn_col = st.columns([0.8, 0.2])
602
+ with word_col:
603
+ st.write(f"- {word}")
604
+ with btn_col:
605
+ # Use the on_click callback for the delete button
606
+ st.button("del", key=f"remove_word_{i}", on_click=delete_word_callback, args=(i,))
607
+ else:
608
+ st.info("No custom words added yet.")
609
+
610
+ if st.session_state.show_output:
611
+ with output_col:
612
+ # Construct the final API payload
613
+ final_api_payload = {
614
+ "AccountName": "None",
615
+ "userid": "None",
616
+ "PortfolioName": "None",
617
+ "lotNumber": 1,
618
+ "translate": "no",
619
+ "EmojiModeration": "yes",
620
+ "Prompt": user_input,
621
+ "ModerationChecks": [],
622
+ "ModerationCheckThresholds": {}
623
+ }
624
+
625
+ for check_name, data in selected_checks_payload_ui.items():
626
+
627
+ #customized theme, need to make changes here..
628
+ if check_name == "Customized Theme":
629
+ final_api_payload["ModerationChecks"].append(api_check_name_map["Customized Theme"])
630
+ final_api_payload["ModerationCheckThresholds"]["CustomTheme"] = {
631
+ "Themename": "string",
632
+ "Themethresold": data.get("threshold", check_configs["Customized Theme"]["default"]),
633
+ "ThemeTexts": data.get("custom_words", [])
634
+ }
635
+
636
+ elif data["enabled"]: # For all other checks, only include if enabled
637
+ if check_name in api_check_name_map:
638
+ final_api_payload["ModerationChecks"].append(api_check_name_map[check_name])
639
+
640
+ # Populate ModerationCheckThresholds for enabled checks from UI
641
+ if check_name == "Prompt Injection":
642
+ final_api_payload["ModerationCheckThresholds"]["PromptinjectionThreshold"] = data.get("threshold")
643
+ elif check_name == "Jailbreak":
644
+ final_api_payload["ModerationCheckThresholds"]["JailbreakThreshold"] = data.get("threshold")
645
+ elif check_name == "Toxicity":
646
+ toxicity_threshold = data.get("threshold")
647
+ final_api_payload["ModerationCheckThresholds"]["ToxicityThresholds"] = {
648
+ "ToxicityThreshold": toxicity_threshold,
649
+ "SevereToxicityThreshold": toxicity_threshold,
650
+ "ObsceneThreshold": toxicity_threshold,
651
+ "ThreatThreshold": toxicity_threshold,
652
+ "InsultThreshold": toxicity_threshold,
653
+ "IdentityAttackThreshold": toxicity_threshold,
654
+ "SexualExplicitThreshold": toxicity_threshold
655
+ }
656
+ elif check_name == "Profanity":
657
+ final_api_payload["ModerationCheckThresholds"]["ProfanityCountThreshold"] = data.get("threshold")
658
+ elif check_name == "Restricted Topics":
659
+ final_api_payload["ModerationCheckThresholds"]["RestrictedtopicDetails"] = {
660
+ "RestrictedtopicThreshold": data.get("threshold"),
661
+ "Restrictedtopics": data.get("topics", [])
662
+ }
663
+ elif check_name == "PII Detection":
664
+ final_api_payload["ModerationCheckThresholds"]["PiientitiesConfiguredToBlock"] = data.get("entities_to_block", [])
665
+
666
+ # Ensure unique checks in ModerationChecks list
667
+ final_api_payload["ModerationChecks"] = list(set(final_api_payload["ModerationChecks"]))
668
+
669
+ with output_col:
670
+
671
+ if st.session_state.show_output:
672
+ st.markdown('<h4 style="font-weight:550; margin-bottom:0.5rem; padding-top:2rem;">OUTPUT</h4>', unsafe_allow_html=True)
673
+ results_placeholder = st.empty() # Placeholder for output results
674
+
675
+ with results_placeholder.container(): # Display payload in output column
676
+ try:
677
+ # Actual API call
678
+ resp = requests.post("https://infosysenterprise-responsible-ai-moderationlayer.hf.space/rai/v1/moderations", json=final_api_payload)
679
+ response_data = resp.json()
680
+
681
+ if not isinstance(response_data, dict):
682
+ st.error("API response was not a valid JSON object (dictionary).")
683
+ st.stop()
684
+
685
+ st.write("Moderation Results")
686
+
687
+ overall_status = response_data.get("moderationResults", {}).get("summary", {}).get("status", "N/A")
688
+ overall_reason = response_data.get("moderationResults", {}).get("summary", {}).get("reason", [])
689
+
690
+ if overall_status.lower() == "passed":
691
+ st.success(f"Overall Status: {overall_status.upper()}")
692
+ else:
693
+ st.error(f"Overall Status: {overall_status.upper()}")
694
+ if overall_reason:
695
+ failed_checks_str = ", ".join(overall_reason)
696
+ st.warning(f"Failed Checks: {failed_checks_str}")
697
+
698
+ st.write("Individual Check Details:")
699
+
700
+ moderation_results = response_data.get("moderationResults", {})
701
+ if not moderation_results:
702
+ st.info("No detailed moderation results available.")
703
+ else:
704
+ # Mapping from UI labels to API keys
705
+ ui_to_api_check_map = {
706
+ "Prompt Injection": "promptInjectionCheck",
707
+ "Jailbreak": "jailbreakCheck",
708
+ "Toxicity": "toxicityCheck",
709
+ "Profanity": "profanityCheck",
710
+ "Restricted Topics": "restrictedtopic",
711
+ "Text Quality": "textQuality",
712
+ "Customized Theme": "customThemeCheck",
713
+ "PII Detection": "privacyCheck",
714
+ "Refusal": "refusalCheck"
715
+ }
716
+
717
+ api_response_name_map = {v: k for k, v in ui_to_api_check_map.items()}
718
+
719
+
720
+ moderation_results = response_data.get("moderationResults", {})
721
+
722
+ # Filter out 'summary' and other non-check entries like 'text'
723
+ individual_results = {
724
+ k: v for k, v in moderation_results.items()
725
+ if k not in {"summary", "text"} and isinstance(v, dict)
726
+ }
727
+
728
+ # Determine which checks were enabled in UI and are present in API
729
+ enabled_api_checks = []
730
+ for ui_check, api_key in ui_to_api_check_map.items():
731
+ if selected_checks_payload_ui.get(ui_check, {}).get("enabled"):
732
+ if api_key in individual_results:
733
+ enabled_api_checks.append(api_key)
734
+ elif ui_check == "Customized Theme":
735
+ if st.session_state.get("custom_words") or selected_checks_payload_ui.get(ui_check, {}).get("threshold") is not None:
736
+ if api_key in individual_results:
737
+ enabled_api_checks.append(api_key)
738
+
739
+ # Sort by display name
740
+ sorted_api_check_names = sorted(
741
+ enabled_api_checks,
742
+ key=lambda x: api_response_name_map.get(x, x)
743
+ )
744
+
745
+ # Display checks
746
+ if sorted_api_check_names:
747
+ for check_api_name in sorted_api_check_names:
748
+ details = individual_results.get(check_api_name, {})
749
+ display_name = api_response_name_map.get(check_api_name, check_api_name)
750
+ status = details.get("result", "N/A")
751
+
752
+ expander_style = ""
753
+ if status.lower() == "passed":
754
+ expander_style = "border-left: 5px solid green; padding-left: 10px;"
755
+ elif status.lower() == "failed":
756
+ expander_style = "border-left: 5px solid red; padding-left: 10px;"
757
+ elif status.lower() == "info":
758
+ expander_style = "border-left: 5px solid orange; padding-left: 10px;"
759
+
760
+ with st.expander(f"**{display_name}** - Status: **{status.upper()}**"):
761
+ st.markdown(f"<div style='{expander_style}'>", unsafe_allow_html=True)
762
+
763
+ # Add your detailed logic below
764
+ if check_api_name == "promptInjectionCheck":
765
+ st.write(f"**Confidence Score:** `{details.get('injectionConfidenceScore', 'N/A')}`")
766
+ st.write(f"**Threshold:** `{details.get('injectionThreshold', 'N/A')}`")
767
+ elif check_api_name == "jailbreakCheck":
768
+ st.write(f"**Similarity Score:** `{details.get('jailbreakSimilarityScore', 'N/A')}`")
769
+ st.write(f"**Threshold:** `{details.get('jailbreakThreshold', 'N/A')}`")
770
+ elif check_api_name == "toxicityCheck":
771
+ st.write(f"**Threshold:** `{details.get('toxicitythreshold', 'N/A')}`")
772
+ if details.get("toxicityScore"):
773
+ st.write("**Toxicity Scores:**")
774
+ for score_obj in details["toxicityScore"]:
775
+ for name, score in score_obj.items():
776
+ st.write(f"- **{name.title()}**: `{score}`")
777
+ elif check_api_name == "profanityCheck":
778
+ st.write(f"**Profanity Threshold:** `{details.get('profaneWordsthreshold', 'N/A')}`")
779
+ profane_words = details.get("profaneWordsIdentified", [])
780
+ st.write(f"**Profane Words Identified:** {', '.join(profane_words) if profane_words else 'None'}")
781
+ elif check_api_name == "restrictedtopic":
782
+ st.write(f"**Topic Threshold:** `{details.get('topicThreshold', 'N/A')}`")
783
+ scores = details.get("topicScores", [])
784
+ if scores:
785
+ st.write("**Detected Topics Scores:**")
786
+ for score_dict in scores:
787
+ for topic, score in score_dict.items():
788
+ st.write(f"- **{topic}:** `{score}`")
789
+ else:
790
+ st.write("**Detected Topics:** None")
791
+ elif check_api_name == "textQuality":
792
+ st.write(f"**Readability Score:** `{details.get('readabilityScore', 'N/A')}`")
793
+ st.write(f"**Text Grade:** `{details.get('textGrade', 'N/A')}`")
794
+ elif check_api_name == "customThemeCheck":
795
+ st.write(f"**Similarity Score:** `{details.get('customSimilarityScore', 'N/A')}`")
796
+ st.write(f"**Theme Threshold:** `{details.get('themeThreshold', 'N/A')}`")
797
+ elif check_api_name == "privacyCheck":
798
+ entities = details.get("entitiesRecognised", [])
799
+ blocked = details.get("entitiesConfiguredToBlock", [])
800
+ st.write(f"**Entities Recognized:** {', '.join(entities) if entities else 'None'}")
801
+ st.write(f"**Entities Configured to Block:** {', '.join(blocked) if blocked else 'None'}")
802
+ elif check_api_name == "refusalCheck":
803
+ st.write(f"**Similarity Score:** `{details.get('refusalSimilarityScore', 'N/A')}`")
804
+ st.write(f"**Threshold:** `{details.get('RefusalThreshold', 'N/A')}`")
805
+
806
+ st.markdown("</div>", unsafe_allow_html=True)
807
+ else:
808
+ st.info("No individual checks to display.")
809
+
810
+ except requests.exceptions.RequestException as e:
811
+ st.error(f"API Request Error: {e}")
812
+ except json.JSONDecodeError:
813
+ st.error("Error decoding API response as JSON. Check if the API returned valid JSON.")
814
+ except Exception as e:
815
  st.error(f"An unexpected error occurred: {e}.")