SCC / app.py
mabuseif's picture
Update app.py
ea6bf79 verified
raw
history blame
19.4 kB
import streamlit as st
import streamlit.components.v1 as components
import hashlib
import urllib.parse
from datetime import datetime
import pytz
import pandas as pd
import json
# --- Constants ---
MELBOURNE_TIMEZONE = 'Australia/Melbourne'
# --- Custom CSS for simplified UI ---
def load_css():
st.markdown("""
<style>
.main-header {
padding: 2rem;
text-align: center;
margin-bottom: 2rem;
}
.citation-output {
background: #f8f8f8;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 1rem;
margin: 1rem 0;
font-family: 'Courier New', monospace;
}
.copy-button {
background: #e0e0e0;
color: black;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
margin-top: 0.5rem;
}
.copy-button:hover {
background: #d0d0d0;
}
.warning-box {
background: #f8f8f8;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 1rem;
margin: 1rem 0;
}
.success-box {
background: #f8f8f8;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 1rem;
margin: 1rem 0;
}
.info-card {
background: white;
border-radius: 4px;
padding: 1.5rem;
margin: 1rem 0;
border-left: 1px solid #e0e0e0;
}
.footer {
text-align: center;
padding: 2rem;
margin-top: 2rem;
border-top: 1px solid #e0e0e0;
font-size: 0.9rem;
}
.hash-display {
background: #f8f8f8;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 1rem;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
word-break: break-all;
margin: 0.5rem 0;
}
.tab-content {
padding: 2rem 0;
}
.datetime-display {
background: #f8f8f8;
border-radius: 4px;
padding: 0.8rem;
margin: 0.5rem 0;
border-left: 1px solid #e0e0e0;
}
.verification-table {
margin: 1rem 0;
border-radius: 4px;
overflow: hidden;
}
</style>
""", unsafe_allow_html=True)
# --- Helper Functions ---
def generate_citation_hash(author, year, url, fragment_text, cited_text, username, task_name, current_date, current_time):
data = f"{author}, {year} | {url} | {fragment_text} | {cited_text} | {username} | {task_name} | {current_date} | {current_time}"
return hashlib.sha256(data.encode('utf-8')).hexdigest()
def format_citation_html(url, fragment_text, author, year, scc_hash):
encoded_fragment = urllib.parse.quote(fragment_text)
full_url = f"{url}#:~:text={encoded_fragment}"
return f'<a href="{full_url}" data-hash="{scc_hash}">{author}, {year}</a>'
def check_for_fragment(url):
return '#:~:text=' in url
def copy_to_clipboard_js(text, button_id):
"""Generate JavaScript for copying text to clipboard"""
return f"""
<script>
function copyToClipboard_{button_id}() {{
navigator.clipboard.writeText(`{text}`).then(function() {{
document.getElementById('copy_status_{button_id}').innerHTML = 'Copied!';
setTimeout(function() {{
document.getElementById('copy_status_{button_id}').innerHTML = '';
}}, 2000);
}}, function(err) {{
document.getElementById('copy_status_{button_id}').innerHTML = 'Copy failed';
console.error('Could not copy text: ', err);
}});
}}
</script>
<button onclick="copyToClipboard_{button_id}()" class="copy-button">Copy to Clipboard</button>
<span id="copy_status_{button_id}" style="margin-left: 10px; font-weight: bold;"></span>
"""
# --- Live Clock JavaScript ---
def live_clock():
return """
<div class="datetime-display">
<span id="live_datetime"></span>
</div>
<script>
function updateClock() {
const options = {
timeZone: 'Australia/Melbourne',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
};
const formatter = new Intl.DateTimeFormat('en-AU', options);
const now = new Date();
const parts = formatter.formatToParts(now);
const date = `${parts[4].value}-${parts[2].value}-${parts[0].value}`;
const time = `${parts[6].value}:${parts[8].value}:${parts[10].value}`;
const datetimeElement = document.getElementById('live_datetime');
if (datetimeElement) {
datetimeElement.innerText = `${date} ${time}`;
}
}
updateClock();
setInterval(updateClock, 1000);
</script>
"""
# --- Streamlit App ---
st.set_page_config(layout="wide", page_title="Smart Context Citation Tool")
# Load custom CSS
load_css()
# Main header
st.markdown("""
<div class="main-header">
<h1>Smart Context Citation (SCC) Tool</h1>
<p>Next-generation digital referencing system for the age of Generative AI</p>
</div>
""", unsafe_allow_html=True)
# Expandable section for About and Example
with st.expander("About SCC and Example Citation"):
st.markdown("""
<div class="info-card">
<h3>About SCC</h3>
The Smart Context Citation (SCC) style is a next-generation digital referencing system designed for the age of Generative AI. It embeds citation context directly in the document, uses cryptographic hash signatures for integrity, and eliminates traditional reference lists.
<strong>Purpose:</strong> Transparency, integrity, and digital fluency in citations.
<strong>Structure:</strong>
- Inline general author name and date style citation
- Hyperlinked URL with text fragment (#:~:text=)
- SHA-256 hash for verification
<strong>Benefits:</strong> Enhances fairness, integrates with source contexts, promotes digital fluency, prevents fabrication, and eliminates traditional reference lists.
<strong>Technical Legitimacy:</strong> Referencing the <a href="https://wicg.github.io/scroll-to-text-fragment/" target="_blank">Text Fragments WICG specification</a> for technical legitimacy.
</div>
<div class="info-card">
<h3>Example Citation</h3>
<strong>Input:</strong><br>
- Author: <code>Abuseif et al.</code><br>
- Year: <code>2025</code><br>
- URL: <code>https://www.sciencedirect.com/science/article/pii/S2772411523000046</code><br>
- Text: <code>A proposed design framework for green roof settings in general and trees on buildings</code><br>
<strong>Output (HTML):</strong><br>
<div class="hash-display">
&lt;a href="https://www.sciencedirect.com/science/article/pii/S2772411523000046#:~:text=A%20proposed%20design%20framework%20for%20green%20roof%20settings%20in%20general%20and%20trees%20on%20buildings" data-hash="[GENERATED_HASH]"&gt;Abuseif et al., 2025&lt;/a&gt;
</div>
</div>
""", unsafe_allow_html=True)
tabs = st.tabs(["Citation Generator", "Verify Citation"])
with tabs[0]:
st.markdown('<div class="tab-content">', unsafe_allow_html=True)
st.header("Generate New Citation")
# User Information Section
st.subheader("User Information")
col1, col2 = st.columns(2)
with col1:
username = st.text_input("Username", help="Your username for tracking purposes", placeholder="e.g., john_doe")
with col2:
task_name = st.text_input("Task Name", help="The name of the task or project", placeholder="e.g., Literature Review Assignment")
# Citation Info Section
st.subheader("Citation Info")
col3, col4 = st.columns(2)
with col3:
author_name = st.text_input("Author(s) Name", help="The author(s) of the source", placeholder="e.g., Smith or Smith et al.")
with col4:
publication_year = st.text_input("Publication Year", help="The year of publication", placeholder="e.g., 2023")
col5, col6 = st.columns(2)
with col5:
source_url = st.text_input("Source URL", help="The full URL of the source", placeholder="https://example.com/article")
with col6:
annotated_text = st.text_input("Annotated Text", help="The text quoted or paraphrased from the source", placeholder="e.g., Thermal comfort thresholds...")
# Live date and time display
st.markdown("### Current Date and Time")
components.html(live_clock(), height=50)
# Get current date and time in Melbourne timezone for hash generation
melbourne_tz = pytz.timezone(MELBOURNE_TIMEZONE)
current_datetime_melbourne = datetime.now(melbourne_tz)
current_date = current_datetime_melbourne.strftime("%Y-%m-%d")
current_time = current_datetime_melbourne.strftime("%H:%M:%S")
generate_button = st.button("Generate Citation", type="primary", use_container_width=True)
if generate_button:
if not all([username, task_name, author_name, publication_year, source_url, annotated_text]):
st.error("Please fill in all fields before generating a citation.")
elif check_for_fragment(source_url):
st.markdown("""
<div class="warning-box">
<strong>Warning:</strong> It seems like your URL already contains a text fragment (<code>#:~:text=</code>).
This suggests you may have used AI assistance in generating this link. Please go back to the original source,
read the context carefully, and copy the source link again without any existing fragment.
</div>
""", unsafe_allow_html=True)
else:
scc_hash = generate_citation_hash(author_name, publication_year, source_url, annotated_text, annotated_text, username, task_name, current_date, current_time)
st.markdown("## Generated Citations")
col_html1, col_html2 = st.columns(2)
# HTML Citation - Start of Text
with col_html1:
st.markdown("### HTML Citation (Start of Text)")
html_citation_start = f'"{annotated_text}" (<a href="{source_url}#:~:text={urllib.parse.quote(annotated_text)}" data-hash="{scc_hash}">{author_name}, {publication_year}</a>)'
st.markdown('<div class="citation-output">', unsafe_allow_html=True)
st.code(html_citation_start, language='html')
st.markdown(copy_to_clipboard_js(html_citation_start.replace('`', '\\`'), "html_start"), unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# HTML Citation - End of Text
with col_html2:
st.markdown("### HTML Citation (End of Text)")
html_citation_end = f'(<a href="{source_url}#:~:text={urllib.parse.quote(annotated_text)}" data-hash="{scc_hash}">{author_name}, {publication_year}</a>) "{annotated_text}"'
st.markdown('<div class="citation-output">', unsafe_allow_html=True)
st.code(html_citation_end, language='html')
st.markdown(copy_to_clipboard_js(html_citation_end.replace('`', '\\`'), "html_end"), unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# Citation Hash Details
st.markdown("### Citation Hash Details (for Verification)")
hash_details = {
"author": author_name,
"year": publication_year,
"url": source_url,
"fragment_text": annotated_text,
"cited_text": annotated_text,
"username": username,
"task_name": task_name,
"date": current_date,
"time": current_time,
"hash": scc_hash
}
st.markdown('<div class="hash-display">', unsafe_allow_html=True)
st.json(hash_details)
st.markdown('</div>', unsafe_allow_html=True)
# Instructions section
st.markdown("## Instructions for Copying to Word")
st.markdown("""
<div class="info-card">
<strong>To use the generated HTML citation in Microsoft Word:</strong><br><br>
1. Copy the desired HTML citation (Start or End of Text) using the 'Copy to Clipboard' button<br>
2. In Word, go to the 'Insert' tab<br>
3. Click on 'Object' → 'Text from File...'<br>
4. Select 'HTML Document' from the file type dropdown<br>
5. Paste the copied HTML into a new text file (e.g., using Notepad) and save it with a <code>.html</code> extension<br>
6. Select this <code>.html</code> file in the 'Text from File...' dialog<br><br>
<strong>Alternative method:</strong> You might be able to paste directly into Word and then right-click and choose 'Keep Source Formatting' or 'Merge Formatting' if available, but the 'Text from File' method is more reliable for preserving hyperlinks and data attributes.
</div>
""", unsafe_allow_html=True)
st.markdown("## Guidance on Verifying Citations")
st.markdown("""
<div class="info-card">
To verify a citation, you can recompute the hash using the original input data and compare it to the embedded hash.
The <strong>'Verify Citation'</strong> tab allows you to do this easily.
</div>
""", unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
with tabs[1]:
st.markdown('<div class="tab-content">', unsafe_allow_html=True)
st.header("Verify Citation")
st.markdown("""
<div class="info-card">
Enter the citation details below to recompute and verify the hash. This ensures the citation hasn't been tampered with or fabricated.
</div>
""", unsafe_allow_html=True)
# Initialize session state for storing verified hashes
if 'verified_hashes' not in st.session_state:
st.session_state.verified_hashes = []
# User Information Section
st.subheader("User Information")
col1, col2 = st.columns(2)
with col1:
verify_username = st.text_input("Username (for verification)", placeholder="e.g., john_doe")
with col2:
verify_task_name = st.text_input("Task Name (for verification)", placeholder="e.g., Literature Review Assignment")
# Citation Info Section
st.subheader("Citation Info")
col3, col4 = st.columns(2)
with col3:
verify_author_name = st.text_input("Author(s) Name (for verification)", placeholder="e.g., Smith or Smith et al.")
with col4:
verify_publication_year = st.text_input("Publication Year (for verification)", placeholder="e.g., 2023")
col5, col6 = st.columns(2)
with col5:
verify_source_url = st.text_input("Source URL (for verification)", placeholder="https://example.com/article")
with col6:
verify_annotated_text = st.text_input("Annotated Text (for verification)", placeholder="e.g., Thermal comfort thresholds...")
col7, col8 = st.columns(2)
with col7:
verify_date = st.text_input("Date (YYYY-MM-DD) (for verification)", placeholder="e.g., 2025-01-08")
with col8:
verify_time = st.text_input("Time (HH:MM:SS) (for verification)", placeholder="e.g., 14:30:25")
expected_hash = st.text_input("Expected Hash (from the citation)", placeholder="Enter the full hash from the citation")
verify_button = st.button("Verify Hash", type="primary", use_container_width=True)
if verify_button:
if not all([verify_username, verify_task_name, verify_author_name, verify_publication_year,
verify_source_url, verify_annotated_text, verify_date, verify_time, expected_hash]):
st.error("Please fill in all fields before verifying the hash.")
else:
recomputed_hash = generate_citation_hash(
verify_author_name, verify_publication_year, verify_source_url,
verify_annotated_text, verify_annotated_text, verify_username, verify_task_name,
verify_date, verify_time
)
if recomputed_hash == expected_hash:
st.markdown("""
<div class="success-box">
<strong>Hash verified successfully!</strong> The citation is authentic and hasn't been tampered with.
</div>
""", unsafe_allow_html=True)
st.session_state.verified_hashes.append({
"Author": verify_author_name,
"Year": verify_publication_year,
"URL": verify_source_url,
"Fragment text": verify_annotated_text,
"Outlined text": verify_annotated_text,
"Username": verify_username,
"Task name": verify_task_name,
"Date": verify_date,
"Time": verify_time,
"Original Hash": expected_hash,
"Recomputed Hash": recomputed_hash,
"Status": "Verified"
})
else:
st.markdown("""
<div class="warning-box">
<strong>Hash verification failed!</strong> The citation may have been altered or is not authentic.
</div>
""", unsafe_allow_html=True)
st.session_state.verified_hashes.append({
"Author": verify_author_name,
"Year": verify_publication_year,
"URL": verify_source_url,
"Fragment text": verify_annotated_text,
"Cited text": verify_annotated_text,
"Username": verify_username,
"Task name": verify_task_name,
"Date": verify_date,
"Time": verify_time,
"Original Hash": expected_hash,
"Recomputed Hash": recomputed_hash,
"Status": "Failed"
})
if st.session_state.verified_hashes:
st.markdown("## Verification History")
df = pd.DataFrame(st.session_state.verified_hashes)
st.markdown('<div class="verification-table">', unsafe_allow_html=True)
st.dataframe(df, use_container_width=True)
st.markdown('</div>', unsafe_allow_html=True)
# Download as CSV
@st.cache_data
def convert_df_to_csv(df):
return df.to_csv(index=False).encode('utf-8')
csv = convert_df_to_csv(df)
st.download_button(
label="Download Verification History as CSV",
data=csv,
file_name="scc_verification_history.csv",
mime="text/csv",
use_container_width=True
)
# Clear history button
if st.button("Clear Verification History", type="secondary"):
st.session_state.verified_hashes = []
st.experimental_rerun()
st.markdown('</div>', unsafe_allow_html=True)
# Footer
st.markdown("""
<div class="footer">
Developed by: Dr Majed Abuseif<br>
School of Architecture and Built Environment<br>
Deakin University<br>
© 2025
</div>
""", unsafe_allow_html=True)