ask-junkie 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +864 -0
- package/dist/ask-junkie.css +485 -0
- package/dist/ask-junkie.esm.js +1847 -0
- package/dist/ask-junkie.esm.js.map +1 -0
- package/dist/ask-junkie.min.js +1858 -0
- package/dist/ask-junkie.min.js.map +1 -0
- package/dist/junkie-icon.png +0 -0
- package/package.json +43 -0
- package/src/ai/AIProviderFactory.js +43 -0
- package/src/ai/BaseProvider.js +68 -0
- package/src/ai/GeminiProvider.js +58 -0
- package/src/ai/GroqProvider.js +51 -0
- package/src/ai/OpenAIProvider.js +51 -0
- package/src/ai/OpenRouterProvider.js +53 -0
- package/src/analytics/FirebaseLogger.js +115 -0
- package/src/core/AskJunkie.js +585 -0
- package/src/core/EventEmitter.js +52 -0
- package/src/index.js +19 -0
- package/src/ui/ChatWidget.js +611 -0
- package/src/ui/styles.css +485 -0
- package/src/utils/DOMUtils.js +69 -0
- package/src/utils/StorageManager.js +80 -0
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask Junkie SDK - Main SDK Class
|
|
3
|
+
* Handles initialization, configuration, and orchestration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ChatWidget } from '../ui/ChatWidget.js';
|
|
7
|
+
import { AIProviderFactory } from '../ai/AIProviderFactory.js';
|
|
8
|
+
import { FirebaseLogger } from '../analytics/FirebaseLogger.js';
|
|
9
|
+
import { StorageManager } from '../utils/StorageManager.js';
|
|
10
|
+
import { EventEmitter } from './EventEmitter.js';
|
|
11
|
+
|
|
12
|
+
class AskJunkie {
|
|
13
|
+
static instance = null;
|
|
14
|
+
static VERSION = '1.1.1';
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
this.config = null;
|
|
18
|
+
this.widget = null;
|
|
19
|
+
this.aiProvider = null;
|
|
20
|
+
this.analytics = null;
|
|
21
|
+
this.storage = null;
|
|
22
|
+
this.events = new EventEmitter();
|
|
23
|
+
this.chatHistory = [];
|
|
24
|
+
this.isInitialized = false;
|
|
25
|
+
this.siteId = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the SDK with configuration
|
|
30
|
+
* @param {Object} config - Configuration options
|
|
31
|
+
*/
|
|
32
|
+
static async init(config = {}) {
|
|
33
|
+
if (AskJunkie.instance && AskJunkie.instance.isInitialized) {
|
|
34
|
+
console.warn('[AskJunkie] Already initialized. Call destroy() first to reinitialize.');
|
|
35
|
+
return AskJunkie.instance;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
AskJunkie.instance = new AskJunkie();
|
|
39
|
+
|
|
40
|
+
// Check if using sdkKey mode (fetches settings from dashboard)
|
|
41
|
+
if (config.sdkKey) {
|
|
42
|
+
await AskJunkie.instance._initializeWithSdkKey(config.sdkKey, config);
|
|
43
|
+
} else {
|
|
44
|
+
AskJunkie.instance._initialize(config);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return AskJunkie.instance;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Initialize using SDK key - fetches settings from Firebase dashboard
|
|
52
|
+
*/
|
|
53
|
+
async _initializeWithSdkKey(sdkKey, overrides = {}) {
|
|
54
|
+
console.log('[AskJunkie] Initializing with SDK key...');
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// Fetch settings from Firebase
|
|
58
|
+
const settings = await this._fetchSettingsFromDashboard(sdkKey);
|
|
59
|
+
|
|
60
|
+
if (!settings) {
|
|
61
|
+
console.error('[AskJunkie] Invalid SDK key or site not found.');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Merge fetched settings with any overrides
|
|
66
|
+
const config = {
|
|
67
|
+
apiKey: settings.providerApiKey,
|
|
68
|
+
provider: settings.provider,
|
|
69
|
+
model: settings.model,
|
|
70
|
+
botName: settings.botName,
|
|
71
|
+
welcomeMessage: settings.welcomeMessage,
|
|
72
|
+
suggestions: settings.suggestions,
|
|
73
|
+
animatedSuggestions: settings.animatedSuggestions,
|
|
74
|
+
position: settings.position,
|
|
75
|
+
draggable: settings.draggable,
|
|
76
|
+
resizable: settings.resizable,
|
|
77
|
+
voiceInput: settings.speechToText, // Map speechToText to voiceInput
|
|
78
|
+
enabled: settings.enabled,
|
|
79
|
+
theme: settings.theme,
|
|
80
|
+
context: settings.context,
|
|
81
|
+
analytics: {
|
|
82
|
+
enabled: true,
|
|
83
|
+
siteId: this.siteId,
|
|
84
|
+
userId: this.userId // Pass userId for new nested path
|
|
85
|
+
},
|
|
86
|
+
...overrides,
|
|
87
|
+
_sdkKey: sdkKey
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
this._initialize(config);
|
|
91
|
+
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('[AskJunkie] Error fetching settings:', error);
|
|
94
|
+
if (overrides.onError) {
|
|
95
|
+
overrides.onError(error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Fetch settings from Firebase dashboard
|
|
102
|
+
*/
|
|
103
|
+
async _fetchSettingsFromDashboard(sdkKey) {
|
|
104
|
+
const FIREBASE_PROJECT = 'ai-chatbot-fe4a2';
|
|
105
|
+
const API_URL = `https://firestore.googleapis.com/v1/projects/${FIREBASE_PROJECT}/databases/(default)/documents`;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
// Step 1: Get the key lookup from sdkKeys collection
|
|
109
|
+
// Document ID is the SDK key itself
|
|
110
|
+
const lookupUrl = `${API_URL}/sdkKeys/${sdkKey}`;
|
|
111
|
+
const lookupResponse = await fetch(lookupUrl);
|
|
112
|
+
|
|
113
|
+
if (!lookupResponse.ok) {
|
|
114
|
+
console.error('[AskJunkie] SDK key not found in lookup');
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const lookupData = await lookupResponse.json();
|
|
119
|
+
|
|
120
|
+
if (!lookupData.fields) {
|
|
121
|
+
console.error('[AskJunkie] SDK key not found');
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const userId = lookupData.fields.userId.stringValue;
|
|
126
|
+
const keyId = lookupData.fields.keyId.stringValue;
|
|
127
|
+
const status = lookupData.fields.status?.stringValue;
|
|
128
|
+
|
|
129
|
+
this.siteId = keyId;
|
|
130
|
+
this.userId = userId;
|
|
131
|
+
|
|
132
|
+
// Check if key is active
|
|
133
|
+
if (status !== 'active') {
|
|
134
|
+
console.error('[AskJunkie] SDK key is not active');
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Step 2: Fetch the full API key document with settings
|
|
139
|
+
const keyUrl = `${API_URL}/users/${userId}/apiKeys/${keyId}`;
|
|
140
|
+
const keyResponse = await fetch(keyUrl);
|
|
141
|
+
|
|
142
|
+
if (!keyResponse.ok) {
|
|
143
|
+
console.error('[AskJunkie] Could not fetch API key details');
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const keyData = await keyResponse.json();
|
|
148
|
+
|
|
149
|
+
if (!keyData.fields) {
|
|
150
|
+
console.error('[AskJunkie] API key document not found');
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Settings are embedded in the API key document
|
|
155
|
+
if (!keyData.fields.settings) {
|
|
156
|
+
console.warn('[AskJunkie] No settings configured in dashboard. Using defaults.');
|
|
157
|
+
return this._getDefaultSettings();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Parse Firestore format to plain object
|
|
161
|
+
return this._parseFirestoreSettings(keyData.fields.settings.mapValue.fields);
|
|
162
|
+
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error('[AskJunkie] Error fetching from Firebase:', error);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get default settings for SDK
|
|
171
|
+
*/
|
|
172
|
+
_getDefaultSettings() {
|
|
173
|
+
return {
|
|
174
|
+
enabled: true,
|
|
175
|
+
botName: 'AI Assistant',
|
|
176
|
+
welcomeMessage: "Hi! 👋 I'm your AI assistant. How can I help you today?",
|
|
177
|
+
suggestions: [],
|
|
178
|
+
animatedSuggestions: true,
|
|
179
|
+
speechToText: true,
|
|
180
|
+
provider: 'groq',
|
|
181
|
+
providerApiKey: '', // User must provide in overrides
|
|
182
|
+
model: 'llama-3.3-70b-versatile',
|
|
183
|
+
position: 'bottom-right',
|
|
184
|
+
draggable: true,
|
|
185
|
+
resizable: false,
|
|
186
|
+
theme: {
|
|
187
|
+
mode: 'gradient',
|
|
188
|
+
preset: 1,
|
|
189
|
+
primary: '#6366f1',
|
|
190
|
+
secondary: '#ec4899'
|
|
191
|
+
},
|
|
192
|
+
context: {
|
|
193
|
+
siteName: document.title || 'Website',
|
|
194
|
+
siteDescription: '',
|
|
195
|
+
customInfo: '',
|
|
196
|
+
restrictions: ''
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Parse Firestore document format to plain object
|
|
203
|
+
*/
|
|
204
|
+
_parseFirestoreSettings(fields) {
|
|
205
|
+
const getString = (field) => field?.stringValue || '';
|
|
206
|
+
const getNumber = (field) => field?.integerValue || field?.doubleValue || 0;
|
|
207
|
+
const getBool = (field) => field?.booleanValue || false;
|
|
208
|
+
const getArray = (field) => {
|
|
209
|
+
if (!field?.arrayValue?.values) return [];
|
|
210
|
+
return field.arrayValue.values.map(v => v.stringValue || '');
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const themeFields = fields.theme?.mapValue?.fields || {};
|
|
214
|
+
const contextFields = fields.context?.mapValue?.fields || {};
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
enabled: fields.enabled?.booleanValue !== false, // Default true if not set
|
|
218
|
+
botName: getString(fields.botName) || 'AI Assistant',
|
|
219
|
+
welcomeMessage: getString(fields.welcomeMessage) || "Hi! 👋 I'm your AI assistant.",
|
|
220
|
+
suggestions: getArray(fields.suggestions),
|
|
221
|
+
animatedSuggestions: fields.animatedSuggestions?.booleanValue !== false, // Default true
|
|
222
|
+
speechToText: fields.speechToText?.booleanValue !== false, // Default true
|
|
223
|
+
provider: getString(fields.provider) || 'groq',
|
|
224
|
+
providerApiKey: getString(fields.providerApiKey),
|
|
225
|
+
model: getString(fields.model),
|
|
226
|
+
position: getString(fields.position) || 'bottom-right',
|
|
227
|
+
draggable: getBool(fields.draggable),
|
|
228
|
+
resizable: getBool(fields.resizable),
|
|
229
|
+
theme: {
|
|
230
|
+
mode: getString(themeFields.mode) || 'gradient',
|
|
231
|
+
preset: getNumber(themeFields.preset) || 1,
|
|
232
|
+
primary: getString(themeFields.primary) || '#6366f1',
|
|
233
|
+
secondary: getString(themeFields.secondary) || '#ec4899'
|
|
234
|
+
},
|
|
235
|
+
context: {
|
|
236
|
+
siteName: getString(contextFields.siteName),
|
|
237
|
+
siteDescription: getString(contextFields.siteDescription),
|
|
238
|
+
customInfo: getString(contextFields.customInfo),
|
|
239
|
+
restrictions: getString(contextFields.restrictions)
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Internal initialization logic
|
|
246
|
+
*/
|
|
247
|
+
_initialize(config) {
|
|
248
|
+
// Merge with defaults
|
|
249
|
+
this.config = this._mergeConfig(config);
|
|
250
|
+
|
|
251
|
+
// Check if chatbot is enabled
|
|
252
|
+
if (this.config.enabled === false) {
|
|
253
|
+
console.log('[AskJunkie] Chatbot is disabled in settings. Widget will not render.');
|
|
254
|
+
this.isInitialized = true;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Validate required config
|
|
259
|
+
if (!this.config.apiKey && !this.config._sdkKey) {
|
|
260
|
+
console.error('[AskJunkie] API key or SDK key is required.');
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Initialize storage manager
|
|
265
|
+
this.storage = new StorageManager(this.config.storagePrefix);
|
|
266
|
+
|
|
267
|
+
// Load chat history from storage
|
|
268
|
+
this.chatHistory = this.storage.get('chat_history') || [];
|
|
269
|
+
|
|
270
|
+
// Initialize AI provider
|
|
271
|
+
this.aiProvider = AIProviderFactory.create(
|
|
272
|
+
this.config.provider,
|
|
273
|
+
this.config.apiKey,
|
|
274
|
+
this.config.model,
|
|
275
|
+
this.config.proxyUrl
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
// Initialize Firebase analytics (if enabled)
|
|
279
|
+
if (this.config.analytics.enabled) {
|
|
280
|
+
this.analytics = new FirebaseLogger(this.config.analytics);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Create and render the chat widget
|
|
284
|
+
this.widget = new ChatWidget({
|
|
285
|
+
...this.config,
|
|
286
|
+
onSendMessage: (message) => this._handleUserMessage(message),
|
|
287
|
+
onClearHistory: () => this._clearHistory(),
|
|
288
|
+
chatHistory: this.chatHistory
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
this.widget.render();
|
|
292
|
+
|
|
293
|
+
this.isInitialized = true;
|
|
294
|
+
this.events.emit('ready');
|
|
295
|
+
|
|
296
|
+
if (this.config.onReady) {
|
|
297
|
+
this.config.onReady();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.log(`[AskJunkie] SDK v${AskJunkie.VERSION} initialized successfully`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Merge user config with defaults
|
|
305
|
+
*/
|
|
306
|
+
_mergeConfig(userConfig) {
|
|
307
|
+
const defaults = {
|
|
308
|
+
// Required
|
|
309
|
+
apiKey: null,
|
|
310
|
+
|
|
311
|
+
// Master enable flag
|
|
312
|
+
enabled: true,
|
|
313
|
+
|
|
314
|
+
// AI Provider
|
|
315
|
+
provider: 'groq',
|
|
316
|
+
model: null, // Will use provider default
|
|
317
|
+
|
|
318
|
+
// Appearance
|
|
319
|
+
botName: 'AI Assistant',
|
|
320
|
+
botAvatar: null, // URL to custom avatar
|
|
321
|
+
welcomeMessage: "Hi! 👋 I'm your AI assistant. How can I help you today?",
|
|
322
|
+
position: 'bottom-right',
|
|
323
|
+
theme: {
|
|
324
|
+
mode: 'gradient',
|
|
325
|
+
preset: 1,
|
|
326
|
+
primary: '#6366f1',
|
|
327
|
+
secondary: '#ec4899'
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
// Behavior
|
|
331
|
+
draggable: false,
|
|
332
|
+
resizable: false,
|
|
333
|
+
persistChat: true,
|
|
334
|
+
voiceInput: true,
|
|
335
|
+
suggestions: [],
|
|
336
|
+
animatedSuggestions: true,
|
|
337
|
+
openOnLoad: false,
|
|
338
|
+
|
|
339
|
+
// Context for AI
|
|
340
|
+
context: {
|
|
341
|
+
siteName: document.title || 'Website',
|
|
342
|
+
siteDescription: '',
|
|
343
|
+
customInfo: '',
|
|
344
|
+
restrictions: ''
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
// Analytics
|
|
348
|
+
analytics: {
|
|
349
|
+
enabled: true,
|
|
350
|
+
siteId: window.location.hostname.replace(/[^a-zA-Z0-9.-]/g, '_')
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
// Proxy mode (optional)
|
|
354
|
+
proxyUrl: null,
|
|
355
|
+
|
|
356
|
+
// Storage prefix
|
|
357
|
+
storagePrefix: 'ask_junkie_',
|
|
358
|
+
|
|
359
|
+
// Event callbacks
|
|
360
|
+
onReady: null,
|
|
361
|
+
onMessage: null,
|
|
362
|
+
onOpen: null,
|
|
363
|
+
onClose: null,
|
|
364
|
+
onError: null
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// Deep merge theme object
|
|
368
|
+
const theme = { ...defaults.theme, ...(userConfig.theme || {}) };
|
|
369
|
+
const context = { ...defaults.context, ...(userConfig.context || {}) };
|
|
370
|
+
const analytics = { ...defaults.analytics, ...(userConfig.analytics || {}) };
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
...defaults,
|
|
374
|
+
...userConfig,
|
|
375
|
+
theme,
|
|
376
|
+
context,
|
|
377
|
+
analytics
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Handle user message submission
|
|
383
|
+
*/
|
|
384
|
+
async _handleUserMessage(message) {
|
|
385
|
+
if (!message.trim()) return;
|
|
386
|
+
|
|
387
|
+
// Add user message to history
|
|
388
|
+
this.chatHistory.push({
|
|
389
|
+
role: 'user',
|
|
390
|
+
content: message,
|
|
391
|
+
timestamp: new Date().toISOString()
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// Show typing indicator
|
|
395
|
+
this.widget.showTyping();
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
// Build context string
|
|
399
|
+
const contextString = this._buildContext();
|
|
400
|
+
|
|
401
|
+
// Get AI response
|
|
402
|
+
const response = await this.aiProvider.sendMessage(
|
|
403
|
+
message,
|
|
404
|
+
contextString,
|
|
405
|
+
this.chatHistory.slice(-10) // Send last 10 messages for context
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
// Add bot response to history
|
|
409
|
+
this.chatHistory.push({
|
|
410
|
+
role: 'assistant',
|
|
411
|
+
content: response,
|
|
412
|
+
timestamp: new Date().toISOString()
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Display response
|
|
416
|
+
this.widget.hideTyping();
|
|
417
|
+
this.widget.addMessage(response, 'bot');
|
|
418
|
+
|
|
419
|
+
// Save to storage
|
|
420
|
+
if (this.config.persistChat) {
|
|
421
|
+
this.storage.set('chat_history', this.chatHistory.slice(-50));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Log to analytics
|
|
425
|
+
if (this.analytics) {
|
|
426
|
+
this.analytics.logChat({
|
|
427
|
+
userMessage: message,
|
|
428
|
+
aiResponse: response,
|
|
429
|
+
pageUrl: window.location.href,
|
|
430
|
+
sessionId: this.storage.getSessionId()
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Fire callback
|
|
435
|
+
if (this.config.onMessage) {
|
|
436
|
+
this.config.onMessage(response, true);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
this.events.emit('message', { message: response, isBot: true });
|
|
440
|
+
|
|
441
|
+
} catch (error) {
|
|
442
|
+
this.widget.hideTyping();
|
|
443
|
+
const errorMessage = 'Sorry, I encountered an error. Please try again.';
|
|
444
|
+
this.widget.addMessage(errorMessage, 'bot');
|
|
445
|
+
|
|
446
|
+
console.error('[AskJunkie] Error:', error);
|
|
447
|
+
|
|
448
|
+
if (this.config.onError) {
|
|
449
|
+
this.config.onError(error);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
this.events.emit('error', error);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Build AI context from configuration
|
|
458
|
+
*/
|
|
459
|
+
_buildContext() {
|
|
460
|
+
const ctx = this.config.context;
|
|
461
|
+
let context = '';
|
|
462
|
+
|
|
463
|
+
context += `You are "${this.config.botName}", a friendly and helpful AI assistant for "${ctx.siteName}". `;
|
|
464
|
+
context += `Your personality is warm, professional, and knowledgeable. `;
|
|
465
|
+
|
|
466
|
+
context += `IMPORTANT IDENTITY RULES: `;
|
|
467
|
+
context += `1. Your name is ${this.config.botName}. `;
|
|
468
|
+
context += `2. If asked who created you, politely say you're an AI assistant designed to help with website inquiries. `;
|
|
469
|
+
context += `3. Stay focused on helping users with questions about this website. `;
|
|
470
|
+
|
|
471
|
+
if (ctx.siteDescription) {
|
|
472
|
+
context += `\n\nABOUT THIS WEBSITE: ${ctx.siteDescription} `;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (ctx.customInfo) {
|
|
476
|
+
context += `\n\nADDITIONAL INFORMATION: ${ctx.customInfo} `;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (ctx.restrictions) {
|
|
480
|
+
context += `\n\n🚫 RESTRICTIONS (follow strictly): ${ctx.restrictions} `;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
context += `\n\nRESPONSE FORMATTING: `;
|
|
484
|
+
context += `1. Be professional and courteous. `;
|
|
485
|
+
context += `2. Use clear paragraphs with line breaks. `;
|
|
486
|
+
context += `3. For lists, use numbered points or bullets. `;
|
|
487
|
+
context += `4. For links, use markdown format: [Link Text](URL). `;
|
|
488
|
+
context += `5. Use **bold** for important terms. `;
|
|
489
|
+
context += `6. Keep responses helpful but concise. `;
|
|
490
|
+
|
|
491
|
+
return context;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Clear chat history
|
|
496
|
+
*/
|
|
497
|
+
_clearHistory() {
|
|
498
|
+
this.chatHistory = [];
|
|
499
|
+
this.storage.remove('chat_history');
|
|
500
|
+
this.events.emit('clear');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// ===== PUBLIC API =====
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Open the chat widget
|
|
507
|
+
*/
|
|
508
|
+
static open() {
|
|
509
|
+
if (AskJunkie.instance?.widget) {
|
|
510
|
+
AskJunkie.instance.widget.open();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Close the chat widget
|
|
516
|
+
*/
|
|
517
|
+
static close() {
|
|
518
|
+
if (AskJunkie.instance?.widget) {
|
|
519
|
+
AskJunkie.instance.widget.close();
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Toggle the chat widget
|
|
525
|
+
*/
|
|
526
|
+
static toggle() {
|
|
527
|
+
if (AskJunkie.instance?.widget) {
|
|
528
|
+
AskJunkie.instance.widget.toggle();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Send a message programmatically
|
|
534
|
+
*/
|
|
535
|
+
static sendMessage(text) {
|
|
536
|
+
if (AskJunkie.instance) {
|
|
537
|
+
AskJunkie.instance.widget.addMessage(text, 'user');
|
|
538
|
+
AskJunkie.instance._handleUserMessage(text);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Update context dynamically
|
|
544
|
+
*/
|
|
545
|
+
static setContext(newContext) {
|
|
546
|
+
if (AskJunkie.instance) {
|
|
547
|
+
AskJunkie.instance.config.context = {
|
|
548
|
+
...AskJunkie.instance.config.context,
|
|
549
|
+
...newContext
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Subscribe to events
|
|
556
|
+
*/
|
|
557
|
+
static on(event, callback) {
|
|
558
|
+
if (AskJunkie.instance) {
|
|
559
|
+
AskJunkie.instance.events.on(event, callback);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Destroy the SDK instance
|
|
565
|
+
*/
|
|
566
|
+
static destroy() {
|
|
567
|
+
if (AskJunkie.instance) {
|
|
568
|
+
if (AskJunkie.instance.widget) {
|
|
569
|
+
AskJunkie.instance.widget.destroy();
|
|
570
|
+
}
|
|
571
|
+
AskJunkie.instance.isInitialized = false;
|
|
572
|
+
AskJunkie.instance = null;
|
|
573
|
+
console.log('[AskJunkie] Destroyed');
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Get SDK version
|
|
579
|
+
*/
|
|
580
|
+
static getVersion() {
|
|
581
|
+
return AskJunkie.VERSION;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export default AskJunkie;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple Event Emitter for SDK events
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class EventEmitter {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.events = {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Subscribe to an event
|
|
12
|
+
*/
|
|
13
|
+
on(event, callback) {
|
|
14
|
+
if (!this.events[event]) {
|
|
15
|
+
this.events[event] = [];
|
|
16
|
+
}
|
|
17
|
+
this.events[event].push(callback);
|
|
18
|
+
return () => this.off(event, callback);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Unsubscribe from an event
|
|
23
|
+
*/
|
|
24
|
+
off(event, callback) {
|
|
25
|
+
if (!this.events[event]) return;
|
|
26
|
+
this.events[event] = this.events[event].filter(cb => cb !== callback);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Emit an event
|
|
31
|
+
*/
|
|
32
|
+
emit(event, data) {
|
|
33
|
+
if (!this.events[event]) return;
|
|
34
|
+
this.events[event].forEach(callback => {
|
|
35
|
+
try {
|
|
36
|
+
callback(data);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(`[AskJunkie] Event handler error for "${event}":`, error);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Subscribe to an event once
|
|
45
|
+
*/
|
|
46
|
+
once(event, callback) {
|
|
47
|
+
const unsubscribe = this.on(event, (data) => {
|
|
48
|
+
unsubscribe();
|
|
49
|
+
callback(data);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask Junkie SDK - Main Entry Point
|
|
3
|
+
* Universal AI Chatbot for any website
|
|
4
|
+
*
|
|
5
|
+
* @author Junkies Coder
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import AskJunkie from './core/AskJunkie.js';
|
|
10
|
+
import './ui/styles.css';
|
|
11
|
+
|
|
12
|
+
// Export for ES modules
|
|
13
|
+
export default AskJunkie;
|
|
14
|
+
export { AskJunkie };
|
|
15
|
+
|
|
16
|
+
// Auto-attach to window for script tag usage
|
|
17
|
+
if (typeof window !== 'undefined') {
|
|
18
|
+
window.AskJunkie = AskJunkie;
|
|
19
|
+
}
|