Spaces:
Sleeping
Sleeping
import gradio as gr | |
import sqlite3 | |
import pandas as pd | |
from pathlib import Path | |
import re | |
def get_db_connection(): | |
db_path = Path(__file__).parent / 'data' / 'samaritanus.db' | |
return sqlite3.connect(db_path) | |
def highlight_text(text, search_term): | |
if not search_term: | |
return text | |
pattern = re.escape(search_term) | |
return re.sub(f'({pattern})', r'<mark>\1</mark>', text, flags=re.IGNORECASE) | |
def search_verses(search_term, exact_match, page=1, per_page=10): | |
conn = get_db_connection() | |
if exact_match: | |
# Exact match | |
query = """ | |
SELECT v.*, | |
COUNT(*) OVER() as total_count | |
FROM verses v | |
JOIN verses_fts fts ON v.id = fts.rowid | |
WHERE verses_fts MATCH ? | |
ORDER BY v.book, v.chapter, v.verse | |
LIMIT ? OFFSET ? | |
""" | |
params = [search_term, per_page, (page - 1) * per_page] | |
else: | |
# Fuzzy match using SQLite's built-in fuzzy search | |
words = search_term.split() | |
# Permissive fuzzy match - use more lenient matching | |
fuzzy_terms = [] | |
for word in words: | |
# Add the word itself | |
fuzzy_terms.append(word) | |
# Add prefix match | |
fuzzy_terms.append(f"{word}*") | |
match_expr = " OR ".join(fuzzy_terms) | |
query = """ | |
SELECT v.*, | |
COUNT(*) OVER() as total_count | |
FROM verses v | |
JOIN verses_fts fts ON v.id = fts.rowid | |
WHERE verses_fts MATCH ? | |
ORDER BY v.book, v.chapter, v.verse | |
LIMIT ? OFFSET ? | |
""" | |
params = [match_expr, per_page, (page - 1) * per_page] | |
df = pd.read_sql_query(query, conn, params=params) | |
conn.close() | |
return df | |
def format_results(results): | |
if results.empty: | |
return "No results found" | |
formatted_results = [] | |
for _, row in results.iterrows(): | |
verse_ref = f"{row['book_name']} {row['chapter']}:{row['verse']}" | |
verse_text = row['text'] | |
formatted_results.append(f"<div style='text-align: right; direction: rtl;'><b>{verse_ref}</b><br>{verse_text}</div><hr>") | |
return "\n".join(formatted_results) | |
def search(search_term, exact_match, page=1): | |
if not search_term: | |
return "Please enter a search term", 1, 1 | |
results = search_verses(search_term, exact_match, page) | |
if results.empty: | |
return "No results found", 1, 1 | |
total_results = results['total_count'].iloc[0] | |
total_pages = (total_results + 9) // 10 # Round up division | |
formatted_results = format_results(results) | |
result_count = f"Found {total_results} {'result' if total_results == 1 else 'results'}" | |
return f"{result_count}\n\n{formatted_results}", page, total_pages | |
# Custom CSS | |
css = """ | |
@font-face { | |
font-family: 'NarkisClassic'; | |
src: url('https://raw.githubusercontent.com/johnlockejrr/samaritanus_search/main/public/fonts/NarkisClassic-Regular.woff') format('woff'); | |
font-weight: normal; | |
font-style: normal; | |
} | |
@font-face { | |
font-family: 'NarkisClassic'; | |
src: url('https://raw.githubusercontent.com/johnlockejrr/samaritanus_search/main/public/fonts/NarkisClassic-Bold.woff') format('woff'); | |
font-weight: bold; | |
font-style: normal; | |
} | |
.gradio-container { | |
font-family: 'NarkisClassic', serif; | |
} | |
mark { | |
background-color: yellow; | |
font-weight: bold; | |
} | |
""" | |
# Create the Gradio interface | |
with gr.Blocks(css=css) as demo: | |
gr.Markdown("# Samaritan Torah Search") | |
# Store current page in session state | |
current_page = gr.State(1) | |
total_pages = gr.State(1) | |
with gr.Row(): | |
search_input = gr.Textbox( | |
label="Search", | |
placeholder="Enter search term...", | |
scale=4 | |
) | |
search_type = gr.Radio( | |
choices=["Exact Match", "Fuzzy Match"], | |
value="Exact Match", | |
label="Search Type", | |
scale=1 | |
) | |
search_button = gr.Button("Search") | |
with gr.Row(): | |
output = gr.Markdown() | |
with gr.Row(visible=False) as pagination_row: | |
with gr.Column(scale=1): | |
prev_button = gr.Button("β", size="sm") | |
with gr.Column(scale=3): | |
page_info = gr.Markdown() | |
with gr.Column(scale=1): | |
next_button = gr.Button("β", size="sm") | |
def search_wrapper(search_term, search_type, page): | |
exact_match = search_type == "Exact Match" | |
return search(search_term, exact_match, page) | |
def reset_page(): | |
return 1, 1 | |
def update_pagination(page, total): | |
if total > 1: | |
return gr.Row.update(visible=True), f"Page {page} of {total}" | |
return gr.Row.update(visible=False), "" | |
def prev_page(page, total): | |
if page > 1: | |
return page - 1 | |
return page | |
def next_page(page, total): | |
if page < total: | |
return page + 1 | |
return page | |
# Search button click | |
search_button.click( | |
fn=reset_page, | |
outputs=[current_page, total_pages] | |
).then( | |
fn=search_wrapper, | |
inputs=[search_input, search_type, current_page], | |
outputs=[output, current_page, total_pages] | |
).then( | |
fn=update_pagination, | |
inputs=[current_page, total_pages], | |
outputs=[pagination_row, page_info] | |
) | |
# Search input submit | |
search_input.submit( | |
fn=reset_page, | |
outputs=[current_page, total_pages] | |
).then( | |
fn=search_wrapper, | |
inputs=[search_input, search_type, current_page], | |
outputs=[output, current_page, total_pages] | |
).then( | |
fn=update_pagination, | |
inputs=[current_page, total_pages], | |
outputs=[pagination_row, page_info] | |
) | |
# Pagination buttons | |
prev_button.click( | |
fn=prev_page, | |
inputs=[current_page, total_pages], | |
outputs=current_page | |
).then( | |
fn=search_wrapper, | |
inputs=[search_input, search_type, current_page], | |
outputs=[output, current_page, total_pages] | |
).then( | |
fn=update_pagination, | |
inputs=[current_page, total_pages], | |
outputs=[pagination_row, page_info] | |
) | |
next_button.click( | |
fn=next_page, | |
inputs=[current_page, total_pages], | |
outputs=current_page | |
).then( | |
fn=search_wrapper, | |
inputs=[search_input, search_type, current_page], | |
outputs=[output, current_page, total_pages] | |
).then( | |
fn=update_pagination, | |
inputs=[current_page, total_pages], | |
outputs=[pagination_row, page_info] | |
) | |
if __name__ == "__main__": | |
demo.launch() |