Domify commited on
Commit
ad0fe02
Β·
verified Β·
1 Parent(s): 76f361c

Update consultant.js

Browse files
Files changed (1) hide show
  1. consultant.js +55 -46
consultant.js CHANGED
@@ -1,10 +1,8 @@
1
  // consultant.js β€” Isabella Consultant Engine
2
- // DuckDuckGo search + URL scraping + knowledge base
3
- // Independent of TTS code
4
-
5
  import fetch from 'node-fetch';
6
 
7
- // ── Cache (5 minute TTL, in-memory) ──────────────
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 Instant Answer API ─────────────────
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 (extract readable text) ────────────
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(/&nbsp;/g, ' ')
83
- .replace(/&amp;/g, '&')
84
- .replace(/&lt;/g, '<')
85
- .replace(/&gt;/g, '>')
86
- .replace(/&quot;/g, '"')
87
- .replace(/&#39;/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
- // ── Build Isabella's System Prompt ─────────────────
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 (page not loading, can't sign up, etc.), log it clearly with [ISSUE:] prefix
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
- prompt += `\nYou are speaking with ${userName}. Use their name occasionally but not every message.`;
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(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/&lt;/g, '<')
76
+ .replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/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
  }