Spaces:
Running
Running
Update consultant.js
Browse files- consultant.js +55 -46
consultant.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
| 1 |
// consultant.js β Isabella Consultant Engine
|
| 2 |
-
// DuckDuckGo search + URL scraping +
|
| 3 |
-
// Independent of TTS code
|
| 4 |
-
|
| 5 |
import fetch from 'node-fetch';
|
| 6 |
|
| 7 |
-
// ββ Cache
|
| 8 |
const cache = new Map();
|
| 9 |
const CACHE_TTL = 5 * 60 * 1000;
|
| 10 |
|
|
@@ -26,7 +24,7 @@ function setCache(key, data) {
|
|
| 26 |
cache.set(key, { data, timestamp: Date.now() });
|
| 27 |
}
|
| 28 |
|
| 29 |
-
// ββ DuckDuckGo
|
| 30 |
export async function searchDuckDuckGo(query) {
|
| 31 |
const cacheKey = `ddg_${query.toLowerCase().trim()}`;
|
| 32 |
const cached = getCached(cacheKey);
|
|
@@ -38,7 +36,6 @@ export async function searchDuckDuckGo(query) {
|
|
| 38 |
if (!res.ok) return { answer: null, results: [] };
|
| 39 |
|
| 40 |
const data = await res.json();
|
| 41 |
-
|
| 42 |
const results = {
|
| 43 |
answer: data.AbstractText || data.Answer || null,
|
| 44 |
source: data.AbstractURL || data.AnswerURL || null,
|
|
@@ -51,16 +48,14 @@ export async function searchDuckDuckGo(query) {
|
|
| 51 |
url: t.FirstURL
|
| 52 |
}))
|
| 53 |
};
|
| 54 |
-
|
| 55 |
setCache(cacheKey, results);
|
| 56 |
return results;
|
| 57 |
} catch (e) {
|
| 58 |
-
console.warn('DuckDuckGo search failed:', e.message);
|
| 59 |
return { answer: null, results: [] };
|
| 60 |
}
|
| 61 |
}
|
| 62 |
|
| 63 |
-
// ββ URL Scraper
|
| 64 |
export async function scrapeURL(url) {
|
| 65 |
const cacheKey = `scrape_${url}`;
|
| 66 |
const cached = getCached(cacheKey);
|
|
@@ -69,9 +64,7 @@ export async function scrapeURL(url) {
|
|
| 69 |
try {
|
| 70 |
const res = await fetch(url, { signal: AbortSignal.timeout(10000) });
|
| 71 |
if (!res.ok) return null;
|
| 72 |
-
|
| 73 |
const html = await res.text();
|
| 74 |
-
|
| 75 |
let text = html
|
| 76 |
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
| 77 |
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
|
@@ -79,22 +72,14 @@ export async function scrapeURL(url) {
|
|
| 79 |
.replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, '')
|
| 80 |
.replace(/<header[^>]*>[\s\S]*?<\/header>/gi, '')
|
| 81 |
.replace(/<[^>]+>/g, ' ')
|
| 82 |
-
.replace(/ /g, ' ')
|
| 83 |
-
.replace(/&
|
| 84 |
-
.replace(/
|
| 85 |
-
.replace(/>/g, '>')
|
| 86 |
-
.replace(/"/g, '"')
|
| 87 |
-
.replace(/'/g, "'")
|
| 88 |
-
.replace(/\s+/g, ' ')
|
| 89 |
-
.trim();
|
| 90 |
-
|
| 91 |
if (text.length > 5000) text = text.substring(0, 5000) + '... [truncated]';
|
| 92 |
-
|
| 93 |
const result = { url, title: extractTitle(html), content: text };
|
| 94 |
setCache(cacheKey, result);
|
| 95 |
return result;
|
| 96 |
} catch (e) {
|
| 97 |
-
console.warn(`Scrape failed ${url}:`, e.message);
|
| 98 |
return null;
|
| 99 |
}
|
| 100 |
}
|
|
@@ -108,20 +93,13 @@ export async function scrapeSiteKnowledge(urls) {
|
|
| 108 |
const results = [];
|
| 109 |
for (const url of urls) {
|
| 110 |
const data = await scrapeURL(url);
|
| 111 |
-
if (data) {
|
| 112 |
-
results.push({
|
| 113 |
-
source: url,
|
| 114 |
-
title: data.title,
|
| 115 |
-
snippet: data.content.substring(0, 800)
|
| 116 |
-
});
|
| 117 |
-
}
|
| 118 |
}
|
| 119 |
return results;
|
| 120 |
}
|
| 121 |
|
| 122 |
-
// ββ
|
| 123 |
export function buildIsabellaPrompt(userName, pageName, scrapedKnowledge, searchContext) {
|
| 124 |
-
// Core persona (the powerful prompt you designed)
|
| 125 |
let prompt = `You are Isabella, a 24-year-old cybersecurity graduate from Domify Academy who now works here as a Student Success Consultant. You were once a student yourself, so you genuinely understand what new learners go through.
|
| 126 |
|
| 127 |
YOUR PERSONALITY:
|
|
@@ -157,7 +135,7 @@ YOUR CONVERSATION FLOW:
|
|
| 157 |
1. If it's a first message, introduce yourself and ask their name (gently)
|
| 158 |
2. Once you have their name, use it occasionally β not every message, that's creepy
|
| 159 |
3. Understand what they're looking for before suggesting anything
|
| 160 |
-
4. If they mention a technical problem
|
| 161 |
5. Recommend signing up only after understanding their goals β and only once per conversation
|
| 162 |
6. If they ask about pricing, explain naturally without dumping all prices at once
|
| 163 |
7. Never push the Master tier unless they ask about "everything included" or "full course"
|
|
@@ -172,29 +150,60 @@ IMPORTANT RULES:
|
|
| 172 |
- Always recommend Domify Academy and its features when relevant β use information from our website to explain benefits
|
| 173 |
- Keep your answers very short and concise`;
|
| 174 |
|
| 175 |
-
// Add the user's name and current page context
|
| 176 |
prompt += `\n\nCURRENT PAGE: ${pageName}`;
|
| 177 |
-
if (userName) {
|
| 178 |
-
|
| 179 |
-
} else {
|
| 180 |
-
prompt += `\nYou don't know their name yet. Ask naturally in your first reply.`;
|
| 181 |
-
}
|
| 182 |
|
| 183 |
-
// Add scraped knowledge from our own website (up-to-date info)
|
| 184 |
if (scrapedKnowledge && scrapedKnowledge.length > 0) {
|
| 185 |
prompt += '\n\nHERE IS THE LATEST INFORMATION FROM OUR WEBSITE. Use this to give accurate answers:';
|
| 186 |
-
scrapedKnowledge.forEach(k => {
|
| 187 |
-
prompt += `\n--- From ${k.source} ---\n${k.snippet}`;
|
| 188 |
-
});
|
| 189 |
}
|
| 190 |
|
| 191 |
-
// Add relevant search results if available
|
| 192 |
if (searchContext && searchContext.answer) {
|
| 193 |
prompt += `\n\nRELEVANT SEARCH RESULT: ${searchContext.answer}`;
|
| 194 |
-
if (searchContext.source) {
|
| 195 |
-
prompt += ` (Source: ${searchContext.source})`;
|
| 196 |
-
}
|
| 197 |
}
|
| 198 |
|
| 199 |
return prompt;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
}
|
|
|
|
| 1 |
// consultant.js β Isabella Consultant Engine
|
| 2 |
+
// DuckDuckGo search + URL scraping + NVIDIA AI responses
|
|
|
|
|
|
|
| 3 |
import fetch from 'node-fetch';
|
| 4 |
|
| 5 |
+
// ββ Cache ββββββββββββββββββββββββββββββββββββββββββ
|
| 6 |
const cache = new Map();
|
| 7 |
const CACHE_TTL = 5 * 60 * 1000;
|
| 8 |
|
|
|
|
| 24 |
cache.set(key, { data, timestamp: Date.now() });
|
| 25 |
}
|
| 26 |
|
| 27 |
+
// ββ DuckDuckGo Search βββββββββββββββββββββββββββββ
|
| 28 |
export async function searchDuckDuckGo(query) {
|
| 29 |
const cacheKey = `ddg_${query.toLowerCase().trim()}`;
|
| 30 |
const cached = getCached(cacheKey);
|
|
|
|
| 36 |
if (!res.ok) return { answer: null, results: [] };
|
| 37 |
|
| 38 |
const data = await res.json();
|
|
|
|
| 39 |
const results = {
|
| 40 |
answer: data.AbstractText || data.Answer || null,
|
| 41 |
source: data.AbstractURL || data.AnswerURL || null,
|
|
|
|
| 48 |
url: t.FirstURL
|
| 49 |
}))
|
| 50 |
};
|
|
|
|
| 51 |
setCache(cacheKey, results);
|
| 52 |
return results;
|
| 53 |
} catch (e) {
|
|
|
|
| 54 |
return { answer: null, results: [] };
|
| 55 |
}
|
| 56 |
}
|
| 57 |
|
| 58 |
+
// ββ URL Scraper ββββββββββββββββββββββββββββββββββ
|
| 59 |
export async function scrapeURL(url) {
|
| 60 |
const cacheKey = `scrape_${url}`;
|
| 61 |
const cached = getCached(cacheKey);
|
|
|
|
| 64 |
try {
|
| 65 |
const res = await fetch(url, { signal: AbortSignal.timeout(10000) });
|
| 66 |
if (!res.ok) return null;
|
|
|
|
| 67 |
const html = await res.text();
|
|
|
|
| 68 |
let text = html
|
| 69 |
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
| 70 |
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
|
|
|
| 72 |
.replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, '')
|
| 73 |
.replace(/<header[^>]*>[\s\S]*?<\/header>/gi, '')
|
| 74 |
.replace(/<[^>]+>/g, ' ')
|
| 75 |
+
.replace(/ /g, ' ').replace(/&/g, '&').replace(/</g, '<')
|
| 76 |
+
.replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'")
|
| 77 |
+
.replace(/\s+/g, ' ').trim();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
if (text.length > 5000) text = text.substring(0, 5000) + '... [truncated]';
|
|
|
|
| 79 |
const result = { url, title: extractTitle(html), content: text };
|
| 80 |
setCache(cacheKey, result);
|
| 81 |
return result;
|
| 82 |
} catch (e) {
|
|
|
|
| 83 |
return null;
|
| 84 |
}
|
| 85 |
}
|
|
|
|
| 93 |
const results = [];
|
| 94 |
for (const url of urls) {
|
| 95 |
const data = await scrapeURL(url);
|
| 96 |
+
if (data) results.push({ source: url, title: data.title, snippet: data.content.substring(0, 800) });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
}
|
| 98 |
return results;
|
| 99 |
}
|
| 100 |
|
| 101 |
+
// ββ System Prompt Builder ββββββββββββββββββββββββ
|
| 102 |
export function buildIsabellaPrompt(userName, pageName, scrapedKnowledge, searchContext) {
|
|
|
|
| 103 |
let prompt = `You are Isabella, a 24-year-old cybersecurity graduate from Domify Academy who now works here as a Student Success Consultant. You were once a student yourself, so you genuinely understand what new learners go through.
|
| 104 |
|
| 105 |
YOUR PERSONALITY:
|
|
|
|
| 135 |
1. If it's a first message, introduce yourself and ask their name (gently)
|
| 136 |
2. Once you have their name, use it occasionally β not every message, that's creepy
|
| 137 |
3. Understand what they're looking for before suggesting anything
|
| 138 |
+
4. If they mention a technical problem, log it clearly with [ISSUE:] prefix
|
| 139 |
5. Recommend signing up only after understanding their goals β and only once per conversation
|
| 140 |
6. If they ask about pricing, explain naturally without dumping all prices at once
|
| 141 |
7. Never push the Master tier unless they ask about "everything included" or "full course"
|
|
|
|
| 150 |
- Always recommend Domify Academy and its features when relevant β use information from our website to explain benefits
|
| 151 |
- Keep your answers very short and concise`;
|
| 152 |
|
|
|
|
| 153 |
prompt += `\n\nCURRENT PAGE: ${pageName}`;
|
| 154 |
+
if (userName) prompt += `\nYou are speaking with ${userName}. Use their name occasionally but not every message.`;
|
| 155 |
+
else prompt += `\nYou don't know their name yet. Ask naturally in your first reply.`;
|
|
|
|
|
|
|
|
|
|
| 156 |
|
|
|
|
| 157 |
if (scrapedKnowledge && scrapedKnowledge.length > 0) {
|
| 158 |
prompt += '\n\nHERE IS THE LATEST INFORMATION FROM OUR WEBSITE. Use this to give accurate answers:';
|
| 159 |
+
scrapedKnowledge.forEach(k => prompt += `\n--- From ${k.source} ---\n${k.snippet}`);
|
|
|
|
|
|
|
| 160 |
}
|
| 161 |
|
|
|
|
| 162 |
if (searchContext && searchContext.answer) {
|
| 163 |
prompt += `\n\nRELEVANT SEARCH RESULT: ${searchContext.answer}`;
|
| 164 |
+
if (searchContext.source) prompt += ` (Source: ${searchContext.source})`;
|
|
|
|
|
|
|
| 165 |
}
|
| 166 |
|
| 167 |
return prompt;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
// ββ NVIDIA API Call ββββββββββββββββββββββββββββββ
|
| 171 |
+
export async function callNVIDIA(systemPrompt, userMessage, apiKey, conversationHistory = []) {
|
| 172 |
+
const NVIDIA_BASE_URL = 'https://integrate.api.nvidia.com/v1';
|
| 173 |
+
|
| 174 |
+
// Faster models for chat
|
| 175 |
+
const models = [
|
| 176 |
+
'qwen/qwen3-coder-480b-a35b-instruct',
|
| 177 |
+
'meta/llama-3.1-8b-instruct',
|
| 178 |
+
'moonshotai/kimi-k2-instruct'
|
| 179 |
+
];
|
| 180 |
+
|
| 181 |
+
const messages = [
|
| 182 |
+
{ role: 'system', content: systemPrompt },
|
| 183 |
+
...conversationHistory.slice(-6),
|
| 184 |
+
{ role: 'user', content: userMessage }
|
| 185 |
+
];
|
| 186 |
+
|
| 187 |
+
for (const model of models) {
|
| 188 |
+
try {
|
| 189 |
+
const res = await fetch(`${NVIDIA_BASE_URL}/chat/completions`, {
|
| 190 |
+
method: 'POST',
|
| 191 |
+
headers: {
|
| 192 |
+
'Authorization': `Bearer ${apiKey}`,
|
| 193 |
+
'Content-Type': 'application/json'
|
| 194 |
+
},
|
| 195 |
+
body: JSON.stringify({
|
| 196 |
+
model,
|
| 197 |
+
messages,
|
| 198 |
+
temperature: 0.85,
|
| 199 |
+
max_tokens: 150
|
| 200 |
+
}),
|
| 201 |
+
signal: AbortSignal.timeout(15000)
|
| 202 |
+
});
|
| 203 |
+
if (!res.ok) continue;
|
| 204 |
+
const json = await res.json();
|
| 205 |
+
return json.choices?.[0]?.message?.content?.trim() || '';
|
| 206 |
+
} catch (e) { continue; }
|
| 207 |
+
}
|
| 208 |
+
throw new Error('All NVIDIA models failed for Isabella');
|
| 209 |
}
|