Beta, live agent support
Bring your own Webhook, lavet vha. vibe coding.
<!-- Agent Chat Embed START - kopier hele blokken ind i et WordPress “Brugerdefineret HTML”-blok -->
<script>
/*
* WEBHOOK KONFIGURATION
* Indsæt dine URLs herunder – søg på "INDSÆT HER!" senere for at finde dem igen.
*/
const CHAT_WEBHOOK_CONFIG = ""; // INDSÆT HER! Chat webhook URL
const MAIL_WEBHOOK_CONFIG = ""; // INDSÆT HER! Mail webhook URL
</script>
<div class="agent-chat-embed">
<div class="embed-container">
<!-- Opdater data-webhook/data-escalation-webhook for at bruge egne endpoints (søg: INDSÆT HER!) -->
<div id="agent-chatbox" data-webhook="INDSÆT HER! Chat webhook URL" data-escalation-webhook="INDSÆT HER! Mail webhook URL" data-header-offset="0">
<div id="agent-chatUI">
<div id="agent-messages"></div>
<div id="agent-chatInput">
<textarea id="agent-userInput" placeholder="Skriv din besked... (Shift+Enter for linjeskift, Enter for at sende)" rows="1" disabled></textarea>
<button type="button" id="agent-escalateBtn" aria-label="Kontakt personalet" style="display:none;" disabled>
<svg width="18" height="18" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M2 6c0-1.1.9-2 2-2h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6zm2 0l8 5 8-5H4zm16 2.7l-8 5-8-5V18h16V8.7z" fill="currentColor"></path>
</svg>
</button>
<button type="button" id="agent-sendBtn" aria-label="Send besked" disabled>
<svg width="20" height="20" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M3.4 20.6L21 12 3.4 3.4 3 9l10 3-10 3z" fill="currentColor"></path>
</svg>
</button>
</div>
</div>
<div id="agent-escalateModal" class="agent-escalate-modal" aria-hidden="true" hidden>
<div class="agent-escalate-content" role="dialog" aria-labelledby="agent-escalate-title">
<header>
<h5 id="agent-escalate-title">Kontakt personalet direkte</h5>
<button type="button" id="agent-escalate-close" aria-label="Luk formular">✕</button>
</header>
<form id="agent-escalate-form">
<label>
Telefon (valgfri)
<input type="tel" id="agent-escalate-phone" name="phone" placeholder="+45 12 34 56 78" autocomplete="tel">
</label>
<label>
<span class="agent-label-inline">
Besked <span aria-hidden="true">*</span>
</span>
<textarea id="agent-escalate-message" name="message" rows="4" required placeholder="Forklar kort din henvendelse"></textarea>
</label>
<div id="agent-escalate-error" class="agent-escalate-feedback" aria-live="polite"></div>
<div class="agent-escalate-actions">
<button type="button" id="agent-escalate-cancel">Annuller</button>
<button type="submit" id="agent-escalate-submit">Send til personalet</button>
</div>
</form>
</div>
</div>
</div>
</div>
<style>
:root {
--agent-chatbox-height: 600px;
}
.agent-chat-embed {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
width: 100%;
background: transparent;
padding: 32px 16px;
box-sizing: border-box;
}
.agent-chat-embed .embed-container {
display: flex;
justify-content: center;
max-width: 980px;
margin: 0 auto;
position: relative;
}
.agent-chat-embed #agent-chatbox {
background: #ffffff;
border: 1px solid rgba(99, 102, 106, 0.2);
width: 100%;
max-width: 760px;
height: var(--agent-chatbox-height);
min-height: 360px;
margin: 0 auto;
border-radius: 18px;
font-size: 1em;
line-height: 1.5;
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
position: relative;
}
.agent-chat-embed .agent-form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.agent-chat-embed .agent-form-group label {
font-weight: 600;
color: #374151;
font-size: 1em;
}
.agent-chat-embed .agent-form-group input {
padding: 12px 16px;
border: 1px solid rgba(15, 23, 42, 0.18);
border-radius: 12px;
font-size: 1em;
line-height: 1.2;
transition: border-color 0.2s ease, background 0.2s ease;
background: #f8fafc;
color: #111827;
}
.agent-chat-embed .agent-form-group input:focus {
outline: 2px solid rgba(29, 76, 139, 0.3);
border-color: #1d4c8b;
background: #fff;
}
.agent-chat-embed .agent-start-btn {
background: linear-gradient(135deg, #1d4c8b, #12325a);
color: #fff;
border: none;
padding: 12px 24px;
border-radius: 12px;
font-size: 1em;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
margin-top: 0.5rem;
}
.agent-chat-embed .agent-start-btn:hover {
transform: translateY(-2px);
}
.agent-chat-embed #agent-chatUI {
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
}
.agent-chat-embed #agent-messages {
flex: 1;
overflow-y: auto;
padding: 24px;
background: #ffffff;
display: flex;
flex-direction: column;
gap: 18px;
min-height: 200px;
scrollbar-width: none;
-ms-overflow-style: none;
}
.agent-chat-embed #agent-messages::-webkit-scrollbar {
display: none;
}
.agent-chat-embed .agent-message {
display: flex;
flex-direction: column;
gap: 0.5rem;
animation: slideIn 0.3s ease-out;
}
.agent-chat-embed .agent-message.user {
align-items: flex-end;
}
.agent-chat-embed .agent-message.ai {
align-items: flex-start;
}
.agent-chat-embed .agent-message-bubble {
max-width: 78%;
padding: 14px 18px;
border-radius: 20px;
font-size: 15px;
line-height: 1.5;
word-wrap: break-word;
position: relative;
display: inline-block;
}
.agent-chat-embed .agent-message.user .agent-message-bubble {
background: #1f2937;
color: #fff;
border-bottom-right-radius: 8px;
}
.agent-chat-embed .agent-message.ai .agent-message-bubble {
background: #f3f4f6;
color: #0f172a;
border-bottom-left-radius: 8px;
border: 1px solid rgba(148, 163, 184, 0.55);
}
.agent-chat-embed .agent-message strong {
font-weight: 600;
}
.agent-chat-embed .agent-message .message-time {
font-size: 0.75em;
opacity: 0.6;
margin-top: 4px;
font-weight: 400;
}
.agent-chat-embed .agent-disclaimer {
margin-top: 10px;
font-size: 10px;
color: #475569;
line-height: 1.4;
opacity: 0.9;
}
.agent-chat-embed .typing-indicator {
animation: slideIn 0.3s ease-out;
}
.agent-chat-embed .typing-bubble {
position: relative;
overflow: hidden;
background: #f3f4f6;
border: 1px solid rgba(148, 163, 184, 0.55);
}
.agent-chat-embed .typing-dots {
display: inline-flex;
gap: 6px;
align-items: center;
margin-left: 8px;
}
.agent-chat-embed .typing-dots span {
width: 8px;
height: 8px;
background: #6b7280;
border-radius: 50%;
animation: typingBounce 1.4s infinite ease-in-out;
display: inline-block;
}
.agent-chat-embed .typing-dots span:nth-child(1) { animation-delay: 0s; }
.agent-chat-embed .typing-dots span:nth-child(2) { animation-delay: 0.2s; }
.agent-chat-embed .typing-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes typingBounce {
0%, 60%, 100% {
transform: translateY(0) scale(1);
opacity: 0.5;
}
30% {
transform: translateY(-8px) scale(1.2);
opacity: 1;
}
}
.agent-chat-embed .typing-bubble::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(107, 114, 128, 0.1), transparent);
animation: typingPulse 2s infinite;
pointer-events: none;
}
@keyframes typingPulse {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
@keyframes slideIn {
from {
transform: translateY(10px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.agent-chat-embed #agent-chatInput {
padding: 16px 20px;
background: #f5f7fa;
border-top: 1px solid rgba(148, 163, 184, 0.25);
display: flex;
gap: 12px;
align-items: flex-end;
flex-shrink: 0;
border-radius: 0 0 18px 18px;
}
.agent-chat-embed #agent-chatInput textarea {
flex: 1;
border: 1px solid rgba(148, 163, 184, 0.35);
border-radius: 18px;
padding: 10px 14px;
font-size: 15px;
outline: none;
resize: none;
min-height: 40px;
max-height: 140px;
font-family: inherit;
line-height: 1.4;
background: #ffffff;
color: #111827;
overflow-y: auto;
}
.agent-chat-embed #agent-chatInput textarea:focus {
border-color: #1d4c8b;
background: #fff;
}
.agent-chat-embed #agent-chatInput textarea::placeholder {
color: #94a3b8;
}
.agent-chat-embed #agent-chatInput textarea::-webkit-scrollbar {
display: none;
}
.agent-chat-embed #agent-chatInput button {
background: linear-gradient(135deg, #1e3a5f, #1b2f4d);
color: #fff;
border: none;
width: 50px;
height: 50px;
border-radius: 50%;
cursor: pointer;
transition: transform 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.agent-chat-embed #agent-chatInput button svg {
display: block;
}
.agent-chat-embed #agent-chatInput button:hover:not(:disabled) {
transform: translateY(-2px);
}
.agent-chat-embed #agent-chatInput button:disabled {
background: rgba(148, 163, 184, 0.6);
cursor: not-allowed;
}
.agent-chat-embed #agent-escalateBtn {
width: 44px;
height: 44px;
background: #0f172a;
border: 1px solid rgba(255, 255, 255, 0.15);
color: #fff;
}
.agent-chat-embed #agent-escalateBtn:disabled {
background: rgba(148, 163, 184, 0.4);
border-color: rgba(148, 163, 184, 0.4);
}
.agent-chat-embed #agent-escalateBtn svg {
width: 18px;
height: 18px;
}
.agent-escalate-modal[hidden] {
display: none;
}
.agent-escalate-modal {
position: absolute;
inset: 0;
background: rgba(15, 23, 42, 0.35);
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
z-index: 5;
}
.agent-escalate-content {
width: 100%;
max-width: 440px;
background: #fff;
border: 1px solid rgba(148, 163, 184, 0.4);
border-radius: 18px;
padding: 1.25rem 1.5rem 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.agent-escalate-content header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
min-height: 48px;
padding-bottom: 0.35rem;
}
.agent-escalate-content h5 {
margin: 0;
font-size: 1em;
line-height: 1.1;
color: #0f172a;
}
.agent-escalate-content header button {
border: none;
background: rgba(148, 163, 184, 0.2);
font-size: 1em;
cursor: pointer;
padding: 0.35rem;
height: 34px;
width: 34px;
border-radius: 50%;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;
}
#agent-escalate-form label {
display: flex;
flex-direction: column;
font-size: 1em;
color: #1f2937;
gap: 0.35rem;
}
.agent-label-inline {
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
#agent-escalate-form input,
#agent-escalate-form textarea {
border: 1px solid rgba(15, 23, 42, 0.18);
border-radius: 12px;
padding: 12px 16px;
font-family: inherit;
font-size: 1em;
line-height: 1.2;
color: #0f172a;
background: #f8fafc;
}
#agent-escalate-form input:focus,
#agent-escalate-form textarea:focus {
outline: 2px solid rgba(29, 76, 139, 0.3);
border-color: #1d4c8b;
background: #fff;
}
#agent-escalate-form textarea {
resize: vertical;
min-height: 120px;
}
.agent-escalate-feedback {
min-height: 1.25rem;
font-size: 0.9rem;
color: #b91c1c;
}
.agent-escalate-actions {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
}
.agent-escalate-actions button {
border: none;
border-radius: 999px;
padding: 0.6rem 1.5rem;
font-size: 1em;
cursor: pointer;
}
#agent-escalate-cancel {
background: #e2e8f0;
color: #1f2937;
}
#agent-escalate-submit {
background: linear-gradient(135deg, #1d4c8b, #12325a);
color: #fff;
}
#agent-escalate-submit:disabled {
background: rgba(148, 163, 184, 0.6);
cursor: not-allowed;
}
.agent-chat-embed #agent-userInput:disabled {
background: #f3f4f6;
color: #9ca3af;
cursor: not-allowed;
}
.agent-chat-embed .agent-prechat-card {
width: 100%;
align-items: stretch;
}
.agent-chat-embed .agent-prechat-form {
display: flex;
flex-direction: column;
gap: 0.85rem;
padding: 18px;
border-radius: 14px;
border: 1px solid rgba(148, 163, 184, 0.3);
background: #fdfdfd;
width: 100%;
box-sizing: border-box;
}
.agent-chat-embed .agent-prechat-form h4 {
margin: 0;
font-size: 15px;
color: #1f2937;
font-weight: 600;
}
.agent-chat-embed .agent-prechat-form p {
margin: 0;
font-size: 13px;
color: #4b5563;
}
.agent-chat-embed .agent-prechat-form .agent-form-group {
gap: 0.3rem;
}
.agent-chat-embed .agent-prechat-form .agent-form-group label {
font-size: 0.85em;
color: #4b5563;
font-weight: 600;
}
.agent-chat-embed .agent-prechat-form .agent-form-group input {
padding: 12px 14px;
border-radius: 10px;
border: 1px solid #d1d5db;
background: #f9fafb;
font-size: 15px;
line-height: 1.2;
}
.agent-chat-embed .agent-prechat-form .extra-info {
font-size: 12px;
color: #6b7280;
}
.agent-chat-embed .agent-prechat-form .error-message {
background: rgba(248, 113, 113, 0.12);
border: 1px solid rgba(248, 113, 113, 0.25);
color: #b91c1c;
padding: 10px 14px;
border-radius: 10px;
font-size: 13px;
}
@media (max-width: 768px) {
.agent-chat-embed {
padding: 0;
background: #f5f7fa;
}
.agent-chat-embed #agent-chatbox {
border-radius: 0;
max-width: none;
border: none;
}
.agent-chat-embed .agent-message-bubble {
max-width: 85%;
}
.agent-chat-embed #agent-chatInput,
.agent-chat-embed #agent-messages {
padding: 1rem;
}
}
</style>
</div>
<script>
;(function() {
if (typeof window !== "undefined" && window.AgentChatWidgetInitialized) {
console.warn("Agent chat widget er allerede initialiseret.");
return;
}
if (typeof window !== "undefined") {
window.AgentChatWidgetInitialized = true;
}
const AGENT_PLACEHOLDER_WEBHOOK = "INDSÆT HER! Chat webhook URL";
const AGENT_DEFAULT_WEBHOOK_SOURCE =
(CHAT_WEBHOOK_CONFIG && CHAT_WEBHOOK_CONFIG.trim())
? CHAT_WEBHOOK_CONFIG.trim()
: ((typeof window !== "undefined" && window.AGENT_DEFAULT_WEBHOOK !== undefined)
? window.AGENT_DEFAULT_WEBHOOK
: "");
const AGENT_DEFAULT_WEBHOOK = (AGENT_DEFAULT_WEBHOOK_SOURCE || "").trim();
const AGENT_PLACEHOLDER_ESCALATION_WEBHOOK = "INDSÆT HER! Mail webhook URL";
const AGENT_DEFAULT_ESCALATION_WEBHOOK_SOURCE =
(MAIL_WEBHOOK_CONFIG && MAIL_WEBHOOK_CONFIG.trim())
? MAIL_WEBHOOK_CONFIG.trim()
: ((typeof window !== "undefined" && window.AGENT_ESCALATION_WEBHOOK !== undefined)
? window.AGENT_ESCALATION_WEBHOOK
: "");
const AGENT_DEFAULT_ESCALATION_WEBHOOK = (AGENT_DEFAULT_ESCALATION_WEBHOOK_SOURCE || "").trim();
let agentMessages = [];
let agentUserInfo = {};
let isAgentTyping = false;
let agentThreadId = null;
let agentConfiguredWebhook = null;
let agentWebhookUrl = null;
let agentDefaultInputPlaceholder = null;
let agentEscalationWebhookUrl = null;
let agentIsLoggedIn = false;
let agentEscalateModalOpen = false;
function resolveAgentConfiguredWebhook() {
const container = document.getElementById('agent-chatbox');
const sourceFromAttribute = container && container.hasAttribute('data-webhook')
? (container.getAttribute('data-webhook') || '').trim()
: '';
if (sourceFromAttribute && sourceFromAttribute !== AGENT_PLACEHOLDER_WEBHOOK) {
return sourceFromAttribute;
}
if (AGENT_DEFAULT_WEBHOOK && AGENT_DEFAULT_WEBHOOK !== AGENT_PLACEHOLDER_WEBHOOK) {
return AGENT_DEFAULT_WEBHOOK;
}
return "";
}
function resolveAgentEscalationWebhook() {
const container = document.getElementById('agent-chatbox');
const sourceFromAttribute = container && container.hasAttribute('data-escalation-webhook')
? (container.getAttribute('data-escalation-webhook') || '').trim()
: '';
if (sourceFromAttribute && sourceFromAttribute !== AGENT_PLACEHOLDER_ESCALATION_WEBHOOK) {
return sourceFromAttribute;
}
if (AGENT_DEFAULT_ESCALATION_WEBHOOK && AGENT_DEFAULT_ESCALATION_WEBHOOK !== AGENT_PLACEHOLDER_ESCALATION_WEBHOOK) {
return AGENT_DEFAULT_ESCALATION_WEBHOOK;
}
return "";
}
function setAgentInputEnabled(enabled) {
const input = document.getElementById('agent-userInput');
const sendBtn = document.getElementById('agent-sendBtn');
const escalateBtn = document.getElementById('agent-escalateBtn');
if (!input || !sendBtn) return;
if (agentDefaultInputPlaceholder === null) {
agentDefaultInputPlaceholder = input.placeholder;
}
input.disabled = !enabled;
sendBtn.disabled = !enabled;
if (escalateBtn) {
escalateBtn.disabled = !enabled;
if (!enabled) {
escalateBtn.style.display = 'none';
}
}
if (enabled) {
input.placeholder = agentDefaultInputPlaceholder;
} else {
input.placeholder = "Udfyld dine oplysninger for at starte chatten";
input.value = "";
}
updateAgentEscalateButtonVisibility();
}
function renderAgentPrechat() {
const messagesDiv = document.getElementById('agent-messages');
if (!messagesDiv) return;
messagesDiv.innerHTML = "";
agentIsLoggedIn = false;
updateAgentEscalateButtonVisibility();
const formWrapper = document.createElement('div');
formWrapper.className = 'agent-prechat-card';
formWrapper.innerHTML = `
<form id="agent-prechat-form" class="agent-prechat-form">
<h4>Start chatten</h4>
<p>Udfyld felterne herunder for at åbne for beskeder.</p>
<div class="agent-form-group">
<label for="agent-prechat-name">Navn</label>
<input type="text" id="agent-prechat-name" name="name" placeholder="Dit navn" autocomplete="name" required />
</div>
<div class="agent-form-group">
<label for="agent-prechat-email">Mailadresse</label>
<input type="email" id="agent-prechat-email" name="email" placeholder="din@mail.dk" autocomplete="email" required />
</div>
<div class="agent-form-group"${agentConfiguredWebhook ? ' style="display:none;"' : ''}>
<label for="agent-prechat-webhook">Webhook URL</label>
<input type="url" id="agent-prechat-webhook" name="webhook" placeholder="${AGENT_PLACEHOLDER_WEBHOOK}" autocomplete="off" />
<span class="extra-info">Indsæt din webhook for at teste dit eget setup.</span>
</div>
<div id="agent-prechat-error" class="error-message" style="display:none;"></div>
<button type="submit" class="agent-start-btn">
<span aria-hidden="true">💬</span>
Start chat
</button>
</form>
`;
messagesDiv.appendChild(formWrapper);
const form = document.getElementById('agent-prechat-form');
if (form) {
form.addEventListener('submit', startAgentChat);
}
const nameInput = document.getElementById('agent-prechat-name');
if (nameInput) {
nameInput.focus();
}
}
function showAgentPrechatError(message) {
const errorDiv = document.getElementById('agent-prechat-error');
if (!errorDiv) return;
if (!message) {
errorDiv.textContent = '';
errorDiv.style.display = 'none';
} else {
errorDiv.textContent = message;
errorDiv.style.display = 'block';
}
}
function startAgentChat(event) {
if (event) {
event.preventDefault();
}
const nameInput = document.getElementById('agent-prechat-name');
const emailInput = document.getElementById('agent-prechat-email');
const webhookInput = document.getElementById('agent-prechat-webhook');
const name = nameInput ? nameInput.value.trim() : '';
const email = emailInput ? emailInput.value.trim() : '';
const webhookFromFormRaw = webhookInput ? webhookInput.value.trim() : '';
if (!name || !email) {
showAgentPrechatError("Udfyld navn og mail for at starte chatten.");
return;
}
if (!isValidAgentEmail(email)) {
showAgentPrechatError("Indtast en gyldig mailadresse.");
return;
}
const webhookFromForm = (webhookFromFormRaw && webhookFromFormRaw !== AGENT_PLACEHOLDER_WEBHOOK) ? webhookFromFormRaw : '';
const configuredWebhook = (agentConfiguredWebhook && agentConfiguredWebhook !== AGENT_PLACEHOLDER_WEBHOOK) ? agentConfiguredWebhook : '';
const resolvedWebhook = configuredWebhook || webhookFromForm;
if (!resolvedWebhook) {
showAgentPrechatError("Tilføj en webhook URL for at fortsætte.");
return;
}
agentMessages = [];
agentThreadId = generateThreadId();
agentUserInfo = { name, email, threadId: agentThreadId };
agentWebhookUrl = resolvedWebhook;
agentIsLoggedIn = true;
showAgentPrechatError("");
const formWrapper = document.querySelector('.agent-prechat-card');
if (formWrapper) {
formWrapper.remove();
}
setAgentInputEnabled(true);
updateAgentEscalateButtonVisibility();
addAgentMessage('ai', `Hej ${name}! 👋\nVelkommen til AI Agenten 🤖\nStart med at skrive nu!`);
appendAgentDisclaimer();
const input = document.getElementById('agent-userInput');
if (input) {
input.focus();
}
}
function addAgentMessage(sender, text) {
const messagesDiv = document.getElementById('agent-messages');
if (!messagesDiv) return;
const div = document.createElement('div');
div.className = 'agent-message ' + sender;
const formatted = text.replace(/\n/g, "<br>");
const time = new Date().toLocaleTimeString('da-DK', {
hour: '2-digit',
minute: '2-digit'
});
div.innerHTML = `
<div class="agent-message-bubble">
<strong>${sender === 'user' ? 'Du' : 'AI'}:</strong> ${formatted}
</div>
<div class="message-time">${time}</div>
`;
messagesDiv.appendChild(div);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
function appendAgentDisclaimer() {
const messagesDiv = document.getElementById('agent-messages');
if (!messagesDiv) return;
const lastBubble = messagesDiv.querySelector('.agent-message.ai:last-child .agent-message-bubble');
if (!lastBubble) return;
if (lastBubble.querySelector('.agent-disclaimer')) {
return;
}
const disclaimer = document.createElement('div');
disclaimer.className = 'agent-disclaimer';
disclaimer.textContent = "Dette er en AI. AI kan lave fejl.";
lastBubble.appendChild(disclaimer);
}
function showAgentTyping() {
isAgentTyping = true;
const messagesDiv = document.getElementById('agent-messages');
if (!messagesDiv) return;
const existingTyping = messagesDiv.querySelector('.typing-indicator');
if (existingTyping) {
existingTyping.remove();
}
const typingDiv = document.createElement('div');
typingDiv.className = 'agent-message ai typing-indicator';
typingDiv.innerHTML = `
<div class="agent-message-bubble typing-bubble">
<strong>AI:</strong>
<div class="typing-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
`;
messagesDiv.appendChild(typingDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
function hideAgentTyping() {
isAgentTyping = false;
const typingIndicator = document.querySelector('.typing-indicator');
if (typingIndicator) {
typingIndicator.remove();
}
}
async function sendAgentMessage() {
const input = document.getElementById('agent-userInput');
const sendBtn = document.getElementById('agent-sendBtn');
if (!input || !sendBtn) return;
const text = input.value.trim();
if (!text || isAgentTyping) return;
addAgentMessage('user', text);
agentMessages.push({ role: "user", content: text });
input.value = '';
input.style.height = 'auto';
updateAgentEscalateButtonVisibility();
sendBtn.disabled = true;
if (!agentWebhookUrl) {
addAgentMessage('ai', "Webhook mangler. Genstart chatten for at konfigurere den.");
sendBtn.disabled = false;
input.focus();
return;
}
showAgentTyping();
try {
const historyText = agentMessages.length > 1
? agentMessages
.slice(0, agentMessages.length - 1)
.map(m => `\n${m.role === 'user' ? 'Bruger' : 'AI'}: ${m.content}`)
.join("")
: "";
const res = await fetch(agentWebhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
threadId: agentUserInfo.threadId,
name: agentUserInfo.name,
email: agentUserInfo.email,
history: historyText.trim(),
question: text
})
});
const raw = await res.text();
console.log("RAW response fra webhook:", raw);
hideAgentTyping();
let reply = "";
try {
const data = JSON.parse(raw);
if (data && typeof data.reply === "string") {
reply = data.reply;
} else if (data && typeof data.message === "string") {
reply = data.message;
} else if (typeof data === "string") {
reply = data;
} else {
reply = raw;
}
} catch (err) {
console.log("Response er ikke JSON, viser som tekst");
reply = raw;
}
if (!reply || reply.trim() === "Accepted" || reply.trim() === "") {
reply = "Tak for din besked! Jeg har modtaget den og vender tilbage snarest.";
}
agentMessages.push({ role: "ai", content: reply });
addAgentMessage('ai', reply);
} catch (error) {
console.error("Fejl ved svar fra AI-agent:", error);
hideAgentTyping();
addAgentMessage('ai', "Beklager, der opstod en fejl. Prøv venligst igen senere. 😔");
} finally {
sendBtn.disabled = false;
input.focus();
updateAgentEscalateButtonVisibility();
}
}
function isValidAgentEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
function generateThreadId() {
const timestamp = Date.now().toString(36);
const randomStr = Math.random().toString(36).substring(2, 15);
return `thread_${timestamp}_${randomStr}`;
}
function autoResizeAgentInput(event) {
const textarea = event.target;
textarea.style.height = 'auto';
textarea.style.height = Math.min(textarea.scrollHeight, 140) + 'px';
updateAgentEscalateButtonVisibility();
}
function updateAgentEscalateButtonVisibility() {
const btn = document.getElementById('agent-escalateBtn');
const input = document.getElementById('agent-userInput');
if (!btn || !input) return;
const hasEndpoint = Boolean(agentEscalationWebhookUrl);
const shouldShow = agentIsLoggedIn && hasEndpoint && !agentEscalateModalOpen && input.value.trim() === "" && !input.disabled;
btn.style.display = shouldShow ? 'flex' : 'none';
}
function setAgentEscalateStatus(message, isError = true) {
const feedback = document.getElementById('agent-escalate-error');
if (!feedback) return;
feedback.textContent = message || '';
feedback.style.color = isError ? '#b91c1c' : '#065f46';
}
function getAgentTranscript() {
if (!agentMessages.length) return "";
return agentMessages
.map(m => `${m.role === 'user' ? 'Bruger' : 'AI'}: ${m.content}`)
.join("\n");
}
function openAgentEscalationModal() {
if (!agentEscalationWebhookUrl) {
addAgentMessage('ai', "Kontakt-funktionen er ikke konfigureret.");
return;
}
const modal = document.getElementById('agent-escalateModal');
if (!modal) return;
agentEscalateModalOpen = true;
modal.hidden = false;
modal.setAttribute('aria-hidden', 'false');
setAgentEscalateStatus("");
const form = document.getElementById('agent-escalate-form');
if (form) {
form.reset();
}
const messageInput = document.getElementById('agent-escalate-message');
if (messageInput) {
setTimeout(() => messageInput.focus(), 10);
}
updateAgentEscalateButtonVisibility();
}
function closeAgentEscalationModal() {
const modal = document.getElementById('agent-escalateModal');
if (!modal) return;
agentEscalateModalOpen = false;
modal.hidden = true;
modal.setAttribute('aria-hidden', 'true');
setAgentEscalateStatus("");
updateAgentEscalateButtonVisibility();
}
async function submitAgentEscalationRequest(event) {
event.preventDefault();
if (!agentEscalationWebhookUrl) {
setAgentEscalateStatus("Kontakt-funktionen er ikke konfigureret.");
return;
}
const phoneInput = document.getElementById('agent-escalate-phone');
const messageInput = document.getElementById('agent-escalate-message');
const submitBtn = document.getElementById('agent-escalate-submit');
const message = messageInput ? messageInput.value.trim() : '';
const phone = phoneInput ? phoneInput.value.trim() : '';
if (!message) {
setAgentEscalateStatus("Besked er påkrævet.");
return;
}
if (!agentUserInfo || !agentUserInfo.name) {
setAgentEscalateStatus("Start chatten før du kontakter personalet.");
return;
}
setAgentEscalateStatus("Sender besked til personalet...", false);
if (submitBtn) {
submitBtn.disabled = true;
}
const transcript = getAgentTranscript();
const payload = {
threadId: agentUserInfo.threadId,
name: agentUserInfo.name,
email: agentUserInfo.email,
phone,
message,
history: transcript
};
try {
await fetch(agentEscalationWebhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
setAgentEscalateStatus("Din besked er sendt til personalet. Du modtager selv en bekræftelse.", false);
addAgentMessage('ai', "Din besked sendes til personalet. Du modtager selv en bekræftelse.");
setTimeout(() => {
closeAgentEscalationModal();
}, 1500);
} catch (error) {
console.error("Fejl ved eskalering:", error);
setAgentEscalateStatus("Kunne ikke sende beskeden. Prøv igen.");
} finally {
if (submitBtn) {
submitBtn.disabled = false;
}
}
}
function getManualHeaderOffset() {
const chatbox = document.getElementById('agent-chatbox');
if (!chatbox) return 0;
const attr = (chatbox.getAttribute('data-header-offset') || '').trim();
if (!attr) return 0;
const parsed = parseInt(attr, 10);
return Number.isNaN(parsed) ? 0 : parsed;
}
function detectStickyHeaderHeight() {
const selectors = ['#wpadminbar', 'header[role="banner"]', '.site-header', '#masthead', 'header'];
for (const selector of selectors) {
const node = document.querySelector(selector);
if (!node) continue;
const styles = window.getComputedStyle(node);
const isPinned = styles && (styles.position === 'fixed' || styles.position === 'sticky');
if (!isPinned) continue;
const rect = node.getBoundingClientRect();
if (!rect || rect.height <= 0) continue;
if (styles.position === 'sticky' && rect.top > 0) continue;
return rect.height;
}
return 0;
}
function computeHeaderOffset() {
return Math.max(getManualHeaderOffset(), detectStickyHeaderHeight());
}
function updateAgentChatboxHeight() {
const maxHeight = 1000;
const minHeight = 360;
const viewportTarget = window.innerWidth < 768 ? window.innerHeight : window.innerHeight * 0.85;
const rawHeight = viewportTarget - computeHeaderOffset();
const constrainedHeight = Math.max(minHeight, rawHeight);
const nextHeight = Math.min(constrainedHeight, maxHeight);
document.documentElement.style.setProperty('--agent-chatbox-height', `${nextHeight}px`);
}
document.addEventListener('DOMContentLoaded', function() {
const sendBtn = document.getElementById('agent-sendBtn');
if (sendBtn) {
sendBtn.addEventListener('click', sendAgentMessage);
}
const escalateBtn = document.getElementById('agent-escalateBtn');
if (escalateBtn) {
escalateBtn.addEventListener('click', openAgentEscalationModal);
}
const escalateCancel = document.getElementById('agent-escalate-cancel');
if (escalateCancel) {
escalateCancel.addEventListener('click', closeAgentEscalationModal);
}
const escalateClose = document.getElementById('agent-escalate-close');
if (escalateClose) {
escalateClose.addEventListener('click', closeAgentEscalationModal);
}
const escalateForm = document.getElementById('agent-escalate-form');
if (escalateForm) {
escalateForm.addEventListener('submit', submitAgentEscalationRequest);
}
const escalateModal = document.getElementById('agent-escalateModal');
if (escalateModal) {
escalateModal.addEventListener('click', function(event) {
if (event.target === escalateModal) {
closeAgentEscalationModal();
}
});
}
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape' && agentEscalateModalOpen) {
closeAgentEscalationModal();
}
});
agentConfiguredWebhook = resolveAgentConfiguredWebhook();
agentWebhookUrl = (agentConfiguredWebhook && agentConfiguredWebhook !== AGENT_PLACEHOLDER_WEBHOOK)
? agentConfiguredWebhook
: null;
agentEscalationWebhookUrl = resolveAgentEscalationWebhook();
renderAgentPrechat();
setAgentInputEnabled(false);
updateAgentChatboxHeight();
window.addEventListener('resize', updateAgentChatboxHeight);
const userInput = document.getElementById('agent-userInput');
if (userInput) {
userInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendAgentMessage();
}
});
userInput.addEventListener('input', autoResizeAgentInput);
}
updateAgentEscalateButtonVisibility();
});
})();
</script>
<!-- Agent Chat Embed END -->