<div class="chatbot" data-lang="de">
<div class="chatbot__inner">
<div class="chatbot__header">
<div class="chatbot__intro">
<div class="chatbot__headline">
<h2 class="headline headline--module-1">AI Assistant</h2>
</div>
<div class="chatbot__text large">Fragen zu unseren Trainings, Mobility-Programmen oder den Graduate Schools? Der AI Assistant liefert Antworten zu HIDA und ihren Angeboten.</div>
</div><a class="chatbot__feedback-link" href="https://de.surveymonkey.com/r/RY6L9KZ" target="_blank"><svg class="icon icon--llmagent" viewBox="0 0 200 200" role="presentation">
<use xlink:href="#icon-llmagent"></use>
</svg><span class="f-secondary f-size-small">Beta version feedback here</span></a>
</div>
<div class="chatbot__chat">
<div class="chatbot__chat-inner"></div>
</div>
<form class="chatbot__form medium"><input placeholder="Stelle eine Frage" minlength="5" maxlength="2000" required="required"></input><button class="chatbot__form-button"></button></form>
<ul class="chatbot__footer">
<li><button class="chatbot__footer-button small"><span>Was ist HIDA?</span></button></li>
<li><button class="chatbot__footer-button small"><span>Wie werde ich besser in Python?</span></button></li>
<li><button class="chatbot__footer-button small"><span>Was ist das Norway Mobility Program?</span></button></li>
<li class="has-icon"><button class="small" aria-expanded="false" aria-haspopup="true"><span class="u-hidden-visually">How to?</span><svg class="icon icon--info" viewBox="0 0 200 200" role="presentation">
<use xlink:href="#icon-info"></use>
</svg></button>
<p class="small" role="dialog" aria-modal="true">Unverbindlich. Basiert auf der ChatGPT API von OpenAI Ltd und aktuellen Webseiteninhalten. Teilen Sie keine sensiblen Daten wie Namen oder Passwörter mit unserem KI-Modell. Lesen Sie unsere Datenschutzrichtlinien, um zu erfahren, wie wir Ihre Daten verarbeiten.</p>
</li>
</ul>
</div>
</div>
#{tag || 'div'}.chatbot(
data-lang=language || 'de'
)&attributes(attr)
.chatbot__inner
.chatbot__header
.chatbot__intro
if headline
.chatbot__headline
+include('@headline--module-1', { text: headline.text, level: 2 })
if text
.chatbot__text(class="large") #{text}
if link
#{tag || 'a'}(href=link.url, target='_blank', class='chatbot__feedback-link')
+include('@icon', { icon: 'llmagent' })
span.f-secondary.f-size-small #{link.text}
.chatbot__chat
.chatbot__chat-inner
form.chatbot__form(class="medium")
#{tag || 'input'}(placeholder=placeholder || "Stelle eine Frage", minlength="5", maxlength="2000", required)
#{tag || 'button'}(class="chatbot__form-button")
ul.chatbot__footer
if buttonQuestions && buttonQuestions.length > 0
each question in buttonQuestions
li
button(class="chatbot__footer-button small")
span #{question}
li(class="has-icon")
button(class="small", aria-expanded="false", aria-haspopup="true")
span(class="u-hidden-visually") #{howToButton || "How to?"}
+include('@icon', { icon: 'info' })
p(class="small", role="dialog", aria-modal="true") #{privacyText || "Unverbindlich. Basiert auf der ChatGPT API von OpenAI Ltd und aktuellen Webseiteninhalten. Teilen Sie keine sensiblen Daten wie Namen oder Passwörter mit unserem KI-Modell. Lesen Sie unsere Datenschutzrichtlinien, um zu erfahren, wie wir Ihre Daten verarbeiten."}
//- .chatbot__chat-overlay
{
"language": "de",
"headline": {
"text": "AI Assistant"
},
"text": "Fragen zu unseren Trainings, Mobility-Programmen oder den Graduate Schools? Der AI Assistant liefert Antworten zu HIDA und ihren Angeboten.",
"link": {
"text": "Beta version feedback here",
"url": "https://de.surveymonkey.com/r/RY6L9KZ"
},
"placeholder": "Stelle eine Frage",
"buttonQuestions": [
"Was ist HIDA?",
"Wie werde ich besser in Python?",
"Was ist das Norway Mobility Program?"
],
"howToButton": "How to?",
"privacyText": "Unverbindlich. Basiert auf der ChatGPT API von OpenAI Ltd und aktuellen Webseiteninhalten. Teilen Sie keine sensiblen Daten wie Namen oder Passwörter mit unserem KI-Modell. Lesen Sie unsere Datenschutzrichtlinien, um zu erfahren, wie wir Ihre Daten verarbeiten."
}
const chatbotQuestion = (
chatInputValue,
strings = {}
) => {
const timeLabel = strings.timeLabel || 'Uhr';
return `<div class="chatbot__chat-question">
<p class="medium">${chatInputValue}</p>
</div>`;
};
const chatbotAnswer = (
chatQuestion,
chatAnswer,
currentTime,
hasRetry = true,
strings = {}
) => {
const copyText = strings.copy || 'Kopieren';
const retryText = strings.retry || 'Nochmal versuchen';
const timeLabel = strings.timeLabel || 'Uhr';
return `<div class="chatbot__chat-answer chatbot__chat-copy-text" data-question="${chatQuestion}">
<p class="medium">${chatAnswer}</p>
<ul class="chatbot__chat-actions">
<li><button class="chatbot__chat-actions-copy"><span>${copyText}</span></button><span class="small">${copyText}</span></li>
${hasRetry
? `<li><button class="chatbot__chat-actions-retry"><span>${retryText}</span></button><span class="small">${retryText}</span></li>`
: ''}
</ul>
</div>`;
};
const chatOverlay = (
strings = {}
) => {
const closeText = strings.close || 'Schließen';
const sourcesText = strings.sources || 'Quellenangaben';
return `<aside class="chatbot__chat-overlay">
<div class="chatbot__chat-overlay-inner">
<button class="chatbot__chat-overlay-button-close">
<svg class="icon icon--close" viewBox="0 0 200 200" role="presentation"><use xlink:href="#icon-close"></use></svg>
<span>${closeText}</span>
</button>
<h3 class="headline headline--module-2">${sourcesText}</h3>
<ul>
<li><span>wikipedia</span>
<h3><a href="#" target="_blank">Beispiel Titel lorem ipsum</a></h3>
<p>5. März 2025</p>
</li>
<li><span>wikipedia</span>
<h3><a href="#" target="_blank">Beispiel Titel lorem ipsum</a></h3>
<p>5. März 2025</p>
</li>
</ul>
</div>
</aside>`;
};
export {
chatbotQuestion, chatbotAnswer, chatOverlay,
};
/* eslint-disable no-console */
import MarkdownIt from 'markdown-it/dist/markdown-it.min.js';
import {
chatbotQuestion, chatbotAnswer,
} from './chatbot-templates';
const md = new MarkdownIt({
breaks: true,
linkify: true,
typographer: true,
html: false,
});
const defaultLinkRender = md.renderer.rules.link_open || function(tokens, idx, options, env, renderer) {
return renderer.renderToken(tokens, idx, options);
};
md.renderer.rules.link_open = function(tokens, idx, options, env, renderer) {
const aIndex = tokens[idx].attrIndex('target');
if (aIndex < 0) {
tokens[idx].attrPush(['target', '_blank']);
} else {
tokens[idx].attrs[aIndex][1] = '_blank';
}
const relIndex = tokens[idx].attrIndex('rel');
if (relIndex < 0) {
tokens[idx].attrPush(['rel', 'noopener noreferrer']);
} else {
tokens[idx].attrs[relIndex][1] = 'noopener noreferrer';
}
return defaultLinkRender(tokens, idx, options, env, renderer);
};
class MarkdownParser {
static parse(markdown) {
if (!markdown) return '';
try {
return md.render(markdown);
} catch (error) {
console.error('Markdown parsing error:', error);
return `<p>${markdown.replace(/\n/g, '<br>')}</p>`;
}
}
}
class ChatbotUI {
constructor(chatbotElement) {
this.chatbot = chatbotElement;
this.questionTrigger = this.chatbot.querySelector('.chatbot__form-button');
this.chatScrollContainer = this.chatbot.querySelector('.chatbot__chat');
this.chatWrapper = this.chatbot.querySelector('.chatbot__chat-inner');
this.chatInput = this.chatbot.querySelector('.chatbot__form input');
this.requestObject = undefined;
this.footerButtons = this.chatbot.querySelectorAll('.chatbot__footer-button');
this.howToTrigger = this.chatbot.querySelector('.has-icon button');
this.howToTarget = this.chatbot.querySelector('.has-icon p');
this.currentResponse = '';
this.currentResources = [];
this.isGenerating = false;
this.currentAnswerElement = null;
this.currentQuestionElement = null;
this.typingQueue = '';
this.displayedLength = 0;
this.isTyping = false;
// Track thinking element and progress
this.currentThinkingElement = null;
this.connectionTimeout = null;
// Generate unique session user ID
this.sessionUserId = this.generateSessionUserId();
// Initialize i18n strings
this.initializeI18nStrings();
}
generateSessionUserId() {
const timestamp = Date.now();
const randomPart = Math.random().toString(36).substring(2, 15);
return `user-${timestamp}-${randomPart}`;
}
initializeI18nStrings() {
const lang = this.chatbot.dataset.lang || 'de';
this.strings = {
de: {
textCopied: 'Text kopiert',
textCopyError: 'Text konnte nicht kopiert werden',
waitingMessage: 'Bitte warten Sie, während die Antwort generiert wird...',
thinking: 'Ich denke nach',
answerGenerated: 'Antwort generiert',
close: 'Schließen',
sources: 'Quellen',
noSourcesAvailable: 'Für diese Antwort sind keine spezifischen Quellen verfügbar.',
errorMessage: 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.',
dateLocale: 'de-DE',
researchProgress: 'Recherche läuft',
usingTool: 'Verwende Tool',
processing: 'Verarbeite...',
copy: 'Kopieren',
retry: 'Nochmal versuchen',
timeLabel: 'Uhr',
answerSupplement: 'Es handelt sich hier um KI-generierte Antworten, bitte überprüfen Sie alle Aussagen anhand der vorgeschlagenen Links.'
},
en: {
textCopied: 'Text copied',
textCopyError: 'Text could not be copied',
waitingMessage: 'Please wait while the answer is being generated...',
thinking: 'I am thinking',
answerGenerated: 'Answer generated',
close: 'Close',
sources: 'Sources',
noSourcesAvailable: 'No specific sources are available for this answer.',
errorMessage: 'An error occurred. Please try again.',
dateLocale: 'en-US',
researchProgress: 'Research in progress',
usingTool: 'Using tool',
processing: 'Processing...',
copy: 'Copy',
retry: 'Try again',
timeLabel: '',
answerSupplement: 'These are AI-generated answers. Please verify all statements using the links provided.'
}
};
this.t = this.strings[lang] || this.strings.de;
}
resetConnectionTimeout() {
if (this.connectionTimeout) {
clearTimeout(this.connectionTimeout);
}
this.connectionTimeout = setTimeout(() => {
console.warn('Connection timeout - no data received for 30 seconds');
}, 30000);
}
scrollToElement(element, offset = 0) {
if (!element || !this.chatScrollContainer) return;
const containerRect = this.chatScrollContainer.getBoundingClientRect();
const elementRect = element.getBoundingClientRect();
const currentScrollTop = this.chatScrollContainer.scrollTop;
const targetScrollTop = currentScrollTop + elementRect.top - containerRect.top - offset;
this.chatScrollContainer.scrollTo({
top: targetScrollTop,
behavior: 'smooth'
});
}
scrollToBottom() {
if (!this.chatScrollContainer) return;
this.chatScrollContainer.scrollTo({
top: this.chatScrollContainer.scrollHeight,
behavior: 'smooth'
});
}
handleButtonAria(button) {
let ariaExpandedState = button.getAttribute('aria-expanded');
ariaExpandedState = ariaExpandedState === 'true' ? 'false' : 'true';
button.setAttribute('aria-expanded', ariaExpandedState);
}
async copyToClipboard(elements) {
if (!elements.length) return;
elements.forEach((el) => {
const sourceElement = el.querySelector('.large');
const copyButton = el.querySelector('.chatbot__chat-actions-copy');
const output = el.querySelector('.chatbot__chat-actions-copy + .small');
copyButton.addEventListener('click', async () => {
try {
const htmlBlob = new Blob([sourceElement.innerHTML], { type: 'text/html' });
const textBlob = new Blob([sourceElement.textContent], { type: 'text/plain' });
await navigator.clipboard.write([
new ClipboardItem({
'text/html': htmlBlob,
'text/plain': textBlob
})
]);
output.textContent = this.t.textCopied;
} catch (error) {
try {
await navigator.clipboard.writeText(sourceElement.textContent);
output.textContent = this.t.textCopied;
} catch (fallbackError) {
output.textContent = this.t.textCopyError;
console.warn('Unable to copy.', fallbackError);
}
}
});
});
}
setCurrentTime() {
const currentDate = new Date();
const currentHour = currentDate.getHours();
const currentMinute = String(currentDate.getMinutes()).padStart(2, '0');
const currentTime = `${currentHour}:${currentMinute}`;
return currentTime;
}
setRequestObject(content) {
const o = {
model: '3pc',
messages: [{ role: 'user', content }],
temperature: 0,
top_p: 1,
n: 1,
stream: true,
max_tokens: 500,
user: this.sessionUserId,
};
return o;
}
handleReferencesUI() {
// Sources/Quellen button functionality removed
// This method intentionally left empty to maintain compatibility
}
disableAllRetryButtons() {
const retryButtons = this.chatbot.querySelectorAll('.chatbot__chat-actions-retry');
retryButtons.forEach(button => {
button.disabled = true;
button.style.opacity = '0.3';
button.style.cursor = 'not-allowed';
});
}
async getStreamingAPIResponse(requestBody) {
try {
const response = await fetch('https://hida-chatbot.intra.3pc.de/chat/completions', {
method: 'POST',
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
body: JSON.stringify(requestBody),
keepalive: true,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
const self = this; // Capture 'this' context
return {
async *[Symbol.asyncIterator]() {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
// Handle SSE keep-alive comments
if (line.startsWith(': ')) {
self.resetConnectionTimeout();
continue;
}
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
return;
}
try {
const chunk = JSON.parse(data);
if (chunk.choices && chunk.choices[0] && chunk.choices[0].finish_reason === 'stop') {
yield { type: 'final', resources: chunk.resources || [] };
return;
}
const content = chunk.choices && chunk.choices[0] && chunk.choices[0].delta && chunk.choices[0].delta.content;
if (content) {
// Detect language from content for status messages
const lang = self.detectContentLanguage(content);
// Parse different content types with language detection
if (content.includes('🔧') || content.includes('Using') || content.includes('Verwende')) {
yield { type: 'tool_start', content, language: lang };
} else if (content.includes('💭')) {
yield { type: 'agent_thinking', content, language: lang };
} else if (content.includes('🚀') || content.includes('📝') || content.includes('Beginne') || content.includes('Erstelle')) {
yield { type: 'research_progress', content, language: lang };
} else if (content.includes('⏳') || content.includes('🔍') || content.includes('Arbeite')) {
yield { type: 'heartbeat', content, language: lang };
} else {
yield { type: 'content', content, language: lang };
}
}
} catch (e) {
console.error('Error parsing chunk:', e);
}
}
}
}
}
};
} catch (error) {
console.warn('Streaming error:', error);
throw error;
}
}
createThinkingElement() {
const thinkingId = `thinking-${Date.now()}`;
const thinkingHTML = `
<div class="chatbot__thinking" id="${thinkingId}">
<div class="chatbot__thinking-header" onclick="this.parentElement.classList.toggle('expanded')">
<span class="chatbot__thinking-text">${this.t.thinking}</span>
<div class="chatbot__thinking-dots">
<span></span><span></span><span></span>
</div>
</div>
<div class="chatbot__thinking-content"></div>
</div>
`;
this.chatWrapper.insertAdjacentHTML('beforeend', thinkingHTML);
this.currentThinkingElement = document.getElementById(thinkingId);
return this.currentThinkingElement;
}
updateThinkingStatus(element, content, type = 'thinking') {
if (!element) return;
const headerText = element.querySelector('.chatbot__thinking-text');
const thinkingContent = element.querySelector('.chatbot__thinking-content');
let displayText = content;
// Clean up emoji prefixes
if (type === 'agent_thinking') {
displayText = content.replace(/💭\s*/, '').trim();
} else if (type === 'tool_start') {
displayText = content.replace(/🔧\s*/, '').trim();
} else if (type === 'research_progress') {
displayText = content.replace(/🚀|📝/g, '').trim();
}
if (displayText) {
headerText.classList.add('updating');
setTimeout(() => {
if (type === 'research_progress') {
headerText.textContent = displayText;
} else if (type === 'tool_start') {
headerText.textContent = `${this.t.usingTool}: ${displayText}`;
} else {
headerText.textContent = displayText;
}
headerText.classList.remove('updating');
}, 150);
// Add to thinking content log
if (thinkingContent && displayText !== content) {
thinkingContent.innerHTML += `<div class="thinking-step">${content}</div>`;
}
}
}
finishThinking(element) {
if (!element) return;
const headerText = element.querySelector('.chatbot__thinking-text');
const dots = element.querySelector('.chatbot__thinking-dots');
headerText.textContent = this.t.answerGenerated;
headerText.classList.add('finished');
if (dots) {
dots.style.display = 'none';
}
}
extractDomainName(url) {
try {
const domain = new URL(url).hostname;
return domain.replace('www.', '');
} catch {
return url;
}
}
detectInputLanguage(text) {
const textLower = text.toLowerCase();
const germanWords = ['der', 'die', 'das', 'und', 'ich', 'ist', 'ein', 'eine', 'von', 'zu', 'auf', 'mit', 'für', 'wird', 'werden', 'können', 'gibt', 'ansprechpartner', 'kurse', 'schulungen'];
const englishWords = ['the', 'and', 'is', 'are', 'a', 'an', 'of', 'to', 'in', 'for', 'with', 'will', 'can', 'there', 'contact', 'courses', 'training'];
const germanCount = germanWords.filter(word => textLower.includes(word)).length;
const englishCount = englishWords.filter(word => textLower.includes(word)).length;
return germanCount > englishCount ? 'de' : 'en';
}
detectContentLanguage(content) {
const germanIndicators = ['Beginne', 'Verwende', 'Erstelle', 'Arbeite', 'Analysiere', 'Suche'];
const englishIndicators = ['Starting', 'Using', 'Preparing', 'Working', 'Analyzing', 'Searching'];
const hasGerman = germanIndicators.some(word => content.includes(word));
const hasEnglish = englishIndicators.some(word => content.includes(word));
if (hasGerman) return 'de';
if (hasEnglish) return 'en';
return this.detectedLanguage;
}
setSubmitDisabled(disabled) {
const button = this.questionTrigger;
const repostTriggers = this.chatbot.querySelectorAll('.chatbot__chat-actions-retry');
repostTriggers.forEach(trigger => {
trigger.disabled = disabled ? true : false;
trigger.style.opacity = disabled ? '0.5' : '1';
trigger.style.cursor = disabled ? 'not-allowed' : 'pointer';
trigger.title = disabled ? this.t.waitingMessage : '';
});
if (disabled) {
button.disabled = true;
button.style.opacity = '0.5';
button.style.cursor = 'not-allowed';
button.title = this.t.waitingMessage;
} else {
button.disabled = false;
button.style.opacity = '1';
button.style.cursor = 'pointer';
button.title = '';
}
}
// displayResources(resources) {
// const chatOverlay = this.chatbot.querySelector('.chatbot__chat-overlay');
// if (chatOverlay) {
// let resourcesHTML;
//
// if (resources && resources.length > 0) {
// resourcesHTML = `
// <div class="chatbot__chat-overlay-inner">
// <button class="chatbot__chat-overlay-button-close">
// <svg class="icon icon--close" viewBox="0 0 200 200" role="presentation"><use xlink:href="#icon-close"></use></svg>
// <span>${this.t.close}</span>
// </button>
// <h3 class="headline headline--module-2">${this.t.sources}</h3>
// <ul>
// ${resources.map(url => `
// <li>
// <span class="chatbot__resource-icon">
// <img class="chatbot_favicon" src="https://www.google.com/s2/favicons?domain=${this.extractDomainName(url)}&sz=32"
// alt="${this.extractDomainName(url)}"
// style="width: auto;"
// onerror="this.style.display='none'">
// </span>
// <div class="chatbot__resource-content">
// <h3><a href="${url}" target="_blank">${this.extractDomainName(url)}</a></h3>
// <p>${new Date().toLocaleDateString(this.t.dateLocale)}</p>
// </div>
// </li>
// `).join('')}
// </ul>
// </div>
// `;
// } else {
// resourcesHTML = `
// <div class="chatbot__chat-overlay-inner">
// <button class="chatbot__chat-overlay-button-close">
// <svg class="icon icon--close" viewBox="0 0 200 200" role="presentation"><use xlink:href="#icon-close"></use></svg>
// <span>${this.t.close}</span>
// </button>
// <h3 class="headline headline--module-2">${this.t.sources}</h3>
// <p>${this.t.noSourcesAvailable}</p>
// </div>
// `;
// }
//
// chatOverlay.innerHTML = resourcesHTML;
//
// const closeButton = chatOverlay.querySelector('.chatbot__chat-overlay-button-close');
// const refsTrigger = this.chatbot.querySelector('.chatbot__chat-footer-button');
//
// if (closeButton && refsTrigger) {
// closeButton.addEventListener('click', () => {
// chatOverlay.classList.remove('is-active');
// this.handleButtonAria(refsTrigger);
// });
// }
// }
// }
startGeneratingResponse() {
this.chatWrapper.insertAdjacentHTML(
'beforeend',
chatbotAnswer(
this.requestObject.messages[0].content,
'',
this.setCurrentTime(),
true,
this.t,
),
);
const answerElements = this.chatbot.querySelectorAll('.chatbot__chat-answer p.medium');
this.currentAnswerElement = answerElements[answerElements.length - 1];
// Scroll to the new answer element immediately after it's created
setTimeout(() => {
this.scrollToElement(this.currentAnswerElement, 20);
}, 100);
this.currentResponse = '';
this.isGenerating = true;
this.typingQueue = '';
this.displayedLength = 0;
this.isTyping = false;
this.startSmoothTyping();
}
updateGeneratedResponse(content) {
if (this.currentAnswerElement && this.isGenerating) {
this.typingQueue += content;
this.currentResponse += content;
}
}
startSmoothTyping() {
if (this.isTyping) return;
this.isTyping = true;
const typeNextChunk = () => {
if (!this.isGenerating && this.displayedLength >= this.currentResponse.length) {
this.isTyping = false;
return;
}
const targetLength = Math.min(
this.currentResponse.length,
this.displayedLength + Math.max(1, Math.floor(Math.random() * 3) + 1)
);
if (targetLength > this.displayedLength) {
this.displayedLength = targetLength;
const displayText = this.currentResponse.substring(0, this.displayedLength);
const htmlResponse = MarkdownParser.parse(displayText);
this.currentAnswerElement.innerHTML = htmlResponse;
}
let delay = 5;
const lastChar = this.currentResponse.charAt(this.displayedLength - 1);
if (lastChar === '.' || lastChar === '!' || lastChar === '?') {
delay = 30;
} else if (lastChar === ',' || lastChar === ';' || lastChar === ':') {
delay = 20;
} else if (lastChar === ' ') {
delay = 10;
}
delay += Math.random() * 8;
setTimeout(typeNextChunk, delay);
};
typeNextChunk();
}
setAnswerSupplement = currentAnswerElement => {
currentAnswerElement.insertAdjacentHTML(
'beforeend',
`<p>${this.t.answerSupplement}</p>`,
);
}
finishGeneratingResponse(resources) {
this.currentResources = resources;
const waitForTypingComplete = () => {
if (this.displayedLength < this.currentResponse.length) {
setTimeout(waitForTypingComplete, 50);
return;
}
this.setAnswerSupplement(this.currentAnswerElement);
this.copyToClipboard(this.chatbot.querySelectorAll('.chatbot__chat-copy-text'));
this.handleReferencesUI();
// this.displayResources(resources);
const repostTriggers = this.chatbot.querySelectorAll('.chatbot__chat-actions-retry');
if (repostTriggers.length) {
repostTriggers.forEach((trigger) => {
trigger.addEventListener('click', () => {
const answerWrapper = trigger.closest('.chatbot__chat-answer');
this.repostQuestion(answerWrapper);
});
});
}
this.isGenerating = false;
this.setSubmitDisabled(false);
// Clear connection timeout
if (this.connectionTimeout) {
clearTimeout(this.connectionTimeout);
}
};
waitForTypingComplete();
}
handleQuestionSubmit = async (e) => {
e.preventDefault();
if (this.isGenerating) {
return;
}
if (!this.chatInput.value.trim()) {
this.chatInput.value = '';
return;
}
// Disable all previous retry buttons
this.disableAllRetryButtons();
// Detect language from user input
this.detectedLanguage = this.detectInputLanguage(this.chatInput.value);
this.chatWrapper.insertAdjacentHTML(
'beforeend',
chatbotQuestion(this.chatInput.value, this.setCurrentTime(), this.t),
);
const questionElements = this.chatbot.querySelectorAll('.chatbot__chat-question');
this.currentQuestionElement = questionElements[questionElements.length - 1];
setTimeout(() => {
this.scrollToElement(this.currentQuestionElement, 20);
}, 100);
this.requestObject = this.setRequestObject(this.chatInput.value);
this.chatInput.value = '';
this.chatbot.classList.add('is-loading');
this.setSubmitDisabled(true);
// Create thinking element BEFORE creating answer element
const thinkingId = `thinking-${Date.now()}`;
const thinkingHTML = `
<div class="chatbot__thinking" id="${thinkingId}">
<div class="chatbot__thinking-header" onclick="this.parentElement.classList.toggle('expanded')">
<span class="chatbot__thinking-text">${this.t.thinking}</span>
<div class="chatbot__thinking-dots">
<span></span><span></span><span></span>
</div>
</div>
<div class="chatbot__thinking-content"></div>
</div>
`;
this.chatWrapper.insertAdjacentHTML('beforeend', thinkingHTML);
let thinkingElement = document.getElementById(thinkingId);
this.chatbot.classList.remove('is-loading');
// Start connection timeout
this.resetConnectionTimeout();
try {
const stream = await this.getStreamingAPIResponse(this.requestObject);
for await (const chunk of stream) {
// Reset timeout on any activity
this.resetConnectionTimeout();
if (chunk.type === 'content') {
if (!this.isGenerating) {
this.finishThinking(thinkingElement);
this.startGeneratingResponse();
}
this.updateGeneratedResponse(chunk.content);
} else if (chunk.type === 'agent_thinking') {
this.updateThinkingStatus(thinkingElement, chunk.content, 'agent_thinking');
} else if (chunk.type === 'tool_start') {
this.updateThinkingStatus(thinkingElement, chunk.content, 'tool_start');
} else if (chunk.type === 'research_progress') {
this.updateThinkingStatus(thinkingElement, chunk.content, 'research_progress');
} else if (chunk.type === 'heartbeat') {
// Just reset timeout, no UI update needed
console.log('Heartbeat received');
} else if (chunk.type === 'final') {
if (this.isGenerating) {
this.finishGeneratingResponse(chunk.resources);
}
break;
}
}
} catch (error) {
this.chatbot.classList.remove('is-loading');
this.setSubmitDisabled(false);
console.error('Stream processing error:', error);
this.chatWrapper.insertAdjacentHTML(
'beforeend',
chatbotAnswer(
this.requestObject.messages[0].content,
this.t.errorMessage,
this.setCurrentTime(),
true,
this.t,
),
);
// Clear connection timeout on error
if (this.connectionTimeout) {
clearTimeout(this.connectionTimeout);
}
}
};
repostQuestion = async (answerElement) => {
this.setSubmitDisabled(true);
const repostQuestion = answerElement.getAttribute('data-question');
if (!repostQuestion) return;
// Find the answer content element to replace
const answerContent = answerElement.querySelector('p.medium');
this.currentAnswerElement = answerContent;
// Clear the existing answer content
if (answerContent) {
answerContent.innerHTML = '';
// Make display none
answerContent.style.display ="None"
}
// Remove any existing thinking elements
const existingThinking = this.chatbot.querySelectorAll('.chatbot__thinking');
existingThinking.forEach(thinking => thinking.remove());
this.requestObject = this.setRequestObject(repostQuestion);
this.chatbot.classList.add('is-loading');
// Reset response state for replacement
this.currentResponse = '';
this.displayedLength = 0;
this.isTyping = false;
this.isGenerating = false; // Important: reset this flag
const thinkingId = `thinking-${Date.now()}`;
const thinkingHTML = `
<div class="chatbot__thinking" id="${thinkingId}">
<div class="chatbot__thinking-header" onclick="this.parentElement.classList.toggle('expanded')">
<span class="chatbot__thinking-text">${this.t.thinking}</span>
<div class="chatbot__thinking-dots">
<span></span><span></span><span></span>
</div>
</div>
<div class="chatbot__thinking-content"></div>
</div>
`;
answerElement.insertAdjacentHTML('beforebegin', thinkingHTML);
let thinkingElement = document.getElementById(thinkingId);
this.chatbot.classList.remove('is-loading');
setTimeout(() => {
this.scrollToElement(thinkingElement, 20);
}, 100);
// Start connection timeout
this.resetConnectionTimeout();
try {
const stream = await this.getStreamingAPIResponse(this.requestObject);
for await (const chunk of stream) {
// Reset timeout on any activity
this.resetConnectionTimeout();
if (chunk.type === 'content') {
if (!this.isGenerating) {
this.finishThinking(thinkingElement);
// Don't call startGeneratingResponse() since we already have the answer element
// Just start typing in the existing element
this.isGenerating = true;
answerContent.style.display = "block"
this.startSmoothTyping();
}
this.updateGeneratedResponse(chunk.content);
} else if (chunk.type === 'agent_thinking') {
this.updateThinkingStatus(thinkingElement, chunk.content, 'agent_thinking');
} else if (chunk.type === 'tool_start') {
this.updateThinkingStatus(thinkingElement, chunk.content, 'tool_start');
} else if (chunk.type === 'research_progress') {
this.updateThinkingStatus(thinkingElement, chunk.content, 'research_progress');
} else if (chunk.type === 'heartbeat') {
console.log('Heartbeat received');
} else if (chunk.type === 'final') {
if (this.isGenerating) {
this.setSubmitDisabled(false);
this.finishGeneratingResponse(chunk.resources);
}
break;
}
}
} catch (error) {
this.chatbot.classList.remove('is-loading');
this.setSubmitDisabled(false);
console.error('Stream processing error:', error);
if (answerContent) {
answerContent.innerHTML = this.t.errorMessage;
}
// Clear connection timeout on error
if (this.connectionTimeout) {
clearTimeout(this.connectionTimeout);
}
}
}
bindEvents() {
if (!this.questionTrigger) return;
this.questionTrigger.addEventListener('click', this.handleQuestionSubmit);
if (!this.footerButtons.length) return;
this.footerButtons.forEach((button) => {
button.addEventListener('click', () => {
this.chatInput.value = button.textContent;
});
});
if (!this.howToTrigger && !this.howToTarget) return;
this.howToTrigger.addEventListener('click', () => {
this.howToTarget.classList.toggle('is-active');
this.handleButtonAria(this.howToTrigger);
});
}
init() {
this.bindEvents();
}
}
const chatbots = document.querySelectorAll('.chatbot');
if (chatbots.length) {
chatbots.forEach((chatbot) => {
const chatbotUI = new ChatbotUI(chatbot);
chatbotUI.init();
});
}
/* stylelint-disable property-no-unknown */
/* stylelint-disable max-nesting-depth */
.chatbot {
background-image: url('../images/backgrounds/bg-chatbot.svg');
background-size: cover;
color: #fff;
padding: 5rem 1.5rem;
position: relative;
@include mq($from: m) {
padding: 5rem 3rem;
}
@include mq($from: xl) {
padding: 10rem 9rem 8rem;
}
}
.chatbot__header {
display: flex;
flex-direction: column;
gap: 2rem;
@include mq($from: m) {
flex-direction: row;
justify-content: space-between;
}
}
.chatbot__headline {
color: #fff;
margin-bottom: 2rem;
}
.chatbot__text {
margin-bottom: 3rem;
}
.chatbot__headline + .chatbot__text {
margin-top: -1rem;
}
.chatbot__feedback-link {
display: flex;
flex-direction: column;
gap: 10px;
max-width: 150px;
align-items: center;
justify-content: center;
&:hover svg {
background-color: $color-blue;
}
}
.chatbot__feedback-link svg {
background-color: $color-blue-light;
padding: 5px;
height: auto;
width: 110px;
border-radius: 50%;
}
.chatbot__feedback-link span {
text-align: center;
}
.chatbot__inner {
margin: 0 auto;
max-width: 127rem;
position: relative;
}
.chatbot__form {
background-clip: padding-box;
background-color: $color-dark;
background-image: url("data:image/svg+xml,%3Csvg width='28' height='28' viewBox='0 0 28 28' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17.6021 6.04744C18.3709 6.70554 19.1251 7.40355 19.8608 8.13775C23.3671 11.645 25.9157 15.4627 27.153 18.8232C28.3972 22.2029 28.2847 25.0199 26.6526 26.6534C25.6926 27.6127 24.3335 28.0548 22.6677 27.9946C21.0664 27.9367 19.1984 27.413 17.1785 26.4567L17.8309 25.1212C19.54 25.9285 21.092 26.3841 22.3924 26.4874C23.7525 26.5955 24.8821 26.3205 25.6007 25.6029L25.6011 25.6025C26.2717 24.9313 26.5505 23.9396 26.5196 22.7779C26.4885 21.6144 26.1471 20.2463 25.5374 18.7707C24.3174 15.8176 22.0004 12.3802 18.8094 9.18965C18.1047 8.485 17.3807 7.81478 16.6442 7.18444L17.6021 6.04744Z' fill='url(%23paint0_linear_598_574)'/%3E%3Cpath d='M21.9451 17.6207C21.3021 18.3671 20.6065 19.1159 19.8608 19.8617L19.5311 20.1876C16.1154 23.5264 12.4306 25.9557 9.17529 27.1543C5.79567 28.3986 2.97899 28.2865 1.34648 26.6539C0.387363 25.6946 -0.0543982 24.3367 0.00533235 22.6723C0.062794 21.0723 0.585065 19.2053 1.5404 17.1863L2.87551 17.8388C2.06913 19.5462 1.61478 21.0972 1.51204 22.3965C1.40462 23.7556 1.68036 24.8842 2.39792 25.602C3.06874 26.2728 4.05996 26.5519 5.22159 26.5209C6.38486 26.4899 7.75268 26.1485 9.22831 25.5388C12.1812 24.3186 15.6182 22.0014 18.8094 18.8102C19.5265 18.093 20.1948 17.375 20.8118 16.659L21.9451 17.6207Z' fill='url(%23paint1_linear_598_574)'/%3E%3Cpath d='M5.32669 0.00528542C6.92642 0.0624603 8.79312 0.584388 10.8117 1.53896L10.1593 2.87592C8.45215 2.0696 6.90185 1.61518 5.60292 1.51245C4.24426 1.40504 3.11585 1.6804 2.39792 2.39787C1.72672 3.06866 1.44771 4.06038 1.47855 5.22201C1.50949 6.38532 1.85051 7.75353 2.46023 9.22918C3.6804 12.1821 5.99802 15.619 9.18878 18.8102C9.89066 19.5136 10.6131 20.1809 11.3479 20.8094L10.3895 21.9464C9.62285 21.2913 8.8703 20.5941 8.13641 19.8617C4.63022 16.3544 2.08166 12.5365 0.844715 9.17617C-0.399317 5.79648 -0.286796 2.97951 1.34648 1.3469C2.30488 0.387869 3.66252 -0.0541829 5.32669 0.00528542Z' fill='url(%23paint2_linear_598_574)'/%3E%3Cpath d='M18.8219 0.845132C22.2015 -0.398984 25.0187 -0.286469 26.6521 1.3469C27.612 2.30682 28.0545 3.66815 27.9947 5.33687C27.9371 6.94107 27.4132 8.81272 26.4563 10.8349L25.1208 10.1825C25.9289 8.4709 26.3853 6.91585 26.4884 5.61263C26.5962 4.24975 26.32 3.11778 25.6007 2.39834C24.9295 1.72741 23.9378 1.44806 22.7761 1.47897C21.6126 1.50994 20.2445 1.85133 18.7689 2.46112C15.8161 3.68139 12.3794 5.99869 9.18924 9.18965C8.4812 9.89627 7.80844 10.6238 7.17612 11.3637L6.03912 10.4066C6.69776 9.63475 7.39922 8.87551 8.13641 8.13682C11.6435 4.63043 15.4616 2.08217 18.8219 0.845132Z' fill='url(%23paint3_linear_598_574)'/%3E%3Cpath d='M13.9986 9.33333C14.3885 10.4989 14.901 11.7768 15.4357 12.3237C16.0206 12.9213 17.4329 13.5051 18.6667 13.9297C17.4802 14.3543 16.1348 14.9307 15.5722 15.4987C14.9984 16.0752 14.4245 17.4575 13.9997 18.6667C13.5722 17.4575 12.9988 16.0753 12.4279 15.4987C11.8653 14.9307 10.5198 14.3585 9.33335 13.9297C10.57 13.5051 11.9819 12.9213 12.5626 12.3237C13.0974 11.7768 13.6142 10.4989 13.9986 9.33333Z' fill='url(%23paint4_linear_598_574)'/%3E%3Cdefs%3E%3ClinearGradient id='paint0_linear_598_574' x1='24.7395' y1='8.29428' x2='2.32938' y2='18.0243' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0.015' stop-color='%23009EE3'/%3E%3Cstop offset='1' stop-color='%23F50096'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint1_linear_598_574' x1='24.7395' y1='8.29428' x2='2.32938' y2='18.0243' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0.015' stop-color='%23009EE3'/%3E%3Cstop offset='1' stop-color='%23F50096'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint2_linear_598_574' x1='24.7395' y1='8.29428' x2='2.32938' y2='18.0243' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0.015' stop-color='%23009EE3'/%3E%3Cstop offset='1' stop-color='%23F50096'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint3_linear_598_574' x1='24.7395' y1='8.29428' x2='2.32938' y2='18.0243' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0.015' stop-color='%23009EE3'/%3E%3Cstop offset='1' stop-color='%23F50096'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint4_linear_598_574' x1='24.7395' y1='8.29428' x2='2.32938' y2='18.0243' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0.015' stop-color='%23009EE3'/%3E%3Cstop offset='1' stop-color='%23F50096'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E%0A");
background-position: 2rem center;
background-size: 4rem 4rem;
border: 0.2rem solid transparent;
border-radius: 5rem;
display: flex;
justify-content: space-between;
padding: 1.25rem 0.75rem;
position: relative;
.is-loading & {
opacity: 0.3;
pointer-events: none;
}
&::before {
background: linear-gradient(to right, $color-blue-light, $color-mardata-accent, $color-blue, $color-mardata-accent, $color-blue-light) border-box;
border: 0.2rem solid transparent;
border-radius: 5rem;
content: '';
inset: 0;
mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
mask-composite: exclude;
position: absolute;
}
input {
margin-left: 8rem;
position: relative;
width: 85%;
z-index: 1;
&:focus {
outline: unset;
}
}
input::placeholder {
color: #fff;
opacity: 1;
}
button {
background-color: $color-blue;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 28 24'%3E%3Cpath fill='%23fff' fill-rule='evenodd' d='M27.813 12 .5.5l1.726 9.324L11.802 12l-9.576 2.176L.5 23.5 27.813 12Z' clip-rule='evenodd'/%3E%3C/svg%3E");
background-position: 1.2rem center;
background-size: 60%;
border-radius: 50%;
cursor: pointer;
display: inline-block;
flex-shrink: 0;
height: 4.7rem;
margin-right: 1.6rem;
position: relative;
width: 4.7rem;
&:hover {
background-color: $color-blue-hover;
}
}
}
.chatbot__chat {
display: flex;
flex-flow: column nowrap;
margin-bottom: 2rem;
max-height: 80vh;
overflow-x: hidden;
overflow-y: auto;
padding-right: 5%;
position: relative;
scrollbar-color: #fff rgba(255, 255, 255, 0.2);
scrollbar-color: #fff transparent;
scrollbar-width: thin;
@include mq($from: xl) {
max-height: 600px;
margin-top: 60px;
}
}
.chatbot__chat-question {
margin: 4rem 0;
margin-left: auto;
max-width: 85%;
p {
background-color: #fff;
border-radius: 2rem;
border-bottom-right-radius: 0;
color: $color-dark;
padding: 2rem;
}
.chatbot__chat-actions {
justify-content: flex-end;
margin-left: auto;
}
@include mq($from: m) {
max-width: 70%;
}
}
.chatbot__loader {
animation: l1 1s steps(4) infinite;
aspect-ratio: 4;
background: radial-gradient(circle closest-side, #fff 90%, #0000) 0/calc(100%/3) 100% space;
bottom: 173px;
clip-path: inset(0 100% 0 0);
display: none;
left: 120px;
position: absolute;
width: 30px;
.is-loading & {
display: block;
}
}
@keyframes l1 {to {clip-path: inset(0 -34% 0 0);}}
.chatbot__thinking {
background-color: rgba(255, 255, 255, 0.1);
border: 0.1rem solid rgba(255, 255, 255, 0.2);
border-radius: 1.5rem;
margin: 2rem 0;
max-width: 90%;
@include mq($from: m) {
max-width: 80%;
}
}
.chatbot__thinking-header {
align-items: center;
cursor: pointer;
display: flex;
justify-content: space-between;
padding: 1.5rem 2rem;
user-select: none;
&:hover {
background-color: rgba(255, 255, 255, 0.05);
border-radius: 1.5rem;
}
}
.chatbot__thinking-text {
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
position: relative;
overflow: hidden;
transition: opacity 0.3s ease;
&.updating {
opacity: 0;
}
}
.chatbot__thinking-content {
color: rgba(255, 255, 255, 0.7);
font-size: 0.9em;
max-height: 0;
overflow: scroll;
transition: max-height 0.3s ease, padding 0.3s ease;
padding-left: 2rem;
}
.thinking-step {
margin-top: 15px;
}
.chatbot__thinking.expanded .chatbot__thinking-content {
border-top: 0.1rem solid rgba(255, 255, 255, 0.2);
max-height: 50rem;
}
.chatbot__thinking-dots {
display: flex;
gap: 0.4rem;
span {
animation: thinking-pulse 1.4s infinite ease-in-out;
background: rgba(255, 255, 255, 0.6);
border-radius: 50%;
height: 0.6rem;
width: 0.6rem;
&:nth-child(1) {
animation-delay: -0.32s;
}
&:nth-child(2) {
animation-delay: -0.16s;
}
}
}
@keyframes thinking-pulse {
0%, 80%, 100% {
opacity: 0.5;
transform: scale(0.8);
}
40% {
opacity: 1;
transform: scale(1);
}
}
.chatbot__chat-answer {
margin: 4rem 0;
max-width: 90%;
p {
background-color: $color-blue-light;
border-radius: 2rem;
border-bottom-left-radius: 0;
padding: 2rem;
line-height: 1.6;
// Markdown styling within chat answers
h1, h2, h3, h4, h5, h6 {
color: inherit;
font-weight: bold;
margin: 0.8rem 0 0.5rem 0;
line-height: 1.2;
&:first-child {
margin-top: 0;
}
}
h1 { font-size: 1.3em; }
h2 { font-size: 1.2em; }
h3 { font-size: 1.1em; }
h4, h5, h6 { font-size: 1em; }
strong {
font-weight: bold;
color: rgba(255, 255, 255, 0.95);
}
em {
font-style: italic;
}
del {
text-decoration: line-through;
opacity: 0.8;
}
code {
background-color: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 0.3rem;
font-family: 'Courier New', Courier, monospace;
font-size: 0.9em;
padding: 0.1rem 0.3rem;
}
pre {
background-color: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 0.5rem;
margin: 0.8rem 0;
overflow-x: auto;
padding: 0.8rem;
code {
background-color: transparent;
border: none;
padding: 0;
}
}
// Table styling
.markdown-table {
border-collapse: collapse;
margin: 0.8rem 0;
width: 100%;
th, td {
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 0.5rem 0.8rem;
text-align: left;
}
th {
background-color: rgba(0, 0, 0, 0.2);
font-weight: bold;
}
tr:nth-child(even) {
background-color: rgba(255, 255, 255, 0.05);
}
}
// List styling
ul, ol {
margin: 0.5rem 0;
padding-left: 1.2rem;
}
li {
margin: 0.2rem 0;
}
// Blockquote styling
blockquote {
border-left: 3px solid rgba(255, 255, 255, 0.4);
margin: 0.8rem 0;
padding-left: 1rem;
font-style: italic;
opacity: 0.9;
}
a {
color: #fff;
text-decoration: underline;
text-decoration-color: rgba(255, 255, 255, 0.6);
transition: all 0.2s ease;
&:hover {
color: rgba(255, 255, 255, 0.8);
text-decoration-color: rgba(255, 255, 255, 0.8);
}
&:visited {
color: rgba(255, 255, 255, 0.7);
}
}
// Improve paragraph spacing for markdown content
p {
background-color: transparent;
border-radius: 0;
margin: 0.5rem 0;
padding: 0;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
}
@include mq($from: m) {
max-width: 80%;
}
}
.chatbot__chat-actions {
display: flex;
gap: 3rem;
list-style: none;
margin-top: 1rem;
padding: 0;
li {
position: relative;
}
button {
height: 2.2rem;
width: 2.2rem;
}
button span {
@include hidden-visually();
}
span {
bottom: 0;
display: none;
left: 0;
position: absolute;
text-wrap: nowrap;
top: 2.5rem;
}
li:hover span {
display: block;
}
}
.chatbot__chat-actions-copy {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 22 22'%3E%3Cpath fill='%23fff' fill-rule='evenodd' d='M7.092.98a2.75 2.75 0 0 0-2.75 2.75v2.112h1.5V3.73c0-.69.56-1.25 1.25-1.25H18.27c.69 0 1.25.56 1.25 1.25v11.178c0 .69-.56 1.25-1.25 1.25h-2.112v1.5h2.111a2.75 2.75 0 0 0 2.75-2.75V3.73A2.75 2.75 0 0 0 18.27.98H7.093Z' clip-rule='evenodd'/%3E%3Crect width='15.178' height='15.178' x='.981' y='5.842' stroke='%23fff' stroke-width='1.5' rx='2'/%3E%3C/svg%3E");
}
.chatbot__chat-actions-retry {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 22 22'%3E%3Cpath stroke='%23fff' stroke-miterlimit='10' stroke-width='2' d='M2.917 1v4.625h4.667'/%3E%3Cpath stroke='%23fff' stroke-miterlimit='10' stroke-width='2' d='M1.5 8.791a9.367 9.367 0 0 0-.292 2.417A9.78 9.78 0 0 0 11 21a9.78 9.78 0 0 0 9.792-9.792A9.78 9.78 0 0 0 11 1.417c-3.333 0-6.291 1.666-8.083 4.208'/%3E%3C/svg%3E");
}
.chatbot__chat-actions-time {
align-items: center;
display: flex;
height: unset;
width: unset;
}
.chatbot__chat-footer {
display: flex;
margin-bottom: 2.5rem;
}
.chatbot__chat-footer-button {
background-color: #fff;
border-radius: 2rem;
color: $color-dark;
margin-right: 1.5rem;
padding: 0.4em 0.8em;
text-align: center;
&:hover {
background-color: $color-blue-light;
}
}
// .chatbot__chat-overlay {
// background-color: $color-dark;
// bottom: 0;
// display: none;
// height: auto;
// left: 0;
// position: absolute;
// width: 100%;
// z-index: 2;
// &.is-active {
// display: block;
// }
// p {
// margin-top: 1rem;
// }
// ul {
// list-style: none;
// margin-top: 3rem;
// padding: 0;
// }
// li span {
// align-items: center;
// display: flex;
// }
// @include mq($from: m) {
// bottom: unset;
// height: 100%;
// left: unset;
// min-width: 400px;
// right: 0;
// top: 0;
// width: 35%;
// }
// }
// .chatbot__resources {
// padding: 2rem;
// h3 {
// color: #fff;
// font-size: 1.4rem;
// margin: 0 0 2rem 0;
// }
// ul {
// list-style: none;
// margin: 0;
// padding: 0;
// }
// li {
// align-items: flex-start;
// display: flex;
// gap: 1rem;
// margin-bottom: 1.5rem;
// &:not(:first-child) {
// margin-top: 1.5rem;
// }
// }
// .chatbot__resource-icon {
// flex-shrink: 0;
// margin-top: 0.1rem;
// }
// .chatbot_favicon {
// height: 2.4rem;
// width: 2.4rem;
// }
// .chatbot__resource-content {
// flex: 1;
// h3 {
// margin: 0 0 0.5rem 0;
// }
// p {
// margin: 0;
// }
// }
// a {
// color: $color-blue-light;
// font-size: 0.9rem;
// text-decoration: none;
// word-break: break-all;
// &:hover {
// color: #fff;
// text-decoration: underline;
// }
// }
// }
.chatbot__chat-overlay-inner {
padding: 8rem 6rem 4rem 4rem;
position: relative;
overflow: scroll;
height: 100%;
}
.chatbot__chat-overlay-button-close {
position: absolute;
right: 3rem;
top: 3rem;
svg {
height: 4.2rem;
width: 4.2rem;
}
span {
@include hidden-visually();
}
}
.chatbot__footer {
display: flex;
flex-flow: row wrap;
gap: 2rem;
list-style: none;
margin-top: 3rem;
padding: 0;
li {
align-items: center;
background-color: $color-blue-light;
border-radius: 4rem;
display: flex;
padding: 0.6rem 1.5rem 0.4rem;
text-align: center;
width: 100%;
&:hover {
background-color: $color-blue-hover;
}
@include mq($from: s) {
width: calc(50% - 2rem);
}
@include mq($from: xl) {
width: auto;
}
}
li button {
width: 100%;
}
.has-icon {
background-color: #fff;
color: $color-dark;
padding: 0;
position: relative;
&:hover {
background-color: $color-blue-light;
}
@include mq($from: xl) {
margin-left: auto;
}
}
.has-icon button {
padding: 5px;
}
.has-icon svg {
width: 20px;
height: 20px;
}
li:last-child p {
background-color: #fff;
border-radius: 1rem;
bottom: 5rem;
display: none;
padding: 1.5em 1em;
position: absolute;
right: 0;
text-align: left;
width: 350px;
}
li:last-child .is-active {
display: block;
}
span {
font-family: $font-family-secondary;
font-weight: $font-weight-bold;
text-transform: uppercase;
}
}
There are no notes for this item.