Wiki source code of Home

Version 15.1 by Jiahao Lai on 2026/03/26 16:08

Hide last authors
Wecon 1.1 1 {{include reference="Help.Code.VelocityMacros"/}}
2
3 {{velocity output="false"}}
4 #macro (display4Cards $cards)
5 <div class="row">
6 #foreach ($card in $cards)
7 ## See http://getbootstrap.com/css/#grid-responsive-resets .
8 #if ($foreach.index > 0 && $foreach.index % 2 == 0)
9 <div class="clearfix visible-sm-block "></div>
10 #end
11 #if ($foreach.index > 0 && $foreach.index % 3 == 0)
12 <div class="clearfix visible-md-block"></div>
13 #end
14 #if ($foreach.index > 0 && $foreach.index % 4 == 0)
15 <div class="clearfix visible-lg-block"></div>
16 #end
17 <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3">
18 #helpExampleCard($card)
19 </div>
20 #end
21 </div>
22 #end
23
24
25 #set ($PI = [{
26 'icon': 'fa fa-file-text-o',
27 'title': "Manual",
28 'description':'Mainly software configuration manuals',
29 'documentation': "https://docs.we-con.com.cn/bin/view/PIStudio/2.Installation%20Software/"
30 }, {
31 'icon': 'fa fa-support',
32 'title': "Demo",
33 'description': 'Abundant functions are demonstrated through different demo',
34 'documentation': "https://docs.we-con.com.cn/bin/view/PIStudio/2%20Demo/"
35 }, {
36 'icon': 'fa fa-play-circle',
37 'title': "Videos",
38 'description': 'Video shows function more clearly',
39 'documentation': "https://www.youtube.com/playlist?list=PL_Bpnb2RgaktphrxRaCpFA809H_0xs-cU"
40 },{
41 'icon': 'fa fa-download',
42 'title': "Download",
43 'description': 'Software Download',
44 'documentation': "https://docs.we-con.com.cn/bin/view/PIStudio/Download/"
45 }])
46
47
48 #set ($PLC = [{
49 'icon': 'fa fa-file-text-o',
50 'title': "Manual",
51 'description': 'Mainly software configuration manuals',
52 'documentation': "https://docs.we-con.com.cn/bin/view/PLC%20Editor2/01%20Program%20execution/"
53 }, {
54 'icon': 'fa fa-support',
55 'title': "Demo",
56 'description': 'Abundant functions are demonstrated through different demo',
57 'documentation': "https://docs.we-con.com.cn/bin/view/PLC%20Editor2/2%20Demos/"
58 }, {
59 'icon': 'fa fa-play-circle',
60 'title': "Videos",
61 'description': 'Video shows function more clearly',
62 'documentation': "https://www.youtube.com/playlist?list=PL_Bpnb2RgaktxcT6G9n1meunomIw3T81_"
63 },{
64 'icon': 'fa fa-download',
65 'title': "Download",
66 'description': 'Software Download',
67 'documentation': "https://docs.we-con.com.cn/bin/view/PLC%20Editor2/Download/"
68 }])
69
70 #set ($V-BOX = [{
71 'icon': 'fa fa-file-text-o',
72 'title': "Manual",
73 'description': 'Mainly software configuration manuals',
74 'documentation': "https://docs.we-con.com.cn/bin/view/V-BOX/V-Net/Manual/"
75 }, {
76 'icon': 'fa fa-support',
77 'title': "Demo",
78 'description': 'Abundant functions are demonstrated through different demo',
79 'documentation': "https://docs.we-con.com.cn/bin/view/V-BOX/V-Net/Training/"
80 }, {
81 'icon': 'fa fa-play-circle',
82 'title': "Videos",
83 'description': 'Video shows function more clearly',
84 'documentation': "https://www.youtube.com/playlist?list=PL_Bpnb2RgakvYq_Ypk9bydIP7lUfkTDBN"
85 },{
86 'icon': 'fa fa-download',
87 'title': "Download",
88 'description': 'Software Download',
89 'documentation': "https://docs.we-con.com.cn/bin/view/V-BOX/V-Net/Download/"
90 }])
91
92 #set ($Servo = [{
93 'icon': 'fa fa-file-text-o',
94 'title': "Manual",
95 'description': 'Mainly software configuration manuals',
96 'documentation': "https://docs.we-con.com.cn/bin/view/Servo/Manual/"
97 }, {
98 'icon': 'fa fa-support',
99 'title': "Demo",
100 'description': 'Abundant functions are demonstrated through different demo',
101 'documentation': "https://docs.we-con.com.cn/bin/view/Servo/Demo/"
102 }, {
103 'icon': 'fa fa-play-circle',
104 'title': "Videos",
105 'description': 'Video shows function more clearly',
106 'documentation': "https://www.youtube.com/playlist?list=PL_Bpnb2Rgakum51_QOIFgMjqsod2i1UcB"
107 },{
108 'icon': 'fa fa-download',
109 'title': "Download",
110 'description': 'Software Download',
111 'documentation': "https://docs.we-con.com.cn/bin/view/Servo/Download/"
112 }])
113
114 #set ($Inverter = [{
115 'icon': 'fa fa-file-text-o',
116 'title': "Manual",
117 'description': 'Mainly software configuration manuals',
118 'documentation': "https://docs.we-con.com.cn/bin/view/VFD/VM%20AC%20Drive%20User%20Manual/"
119 }, {
120 'icon': 'fa fa-support',
121 'title': "Demo",
122 'description': 'Abundant functions are demonstrated through different demo',
123 'documentation': "https://docs.we-con.com.cn/bin/view/VFD/3.%20Demo/"
124 }, {
125 'icon': 'fa fa-play-circle',
126 'title': "Videos",
127 'description': 'Video shows function more clearly',
128 'documentation': "https://www.youtube.com/playlist?list=PL_Bpnb2RgaktOITeNo-g3fq0jBFoubYGz"
129 },{
130 'icon': 'fa fa-download',
131 'title': "Download",
132 'description': 'Software Download',
133 'documentation': "https://docs.we-con.com.cn/bin/view/VFD/Download/"
134 }])
135
136 {{/velocity}}
137
138 {{velocity}}
139 = $services.localization.render("PI HMI") =
140
141 $services.localization.render("")
142
143 {{html clean="false"}}
144 #display4Cards($PI)
145 {{/html}}
146 {{/velocity}}
147
148
149 {{velocity}}
150 = $services.localization.render("PLC") =
151
152 $services.localization.render("")
153
154 {{html clean="false"}}
155 #display4Cards($PLC)
156 {{/html}}
157 {{/velocity}}
158
159 {{velocity}}
160 = $services.localization.render("V-BOX") =
161
162 $services.localization.render("")
163
164 {{html clean="false"}}
165 #display4Cards($V-BOX)
166 {{/html}}
167 {{/velocity}}
168
169 {{velocity}}
170 = $services.localization.render("Servo") =
171
172 $services.localization.render("")
173
174 {{html clean="false"}}
175 #display4Cards($Servo)
176 {{/html}}
177 {{/velocity}}
178
179 {{velocity}}
180 = $services.localization.render("Inverter") =
181
182 $services.localization.render("")
183
184 {{html clean="false"}}
185 #display4Cards($Inverter)
186 {{/html}}
187 {{/velocity}}
Jiahao Lai 15.1 188
189
190
191 {{html clean="false"}}
192
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() {
308 try {
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 }
330
331 // Initialize the chat widget and toggle button
332 async function initializeChatWidget() {
333 chatWidget = createChatWidget();
334 toggleChatButton = createToggleChatButton();
335
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);
351
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';
404 }
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
816 {{/html}}