Changes for page Home

Last modified by Iris on 2026/04/14 17:40

From version 12.1
edited by Jiahao Lai
on 2026/03/26 15:46
Change comment: There is no comment for this version
To version 15.1
edited by Jiahao Lai
on 2026/03/26 16:08
Change comment: There is no comment for this version

Summary

Details

Page properties
Content
... ... @@ -189,51 +189,628 @@
189 189  
190 190  
191 191  {{html clean="false"}}
192 -<div id="llm-chat-widget" style="position:fixed;right:20px;bottom:20px;z-index:9999;"></div>
193 193  
194 -<script>
195 -document.addEventListener('DOMContentLoaded', function() {
196 - setTimeout(async () => {
193 +// Initialize marked library
194 +marked.use({ breaks: true });
195 +
196 +// Initialize variables
197 +let currentRequest = null;
198 +let abortController = null;
199 +let conversationHistory = [];
200 +let userSettings = {
201 + model: '',
202 + temperature: 0,
203 + stream: true,
204 + settingsCollapsed: false
205 +};
206 +let chatHistory;
207 +let chatInput;
208 +let sendButton;
209 +let stopButton;
210 +let modelSelect;
211 +let temperatureInput;
212 +let streamCheckbox;
213 +let chatWidget;
214 +let toggleChatButton;
215 +let settingsContainer;
216 +let settingsToggle;
217 +let newConvButton;
218 +let isResizing = false;
219 +let startX, startY, startWidth, startHeight;
220 +
221 +// Get the script tag
222 +const scriptTag = document.getElementById('chat-widget');
223 +
224 +XWikiAiAPI.setBaseURL(scriptTag.dataset.baseUrl || '');
225 +
226 +// Set the wiki name
227 +if (scriptTag && scriptTag.dataset.wikiName) {
228 + XWikiAiAPI.setWikiName(scriptTag.dataset.wikiName);
229 +} else {
230 + // Set default wiki name to 'xwiki' if not provided
231 + XWikiAiAPI.setWikiName('xwiki');
232 +}
233 +
234 +
235 +// Create the chat widget HTML dynamically
236 +function createChatWidget() {
237 + const chatWidgetElement = document.createElement('div');
238 + chatWidgetElement.id = 'chat-widget';
239 + chatWidgetElement.innerHTML = `
240 + <div id="resize-handle"></div>
241 + <div id="chat-widget-divider"></div>
242 + <div id="chat-container">
243 + <h2>XWiki AI Chat</h2>
244 + <div class="settings-container">
245 + <button id="new-conv">New</button>
246 + <button id="settings-toggle">Settings</button>
247 + <div class="settings-wrapper">
248 + <div class="settings-top-line"></div>
249 + <div class="settings">
250 + <div>
251 + <label for="model-select">Model:</label>
252 + <select id="model-select"></select>
253 + </div>
254 + <div>
255 + <label for="temperature-input">Temp:</label>
256 + <input type="number" id="temperature-input" min="0" max="2" step="0.1" value="0">
257 + </div>
258 + <div>
259 + <label for="stream-checkbox">Stream:</label>
260 + <input type="checkbox" id="stream-checkbox" checked>
261 + </div>
262 + </div>
263 + <div class="settings-bottom-line"></div>
264 + </div>
265 + </div>
266 + <div id="chat-history"></div>
267 + <textarea id="chat-input" placeholder="Type your message..."></textarea>
268 + <div id="button-container">
269 + <button id="send-button">Send</button>
270 + <button id="stop-button" style="display: none;">Stop</button>
271 + </div>
272 + </div>
273 + `;
274 + document.body.appendChild(chatWidgetElement);
275 + return chatWidgetElement;
276 +}
277 +
278 +// Create the toggle chat button HTML dynamically
279 +function createToggleChatButton() {
280 + const toggleChatButtonElement = document.createElement('button');
281 + toggleChatButtonElement.id = 'toggle-chat-button';
282 + toggleChatButtonElement.textContent = '✨ Chat';
283 + document.body.appendChild(toggleChatButtonElement);
284 + return toggleChatButtonElement;
285 +}
286 +
287 +// Create an expandable bubble with sources
288 +function createSourcesBubble(sources) {
289 + const sourcesBubble = document.createElement('div');
290 + sourcesBubble.classList.add('sources-bubble');
291 + const sourceLinks = sources.split('\n').map(source => {
292 + const trimmedSource = source.trim();
293 + return `<li><a href="${trimmedSource}" target="_blank">${trimmedSource}</a></li>`;
294 + }).join('');
295 + sourcesBubble.innerHTML = `
296 + <div class="sources-header">Sources <span class="expand-icon">+</span></div>
297 + <div class="sources-content hidden"><ul>${sourceLinks}</ul></div>
298 + `;
299 + sourcesBubble.querySelector('.sources-header').addEventListener('click', () => {
300 + sourcesBubble.querySelector('.sources-content').classList.toggle('hidden');
301 + sourcesBubble.querySelector('.expand-icon').textContent = sourcesBubble.querySelector('.sources-content').classList.contains('hidden') ? '+' : '-';
302 + });
303 + return sourcesBubble;
304 +}
305 +
306 +// Fetch available models and populate the select dropdown
307 +async function populateModelSelect() {
197 197   try {
198 - // 1. 先加载依赖库 marked.js(子 wiki 用的版本)
199 - await new Promise((resolve, reject) => {
200 - const markedScript = document.createElement('script');
201 - markedScript.src = 'https://docs.we-con.com.cn/webjars/wiki%3Awonway/marked/4.0.2/marked.min.js';
202 - markedScript.onload = resolve;
203 - markedScript.onerror = reject;
204 - document.body.appendChild(markedScript);
205 - });
206 - console.log("✅ marked.js 加载成功");
309 + const response = await XWikiAiAPI.getModels();
310 + const models = response.data;
311 + modelSelect.innerHTML = '';
312 + models.forEach(model => {
313 + const option = document.createElement('option');
314 + option.value = model.id;
315 + option.text = model.name;
316 + modelSelect.appendChild(option);
317 + });
318 + // Set the selected model to the user's stored setting or the first option
319 + const storedModel = userSettings.model;
320 + if (storedModel && models.some(model => model.id === storedModel)) {
321 + modelSelect.value = storedModel;
322 + } else {
323 + modelSelect.selectedIndex = 0;
324 + userSettings.model = modelSelect.value;
325 + }
326 + } catch (error) {
327 + console.error('Failed to fetch models:', error);
328 + }
329 +}
207 207  
208 - // 2. 加载 CSS
209 - await new Promise((resolve) => {
210 - const link = document.createElement('link');
211 - link.rel = 'stylesheet';
212 - link.href = 'https://docs.we-con.com.cn/webjars/wiki%3Awonway/application-ai-llm-chat-webjar/0.7.2/chatWidget.css';
213 - link.onload = resolve;
214 - document.head.appendChild(link);
215 - });
331 +// Initialize the chat widget and toggle button
332 +async function initializeChatWidget() {
333 + chatWidget = createChatWidget();
334 + toggleChatButton = createToggleChatButton();
216 216  
217 - // 3. 加载 chatWidget.js
218 - await new Promise((resolve) => {
219 - const script = document.createElement('script');
220 - script.src = 'https://docs.we-con.com.cn/webjars/wiki%3Awonway/application-ai-llm-chat-webjar/0.7.2/chatWidget.js';
221 - script.onload = resolve;
222 - document.body.appendChild(script);
223 - });
336 + // Get references to the dynamically created elements
337 + chatHistory = document.getElementById('chat-history');
338 + chatInput = document.getElementById('chat-input');
339 + sendButton = document.getElementById('send-button');
340 + stopButton = document.getElementById('stop-button');
341 + modelSelect = document.getElementById('model-select');
342 + temperatureInput = document.getElementById('temperature-input');
343 + streamCheckbox = document.getElementById('stream-checkbox');
344 + settingsContainer = document.querySelector('.settings-container');
345 + settingsToggle = document.getElementById('settings-toggle');
346 + newConvButton = document.getElementById('new-conv');
347 + const resizeHandle = document.getElementById('resize-handle');
348 + resizeHandle.addEventListener('mousedown', initResize);
349 + document.addEventListener('mousemove', resize);
350 + document.addEventListener('mouseup', stopResize);
224 224  
225 - // 4. 启动浮窗
226 - if (window.LLMChatWidget && typeof LLMChatWidget.init === 'function') {
227 - LLMChatWidget.init();
228 - console.log("✅ 浮窗启动成功!");
229 - } else {
230 - throw new Error("LLMChatWidget 未初始化");
231 - }
232 - } catch (e) {
233 - console.error("❌ 加载失败", e);
234 - alert("浮窗加载失败:" + e.message);
352 + // Populate the model select dropdown
353 + await populateModelSelect();
354 +
355 + // Load user settings from local storage
356 + loadUserSettings();
357 +
358 + // Update user settings when changed
359 + modelSelect.addEventListener('change', updateUserSettings);
360 + temperatureInput.addEventListener('input', updateUserSettings);
361 + streamCheckbox.addEventListener('change', updateUserSettings);
362 +
363 + // Add event listeners
364 + newConvButton.addEventListener('click', startNewConversation);
365 + sendButton.addEventListener('click', sendMessage);
366 + chatInput.addEventListener('keydown', handleChatInputKeydown);
367 + toggleChatButton.addEventListener('click', toggleChatWidget);
368 + chatInput.addEventListener('focus', handleChatInputFocus);
369 + chatInput.addEventListener('blur', handleChatInputBlur);
370 + settingsToggle.addEventListener('click', toggleSettings);
371 +
372 + // Load last conversation from local storage
373 + loadLastConversation();
374 +}
375 +
376 +
377 +function initResize(e) {
378 + isResizing = true;
379 + startX = e.clientX;
380 + startY = e.clientY;
381 + startWidth = parseInt(document.defaultView.getComputedStyle(chatWidget).width, 10);
382 + startHeight = parseInt(document.defaultView.getComputedStyle(chatWidget).height, 10);
383 + e.preventDefault();
384 +}
385 +
386 +function startResize(e) {
387 + isResizing = true;
388 + startX = e.clientX;
389 + startY = e.clientY;
390 + startWidth = parseInt(document.defaultView.getComputedStyle(chatWidget).width, 10);
391 + startHeight = parseInt(document.defaultView.getComputedStyle(chatWidget).height, 10);
392 + document.addEventListener('mousemove', resize);
393 + document.addEventListener('mouseup', stopResize);
394 + e.preventDefault();
395 +}
396 +
397 +function resize(e) {
398 + if (!isResizing) return;
399 + const width = startWidth - (e.clientX - startX);
400 + const height = startHeight - (e.clientY - startY);
401 + if (width > 300 && height > 400) {
402 + chatWidget.style.width = width + 'px';
403 + chatWidget.style.height = height + 'px';
235 235   }
236 - }, 1000);
237 -});
238 -</script>
405 +}
406 +
407 +function stopResize() {
408 + isResizing = false;
409 +}
410 +
411 +// Modify the toggleChatWidget function
412 +function toggleChatWidget() {
413 + if (isPanelMode) {
414 + chatWidget.style.display = chatWidget.style.display === 'none' ? 'flex' : 'none';
415 + } else {
416 + chatWidget.style.display = chatWidget.style.display === 'none' ? 'block' : 'none';
417 + }
418 +}
419 +
420 +// Handle new conversation
421 +function startNewConversation() {
422 + // Clear the conversation history
423 + conversationHistory = [];
424 + saveConversationHistory();
425 +
426 + // Clear the chat history
427 + chatHistory.innerHTML = '';
428 +}
429 +
430 +// Handle chat input keydown event
431 +function handleChatInputKeydown(event) {
432 + if (event.key === 'Enter' && !event.shiftKey) {
433 + event.preventDefault();
434 + sendMessage();
435 + }
436 +}
437 +
438 +// Toggle the visibility of the chat widget
439 +function toggleChatWidget() {
440 + chatWidget.style.display = chatWidget.style.display === 'none' ? 'block' : 'none';
441 +}
442 +
443 +// Handle chat input focus event
444 +function handleChatInputFocus() {
445 + chatWidget.classList.add('keyboard-open');
446 +}
447 +
448 +// Handle chat input blur event
449 +function handleChatInputBlur() {
450 + chatWidget.classList.remove('keyboard-open');
451 +}
452 +
453 +// Change the state of the send and stop buttons
454 +function changeBtnState(enabled) {
455 + sendButton.disabled = !enabled;
456 + stopButton.style.display = enabled ? 'none' : 'inline-block';
457 +}
458 +
459 +// Show the waiting animation
460 +function showWaitingAnimation() {
461 + const waitingLine = document.createElement('div');
462 + waitingLine.classList.add('waiting-line');
463 + waitingLine.innerHTML = `
464 + <div class="dot dot1"></div>
465 + <div class="dot dot2"></div>
466 + <div class="dot dot3"></div>
467 + `;
468 + chatHistory.appendChild(waitingLine);
469 + chatHistory.scrollTop = chatHistory.scrollHeight;
470 +}
471 +
472 +// Remove the waiting animation
473 +function removeWaitingAnimation() {
474 + const waitingLine = chatHistory.querySelector('.waiting-line');
475 + if (waitingLine) {
476 + waitingLine.remove();
477 + }
478 +}
479 +
480 +// Send a message to the assistant
481 +function sendMessage() {
482 + const userMessage = chatInput.value.trim();
483 + if (userMessage === '') return;
484 +
485 + // Add user message to conversation history
486 + conversationHistory.push({ role: 'user', content: userMessage });
487 +
488 + // Display user message in the chat history
489 + displayUserMessage(userMessage);
490 +
491 + // Clear the chat input
492 + chatInput.value = '';
493 +
494 + // Create a new abort controller for the request
495 + abortController = new AbortController();
496 + const signal = abortController.signal;
497 +
498 + // Create a new request with the full conversation history
499 + const request = new ChatCompletionRequest(
500 + modelSelect.value,
501 + parseFloat(temperatureInput.value),
502 + conversationHistory,
503 + streamCheckbox.checked
504 + );
505 +
506 + // Display the assistant message container in the chat history
507 + const assistantMessageElement = displayAssistantMessage('', modelSelect.options[modelSelect.selectedIndex].text, 0);
508 +
509 + // Show the waiting animation
510 + showWaitingAnimation();
511 +
512 + // Send the request to the API
513 + if (request.stream) {
514 + handleStreamingRequest(request, signal, assistantMessageElement);
515 + } else {
516 + handleNonStreamingRequest(request, signal, assistantMessageElement);
517 + }
518 +
519 + // Disable the send button and show the stop button
520 + changeBtnState(false);
521 +
522 + // Add event listener to the stop button
523 + stopButton.addEventListener('click', stopRequest);
524 +}
525 +
526 +// Handle streaming request
527 +function handleStreamingRequest(request, signal, assistantMessageElement) {
528 + let messageText = '';
529 + let sourcesText = '';
530 + const startTime = new Date().getTime();
531 + let updateTimer;
532 + let sourcesBubble = null;
533 +
534 + const assistantInfoElement = assistantMessageElement.previousElementSibling;
535 + updateTimer = setInterval(() => {
536 + updateResponseTime(startTime, assistantInfoElement);
537 + }, 100);
538 +
539 + XWikiAiAPI.getCompletions(request, messageChunk => {
540 + if (messageChunk.choices.length > 0 && messageChunk.choices[0].delta.content !== null) {
541 + const content = messageChunk.choices[0].delta.content;
542 +
543 + if (content.startsWith("Sources:")) {
544 + sourcesText += content.replace("Sources:", "").trim();
545 + if (!sourcesBubble) {
546 + sourcesBubble = createSourcesBubble(sourcesText);
547 + assistantMessageElement.parentElement.insertBefore(sourcesBubble, assistantMessageElement);
548 + } else {
549 + sourcesBubble.querySelector('.sources-content').innerHTML = sourcesText;
550 + }
551 + } else {
552 + messageText += content;
553 + assistantMessageElement.innerHTML = DOMPurify.sanitize(marked.parse(messageText), { FORBID_TAGS: ['style'], FORBID_ATTR: ['src'] });
554 + }
555 +
556 + removeWaitingAnimation();
557 + }
558 + }, signal)
559 + .then((usageData) => {
560 + if (messageText !== '') {
561 + conversationHistory.push({ role: 'assistant', content: assistantMessageElement.textContent });
562 + saveConversationHistory();
563 + }
564 + clearInterval(updateTimer);
565 +
566 + if (usageData) {
567 + displayUsageInfo(assistantInfoElement, usageData, startTime);
568 + }
569 + })
570 + .catch(error => {
571 + handleRequestError(error, updateTimer);
572 + })
573 + .finally(() => {
574 + changeBtnState(true);
575 + });
576 +}
577 +
578 +// Display usage information
579 +function displayUsageInfo(assistantLabel, usageData, startTime) {
580 + const responseTime = endTimer(startTime);
581 + assistantLabel.innerHTML = `<strong>Assistant (${modelSelect.options[modelSelect.selectedIndex].text})</strong><em> - &Delta;T ${responseTime.toFixed(1)}s </em>`;
582 +
583 + const usageSpan = document.createElement('span');
584 + usageSpan.classList.add('usage');
585 + usageSpan.innerHTML = '<em> - tokens<span class="usage-info">(&#128202;)</span></em>';
586 + assistantLabel.appendChild(usageSpan);
587 +
588 + const usageInfo = document.createElement('div');
589 + usageInfo.classList.add('usage-info-box');
590 + usageInfo.innerHTML = `
591 + <p>Prompt tokens: ${usageData.prompt_tokens}</p>
592 + <p>Completion tokens: ${usageData.completion_tokens}</p>
593 + <p>Total tokens: ${usageData.total_tokens}</p>
594 + `;
595 + usageSpan.appendChild(usageInfo);
596 +}
597 +
598 +
599 +// Handle non-streaming request
600 +function handleNonStreamingRequest(request, signal, assistantMessageElement) {
601 + const startTime = new Date().getTime();
602 +
603 + XWikiAiAPI.getCompletions(request, null, signal)
604 + .then(response => {
605 + handleNonStreamingResponse(response, startTime, assistantMessageElement);
606 + })
607 + .catch(error => {
608 + handleRequestError(error);
609 + })
610 + .finally(() => {
611 + changeBtnState(true);
612 + });
613 +}
614 +
615 +// Handle non-streaming response
616 +function handleNonStreamingResponse(response, startTime, assistantMessageElement) {
617 + const endTime = new Date().getTime();
618 + const responseTime = (endTime - startTime) / 1000;
619 +
620 + const assistantMessage = response.choices[0].message.content;
621 + const llmMemory = response.choices[0].message.memory;
622 + console.debug('LLM memory before response:', llmMemory);
623 +
624 + const sourcesMatch = assistantMessage.match(/Sources:([\s\S]*?)(?=\n\n|$)/);
625 + if (sourcesMatch) {
626 + const sources = sourcesMatch[1].trim();
627 + const content = assistantMessage.replace(sourcesMatch[0], '').trim();
628 + const sourcesBubble = createSourcesBubble(sources);
629 + assistantMessageElement.parentElement.insertBefore(sourcesBubble, assistantMessageElement);
630 + assistantMessageElement.innerHTML = DOMPurify.sanitize(marked.parse(content), { FORBID_TAGS: ['style'], FORBID_ATTR: ['src'] });
631 + } else {
632 + assistantMessageElement.innerHTML = DOMPurify.sanitize(marked.parse(assistantMessage), { FORBID_TAGS: ['style'], FORBID_ATTR: ['src'] });
633 + }
634 +
635 + conversationHistory.push({ role: 'assistant', content: assistantMessage });
636 + chatHistory.scrollTop = chatHistory.scrollHeight;
637 + removeWaitingAnimation();
638 + saveConversationHistory();
639 +
640 + const assistantInfoElement = assistantMessageElement.previousElementSibling.querySelector('.msg-info');
641 + if (assistantInfoElement) {
642 + assistantInfoElement.innerHTML = `<strong>Assistant (${modelSelect.options[modelSelect.selectedIndex].text})</strong> - <em>&Delta;T ${responseTime.toFixed(1)}s </em>`;
643 +
644 + if (response.usage) {
645 + displayUsageInfo(assistantInfoElement, response.usage, startTime);
646 + }
647 + } else {
648 + console.warn('Assistant info element not found');
649 + }
650 +}
651 +
652 +function endTimer(startTime) {
653 + const endTime = new Date().getTime();
654 + const responseTime = (endTime - startTime) / 1000;
655 + return responseTime;
656 +}
657 +
658 +// Handle request error
659 +function handleRequestError(error, updateTimer) {
660 + if (error.name === 'AbortError') {
661 + console.log('Request aborted');
662 + } else {
663 + console.error('Failed to get chat completions:', error);
664 + displayErrorMessage('An error occurred: ' + error.message);
665 + }
666 + removeWaitingAnimation();
667 + clearInterval(updateTimer);
668 +}
669 +
670 +// Stop the current request
671 +function stopRequest() {
672 + if (abortController) {
673 + console.log('Aborting request...');
674 + abortController.abort();
675 + abortController = null;
676 + removeWaitingAnimation();
677 + changeBtnState(true);
678 + }
679 +}
680 +
681 +// Display user message in the chat history
682 +function displayUserMessage(message) {
683 + const messageElement = document.createElement('div');
684 + messageElement.classList.add('user-message');
685 +
686 + const userLabel = document.createElement('div');
687 + userLabel.classList.add('msg-info');
688 + userLabel.textContent = 'User:';
689 +
690 + const userMessageContent = document.createElement('div');
691 + userMessageContent.classList.add('message-content');
692 + userMessageContent.textContent = message;
693 +
694 + messageElement.appendChild(userLabel);
695 + messageElement.appendChild(userMessageContent);
696 +
697 + chatHistory.appendChild(messageElement);
698 + chatHistory.scrollTop = chatHistory.scrollHeight;
699 +}
700 +
701 +// Display assistant message in the chat history
702 +function displayAssistantMessage(message, modelName = '', responseTime = null) {
703 + const messageElement = document.createElement('div');
704 + messageElement.classList.add('assistant');
705 +
706 + const assistantLabel = document.createElement('div');
707 + assistantLabel.classList.add('msg-info');
708 +
709 + if (modelName && responseTime !== null) {
710 + assistantLabel.innerHTML = `<strong>Assistant (${modelName})</strong><em> - &Delta;T ${responseTime.toFixed(1)}s:</em>`;
711 + } else {
712 + assistantLabel.innerHTML = '<strong>Assistant:</strong>';
713 + }
714 +
715 + const assistantMessageContent = document.createElement('div');
716 + assistantMessageContent.classList.add('message-text');
717 + assistantMessageContent.innerHTML = DOMPurify.sanitize(marked.parse(message), { FORBID_TAGS: ['style'], FORBID_ATTR: ['src'] });
718 +
719 + messageElement.appendChild(assistantLabel);
720 + messageElement.appendChild(assistantMessageContent);
721 +
722 + chatHistory.appendChild(messageElement);
723 + chatHistory.scrollTop = chatHistory.scrollHeight;
724 +
725 + return assistantMessageContent;
726 +}
727 +
728 +
729 +
730 +// Update the response time in the assistant label
731 +function updateResponseTime(startTime, assistantInfoElement) {
732 + const currentTime = new Date().getTime();
733 + const elapsedTime = (currentTime - startTime) / 1000;
734 + assistantInfoElement.innerHTML = `<strong>Assistant (${modelSelect.options[modelSelect.selectedIndex].text})</strong> - &Delta;T ${elapsedTime.toFixed(1)}s:`;
735 +}
736 +
737 +// Display error message in the chat history
738 +function displayErrorMessage(message) {
739 + const messageElement = document.createElement('div');
740 + messageElement.classList.add('error-message');
741 + messageElement.textContent = message;
742 +
743 + chatHistory.appendChild(messageElement);
744 + chatHistory.scrollTop = chatHistory.scrollHeight;
745 +}
746 +
747 +// Update user settings
748 +function updateUserSettings() {
749 + userSettings.model = modelSelect.value;
750 + userSettings.temperature = parseFloat(temperatureInput.value);
751 + userSettings.stream = streamCheckbox.checked;
752 + saveUserSettings();
753 +}
754 +
755 +// Save user settings to local storage
756 +function saveUserSettings() {
757 + localStorage.setItem('userSettings', JSON.stringify(userSettings));
758 +}
759 +
760 +// Load user settings from local storage
761 +function loadUserSettings() {
762 + const storedSettings = localStorage.getItem('userSettings');
763 + if (storedSettings) {
764 + userSettings = JSON.parse(storedSettings);
765 + modelSelect.value = userSettings.model;
766 + temperatureInput.value = userSettings.temperature;
767 + streamCheckbox.checked = userSettings.stream;
768 +
769 + // Apply the collapsed state to the settings section
770 + const settingsWrapper = document.querySelector('.settings-wrapper');
771 + if (userSettings.settingsCollapsed) {
772 + settingsWrapper.classList.add('collapsed');
773 + } else {
774 + settingsWrapper.classList.remove('collapsed');
775 + }
776 + }
777 +}
778 +
779 +
780 +// Save conversation history to local storage
781 +function saveConversationHistory() {
782 + if (conversationHistory.length === 0) {
783 + localStorage.removeItem('conversationHistory');
784 + } else {
785 + localStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
786 + }
787 +}
788 +
789 +// Load last conversation from local storage
790 +function loadLastConversation() {
791 + const storedConversation = localStorage.getItem('conversationHistory');
792 + if (storedConversation) {
793 + conversationHistory = JSON.parse(storedConversation);
794 + conversationHistory.forEach(message => {
795 + if (message.role === 'user') {
796 + displayUserMessage(message.content);
797 + } else if (message.role === 'assistant') {
798 + displayAssistantMessage(message.content);
799 + }
800 + });
801 +}
802 +}
803 +
804 +// Toggle settings visibility
805 +function toggleSettings() {
806 + const settingsWrapper = document.querySelector('.settings-wrapper');
807 + settingsWrapper.classList.toggle('collapsed');
808 + userSettings.settingsCollapsed = settingsWrapper.classList.contains('collapsed');
809 + saveUserSettings();
810 +}
811 +
812 +
813 +// Call the initialization function when the DOM content is loaded
814 +document.addEventListener('DOMContentLoaded', initializeChatWidget);
815 +
239 239  {{/html}}