Decoo's picture
modified app.py
ffc8c24
raw
history blame
16.9 kB
import gradio as gr
import json
import os
from datetime import datetime
import urllib.parse
# Data folder paths
DATA_BASE = "Viewer/Data/Our_system"
FOLDER_MAP = {
"qa_subtopics": f"{DATA_BASE}/QA+Topics",
"qa_sdgs": f"{DATA_BASE}/QA+SDGs",
"summary_subtopics": f"{DATA_BASE}/Summary+Topics",
"summary_sdgs": f"{DATA_BASE}/Summary+SDG"
}
def get_available_events():
"""Get all available events from JSON files"""
events = set()
for folder in FOLDER_MAP.values():
if os.path.exists(folder):
for file in os.listdir(folder):
if file.endswith('_combined_data.json'):
event_name = file.replace('_combined_data.json', '')
if not event_name.isdigit():
events.add(event_name)
return sorted(list(events))
def load_event_data(event_name, format_type, view_type):
"""Load and render event data as HTML"""
if not event_name:
return '<div style="text-align: center; padding: 40px; color: #6c757d;"><h2>Please select an event from the dropdown above.</h2></div>'
key = f"{format_type}_{view_type}"
folder = FOLDER_MAP.get(key)
filename = f"{folder}/{event_name}_combined_data.json"
try:
with open(filename, 'r', encoding='utf-8') as f:
data = json.load(f)
return render_data_as_html(data, event_name, format_type, view_type)
except FileNotFoundError:
return f'<div style="text-align: center; padding: 40px;"><h2>⚠ File not found</h2><p>{filename}</p></div>'
except Exception as e:
return f'<div style="text-align: center; padding: 40px;"><h2>⚠ Error</h2><p>{str(e)}</p></div>'
def render_data_as_html(data, event_name, format_type, view_type):
"""Render JSON data as HTML"""
# Estrai informazioni
file_name = data.get('file_name', event_name)
summary = data.get('summary', 'No summary available')
summary_contexts = data.get('summary_contexts', {})
# Process clusters
clusters = process_clusters(data, format_type)
# Genera un ID unico per questa istanza
unique_id = f"viewer_{abs(hash(event_name + format_type + view_type)) % 100000}"
# Render HTML con modal
html_parts = [f'''
<div id="{unique_id}_container">
<!-- Modal for citations -->
<div id="{unique_id}_modal" class="citation-modal" style="display: none; position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); backdrop-filter: blur(3px);">
<div class="citation-modal-content" style="background: white; margin: 5% auto; padding: 0; border-radius: 15px; width: 90%; max-width: 700px; max-height: 80vh; overflow-y: auto; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); animation: slideIn 0.3s ease-out;">
<div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 20px; border-radius: 15px 15px 0 0; display: flex; justify-content: space-between; align-items: center;">
<h3 style="font-size: 1.2em; font-weight: 600; margin: 0;">Citation Details</h3>
<span class="citation-close" style="color: white; font-size: 28px; font-weight: bold; cursor: pointer; line-height: 1; transition: opacity 0.3s;">&times;</span>
</div>
<div style="padding: 25px;">
<div style="margin-bottom: 20px;">
<div style="font-weight: 600; color: #495057; margin-bottom: 8px; font-size: 1.05em;">Context:</div>
<div class="citation-context" style="background: #f8f9fa; padding: 15px; border-radius: 8px; line-height: 1.6; border-left: 4px solid #007bff;"></div>
</div>
<div style="margin-bottom: 20px;">
<div style="font-weight: 600; color: #495057; margin-bottom: 8px; font-size: 1.05em;">Title:</div>
<div class="citation-title"></div>
</div>
<div style="margin-top: 15px;">
<div style="font-weight: 600; color: #495057; margin-bottom: 8px; font-size: 1.05em;">Source URL:</div>
<div><a class="citation-url" href="#" target="_blank" style="color: #007bff; text-decoration: none; word-break: break-all; font-size: 0.95em;"></a></div>
</div>
</div>
</div>
</div>
<div style="margin-bottom: 40px; border: 2px solid #e1e5e9; border-radius: 12px; overflow: hidden;">
<div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 20px; font-size: 1.3em; font-weight: 600;">
πŸ“„ {escape_html(file_name)}
</div>
<div style="padding: 25px;">
<div style="background: #f8f9fa; border-left: 4px solid #667eea; padding: 20px; margin: 20px 0; border-radius: 0 8px 8px 0; line-height: 1.6;">
<strong>Summary:</strong><br>{process_citations(escape_html(summary), summary_contexts, unique_id)}
</div>
''']
# Render clusters
for cluster in clusters:
html_parts.append(render_cluster(cluster, format_type, unique_id))
html_parts.append(f'''
</div>
</div>
</div>
<style>
@keyframes slideIn {{
from {{ transform: translateY(-50px); opacity: 0; }}
to {{ transform: translateY(0); opacity: 1; }}
}}
.citation-close:hover {{
opacity: 0.7;
}}
.citation-badge {{
display: inline;
background: #007bff;
color: white;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.85em;
font-weight: 600;
cursor: pointer;
margin: 0 2px;
transition: all 0.3s ease;
}}
.citation-badge:hover {{
background: #0056b3;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.3);
}}
</style>
<script>
(function() {{
const container = document.getElementById('{unique_id}_container');
const modal = container.querySelector('#{unique_id}_modal');
const closeBtn = modal.querySelector('.citation-close');
// Close on X button
closeBtn.addEventListener('click', function() {{
modal.style.display = 'none';
}});
// Close on outside click
modal.addEventListener('click', function(event) {{
if (event.target === modal) {{
modal.style.display = 'none';
}}
}});
// Close on ESC
document.addEventListener('keydown', function(event) {{
if (event.key === 'Escape' && modal.style.display === 'block') {{
modal.style.display = 'none';
}}
}});
// Handle citation clicks
container.addEventListener('click', function(event) {{
const target = event.target;
if (target.classList && target.classList.contains('citation-badge')) {{
const contextData = target.getAttribute('data-context');
const titleData = target.getAttribute('data-title');
const urlData = target.getAttribute('data-url');
if (contextData || titleData || urlData) {{
const contextEl = modal.querySelector('.citation-context');
const titleEl = modal.querySelector('.citation-title');
const urlEl = modal.querySelector('.citation-url');
contextEl.textContent = decodeURIComponent(contextData || 'No context available.');
titleEl.textContent = decodeURIComponent(titleData || 'No title available.');
urlEl.textContent = decodeURIComponent(urlData || 'No URL provided.');
urlEl.href = decodeURIComponent(urlData || '#');
modal.style.display = 'block';
}}
}}
}});
}})();
</script>
''')
return ''.join(html_parts)
def process_clusters(data, format_type):
"""Extract clusters from data"""
if 'clusters' in data and isinstance(data['clusters'], list):
return data['clusters']
ignore_keys = ['file_name', 'summary', 'summary_contexts']
clusters = []
for key, value in data.items():
if key in ignore_keys:
continue
if format_type == 'qa':
clusters.append({
'cluster_headline': key,
'questions_and_answers': value if isinstance(value, list) else []
})
else:
if isinstance(value, list):
summary_text = '\n\n'.join([
v.get('cluster_summary') or v.get('summary', '')
for v in value if isinstance(v, dict)
])
clusters.append({
'cluster_headline': key,
'cluster_summary': summary_text or 'No summary',
'used_contexts': value[0].get('used_contexts', {}) if value else {}
})
elif isinstance(value, dict):
clusters.append({
'cluster_headline': key,
'cluster_summary': value.get('cluster_summary') or value.get('summary', 'No summary'),
'used_contexts': value.get('used_contexts', {})
})
return clusters
def render_cluster(cluster, format_type, unique_id):
"""Render a single cluster as HTML"""
header = cluster.get('cluster_headline', 'Cluster')
html = f'''
<div style="margin: 30px 0; border: 1px solid #dee2e6; border-radius: 10px; overflow: hidden;">
<details>
<summary style="background: linear-gradient(45deg, #28a745, #20c997); color: white; padding: 15px 20px; font-weight: 600; font-size: 1.1em; cursor: pointer;">
🎯 {escape_html(header)}
</summary>
<div style="padding: 20px;">
'''
if format_type == 'summary':
summary_text = cluster.get('cluster_summary', 'No summary')
contexts = cluster.get('used_contexts', {})
html += f'<div style="background: #e8f5e8; border-radius: 8px; padding: 15px; line-height: 1.6;">{process_citations(escape_html(summary_text), contexts, unique_id)}</div>'
else:
qas = cluster.get('questions_and_answers', [])
if not qas:
html += '<div style="text-align: center; color: #6c757d; font-style: italic;">No Q&A available</div>'
else:
for qa in qas:
question = qa.get('question') or qa.get('Question', 'No question')
answer = qa.get('updated_retrieved_answer') or qa.get('retrieved_answer') or qa.get('answer', 'No answer')
contexts = qa.get('used_contexts') or qa.get('summary_contexts', {})
html += f'''
<div style="margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px; border-left: 4px solid #007bff;">
<div style="font-weight: 600; color: #495057; margin-bottom: 10px; font-size: 1.05em;">
❓ {escape_html(question)}
</div>
<div style="color: #6c757d; line-height: 1.5;">
{process_citations(escape_html(answer), contexts, unique_id)}
</div>
</div>
'''
html += '</div></details></div>'
return html
def process_citations(text, contexts, unique_id):
"""Add citation spans with popup data"""
import re
def replace_citation(match):
cit_id = match.group(1)
ctx = contexts.get(cit_id, {})
if not ctx:
return match.group(0)
context_text = urllib.parse.quote(ctx.get('context', '')[:500])
title_text = urllib.parse.quote(ctx.get('title', ''))
url_text = urllib.parse.quote(ctx.get('url', ''))
return f'<span class="citation-badge" data-context="{context_text}" data-title="{title_text}" data-url="{url_text}">[{cit_id}]</span>'
return re.sub(r'\[(\d+)\]', replace_citation, text)
def escape_html(text):
"""Escape HTML special characters"""
if not text:
return ''
return str(text).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#039;')
def save_feedback(event, format_type, view_type, rating, comment):
"""Save feedback to JSON file"""
if not event:
return "⚠️ Please select an event first"
feedback_dir = "feedback"
os.makedirs(feedback_dir, exist_ok=True)
feedback_file = os.path.join(feedback_dir, "feedback.json")
if os.path.exists(feedback_file):
with open(feedback_file, 'r', encoding='utf-8') as f:
feedbacks = json.load(f)
else:
feedbacks = []
feedbacks.append({
"timestamp": datetime.now().isoformat(),
"event": event,
"format": format_type,
"view": view_type,
"rating": rating,
"comment": comment
})
with open(feedback_file, 'w', encoding='utf-8') as f:
json.dump(feedbacks, f, indent=2, ensure_ascii=False)
return "βœ… Thank you for your feedback!"
# Get events
events = get_available_events()
# CSS personalizzato con font originale
custom_css = """
* {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
}
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.gradio-container {
max-width: 1200px !important;
}
"""
# Create Gradio interface
with gr.Blocks(title="Report Viewer", theme=gr.themes.Soft(), css=custom_css) as demo:
gr.Markdown("""
<div style="text-align: center; color: white; margin-bottom: 30px;">
<h1 style="font-size: 2.5em; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0,0,0,0.3);">πŸ“Š Our Report Viewer</h1>
<p style="font-size: 1.2em; opacity: 0.9;">Explore events organized by topics or SDGs</p>
</div>
""")
with gr.Tab("πŸ“– Viewer"):
with gr.Row():
with gr.Column(scale=2):
event_dropdown = gr.Dropdown(
choices=events,
label="Select Event",
value=None
)
with gr.Column(scale=1):
view_radio = gr.Radio(
choices=["subtopics", "sdgs"],
label="Arranged by",
value="subtopics"
)
with gr.Column(scale=1):
format_radio = gr.Radio(
choices=["qa", "summary"],
label="Presented as",
value="qa"
)
content_html = gr.HTML(
'<div style="text-align: center; padding: 40px; color: #6c757d;"><h2>Please select an event from the dropdown above.</h2></div>'
)
# Update on any change
event_dropdown.change(
load_event_data,
inputs=[event_dropdown, format_radio, view_radio],
outputs=content_html
)
format_radio.change(
load_event_data,
inputs=[event_dropdown, format_radio, view_radio],
outputs=content_html
)
view_radio.change(
load_event_data,
inputs=[event_dropdown, format_radio, view_radio],
outputs=content_html
)
with gr.Tab("πŸ’¬ Feedback"):
gr.Markdown("### Leave your feedback on the results")
with gr.Row():
fb_event = gr.Dropdown(choices=events, label="Event")
fb_format = gr.Radio(choices=["qa", "summary"], label="Format", value="qa")
fb_view = gr.Radio(choices=["subtopics", "sdgs"], label="View", value="subtopics")
fb_rating = gr.Slider(minimum=1, maximum=5, step=1, label="Rating (1-5 stars)", value=3)
fb_comment = gr.Textbox(label="Comment (optional)", lines=5, placeholder="Write your feedback here...")
fb_submit = gr.Button("Submit Feedback", variant="primary")
fb_output = gr.Textbox(label="Status", interactive=False)
fb_submit.click(
save_feedback,
inputs=[fb_event, fb_format, fb_view, fb_rating, fb_comment],
outputs=fb_output
)
if __name__ == "__main__":
demo.launch()