Spaces:
Running
Running
| <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"> | |
| ❤︎ 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> |