This commit is contained in:
Oliver
2025-07-28 07:22:36 -03:00
parent abe9ae4712
commit 7695e30d59
8 changed files with 325 additions and 320 deletions

View File

@@ -11,7 +11,7 @@
<link rel="stylesheet" href="style.css">
<script src="widget/widget.js"></script>
<link rel="stylesheet" href="widget/OD8N.css">
<link rel="stylesheet" href="widget/custom/od8n.css">
<script type="application/ld+json">
{
"@context": "https://schema.org",

View File

@@ -1,184 +0,0 @@
/* Container */
#cw-chatToggle {
position: fixed;
bottom: 24px;
right: 24px;
background-color: #FFA500;
color: white;
padding: 12px 16px;
border-radius: 25px;
font-weight: 600;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
z-index: 100000;
border: none;
user-select: none;
transition: background-color 0.3s ease;
}
#cw-chatToggle:hover {
transform: translateY(-5px);
}
#cw-chatToggle div {
display: flex;
align-items: center;
gap: 8px;
}
#cw-chatToggle img {
image-rendering: pixelated;
image-rendering: optimizeQuality;
image-rendering: -webkit-optimize-contrast; /* Chrome */
image-rendering: crisp-edges; /* Fallback */
}
#cw-chatToggle span.text-sm {
font-weight: 500;
font-size: 0.875rem; /* 14px */
}
#cw-chatToggle span.text-xs {
font-size: 0.75rem; /* 12px */
}
/* Chat widget container */
#cw-chatWidget {
display: none;
position: fixed;
bottom: 100px;
right: 24px;
width: 500px;
max-height: 70vh;
background: white;
border: 1px solid #ccc;
border-radius: 12px;
flex-direction: column;
z-index: 100000;
font-family: system-ui, sans-serif;
}
#cw-chatWidget.active {
display: flex;
}
/* Logo container */
#cw-chatWidget .logo-container {
display: flex;
justify-content: center;
padding-top: 16px;
padding-bottom: 10px;
}
#cw-chatWidget .logo-container img {
height: 60px;
user-select: none;
image-rendering: optimizeQuality;
image-rendering: pixelated;
image-rendering: -webkit-optimize-contrast; /* Chrome */
image-rendering: crisp-edges; /* Fallback */
}
/* Header */
#cw-status {
padding: 10px 10px;
background-color: #f0f0f0;
border-bottom: 1px solid #ddd;
max-height: 0;
transition: max-height 0.5s ease-in-out, padding 0.5s ease-in-out;
}
#cw-chatWidget .header {
background-color: #0070c0;
color: white;
padding: 8px 16px;
font-weight: 600;
text-align: center;
user-select: none;
}
/* Messages container */
#cw-chatMessages {
flex: 1 1 auto;
overflow-y: auto;
padding: 16px;
font-size: 0.875rem;
line-height: 1.4;
user-select: text;
scroll-behavior: smooth;
background: #fafafa;
}
/* Chat form */
#cw-chatForm {
display: flex;
margin: 0px;
border-top: 1px solid #ddd;
}
#cw-chatInput {
flex: 1;
border: none;
padding: 8px 20px;
font-size: 1rem;
outline-offset: 2px;
border-radius: 0 0 0 12px;
}
#cw-chatInput:focus {
outline: 0px solid #0070c0;
}
#cw-chatForm button {
background-color: #0070c0;
border: none;
color: white;
padding: 0 16px;
cursor: pointer;
font-weight: 600;
border-radius: 0 0 12px 0;
transition: background-color 0.3s ease;
}
#cw-chatForm button:hover {
background-color: #005a9e;
}
/* Message bubbles */
.cw-message {
max-width: 75%;
margin-bottom: 8px;
padding: 8px 12px;
border-radius: 12px;
word-wrap: break-word;
white-space: pre-wrap;
}
.cw-message.user {
background-color: #DCF8C6;
color: #333;
margin-left: auto;
text-align: right;
}
.cw-message.bot {
background-color: #E0E0E0;
color: #333;
margin-right: auto;
text-align: left;
}
.button {
display: inline-block;
padding: 12px 24px;
background-color: #4CAF50; /* Green */
color: white;
text-align: center;
text-decoration: none;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
transition: background-color 0.3s ease, box-shadow 0.3s ease;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.button:hover {
background-color: #45a049;
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
}
.button:active {
background-color: #3e8e41;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Order Status Wizard</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<ul class="wizard">
<li class="open">
<div class="header">
<i class="fa-solid fa-signature"></i>
<span class="title">Name</span>
</div>
<div class="text">Open</div>
</li>
<li class="done">
<div class="header">
<i class="fa-solid fa-envelope"></i>
<span class="title">Email</span>
</div>
<div class="text">deraa@fewew.de</div>
</li>
<li class="done">
<div class="header">
<i class="fa-solid fa-cart-shopping"></i>
<span class="title">Product</span>
</div>
<div class="text">3 Hours</div>
</li>
</ul>
</body>
</html>

View File

@@ -0,0 +1,69 @@
body {
margin: 0;
font-family: Arial, sans-serif;
}
.wizard {
display: flex;
justify-content: space-between;
list-style: none;
padding: 0;
margin: 0;
width: 100%;
background-color: #e0e0e0;
}
.wizard li {
flex: 1;
display: flex;
flex-direction: column;
border-right: 2px solid #fff;
color: white;
box-sizing: border-box;
}
.wizard li:last-child {
border-right: none;
}
.wizard .header {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
padding: 10px;
font-weight: bold;
font-size: 16px;
text-align: center;
}
/* Detail text section */
.wizard .text {
padding: 20px 10px;
text-align: center;
font-size: 14px;
flex-grow: 1;
}
/* DARK GREY: Open */
.wizard li.open {
background-color: #7f8c8d;
}
.wizard li.open .header {
background-color: #5d6d7e;
}
/* DARK GREEN: Done */
.wizard li.done {
background-color: #1e8449;
}
.wizard li.done .header {
background-color: #145a32;
}
.wizard i {
font-size: 18px;
}

View File

@@ -0,0 +1,6 @@
{
"api": "https://ai.odoo4projects.com/webhook/81742473-b50b-4845-a5f9-916d9fe60876/chat",
"preamble": "👋 Welcome! Need help? I'm powered by n8n automation to assist you!",
"widgetHTML": "<button id='cw-chatToggle' aria-label='Toggle chat widget' type='button'>\n <img src='logo.svg' alt='Logo'><span class='text-xs'>Agent</span>\n</button>\n<div id='cw-chatWidget' style='display: none;' role='region' aria-live='polite' aria-label='Chat widget'>\n <div class='logo-container'><img src='logo.svg' alt='Logo' /></div>\n <div class='header'>Support</div>\n <div id='cw-status' style='transition: all 0.3s ease; overflow: hidden;'></div>\n <div id='cw-chatMessages' style='max-height: 200px; overflow-y: auto; padding: 10px;'></div>\n <form id='cw-chatForm' autocomplete='off'>\n <input type='text' id='cw-chatInput' placeholder='Type your message...' required autocomplete='off' />\n <button type='submit'>Send</button>\n </form>\n</div>"
}

View File

@@ -0,0 +1,80 @@
/* Headline above the wizard */
.wizard-headline {
text-align: center;
font-size: 24px;
font-weight: bold;
padding: 20px 0;
background-color: #f8f9f9;
color: #2c3e50;
border-bottom: 2px solid #ddd;
}
/* Main wizard container */
.wizard {
display: flex;
justify-content: space-between;
list-style: none;
padding: 0;
margin: 0;
width: 100%;
background-color: #e0e0e0;
}
/* Each step box */
.wizard li {
flex: 1;
display: flex;
flex-direction: column;
border-right: 2px solid #fff;
color: white;
box-sizing: border-box;
}
.wizard li:last-child {
border-right: none;
}
/* Header inside each box (icon + label) */
.wizard .header {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
padding: 10px;
font-weight: bold;
font-size: 16px;
text-align: center;
}
/* Detail text area inside the box */
.wizard .text {
padding: 20px 10px;
text-align: center;
font-size: 14px;
flex-grow: 1;
}
/* DARK GREY: Open */
.wizard li.open {
background-color: #7f8c8d;
}
.wizard li.open .header {
background-color: #5d6d7e;
}
/* DARK GREEN: Done */
.wizard li.done {
background-color: #1e8449;
}
.wizard li.done .header {
background-color: #145a32;
}
/* Icon style */
.wizard i {
font-size: 18px;
}

View File

@@ -0,0 +1,6 @@
{
"api": "https://ai.odoo4projects.com/webhook/81742473-b50b-4845-a5f9-916d9fe60876/chat",
"preamble": "Welcome to OD8N. We like to help you assisting your order as well as answering FAQs about our service",
"widgetHTML": "<button id='cw-chatToggle' aria-label='Toggle chat widget' type='button'>\n <img src='logo.svg' alt='Logo'><span class='text-xs'>Agent</span>\n</button>\n<div id='cw-chatWidget' style='display: none;' role='region' aria-live='polite' aria-label='Chat widget'>\n <div class='logo-container'><img src='logo.svg' alt='Logo' /></div>\n <div class='header'>Support</div>\n <div id='cw-status' style='transition: all 0.3s ease; overflow: hidden;'></div>\n <div id='cw-chatMessages' style='max-height: 200px; overflow-y: auto; padding: 10px;'></div>\n <form id='cw-chatForm' autocomplete='off'>\n <input type='text' id='cw-chatInput' placeholder='Type your message...' required autocomplete='off' />\n <button type='submit'>Send</button>\n </form>\n</div>"
}

View File

@@ -1,150 +1,141 @@
document.addEventListener('DOMContentLoaded', () => {
// 1. Inject the widget HTML into the page
const widgetHTML = `
<button id="cw-chatToggle" aria-label="Toggle chat widget" type="button">
<img src="logo.svg" alt="Logo"><span class="text-xs">Agent</span>
</button>
document.addEventListener('DOMContentLoaded', async () => {
try {
let id = window.location.hostname || "localhost";
console.log("Detected domain ID:", id);
<div id="cw-chatWidget" role="region" aria-live="polite" aria-label="Customer support chat widget" style="display: none;">
<div class="logo-container">
<img src="logo.svg" alt="Logo" />
</div>
<div class="header">Kundenservice</div>
<div id="cw-status" style="transition: all 0.3s ease; overflow: hidden;"></div>
<div id="cw-chatMessages" style="max-height: 200px; overflow-y: auto; padding: 10px;"></div>
<form id="cw-chatForm" autocomplete="off">
<input type="text" id="cw-chatInput" placeholder="Type your message..." required autocomplete="off" />
<button type="submit">Senden</button>
</form>
</div>
`;
document.body.insertAdjacentHTML('beforeend', widgetHTML);
const configUrl = `http://localhost:8000/widget/custom/${id}.json`;
console.log(configUrl)
const response = await fetch(configUrl);
if (!response.ok) throw new Error(`Config not found at ${configUrl}`);
// 2. Define variables and constants
const preamble = `
We use what we preach — this chatbot is powered by our own n8n automation 🤖 to help you quickly find what you need.
const config = await response.json();
const { widgetHTML, preamble, api } = config;
Want to talk to a real automation expert? Just buy a service pack 💼 — it includes a 1-hour get-to-know session with our team 👥.
If its not the right fit, no worries — well refund you 💸.
`;
const api = 'https://ai.odoo4projects.com/webhook/81742473-b50b-4845-a5f9-916d9fe60876/chat';
const sessionId = crypto.randomUUID();
let chatOpened = false;
// 3. Select DOM elements
const chatToggle = document.getElementById('cw-chatToggle');
const chatWidget = document.getElementById('cw-chatWidget');
const chatForm = document.getElementById('cw-chatForm');
const chatInput = document.getElementById('cw-chatInput');
const chatMessages = document.getElementById('cw-chatMessages');
const statusDiv = document.getElementById('cw-status');
// 4. Append a message to the chat
function appendMessage(text, sender) {
const msg = document.createElement('div');
msg.classList.add('cw-message', sender === 'user' ? 'user' : 'bot');
msg.innerHTML = text;
chatMessages.appendChild(msg);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// 5. Toggle the chat widget
chatToggle.addEventListener('click', () => {
const isVisible = chatWidget.style.display === 'block';
chatWidget.style.display = isVisible ? 'none' : 'block';
if (!isVisible && !chatOpened) {
appendMessage(preamble, 'bot');
chatOpened = true;
if (!widgetHTML || !api) {
console.error('Invalid config format. Expected widgetHTML and api.');
return;
}
});
// 6. Handle chat message submission
chatForm.addEventListener('submit', async (e) => {
e.preventDefault();
const input = chatInput.value.trim();
if (!input) return;
// Step 3: Inject HTML into page
document.body.insertAdjacentHTML('beforeend', widgetHTML);
appendMessage(input, 'user');
chatInput.value = '';
chatInput.focus();
// Step 4: Select DOM elements
const chatToggle = document.getElementById('cw-chatToggle');
const chatWidget = document.getElementById('cw-chatWidget');
const chatForm = document.getElementById('cw-chatForm');
const chatInput = document.getElementById('cw-chatInput');
const chatMessages = document.getElementById('cw-chatMessages');
const statusDiv = document.getElementById('cw-status');
try {
const response = await fetch(api, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'sendMessage',
sessionId,
chatInput: input
})
});
const sessionId = crypto.randomUUID();
let chatOpened = false;
console.log(response)
const data = await response.json();
if (data.status) {
statusDiv.innerHTML = data.status;
requestAnimationFrame(() => {
statusDiv.style.padding = '10px';
statusDiv.style.maxHeight = statusDiv.scrollHeight + 'px';
});
} else {
statusDiv.style.maxHeight = '0';
statusDiv.style.padding = '0 10px';
}
appendMessage(data.output, 'bot');
} catch (error) {
appendMessage('Fehler beim Verbinden mit dem Server. Bitte versuchen Sie es später erneut.', 'bot');
console.error(error);
// Step 5: Utility to append message
function appendMessage(text, sender) {
const msg = document.createElement('div');
msg.classList.add('cw-message', sender === 'user' ? 'user' : 'bot');
msg.innerHTML = text;
chatMessages.appendChild(msg);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
});
// 7. Handle buy button clicks
function handleBuyClick(buttonId, message) {
const btn = document.getElementById(buttonId);
if (!btn) return;
// Step 6: Toggle chat visibility
chatToggle.addEventListener('click', () => {
const isVisible = chatWidget.style.display === 'block';
chatWidget.style.display = isVisible ? 'none' : 'block';
btn.addEventListener('click', (e) => {
e.preventDefault();
if (chatWidget.style.display !== 'block') {
chatWidget.style.display = 'block';
if (!chatOpened) {
appendMessage(preamble, 'bot');
chatOpened = true;
}
if (!isVisible && !chatOpened) {
appendMessage(preamble, 'bot');
chatOpened = true;
}
appendMessage(message, 'user');
fetch(api, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'sendMessage',
sessionId,
chatInput: message
})
})
.then(res => res.json())
.then(data => {
appendMessage(data.output, 'bot');
})
.catch(err => {
appendMessage('Fehler beim Verbinden mit dem Server. Bitte versuchen Sie es später erneut.', 'bot');
console.error(err);
});
});
}
// 8. Attach handlers to pricing buttons (if they exist in your page)
handleBuyClick('buy-3h', 'I want to buy the 3 Hours pack for $450.');
handleBuyClick('buy-5h', 'I want to buy the 5 Hours pack for $675.');
handleBuyClick('buy-10h', 'I want to buy the 10 Hours pack for $1200.');
handleBuyClick('buy-bundle', 'I want to buy the ODOO and N8N bundle for $395 per year.');
// Step 7: Handle form submission
chatForm.addEventListener('submit', async (e) => {
e.preventDefault();
const input = chatInput.value.trim();
if (!input) return;
appendMessage(input, 'user');
chatInput.value = '';
chatInput.focus();
try {
const res = await fetch(api, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'sendMessage',
sessionId,
chatInput: input
})
});
console.log(res)
const data = await res.json();
if (data.status) {
statusDiv.innerHTML = data.status;
requestAnimationFrame(() => {
statusDiv.style.padding = '10px';
statusDiv.style.maxHeight = statusDiv.scrollHeight + 'px';
});
} else {
statusDiv.style.maxHeight = '0';
statusDiv.style.padding = '0 10px';
}
appendMessage(data.output, 'bot');
} catch (error) {
appendMessage('Fehler beim Verbinden mit dem Server. Bitte versuchen Sie es später erneut.', 'bot');
console.error(error);
}
});
// Step 8: Attach pricing buttons if they exist
function handleBuyClick(buttonId, message) {
const btn = document.getElementById(buttonId);
if (!btn) return;
btn.addEventListener('click', (e) => {
e.preventDefault();
if (chatWidget.style.display !== 'block') {
chatWidget.style.display = 'block';
if (!chatOpened) {
appendMessage(preamble, 'bot');
chatOpened = true;
}
}
appendMessage(message, 'user');
fetch(api, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'sendMessage',
sessionId,
chatInput: message
})
})
.then(res => res.json())
.then(data => {
appendMessage(data.output, 'bot');
})
.catch(err => {
appendMessage('Fehler beim Verbinden mit dem Server. Bitte versuchen Sie es später erneut.', 'bot');
console.error(err);
});
});
}
handleBuyClick('buy-3h', 'I want to buy the 3 Hours pack for $450.');
handleBuyClick('buy-5h', 'I want to buy the 5 Hours pack for $675.');
handleBuyClick('buy-10h', 'I want to buy the 10 Hours pack for $1200.');
handleBuyClick('buy-bundle', 'I want to buy the ODOO and N8N bundle for $395 per year.');
} catch (err) {
console.error('Failed to initialize chat widget:', err);
}
});