Ab-Romia commited on
Commit
7d37cda
·
verified ·
1 Parent(s): 909b00f

Update static/app.js

Browse files
Files changed (1) hide show
  1. static/app.js +277 -72
static/app.js CHANGED
@@ -12,13 +12,28 @@ class ContextAwareApp {
12
  taskSelect: document.getElementById('task-select'),
13
  charCount: document.getElementById('char-count'),
14
  wordCount: document.getElementById('word-count'),
 
 
 
 
 
 
 
 
 
 
15
  };
 
16
  // Application state
17
  this.state = {
18
  isIndexing: false,
19
  isGenerating: false,
20
  isIndexed: false,
 
 
 
21
  };
 
22
  this.initialize();
23
  }
24
 
@@ -26,25 +41,28 @@ class ContextAwareApp {
26
  * Sets up the application, event listeners, and initial state.
27
  */
28
  async initialize() {
29
- this.addEventListeners();
30
-
31
- // Check API configuration first
32
- const apiConfigured = await this.checkApiConfiguration();
33
-
34
- if (apiConfigured) {
35
  this.addMessageToChat(
36
- "Welcome! Here's how to get started:\n1. Paste your context into the 'Knowledge Base' on the left.\n2. Click 'Index Context' for Q&A, or select a different action from the dropdown.\n3. Provide a prompt if needed and click the send button.",
37
- 'ai'
 
 
 
 
 
38
  );
 
 
39
  }
40
-
41
- this.updateUI();
42
- }
43
 
44
  /**
45
  * Binds all necessary event listeners to DOM elements.
46
  */
47
  addEventListeners() {
 
48
  this.elements.indexContextBtn.addEventListener('click', () => this.handleIndexContext());
49
  this.elements.clearContextBtn.addEventListener('click', () => this.handleClearContext());
50
  this.elements.sendButton.addEventListener('click', () => this.handleSubmit());
@@ -56,12 +74,194 @@ class ContextAwareApp {
56
  });
57
  this.elements.contextInput.addEventListener('input', () => this.updateContextStats());
58
  this.elements.chatInput.addEventListener('input', () => this.autoResizeTextarea(this.elements.chatInput));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
60
 
61
  /**
62
  * Main handler for the send button. Directs to the correct function based on the selected task.
63
  */
64
  handleSubmit() {
 
 
 
 
 
 
 
 
 
65
  const selectedTask = this.elements.taskSelect.value;
66
  if (selectedTask === 'q_and_a') {
67
  this.handleSendPrompt();
@@ -70,29 +270,15 @@ class ContextAwareApp {
70
  }
71
  }
72
 
73
- async checkApiConfiguration() {
74
- try {
75
- const response = await fetch('/debug');
76
- const config = await response.json();
77
-
78
- if (!config.api_key_configured) {
79
- this.addMessageToChat(
80
- "⚠️ **Configuration Issue**: OpenRouter API key is not set. " +
81
- "Please contact the administrator to configure the OPENROUTER_API_KEY environment variable.",
82
- 'system'
83
- );
84
- return false;
85
- }
86
- return true;
87
- } catch (error) {
88
- console.error('Error checking API configuration:', error);
89
- return false;
90
- }
91
- }
92
  /**
93
  * Handles the logic for indexing the provided context.
94
  */
95
  async handleIndexContext() {
 
 
 
 
 
96
  const context = this.elements.contextInput.value.trim();
97
  if (context.length < 20) {
98
  this.showStatus('Context is too short. Please provide at least 20 characters.', 'error');
@@ -106,7 +292,10 @@ class ContextAwareApp {
106
  try {
107
  const response = await fetch('/api/v1/index', {
108
  method: 'POST',
109
- headers: { 'Content-Type': 'application/json' },
 
 
 
110
  body: JSON.stringify({ context }),
111
  });
112
 
@@ -147,7 +336,10 @@ class ContextAwareApp {
147
  try {
148
  const response = await fetch('/api/v1/generate', {
149
  method: 'POST',
150
- headers: { 'Content-Type': 'application/json' },
 
 
 
151
  body: JSON.stringify({ prompt }),
152
  });
153
 
@@ -197,7 +389,10 @@ class ContextAwareApp {
197
  try {
198
  const response = await fetch('/api/v1/task', {
199
  method: 'POST',
200
- headers: { 'Content-Type': 'application/json' },
 
 
 
201
  body: JSON.stringify({ context, task_type, prompt }),
202
  });
203
 
@@ -218,7 +413,6 @@ class ContextAwareApp {
218
  }
219
  }
220
 
221
-
222
  /**
223
  * Clears the context input and the indexed data on the backend.
224
  */
@@ -230,7 +424,12 @@ class ContextAwareApp {
230
  this.showStatus('Clearing knowledge base...', 'loading');
231
 
232
  try {
233
- await fetch('/api/v1/clear_index', { method: 'POST' });
 
 
 
 
 
234
  this.showStatus('Knowledge base cleared. Ready for new context.', 'success');
235
  } catch (error) {
236
  this.showStatus(`Error clearing index: ${error.message}`, 'error');
@@ -243,16 +442,22 @@ class ContextAwareApp {
243
  updateUI() {
244
  const hasContext = this.elements.contextInput.value.trim().length > 10;
245
  const isQandA = this.elements.taskSelect.value === 'q_and_a';
 
246
 
247
- this.elements.indexContextBtn.disabled = this.state.isIndexing || !hasContext;
 
248
 
249
  if (isQandA) {
250
- this.elements.sendButton.disabled = this.state.isGenerating || !this.state.isIndexed;
251
  } else {
252
- this.elements.sendButton.disabled = this.state.isGenerating || !hasContext;
253
  }
254
- }
255
 
 
 
 
 
 
256
 
257
  /**
258
  * Displays a status message to the user.
@@ -279,39 +484,39 @@ class ContextAwareApp {
279
  * @param {string} message - The message content.
280
  * @param {'user'|'ai'|'system'} sender - The sender of the message.
281
  */
282
- addMessageToChat(message, sender) {
283
- const messageDiv = document.createElement('div');
284
- messageDiv.className = 'chat-message flex items-start space-x-3 animate-fade-in';
285
- const timestamp = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
286
-
287
- const icons = {
288
- user: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`,
289
- ai: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>`,
290
- system: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`
291
- };
292
- const bubbleClasses = {
293
- user: 'bg-gradient-to-br from-emerald-500 to-teal-600',
294
- ai: 'bg-gradient-to-br from-indigo-500 to-purple-600',
295
- system: 'bg-gradient-to-br from-red-500 to-orange-600'
296
- };
297
-
298
- // Use marked to parse markdown, but sanitize first
299
- const formattedMessage = sender === 'user' ?
300
- this.escapeHtml(message).replace(/\n/g, '<br>') :
301
- marked.parse(message);
302
-
303
- messageDiv.innerHTML = `
304
- <div class="w-8 h-8 ${bubbleClasses[sender]} rounded-full flex items-center justify-center flex-shrink-0 text-white">${icons[sender]}</div>
305
- <div class="flex-1">
306
- <div class="bg-slate-800/50 rounded-xl p-4 border border-slate-600/30">
307
- <div class="text-slate-200 leading-relaxed markdown-content">${formattedMessage}</div>
308
- </div>
309
- <div class="text-xs text-slate-500 mt-2">${timestamp}</div>
310
- </div>`;
311
-
312
- this.elements.chatContainer.appendChild(messageDiv);
313
- this.elements.chatContainer.scrollTop = this.elements.chatContainer.scrollHeight;
314
- }
315
 
316
  /**
317
  * Updates character and word counts for the context input.
 
12
  taskSelect: document.getElementById('task-select'),
13
  charCount: document.getElementById('char-count'),
14
  wordCount: document.getElementById('word-count'),
15
+ // API Key elements
16
+ apiKeyInput: document.getElementById('api-key-input'),
17
+ testApiKeyBtn: document.getElementById('test-api-key'),
18
+ saveApiKeyBtn: document.getElementById('save-api-key'),
19
+ apiKeyStatus: document.getElementById('api-key-status'),
20
+ apiStatusIcon: document.getElementById('api-status-icon'),
21
+ apiStatusText: document.getElementById('api-status-text'),
22
+ toggleApiSection: document.getElementById('toggle-api-section'),
23
+ apiKeyContent: document.getElementById('api-key-content'),
24
+ toggleIcon: document.getElementById('toggle-icon'),
25
  };
26
+
27
  // Application state
28
  this.state = {
29
  isIndexing: false,
30
  isGenerating: false,
31
  isIndexed: false,
32
+ apiKeyValidated: false,
33
+ userApiKey: '',
34
+ apiSectionCollapsed: false,
35
  };
36
+
37
  this.initialize();
38
  }
39
 
 
41
  * Sets up the application, event listeners, and initial state.
42
  */
43
  async initialize() {
44
+ this.addEventListeners();
45
+ this.loadStoredApiKey();
46
+
47
+ // Show welcome message
 
 
48
  this.addMessageToChat(
49
+ "👋 **Welcome to ContextIQ!**\n\n" +
50
+ "To get started:\n" +
51
+ "1. **Enter your OpenRouter API key** in the configuration section above\n" +
52
+ "2. **Add your context** in the Knowledge Base on the left\n" +
53
+ "3. **Index the context** and start asking questions!\n\n" +
54
+ "🆓 You can get a free API key from [openrouter.ai](https://openrouter.ai) - no credit card required!",
55
+ 'system'
56
  );
57
+
58
+ this.updateUI();
59
  }
 
 
 
60
 
61
  /**
62
  * Binds all necessary event listeners to DOM elements.
63
  */
64
  addEventListeners() {
65
+ // Existing listeners
66
  this.elements.indexContextBtn.addEventListener('click', () => this.handleIndexContext());
67
  this.elements.clearContextBtn.addEventListener('click', () => this.handleClearContext());
68
  this.elements.sendButton.addEventListener('click', () => this.handleSubmit());
 
74
  });
75
  this.elements.contextInput.addEventListener('input', () => this.updateContextStats());
76
  this.elements.chatInput.addEventListener('input', () => this.autoResizeTextarea(this.elements.chatInput));
77
+
78
+ // API Key listeners
79
+ this.elements.testApiKeyBtn.addEventListener('click', () => this.testApiKey());
80
+ this.elements.saveApiKeyBtn.addEventListener('click', () => this.saveApiKey());
81
+ this.elements.apiKeyInput.addEventListener('input', () => this.onApiKeyInputChange());
82
+ this.elements.apiKeyInput.addEventListener('keydown', e => {
83
+ if (e.key === 'Enter') {
84
+ e.preventDefault();
85
+ this.testApiKey();
86
+ }
87
+ });
88
+
89
+ // Toggle API section
90
+ this.elements.toggleApiSection.addEventListener('click', () => this.toggleApiKeySection());
91
+ }
92
+
93
+ /**
94
+ * Load stored API key from localStorage if available
95
+ */
96
+ loadStoredApiKey() {
97
+ const storedKey = localStorage.getItem('openrouter_api_key');
98
+ if (storedKey) {
99
+ this.elements.apiKeyInput.value = storedKey;
100
+ this.state.userApiKey = storedKey;
101
+ this.testApiKey(true); // Test silently on load
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Handle API key input changes
107
+ */
108
+ onApiKeyInputChange() {
109
+ const apiKey = this.elements.apiKeyInput.value.trim();
110
+ this.state.apiKeyValidated = false;
111
+ this.updateApiKeyStatus('pending', 'Enter API key and click Test');
112
+ this.updateUI();
113
+ }
114
+
115
+ /**
116
+ * Test the API key validity
117
+ */
118
+ async testApiKey(silent = false) {
119
+ const apiKey = this.elements.apiKeyInput.value.trim();
120
+
121
+ if (!apiKey) {
122
+ this.updateApiKeyStatus('error', 'Please enter an API key');
123
+ return;
124
+ }
125
+
126
+ if (!apiKey.startsWith('sk-or-')) {
127
+ this.updateApiKeyStatus('error', 'OpenRouter API keys should start with "sk-or-"');
128
+ return;
129
+ }
130
+
131
+ if (!silent) {
132
+ this.updateApiKeyStatus('testing', 'Testing API key...');
133
+ this.elements.testApiKeyBtn.disabled = true;
134
+ }
135
+
136
+ try {
137
+ const response = await fetch('/api/v1/test-api-key', {
138
+ method: 'POST',
139
+ headers: {
140
+ 'Content-Type': 'application/json',
141
+ 'X-API-Key': apiKey
142
+ },
143
+ body: JSON.stringify({ api_key: apiKey }),
144
+ });
145
+
146
+ if (response.ok) {
147
+ this.state.apiKeyValidated = true;
148
+ this.state.userApiKey = apiKey;
149
+ this.updateApiKeyStatus('success', 'API key is valid!');
150
+
151
+ if (!silent) {
152
+ this.addMessageToChat(
153
+ "✅ **API Key Validated!** You can now use the assistant. Start by adding some context to the Knowledge Base.",
154
+ 'system'
155
+ );
156
+ }
157
+
158
+ // Auto-collapse the API section after successful validation
159
+ if (!this.state.apiSectionCollapsed) {
160
+ this.toggleApiKeySection();
161
+ }
162
+ } else {
163
+ const error = await response.json();
164
+ this.state.apiKeyValidated = false;
165
+ this.updateApiKeyStatus('error', `Invalid API key: ${error.detail || 'Unknown error'}`);
166
+ }
167
+ } catch (error) {
168
+ this.state.apiKeyValidated = false;
169
+ this.updateApiKeyStatus('error', `Error testing API key: ${error.message}`);
170
+ } finally {
171
+ this.elements.testApiKeyBtn.disabled = false;
172
+ this.updateUI();
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Save the API key to localStorage
178
+ */
179
+ saveApiKey() {
180
+ const apiKey = this.elements.apiKeyInput.value.trim();
181
+
182
+ if (!this.state.apiKeyValidated) {
183
+ this.updateApiKeyStatus('error', 'Please test the API key first');
184
+ return;
185
+ }
186
+
187
+ try {
188
+ localStorage.setItem('openrouter_api_key', apiKey);
189
+ this.updateApiKeyStatus('success', 'API key saved locally!');
190
+
191
+ this.addMessageToChat(
192
+ "💾 **API Key Saved!** Your key is stored locally and will be remembered for future sessions.",
193
+ 'system'
194
+ );
195
+ } catch (error) {
196
+ this.updateApiKeyStatus('error', 'Failed to save API key locally');
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Update API key status display
202
+ */
203
+ updateApiKeyStatus(status, message) {
204
+ const statusElement = this.elements.apiKeyStatus;
205
+ const iconElement = this.elements.apiStatusIcon;
206
+ const textElement = this.elements.apiStatusText;
207
+
208
+ statusElement.classList.remove('hidden');
209
+ statusElement.className = 'p-3 rounded-lg text-sm';
210
+
211
+ switch (status) {
212
+ case 'testing':
213
+ statusElement.classList.add('bg-blue-500/20', 'text-blue-300', 'border', 'border-blue-500/30');
214
+ iconElement.className = 'w-3 h-3 bg-blue-500 rounded-full animate-pulse';
215
+ textElement.textContent = 'Testing...';
216
+ break;
217
+ case 'success':
218
+ statusElement.classList.add('bg-green-500/20', 'text-green-300', 'border', 'border-green-500/30');
219
+ iconElement.className = 'w-3 h-3 bg-green-500 rounded-full';
220
+ textElement.textContent = 'API Key Valid';
221
+ break;
222
+ case 'error':
223
+ statusElement.classList.add('bg-red-500/20', 'text-red-300', 'border', 'border-red-500/30');
224
+ iconElement.className = 'w-3 h-3 bg-red-500 rounded-full';
225
+ textElement.textContent = 'API Key Required';
226
+ break;
227
+ case 'pending':
228
+ statusElement.classList.add('bg-yellow-500/20', 'text-yellow-300', 'border', 'border-yellow-500/30');
229
+ iconElement.className = 'w-3 h-3 bg-yellow-500 rounded-full';
230
+ textElement.textContent = 'API Key Pending';
231
+ break;
232
+ }
233
+
234
+ statusElement.textContent = message;
235
+ }
236
+
237
+ /**
238
+ * Toggle API key section visibility
239
+ */
240
+ toggleApiKeySection() {
241
+ this.state.apiSectionCollapsed = !this.state.apiSectionCollapsed;
242
+
243
+ if (this.state.apiSectionCollapsed) {
244
+ this.elements.apiKeyContent.style.display = 'none';
245
+ this.elements.toggleIcon.style.transform = 'rotate(-90deg)';
246
+ } else {
247
+ this.elements.apiKeyContent.style.display = 'block';
248
+ this.elements.toggleIcon.style.transform = 'rotate(0deg)';
249
+ }
250
  }
251
 
252
  /**
253
  * Main handler for the send button. Directs to the correct function based on the selected task.
254
  */
255
  handleSubmit() {
256
+ if (!this.state.apiKeyValidated) {
257
+ this.updateApiKeyStatus('error', 'Please enter and test a valid API key first');
258
+ this.addMessageToChat(
259
+ "🔑 **API Key Required**: Please enter your OpenRouter API key in the configuration section above to use the assistant.",
260
+ 'system'
261
+ );
262
+ return;
263
+ }
264
+
265
  const selectedTask = this.elements.taskSelect.value;
266
  if (selectedTask === 'q_and_a') {
267
  this.handleSendPrompt();
 
270
  }
271
  }
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  /**
274
  * Handles the logic for indexing the provided context.
275
  */
276
  async handleIndexContext() {
277
+ if (!this.state.apiKeyValidated) {
278
+ this.updateApiKeyStatus('error', 'Please enter and test a valid API key first');
279
+ return;
280
+ }
281
+
282
  const context = this.elements.contextInput.value.trim();
283
  if (context.length < 20) {
284
  this.showStatus('Context is too short. Please provide at least 20 characters.', 'error');
 
292
  try {
293
  const response = await fetch('/api/v1/index', {
294
  method: 'POST',
295
+ headers: {
296
+ 'Content-Type': 'application/json',
297
+ 'X-API-Key': this.state.userApiKey
298
+ },
299
  body: JSON.stringify({ context }),
300
  });
301
 
 
336
  try {
337
  const response = await fetch('/api/v1/generate', {
338
  method: 'POST',
339
+ headers: {
340
+ 'Content-Type': 'application/json',
341
+ 'X-API-Key': this.state.userApiKey
342
+ },
343
  body: JSON.stringify({ prompt }),
344
  });
345
 
 
389
  try {
390
  const response = await fetch('/api/v1/task', {
391
  method: 'POST',
392
+ headers: {
393
+ 'Content-Type': 'application/json',
394
+ 'X-API-Key': this.state.userApiKey
395
+ },
396
  body: JSON.stringify({ context, task_type, prompt }),
397
  });
398
 
 
413
  }
414
  }
415
 
 
416
  /**
417
  * Clears the context input and the indexed data on the backend.
418
  */
 
424
  this.showStatus('Clearing knowledge base...', 'loading');
425
 
426
  try {
427
+ await fetch('/api/v1/clear_index', {
428
+ method: 'POST',
429
+ headers: {
430
+ 'X-API-Key': this.state.userApiKey
431
+ }
432
+ });
433
  this.showStatus('Knowledge base cleared. Ready for new context.', 'success');
434
  } catch (error) {
435
  this.showStatus(`Error clearing index: ${error.message}`, 'error');
 
442
  updateUI() {
443
  const hasContext = this.elements.contextInput.value.trim().length > 10;
444
  const isQandA = this.elements.taskSelect.value === 'q_and_a';
445
+ const hasValidApiKey = this.state.apiKeyValidated;
446
 
447
+ // Disable buttons if no API key
448
+ this.elements.indexContextBtn.disabled = this.state.isIndexing || !hasContext || !hasValidApiKey;
449
 
450
  if (isQandA) {
451
+ this.elements.sendButton.disabled = this.state.isGenerating || !this.state.isIndexed || !hasValidApiKey;
452
  } else {
453
+ this.elements.sendButton.disabled = this.state.isGenerating || !hasContext || !hasValidApiKey;
454
  }
 
455
 
456
+ // Update API key input button states
457
+ const apiKeyEntered = this.elements.apiKeyInput.value.trim().length > 0;
458
+ this.elements.testApiKeyBtn.disabled = !apiKeyEntered;
459
+ this.elements.saveApiKeyBtn.disabled = !this.state.apiKeyValidated;
460
+ }
461
 
462
  /**
463
  * Displays a status message to the user.
 
484
  * @param {string} message - The message content.
485
  * @param {'user'|'ai'|'system'} sender - The sender of the message.
486
  */
487
+ addMessageToChat(message, sender) {
488
+ const messageDiv = document.createElement('div');
489
+ messageDiv.className = 'chat-message flex items-start space-x-3 animate-fade-in';
490
+ const timestamp = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
491
+
492
+ const icons = {
493
+ user: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`,
494
+ ai: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>`,
495
+ system: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>`
496
+ };
497
+ const bubbleClasses = {
498
+ user: 'bg-gradient-to-br from-emerald-500 to-teal-600',
499
+ ai: 'bg-gradient-to-br from-indigo-500 to-purple-600',
500
+ system: 'bg-gradient-to-br from-blue-500 to-cyan-600'
501
+ };
502
+
503
+ // Use marked to parse markdown, but sanitize first
504
+ const formattedMessage = sender === 'user' ?
505
+ this.escapeHtml(message).replace(/\n/g, '<br>') :
506
+ marked.parse(message);
507
+
508
+ messageDiv.innerHTML = `
509
+ <div class="w-8 h-8 ${bubbleClasses[sender]} rounded-full flex items-center justify-center flex-shrink-0 text-white">${icons[sender]}</div>
510
+ <div class="flex-1">
511
+ <div class="bg-slate-800/50 rounded-xl p-4 border border-slate-600/30">
512
+ <div class="text-slate-200 leading-relaxed markdown-content">${formattedMessage}</div>
513
+ </div>
514
+ <div class="text-xs text-slate-500 mt-2">${timestamp}</div>
515
+ </div>`;
516
+
517
+ this.elements.chatContainer.appendChild(messageDiv);
518
+ this.elements.chatContainer.scrollTop = this.elements.chatContainer.scrollHeight;
519
+ }
520
 
521
  /**
522
  * Updates character and word counts for the context input.