Spaces:
Sleeping
Sleeping
Commit Β·
2c0b974
1
Parent(s): a9e370b
Update 2026-01-30 19:35:53
Browse files- app.py +125 -183
- templates/index.html +278 -96
app.py
CHANGED
|
@@ -3,10 +3,11 @@ Flask Application for Reference Management Pipeline
|
|
| 3 |
Complete implementation matching overleaf.py functionality
|
| 4 |
Includes LaTeX citation parsing, BibTeX processing, and full pipeline
|
| 5 |
|
| 6 |
-
|
| 7 |
- Fixed abbreviations to include periods (ISO 4 standard): "Energy" β "Ener."
|
| 8 |
-
-
|
| 9 |
-
-
|
|
|
|
| 10 |
"""
|
| 11 |
|
| 12 |
from flask import Flask, render_template, request, jsonify, send_file
|
|
@@ -78,7 +79,7 @@ def check_api_key():
|
|
| 78 |
# =====================================================================
|
| 79 |
|
| 80 |
def parse_citations_from_tex(tex_content: str) -> pd.DataFrame:
|
| 81 |
-
"""Parse citations from LaTeX content"""
|
| 82 |
print("π Parsing citations from LaTeX")
|
| 83 |
|
| 84 |
lines = tex_content.split('\n')
|
|
@@ -559,20 +560,30 @@ def index():
|
|
| 559 |
|
| 560 |
@app.route('/api/process', methods=['POST'])
|
| 561 |
def process_bibtex():
|
| 562 |
-
"""Process BibTeX content
|
| 563 |
if not check_api_key():
|
| 564 |
return jsonify({'error': 'Unauthorized'}), 401
|
| 565 |
|
| 566 |
try:
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
|
| 577 |
print(f"π₯ Received: {len(bibtex_content)} chars, mode={input_mode}")
|
| 578 |
|
|
@@ -589,14 +600,12 @@ def process_bibtex():
|
|
| 589 |
for i, title in enumerate(titles, 1):
|
| 590 |
print(f" [{i}/{len(titles)}] Searching: {title[:50]}...")
|
| 591 |
try:
|
| 592 |
-
# Search Crossref
|
| 593 |
url = f"https://api.crossref.org/works?query.bibliographic={requests.utils.quote(title)}&rows=1"
|
| 594 |
response = HTTP.get(url, timeout=15)
|
| 595 |
items = response.json().get("message", {}).get("items", [])
|
| 596 |
|
| 597 |
if items and items[0].get('DOI'):
|
| 598 |
doi = items[0]['DOI']
|
| 599 |
-
# Fetch BibTeX
|
| 600 |
bibtex_r = HTTP.get(
|
| 601 |
f"https://doi.org/{doi}",
|
| 602 |
headers={"Accept": "application/x-bibtex"},
|
|
@@ -610,22 +619,39 @@ def process_bibtex():
|
|
| 610 |
except Exception as e:
|
| 611 |
print(f" β οΈ Failed: {e}")
|
| 612 |
|
| 613 |
-
# Join all found entries
|
| 614 |
bibtex_content = '\n\n'.join(bibtex_entries)
|
| 615 |
print(f"β
Retrieved {len(bibtex_entries)} BibTeX entries from titles")
|
| 616 |
|
| 617 |
if not bibtex_content:
|
| 618 |
return jsonify({'error': 'No BibTeX entries found for the provided titles'}), 400
|
| 619 |
|
| 620 |
-
# Normal BibTeX mode validation
|
| 621 |
if not bibtex_content or len(bibtex_content.strip()) == 0:
|
| 622 |
return jsonify({'error': 'No BibTeX content provided'}), 400
|
| 623 |
|
|
|
|
| 624 |
df = parse_bibtex_input(bibtex_content)
|
| 625 |
|
| 626 |
if df.empty:
|
| 627 |
return jsonify({'error': 'No valid BibTeX entries found'}), 400
|
| 628 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 629 |
# Enrich if requested
|
| 630 |
if enrich:
|
| 631 |
df = enrich_with_crossref(df)
|
|
@@ -646,28 +672,33 @@ def process_bibtex():
|
|
| 646 |
for _, row in df.iterrows():
|
| 647 |
doi = row.get('DOI', '').strip()
|
| 648 |
year_int = extract_year_int(row.get('Year', ''))
|
|
|
|
| 649 |
|
| 650 |
try:
|
| 651 |
cursor.execute('''
|
| 652 |
INSERT OR REPLACE INTO bibliography
|
| 653 |
-
(
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
|
|
|
| 658 |
''', (
|
| 659 |
-
|
| 660 |
-
row
|
| 661 |
-
|
| 662 |
-
row
|
| 663 |
-
row.get('
|
|
|
|
|
|
|
|
|
|
| 664 |
row.get('Title_Similarity', 0), row.get('Journal_Abbreviation', ''),
|
| 665 |
-
row.get('Crossref_BibTeX_Abbrev',
|
| 666 |
-
row.get('Crossref_BibTeX_Protected',
|
| 667 |
-
|
| 668 |
))
|
| 669 |
except sqlite3.IntegrityError as e:
|
| 670 |
-
print(f"β οΈ DB insert failed for {
|
| 671 |
|
| 672 |
conn.commit()
|
| 673 |
db_id = session_id
|
|
@@ -681,19 +712,21 @@ def process_bibtex():
|
|
| 681 |
else:
|
| 682 |
final_bibtex_col = 'Crossref_BibTeX_LocalKey'
|
| 683 |
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
|
|
|
| 688 |
|
| 689 |
-
response_df
|
| 690 |
-
|
| 691 |
-
]
|
| 692 |
|
| 693 |
return jsonify({
|
| 694 |
'success': True,
|
| 695 |
'count': len(df),
|
| 696 |
'db_id': db_id,
|
|
|
|
|
|
|
| 697 |
'data': response_df.to_dict(orient='records'),
|
| 698 |
'full_data': df.to_dict(orient='records')
|
| 699 |
})
|
|
@@ -703,170 +736,81 @@ def process_bibtex():
|
|
| 703 |
traceback.print_exc()
|
| 704 |
return jsonify({'error': str(e)}), 500
|
| 705 |
|
| 706 |
-
@app.route('/api/
|
| 707 |
-
def
|
| 708 |
-
"""
|
| 709 |
-
if not check_api_key():
|
| 710 |
-
return jsonify({'error': 'Unauthorized'}), 401
|
| 711 |
-
|
| 712 |
try:
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
return jsonify({'error': 'Both tex_file and bib_file are required'}), 400
|
| 716 |
-
|
| 717 |
-
tex_file = request.files['tex_file']
|
| 718 |
-
bib_file = request.files['bib_file']
|
| 719 |
-
|
| 720 |
-
if tex_file.filename == '' or bib_file.filename == '':
|
| 721 |
-
return jsonify({'error': 'Both files must be provided'}), 400
|
| 722 |
-
|
| 723 |
-
# Read file contents
|
| 724 |
-
tex_content = tex_file.read().decode('utf-8')
|
| 725 |
-
bib_content = bib_file.read().decode('utf-8')
|
| 726 |
-
|
| 727 |
-
# Get options
|
| 728 |
-
enrich = request.form.get('enrich', 'false').lower() == 'true'
|
| 729 |
-
save_to_db = request.form.get('save_to_db', 'false').lower() == 'true'
|
| 730 |
-
|
| 731 |
-
# Parse LaTeX citations
|
| 732 |
-
citations_df = parse_citations_from_tex(tex_content)
|
| 733 |
-
|
| 734 |
-
# Parse BibTeX
|
| 735 |
-
bib_df = parse_bibtex_input(bib_content)
|
| 736 |
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
|
| 741 |
-
#
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
|
| 748 |
-
|
| 749 |
-
merged_df = add_journal_abbreviations(merged_df)
|
| 750 |
-
|
| 751 |
-
# Save to database if requested
|
| 752 |
-
db_id = None
|
| 753 |
-
if save_to_db:
|
| 754 |
-
conn = get_db_connection()
|
| 755 |
-
cursor = conn.cursor()
|
| 756 |
-
session_id = datetime.now().isoformat()
|
| 757 |
-
|
| 758 |
-
for _, row in merged_df.iterrows():
|
| 759 |
-
doi = row.get('DOI', '').strip()
|
| 760 |
-
year_int = extract_year_int(row.get('Year', ''))
|
| 761 |
-
key_val = row.get('Reference') or row.get('Key', f"ref_{row.get('Index', 0)}")
|
| 762 |
-
|
| 763 |
-
try:
|
| 764 |
-
cursor.execute('''
|
| 765 |
-
INSERT OR REPLACE INTO bibliography
|
| 766 |
-
(index_num, session_id, reference, frequency, sections, key, doi, type,
|
| 767 |
-
authors, title, journal_booktitle, year, year_int, publisher, volume, pages,
|
| 768 |
-
bibtex, crossref_bibtex, crossref_bibtex_localkey, title_similarity,
|
| 769 |
-
journal_abbreviation, crossref_bibtex_abbrev, crossref_bibtex_protected,
|
| 770 |
-
imported_date)
|
| 771 |
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 772 |
-
''', (
|
| 773 |
-
row.get('Index'), session_id, row.get('Reference', ''),
|
| 774 |
-
row.get('Frequency', 0), row.get('Sections', ''),
|
| 775 |
-
key_val, doi, row.get('Type', ''), row.get('Authors', ''),
|
| 776 |
-
row.get('Title', ''), row.get('Journal/Booktitle', ''),
|
| 777 |
-
row.get('Year', ''), year_int, row.get('Publisher', ''),
|
| 778 |
-
row.get('Volume', ''), row.get('Pages', ''),
|
| 779 |
-
row.get('BibTeX', ''), row.get('Crossref_BibTeX', ''),
|
| 780 |
-
row.get('Crossref_BibTeX_LocalKey', ''),
|
| 781 |
-
row.get('Title_Similarity', 0), row.get('Journal_Abbreviation', ''),
|
| 782 |
-
row.get('Crossref_BibTeX_Abbrev', ''),
|
| 783 |
-
row.get('Crossref_BibTeX_Protected', ''),
|
| 784 |
-
datetime.now().isoformat()
|
| 785 |
-
))
|
| 786 |
-
except sqlite3.IntegrityError as e:
|
| 787 |
-
print(f"β οΈ DB insert failed: {e}")
|
| 788 |
-
|
| 789 |
-
conn.commit()
|
| 790 |
-
db_id = session_id
|
| 791 |
-
conn.close()
|
| 792 |
|
| 793 |
return jsonify({
|
| 794 |
'success': True,
|
| 795 |
-
'
|
| 796 |
-
'db_id': db_id,
|
| 797 |
-
'citations_found': len(citations_df),
|
| 798 |
-
'data': merged_df.to_dict(orient='records')
|
| 799 |
})
|
| 800 |
-
|
| 801 |
except Exception as e:
|
| 802 |
-
import traceback
|
| 803 |
-
traceback.print_exc()
|
| 804 |
return jsonify({'error': str(e)}), 500
|
| 805 |
|
| 806 |
-
@app.route('/api/
|
| 807 |
-
def
|
| 808 |
-
"""
|
| 809 |
-
|
| 810 |
-
|
|
|
|
|
|
|
| 811 |
|
| 812 |
try:
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
bib_file = request.files['bib_file']
|
| 817 |
-
|
| 818 |
-
if bib_file.filename == '':
|
| 819 |
-
return jsonify({'error': 'File must be provided'}), 400
|
| 820 |
-
|
| 821 |
-
bib_content = bib_file.read().decode('utf-8')
|
| 822 |
-
save_to_db = request.form.get('save_to_db', 'false').lower() == 'true'
|
| 823 |
|
| 824 |
-
#
|
| 825 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 826 |
|
| 827 |
-
|
| 828 |
-
|
| 829 |
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
year_int = extract_year_int(row.get('Year', ''))
|
| 840 |
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
''
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
row['Publisher'], row.get('Volume', ''), row.get('Pages', ''),
|
| 851 |
-
row['BibTeX'], row.get('Used'), datetime.now().isoformat()
|
| 852 |
-
))
|
| 853 |
-
except sqlite3.IntegrityError as e:
|
| 854 |
-
print(f"β οΈ DB insert failed: {e}")
|
| 855 |
-
|
| 856 |
-
conn.commit()
|
| 857 |
-
db_id = session_id
|
| 858 |
-
conn.close()
|
| 859 |
|
| 860 |
return jsonify({
|
| 861 |
'success': True,
|
| 862 |
-
'
|
| 863 |
-
'
|
| 864 |
-
'data': df.to_dict(orient='records')
|
| 865 |
})
|
| 866 |
-
|
| 867 |
except Exception as e:
|
| 868 |
-
import traceback
|
| 869 |
-
traceback.print_exc()
|
| 870 |
return jsonify({'error': str(e)}), 500
|
| 871 |
|
| 872 |
@app.route('/api/database/entries', methods=['GET'])
|
|
@@ -920,11 +864,9 @@ def clear_database():
|
|
| 920 |
conn = get_db_connection()
|
| 921 |
cursor = conn.cursor()
|
| 922 |
|
| 923 |
-
# Delete all entries
|
| 924 |
cursor.execute('DELETE FROM bibliography')
|
| 925 |
deleted_count = cursor.rowcount
|
| 926 |
|
| 927 |
-
# Reset the auto-increment counter
|
| 928 |
cursor.execute('DELETE FROM sqlite_sequence WHERE name="bibliography"')
|
| 929 |
|
| 930 |
conn.commit()
|
|
|
|
| 3 |
Complete implementation matching overleaf.py functionality
|
| 4 |
Includes LaTeX citation parsing, BibTeX processing, and full pipeline
|
| 5 |
|
| 6 |
+
FEATURES:
|
| 7 |
- Fixed abbreviations to include periods (ISO 4 standard): "Energy" β "Ener."
|
| 8 |
+
- LaTeX manuscript analysis for citation frequency and section tracking
|
| 9 |
+
- Filter references by section
|
| 10 |
+
- Clear entire database
|
| 11 |
"""
|
| 12 |
|
| 13 |
from flask import Flask, render_template, request, jsonify, send_file
|
|
|
|
| 79 |
# =====================================================================
|
| 80 |
|
| 81 |
def parse_citations_from_tex(tex_content: str) -> pd.DataFrame:
|
| 82 |
+
"""Parse citations from LaTeX content with section tracking"""
|
| 83 |
print("π Parsing citations from LaTeX")
|
| 84 |
|
| 85 |
lines = tex_content.split('\n')
|
|
|
|
| 560 |
|
| 561 |
@app.route('/api/process', methods=['POST'])
|
| 562 |
def process_bibtex():
|
| 563 |
+
"""Process BibTeX content with optional LaTeX analysis"""
|
| 564 |
if not check_api_key():
|
| 565 |
return jsonify({'error': 'Unauthorized'}), 401
|
| 566 |
|
| 567 |
try:
|
| 568 |
+
# Handle both JSON and FormData
|
| 569 |
+
if request.is_json:
|
| 570 |
+
data = request.get_json()
|
| 571 |
+
bibtex_content = data.get('bibtex_content') or data.get('bibtex', '')
|
| 572 |
+
input_mode = data.get('input_mode', 'bibtex')
|
| 573 |
+
enrich = data.get('enrich', False)
|
| 574 |
+
abbreviate = data.get('abbreviate', False)
|
| 575 |
+
protect = data.get('protect', False)
|
| 576 |
+
save_to_db = data.get('save_to_db', False)
|
| 577 |
+
latex_file = None
|
| 578 |
+
else:
|
| 579 |
+
# FormData from file upload
|
| 580 |
+
bibtex_content = request.form.get('bibtex_content', '')
|
| 581 |
+
input_mode = request.form.get('input_mode', 'bibtex')
|
| 582 |
+
enrich = request.form.get('enrich', 'false').lower() == 'true'
|
| 583 |
+
abbreviate = request.form.get('abbreviate', 'false').lower() == 'true'
|
| 584 |
+
protect = request.form.get('protect', 'false').lower() == 'true'
|
| 585 |
+
save_to_db = request.form.get('save_to_db', 'false').lower() == 'true'
|
| 586 |
+
latex_file = request.files.get('latex_file')
|
| 587 |
|
| 588 |
print(f"π₯ Received: {len(bibtex_content)} chars, mode={input_mode}")
|
| 589 |
|
|
|
|
| 600 |
for i, title in enumerate(titles, 1):
|
| 601 |
print(f" [{i}/{len(titles)}] Searching: {title[:50]}...")
|
| 602 |
try:
|
|
|
|
| 603 |
url = f"https://api.crossref.org/works?query.bibliographic={requests.utils.quote(title)}&rows=1"
|
| 604 |
response = HTTP.get(url, timeout=15)
|
| 605 |
items = response.json().get("message", {}).get("items", [])
|
| 606 |
|
| 607 |
if items and items[0].get('DOI'):
|
| 608 |
doi = items[0]['DOI']
|
|
|
|
| 609 |
bibtex_r = HTTP.get(
|
| 610 |
f"https://doi.org/{doi}",
|
| 611 |
headers={"Accept": "application/x-bibtex"},
|
|
|
|
| 619 |
except Exception as e:
|
| 620 |
print(f" β οΈ Failed: {e}")
|
| 621 |
|
|
|
|
| 622 |
bibtex_content = '\n\n'.join(bibtex_entries)
|
| 623 |
print(f"β
Retrieved {len(bibtex_entries)} BibTeX entries from titles")
|
| 624 |
|
| 625 |
if not bibtex_content:
|
| 626 |
return jsonify({'error': 'No BibTeX entries found for the provided titles'}), 400
|
| 627 |
|
|
|
|
| 628 |
if not bibtex_content or len(bibtex_content.strip()) == 0:
|
| 629 |
return jsonify({'error': 'No BibTeX content provided'}), 400
|
| 630 |
|
| 631 |
+
# Parse BibTeX
|
| 632 |
df = parse_bibtex_input(bibtex_content)
|
| 633 |
|
| 634 |
if df.empty:
|
| 635 |
return jsonify({'error': 'No valid BibTeX entries found'}), 400
|
| 636 |
|
| 637 |
+
# Parse LaTeX file if provided
|
| 638 |
+
latex_analyzed = False
|
| 639 |
+
citations_found = 0
|
| 640 |
+
if latex_file:
|
| 641 |
+
print("π LaTeX file provided, analyzing...")
|
| 642 |
+
try:
|
| 643 |
+
latex_content = latex_file.read().decode('utf-8')
|
| 644 |
+
citations_df = parse_citations_from_tex(latex_content)
|
| 645 |
+
citations_found = len(citations_df)
|
| 646 |
+
|
| 647 |
+
# Merge LaTeX citation data with BibTeX data
|
| 648 |
+
df = merge_citations_with_bib(citations_df, df)
|
| 649 |
+
df.insert(0, "Index", range(1, len(df) + 1))
|
| 650 |
+
latex_analyzed = True
|
| 651 |
+
print(f"β
LaTeX analyzed: {citations_found} citations found")
|
| 652 |
+
except Exception as e:
|
| 653 |
+
print(f"β οΈ LaTeX analysis failed: {e}")
|
| 654 |
+
|
| 655 |
# Enrich if requested
|
| 656 |
if enrich:
|
| 657 |
df = enrich_with_crossref(df)
|
|
|
|
| 672 |
for _, row in df.iterrows():
|
| 673 |
doi = row.get('DOI', '').strip()
|
| 674 |
year_int = extract_year_int(row.get('Year', ''))
|
| 675 |
+
key_val = row.get('Reference') or row.get('Key', f"ref_{row.name}")
|
| 676 |
|
| 677 |
try:
|
| 678 |
cursor.execute('''
|
| 679 |
INSERT OR REPLACE INTO bibliography
|
| 680 |
+
(index_num, session_id, reference, frequency, sections, key, doi, type,
|
| 681 |
+
authors, title, journal_booktitle, year, year_int, publisher, volume, pages,
|
| 682 |
+
bibtex, crossref_bibtex, crossref_bibtex_localkey, title_similarity,
|
| 683 |
+
journal_abbreviation, crossref_bibtex_abbrev, crossref_bibtex_protected,
|
| 684 |
+
imported_date)
|
| 685 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 686 |
''', (
|
| 687 |
+
row.get('Index'), session_id, row.get('Reference', key_val),
|
| 688 |
+
row.get('Frequency', 0), row.get('Sections', ''),
|
| 689 |
+
key_val, doi, row.get('Type', ''), row.get('Authors', ''),
|
| 690 |
+
row.get('Title', ''), row.get('Journal/Booktitle', ''),
|
| 691 |
+
row.get('Year', ''), year_int, row.get('Publisher', ''),
|
| 692 |
+
row.get('Volume', ''), row.get('Pages', ''),
|
| 693 |
+
row.get('BibTeX', ''), row.get('Crossref_BibTeX', ''),
|
| 694 |
+
row.get('Crossref_BibTeX_LocalKey', ''),
|
| 695 |
row.get('Title_Similarity', 0), row.get('Journal_Abbreviation', ''),
|
| 696 |
+
row.get('Crossref_BibTeX_Abbrev', ''),
|
| 697 |
+
row.get('Crossref_BibTeX_Protected', ''),
|
| 698 |
+
datetime.now().isoformat()
|
| 699 |
))
|
| 700 |
except sqlite3.IntegrityError as e:
|
| 701 |
+
print(f"β οΈ DB insert failed for {key_val}: {e}")
|
| 702 |
|
| 703 |
conn.commit()
|
| 704 |
db_id = session_id
|
|
|
|
| 712 |
else:
|
| 713 |
final_bibtex_col = 'Crossref_BibTeX_LocalKey'
|
| 714 |
|
| 715 |
+
# Prepare response columns
|
| 716 |
+
response_cols = ['Key', 'Type', 'Authors', 'Title', 'Journal/Booktitle', 'Year', final_bibtex_col]
|
| 717 |
+
if latex_analyzed:
|
| 718 |
+
response_cols.insert(6, 'Frequency')
|
| 719 |
+
response_cols.insert(7, 'Sections')
|
| 720 |
|
| 721 |
+
response_df = df[[col for col in response_cols if col in df.columns]].copy()
|
| 722 |
+
response_df.columns = list(response_df.columns[:-1]) + ['Final_BibTeX']
|
|
|
|
| 723 |
|
| 724 |
return jsonify({
|
| 725 |
'success': True,
|
| 726 |
'count': len(df),
|
| 727 |
'db_id': db_id,
|
| 728 |
+
'latex_analyzed': latex_analyzed,
|
| 729 |
+
'citations_found': citations_found,
|
| 730 |
'data': response_df.to_dict(orient='records'),
|
| 731 |
'full_data': df.to_dict(orient='records')
|
| 732 |
})
|
|
|
|
| 736 |
traceback.print_exc()
|
| 737 |
return jsonify({'error': str(e)}), 500
|
| 738 |
|
| 739 |
+
@app.route('/api/sections/list', methods=['GET'])
|
| 740 |
+
def list_sections():
|
| 741 |
+
"""Get list of all unique sections from database"""
|
|
|
|
|
|
|
|
|
|
| 742 |
try:
|
| 743 |
+
conn = get_db_connection()
|
| 744 |
+
cursor = conn.cursor()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 745 |
|
| 746 |
+
cursor.execute('SELECT DISTINCT sections FROM bibliography WHERE sections IS NOT NULL AND sections != ""')
|
| 747 |
+
rows = cursor.fetchall()
|
| 748 |
+
conn.close()
|
| 749 |
|
| 750 |
+
# Parse and deduplicate sections
|
| 751 |
+
all_sections = set()
|
| 752 |
+
for row in rows:
|
| 753 |
+
if row[0]:
|
| 754 |
+
sections = [s.strip() for s in row[0].split(',')]
|
| 755 |
+
all_sections.update(sections)
|
| 756 |
|
| 757 |
+
sections_list = sorted(list(all_sections))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 758 |
|
| 759 |
return jsonify({
|
| 760 |
'success': True,
|
| 761 |
+
'sections': sections_list
|
|
|
|
|
|
|
|
|
|
| 762 |
})
|
|
|
|
| 763 |
except Exception as e:
|
|
|
|
|
|
|
| 764 |
return jsonify({'error': str(e)}), 500
|
| 765 |
|
| 766 |
+
@app.route('/api/sections/references', methods=['GET'])
|
| 767 |
+
def get_references_by_section():
|
| 768 |
+
"""Get all references for a specific section"""
|
| 769 |
+
section = request.args.get('section', '')
|
| 770 |
+
|
| 771 |
+
if not section:
|
| 772 |
+
return jsonify({'error': 'Section parameter required'}), 400
|
| 773 |
|
| 774 |
try:
|
| 775 |
+
conn = get_db_connection()
|
| 776 |
+
cursor = conn.cursor()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 777 |
|
| 778 |
+
# Find all references that contain this section
|
| 779 |
+
cursor.execute('''
|
| 780 |
+
SELECT key, title, authors, year, frequency, sections, reference
|
| 781 |
+
FROM bibliography
|
| 782 |
+
WHERE sections LIKE ?
|
| 783 |
+
''', (f'%{section}%',))
|
| 784 |
|
| 785 |
+
rows = cursor.fetchall()
|
| 786 |
+
conn.close()
|
| 787 |
|
| 788 |
+
references = []
|
| 789 |
+
for row in rows:
|
| 790 |
+
# Calculate frequency in this specific section
|
| 791 |
+
sections_list = [s.strip() for s in row[5].split(',') if s.strip()]
|
| 792 |
+
if section in sections_list:
|
| 793 |
+
# Count occurrences in this section (simplified - assumes equal distribution)
|
| 794 |
+
total_freq = row[4] or 0
|
| 795 |
+
num_sections = len(sections_list)
|
| 796 |
+
freq_in_section = total_freq // num_sections if num_sections > 0 else total_freq
|
|
|
|
| 797 |
|
| 798 |
+
references.append({
|
| 799 |
+
'key': row[0],
|
| 800 |
+
'title': row[1],
|
| 801 |
+
'authors': row[2],
|
| 802 |
+
'year': row[3],
|
| 803 |
+
'frequency_in_section': freq_in_section,
|
| 804 |
+
'total_frequency': total_freq,
|
| 805 |
+
'all_sections': row[5]
|
| 806 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 807 |
|
| 808 |
return jsonify({
|
| 809 |
'success': True,
|
| 810 |
+
'section': section,
|
| 811 |
+
'references': references
|
|
|
|
| 812 |
})
|
|
|
|
| 813 |
except Exception as e:
|
|
|
|
|
|
|
| 814 |
return jsonify({'error': str(e)}), 500
|
| 815 |
|
| 816 |
@app.route('/api/database/entries', methods=['GET'])
|
|
|
|
| 864 |
conn = get_db_connection()
|
| 865 |
cursor = conn.cursor()
|
| 866 |
|
|
|
|
| 867 |
cursor.execute('DELETE FROM bibliography')
|
| 868 |
deleted_count = cursor.rowcount
|
| 869 |
|
|
|
|
| 870 |
cursor.execute('DELETE FROM sqlite_sequence WHERE name="bibliography"')
|
| 871 |
|
| 872 |
conn.commit()
|
templates/index.html
CHANGED
|
@@ -251,6 +251,11 @@
|
|
| 251 |
color: #0c5460;
|
| 252 |
}
|
| 253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
.bibtex-preview {
|
| 255 |
background: #f5f5f5;
|
| 256 |
padding: 15px;
|
|
@@ -380,6 +385,56 @@
|
|
| 380 |
background: #c82333;
|
| 381 |
}
|
| 382 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
@media (max-width: 1024px) {
|
| 384 |
.main-grid {
|
| 385 |
grid-template-columns: 1fr;
|
|
@@ -404,6 +459,16 @@
|
|
| 404 |
.hidden {
|
| 405 |
display: none;
|
| 406 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
</style>
|
| 408 |
</head>
|
| 409 |
<body>
|
|
@@ -416,43 +481,14 @@
|
|
| 416 |
<!-- Main Tabs -->
|
| 417 |
<div class="card">
|
| 418 |
<div class="tabs">
|
| 419 |
-
<button class="tab-button active" onclick="switchMainTab('
|
| 420 |
-
<button class="tab-button" onclick="switchMainTab('
|
|
|
|
| 421 |
<button class="tab-button" onclick="switchMainTab('help')">βΉοΈ Help & Info</button>
|
| 422 |
</div>
|
| 423 |
|
| 424 |
-
<!--
|
| 425 |
-
<div id="
|
| 426 |
-
<h2>πΎ Database Management</h2>
|
| 427 |
-
|
| 428 |
-
<div class="stats-grid" id="statsContainer" style="margin-top: 20px;">
|
| 429 |
-
<div class="stat-box">
|
| 430 |
-
<h3 id="statTotal">0</h3>
|
| 431 |
-
<p>Total Entries</p>
|
| 432 |
-
</div>
|
| 433 |
-
<div class="stat-box">
|
| 434 |
-
<h3 id="statTypes">0</h3>
|
| 435 |
-
<p>Entry Types</p>
|
| 436 |
-
</div>
|
| 437 |
-
<div class="stat-box">
|
| 438 |
-
<h3 id="statYears">0</h3>
|
| 439 |
-
<p>Unique Years</p>
|
| 440 |
-
</div>
|
| 441 |
-
</div>
|
| 442 |
-
|
| 443 |
-
<div class="action-buttons">
|
| 444 |
-
<button class="export-btn" onclick="loadDatabaseEntries()">π Refresh</button>
|
| 445 |
-
<button class="export-btn" onclick="exportCSV()">π₯ Export CSV</button>
|
| 446 |
-
<button class="export-btn" onclick="exportBibTeX()">π₯ Export BibTeX</button>
|
| 447 |
-
<button class="export-btn" onclick="downloadDatabase()">πΎ Download Database</button>
|
| 448 |
-
<button class="clear-db-btn" onclick="clearDatabase()">ποΈ Clear Entire Database</button>
|
| 449 |
-
</div>
|
| 450 |
-
|
| 451 |
-
<div id="databaseEntries"></div>
|
| 452 |
-
</div>
|
| 453 |
-
|
| 454 |
-
<!-- Process References Tab -->
|
| 455 |
-
<div id="process" class="tab-content">
|
| 456 |
<div class="main-grid">
|
| 457 |
<!-- Input Panel -->
|
| 458 |
<div>
|
|
@@ -481,6 +517,15 @@
|
|
| 481 |
year = {2024}
|
| 482 |
}"></textarea>
|
| 483 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 484 |
<div class="options">
|
| 485 |
<h3 style="color: #333; margin-bottom: 15px; font-size: 1.1em;">Processing Options</h3>
|
| 486 |
|
|
@@ -551,6 +596,57 @@
|
|
| 551 |
</div>
|
| 552 |
</div>
|
| 553 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 554 |
<!-- Help Content -->
|
| 555 |
<div id="help" class="tab-content">
|
| 556 |
<h2>βΉοΈ Help & Information</h2>
|
|
@@ -562,12 +658,36 @@
|
|
| 562 |
<dd style="margin-left: 20px; color: #666;">Paste complete BibTeX entries. The system can enrich them with Crossref, apply abbreviations, and protect acronyms.</dd>
|
| 563 |
|
| 564 |
<dt style="font-weight: 600; margin-top: 10px;">π Title Mode</dt>
|
| 565 |
-
<dd style="margin-left: 20px; color: #666;">Paste only paper titles (one per line). The system automatically searches Crossref and retrieves complete BibTeX entries for each title.
|
| 566 |
</dl>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 567 |
|
| 568 |
-
<h3 style="color: #333; margin: 20px 0 10px;">Journal Abbreviations
|
| 569 |
<p style="margin-left: 20px; color: #666; line-height: 1.8;">
|
| 570 |
-
The system
|
| 571 |
</p>
|
| 572 |
<ul style="margin-left: 40px; color: #666; line-height: 1.8;">
|
| 573 |
<li>"Energy" β "Ener."</li>
|
|
@@ -576,46 +696,20 @@
|
|
| 576 |
<li>"IEEE Transactions on Neural Networks" β "IEEE Trans. on Neural Netw."</li>
|
| 577 |
</ul>
|
| 578 |
|
| 579 |
-
<h3 style="color: #333; margin: 20px 0 10px;">
|
| 580 |
-
<ol style="line-height: 1.8; margin-left: 20px;">
|
| 581 |
-
<li><strong>Database Tab (Default):</strong> View and manage your saved references</li>
|
| 582 |
-
<li><strong>Process Tab:</strong> Add new references
|
| 583 |
-
<ul style="margin-left: 20px;">
|
| 584 |
-
<li>Choose BibTeX Mode or Title Mode</li>
|
| 585 |
-
<li>Paste your content</li>
|
| 586 |
-
<li>Select processing options</li>
|
| 587 |
-
<li>Click "Process References"</li>
|
| 588 |
-
</ul>
|
| 589 |
-
</li>
|
| 590 |
-
<li><strong>Clear Database:</strong> Use the red "Clear Entire Database" button to remove all entries and start fresh</li>
|
| 591 |
-
</ol>
|
| 592 |
-
|
| 593 |
-
<h3 style="color: #333; margin: 20px 0 10px;">Processing Options Explained</h3>
|
| 594 |
<dl style="margin-left: 20px;">
|
| 595 |
-
<dt style="font-weight: 600; margin-top: 10px;">
|
| 596 |
-
<dd style="margin-left: 20px; color: #666;">
|
| 597 |
|
| 598 |
-
<dt style="font-weight: 600; margin-top: 10px;">
|
| 599 |
-
<dd style="margin-left: 20px; color: #666;">
|
| 600 |
|
| 601 |
-
<dt style="font-weight: 600; margin-top: 10px;">
|
| 602 |
-
<dd style="margin-left: 20px; color: #666;">
|
| 603 |
|
| 604 |
-
<dt style="font-weight: 600; margin-top: 10px;">
|
| 605 |
-
<dd style="margin-left: 20px; color: #666;">
|
| 606 |
</dl>
|
| 607 |
-
|
| 608 |
-
<h3 style="color: #333; margin: 20px 0 10px;">Data Storage</h3>
|
| 609 |
-
<p style="margin-left: 20px; color: #666;">
|
| 610 |
-
All data is stored in <code>refs_management.db</code> (SQLite). The database includes:
|
| 611 |
-
</p>
|
| 612 |
-
<ul style="margin-left: 20px;">
|
| 613 |
-
<li>Original BibTeX</li>
|
| 614 |
-
<li>Enriched metadata from Crossref</li>
|
| 615 |
-
<li>Journal abbreviations with periods (ISO 4)</li>
|
| 616 |
-
<li>Acronym-protected versions</li>
|
| 617 |
-
<li>Timestamps and session info</li>
|
| 618 |
-
</ul>
|
| 619 |
</div>
|
| 620 |
</div>
|
| 621 |
</div>
|
|
@@ -623,16 +717,17 @@
|
|
| 623 |
|
| 624 |
<script>
|
| 625 |
let lastResults = null;
|
|
|
|
| 626 |
|
| 627 |
function switchTab(tabName) {
|
| 628 |
-
// Switch between Preview and BibTeX in results
|
| 629 |
const parent = event.target.closest('.card') || document;
|
| 630 |
parent.querySelectorAll('.tab-content').forEach(el => {
|
| 631 |
-
if (el.id === 'database' || el.id === 'process' || el.id === 'help') return;
|
| 632 |
el.classList.remove('active');
|
| 633 |
});
|
| 634 |
parent.querySelectorAll('.tab-button').forEach(el => {
|
| 635 |
-
if (el.textContent.includes('Database') || el.textContent.includes('Process') ||
|
|
|
|
| 636 |
el.classList.remove('active');
|
| 637 |
});
|
| 638 |
|
|
@@ -641,7 +736,7 @@
|
|
| 641 |
}
|
| 642 |
|
| 643 |
function switchMainTab(tabName) {
|
| 644 |
-
document.querySelectorAll('#database, #process, #help').forEach(el => {
|
| 645 |
el.classList.remove('active');
|
| 646 |
});
|
| 647 |
document.querySelectorAll('.tabs > .tab-button').forEach(el => {
|
|
@@ -654,6 +749,8 @@
|
|
| 654 |
if (tabName === 'database') {
|
| 655 |
loadDatabaseEntries();
|
| 656 |
loadStats();
|
|
|
|
|
|
|
| 657 |
}
|
| 658 |
}
|
| 659 |
|
|
@@ -690,6 +787,7 @@ Natural language processing techniques`;
|
|
| 690 |
|
| 691 |
async function processReferences() {
|
| 692 |
const bibtexContent = document.getElementById('bibtexInput').value;
|
|
|
|
| 693 |
const inputMode = document.querySelector('input[name="inputMode"]:checked').value;
|
| 694 |
const loading = document.getElementById('loadingIndicator');
|
| 695 |
const message = document.getElementById('inputMessage');
|
|
@@ -704,19 +802,21 @@ Natural language processing techniques`;
|
|
| 704 |
message.style.display = 'none';
|
| 705 |
|
| 706 |
try {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 707 |
const response = await fetch('/api/process', {
|
| 708 |
method: 'POST',
|
| 709 |
-
|
| 710 |
-
'Content-Type': 'application/json'
|
| 711 |
-
},
|
| 712 |
-
body: JSON.stringify({
|
| 713 |
-
bibtex_content: bibtexContent,
|
| 714 |
-
input_mode: inputMode,
|
| 715 |
-
enrich: document.getElementById('enrichCheckbox').checked,
|
| 716 |
-
abbreviate: document.getElementById('abbreviateCheckbox').checked,
|
| 717 |
-
protect: document.getElementById('protectCheckbox').checked,
|
| 718 |
-
save_to_db: document.getElementById('saveDbCheckbox').checked
|
| 719 |
-
})
|
| 720 |
});
|
| 721 |
|
| 722 |
const data = await response.json();
|
|
@@ -725,9 +825,12 @@ Natural language processing techniques`;
|
|
| 725 |
if (data.success) {
|
| 726 |
lastResults = data;
|
| 727 |
displayResults(data);
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
|
|
|
|
|
|
|
|
|
| 731 |
|
| 732 |
if (data.db_id) {
|
| 733 |
loadDatabaseEntries();
|
|
@@ -754,6 +857,9 @@ Natural language processing techniques`;
|
|
| 754 |
|
| 755 |
let tableHtml = '<table class="results-table"><thead><tr>';
|
| 756 |
const columns = ['Key', 'Type', 'Authors', 'Title', 'Year'];
|
|
|
|
|
|
|
|
|
|
| 757 |
columns.forEach(col => {
|
| 758 |
tableHtml += `<th>${col}</th>`;
|
| 759 |
});
|
|
@@ -767,11 +873,15 @@ Natural language processing techniques`;
|
|
| 767 |
tableHtml += `<td>${row.Authors.substring(0, 50)}${row.Authors.length > 50 ? '...' : ''}</td>`;
|
| 768 |
tableHtml += `<td>${row.Title.substring(0, 50)}${row.Title.length > 50 ? '...' : ''}</td>`;
|
| 769 |
tableHtml += `<td>${row.Year}</td>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 770 |
tableHtml += '</tr>';
|
| 771 |
});
|
| 772 |
|
| 773 |
if (hasMore) {
|
| 774 |
-
tableHtml += `<tr><td colspan="
|
| 775 |
}
|
| 776 |
|
| 777 |
tableHtml += '</tbody></table>';
|
|
@@ -801,7 +911,7 @@ Natural language processing techniques`;
|
|
| 801 |
const container = document.getElementById('databaseEntries');
|
| 802 |
|
| 803 |
if (data.count === 0) {
|
| 804 |
-
container.innerHTML = '<
|
| 805 |
return;
|
| 806 |
}
|
| 807 |
|
|
@@ -814,16 +924,20 @@ Natural language processing techniques`;
|
|
| 814 |
}
|
| 815 |
|
| 816 |
data.entries.slice(0, previewLimit).forEach(entry => {
|
|
|
|
|
|
|
|
|
|
| 817 |
html += `
|
| 818 |
<div class="db-entry-card">
|
| 819 |
<h4>
|
| 820 |
-
<span>${entry.key} <span class="badge badge-info">${entry.type}</span></span>
|
| 821 |
<button class="delete-btn" onclick="deleteEntry('${entry.key}')">Delete</button>
|
| 822 |
</h4>
|
| 823 |
<div class="entry-meta">
|
| 824 |
<span><strong>Title:</strong> ${entry.title.substring(0, 80)}${entry.title.length > 80 ? '...' : ''}</span>
|
| 825 |
<span><strong>Year:</strong> ${entry.year || 'N/A'}</span>
|
| 826 |
<span><strong>Added:</strong> ${new Date(entry.created_at).toLocaleDateString()}</span>
|
|
|
|
| 827 |
</div>
|
| 828 |
</div>
|
| 829 |
`;
|
|
@@ -835,6 +949,74 @@ Natural language processing techniques`;
|
|
| 835 |
}
|
| 836 |
}
|
| 837 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 838 |
async function deleteEntry(key) {
|
| 839 |
if (!confirm(`Delete entry "${key}"?`)) return;
|
| 840 |
|
|
@@ -857,7 +1039,6 @@ Natural language processing techniques`;
|
|
| 857 |
return;
|
| 858 |
}
|
| 859 |
|
| 860 |
-
// Double confirmation
|
| 861 |
if (!confirm('This action CANNOT be undone. All your references will be lost.\n\nClick OK to proceed with clearing the database.')) {
|
| 862 |
return;
|
| 863 |
}
|
|
@@ -873,6 +1054,7 @@ Natural language processing techniques`;
|
|
| 873 |
alert(`β
${result.message}`);
|
| 874 |
loadDatabaseEntries();
|
| 875 |
loadStats();
|
|
|
|
| 876 |
} else {
|
| 877 |
alert(`β Error: ${result.error}`);
|
| 878 |
}
|
|
@@ -908,6 +1090,7 @@ Natural language processing techniques`;
|
|
| 908 |
|
| 909 |
function clearInput() {
|
| 910 |
document.getElementById('bibtexInput').value = '';
|
|
|
|
| 911 |
}
|
| 912 |
|
| 913 |
function showMessage(element, message, type) {
|
|
@@ -916,10 +1099,9 @@ Natural language processing techniques`;
|
|
| 916 |
element.style.display = 'block';
|
| 917 |
}
|
| 918 |
|
| 919 |
-
// Initial load
|
| 920 |
window.addEventListener('DOMContentLoaded', function() {
|
| 921 |
loadStats();
|
| 922 |
-
loadDatabaseEntries();
|
| 923 |
});
|
| 924 |
</script>
|
| 925 |
</body>
|
|
|
|
| 251 |
color: #0c5460;
|
| 252 |
}
|
| 253 |
|
| 254 |
+
.badge-warning {
|
| 255 |
+
background: #fff3cd;
|
| 256 |
+
color: #856404;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
.bibtex-preview {
|
| 260 |
background: #f5f5f5;
|
| 261 |
padding: 15px;
|
|
|
|
| 385 |
background: #c82333;
|
| 386 |
}
|
| 387 |
|
| 388 |
+
.file-upload {
|
| 389 |
+
margin: 15px 0;
|
| 390 |
+
padding: 15px;
|
| 391 |
+
background: #f5f5f5;
|
| 392 |
+
border-radius: 8px;
|
| 393 |
+
border: 2px dashed #ddd;
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
.file-upload label {
|
| 397 |
+
display: block;
|
| 398 |
+
margin-bottom: 8px;
|
| 399 |
+
font-weight: 600;
|
| 400 |
+
color: #333;
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
.file-upload input[type="file"] {
|
| 404 |
+
width: 100%;
|
| 405 |
+
padding: 8px;
|
| 406 |
+
border: 1px solid #ddd;
|
| 407 |
+
border-radius: 4px;
|
| 408 |
+
background: white;
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
.section-filter {
|
| 412 |
+
margin: 20px 0;
|
| 413 |
+
padding: 15px;
|
| 414 |
+
background: #f5f5f5;
|
| 415 |
+
border-radius: 8px;
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
.section-filter select {
|
| 419 |
+
width: 100%;
|
| 420 |
+
padding: 10px;
|
| 421 |
+
border: 2px solid #e0e0e0;
|
| 422 |
+
border-radius: 8px;
|
| 423 |
+
font-size: 1em;
|
| 424 |
+
background: white;
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
.empty-state {
|
| 428 |
+
text-align: center;
|
| 429 |
+
padding: 60px 20px;
|
| 430 |
+
color: #999;
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
.empty-state h3 {
|
| 434 |
+
color: #666;
|
| 435 |
+
margin-bottom: 10px;
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
@media (max-width: 1024px) {
|
| 439 |
.main-grid {
|
| 440 |
grid-template-columns: 1fr;
|
|
|
|
| 459 |
.hidden {
|
| 460 |
display: none;
|
| 461 |
}
|
| 462 |
+
|
| 463 |
+
.info-box {
|
| 464 |
+
background: #d1ecf1;
|
| 465 |
+
border: 1px solid #bee5eb;
|
| 466 |
+
color: #0c5460;
|
| 467 |
+
padding: 12px;
|
| 468 |
+
border-radius: 8px;
|
| 469 |
+
margin: 10px 0;
|
| 470 |
+
font-size: 0.9em;
|
| 471 |
+
}
|
| 472 |
</style>
|
| 473 |
</head>
|
| 474 |
<body>
|
|
|
|
| 481 |
<!-- Main Tabs -->
|
| 482 |
<div class="card">
|
| 483 |
<div class="tabs">
|
| 484 |
+
<button class="tab-button active" onclick="switchMainTab('process')">π Process References</button>
|
| 485 |
+
<button class="tab-button" onclick="switchMainTab('database')">πΎ Database</button>
|
| 486 |
+
<button class="tab-button" onclick="switchMainTab('sections')">π Select by Section</button>
|
| 487 |
<button class="tab-button" onclick="switchMainTab('help')">βΉοΈ Help & Info</button>
|
| 488 |
</div>
|
| 489 |
|
| 490 |
+
<!-- Process References Tab (Default) -->
|
| 491 |
+
<div id="process" class="tab-content active">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 492 |
<div class="main-grid">
|
| 493 |
<!-- Input Panel -->
|
| 494 |
<div>
|
|
|
|
| 517 |
year = {2024}
|
| 518 |
}"></textarea>
|
| 519 |
|
| 520 |
+
<!-- Optional LaTeX File Upload -->
|
| 521 |
+
<div class="file-upload">
|
| 522 |
+
<label>π LaTeX Manuscript (Optional)</label>
|
| 523 |
+
<p style="color: #666; font-size: 0.9em; margin-bottom: 8px;">
|
| 524 |
+
Upload your .tex file to track citation frequency and sections
|
| 525 |
+
</p>
|
| 526 |
+
<input type="file" id="latexFile" accept=".tex" />
|
| 527 |
+
</div>
|
| 528 |
+
|
| 529 |
<div class="options">
|
| 530 |
<h3 style="color: #333; margin-bottom: 15px; font-size: 1.1em;">Processing Options</h3>
|
| 531 |
|
|
|
|
| 596 |
</div>
|
| 597 |
</div>
|
| 598 |
|
| 599 |
+
<!-- Database Tab -->
|
| 600 |
+
<div id="database" class="tab-content">
|
| 601 |
+
<h2>πΎ Database Management</h2>
|
| 602 |
+
|
| 603 |
+
<div class="stats-grid" id="statsContainer" style="margin-top: 20px;">
|
| 604 |
+
<div class="stat-box">
|
| 605 |
+
<h3 id="statTotal">0</h3>
|
| 606 |
+
<p>Total Entries</p>
|
| 607 |
+
</div>
|
| 608 |
+
<div class="stat-box">
|
| 609 |
+
<h3 id="statTypes">0</h3>
|
| 610 |
+
<p>Entry Types</p>
|
| 611 |
+
</div>
|
| 612 |
+
<div class="stat-box">
|
| 613 |
+
<h3 id="statYears">0</h3>
|
| 614 |
+
<p>Unique Years</p>
|
| 615 |
+
</div>
|
| 616 |
+
</div>
|
| 617 |
+
|
| 618 |
+
<div class="action-buttons">
|
| 619 |
+
<button class="export-btn" onclick="loadDatabaseEntries()">π Refresh</button>
|
| 620 |
+
<button class="export-btn" onclick="exportCSV()">π₯ Export CSV</button>
|
| 621 |
+
<button class="export-btn" onclick="exportBibTeX()">π₯ Export BibTeX</button>
|
| 622 |
+
<button class="export-btn" onclick="downloadDatabase()">πΎ Download Database</button>
|
| 623 |
+
<button class="clear-db-btn" onclick="clearDatabase()">ποΈ Clear Entire Database</button>
|
| 624 |
+
</div>
|
| 625 |
+
|
| 626 |
+
<div id="databaseEntries"></div>
|
| 627 |
+
</div>
|
| 628 |
+
|
| 629 |
+
<!-- Select by Section Tab -->
|
| 630 |
+
<div id="sections" class="tab-content">
|
| 631 |
+
<h2>π Select References by Section</h2>
|
| 632 |
+
|
| 633 |
+
<div class="info-box">
|
| 634 |
+
<strong>βΉοΈ Note:</strong> This feature requires uploading a LaTeX manuscript (.tex file) when processing references.
|
| 635 |
+
It shows which references appear in which sections and their citation frequency.
|
| 636 |
+
</div>
|
| 637 |
+
|
| 638 |
+
<div class="section-filter">
|
| 639 |
+
<label style="font-weight: 600; color: #333; margin-bottom: 8px; display: block;">
|
| 640 |
+
Filter by Section:
|
| 641 |
+
</label>
|
| 642 |
+
<select id="sectionSelect" onchange="filterBySection()">
|
| 643 |
+
<option value="">Loading sections...</option>
|
| 644 |
+
</select>
|
| 645 |
+
</div>
|
| 646 |
+
|
| 647 |
+
<div id="sectionResults"></div>
|
| 648 |
+
</div>
|
| 649 |
+
|
| 650 |
<!-- Help Content -->
|
| 651 |
<div id="help" class="tab-content">
|
| 652 |
<h2>βΉοΈ Help & Information</h2>
|
|
|
|
| 658 |
<dd style="margin-left: 20px; color: #666;">Paste complete BibTeX entries. The system can enrich them with Crossref, apply abbreviations, and protect acronyms.</dd>
|
| 659 |
|
| 660 |
<dt style="font-weight: 600; margin-top: 10px;">π Title Mode</dt>
|
| 661 |
+
<dd style="margin-left: 20px; color: #666;">Paste only paper titles (one per line). The system automatically searches Crossref and retrieves complete BibTeX entries for each title.</dd>
|
| 662 |
</dl>
|
| 663 |
+
|
| 664 |
+
<h3 style="color: #333; margin: 20px 0 10px;">LaTeX Manuscript Analysis (NEW!)</h3>
|
| 665 |
+
<p style="margin-left: 20px; color: #666; line-height: 1.8;">
|
| 666 |
+
Upload your LaTeX manuscript (.tex file) to enable advanced tracking:
|
| 667 |
+
</p>
|
| 668 |
+
<ul style="margin-left: 40px; color: #666; line-height: 1.8;">
|
| 669 |
+
<li><strong>Citation Frequency:</strong> How many times each reference is cited</li>
|
| 670 |
+
<li><strong>Section Tracking:</strong> Which sections cite each reference</li>
|
| 671 |
+
<li><strong>Section Filtering:</strong> View references by specific section (Introduction, Methods, etc.)</li>
|
| 672 |
+
</ul>
|
| 673 |
+
<p style="margin-left: 20px; color: #666; line-height: 1.8; margin-top: 10px;">
|
| 674 |
+
Example LaTeX structure:
|
| 675 |
+
</p>
|
| 676 |
+
<pre style="margin-left: 40px; background: #f5f5f5; padding: 15px; border-radius: 5px; overflow-x: auto;">
|
| 677 |
+
\section{Introduction}
|
| 678 |
+
Text here \cite{reference_1} more text \cite{reference_2}
|
| 679 |
+
and again \cite{reference_1}
|
| 680 |
+
|
| 681 |
+
\section{Methods}
|
| 682 |
+
Description \cite{reference_3} and \cite{reference_1}
|
| 683 |
+
</pre>
|
| 684 |
+
<p style="margin-left: 20px; color: #666; line-height: 1.8;">
|
| 685 |
+
Results in: reference_1 appears in Introduction (2Γ) and Methods (1Γ)
|
| 686 |
+
</p>
|
| 687 |
|
| 688 |
+
<h3 style="color: #333; margin: 20px 0 10px;">Journal Abbreviations</h3>
|
| 689 |
<p style="margin-left: 20px; color: #666; line-height: 1.8;">
|
| 690 |
+
The system uses <strong>ISO 4 standard abbreviations with periods</strong>:
|
| 691 |
</p>
|
| 692 |
<ul style="margin-left: 40px; color: #666; line-height: 1.8;">
|
| 693 |
<li>"Energy" β "Ener."</li>
|
|
|
|
| 696 |
<li>"IEEE Transactions on Neural Networks" β "IEEE Trans. on Neural Netw."</li>
|
| 697 |
</ul>
|
| 698 |
|
| 699 |
+
<h3 style="color: #333; margin: 20px 0 10px;">Tab Overview</h3>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 700 |
<dl style="margin-left: 20px;">
|
| 701 |
+
<dt style="font-weight: 600; margin-top: 10px;">π Process References (Tab 1)</dt>
|
| 702 |
+
<dd style="margin-left: 20px; color: #666;">Add new references via BibTeX or titles. Optionally upload LaTeX manuscript for section tracking.</dd>
|
| 703 |
|
| 704 |
+
<dt style="font-weight: 600; margin-top: 10px;">πΎ Database (Tab 2)</dt>
|
| 705 |
+
<dd style="margin-left: 20px; color: #666;">View all saved references, export data, or clear database.</dd>
|
| 706 |
|
| 707 |
+
<dt style="font-weight: 600; margin-top: 10px;">π Select by Section (Tab 3)</dt>
|
| 708 |
+
<dd style="margin-left: 20px; color: #666;">Filter references by LaTeX section. Only available if LaTeX manuscript was uploaded.</dd>
|
| 709 |
|
| 710 |
+
<dt style="font-weight: 600; margin-top: 10px;">βΉοΈ Help & Info (Tab 4)</dt>
|
| 711 |
+
<dd style="margin-left: 20px; color: #666;">Documentation and usage guide.</dd>
|
| 712 |
</dl>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 713 |
</div>
|
| 714 |
</div>
|
| 715 |
</div>
|
|
|
|
| 717 |
|
| 718 |
<script>
|
| 719 |
let lastResults = null;
|
| 720 |
+
let availableSections = [];
|
| 721 |
|
| 722 |
function switchTab(tabName) {
|
|
|
|
| 723 |
const parent = event.target.closest('.card') || document;
|
| 724 |
parent.querySelectorAll('.tab-content').forEach(el => {
|
| 725 |
+
if (el.id === 'database' || el.id === 'process' || el.id === 'help' || el.id === 'sections') return;
|
| 726 |
el.classList.remove('active');
|
| 727 |
});
|
| 728 |
parent.querySelectorAll('.tab-button').forEach(el => {
|
| 729 |
+
if (el.textContent.includes('Database') || el.textContent.includes('Process') ||
|
| 730 |
+
el.textContent.includes('Help') || el.textContent.includes('Section')) return;
|
| 731 |
el.classList.remove('active');
|
| 732 |
});
|
| 733 |
|
|
|
|
| 736 |
}
|
| 737 |
|
| 738 |
function switchMainTab(tabName) {
|
| 739 |
+
document.querySelectorAll('#database, #process, #help, #sections').forEach(el => {
|
| 740 |
el.classList.remove('active');
|
| 741 |
});
|
| 742 |
document.querySelectorAll('.tabs > .tab-button').forEach(el => {
|
|
|
|
| 749 |
if (tabName === 'database') {
|
| 750 |
loadDatabaseEntries();
|
| 751 |
loadStats();
|
| 752 |
+
} else if (tabName === 'sections') {
|
| 753 |
+
loadSections();
|
| 754 |
}
|
| 755 |
}
|
| 756 |
|
|
|
|
| 787 |
|
| 788 |
async function processReferences() {
|
| 789 |
const bibtexContent = document.getElementById('bibtexInput').value;
|
| 790 |
+
const latexFile = document.getElementById('latexFile').files[0];
|
| 791 |
const inputMode = document.querySelector('input[name="inputMode"]:checked').value;
|
| 792 |
const loading = document.getElementById('loadingIndicator');
|
| 793 |
const message = document.getElementById('inputMessage');
|
|
|
|
| 802 |
message.style.display = 'none';
|
| 803 |
|
| 804 |
try {
|
| 805 |
+
const formData = new FormData();
|
| 806 |
+
formData.append('bibtex_content', bibtexContent);
|
| 807 |
+
formData.append('input_mode', inputMode);
|
| 808 |
+
formData.append('enrich', document.getElementById('enrichCheckbox').checked);
|
| 809 |
+
formData.append('abbreviate', document.getElementById('abbreviateCheckbox').checked);
|
| 810 |
+
formData.append('protect', document.getElementById('protectCheckbox').checked);
|
| 811 |
+
formData.append('save_to_db', document.getElementById('saveDbCheckbox').checked);
|
| 812 |
+
|
| 813 |
+
if (latexFile) {
|
| 814 |
+
formData.append('latex_file', latexFile);
|
| 815 |
+
}
|
| 816 |
+
|
| 817 |
const response = await fetch('/api/process', {
|
| 818 |
method: 'POST',
|
| 819 |
+
body: formData
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 820 |
});
|
| 821 |
|
| 822 |
const data = await response.json();
|
|
|
|
| 825 |
if (data.success) {
|
| 826 |
lastResults = data;
|
| 827 |
displayResults(data);
|
| 828 |
+
|
| 829 |
+
let msg = `β
Successfully processed ${data.count} references`;
|
| 830 |
+
if (data.db_id) msg += ' and saved to database';
|
| 831 |
+
if (data.latex_analyzed) msg += ` (LaTeX analysis: ${data.citations_found} citations found)`;
|
| 832 |
+
|
| 833 |
+
showMessage(message, msg, 'success');
|
| 834 |
|
| 835 |
if (data.db_id) {
|
| 836 |
loadDatabaseEntries();
|
|
|
|
| 857 |
|
| 858 |
let tableHtml = '<table class="results-table"><thead><tr>';
|
| 859 |
const columns = ['Key', 'Type', 'Authors', 'Title', 'Year'];
|
| 860 |
+
if (data.latex_analyzed) {
|
| 861 |
+
columns.push('Frequency', 'Sections');
|
| 862 |
+
}
|
| 863 |
columns.forEach(col => {
|
| 864 |
tableHtml += `<th>${col}</th>`;
|
| 865 |
});
|
|
|
|
| 873 |
tableHtml += `<td>${row.Authors.substring(0, 50)}${row.Authors.length > 50 ? '...' : ''}</td>`;
|
| 874 |
tableHtml += `<td>${row.Title.substring(0, 50)}${row.Title.length > 50 ? '...' : ''}</td>`;
|
| 875 |
tableHtml += `<td>${row.Year}</td>`;
|
| 876 |
+
if (data.latex_analyzed) {
|
| 877 |
+
tableHtml += `<td><span class="badge badge-warning">${row.Frequency || 0}Γ</span></td>`;
|
| 878 |
+
tableHtml += `<td style="font-size: 0.85em;">${row.Sections || '-'}</td>`;
|
| 879 |
+
}
|
| 880 |
tableHtml += '</tr>';
|
| 881 |
});
|
| 882 |
|
| 883 |
if (hasMore) {
|
| 884 |
+
tableHtml += `<tr><td colspan="${columns.length}" style="text-align: center; padding: 20px; color: #666; font-style: italic;">... and ${data.count - previewLimit} more references (check Database tab or export to see all)</td></tr>`;
|
| 885 |
}
|
| 886 |
|
| 887 |
tableHtml += '</tbody></table>';
|
|
|
|
| 911 |
const container = document.getElementById('databaseEntries');
|
| 912 |
|
| 913 |
if (data.count === 0) {
|
| 914 |
+
container.innerHTML = '<div class="empty-state"><h3>No entries in database yet</h3><p>Process some references to get started</p></div>';
|
| 915 |
return;
|
| 916 |
}
|
| 917 |
|
|
|
|
| 924 |
}
|
| 925 |
|
| 926 |
data.entries.slice(0, previewLimit).forEach(entry => {
|
| 927 |
+
const freqBadge = entry.frequency ? `<span class="badge badge-warning">${entry.frequency}Γ cited</span>` : '';
|
| 928 |
+
const sectionInfo = entry.sections ? `<br><strong>Sections:</strong> ${entry.sections}` : '';
|
| 929 |
+
|
| 930 |
html += `
|
| 931 |
<div class="db-entry-card">
|
| 932 |
<h4>
|
| 933 |
+
<span>${entry.key} <span class="badge badge-info">${entry.type}</span> ${freqBadge}</span>
|
| 934 |
<button class="delete-btn" onclick="deleteEntry('${entry.key}')">Delete</button>
|
| 935 |
</h4>
|
| 936 |
<div class="entry-meta">
|
| 937 |
<span><strong>Title:</strong> ${entry.title.substring(0, 80)}${entry.title.length > 80 ? '...' : ''}</span>
|
| 938 |
<span><strong>Year:</strong> ${entry.year || 'N/A'}</span>
|
| 939 |
<span><strong>Added:</strong> ${new Date(entry.created_at).toLocaleDateString()}</span>
|
| 940 |
+
${sectionInfo}
|
| 941 |
</div>
|
| 942 |
</div>
|
| 943 |
`;
|
|
|
|
| 949 |
}
|
| 950 |
}
|
| 951 |
|
| 952 |
+
async function loadSections() {
|
| 953 |
+
try {
|
| 954 |
+
const response = await fetch('/api/sections/list');
|
| 955 |
+
const data = await response.json();
|
| 956 |
+
|
| 957 |
+
const select = document.getElementById('sectionSelect');
|
| 958 |
+
const resultsDiv = document.getElementById('sectionResults');
|
| 959 |
+
|
| 960 |
+
if (!data.success || data.sections.length === 0) {
|
| 961 |
+
select.innerHTML = '<option value="">No sections available</option>';
|
| 962 |
+
resultsDiv.innerHTML = '<div class="empty-state"><h3>No LaTeX Analysis Available</h3><p>Upload a LaTeX manuscript when processing references to enable this feature</p></div>';
|
| 963 |
+
return;
|
| 964 |
+
}
|
| 965 |
+
|
| 966 |
+
availableSections = data.sections;
|
| 967 |
+
select.innerHTML = '<option value="">-- Select a section --</option>';
|
| 968 |
+
data.sections.forEach(section => {
|
| 969 |
+
const option = document.createElement('option');
|
| 970 |
+
option.value = section;
|
| 971 |
+
option.textContent = section;
|
| 972 |
+
select.appendChild(option);
|
| 973 |
+
});
|
| 974 |
+
|
| 975 |
+
resultsDiv.innerHTML = '<p style="color: #666; text-align: center; padding: 40px;">Select a section to view its references</p>';
|
| 976 |
+
} catch (error) {
|
| 977 |
+
console.error('Error loading sections:', error);
|
| 978 |
+
}
|
| 979 |
+
}
|
| 980 |
+
|
| 981 |
+
async function filterBySection() {
|
| 982 |
+
const section = document.getElementById('sectionSelect').value;
|
| 983 |
+
const resultsDiv = document.getElementById('sectionResults');
|
| 984 |
+
|
| 985 |
+
if (!section) {
|
| 986 |
+
resultsDiv.innerHTML = '<p style="color: #666; text-align: center; padding: 40px;">Select a section to view its references</p>';
|
| 987 |
+
return;
|
| 988 |
+
}
|
| 989 |
+
|
| 990 |
+
try {
|
| 991 |
+
const response = await fetch(`/api/sections/references?section=${encodeURIComponent(section)}`);
|
| 992 |
+
const data = await response.json();
|
| 993 |
+
|
| 994 |
+
if (!data.success || data.references.length === 0) {
|
| 995 |
+
resultsDiv.innerHTML = `<div class="empty-state"><h3>No references found</h3><p>Section "${section}" has no citations</p></div>`;
|
| 996 |
+
return;
|
| 997 |
+
}
|
| 998 |
+
|
| 999 |
+
let html = `<h3 style="color: #333; margin-bottom: 15px;">References in "${section}" (${data.references.length} total)</h3>`;
|
| 1000 |
+
html += '<table class="results-table"><thead><tr><th>Key</th><th>Title</th><th>Authors</th><th>Year</th><th>Frequency</th></tr></thead><tbody>';
|
| 1001 |
+
|
| 1002 |
+
data.references.forEach(ref => {
|
| 1003 |
+
html += `<tr>
|
| 1004 |
+
<td><span class="badge badge-info">${ref.key}</span></td>
|
| 1005 |
+
<td>${ref.title.substring(0, 60)}${ref.title.length > 60 ? '...' : ''}</td>
|
| 1006 |
+
<td>${ref.authors.substring(0, 40)}${ref.authors.length > 40 ? '...' : ''}</td>
|
| 1007 |
+
<td>${ref.year}</td>
|
| 1008 |
+
<td><span class="badge badge-warning">${ref.frequency_in_section}Γ</span></td>
|
| 1009 |
+
</tr>`;
|
| 1010 |
+
});
|
| 1011 |
+
|
| 1012 |
+
html += '</tbody></table>';
|
| 1013 |
+
resultsDiv.innerHTML = html;
|
| 1014 |
+
} catch (error) {
|
| 1015 |
+
console.error('Error filtering by section:', error);
|
| 1016 |
+
resultsDiv.innerHTML = '<div class="error">Error loading references for this section</div>';
|
| 1017 |
+
}
|
| 1018 |
+
}
|
| 1019 |
+
|
| 1020 |
async function deleteEntry(key) {
|
| 1021 |
if (!confirm(`Delete entry "${key}"?`)) return;
|
| 1022 |
|
|
|
|
| 1039 |
return;
|
| 1040 |
}
|
| 1041 |
|
|
|
|
| 1042 |
if (!confirm('This action CANNOT be undone. All your references will be lost.\n\nClick OK to proceed with clearing the database.')) {
|
| 1043 |
return;
|
| 1044 |
}
|
|
|
|
| 1054 |
alert(`β
${result.message}`);
|
| 1055 |
loadDatabaseEntries();
|
| 1056 |
loadStats();
|
| 1057 |
+
loadSections();
|
| 1058 |
} else {
|
| 1059 |
alert(`β Error: ${result.error}`);
|
| 1060 |
}
|
|
|
|
| 1090 |
|
| 1091 |
function clearInput() {
|
| 1092 |
document.getElementById('bibtexInput').value = '';
|
| 1093 |
+
document.getElementById('latexFile').value = '';
|
| 1094 |
}
|
| 1095 |
|
| 1096 |
function showMessage(element, message, type) {
|
|
|
|
| 1099 |
element.style.display = 'block';
|
| 1100 |
}
|
| 1101 |
|
| 1102 |
+
// Initial load
|
| 1103 |
window.addEventListener('DOMContentLoaded', function() {
|
| 1104 |
loadStats();
|
|
|
|
| 1105 |
});
|
| 1106 |
</script>
|
| 1107 |
</body>
|