Spaces:
Running
Running
| 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"> | |
| <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]">Abuseif et al., 2025</a> | |
| </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 | |
| 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) |