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()