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'\1', 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"
{verse_ref}
{verse_text}
")
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()