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