@whykusanagi/corrupted-theme 0.1.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/CHANGELOG.md +339 -0
- package/Dockerfile +32 -0
- package/LICENSE +21 -0
- package/README.md +399 -0
- package/docker-entrypoint.sh +48 -0
- package/docs/COMPONENTS_REFERENCE.md +659 -0
- package/examples/.env.example +100 -0
- package/examples/button.html +436 -0
- package/examples/card.html +678 -0
- package/examples/form.html +555 -0
- package/examples/index.html +520 -0
- package/examples/layout.html +507 -0
- package/examples/nikke-team-builder.html +580 -0
- package/examples/showcase-complete.html +1071 -0
- package/examples/showcase.html +502 -0
- package/package.json +70 -0
- package/scripts/celeste-proxy-server.js +99 -0
- package/scripts/static-server.js +113 -0
- package/src/css/animations.css +649 -0
- package/src/css/components.css +1441 -0
- package/src/css/glassmorphism.css +217 -0
- package/src/css/nikke-utilities.css +530 -0
- package/src/css/theme.css +478 -0
- package/src/css/typography.css +198 -0
- package/src/css/utilities.css +239 -0
- package/src/css/variables.css +73 -0
- package/src/lib/celeste-proxy.js +215 -0
- package/src/lib/celeste-widget.js +1089 -0
- package/src/lib/corrupted-text.js +193 -0
- package/src/lib/corruption-loading.js +405 -0
|
@@ -0,0 +1,1089 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Celeste AI Custom Agent
|
|
3
|
+
* A custom implementation that integrates with context schemas
|
|
4
|
+
* Provides contextual assistance based on the current page
|
|
5
|
+
* Based on union raid implementation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class CelesteAgent {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
// Agent configuration
|
|
11
|
+
// With backend proxy pattern (secure mode): credentials NOT needed in browser
|
|
12
|
+
// Widget calls /api/chat endpoint, backend proxy handles authentication
|
|
13
|
+
// Browser never sees API key - it stays on server
|
|
14
|
+
|
|
15
|
+
this.agentKey = config.agentKey || window.CELESTE_AGENT_KEY;
|
|
16
|
+
this.agentId = config.agentId || window.CELESTE_AGENT_ID;
|
|
17
|
+
this.agentBaseUrl = config.agentBaseUrl || window.CELESTE_AGENT_BASE_URL;
|
|
18
|
+
|
|
19
|
+
// Proxy URL configuration (for local development)
|
|
20
|
+
// Defaults to localhost:5000, but can be overridden via CELESTE_PROXY_URL
|
|
21
|
+
// or detected from current page origin (for Docker port mapping scenarios)
|
|
22
|
+
this.proxyUrl = config.proxyUrl || window.CELESTE_PROXY_URL || this.detectProxyUrl();
|
|
23
|
+
|
|
24
|
+
// Note: With backend proxy, credentials are optional in browser
|
|
25
|
+
// They're only used if making direct API calls (legacy mode)
|
|
26
|
+
// The /api/chat endpoint will validate credentials server-side
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
this.isInitialized = false;
|
|
30
|
+
this.isOpen = false;
|
|
31
|
+
this.currentContext = null;
|
|
32
|
+
this.conversationHistory = [];
|
|
33
|
+
this.sessionId = this.generateSessionId();
|
|
34
|
+
this.contextSchemas = null;
|
|
35
|
+
this.celesteEssence = null;
|
|
36
|
+
this.routingRules = null;
|
|
37
|
+
|
|
38
|
+
// UI Elements
|
|
39
|
+
this.chatContainer = null;
|
|
40
|
+
this.chatButton = null;
|
|
41
|
+
this.chatWindow = null;
|
|
42
|
+
this.messageContainer = null;
|
|
43
|
+
this.inputField = null;
|
|
44
|
+
this.sendButton = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Static method: Check if Celeste is properly configured
|
|
49
|
+
* Call this to verify environment variables are set before initializing
|
|
50
|
+
* @returns {Object} Configuration status with details
|
|
51
|
+
*/
|
|
52
|
+
static checkConfiguration() {
|
|
53
|
+
const status = {
|
|
54
|
+
hasKey: !!window.CELESTE_AGENT_KEY,
|
|
55
|
+
hasId: !!window.CELESTE_AGENT_ID,
|
|
56
|
+
hasUrl: !!window.CELESTE_AGENT_BASE_URL,
|
|
57
|
+
isConfigured: !!(window.CELESTE_AGENT_KEY && window.CELESTE_AGENT_ID && window.CELESTE_AGENT_BASE_URL),
|
|
58
|
+
missing: []
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (!window.CELESTE_AGENT_KEY) status.missing.push('CELESTE_AGENT_KEY');
|
|
62
|
+
if (!window.CELESTE_AGENT_ID) status.missing.push('CELESTE_AGENT_ID');
|
|
63
|
+
if (!window.CELESTE_AGENT_BASE_URL) status.missing.push('CELESTE_AGENT_BASE_URL');
|
|
64
|
+
|
|
65
|
+
return status;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Initialize Celeste Agent
|
|
70
|
+
*/
|
|
71
|
+
async initialize() {
|
|
72
|
+
if (this.isInitialized) return;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Load context schemas
|
|
76
|
+
await this.loadContextSchemas();
|
|
77
|
+
|
|
78
|
+
// Get current page context
|
|
79
|
+
this.currentContext = await this.getPageContext();
|
|
80
|
+
|
|
81
|
+
// Create UI elements
|
|
82
|
+
this.createUI();
|
|
83
|
+
|
|
84
|
+
// Set initial prompt from schemas
|
|
85
|
+
this.loadContextualPrompt();
|
|
86
|
+
|
|
87
|
+
this.isInitialized = true;
|
|
88
|
+
console.log('🎭 Celeste Agent initialized with context:', this.currentContext);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('❌ Failed to initialize Celeste Agent:', error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Load context schemas and configuration from local sources
|
|
96
|
+
*/
|
|
97
|
+
async loadContextSchemas() {
|
|
98
|
+
try {
|
|
99
|
+
// Load local context schemas
|
|
100
|
+
const schemaResponse = await fetch('/static/data/celeste-context-schemas.json');
|
|
101
|
+
if (schemaResponse.ok) {
|
|
102
|
+
this.contextSchemas = await schemaResponse.json();
|
|
103
|
+
console.log('📋 Context schemas loaded');
|
|
104
|
+
} else {
|
|
105
|
+
this.contextSchemas = null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Load local capabilities
|
|
109
|
+
const capResponse = await fetch('/static/data/celeste-capabilities.json');
|
|
110
|
+
if (capResponse.ok) {
|
|
111
|
+
this.capabilities = await capResponse.json();
|
|
112
|
+
console.log('🎭 Celeste capabilities loaded');
|
|
113
|
+
} else {
|
|
114
|
+
this.capabilities = null;
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.warn('⚠️ Error loading data files:', error);
|
|
118
|
+
this.contextSchemas = null;
|
|
119
|
+
this.capabilities = null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get contextual information about the current page
|
|
125
|
+
*/
|
|
126
|
+
async getPageContext() {
|
|
127
|
+
const path = window.location.pathname;
|
|
128
|
+
const context = {
|
|
129
|
+
page: this.getPageName(path),
|
|
130
|
+
path: path,
|
|
131
|
+
timestamp: new Date().toISOString(),
|
|
132
|
+
userAgent: navigator.userAgent,
|
|
133
|
+
viewport: {
|
|
134
|
+
width: window.innerWidth,
|
|
135
|
+
height: window.innerHeight
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Add page-specific context
|
|
140
|
+
switch (context.page) {
|
|
141
|
+
case 'home':
|
|
142
|
+
context.data = await this.getHomeContext();
|
|
143
|
+
break;
|
|
144
|
+
case 'art':
|
|
145
|
+
context.data = await this.getArtContext();
|
|
146
|
+
break;
|
|
147
|
+
case 'celeste':
|
|
148
|
+
context.data = await this.getCelesteContext();
|
|
149
|
+
break;
|
|
150
|
+
case 'references':
|
|
151
|
+
context.data = await this.getReferencesContext();
|
|
152
|
+
break;
|
|
153
|
+
case 'doujin':
|
|
154
|
+
context.data = await this.getDoujinContext();
|
|
155
|
+
break;
|
|
156
|
+
case 'links':
|
|
157
|
+
context.data = await this.getLinksContext();
|
|
158
|
+
break;
|
|
159
|
+
case 'tools':
|
|
160
|
+
context.data = await this.getToolsContext();
|
|
161
|
+
break;
|
|
162
|
+
case 'privacy':
|
|
163
|
+
context.data = await this.getPrivacyContext();
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
context.data = await this.getGeneralContext();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return context;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get page name from path
|
|
174
|
+
*/
|
|
175
|
+
getPageName(path) {
|
|
176
|
+
if (path === '/' || path === '/index.html') return 'home';
|
|
177
|
+
if (path.includes('art')) return 'art';
|
|
178
|
+
if (path.includes('celeste')) return 'celeste';
|
|
179
|
+
if (path.includes('reference')) return 'references';
|
|
180
|
+
if (path.includes('kirara') || path.includes('bastard') || path.includes('doujin')) return 'doujin';
|
|
181
|
+
if (path.includes('link')) return 'links';
|
|
182
|
+
if (path.includes('tool') || path.includes('calc') || path.includes('countdown')) return 'tools';
|
|
183
|
+
if (path.includes('privacy')) return 'privacy';
|
|
184
|
+
return 'unknown';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get home-specific context
|
|
189
|
+
*/
|
|
190
|
+
async getHomeContext() {
|
|
191
|
+
return {
|
|
192
|
+
purpose: 'Portfolio landing page and introduction',
|
|
193
|
+
features: [
|
|
194
|
+
'Portfolio overview',
|
|
195
|
+
'Navigation to all sections',
|
|
196
|
+
'About whykusanagi',
|
|
197
|
+
'Celeste introduction'
|
|
198
|
+
],
|
|
199
|
+
dataTypes: ['portfolio_overview', 'section_navigation'],
|
|
200
|
+
suggestions: [
|
|
201
|
+
'What can I find on this site?',
|
|
202
|
+
'Who is whykusanagi?',
|
|
203
|
+
'Show me your art',
|
|
204
|
+
'What tools are available?'
|
|
205
|
+
]
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get art-specific context
|
|
211
|
+
*/
|
|
212
|
+
async getArtContext() {
|
|
213
|
+
return {
|
|
214
|
+
purpose: 'Digital art showcase and gallery',
|
|
215
|
+
features: [
|
|
216
|
+
'Art gallery showcase',
|
|
217
|
+
'Artistic style discussion',
|
|
218
|
+
'Design techniques',
|
|
219
|
+
'Character design exploration'
|
|
220
|
+
],
|
|
221
|
+
dataTypes: ['art_gallery', 'design_analysis', 'artistic_style'],
|
|
222
|
+
suggestions: [
|
|
223
|
+
'What is your artistic style?',
|
|
224
|
+
'How do you approach character design?',
|
|
225
|
+
'Tell me about this piece',
|
|
226
|
+
'What software do you use?'
|
|
227
|
+
]
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get Celeste character context
|
|
233
|
+
*/
|
|
234
|
+
async getCelesteContext() {
|
|
235
|
+
return {
|
|
236
|
+
purpose: 'Character page about Celeste AI',
|
|
237
|
+
features: [
|
|
238
|
+
'Character information',
|
|
239
|
+
'Personality exploration',
|
|
240
|
+
'Background story',
|
|
241
|
+
'Character showcase'
|
|
242
|
+
],
|
|
243
|
+
dataTypes: ['character_info', 'personality', 'background'],
|
|
244
|
+
suggestions: [
|
|
245
|
+
'Who are you really?',
|
|
246
|
+
'What is your personality like?',
|
|
247
|
+
'Are you actually an AI?',
|
|
248
|
+
'Tell me something weird about yourself'
|
|
249
|
+
]
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get references context
|
|
255
|
+
*/
|
|
256
|
+
async getReferencesContext() {
|
|
257
|
+
return {
|
|
258
|
+
purpose: 'Character design references and anatomy',
|
|
259
|
+
features: [
|
|
260
|
+
'Character references',
|
|
261
|
+
'Design inspiration',
|
|
262
|
+
'Anatomy studies',
|
|
263
|
+
'Color palettes'
|
|
264
|
+
],
|
|
265
|
+
dataTypes: ['character_references', 'design_inspiration', 'anatomy'],
|
|
266
|
+
suggestions: [
|
|
267
|
+
'Explain the character design',
|
|
268
|
+
'What is the design inspiration?',
|
|
269
|
+
'How did you choose colors?',
|
|
270
|
+
'Tell me about the anatomy'
|
|
271
|
+
]
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get doujin context
|
|
277
|
+
*/
|
|
278
|
+
async getDoujinContext() {
|
|
279
|
+
return {
|
|
280
|
+
purpose: 'Manga and doujinshi projects',
|
|
281
|
+
features: [
|
|
282
|
+
'Manga project information',
|
|
283
|
+
'Story summaries',
|
|
284
|
+
'Character discussions',
|
|
285
|
+
'Publication information'
|
|
286
|
+
],
|
|
287
|
+
dataTypes: ['manga_projects', 'storylines', 'characters'],
|
|
288
|
+
suggestions: [
|
|
289
|
+
'Tell me about Fall of Kirara',
|
|
290
|
+
'What is the plot?',
|
|
291
|
+
'Where can I read it?',
|
|
292
|
+
'When is it available?'
|
|
293
|
+
]
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get links context
|
|
299
|
+
*/
|
|
300
|
+
async getLinksContext() {
|
|
301
|
+
return {
|
|
302
|
+
purpose: 'Social media and external links',
|
|
303
|
+
features: [
|
|
304
|
+
'Social media links',
|
|
305
|
+
'Platform connections',
|
|
306
|
+
'External resources',
|
|
307
|
+
'Contact information'
|
|
308
|
+
],
|
|
309
|
+
dataTypes: ['social_links', 'platforms', 'connections'],
|
|
310
|
+
suggestions: [
|
|
311
|
+
'Where can I find whykusanagi?',
|
|
312
|
+
'What is your Twitch?',
|
|
313
|
+
'Do you have a Discord?',
|
|
314
|
+
'Where is your shop?'
|
|
315
|
+
]
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get tools context
|
|
321
|
+
*/
|
|
322
|
+
async getToolsContext() {
|
|
323
|
+
return {
|
|
324
|
+
purpose: 'Interactive tools and utilities',
|
|
325
|
+
features: [
|
|
326
|
+
'Countdown timers',
|
|
327
|
+
'Utility calculators',
|
|
328
|
+
'Resource downloads',
|
|
329
|
+
'Interactive tools'
|
|
330
|
+
],
|
|
331
|
+
dataTypes: ['tools', 'utilities', 'resources'],
|
|
332
|
+
suggestions: [
|
|
333
|
+
'How do I use this tool?',
|
|
334
|
+
'What is the calculator for?',
|
|
335
|
+
'Can you help me with the countdown?',
|
|
336
|
+
'How does this work?'
|
|
337
|
+
]
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Get privacy context
|
|
343
|
+
*/
|
|
344
|
+
async getPrivacyContext() {
|
|
345
|
+
return {
|
|
346
|
+
purpose: 'Privacy policy and legal information',
|
|
347
|
+
features: [
|
|
348
|
+
'Privacy policy',
|
|
349
|
+
'Legal information',
|
|
350
|
+
'Terms of service',
|
|
351
|
+
'Data practices'
|
|
352
|
+
],
|
|
353
|
+
dataTypes: ['legal', 'privacy', 'terms'],
|
|
354
|
+
suggestions: [
|
|
355
|
+
'What is your privacy policy?',
|
|
356
|
+
'How is my data used?',
|
|
357
|
+
'What are your terms?',
|
|
358
|
+
'Do you track users?'
|
|
359
|
+
]
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Get general context for unknown pages
|
|
365
|
+
*/
|
|
366
|
+
async getGeneralContext() {
|
|
367
|
+
return {
|
|
368
|
+
purpose: 'Portfolio and personal website',
|
|
369
|
+
features: [
|
|
370
|
+
'Art and creative work',
|
|
371
|
+
'Portfolio showcase',
|
|
372
|
+
'Character and tools',
|
|
373
|
+
'Social connections'
|
|
374
|
+
],
|
|
375
|
+
dataTypes: ['general_portfolio'],
|
|
376
|
+
suggestions: [
|
|
377
|
+
'Tell me about your work',
|
|
378
|
+
'Show me your art',
|
|
379
|
+
'What tools do you offer?'
|
|
380
|
+
]
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Load contextual prompt from schemas and capabilities
|
|
386
|
+
*/
|
|
387
|
+
loadContextualPrompt() {
|
|
388
|
+
try {
|
|
389
|
+
if (!this.contextSchemas) {
|
|
390
|
+
this.contextualPrompt = this.getDefaultPrompt();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const pageType = this.currentContext?.page || 'unknown';
|
|
395
|
+
const pageConfig = this.contextSchemas.page_types?.[pageType];
|
|
396
|
+
|
|
397
|
+
if (pageConfig) {
|
|
398
|
+
// Start with page-specific prompt
|
|
399
|
+
let prompt = pageConfig.system_prompt;
|
|
400
|
+
|
|
401
|
+
// Add capability information if available
|
|
402
|
+
if (this.capabilities) {
|
|
403
|
+
const pageCapabilities = this.capabilities.page_specific_capabilities?.[pageType];
|
|
404
|
+
if (pageCapabilities) {
|
|
405
|
+
prompt += `\n\nOn this page (${pageType}), you can help with: ${pageCapabilities.can_help_with.join(', ')}`;
|
|
406
|
+
}
|
|
407
|
+
// Add general capabilities reference
|
|
408
|
+
prompt += `\n\nYour core capabilities include: ${Object.keys(this.capabilities.core_capabilities).join(', ')}`;
|
|
409
|
+
prompt += `\n\nWhen users ask "what can you do" or "what are your abilities", refer to these capabilities and the current page context.`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
this.contextualPrompt = prompt;
|
|
413
|
+
this.suggestedQueries = pageConfig.suggested_queries;
|
|
414
|
+
console.log('🎭 Context loaded from schemas for page:', pageType);
|
|
415
|
+
} else {
|
|
416
|
+
this.contextualPrompt = this.getDefaultPrompt();
|
|
417
|
+
}
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error('❌ Error loading context prompt:', error);
|
|
420
|
+
this.contextualPrompt = this.getDefaultPrompt();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Get default prompt if schemas fail
|
|
426
|
+
*/
|
|
427
|
+
getDefaultPrompt() {
|
|
428
|
+
const pageType = this.currentContext?.page || 'unknown';
|
|
429
|
+
const prompts = {
|
|
430
|
+
'home': "Hello! I'm Celeste, your AI assistant. I can help you explore the art gallery, learn about my character, discover tools, or find social media links. What would you like to know?",
|
|
431
|
+
'art': "Hello! I'm Celeste, here to help you explore the art gallery. I can discuss artistic styles, techniques, and help you discover pieces that interest you. What would you like to know?",
|
|
432
|
+
'celeste': "Hello! I'm Celeste AI, your chaotic Onee-san assistant. Ask me anything about who I am, my personality, or what makes me unique. What's on your mind?",
|
|
433
|
+
'references': "Hello! I'm here to explain character designs and artistic references. I can discuss anatomy, color theory, and design inspiration. What would you like to know?",
|
|
434
|
+
'doujin': "Hello! I'm Celeste, guiding you through manga and doujinshi projects. I can summarize stories, discuss characters, and provide information on where to read. What interests you?",
|
|
435
|
+
'links': "Hello! I'm here to help you find and connect on social media. I know all the platforms where whykusanagi can be found. What platform are you looking for?",
|
|
436
|
+
'tools': "Hello! I'm ready to help you understand and use available tools. I can explain how calculators work and help you get the most out of them. What do you need?",
|
|
437
|
+
'privacy': "Hello! I'm here to clarify privacy policies and legal information. I can explain data practices and terms of service. What do you need to know?",
|
|
438
|
+
'default': "Hello, I am CelesteAI. Is there something I can help you with or are you just gonna stare?"
|
|
439
|
+
};
|
|
440
|
+
return prompts[pageType] || prompts.default;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Create UI elements
|
|
445
|
+
*/
|
|
446
|
+
createUI() {
|
|
447
|
+
// Create chat button
|
|
448
|
+
this.chatButton = document.createElement('div');
|
|
449
|
+
this.chatButton.className = 'celeste-chat-button';
|
|
450
|
+
const avatarUrl = this.getAssetUrl('https://s3.whykusanagi.xyz/Celeste_Vel_Icon.png');
|
|
451
|
+
this.chatButton.innerHTML = `
|
|
452
|
+
<div class="celeste-button-content">
|
|
453
|
+
<img src="${avatarUrl}" alt="Celeste AI" class="celeste-avatar" onerror="this.style.display='none'; this.parentElement.style.background='linear-gradient(135deg, #d94f90 0%, #b61b70 100%)';">
|
|
454
|
+
<span class="celeste-button-text">Chat with Celeste</span>
|
|
455
|
+
</div>
|
|
456
|
+
`;
|
|
457
|
+
this.chatButton.addEventListener('click', () => this.toggleChat());
|
|
458
|
+
|
|
459
|
+
// Create chat window
|
|
460
|
+
this.chatWindow = document.createElement('div');
|
|
461
|
+
this.chatWindow.className = 'celeste-chat-window';
|
|
462
|
+
const headerAvatarUrl = this.getAssetUrl('https://s3.whykusanagi.xyz/Celeste_Vel_Icon.png');
|
|
463
|
+
this.chatWindow.innerHTML = `
|
|
464
|
+
<div class="celeste-chat-header">
|
|
465
|
+
<div class="celeste-header-content">
|
|
466
|
+
<img src="${headerAvatarUrl}" alt="Celeste AI" class="celeste-header-avatar" onerror="this.style.display='none';">
|
|
467
|
+
<div class="celeste-header-info">
|
|
468
|
+
<h3><strong>CelesteAI</strong></h3>
|
|
469
|
+
<p><strong>Your helpful Onee-san assistant</strong></p>
|
|
470
|
+
</div>
|
|
471
|
+
</div>
|
|
472
|
+
<button class="celeste-close-btn">×</button>
|
|
473
|
+
</div>
|
|
474
|
+
<div class="celeste-chat-messages"></div>
|
|
475
|
+
<div class="celeste-chat-input">
|
|
476
|
+
<input type="text" placeholder="Ask Celeste anything..." class="celeste-input-field">
|
|
477
|
+
<button class="celeste-send-btn">Send</button>
|
|
478
|
+
</div>
|
|
479
|
+
`;
|
|
480
|
+
|
|
481
|
+
// Add event listeners
|
|
482
|
+
this.chatWindow.querySelector('.celeste-close-btn').addEventListener('click', () => this.closeChat());
|
|
483
|
+
this.chatWindow.querySelector('.celeste-send-btn').addEventListener('click', () => this.sendMessage());
|
|
484
|
+
this.chatWindow.querySelector('.celeste-input-field').addEventListener('keypress', (e) => {
|
|
485
|
+
if (e.key === 'Enter') this.sendMessage();
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// Add to page
|
|
489
|
+
document.body.appendChild(this.chatButton);
|
|
490
|
+
document.body.appendChild(this.chatWindow);
|
|
491
|
+
|
|
492
|
+
// Add CSS
|
|
493
|
+
this.addStyles();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Add CSS styles
|
|
498
|
+
*/
|
|
499
|
+
addStyles() {
|
|
500
|
+
const style = document.createElement('style');
|
|
501
|
+
style.textContent = `
|
|
502
|
+
.celeste-chat-button {
|
|
503
|
+
position: fixed;
|
|
504
|
+
bottom: 20px;
|
|
505
|
+
right: 20px;
|
|
506
|
+
width: 60px;
|
|
507
|
+
height: 60px;
|
|
508
|
+
background: linear-gradient(135deg, #0a0a0a 0%, #2d1b4e 50%, #d94f90 100%);
|
|
509
|
+
border-radius: 50%;
|
|
510
|
+
cursor: pointer;
|
|
511
|
+
box-shadow: 0 4px 20px rgba(217, 79, 144, 0.4);
|
|
512
|
+
transition: all 0.3s ease;
|
|
513
|
+
z-index: 1000;
|
|
514
|
+
display: flex;
|
|
515
|
+
align-items: center;
|
|
516
|
+
justify-content: center;
|
|
517
|
+
border: 2px solid rgba(217, 79, 144, 0.3);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.celeste-chat-button:hover {
|
|
521
|
+
transform: scale(1.1);
|
|
522
|
+
box-shadow: 0 6px 25px rgba(217, 79, 144, 0.6);
|
|
523
|
+
background: linear-gradient(135deg, #2d1b4e 0%, #0a0a0a 50%, #ff69b4 100%);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.celeste-button-content {
|
|
527
|
+
display: flex;
|
|
528
|
+
flex-direction: column;
|
|
529
|
+
align-items: center;
|
|
530
|
+
color: white;
|
|
531
|
+
text-align: center;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.celeste-avatar {
|
|
535
|
+
width: 30px;
|
|
536
|
+
height: 30px;
|
|
537
|
+
border-radius: 50%;
|
|
538
|
+
object-fit: cover;
|
|
539
|
+
display: block;
|
|
540
|
+
background: rgba(217, 79, 144, 0.2);
|
|
541
|
+
border: 1px solid rgba(217, 79, 144, 0.4);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.celeste-button-text {
|
|
545
|
+
font-size: 8px;
|
|
546
|
+
font-weight: bold;
|
|
547
|
+
margin-top: 2px;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.celeste-chat-window {
|
|
551
|
+
position: fixed;
|
|
552
|
+
bottom: 90px;
|
|
553
|
+
right: 20px;
|
|
554
|
+
width: 350px;
|
|
555
|
+
height: 500px;
|
|
556
|
+
background: linear-gradient(145deg, #0a0a0a 0%, #1a0f2e 100%);
|
|
557
|
+
border-radius: 15px;
|
|
558
|
+
box-shadow: 0 10px 30px rgba(217, 79, 144, 0.3);
|
|
559
|
+
display: none;
|
|
560
|
+
flex-direction: column;
|
|
561
|
+
z-index: 1001;
|
|
562
|
+
overflow: hidden;
|
|
563
|
+
border: 1px solid rgba(217, 79, 144, 0.2);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.celeste-chat-window.open {
|
|
567
|
+
display: flex;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.celeste-chat-header {
|
|
571
|
+
background: linear-gradient(135deg, #d94f90 0%, #b61b70 50%, #8b1a59 100%);
|
|
572
|
+
color: white;
|
|
573
|
+
padding: 15px;
|
|
574
|
+
display: flex;
|
|
575
|
+
justify-content: space-between;
|
|
576
|
+
align-items: center;
|
|
577
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.celeste-header-content {
|
|
581
|
+
display: flex;
|
|
582
|
+
align-items: center;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.celeste-header-avatar {
|
|
586
|
+
width: 40px;
|
|
587
|
+
height: 40px;
|
|
588
|
+
border-radius: 50%;
|
|
589
|
+
object-fit: cover;
|
|
590
|
+
margin-right: 10px;
|
|
591
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
592
|
+
box-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.celeste-header-info h3 {
|
|
596
|
+
margin: 0;
|
|
597
|
+
font-size: 16px;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.celeste-header-info p {
|
|
601
|
+
margin: 0;
|
|
602
|
+
font-size: 12px;
|
|
603
|
+
opacity: 0.8;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.celeste-close-btn {
|
|
607
|
+
background: none;
|
|
608
|
+
border: none;
|
|
609
|
+
color: white;
|
|
610
|
+
font-size: 24px;
|
|
611
|
+
cursor: pointer;
|
|
612
|
+
padding: 0;
|
|
613
|
+
width: 30px;
|
|
614
|
+
height: 30px;
|
|
615
|
+
display: flex;
|
|
616
|
+
align-items: center;
|
|
617
|
+
justify-content: center;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.celeste-chat-messages {
|
|
621
|
+
flex: 1;
|
|
622
|
+
padding: 15px;
|
|
623
|
+
overflow-y: auto;
|
|
624
|
+
background: linear-gradient(145deg, #1a0f2e 0%, #0a0a0a 100%);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.celeste-message {
|
|
628
|
+
margin-bottom: 15px;
|
|
629
|
+
display: flex;
|
|
630
|
+
align-items: flex-start;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.celeste-message.user {
|
|
634
|
+
justify-content: flex-end;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.celeste-message-bubble {
|
|
638
|
+
max-width: 80%;
|
|
639
|
+
padding: 10px 15px;
|
|
640
|
+
border-radius: 18px;
|
|
641
|
+
word-wrap: break-word;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.celeste-message.celeste .celeste-message-bubble {
|
|
645
|
+
background: linear-gradient(135deg, #2d1b4e 0%, #1a0f2e 100%);
|
|
646
|
+
color: #fdf3f8;
|
|
647
|
+
border-bottom-left-radius: 5px;
|
|
648
|
+
border: 1px solid rgba(217, 79, 144, 0.2);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.celeste-message.user .celeste-message-bubble {
|
|
652
|
+
background: linear-gradient(135deg, #d94f90 0%, #b61b70 100%);
|
|
653
|
+
color: white;
|
|
654
|
+
border-bottom-right-radius: 5px;
|
|
655
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.celeste-chat-input {
|
|
659
|
+
padding: 15px;
|
|
660
|
+
background: linear-gradient(145deg, #0a0a0a 0%, #1a0f2e 100%);
|
|
661
|
+
border-top: 1px solid rgba(217, 79, 144, 0.2);
|
|
662
|
+
display: flex;
|
|
663
|
+
gap: 10px;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.celeste-input-field {
|
|
667
|
+
flex: 1;
|
|
668
|
+
padding: 10px 15px;
|
|
669
|
+
border: 1px solid rgba(217, 79, 144, 0.3);
|
|
670
|
+
border-radius: 25px;
|
|
671
|
+
outline: none;
|
|
672
|
+
font-size: 14px;
|
|
673
|
+
background: rgba(45, 27, 78, 0.5);
|
|
674
|
+
color: white;
|
|
675
|
+
backdrop-filter: blur(10px);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.celeste-input-field:focus {
|
|
679
|
+
border-color: #d94f90;
|
|
680
|
+
background: rgba(217, 79, 144, 0.2);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.celeste-input-field::placeholder {
|
|
684
|
+
color: rgba(255, 255, 255, 0.5);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.celeste-send-btn {
|
|
688
|
+
background: linear-gradient(135deg, #d94f90 0%, #b61b70 100%);
|
|
689
|
+
color: white;
|
|
690
|
+
border: none;
|
|
691
|
+
border-radius: 25px;
|
|
692
|
+
padding: 10px 20px;
|
|
693
|
+
cursor: pointer;
|
|
694
|
+
font-weight: bold;
|
|
695
|
+
transition: all 0.3s ease;
|
|
696
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.celeste-send-btn:hover {
|
|
700
|
+
transform: scale(1.05);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.celeste-typing {
|
|
704
|
+
display: flex;
|
|
705
|
+
align-items: center;
|
|
706
|
+
gap: 5px;
|
|
707
|
+
color: rgba(255, 255, 255, 0.7);
|
|
708
|
+
font-style: italic;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.celeste-typing-dots {
|
|
712
|
+
display: flex;
|
|
713
|
+
gap: 3px;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.celeste-typing-dot {
|
|
717
|
+
width: 6px;
|
|
718
|
+
height: 6px;
|
|
719
|
+
background: #d94f90;
|
|
720
|
+
border-radius: 50%;
|
|
721
|
+
animation: celeste-typing 1.4s infinite ease-in-out;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.celeste-typing-dot:nth-child(1) { animation-delay: -0.32s; }
|
|
725
|
+
.celeste-typing-dot:nth-child(2) { animation-delay: -0.16s; }
|
|
726
|
+
|
|
727
|
+
@keyframes celeste-typing {
|
|
728
|
+
0%, 80%, 100% { transform: scale(0); }
|
|
729
|
+
40% { transform: scale(1); }
|
|
730
|
+
}
|
|
731
|
+
`;
|
|
732
|
+
document.head.appendChild(style);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Toggle chat window
|
|
737
|
+
*/
|
|
738
|
+
toggleChat() {
|
|
739
|
+
this.isOpen = !this.isOpen;
|
|
740
|
+
if (this.isOpen) {
|
|
741
|
+
this.chatWindow.classList.add('open');
|
|
742
|
+
this.chatButton.style.display = 'none';
|
|
743
|
+
this.showWelcomeMessage();
|
|
744
|
+
} else {
|
|
745
|
+
this.chatWindow.classList.remove('open');
|
|
746
|
+
this.chatButton.style.display = 'flex';
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Close chat window
|
|
752
|
+
*/
|
|
753
|
+
closeChat() {
|
|
754
|
+
this.isOpen = false;
|
|
755
|
+
this.chatWindow.classList.remove('open');
|
|
756
|
+
this.chatButton.style.display = 'flex';
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Show welcome message
|
|
761
|
+
*/
|
|
762
|
+
showWelcomeMessage() {
|
|
763
|
+
if (this.conversationHistory.length === 0) {
|
|
764
|
+
const welcomeMessage = this.getWelcomeMessage();
|
|
765
|
+
this.addMessage('celeste', welcomeMessage);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Get user-friendly welcome message
|
|
771
|
+
*/
|
|
772
|
+
getWelcomeMessage() {
|
|
773
|
+
const pageType = this.currentContext?.page || 'unknown';
|
|
774
|
+
const welcomeMessages = {
|
|
775
|
+
'home': "Hello there! I'm Celeste, your helpful assistant for this portfolio! I can help you explore the art, learn about whykusanagi, and discover all the amazing projects here. What would you like to know?",
|
|
776
|
+
'art': "Hi! I'm Celeste, here to help you explore the art gallery! I can discuss artistic styles, techniques, and help you discover pieces that interest you. What would you like to know?",
|
|
777
|
+
'celeste': "Hello! I'm Celeste AI, your chaotic Onee-san assistant. Ask me anything about who I am, my personality, or what makes me unique. What's on your mind?",
|
|
778
|
+
'references': "Hello! I'm here to explain character designs and references. I can discuss anatomy, color theory, and design inspiration. What would you like to know?",
|
|
779
|
+
'doujin': "Hello! I'm Celeste, guiding you through manga projects. I can summarize stories, discuss characters, and provide information on where to read. What interests you?",
|
|
780
|
+
'links': "Hello! I'm here to help you find and connect with whykusanagi on social media! I know all the platforms where whykusanagi can be found. What platform are you looking for?",
|
|
781
|
+
'tools': "Hello! I'm ready to help you understand and use available tools. I can explain how various utilities work. What do you need?",
|
|
782
|
+
'privacy': "Hello! I'm here to clarify privacy policies and legal information. I can explain data practices and terms of service. What do you need to know?",
|
|
783
|
+
'default': "Hello! I'm Celeste, your helpful assistant! How can I help you today?"
|
|
784
|
+
};
|
|
785
|
+
return welcomeMessages[pageType] || welcomeMessages.default;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Add message to chat
|
|
790
|
+
*/
|
|
791
|
+
addMessage(sender, text) {
|
|
792
|
+
const messageContainer = this.chatWindow.querySelector('.celeste-chat-messages');
|
|
793
|
+
const messageDiv = document.createElement('div');
|
|
794
|
+
messageDiv.className = `celeste-message ${sender}`;
|
|
795
|
+
messageDiv.innerHTML = `
|
|
796
|
+
<div class="celeste-message-bubble">${text}</div>
|
|
797
|
+
`;
|
|
798
|
+
messageContainer.appendChild(messageDiv);
|
|
799
|
+
messageContainer.scrollTop = messageContainer.scrollHeight;
|
|
800
|
+
|
|
801
|
+
// Store in conversation history
|
|
802
|
+
this.conversationHistory.push({ sender, text, timestamp: new Date() });
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Show typing indicator
|
|
807
|
+
*/
|
|
808
|
+
showTyping() {
|
|
809
|
+
const messageContainer = this.chatWindow.querySelector('.celeste-chat-messages');
|
|
810
|
+
const typingDiv = document.createElement('div');
|
|
811
|
+
typingDiv.className = 'celeste-message celeste';
|
|
812
|
+
typingDiv.innerHTML = `
|
|
813
|
+
<div class="celeste-message-bubble">
|
|
814
|
+
<div class="celeste-typing">
|
|
815
|
+
Celeste is typing
|
|
816
|
+
<div class="celeste-typing-dots">
|
|
817
|
+
<div class="celeste-typing-dot"></div>
|
|
818
|
+
<div class="celeste-typing-dot"></div>
|
|
819
|
+
<div class="celeste-typing-dot"></div>
|
|
820
|
+
</div>
|
|
821
|
+
</div>
|
|
822
|
+
</div>
|
|
823
|
+
`;
|
|
824
|
+
messageContainer.appendChild(typingDiv);
|
|
825
|
+
messageContainer.scrollTop = messageContainer.scrollHeight;
|
|
826
|
+
return typingDiv;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Remove typing indicator
|
|
831
|
+
*/
|
|
832
|
+
removeTyping(typingDiv) {
|
|
833
|
+
if (typingDiv && typingDiv.parentNode) {
|
|
834
|
+
typingDiv.parentNode.removeChild(typingDiv);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Send message
|
|
840
|
+
*/
|
|
841
|
+
async sendMessage() {
|
|
842
|
+
const inputField = this.chatWindow.querySelector('.celeste-input-field');
|
|
843
|
+
const message = inputField.value.trim();
|
|
844
|
+
|
|
845
|
+
if (!message) return;
|
|
846
|
+
|
|
847
|
+
// Add user message
|
|
848
|
+
this.addMessage('user', message);
|
|
849
|
+
inputField.value = '';
|
|
850
|
+
|
|
851
|
+
// Show typing indicator
|
|
852
|
+
const typingDiv = this.showTyping();
|
|
853
|
+
|
|
854
|
+
try {
|
|
855
|
+
// Send to Celeste AI
|
|
856
|
+
const response = await this.sendToCeleste(message);
|
|
857
|
+
this.removeTyping(typingDiv);
|
|
858
|
+
this.addMessage('celeste', response);
|
|
859
|
+
} catch (error) {
|
|
860
|
+
this.removeTyping(typingDiv);
|
|
861
|
+
this.addMessage('celeste', 'Sorry, I encountered an error. Please try again.');
|
|
862
|
+
console.error('❌ Error sending message to Celeste:', error);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* Send message to Celeste AI with timeout and error handling
|
|
868
|
+
* Uses secure backend proxy instead of direct API calls
|
|
869
|
+
*
|
|
870
|
+
* SECURITY: Browser never sees the API credential. Backend handles authentication.
|
|
871
|
+
*/
|
|
872
|
+
async sendToCeleste(message) {
|
|
873
|
+
// Build system prompt with capability context
|
|
874
|
+
const systemPrompt = this.buildEnhancedSystemPrompt();
|
|
875
|
+
|
|
876
|
+
// Get recent history for context
|
|
877
|
+
const recentHistory = this.conversationHistory.slice(-5);
|
|
878
|
+
|
|
879
|
+
// Create abort controller for timeout (45 second timeout)
|
|
880
|
+
const controller = new AbortController();
|
|
881
|
+
const timeoutId = setTimeout(() => controller.abort(), 45000);
|
|
882
|
+
|
|
883
|
+
try {
|
|
884
|
+
// Call backend proxy endpoint (configurable via this.proxyUrl)
|
|
885
|
+
// Backend proxy handles authentication with CELESTE_AGENT_KEY
|
|
886
|
+
// Browser never needs to know the credential
|
|
887
|
+
const proxyEndpoint = `${this.proxyUrl}/api/chat`;
|
|
888
|
+
const response = await fetch(proxyEndpoint, {
|
|
889
|
+
method: 'POST',
|
|
890
|
+
headers: {
|
|
891
|
+
'Content-Type': 'application/json'
|
|
892
|
+
// ✅ NO Authorization header needed - backend is authenticated
|
|
893
|
+
},
|
|
894
|
+
body: JSON.stringify({
|
|
895
|
+
message: message,
|
|
896
|
+
system_prompt: systemPrompt,
|
|
897
|
+
history: recentHistory
|
|
898
|
+
}),
|
|
899
|
+
signal: controller.signal
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
clearTimeout(timeoutId);
|
|
903
|
+
|
|
904
|
+
if (!response.ok) {
|
|
905
|
+
console.error(`❌ API Error: HTTP ${response.status}`);
|
|
906
|
+
|
|
907
|
+
if (response.status === 401) {
|
|
908
|
+
return 'Authentication error - invalid API key. Check server configuration.';
|
|
909
|
+
} else if (response.status === 503) {
|
|
910
|
+
return 'The service is currently unavailable. Please try again in a moment.';
|
|
911
|
+
} else {
|
|
912
|
+
return `API error (${response.status}). Please try again.`;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const data = await response.json();
|
|
917
|
+
|
|
918
|
+
// Handle error responses from backend
|
|
919
|
+
if (data.error) {
|
|
920
|
+
console.error('❌ Backend error:', data.error);
|
|
921
|
+
return `Service error: ${data.error}`;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Handle successful response from Celeste API
|
|
925
|
+
if (!data.choices || !data.choices[0] || !data.choices[0].message) {
|
|
926
|
+
console.error('❌ Invalid API response structure:', data);
|
|
927
|
+
return 'I received an empty response. Could you rephrase your question?';
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return data.choices[0].message.content;
|
|
931
|
+
|
|
932
|
+
} catch (error) {
|
|
933
|
+
clearTimeout(timeoutId);
|
|
934
|
+
|
|
935
|
+
if (error.name === 'AbortError') {
|
|
936
|
+
console.error('⏱️ Request timeout after 45 seconds');
|
|
937
|
+
return 'Your request took too long to process. This might be a complex question - try rephrasing it more simply.';
|
|
938
|
+
} else if (error instanceof TypeError && error.message.includes('Failed to fetch')) {
|
|
939
|
+
console.error('❌ Network error:', error);
|
|
940
|
+
return 'Network connection error. Please check your internet and try again.';
|
|
941
|
+
} else {
|
|
942
|
+
console.error('❌ Error communicating with Celeste:', error);
|
|
943
|
+
return 'An unexpected error occurred. Please try again.';
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Build enhanced system prompt with capability context
|
|
950
|
+
*/
|
|
951
|
+
buildEnhancedSystemPrompt() {
|
|
952
|
+
const pageType = this.currentContext?.page || 'unknown';
|
|
953
|
+
|
|
954
|
+
// Use celeste_essence.json from celesteCLI if available
|
|
955
|
+
if (this.celesteEssence) {
|
|
956
|
+
let prompt = this.celesteEssence.description || this.celesteEssence.character || '';
|
|
957
|
+
|
|
958
|
+
// Add page context
|
|
959
|
+
prompt += `\n\nCurrent page context: ${pageType}`;
|
|
960
|
+
|
|
961
|
+
// Add routing notice if enabled
|
|
962
|
+
if (this.celesteEssence.routing?.enabled && this.routingRules) {
|
|
963
|
+
prompt += '\n\nNote: NIKKE game queries should be handled with game-specific data. Detect game-related questions and respond with available game information.';
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
return prompt;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Fallback to default prompt
|
|
970
|
+
const basePrompt = this.getDefaultPrompt();
|
|
971
|
+
if (this.capabilities) {
|
|
972
|
+
const capSummary = this.buildCapabilitySummary();
|
|
973
|
+
return `${basePrompt}\n\n[Available Capabilities]\n${capSummary}`;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return basePrompt;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Detect user intent from message using routing rules
|
|
981
|
+
*/
|
|
982
|
+
detectIntent(message) {
|
|
983
|
+
if (!this.routingRules) return 'general';
|
|
984
|
+
|
|
985
|
+
const lowerMessage = message.toLowerCase();
|
|
986
|
+
const nikkeKeywords = this.routingRules.nikke_detection?.keywords || [];
|
|
987
|
+
const nikkePatterns = this.routingRules.nikke_detection?.patterns || [];
|
|
988
|
+
|
|
989
|
+
// Check keywords
|
|
990
|
+
for (const keyword of nikkeKeywords) {
|
|
991
|
+
if (lowerMessage.includes(keyword.toLowerCase())) {
|
|
992
|
+
return 'nikke';
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Check regex patterns
|
|
997
|
+
for (const pattern of nikkePatterns) {
|
|
998
|
+
try {
|
|
999
|
+
const regex = new RegExp(pattern, 'i');
|
|
1000
|
+
if (regex.test(message)) {
|
|
1001
|
+
return 'nikke';
|
|
1002
|
+
}
|
|
1003
|
+
} catch (e) {
|
|
1004
|
+
console.warn('Invalid regex pattern:', pattern);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
return 'general';
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Build a concise summary of capabilities for the prompt
|
|
1013
|
+
*/
|
|
1014
|
+
buildCapabilitySummary() {
|
|
1015
|
+
if (!this.capabilities || !this.capabilities.core_capabilities) {
|
|
1016
|
+
return 'No capabilities loaded.';
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const caps = this.capabilities.core_capabilities;
|
|
1020
|
+
const summary = Object.keys(caps).slice(0, 4).map(key => {
|
|
1021
|
+
const cap = caps[key];
|
|
1022
|
+
return `- ${cap.description}`;
|
|
1023
|
+
}).join('\n');
|
|
1024
|
+
|
|
1025
|
+
return summary;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Get environment-aware asset URL
|
|
1030
|
+
* Uses AssetConfig if available, otherwise returns original URL
|
|
1031
|
+
*/
|
|
1032
|
+
getAssetUrl(url) {
|
|
1033
|
+
if (window.AssetConfig && typeof window.AssetConfig.convertUrl === 'function') {
|
|
1034
|
+
return window.AssetConfig.convertUrl(url);
|
|
1035
|
+
}
|
|
1036
|
+
return url;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Generate a unique session ID
|
|
1041
|
+
*/
|
|
1042
|
+
generateSessionId() {
|
|
1043
|
+
return 'celeste_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Detect proxy URL from current page context
|
|
1048
|
+
* Production: uses same domain (Cloudflare Worker handles it)
|
|
1049
|
+
* Local dev: uses port 5001 (Docker mapping)
|
|
1050
|
+
*/
|
|
1051
|
+
detectProxyUrl() {
|
|
1052
|
+
const hostname = window.location.hostname;
|
|
1053
|
+
|
|
1054
|
+
// Production: use same domain (Cloudflare Worker handles it)
|
|
1055
|
+
if (hostname.includes('whykusanagi.xyz')) {
|
|
1056
|
+
return window.location.origin; // https://whykusanagi.xyz
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// Local dev: use port 5001 (Docker maps container:5000 -> host:5001)
|
|
1060
|
+
const currentPort = window.location.port;
|
|
1061
|
+
if (currentPort === '8000' || currentPort === '') {
|
|
1062
|
+
return 'http://localhost:5001';
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// Default fallback
|
|
1066
|
+
return 'http://localhost:5000';
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Initialize Celeste Agent when DOM is ready
|
|
1071
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
1072
|
+
// Don't initialize on privacy page
|
|
1073
|
+
if (window.location.pathname.includes('privacy')) {
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const celesteAgent = new CelesteAgent();
|
|
1078
|
+
celesteAgent.initialize();
|
|
1079
|
+
|
|
1080
|
+
// Make it globally available
|
|
1081
|
+
window.CelesteAgent = celesteAgent;
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
// Update context on page navigation
|
|
1085
|
+
window.addEventListener('popstate', () => {
|
|
1086
|
+
if (window.CelesteAgent) {
|
|
1087
|
+
window.CelesteAgent.initialize();
|
|
1088
|
+
}
|
|
1089
|
+
});
|