import json try: from pyodide.http import pyfetch except: pass import asyncio import gradio as gr import js import asyncio import struct from typing import List from transformers_js_py import pipeline import pandas as pd # Load the Hugging Face embedding model extractor = None async def init_pipeline(): global extractor extractor = await pipeline( 'feature-extraction', None, modelPath='models/Xenova/all-MiniLM-L6-v2', quantized=False ) # Export a function that ensures initialization async def get_extractor(): global extractor if extractor is None: await init_pipeline() return extractor js.eval(r''' async function setup() { // Dynamically import the SQLite module const { default: init } = await import("https://cdn.jsdelivr.net/npm/sqlite-vec-wasm-demo@latest/sqlite3.mjs"); const sqlite3 = await init(); console.log("SQLite3 initialized:", sqlite3 ? "success" : "failed"); // Initialize the database in memory const db = new sqlite3.oo1.DB(":memory:"); console.log("Database initialized"); // Remove the serialize function from JS - we'll do it in Python // Load the database async function loadDatabase(url) { console.log("Fetching database from:", url); const res = await fetch(url); if (!res.ok) { throw new Error(`Failed to fetch database: ${res.status} ${res.statusText}`); } const contentType = res.headers.get('content-type'); console.log("Content-Type:", contentType); const contentLength = res.headers.get('content-length'); console.log("Content-Length:", contentLength); const buffer = await res.arrayBuffer(); console.log("Buffer size:", buffer.byteLength, "bytes"); if (buffer.byteLength === 0) { throw new Error("Empty database file"); } const p = sqlite3.wasm.allocFromTypedArray(buffer); if (!p) { throw new Error("Failed to allocate memory for database"); } console.log("Memory allocated, attempting to deserialize database"); try { const rc = sqlite3.capi.sqlite3_deserialize( db.pointer, 'main', p, buffer.byteLength, buffer.byteLength, sqlite3.capi.SQLITE_DESERIALIZE_FREEONCLOSE ); if (rc !== sqlite3.capi.SQLITE_OK) { const errorMsg = `SQLite error: ${rc} - ${sqlite3.capi.sqlite3_errmsg(db.pointer)}`; console.error(errorMsg); throw new Error(errorMsg); } console.log("Database deserialized successfully"); // List all tables to verify database structure try { const tables = db.selectArrays("SELECT name FROM sqlite_master WHERE type='table'"); console.log("Tables in database:", tables); return true; } catch (e) { console.error("Failed to list tables:", e); return false; } } catch (e) { console.error("Deserialization error:", e); throw e; } } // Get the base URL for the current page const baseUrl = self.location.origin; console.log("Base URL:", baseUrl); // Database URL const dbUrl = baseUrl + "/crop_web.db"; try { const success = await loadDatabase(dbUrl); if (success) { console.log("Database loaded successfully"); } else { console.error("Database load failed"); } } catch (error) { console.error("Error loading database:", error); } // Define similarity search function globally self.similaritySearch = async function(embeddingBytes, k = 5) { try { // Convert byte array to Uint8Array for SQLite const serializedEmbedding = new Uint8Array(embeddingBytes); console.log("Converted to Uint8Array, size:", serializedEmbedding.length); // Try different query variations based on common table structures let query, rows; // First try: Original query structure try { query = ` SELECT vec_sentences.id, distance, text FROM vec_sentences LEFT JOIN sentences ON sentences.id = vec_sentences.id WHERE sentence_embedding MATCH ?1 AND k = ?2 ORDER BY distance `; rows = db.selectArrays(query, [serializedEmbedding, k]); console.log("Query successful:", rows); // Format results as bulleted text let formattedResults = ""; if (rows && rows.length > 0) { formattedResults = "**Search Results:**\n\n"; rows.forEach((row, index) => { // Assuming row structure: [id, distance, text] const text = row[2] || row.text || JSON.stringify(row); const distance = row[1] || row.distance || "N/A"; formattedResults += `**${index + 1}.** (Similarity: ${(1-distance).toFixed(3)})\n${text}\n\n`; }); } else { formattedResults = "No results found."; } return formattedResults; } catch (e1) { console.log("Query 1 failed:", e1.message); // Second try: Simplified query without JOIN try { query = ` SELECT * FROM vec_sentences WHERE sentence_embedding MATCH ?1 AND k = ?2 ORDER BY distance `; rows = db.selectArrays(query, [serializedEmbedding, k]); console.log("Query 2 successful:", rows); // Format results as bulleted text let formattedResults = ""; if (rows && rows.length > 0) { formattedResults = "**Search Results:**\n\n"; rows.forEach((row, index) => { // Try different possible text column positions const text = row[2] || row[1] || JSON.stringify(row); const distance = row[1] || row[0] || "N/A"; formattedResults += `**${index + 1}.** (Distance: ${distance})\n${text}\n\n`; }); } else { formattedResults = "No results found."; } return formattedResults; } catch (e2) { console.log("Query 2 failed:", e2.message); return `Error: Could not execute similarity search. ${e2.message}`; } } } catch (e) { console.error("Similarity search error:", e); return `Error: ${e.message}`; } }; // Define regular search function for fallback self.search = async function(query) { try { const tables = db.selectArrays("SELECT name FROM sqlite_master WHERE type='table'"); console.log("Tables available for query:", tables); const rows = db.selectArrays(query); console.log("Query results:", rows); return JSON.stringify(rows); } catch (e) { console.error("Query error:", e); return `Error: ${e.message}`; } }; } // Run setup and catch errors setup().catch(error => { console.error("Setup error:", error); }); ''') # Wait until functions are defined async def wait_for_functions(): while not hasattr(js, "similaritySearch") or not hasattr(js, "search"): print("Waiting for search functions to be available...") await asyncio.sleep(0.1) return js.similaritySearch, js.search # Python serialization function def serialize(vector: List[float]) -> bytes: """serializes a list of floats into a compact "raw bytes" format""" return struct.pack("%sf" % len(vector), *vector) # Function to get embeddings for text async def get_embeddings(text): if not text.strip(): return [] extractor = await get_extractor() output = await extractor([text], pooling='mean', normalize=True) # Convert the TjsTensorProxy object to a list embedding = output[0].tolist() return embedding # Similarity search function async def similarity_search_function(query, k=5): try: # Get embedding for the query print(f"Getting embeddings for query: {query}") query_embedding = await get_embeddings(query) if not query_embedding: return "Failed to generate embeddings for the query." print(f"Query embedding length: {len(query_embedding)}") # Serialize the embedding in Python serialized_embedding = serialize(query_embedding) print(f"Serialized embedding size: {len(serialized_embedding)} bytes") # Convert bytes to list of integers for JavaScript embedding_bytes = list(serialized_embedding) print(f"Converted to {len(embedding_bytes)} byte values") # Wait for search functions to be available similarity_search_fn, _ = await wait_for_functions() # Perform similarity search with serialized embedding as byte array result = await similarity_search_fn(embedding_bytes, k) return result except Exception as e: return f"Error in similarity search: {str(e)}" # Regular SQL search function for fallback async def sql_search_function(query): try: _, search_fn = await wait_for_functions() result = await search_fn(query) data = json.loads(result) # Convert JSON string to Python object df = pd.DataFrame(data) # Create DataFrame from the data return df except Exception as e: return f"Error in SQL search: {str(e)}" async def respond(message, history): if history is None: history = [] messages = [] for turn in history: if isinstance(turn, (list, tuple)) and len(turn) >= 2: (user_msg, assistant_msg) = turn messages.append({'role': 'user', 'content': user_msg}) messages.append({'role': 'assistant', 'content': assistant_msg}) messages.append({'role': 'user', 'content': message}) # Perform similarity search to get top 5 similar matches context = await similarity_search_function(message, k=5) # Append the context to the system prompt system_prompt = f"You are an AI assistant. Use the following context to answer the user's question.\n\n{context}" messages.insert(0, {'role': 'system', 'content': system_prompt}) payload = {'model': 'gemma3:27b', 'messages': messages, 'stream': True} try: import js fetch_promise = js.fetch('https://ollama.beast.local/api/chat', js.Object.fromEntries(js.Array.of(js.Array.of('method', 'POST'), js.Array.of('headers', js.Object.fromEntries(js.Array.of(js.Array.of('Content-Type', 'application/json')))), js.Array.of('body', json.dumps(payload))))) response = await fetch_promise if not response.ok: error_text = await response.text() yield f'Error {response.status}: {error_text}' return reader = response.body.getReader() decoder = js.TextDecoder.new('utf-8') buffer = '' full_text = '' while True: result = await reader.read() done = result.done if result.value: chunk = decoder.decode(result.value, {'stream': True}) buffer += chunk while '\n' in buffer: (line, buffer) = buffer.split('\n', 1) if not line.strip(): continue try: data = json.loads(line) if 'message' in data and 'content' in data['message']: content = data['message']['content'] full_text += content yield full_text except json.JSONDecodeError: pass if done: if buffer.strip(): try: data = json.loads(buffer) if 'message' in data and 'content' in data['message']: content = data['message']['content'] full_text += content yield full_text except json.JSONDecodeError: pass break if not full_text: yield 'No response generated.' except Exception as e: yield f'Error: {e}' import gradio as gr from chat_response import respond, similarity_search_function, sql_search_function with gr.Blocks() as demo: gr.Markdown("# Chat Apps with Memex Portal") with gr.Tabs(): with gr.Tab("AgriBot"): gr.Markdown("Chat with Agri Data") gr.ChatInterface(fn=respond, type='messages', chatbot=gr.Chatbot(), examples=['Irrigation recommendation', 'Weed Management', 'General Information Agriculture', 'Tell me a story'], title='Chat Apps with Memex Portal', description='Chat Template for creating GenAI applications with Memex Portal', flagging_mode="manual", flagging_options=["Like", "Spam", "Inappropriate", "Other"], save_history=True) with gr.Tab("Similarity Search"): gr.Markdown("Enter a natural language query to find similar content using embeddings.") with gr.Row(): query_input = gr.Textbox( label="Search Query", placeholder="What are some control measures for maida?", lines=2 ) k_input = gr.Number( label="Number of Results (k)", value=5, minimum=1, maximum=20 ) search_btn = gr.Button("Search", variant="primary") similarity_output = gr.Textbox( label="Search Results", lines=10, max_lines=20 ) search_btn.click( similarity_search_function, inputs=[query_input, k_input], outputs=similarity_output ) with gr.Tab("SQL Query"): gr.Markdown("Execute raw SQL queries on the database.") sql_input = gr.Textbox( label="SQL Query", placeholder="SELECT * FROM sentences LIMIT 10;", lines=3 ) sql_btn = gr.Button("Execute", variant="secondary") sql_output = gr.Dataframe(label="Query Results") sql_btn.click( sql_search_function, inputs=sql_input, outputs=sql_output ) demo.launch() transformers-js-py