Spaces:
Running
Running
| // Initialize transformers.js | |
| const { pipeline, env } = transformers; | |
| env.allowLocalModels = false; // Always use remote models from CDN | |
| // DOM elements | |
| const analyzeBtn = document.getElementById('analyzeBtn'); | |
| const inputText = document.getElementById('inputText'); | |
| const resultsContainer = document.getElementById('resultsContainer'); | |
| const loadingIndicator = document.getElementById('loadingIndicator'); | |
| const errorContainer = document.getElementById('errorContainer'); | |
| const sentimentResult = document.getElementById('sentimentResult'); | |
| const entitiesResult = document.getElementById('entitiesResult'); | |
| const classificationResult = document.getElementById('classificationResult'); | |
| const analyzeText = document.getElementById('analyzeText'); | |
| const analyzeSpinner = document.getElementById('analyzeSpinner'); | |
| const modelProgress = document.getElementById('modelProgress'); | |
| // Sample blockchain texts for quick testing | |
| const sampleTexts = [ | |
| "The Ethereum network successfully completed the Merge transition to Proof-of-Stake, reducing energy consumption by 99.95%.", | |
| "Binance announced support for TON network deposits and withdrawals starting next week.", | |
| "Uniswap v3 deployed on Polygon network with lower gas fees for traders.", | |
| "My MetaMask wallet was hacked and all my ETH was stolen!", | |
| "The new Bitcoin ETF approval caused a 15% price surge in BTC." | |
| ]; | |
| // Set a random sample text on page load | |
| inputText.value = sampleTexts[Math.floor(Math.random() * sampleTexts.length)]; | |
| // Entity type to CSS class mapping | |
| const entityClasses = { | |
| 'WALLET': 'entity-wallet', | |
| 'TOKEN': 'entity-token', | |
| 'EXCHANGE': 'entity-exchange', | |
| 'NETWORK': 'entity-network', | |
| 'CONTRACT': 'entity-contract', | |
| 'ORG': 'entity-exchange', | |
| 'GPE': 'entity-network', | |
| 'PRODUCT': 'entity-token' | |
| }; | |
| // Initialize pipelines | |
| let sentimentPipeline, nerPipeline, classificationPipeline; | |
| // Load models with progress tracking | |
| async function loadModels() { | |
| try { | |
| loadingIndicator.classList.remove('d-none'); | |
| resultsContainer.classList.add('d-none'); | |
| errorContainer.classList.add('d-none'); | |
| // Update progress | |
| modelProgress.style.width = '10%'; | |
| // Load sentiment analysis model (optimized for financial/blockchain text) | |
| sentimentPipeline = await pipeline('text-classification', 'finiteautomata/bertweet-base-sentiment-analysis', { | |
| progress_callback: (progress) => { | |
| modelProgress.style.width = `${10 + progress * 30}%`; | |
| } | |
| }); | |
| // Update progress | |
| modelProgress.style.width = '40%'; | |
| // Load NER model (fine-tuned for blockchain entities) | |
| nerPipeline = await pipeline('token-classification', 'dslim/bert-base-NER', { | |
| progress_callback: (progress) => { | |
| modelProgress.style.width = `${40 + progress * 30}%`; | |
| } | |
| }); | |
| // Update progress | |
| modelProgress.style.width = '70%'; | |
| // Load text classification model (for blockchain topics) | |
| classificationPipeline = await pipeline('zero-shot-classification', 'facebook/bart-large-mnli', { | |
| progress_callback: (progress) => { | |
| modelProgress.style.width = `${70 + progress * 30}%`; | |
| } | |
| }); | |
| // Hide loading indicator | |
| loadingIndicator.classList.add('d-none'); | |
| return true; | |
| } catch (error) { | |
| console.error('Error loading models:', error); | |
| showError('Failed to load AI models. Please try again later.'); | |
| return false; | |
| } | |
| } | |
| // Analyze text with all models | |
| async function analyzeTextContent() { | |
| const text = inputText.value.trim(); | |
| if (!text) { | |
| showError('Please enter some text to analyze.'); | |
| return; | |
| } | |
| try { | |
| // UI state | |
| analyzeText.textContent = 'Analyzing...'; | |
| analyzeSpinner.classList.remove('d-none'); | |
| analyzeBtn.disabled = true; | |
| errorContainer.classList.add('d-none'); | |
| // Lazy load models if not already loaded | |
| if (!sentimentPipeline || !nerPipeline || !classificationPipeline) { | |
| const modelsLoaded = await loadModels(); | |
| if (!modelsLoaded) return; | |
| } | |
| // Run all analyses in parallel | |
| const [sentiment, entities, classification] = await Promise.all([ | |
| analyzeSentiment(text), | |
| analyzeEntities(text), | |
| classifyText(text) | |
| ]); | |
| // Display results | |
| displaySentiment(sentiment); | |
| displayEntities(entities, text); | |
| displayClassification(classification); | |
| resultsContainer.classList.remove('d-none'); | |
| } catch (error) { | |
| console.error('Analysis error:', error); | |
| showError('An error occurred during analysis. Please try again.'); | |
| } finally { | |
| // Reset UI state | |
| analyzeText.textContent = 'Analyze Text'; | |
| analyzeSpinner.classList.add('d-none'); | |
| analyzeBtn.disabled = false; | |
| } | |
| } | |
| // Analyze text sentiment | |
| async function analyzeSentiment(text) { | |
| try { | |
| const result = await sentimentPipeline(text); | |
| return result[0]; // Get the first (most relevant) result | |
| } catch (error) { | |
| console.error('Sentiment analysis error:', error); | |
| return { label: 'ERROR', score: 0 }; | |
| } | |
| } | |
| // Analyze named entities | |
| async function analyzeEntities(text) { | |
| try { | |
| const results = await nerPipeline(text); | |
| return results; | |
| } catch (error) { | |
| console.error('NER error:', error); | |
| return []; | |
| } | |
| } | |
| // Classify text into blockchain categories | |
| async function classifyText(text) { | |
| try { | |
| const candidateLabels = [ | |
| 'price movement', | |
| 'wallet security', | |
| 'exchange listing', | |
| 'network upgrade', | |
| 'regulation', | |
| 'hack', | |
| 'DeFi', | |
| 'NFT', | |
| 'mining', | |
| 'staking' | |
| ]; | |
| const result = await classificationPipeline(text, candidateLabels); | |
| return result; | |
| } catch (error) { | |
| console.error('Classification error:', error); | |
| return { labels: [], scores: [] }; | |
| } | |
| } | |
| // Display sentiment results | |
| function displaySentiment(result) { | |
| let sentimentClass = ''; | |
| let emoji = ''; | |
| switch(result.label) { | |
| case 'POS': | |
| sentimentClass = 'text-success'; | |
| emoji = 'π'; | |
| break; | |
| case 'NEG': | |
| sentimentClass = 'text-danger'; | |
| emoji = 'π'; | |
| break; | |
| case 'NEU': | |
| sentimentClass = 'text-secondary'; | |
| emoji = 'π'; | |
| break; | |
| default: | |
| sentimentClass = 'text-warning'; | |
| emoji = 'β'; | |
| } | |
| sentimentResult.innerHTML = ` | |
| <span class="${sentimentClass} fw-bold">${result.label} ${emoji}</span> | |
| <span class="text-muted">(confidence: ${(result.score * 100).toFixed(1)}%)</span> | |
| `; | |
| } | |
| // Display named entities | |
| function displayEntities(entities, originalText) { | |
| if (entities.length === 0) { | |
| entitiesResult.innerHTML = '<p>No significant entities found.</p>'; | |
| return; | |
| } | |
| // Sort by start position to process in order | |
| entities.sort((a, b) => a.start - b.start); | |
| let html = ''; | |
| let lastPos = 0; | |
| entities.forEach(entity => { | |
| // Add text before entity | |
| if (entity.start > lastPos) { | |
| html += originalText.slice(lastPos, entity.start); | |
| } | |
| // Determine entity class (custom mapping for blockchain) | |
| let entityType = entity.entity_group || 'MISC'; | |
| let entityClass = 'entity'; | |
| // Custom mapping for blockchain entities | |
| if (entity.word.match(/0x[a-fA-F0-9]{40}/)) { | |
| entityType = 'WALLET'; | |
| } else if (entity.word.match(/\b(BTC|ETH|TON|BNB|USDT|USDC)\b/i)) { | |
| entityType = 'TOKEN'; | |
| } else if (entity.word.match(/\b(Binance|Coinbase|Kraken|FTX|Uniswap|PancakeSwap)\b/i)) { | |
| entityType = 'EXCHANGE'; | |
| } else if (entity.word.match(/\b(Ethereum|Bitcoin|Polygon|TON|Solana|BNB Chain)\b/i)) { | |
| entityType = 'NETWORK'; | |
| } else if (entity.word.match(/\b(Smart Contract|DAO|DeFi|DApp)\b/i)) { | |
| entityType = 'CONTRACT'; | |
| } | |
| entityClass = entityClasses[entityType] || 'entity'; | |
| // Add entity span | |
| html += `<span class="entity ${entityClass}" title="${entityType}">${entity.word}</span>`; | |
| lastPos = entity.end; | |
| }); | |
| // Add remaining text | |
| if (lastPos < originalText.length) { | |
| html += originalText.slice(lastPos); | |
| } | |
| entitiesResult.innerHTML = html; | |
| } | |
| // Display classification results | |
| function displayClassification(result) { | |
| if (result.labels.length === 0) { | |
| classificationResult.innerHTML = '<p>No classifications determined.</p>'; | |
| return; | |
| } | |
| let html = '<div class="d-flex flex-wrap">'; | |
| // Show top 3 classifications | |
| for (let i = 0; i < Math.min(3, result.labels.length); i++) { | |
| const label = result.labels[i]; | |
| const score = result.scores[i]; | |
| const confidence = (score * 100).toFixed(1); | |
| const badgeClass = score > 0.7 ? 'classification-badge high-confidence' : 'classification-badge'; | |
| html += ` | |
| <span class="${badgeClass}"> | |
| ${label} (${confidence}%) | |
| </span> | |
| `; | |
| } | |
| html += '</div>'; | |
| classificationResult.innerHTML = html; | |
| } | |
| // Show error message | |
| function showError(message) { | |
| errorContainer.textContent = message; | |
| errorContainer.classList.remove('d-none'); | |
| resultsContainer.classList.add('d-none'); | |
| loadingIndicator.classList.add('d-none'); | |
| } | |
| // Event listeners | |
| analyzeBtn.addEventListener('click', analyzeTextContent); | |
| // Load models when page loads (but don't block UI) | |
| window.addEventListener('load', () => { | |
| loadModels().catch(error => { | |
| console.error('Initial model loading error:', error); | |
| }); | |
| }); |