abidlabs's picture
abidlabs HF Staff
add model
f36e14c
<script lang="ts">
import Dataframe from '@gradio/dataframe';
import { onMount } from 'svelte';
import { pipeline, TextStreamer } from "@huggingface/transformers";
// Global state to avoid reloading model
type LLMGlobal = { generator: any | null };
const g = globalThis as any;
let __LLM: LLMGlobal = g.__LLM || { generator: null };
g.__LLM = __LLM;
let isModelReady = !!__LLM.generator;
let modelStatus = __LLM.generator ? '✅ AI ready! Ask me anything about the data.' : 'Loading AI model...';
let loadingProgress = __LLM.generator ? 100 : 0;
// Sample data for the dataframe
let value: { data: string[][]; headers: string[] } = {
headers: ['Name', 'Age', 'City', 'Score', 'Status'],
data: [
['Alice Johnson', '28', 'New York', '92.5', 'Active'],
['Bob Smith', '34', 'Los Angeles', '87.3', 'Active'],
['Carol Davis', '29', 'Chicago', '95.1', 'Inactive'],
['David Wilson', '42', 'Houston', '78.9', 'Active'],
['Eva Brown', '31', 'Phoenix', '88.7', 'Active'],
['Frank Miller', '37', 'Philadelphia', '91.2', 'Inactive'],
['Grace Lee', '26', 'San Antonio', '94.8', 'Active'],
['Henry Taylor', '45', 'San Diego', '82.6', 'Active']
]
};
function handleChange(event: CustomEvent) {
value = event.detail;
}
let query = 'How old is Alice?';
let response = '';
let isSearching = false;
onMount(async () => {
if (__LLM.generator) return; // Already loaded
try {
modelStatus = '📦 Loading model components...';
loadingProgress = 10;
const generator = await pipeline("text-generation", "onnx-community/gemma-3-270m-it-ONNX", {
dtype: "fp32",
device: "webgpu",
progress_callback: (item) => {
if (item.status === "progress" && item.file?.endsWith?.("onnx_data")) {
loadingProgress = Math.min(100, item.progress || 0);
modelStatus = `🚀 Loading model... ${Math.round(loadingProgress)}%`;
}
},
});
__LLM.generator = generator;
isModelReady = true;
loadingProgress = 100;
modelStatus = '✅ AI ready! Ask me anything about the data.';
console.log('Gemma-3 model loaded successfully');
} catch (error) {
console.error('Failed to initialize model:', error);
modelStatus = '⚠️ Failed to load AI model. Using fallback responses.';
isModelReady = false;
loadingProgress = 0;
}
});
async function searchData() {
if (!query.trim()) return;
isSearching = true;
response = '';
if (!isModelReady || !__LLM.generator) {
response = '⏳ AI model is still initializing. Please wait...';
isSearching = false;
return;
}
const userMessage = query.trim();
try {
response = '🤔 Thinking...';
await new Promise(resolve => requestAnimationFrame(resolve));
const systemPrompt = `You are a helpful AI assistant that answers questions about employee data. Here's the current data:
${value.headers.join(' | ')}
${value.data.map(row => row.join(' | ')).join('\n')}
Keep your responses concise and helpful. Answer questions about the data above. Use emojis to make responses engaging.`;
const messages = [
{ role: "system", content: systemPrompt },
{ role: "user", content: userMessage }
];
let fullResponse = '';
const streamer = new TextStreamer(__LLM.generator.tokenizer, {
skip_prompt: true,
skip_special_tokens: true,
callback_function: (token: string) => {
fullResponse += token;
response = fullResponse;
},
});
await __LLM.generator(messages, {
max_new_tokens: 256,
do_sample: false,
streamer,
});
response = fullResponse || 'Sorry, I couldn\'t generate a response.';
} catch (error) {
console.error('Generation error:', error);
// Fallback to smart responses based on query analysis
const lowerQuery = userMessage.toLowerCase();
if (lowerQuery.includes('how many') || lowerQuery.includes('count')) {
if (lowerQuery.includes('active')) {
const activeCount = value.data.filter(row => row[4] === 'Active').length;
response = `📊 There are ${activeCount} active users in the dataset.`;
} else if (lowerQuery.includes('inactive')) {
const inactiveCount = value.data.filter(row => row[4] === 'Inactive').length;
response = `📊 There are ${inactiveCount} inactive users in the dataset.`;
} else {
response = `📊 There are ${value.data.length} total records in the dataset.`;
}
} else if (lowerQuery.includes('highest score') || lowerQuery.includes('best score')) {
const scores = value.data.map(row => parseFloat(row[3]));
const maxScore = Math.max(...scores);
const topUser = value.data.find(row => parseFloat(row[3]) === maxScore);
response = `🏆 ${topUser?.[0]} has the highest score of ${maxScore} from ${topUser?.[2]}.`;
} else if (lowerQuery.includes('lowest score') || lowerQuery.includes('worst score')) {
const scores = value.data.map(row => parseFloat(row[3]));
const minScore = Math.min(...scores);
const bottomUser = value.data.find(row => parseFloat(row[3]) === minScore);
response = `📉 ${bottomUser?.[0]} has the lowest score of ${minScore} from ${bottomUser?.[2]}.`;
} else if (lowerQuery.includes('average age') || lowerQuery.includes('mean age')) {
const ages = value.data.map(row => parseInt(row[1]));
const avgAge = (ages.reduce((sum, age) => sum + age, 0) / ages.length).toFixed(1);
response = `👥 The average age of users is ${avgAge} years old.`;
} else {
response = `⚠️ Sorry, I encountered an error processing your request. Please try a different question about the data.`;
}
} finally {
isSearching = false;
}
}
function handleKeyPress(event: KeyboardEvent) {
if (event.key === 'Enter') {
searchData();
}
}
</script>
<div class="fullscreen-dataframe">
<div class="dataframe-container">
<Dataframe
bind:value={value}
show_row_numbers={true}
editable={true}
on:change={handleChange}
/>
</div>
<div class="search-panel">
<div class="search-container">
<div class="search-header">
<h2>✨ Ask me anything about your data</h2>
<p>Try: "How old is Alice?"</p>
<div class="built-with">
❤︎ &nbsp; Built with Gemma 3 270M Onnx & <a href="https://www.gradio.app/docs/js/dataframe" target="_blank" rel="noopener noreferrer">Gradio's Dataframe.js</a>
</div>
</div>
<div class="search-input-container">
{#if !isModelReady}
<div class="progress-container">
<div class="progress-bar">
<div class="progress-fill" style="width: {loadingProgress}%"></div>
</div>
<div class="progress-text">{modelStatus}</div>
</div>
{:else}
<input
type="text"
bind:value={query}
on:keypress={handleKeyPress}
class="search-input"
disabled={isSearching}
/>
<button
class="search-button"
on:click={searchData}
disabled={isSearching || !query.trim()}
>
{#if isSearching}
<div class="spinner"></div>
{:else}
🔍 Ask
{/if}
</button>
{/if}
</div>
{#if response}
<div class="response-container">
<div class="response-text">
{response}
</div>
</div>
{/if}
</div>
</div>
</div>
<style>
:global(*) {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:global(body, html) {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
}
.fullscreen-dataframe {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
background: white;
display: flex;
flex-direction: column;
}
.dataframe-container {
flex: 1;
overflow: hidden;
}
.search-panel {
background: linear-gradient(135deg, #ff6b35 0%, #1a1a1a 100%);
padding: 2rem;
box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.1);
}
.search-container {
max-width: 800px;
margin: 0 auto;
}
.search-header {
text-align: center;
margin-bottom: 1.5rem;
}
.search-header h2 {
color: white;
font-size: 1.8rem;
font-weight: 700;
margin: 0 0 0.5rem 0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.search-header p {
color: rgba(255, 255, 255, 0.9);
font-size: 1rem;
margin: 0;
opacity: 0.9;
}
.built-with {
color: rgba(255, 255, 255, 0.7);
font-size: 0.85rem;
margin-top: 0.5rem;
font-style: italic;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.built-with a {
color: rgba(255, 255, 255, 0.9);
text-decoration: underline;
text-decoration-color: rgba(255, 255, 255, 0.5);
transition: all 0.2s ease;
}
.built-with a:hover {
color: white;
text-decoration-color: white;
}
.search-input-container {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.progress-container {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.progress-bar {
height: 60px;
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50px;
overflow: hidden;
backdrop-filter: blur(10px);
position: relative;
padding: 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #ff6b35, rgba(255, 255, 255, 0.8));
border-radius: 0;
transition: width 0.5s ease-out;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
}
.progress-fill::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
animation: shimmer 2s infinite;
}
.progress-text {
color: white;
text-align: center;
font-weight: 600;
font-size: 1rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.search-input {
flex: 1;
padding: 1rem 1.5rem;
border: none;
border-radius: 50px;
font-size: 1.1rem;
background: white;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
outline: none;
transition: all 0.3s ease;
}
.search-input:focus {
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.search-input:disabled {
opacity: 0.7;
}
.search-button {
padding: 1rem 2rem;
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50px;
color: white;
font-weight: 600;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
display: flex;
align-items: center;
gap: 0.5rem;
}
.search-button:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.3);
border-color: rgba(255, 255, 255, 0.5);
transform: translateY(-2px);
}
.search-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.response-container {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 1.5rem 2rem;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
animation: slideUp 0.5s ease-out;
}
.response-text {
font-size: 1.1rem;
line-height: 1.6;
color: #2d3748;
margin: 0;
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top: 2px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes shimmer {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
</style>