1/** 2 * muse-search.js v1 â Muse AI Search Assistant for Splurjj.com 3 * ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 4 * Muse AI is trained EXCLUSIVELY on Splurjj.com content: 5 * - GoalGraph knowledge graph (gg_entities, gg_milestones) 6 * - Splurjj brand directory (dir_brands, dir_products) 7 * - Splurjj editorial articles (cms_articles) 8 * - Goals database (dir_goals) 9 * - Local BRAND_DATABASE (10 brand-data files) 10 * 11 * Muse AI answers questions about Splurjj content ONLY. 12 * It does NOT answer general web queries or knowledge outside Splurjj. 13 * 14 * Architecture: 15 * - query() â finds relevant content from Splurjj sources â builds context â returns AI answer 16 * - getContext() â returns structured context about a query for display 17 * - suggest() â smart suggestions based on Splurjj content 18 * - chat() â conversational interface grounded in Splurjj data 19 * 20 * Public API: window.MuseSearch 21 * .query(q) â Promise<{answer, sources, confidence}> 22 * .getContext(q, results) â Promise<{summary, suggestions, relatedTopics}> 23 * .suggest(q) â Promise<string[]> â smart search suggestions 24 * .chat(messages) â Promise<{reply, sources}> 25 * .askAboutBrand(name) â Promise<{brand, answer}> 26 * .askAboutPerson(name) â Promise<{entity, answer}> 27 * .askAboutGoal(title) â Promise<{goal, answer}> 28 * ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 29 */ 30window.MuseSearch = (function () { 31 'use strict'; 32 33 var SB_URL = 'https://mpwvgqeqemdnipoogccv.supabase.co'; 34 var SB_ANON = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1wd3ZncWVxZW1kbmlwb29nY2N2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzgxOTE4NzcsImV4cCI6MjA5Mzc2Nzg3N30.8bvaKPS3fXGjJH2XsUY9eHn7EIHxzLkhZKCKk7qCYKQ'; 35 36 /* ââ Supabase fetch âââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 37 async function sbGet(table, qs, limit) { 38 try { 39 var url = SB_URL + '/rest/v1/' + table + '?' + qs + '&limit=' + (limit || 10); 40 var r = await fetch(url, { 41 headers: { 'apikey': SB_ANON, 'Authorization': 'Bearer ' + SB_ANON, 'Accept': 'application/json' } 42 }); 43 if (!r.ok) return []; 44 return await r.json(); 45 } catch (e) { return []; } 46 } 47 48 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 49 * SPLURJJ KNOWLEDGE RETRIEVAL 50 * Gathers structured facts from all Splurjj sources 51 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 52 async function retrieveSplurjjKnowledge(q) { 53 var enc = encodeURIComponent(q); 54 var knowledge = { brands: [], entities: [], articles: [], goals: [], products: [] }; 55 56 var [brands, entities, articles, goals, products] = await Promise.allSettled([ 57 sbGet('dir_brands', 58 'brand_name=ilike.*' + enc + '*&select=id,brand_name,category,subcategory,founder,description,headquarters,website,tier,verification_status,trust_score', 59 5), 60 sbGet('gg_entities', 61 'or=(name.ilike.*' + enc + '*,summary.ilike.*' + enc + '*,known_for.ilike.*' + enc + '*)&status=eq.active&select=id,name,type,category,summary,known_for,nationality,slug,trust_score,is_verified', 62 8), 63 sbGet('cms_articles', 64 'title=ilike.*' + enc + '*&select=id,title,subtitle,category_name,author_name,slug,published_at', 65 5), 66 sbGet('dir_goals', 67 'or=(goal_title.ilike.*' + enc + '*,description.ilike.*' + enc + '*)&select=*', 68 5), 69 sbGet('dir_products', 70 'product_name=ilike.*' + enc + '*&select=*', 71 5) 72 ]); 73 74 knowledge.brands = brands.status === 'fulfilled' ? brands.value : []; 75 knowledge.entities = entities.status === 'fulfilled' ? entities.value : []; 76 knowledge.articles = articles.status === 'fulfilled' ? articles.value : []; 77 knowledge.goals = goals.status === 'fulfilled' ? goals.value : []; 78 knowledge.products = products.status === 'fulfilled' ? products.value : []; 79 80 // Also check local BRAND_DATABASE 81 if (typeof BRAND_DATABASE !== 'undefined' && knowledge.brands.length < 3) { 82 var qLow = q.toLowerCase();
83 Object.values(BRAND_DATABASE).forEach(function(cat) { 84 (cat.brands || []).forEach(function(b) { 85 if ((b.name || '').toLowerCase().indexOf(qLow) !== -1) { 86 knowledge.brands.push({ 87 brand_name: b.name, 88 category: cat.label || '', 89 website: b.website || '', 90 tier: b.tier || 2, 91 description: b.description || '', 92 _src: 'local' 93 }); 94 } 95 }); 96 }); 97 knowledge.brands = knowledge.brands.slice(0, 5); 98 } 99 100 return knowledge; 101 } 102 103 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 104 * KNOWLEDGE FORMATTER 105 * Converts raw Supabase data into readable context strings 106 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 107 function formatKnowledge(k) { 108 var parts = []; 109 110 if (k.entities && k.entities.length) { 111 k.entities.forEach(function(e) { 112 var line = '⢠' + e.name + (e.type ? ' [' + e.type + ']' : ''); 113 if (e.category) line += ' â ' + e.category; 114 if (e.known_for) line += '. Known for: ' + e.known_for; 115 if (e.summary) line += '. ' + e.summary.substring(0, 150); 116 if (e.nationality) line += ' (' + e.nationality + ')'; 117 parts.push(line); 118 }); 119 } 120 121 if (k.brands && k.brands.length) { 122 k.brands.forEach(function(b) { 123 var line = '⢠' + b.brand_name + ' [brand]'; 124 if (b.category) line += ' â ' + b.category; 125 if (b.founder) line += '. Founded by ' + b.founder; 126 if (b.headquarters) line += '. HQ: ' + b.headquarters; 127 if (b.description) line += '. ' + b.description.substring(0, 100); 128 parts.push(line); 129 }); 130 } 131 132 if (k.articles && k.articles.length) { 133 k.articles.forEach(function(a) { 134 var line = '⢠Article: "' + a.title + '"'; 135 if (a.subtitle) line += ' â ' + a.subtitle; 136 if (a.category_name) line += ' [' + a.category_name + ']'; 137 if (a.author_name) line += ' by ' + a.author_name; 138 parts.push(line); 139 }); 140 } 141 142 if (k.goals && k.goals.length) { 143 k.goals.forEach(function(g) { 144 var line = '⢠Goal: "' + (g.goal_title || g.title || '') + '"'; 145 if (g.description) line += ' â ' + (g.description || '').substring(0, 100); 146 parts.push(line); 147 }); 148 } 149 150 if (k.products && k.products.length) { 151 k.products.forEach(function(p) { 152 var line = '⢠Product: ' + (p.product_name || p.name); 153 if (p.brand_name) line += ' by ' + p.brand_name; 154 parts.push(line); 155 }); 156 } 157 158 return parts.join('\n'); 159 } 160 161 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 162 * MUSE AI RESPONSE ENGINE 163 * Generates contextual answers grounded in Splurjj data 164 * Uses rule-based AI (no external LLM needed â Splurjj data is the model) 165 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 166 function generateMuseAnswer(q, knowledge) { 167 var qLow = q.toLowerCase().trim(); 168 var sources = []; 169 var answer = ''; 170 var confidence = 0; 171 172 // Determine query intent 173 var isWhatIs = /^what (is|are|was|were)\b/i.test(q); 174 var isWhoIs = /^who (is|was|are|were)\b/i.test(q); 175 var isFindBrand = /brand|company|companies/i.test(q); 176 var isArticle = /article|read|blog|news|story/i.test(q); 177 var isGoal = /goal|achieve|how to|want to/i.test(q); 178 179 // Priority: GoalGraph entities (richest data) 180 if (knowledge.entities && knowledge.entities.length) { 181 var top = knowledge.entities[0]; 182 confidence = Math.min(95, 60 + (knowledge.entities.length * 5)); 183 184 if (isWhoIs && top.type === 'person') { 185 answer = top.name + ' is a ' + (top.nationality ? top.nationality + ' ' : '') + (top.category || 'notable person'); 186 if (top.known_for) answer += ' known for ' + top.known_for; 187 if (top.summary) answer += '. ' + top.summary.substring(0, 200); 188 } else if (isWhatIs && top.type === 'company') { 189 answer = top.name + ' is ' + (top.category ? 'a ' + top.category + ' company' : 'a company'); 190 if (top.known_for) answer += ', known for ' + top.known_for; 191 if (top.summary) answer += '. ' + top.summary.substring(0, 200); 192 } else if (top.summary) { 193 answer = top.name + ': ' + top.summary.substring(0, 250); 194 } else { 195 answer = 'Found "' + top.name + '" in the GoalGraph â ' + (top.category || top.type || 'entity') + (top.known_for ? ', known for ' + top.known_for : ''); 196 } 197
198 knowledge.entities.forEach(function(e) { 199 sources.push({ type: 'goalgraph', label: e.name, url: 'goalgraph-entity.html?id=' + e.id }); 200 }); 201 } 202 203 // Add brand context 204 if (knowledge.brands && knowledge.brands.length) { 205 var topBrand = knowledge.brands[0]; 206 if (!answer) { 207 answer = topBrand.brand_name + ' is ' + (topBrand.category ? 'a ' + topBrand.category + ' brand' : 'a brand'); 208 if (topBrand.founder) answer += ', founded by ' + topBrand.founder; 209 if (topBrand.headquarters) answer += ', headquartered in ' + topBrand.headquarters; 210 if (topBrand.description) answer += '. ' + topBrand.description.substring(0, 200); 211 confidence = Math.max(confidence, 75); 212 } else if (knowledge.brands.length > 0) { 213 answer += '\n\nRelated brands: ' + knowledge.brands.slice(0, 3).map(function(b){ return b.brand_name; }).join(', '); 214 } 215 knowledge.brands.forEach(function(b) { 216 sources.push({ type: 'brand', label: b.brand_name, url: b.website || 'brand-profile.html?id=' + (b.id || b.splurjj_id || '') }); 217 }); 218 } 219 220 // Add article context 221 if (knowledge.articles && knowledge.articles.length) { 222 if (!answer) { 223 var art = knowledge.articles[0]; 224 answer = 'Found an article about this on Splurjj: "' + art.title + '"'; 225 if (art.subtitle) answer += ' â ' + art.subtitle; 226 confidence = Math.max(confidence, 65); 227 } else { 228 answer += '\n\nRelated articles on Splurjj: ' + knowledge.articles.slice(0, 2).map(function(a){ return '"' + a.title + '"'; }).join(', '); 229 } 230 knowledge.articles.forEach(function(a) { 231 sources.push({ type: 'article', label: a.title, url: 'article.html?slug=' + a.slug }); 232 }); 233 } 234 235 // Add goal context 236 if (knowledge.goals && knowledge.goals.length) { 237 var topGoal = knowledge.goals[0]; 238 if (!answer) { 239 answer = 'Goal found on Splurjj: "' + (topGoal.goal_title || topGoal.title) + '"'; 240 if (topGoal.description) answer += '. ' + topGoal.description.substring(0, 150); 241 confidence = Math.max(confidence, 70); 242 } 243 knowledge.goals.forEach(function(g) { 244 sources.push({ type: 'goal', label: g.goal_title || g.title, url: 'goals.html?id=' + g.id }); 245 }); 246 } 247 248 // Fallback: nothing found on Splurjj 249 if (!answer) { 250 answer = 'I couldn\'t find specific information about "' + q + '" in Splurjj\'s database. Try browsing the GoalGraph or searching by category.'; 251 confidence = 10; 252 } 253 254 return { 255 answer: answer, 256 sources: sources, 257 confidence: confidence, 258 knowledge: knowledge 259 }; 260 } 261 262 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 263 * QUERY â main entry point 264 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 265 async function query(q) { 266 if (!q || !q.trim()) return { answer: '', sources: [], confidence: 0 }; 267 var knowledge = await retrieveSplurjjKnowledge(q.trim()); 268 return generateMuseAnswer(q.trim(), knowledge); 269 } 270 271 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 272 * GET CONTEXT â returns structured context to enrich search results 273 * Suggestions are REAL entity/brand/article names from Splurjj data, 274 * NOT raw category strings. 275 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 276 async function getContext(q, topResults) { 277 if (!q) return null; 278 topResults = topResults || []; 279 280 // Build related topics from actual result types (not category text) 281 var typeCounts = {};
282 topResults.forEach(function(r) { 283 var t = r._type || ''; 284 if (t) typeCounts[t] = (typeCounts[t] || 0) + 1; 285 }); 286 var relatedTopics = Object.keys(typeCounts) 287 .sort(function(a,b){ return typeCounts[b] - typeCounts[a]; }) 288 .slice(0, 5); 289 290 // Smart suggestions: real names from results + Supabase 291 var suggestions = await buildSuggestions(q, topResults); 292 293 // Summary built from actual top result names 294 var summary = ''; 295 if (topResults.length === 0) { 296 summary = 'No results found for "' + q + '" â try a broader term or browse by category.'; 297 } else { 298 var topName = topResults[0].brand_name || topResults[0].name || topResults[0].title || topResults[0].goal_title || q; 299 summary = topResults.length + ' result' + (topResults.length !== 1 ? 's' : '') + ' for "' + q + '"'; 300 if (topName && topName.toLowerCase() !== q.toLowerCase()) summary += ' â top: ' + topName; 301 if (relatedTopics.length > 1) summary += ' across ' + relatedTopics.slice(0, 3).join(', '); 302 } 303 304 return { 305 summary: summary, 306 suggestions: suggestions, 307 relatedTopics: relatedTopics, 308 resultCount: topResults.length 309 }; 310 } 311 312 /* ââ Smart suggestion builder â returns REAL names from Splurjj data ââââââââ */ 313 async function buildSuggestions(q, results) { 314 var suggestions = []; 315 var qLow = q.toLowerCase(); 316 var seen = {}; 317 seen[qLow] = true; // never suggest the exact same query 318 319 function add(text) { 320 if (!text || text.length < 2) return; 321 var key = text.toLowerCase(); 322 if (seen[key]) return; 323 seen[key] = true; 324 suggestions.push(text); 325 } 326 327 // 1. Names from top search results (entities, brands, articles, goals) 328 results.forEach(function(r) { 329 add(r.name || ''); 330 add(r.brand_name || ''); 331 add(r.title || ''); 332 add(r.goal_title || ''); 333 }); 334 335 // 2. Query Supabase for related names (starts-with fallback to contains) 336 var enc = encodeURIComponent(q); 337 try { 338 var [relBrands, relEntities, relArticles, relGoals] = await Promise.allSettled([ 339 sbGet('dir_brands', 340 'brand_name=ilike.' + enc + '*&select=brand_name,category', 6), 341 sbGet('gg_entities', 342 'name=ilike.' + enc + '*&status=eq.active&select=name,type,category', 6), 343 sbGet('cms_articles', 344 'title=ilike.*' + enc + '*&select=title,category_name&limit=4', 4), 345 sbGet('dir_goals', 346 'goal_title=ilike.*' + enc + '*&select=goal_title&limit=3', 3) 347 ]); 348 (relBrands.status === 'fulfilled' ? relBrands.value : []).forEach(function(b){ add(b.brand_name); }); 349 (relEntities.status === 'fulfilled' ? relEntities.value : []).forEach(function(e){ add(e.name); }); 350 (relArticles.status === 'fulfilled' ? relArticles.value : []).forEach(function(a){ add(a.title); }); 351 (relGoals.status === 'fulfilled' ? relGoals.value : []).forEach(function(g){ add(g.goal_title); }); 352 } catch(e) { /* silent â Supabase may not be reachable */ } 353 354 // 3. Contextual expansions for common themes â appended ONLY if < 4 suggestions so far 355 if (suggestions.length < 4) { 356 var expansions = { 357 'nike': ['Nike Air Max', 'Nike goals', 'Nike sportswear'], 358 'apple': ['Apple iPhone', 'Apple MacBook', 'Apple goals'], 359 'fashion': ['luxury fashion brands', 'streetwear brands', 'fashion goals'], 360 'tech': ['tech brands', 'big tech companies', 'tech skills'], 361 'music': ['music artists', 'music goals', 'music brands'], 362 'fitness': ['fitness goals', 'fitness brands', 'workout habits'], 363 'sneaker': ['sneaker brands', 'Jordan', 'Adidas'], 364 'car': ['luxury cars', 'electric cars', 'car brands'], 365 'style': ['style goals', 'luxury style', 'streetwear'], 366 'brand': ['luxury brands', 'tech brands', 'fashion brands'], 367 'goal': ['fitness goals', 'career goals', 'lifestyle goals'], 368 'book': ['must-read books', 'book goals', 'bestseller books'], 369 'film': ['must-watch films', 'film goals', 'award-winning films'], 370 'song': ['music goals', 'trending songs', 'music artists'], 371 'habit': ['daily habits', 'productivity habits', 'fitness habits'], 372 'skill': ['coding skills', 'leadership skills', 'creative skills'], 373 'location': ['travel goals', 'best cities', 'travel brands'] 374 };
375 Object.keys(expansions).forEach(function(keyword) { 376 if (qLow.indexOf(keyword) !== -1 || keyword.indexOf(qLow) !== -1) { 377 expansions[keyword].forEach(function(exp) { add(exp); }); 378 } 379 }); 380 } 381 382 // 4. KEYWORD_ALIASES expansions 383 if (window.SplurjjSearch && window.SplurjjSearch.KEYWORD_ALIASES) { 384 Object.keys(window.SplurjjSearch.KEYWORD_ALIASES).forEach(function(alias) { 385 if (alias !== qLow && (alias.indexOf(qLow) !== -1 || qLow.indexOf(alias) !== -1)) { 386 add(alias); 387 } 388 }); 389 } 390 391 return suggestions.slice(0, 6); 392 } 393 394 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 395 * SUGGEST â returns smart autocomplete suggestions based on Splurjj content 396 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 397 async function suggest(q) { 398 if (!q || q.length < 1) return []; 399 var enc = encodeURIComponent(q); 400 var [brands, entities] = await Promise.allSettled([ 401 sbGet('dir_brands', 'brand_name=ilike.' + enc + '*&select=brand_name,category', 5), 402 sbGet('gg_entities', 'name=ilike.' + enc + '*&status=eq.active&select=name,type,category', 5) 403 ]); 404 var suggestions = []; 405 var seen = {}; 406 407 (brands.status === 'fulfilled' ? brands.value : []).forEach(function(b) { 408 if (b.brand_name && !seen[b.brand_name]) { 409 seen[b.brand_name] = true; 410 suggestions.push({ text: b.brand_name, type: 'brand', sublabel: b.category || 'Brand' }); 411 } 412 }); 413 (entities.status === 'fulfilled' ? entities.value : []).forEach(function(e) { 414 if (e.name && !seen[e.name]) { 415 seen[e.name] = true; 416 suggestions.push({ text: e.name, type: e.type || 'entity', sublabel: e.category || e.type || '' }); 417 } 418 }); 419 420 return suggestions.slice(0, 10); 421 } 422 423 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 424 * CHAT â conversational AI grounded in Splurjj data 425 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 426 async function chat(messages) { 427 if (!messages || !messages.length) return { reply: '', sources: [] }; 428 429 // Get the latest user message 430 var lastMsg = messages[messages.length - 1]; 431 var userText = lastMsg.content || ''; 432 433 // Retrieve knowledge for this message 434 var knowledge = await retrieveSplurjjKnowledge(userText); 435 var result = generateMuseAnswer(userText, knowledge); 436 437 // Build conversational reply 438 var reply = result.answer; 439 440 // Add follow-up suggestion 441 if (result.sources && result.sources.length) { 442 reply += '\n\n_You can explore more on GoalGraph or the Splurjj Brand Directory._'; 443 } 444 445 return { 446 reply: reply, 447 sources: result.sources, 448 confidence: result.confidence, 449 knowledge: formatKnowledge(knowledge) 450 }; 451 } 452 453 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 454 * ASK ABOUT BRAND â deep dive into a brand from all Splurjj sources 455 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 456 async function askAboutBrand(brandName) { 457 var enc = encodeURIComponent(brandName); 458 var [dirBrand, ggEntity] = await Promise.allSettled([ 459 sbGet('dir_brands', 'brand_name=ilike.*' + enc + '*&select=*', 1), 460 sbGet('gg_entities', 'name=ilike.*' + enc + '*&type=eq.company&status=eq.active&select=*', 1) 461 ]); 462 463 var brand = (dirBrand.status === 'fulfilled' ? dirBrand.value : [])[0] || null; 464 var entity = (ggEntity.status === 'fulfilled' ? ggEntity.value : [])[0] || null; 465 466 // Also check local 467 if (!brand && typeof BRAND_DATABASE !== 'undefined') { 468 var bLow = brandName.toLowerCase(); 469 Object.values(BRAND_DATABASE).some(function(cat) { 470 return (cat.brands || []).some(function(b) { 471 if ((b.name || '').toLowerCase() === bLow) { 472 brand = { brand_name: b.name, category: cat.label, website: b.website, tags: b.tags, tier: b.tier, description: b.description, _src: 'local' }; 473 return true; 474 } 475 }); 476 }); 477 } 478 479 var answer = ''; 480 if (brand) { 481 answer = brand.brand_name + ' is a ' + (brand.category || 'brand'); 482 if (brand.founder) answer += ', founded by ' + brand.founder; 483 if (brand.headquarters) answer += ', based in ' + brand.headquarters; 484 if (brand.description) answer += '. ' + brand.description.substring(0, 300); 485 if (brand.website) answer += ' Website: ' + brand.website; 486 } else if (entity) { 487 answer = entity.name + ': ' + (entity.summary || entity.known_for || 'Company in GoalGraph'); 488 } else { 489 answer = 'Could not find "' + brandName + '" in Splurjj\'s brand directory.';
490 } 491 492 return { brand: brand, entity: entity, answer: answer }; 493 } 494 495 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 496 * ASK ABOUT PERSON â deep dive into a person from GoalGraph 497 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 498 async function askAboutPerson(personName) { 499 var enc = encodeURIComponent(personName); 500 var rows = await sbGet('gg_entities', 501 'name=ilike.*' + enc + '*&type=eq.person&status=eq.active&select=*', 3); 502 503 var entity = (rows || [])[0] || null; 504 var answer = ''; 505 506 if (entity) { 507 answer = entity.name; 508 if (entity.nationality) answer += ' is a ' + entity.nationality + ' person'; 509 if (entity.category) answer += ' in ' + entity.category; 510 if (entity.known_for) answer += ', known for ' + entity.known_for; 511 if (entity.summary) answer += '. ' + entity.summary.substring(0, 300); 512 } else { 513 answer = 'Could not find "' + personName + '" in the GoalGraph knowledge base.'; 514 } 515 516 return { entity: entity, answer: answer }; 517 } 518 519 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 520 * ASK ABOUT GOAL 521 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 522 async function askAboutGoal(goalTitle) { 523 var enc = encodeURIComponent(goalTitle); 524 var rows = await sbGet('dir_goals', 525 'or=(goal_title.ilike.*' + enc + '*,description.ilike.*' + enc + '*)&select=*', 3); 526 527 var goal = (rows || [])[0] || null; 528 var answer = ''; 529 530 if (goal) { 531 answer = 'Goal: "' + (goal.goal_title || goal.title) + '"'; 532 if (goal.description) answer += '. ' + goal.description.substring(0, 300); 533 } else { 534 answer = 'Could not find a goal matching "' + goalTitle + '" in Splurjj\'s goals database.'; 535 } 536 537 return { goal: goal, answer: answer }; 538 } 539 540 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 541 * MUSE AI PANEL RENDERER 542 * Renders the Muse AI response into a DOM element 543 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 544 function renderMusePanel(containerId, queryResult) { 545 var el = document.getElementById(containerId); 546 if (!el) return; 547 548 var r = queryResult; 549 var confClass = r.confidence >= 80 ? 'muse-conf-high' : (r.confidence >= 50 ? 'muse-conf-mid' : 'muse-conf-low'); 550 551 var sourcesHtml = ''; 552 if (r.sources && r.sources.length) { 553 sourcesHtml = '<div class="muse-sources">' + 554 '<span class="muse-sources-label">Sources on Splurjj:</span>' + 555 r.sources.slice(0,4).map(function(s) { 556 return '<a href="' + s.url + '" class="muse-source-chip">' + 557 '<i class="fas ' + (s.type === 'brand' ? 'fa-tag' : s.type === 'article' ? 'fa-newspaper' : s.type === 'goal' ? 'fa-bullseye' : 'fa-network-wired') + '"></i> ' + 558 s.label + 559 '</a>'; 560 }).join('') + 561 '</div>'; 562 } 563 564 el.innerHTML = 565 '<div class="muse-panel">' + 566 '<div class="muse-header">' + 567 '<span class="muse-logo"><i class="fas fa-brain"></i> Muse AI</span>' + 568 '<span class="muse-badge ' + confClass + '">' + r.confidence + '% match</span>' + 569 '</div>' + 570 '<div class="muse-answer">' + r.answer.replace(/\n/g, '<br>') + '</div>' +
571 sourcesHtml + 572 '<div class="muse-footer">Muse AI searches only Splurjj.com content & GoalGraph</div>' + 573 '</div>'; 574 } 575 576 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 577 * MUSE AI PANEL CSS (injected once) 578 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 579 (function injectMuseStyles() { 580 if (document.getElementById('muse-search-styles')) return; 581 var s = document.createElement('style'); 582 s.id = 'muse-search-styles'; 583 s.textContent = ` 584 .muse-panel { 585 background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); 586 border: 1px solid rgba(251,191,36,0.25); 587 border-radius: 16px; 588 padding: 18px 20px; 589 margin: 12px 0; 590 font-family: 'Inter', system-ui, sans-serif; 591 position: relative; 592 overflow: hidden; 593 } 594 .muse-panel::before { 595 content: ''; 596 position: absolute; 597 top: 0; left: 0; right: 0; 598 height: 2px; 599 background: linear-gradient(90deg, #fbbf24, #f59e0b, #d97706); 600 } 601 .muse-header { 602 display: flex; 603 align-items: center; 604 gap: 12px; 605 margin-bottom: 12px; 606 } 607 .muse-logo { 608 font-size: 13px; 609 font-weight: 700; 610 color: #fbbf24; 611 letter-spacing: 0.5px; 612 display: flex; 613 align-items: center; 614 gap: 6px; 615 } 616 .muse-logo i { font-size: 14px; } 617 .muse-badge { 618 font-size: 11px; 619 padding: 2px 8px; 620 border-radius: 20px; 621 font-weight: 600; 622 margin-left: auto; 623 } 624 .muse-conf-high { background: rgba(34,197,94,0.2); color: #86efac; } 625 .muse-conf-mid { background: rgba(234,179,8,0.2); color: #fef08a; } 626 .muse-conf-low { background: rgba(148,163,184,0.15); color: #94a3b8; } 627 .muse-answer { 628 color: #e2e8f0; 629 font-size: 14px; 630 line-height: 1.65; 631 margin-bottom: 14px; 632 } 633 .muse-sources { 634 display: flex; 635 flex-wrap: wrap; 636 gap: 6px; 637 margin-bottom: 12px; 638 align-items: center; 639 } 640 .muse-sources-label { 641 font-size: 11px; 642 color: #64748b; 643 font-weight: 600; 644 text-transform: uppercase; 645 letter-spacing: 0.5px; 646 width: 100%; 647 margin-bottom: 4px; 648 } 649 .muse-source-chip { 650 display: inline-flex; 651 align-items: center; 652 gap: 5px; 653 padding: 4px 10px; 654 background: rgba(255,255,255,0.06); 655 border: 1px solid rgba(255,255,255,0.1); 656 border-radius: 20px; 657 font-size: 12px; 658 color: #cbd5e1; 659 text-decoration: none; 660 transition: all 0.15s; 661 } 662 .muse-source-chip:hover { 663 background: rgba(251,191,36,0.1); 664 border-color: rgba(251,191,36,0.3); 665 color: #fbbf24; 666 } 667 .muse-footer { 668 font-size: 10px; 669 color: #475569; 670 font-style: italic; 671 } 672 /* Modal Muse AI section */ 673 .splModal_museSection { 674 border-top: 1px solid rgba(255,255,255,0.06); 675 margin-top: 8px; 676 padding-top: 8px; 677 } 678 .splModal_museToggle { 679 display: flex; 680 align-items: center; 681 gap: 8px; 682 padding: 8px 14px; 683 cursor: pointer; 684 color: #fbbf24; 685 font-size: 13px; 686 font-weight: 600; 687 transition: opacity 0.15s; 688 border-radius: 8px; 689 background: rgba(251,191,36,0.08); 690 border: 1px solid rgba(251,191,36,0.15); 691 margin: 0 16px 8px; 692 } 693 .splModal_museToggle:hover { opacity: 0.85; } 694 .splModal_museToggle i { font-size: 14px; } 695 .splModal_musePanel { padding: 0 16px 12px; } 696 `; 697 document.head.appendChild(s); 698 })(); 699 700 /* ââ Public API âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 701 return { 702 query: query, 703 getContext: getContext, 704 suggest: suggest, 705 chat: chat, 706 askAboutBrand: askAboutBrand, 707 askAboutPerson: askAboutPerson, 708 askAboutGoal: askAboutGoal, 709 renderMusePanel: renderMusePanel, 710 // Internal helpers exposed for extensibility 711 retrieveSplurjjKnowledge: retrieveSplurjjKnowledge, 712 formatKnowledge: formatKnowledge 713 }; 714 715})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.