Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,6 +5,7 @@ import urllib.parse
|
|
| 5 |
from datetime import datetime
|
| 6 |
import pytz
|
| 7 |
import pandas as pd
|
|
|
|
| 8 |
|
| 9 |
# --- Constants ---
|
| 10 |
MELBOURNE_TIMEZONE = 'Australia/Melbourne'
|
|
@@ -115,6 +116,26 @@ def format_metadata_html(url, author, year, scc_hash, username, task_name, curre
|
|
| 115 |
def check_for_fragment(url):
|
| 116 |
return '#:~:text=' in url
|
| 117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
# --- Live Clock JavaScript ---
|
| 119 |
def live_clock():
|
| 120 |
return """
|
|
@@ -279,15 +300,6 @@ with tabs[0]:
|
|
| 279 |
st.markdown('<div class="hash-display">', unsafe_allow_html=True)
|
| 280 |
st.markdown(metadata_link, unsafe_allow_html=True)
|
| 281 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 282 |
-
|
| 283 |
-
# Guidance on Verifying Citations
|
| 284 |
-
st.markdown("## Guidance on Verifying Citations")
|
| 285 |
-
st.markdown("""
|
| 286 |
-
<div class="info-card">
|
| 287 |
-
To verify a citation, you can recompute the hash using the original input data and compare it to the embedded hash.
|
| 288 |
-
The <strong>'Verify Citation'</strong> tab allows you to do this easily.
|
| 289 |
-
</div>
|
| 290 |
-
""", unsafe_allow_html=True)
|
| 291 |
|
| 292 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 293 |
|
|
@@ -296,128 +308,46 @@ with tabs[1]:
|
|
| 296 |
st.header("Verify Citation")
|
| 297 |
st.markdown("""
|
| 298 |
<div class="info-card">
|
| 299 |
-
|
| 300 |
</div>
|
| 301 |
""", unsafe_allow_html=True)
|
| 302 |
|
| 303 |
-
#
|
| 304 |
-
|
| 305 |
-
|
| 306 |
|
| 307 |
-
|
| 308 |
-
st.subheader("User Information")
|
| 309 |
-
col1, col2 = st.columns(2)
|
| 310 |
-
with col1:
|
| 311 |
-
verify_username = st.text_input("Username (for verification)", placeholder="e.g., john_doe")
|
| 312 |
-
with col2:
|
| 313 |
-
verify_task_name = st.text_input("Task Name (for verification)", placeholder="e.g., Literature Review Assignment")
|
| 314 |
-
|
| 315 |
-
# Citation Info Section
|
| 316 |
-
st.subheader("Citation Info")
|
| 317 |
-
col3, col4 = st.columns(2)
|
| 318 |
-
with col3:
|
| 319 |
-
verify_author_name = st.text_input("Author(s) Name (for verification)", placeholder="e.g., Smith or Smith et al.")
|
| 320 |
-
with col4:
|
| 321 |
-
verify_publication_year = st.text_input("Publication Year (for verification)", placeholder="e.g., 2023")
|
| 322 |
-
|
| 323 |
-
col5, col6 = st.columns(2)
|
| 324 |
-
with col5:
|
| 325 |
-
verify_source_url = st.text_input("Source URL (for verification)", placeholder="https://example.com/article")
|
| 326 |
-
with col6:
|
| 327 |
-
verify_annotated_text = st.text_input("Annotated Text (for verification)", placeholder="e.g., Thermal comfort thresholds...")
|
| 328 |
-
|
| 329 |
-
col7, col8 = st.columns(2)
|
| 330 |
-
with col7:
|
| 331 |
-
verify_date = st.text_input("Date (YYYY-MM-DD) (for verification)", placeholder="e.g., 2025-01-08")
|
| 332 |
-
with col8:
|
| 333 |
-
verify_time = st.text_input("Time (HH:MM:SS) (for verification)", placeholder="e.g., 14:30:25")
|
| 334 |
-
|
| 335 |
-
expected_hash = st.text_input("Expected Hash (from the citation)", placeholder="Enter the full hash from the citation")
|
| 336 |
-
|
| 337 |
-
verify_button = st.button("Verify Hash", type="primary", use_container_width=True)
|
| 338 |
|
| 339 |
if verify_button:
|
| 340 |
-
if not
|
| 341 |
-
|
| 342 |
-
st.error("Please fill in all fields before verifying the hash.")
|
| 343 |
else:
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
st.markdown("""
|
| 352 |
-
<div class="success-box">
|
| 353 |
-
<strong>Hash verified successfully!</strong> The citation is authentic and hasn't been tampered with.
|
| 354 |
-
</div>
|
| 355 |
-
""", unsafe_allow_html=True)
|
| 356 |
-
|
| 357 |
-
st.session_state.verified_hashes.append({
|
| 358 |
-
"Author": verify_author_name,
|
| 359 |
-
"Year": verify_publication_year,
|
| 360 |
-
"URL": verify_source_url,
|
| 361 |
-
"Fragment text": verify_annotated_text,
|
| 362 |
-
"Cited text": verify_annotated_text,
|
| 363 |
-
"Username": verify_username,
|
| 364 |
-
"Task name": verify_task_name,
|
| 365 |
-
"Date": verify_date,
|
| 366 |
-
"Time": verify_time,
|
| 367 |
-
"Original Hash": expected_hash,
|
| 368 |
-
"Recomputed Hash": recomputed_hash,
|
| 369 |
-
"Status": "Verified"
|
| 370 |
-
})
|
| 371 |
else:
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
"
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
"
|
| 383 |
-
|
| 384 |
-
"
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
"
|
| 389 |
-
|
| 390 |
-
"Status": "Failed"
|
| 391 |
-
})
|
| 392 |
-
|
| 393 |
-
if st.session_state.verified_hashes:
|
| 394 |
-
st.markdown("## Verification History")
|
| 395 |
-
df = pd.DataFrame(st.session_state.verified_hashes)
|
| 396 |
-
|
| 397 |
-
st.markdown('<div class="verification-table">', unsafe_allow_html=True)
|
| 398 |
-
st.dataframe(df, use_container_width=True)
|
| 399 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
| 400 |
-
|
| 401 |
-
# Download as CSV
|
| 402 |
-
@st.cache_data
|
| 403 |
-
def convert_df_to_csv(df):
|
| 404 |
-
return df.to_csv(index=False).encode('utf-8')
|
| 405 |
-
|
| 406 |
-
csv = convert_df_to_csv(df)
|
| 407 |
-
|
| 408 |
-
st.download_button(
|
| 409 |
-
label="Download Verification History as CSV",
|
| 410 |
-
data=csv,
|
| 411 |
-
file_name="scc_verification_history.csv",
|
| 412 |
-
mime="text/csv",
|
| 413 |
-
use_container_width=True
|
| 414 |
-
)
|
| 415 |
-
|
| 416 |
-
# Clear history button
|
| 417 |
-
if st.button("Clear Verification History", type="secondary"):
|
| 418 |
-
st.session_state.verified_hashes = []
|
| 419 |
-
st.experimental_rerun()
|
| 420 |
-
|
| 421 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 422 |
|
| 423 |
# Footer
|
|
|
|
| 5 |
from datetime import datetime
|
| 6 |
import pytz
|
| 7 |
import pandas as pd
|
| 8 |
+
import re
|
| 9 |
|
| 10 |
# --- Constants ---
|
| 11 |
MELBOURNE_TIMEZONE = 'Australia/Melbourne'
|
|
|
|
| 116 |
def check_for_fragment(url):
|
| 117 |
return '#:~:text=' in url
|
| 118 |
|
| 119 |
+
def parse_citation(citation_html):
|
| 120 |
+
# Parse citation HTML to extract author, year, URL, and fragment text
|
| 121 |
+
match = re.match(r'<a href="([^"]+)#:~:text=([^"]+)" data-hash="[^"]+">([^<]+) \((\d{4})\)</a>', citation_html)
|
| 122 |
+
if match:
|
| 123 |
+
url, encoded_fragment, author, year = match.groups()
|
| 124 |
+
fragment_text = urllib.parse.unquote(encoded_fragment)
|
| 125 |
+
return author, year, url, fragment_text
|
| 126 |
+
return None, None, None, None
|
| 127 |
+
|
| 128 |
+
def parse_metadata_hash(metadata_html):
|
| 129 |
+
# Parse metadata HTML to extract hash, username, task_name, date, time
|
| 130 |
+
match = re.match(r'<a href="([^"]+)#:~:text=([^"]+)" data-hash="([^"]+)">[^<]+\(\d{4}\)\. ([^<]+)</a>', metadata_html)
|
| 131 |
+
if match:
|
| 132 |
+
url, encoded_metadata, scc_hash, same_hash = match.groups()
|
| 133 |
+
metadata_parts = urllib.parse.unquote(encoded_metadata).split('—')
|
| 134 |
+
if len(metadata_parts) == 4:
|
| 135 |
+
username, task_name, date, time = metadata_parts
|
| 136 |
+
return scc_hash, username, task_name, date, time
|
| 137 |
+
return None, None, None, None, None
|
| 138 |
+
|
| 139 |
# --- Live Clock JavaScript ---
|
| 140 |
def live_clock():
|
| 141 |
return """
|
|
|
|
| 300 |
st.markdown('<div class="hash-display">', unsafe_allow_html=True)
|
| 301 |
st.markdown(metadata_link, unsafe_allow_html=True)
|
| 302 |
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
|
| 304 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 305 |
|
|
|
|
| 308 |
st.header("Verify Citation")
|
| 309 |
st.markdown("""
|
| 310 |
<div class="info-card">
|
| 311 |
+
Paste the generated citation and hash with their embedded links below to verify the citation's authenticity.
|
| 312 |
</div>
|
| 313 |
""", unsafe_allow_html=True)
|
| 314 |
|
| 315 |
+
# Input fields for citation and hash
|
| 316 |
+
citation_input = st.text_area("Paste Citation (with embedded link)", help="Paste the citation HTML, e.g., <a href='URL'>Author (Year)</a>", placeholder='<a href="https://example.com#:~:text=fragment" data-hash="hash">Author (Year)</a>')
|
| 317 |
+
hash_input = st.text_area("Paste Hash (with embedded link)", help="Paste the hash HTML, e.g., <a href='URL'>Author (Year). Hash</a>", placeholder='<a href="https://example.com#:~:text=metadata" data-hash="hash">Author (Year). Hash</a>')
|
| 318 |
|
| 319 |
+
verify_button = st.button("Verify Citation", type="primary", use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
|
| 321 |
if verify_button:
|
| 322 |
+
if not (citation_input and hash_input):
|
| 323 |
+
st.error("Please paste both the citation and hash before verifying.")
|
|
|
|
| 324 |
else:
|
| 325 |
+
# Parse citation
|
| 326 |
+
author, year, url, fragment_text = parse_citation(citation_input)
|
| 327 |
+
# Parse hash and metadata
|
| 328 |
+
scc_hash, username, task_name, date, time = parse_metadata_hash(hash_input)
|
| 329 |
+
|
| 330 |
+
if not all([author, year, url, fragment_text, scc_hash, username, task_name, date, time]):
|
| 331 |
+
st.error("Invalid citation or hash format. Please ensure both inputs are correctly formatted HTML with embedded links.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
else:
|
| 333 |
+
# Recompute hash
|
| 334 |
+
recomputed_hash = generate_citation_hash(
|
| 335 |
+
author, year, url, fragment_text, fragment_text, username, task_name, date, time
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
if recomputed_hash == scc_hash:
|
| 339 |
+
st.markdown("""
|
| 340 |
+
<div class="success-box">
|
| 341 |
+
<strong>Hash verified successfully!</strong> The citation is authentic and hasn't been tampered with.
|
| 342 |
+
</div>
|
| 343 |
+
""", unsafe_allow_html=True)
|
| 344 |
+
else:
|
| 345 |
+
st.markdown("""
|
| 346 |
+
<div class="warning-box">
|
| 347 |
+
<strong>Hash verification failed!</strong> The citation may have been altered or is not authentic.
|
| 348 |
+
</div>
|
| 349 |
+
""", unsafe_allow_html=True)
|
| 350 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 352 |
|
| 353 |
# Footer
|