Spaces:
Running
Running
Update customer.html
Browse files- customer.html +275 -205
customer.html
CHANGED
|
@@ -16,16 +16,21 @@
|
|
| 16 |
sans: ['Vazirmatn', 'sans-serif'],
|
| 17 |
},
|
| 18 |
colors: {
|
| 19 |
-
obsidian: '#09090b', /* مشکی
|
| 20 |
-
coal: '#
|
| 21 |
-
clay: '#
|
| 22 |
-
brass: '#c5a880', /* برنز
|
| 23 |
-
brassHover: '#
|
| 24 |
emeraldMuted: '#10b981', /* سبز تایید نهایی */
|
| 25 |
},
|
| 26 |
boxShadow: {
|
| 27 |
-
glow: '0 0
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
}
|
| 30 |
}
|
| 31 |
}
|
|
@@ -36,83 +41,132 @@
|
|
| 36 |
background-color: #09090b;
|
| 37 |
color: #f4f4f5;
|
| 38 |
-webkit-tap-highlight-color: transparent;
|
|
|
|
| 39 |
}
|
| 40 |
-
/* افکت شیشه
|
| 41 |
.glass-panel {
|
| 42 |
-
background: rgba(
|
| 43 |
-
backdrop-filter: blur(
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
}
|
| 46 |
-
/* بافت رسید
|
| 47 |
.receipt-texture {
|
| 48 |
-
background-color: #
|
| 49 |
-
background-image: radial-gradient(rgba(197, 168, 128, 0.
|
| 50 |
-
background-size:
|
| 51 |
}
|
| 52 |
-
/* حاشیه دندانهدار
|
| 53 |
.jagged-edge {
|
| 54 |
-
background-image: linear-gradient(-135deg, #
|
| 55 |
background-position: left top;
|
| 56 |
background-repeat: repeat-x;
|
| 57 |
background-size: 8px 8px;
|
| 58 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
</style>
|
| 60 |
</head>
|
| 61 |
-
<body class="min-h-screen flex items-center justify-center font-sans overflow-x-hidden antialiased bg-gradient-to-tr from-obsidian via-[#
|
| 62 |
|
| 63 |
-
<!--
|
| 64 |
-
<div class="hidden
|
| 65 |
-
<div class="absolute
|
| 66 |
-
<div class="absolute
|
| 67 |
</div>
|
| 68 |
|
| 69 |
-
<!-- فریم
|
| 70 |
-
<div class="w-full h-screen
|
| 71 |
|
| 72 |
-
<!-- لایه نهایی ثبت موفقیتآمیز سفارش -->
|
| 73 |
-
<div id="successOverlay" class="hidden absolute inset-0 bg-obsidian/
|
| 74 |
-
<div class="w-
|
| 75 |
-
<svg class="w-
|
| 76 |
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
| 77 |
</svg>
|
| 78 |
</div>
|
| 79 |
-
<div class="space-y-
|
| 80 |
-
<h2 class="text-
|
| 81 |
-
<p class="text-xs text-zinc-400 max-w-xs leading-relaxed mx-auto">فاکتور
|
| 82 |
</div>
|
|
|
|
|
|
|
| 83 |
</div>
|
| 84 |
|
| 85 |
-
<!--
|
| 86 |
-
<div
|
| 87 |
-
<div class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
-
<!-- لوگو و برند
|
| 90 |
-
<div class="text-center space-y-
|
| 91 |
-
<div class="inline-flex
|
| 92 |
-
<
|
| 93 |
-
|
| 94 |
-
<
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
</div>
|
| 100 |
-
<h1 class="text-xl font-extrabold text-white tracking-tight">سفارش هوشمند <span class="text-brass">Cafe AI</span></h1>
|
| 101 |
-
<p class="text-[11px] text-zinc-500 leading-relaxed max-w-xs mx-auto">لطفاً ابتدا میز خود را فعال کنید تا ارتباط امن گفتگو آغاز گردد.</p>
|
| 102 |
</div>
|
| 103 |
|
| 104 |
-
<!--
|
| 105 |
-
<div class="space-y-
|
| 106 |
-
<div class="text-[10px] text-zinc-500 font-
|
| 107 |
-
<div class="grid grid-cols-
|
| 108 |
<script>
|
| 109 |
-
for (let i = 1; i <=
|
| 110 |
document.write(`
|
| 111 |
-
<button onclick="selectTable(${i})" id="tableBtn-${i}" class="table-btn aspect-square rounded-xl border border-
|
| 112 |
-
<span class="text-
|
| 113 |
-
<
|
| 114 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
| 115 |
-
</svg>
|
| 116 |
</button>
|
| 117 |
`);
|
| 118 |
}
|
|
@@ -122,91 +176,128 @@
|
|
| 122 |
|
| 123 |
</div>
|
| 124 |
|
| 125 |
-
<!-- دکمه
|
| 126 |
-
<button id="confirmTableBtn" disabled onclick="confirmTableSelection()" class="w-full py-4 bg-zinc-800 text-zinc-500 font-bold rounded-2xl text-xs transition-all duration-300 flex items-center justify-center gap-2 cursor-not-allowed">
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
| 128 |
</button>
|
| 129 |
</div>
|
| 130 |
|
| 131 |
-
<!-- صفحه دوم: محیط چت هوشمند با دستیار هوشمند نیلا -->
|
| 132 |
-
<div id="chatScreen" class="hidden flex-1 flex flex-col h-full bg-obsidian transition-all duration-500 opacity-0 transform translate-y-6 z-10">
|
| 133 |
|
| 134 |
-
<!-- هدر چت
|
| 135 |
-
<header class="px-5 py-4 border-b border-
|
| 136 |
<div class="flex items-center gap-3">
|
| 137 |
-
<div class="
|
| 138 |
-
<
|
| 139 |
-
|
| 140 |
-
<
|
| 141 |
-
</
|
| 142 |
</div>
|
| 143 |
<div>
|
| 144 |
-
<h2 class="text-xs font-extrabold text-white">دستیار
|
| 145 |
-
<p class="text-[9px] text-zinc-500 font-medium
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
</div>
|
| 147 |
</div>
|
| 148 |
|
| 149 |
-
<
|
|
|
|
| 150 |
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
|
| 151 |
-
<span
|
| 152 |
</div>
|
| 153 |
</header>
|
| 154 |
|
| 155 |
-
<!-- باکس چت اصلی حاوی پیامها -->
|
| 156 |
<div id="messagesContainer" class="flex-1 overflow-y-auto p-5 space-y-4">
|
| 157 |
-
<!-- ا
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
</div>
|
| 159 |
|
| 160 |
-
<!--
|
| 161 |
-
<div id="draftOrderContainer" class="hidden border-t border-brass/
|
| 162 |
<div class="jagged-edge h-2 w-full -mt-2"></div>
|
| 163 |
<div class="p-5 space-y-4 receipt-texture">
|
| 164 |
|
| 165 |
-
<div class="flex items-center justify-between border-b border-
|
| 166 |
-
<span class="text-xs font-
|
| 167 |
-
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 168 |
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
| 169 |
</svg>
|
| 170 |
-
صورتحساب م
|
| 171 |
</span>
|
| 172 |
-
<span class="text-[9px] text-zinc-500
|
| 173 |
</div>
|
| 174 |
|
| 175 |
-
<!-- لیست خطوط
|
| 176 |
-
<div id="draftItemsList" class="space-y-2 max-h-[
|
| 177 |
-
<!-- ا
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
</div>
|
| 179 |
|
| 180 |
-
<!-- دکمه تایید و
|
| 181 |
-
<button onclick="submitFinalOrder()" class="w-full py-
|
| 182 |
-
تایید و ارسال
|
|
|
|
|
|
|
|
|
|
| 183 |
</button>
|
| 184 |
</div>
|
| 185 |
</div>
|
| 186 |
|
| 187 |
-
<!-- بخش ک
|
| 188 |
-
<div id="streamingController" class="hidden px-5 py-
|
| 189 |
-
<div class="flex items-center gap-
|
| 190 |
-
<
|
| 191 |
-
|
| 192 |
-
<
|
| 193 |
-
<
|
|
|
|
|
|
|
| 194 |
</div>
|
| 195 |
-
<span>نیلا در حال پردازش
|
| 196 |
</div>
|
| 197 |
|
| 198 |
-
<button onclick="stopStreaming()" class="flex items-center gap-1 px-3 py-1 bg-rose-950/
|
| 199 |
<svg class="w-3 h-3" viewBox="0 0 24 24" fill="currentColor">
|
| 200 |
<rect x="4" y="4" width="16" height="16" rx="2" />
|
| 201 |
</svg>
|
| 202 |
-
توقف پاسخ
|
| 203 |
</button>
|
| 204 |
</div>
|
| 205 |
|
| 206 |
-
<!-- ف
|
| 207 |
-
<form id="customerChatForm" onsubmit="sendCustomerMessage(event)" class="p-3 border-t border-
|
| 208 |
-
<input type="text" id="customerChatInput" placeholder="
|
| 209 |
-
<button type="submit" id="sendBtn" class="bg-brass hover:bg-brassHover text-obsidian font-
|
| 210 |
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
| 211 |
<path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
| 212 |
</svg>
|
|
@@ -217,51 +308,62 @@
|
|
| 217 |
|
| 218 |
</div>
|
| 219 |
|
| 220 |
-
<!-- اسکریپت
|
| 221 |
<script>
|
| 222 |
-
//
|
| 223 |
-
const
|
| 224 |
-
|
|
|
|
| 225 |
let selectedTableNumber = null;
|
| 226 |
let abortController = null;
|
| 227 |
let isGenerating = false;
|
| 228 |
let currentDraftItems = null;
|
| 229 |
|
| 230 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
window.addEventListener('DOMContentLoaded', () => {
|
| 232 |
-
if (initialTableNumber &&
|
| 233 |
-
selectedTableNumber =
|
| 234 |
bypassTableSelector();
|
| 235 |
}
|
| 236 |
});
|
| 237 |
|
|
|
|
| 238 |
function selectTable(num) {
|
| 239 |
document.querySelectorAll('.table-btn').forEach(btn => {
|
| 240 |
btn.classList.remove('border-brass', 'bg-brass/10', 'text-brass', 'shadow-glow');
|
| 241 |
-
btn.classList.add('border-
|
| 242 |
});
|
| 243 |
-
|
| 244 |
const activeBtn = document.getElementById(`tableBtn-${num}`);
|
| 245 |
-
activeBtn.classList.remove('border-
|
| 246 |
activeBtn.classList.add('border-brass', 'bg-brass/10', 'text-brass', 'shadow-glow');
|
| 247 |
-
|
| 248 |
selectedTableNumber = num;
|
| 249 |
-
|
| 250 |
const confirmBtn = document.getElementById('confirmTableBtn');
|
| 251 |
confirmBtn.disabled = false;
|
| 252 |
confirmBtn.classList.remove('bg-zinc-800', 'text-zinc-500', 'cursor-not-allowed');
|
| 253 |
-
confirmBtn.classList.add('bg-brass', 'text-obsidian', 'hover:bg-brassHover');
|
| 254 |
}
|
| 255 |
|
|
|
|
| 256 |
function confirmTableSelection() {
|
| 257 |
if (!selectedTableNumber) return;
|
| 258 |
-
|
|
|
|
| 259 |
}
|
| 260 |
|
|
|
|
| 261 |
function bypassTableSelector() {
|
| 262 |
const screen1 = document.getElementById('tableSelectorScreen');
|
| 263 |
const screen2 = document.getElementById('chatScreen');
|
| 264 |
-
|
| 265 |
screen1.classList.add('opacity-0', 'scale-95');
|
| 266 |
setTimeout(() => {
|
| 267 |
screen1.classList.add('hidden');
|
|
@@ -274,41 +376,40 @@
|
|
| 274 |
}, 300);
|
| 275 |
}
|
| 276 |
|
| 277 |
-
|
| 278 |
-
// --- هندلر استریمینگ و جریان پاسخگویی نیلا (SSE Client Engine) ---
|
| 279 |
-
|
| 280 |
async function triggerNilaGreeting() {
|
| 281 |
-
|
| 282 |
-
await fetchNilaStream("سلام. من روی صندلی خودم نشستم. خوش آمدگویی کن و بگو چطور میتوانی کمکم کنی.");
|
| 283 |
}
|
| 284 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
async function sendCustomerMessage(e) {
|
| 286 |
e.preventDefault();
|
| 287 |
if (isGenerating) return;
|
| 288 |
-
|
| 289 |
const input = document.getElementById('customerChatInput');
|
| 290 |
const userText = input.value.trim();
|
| 291 |
if (!userText) return;
|
| 292 |
-
|
| 293 |
appendChatBubble('user', userText);
|
| 294 |
input.value = '';
|
| 295 |
-
|
| 296 |
await fetchNilaStream(userText);
|
| 297 |
}
|
| 298 |
|
|
|
|
| 299 |
async function fetchNilaStream(userText) {
|
| 300 |
if (isGenerating) return;
|
| 301 |
-
|
| 302 |
isGenerating = true;
|
| 303 |
toggleInputs(true);
|
| 304 |
-
|
| 305 |
-
// ایجاد نمونه جدید آبورت کنترلر برای لغو عملیات
|
| 306 |
abortController = new AbortController();
|
| 307 |
-
|
| 308 |
-
// تزریق حباب پیام خالی جدید برای شروع کلمات استریم
|
| 309 |
const botBubbleId = appendChatBubble('model', '');
|
| 310 |
const botBubble = document.getElementById(botBubbleId);
|
| 311 |
-
|
| 312 |
try {
|
| 313 |
const response = await fetch(`/api/customer/chat_stream/${selectedTableNumber}`, {
|
| 314 |
method: 'POST',
|
|
@@ -316,21 +417,19 @@
|
|
| 316 |
body: JSON.stringify({ message: userText }),
|
| 317 |
signal: abortController.signal
|
| 318 |
});
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
const reader = response.body.getReader();
|
| 323 |
const decoder = new TextDecoder("utf-8");
|
| 324 |
let buffer = "";
|
| 325 |
-
|
| 326 |
while (true) {
|
| 327 |
const { value, done } = await reader.read();
|
| 328 |
if (done) break;
|
| 329 |
-
|
| 330 |
buffer += decoder.decode(value, { stream: true });
|
| 331 |
const lines = buffer.split("\n");
|
| 332 |
-
buffer = lines.pop();
|
| 333 |
-
|
| 334 |
for (const line of lines) {
|
| 335 |
if (!line.trim() || !line.startsWith("data: ")) continue;
|
| 336 |
|
|
@@ -339,44 +438,38 @@
|
|
| 339 |
const data = JSON.parse(jsonStr);
|
| 340 |
|
| 341 |
if (data.type === 'text') {
|
| 342 |
-
// چسباندن کاراکترها به انتهای حباب
|
| 343 |
botBubble.textContent += data.content;
|
| 344 |
} else if (data.type === 'draft') {
|
| 345 |
-
// بالا آمدن کشوی صورتحساب دندانهدار
|
| 346 |
showReceipt(data.items);
|
| 347 |
} else if (data.type === 'error') {
|
| 348 |
botBubble.textContent = data.content;
|
| 349 |
}
|
| 350 |
} catch (e) {
|
| 351 |
-
console.error("خطا در
|
| 352 |
}
|
| 353 |
}
|
| 354 |
|
| 355 |
-
// اسکرول نرم به پایین حین افزایش طول متن
|
| 356 |
const container = document.getElementById('messagesContainer');
|
| 357 |
container.scrollTop = container.scrollHeight;
|
| 358 |
}
|
| 359 |
-
|
| 360 |
finalizeStreamTurn();
|
| 361 |
-
|
| 362 |
} catch (err) {
|
| 363 |
if (err.name === 'AbortError') {
|
| 364 |
-
botBubble.textContent += ' [
|
| 365 |
} else {
|
| 366 |
-
botBubble.textContent = "بروز
|
| 367 |
}
|
| 368 |
finalizeStreamTurn();
|
| 369 |
}
|
| 370 |
}
|
| 371 |
|
|
|
|
| 372 |
async function stopStreaming() {
|
| 373 |
if (abortController) {
|
| 374 |
abortController.abort();
|
| 375 |
}
|
| 376 |
isGenerating = false;
|
| 377 |
toggleInputs(false);
|
| 378 |
-
|
| 379 |
-
// فراخوانی API قطع ارتباط سرور
|
| 380 |
try {
|
| 381 |
await fetch(`/api/customer/stop/${selectedTableNumber}`, { method: 'POST' });
|
| 382 |
} catch (e) {
|
|
@@ -393,78 +486,80 @@
|
|
| 393 |
const input = document.getElementById('customerChatInput');
|
| 394 |
const sendBtn = document.getElementById('sendBtn');
|
| 395 |
const streamController = document.getElementById('streamingController');
|
| 396 |
-
|
|
|
|
| 397 |
if (generating) {
|
| 398 |
input.disabled = true;
|
| 399 |
sendBtn.disabled = true;
|
| 400 |
sendBtn.classList.add('opacity-40', 'cursor-not-allowed');
|
| 401 |
streamController.classList.remove('hidden');
|
|
|
|
| 402 |
} else {
|
| 403 |
input.disabled = false;
|
| 404 |
sendBtn.disabled = false;
|
| 405 |
sendBtn.classList.remove('opacity-40', 'cursor-not-allowed');
|
| 406 |
streamController.classList.add('hidden');
|
|
|
|
| 407 |
}
|
| 408 |
}
|
| 409 |
|
|
|
|
| 410 |
function appendChatBubble(role, content) {
|
| 411 |
const container = document.getElementById('messagesContainer');
|
| 412 |
const bubbleId = 'bubble-' + Date.now();
|
| 413 |
-
|
| 414 |
let bubbleHtml = '';
|
|
|
|
| 415 |
if (role === 'user') {
|
| 416 |
bubbleHtml = `
|
| 417 |
-
<div class="flex justify-end gap-2.5 max-w-[85%] mr-auto">
|
| 418 |
-
<div class="bg-brass/
|
| 419 |
${content}
|
| 420 |
</div>
|
| 421 |
</div>
|
| 422 |
`;
|
| 423 |
} else {
|
| 424 |
bubbleHtml = `
|
| 425 |
-
<div class="flex gap-2.5 max-w-[
|
| 426 |
-
<div class="w-8 h-8 rounded-xl bg-brass/10 border border-brass/
|
| 427 |
-
<
|
| 428 |
-
<circle cx="12" cy="12" r="10"/>
|
| 429 |
-
<path d="M12 8v4l3 3"/>
|
| 430 |
-
</svg>
|
| 431 |
</div>
|
| 432 |
-
<div id="${bubbleId}" class="bg-coal border border-
|
| 433 |
${content}
|
| 434 |
</div>
|
| 435 |
</div>
|
| 436 |
`;
|
| 437 |
}
|
| 438 |
-
|
| 439 |
container.insertAdjacentHTML('beforeend', bubbleHtml);
|
| 440 |
container.scrollTop = container.scrollHeight;
|
| 441 |
return bubbleId;
|
| 442 |
}
|
| 443 |
|
| 444 |
-
|
| 445 |
-
// --- فاکتور صورتحساب کشویی (Sleek Thermal Receipt Modal) ---
|
| 446 |
-
|
| 447 |
function showReceipt(items) {
|
| 448 |
currentDraftItems = items;
|
| 449 |
const container = document.getElementById('draftOrderContainer');
|
| 450 |
const list = document.getElementById('draftItemsList');
|
| 451 |
-
|
| 452 |
list.innerHTML = '';
|
|
|
|
| 453 |
items.forEach(item => {
|
| 454 |
list.insertAdjacentHTML('beforeend', `
|
| 455 |
-
<div class="flex items-center justify-between text-xs bg-
|
| 456 |
<span class="text-zinc-300 font-bold">${item.name}</span>
|
| 457 |
-
<span class="text-brass font-
|
| 458 |
</div>
|
| 459 |
`);
|
| 460 |
});
|
| 461 |
-
|
| 462 |
container.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 463 |
}
|
| 464 |
|
|
|
|
| 465 |
async function submitFinalOrder() {
|
| 466 |
if (!currentDraftItems) return;
|
| 467 |
-
|
| 468 |
try {
|
| 469 |
const response = await fetch('/api/confirm_order', {
|
| 470 |
method: 'POST',
|
|
@@ -474,72 +569,47 @@
|
|
| 474 |
items: currentDraftItems
|
| 475 |
})
|
| 476 |
});
|
| 477 |
-
|
| 478 |
const result = await response.json();
|
| 479 |
if (response.ok && result.success) {
|
| 480 |
document.getElementById('draftOrderContainer').classList.add('hidden');
|
| 481 |
showSuccessTransition();
|
| 482 |
} else {
|
| 483 |
-
alert(result.error || "
|
| 484 |
}
|
| 485 |
} catch (err) {
|
| 486 |
-
alert("قطع ات
|
| 487 |
}
|
| 488 |
}
|
| 489 |
|
|
|
|
| 490 |
function showSuccessTransition() {
|
| 491 |
const overlay = document.getElementById('successOverlay');
|
| 492 |
overlay.classList.remove('hidden');
|
| 493 |
setTimeout(() => {
|
| 494 |
overlay.classList.remove('opacity-0');
|
| 495 |
}, 50);
|
| 496 |
-
|
| 497 |
-
//
|
| 498 |
setTimeout(() => {
|
| 499 |
overlay.classList.add('opacity-0');
|
| 500 |
setTimeout(() => {
|
| 501 |
overlay.classList.add('hidden');
|
| 502 |
-
|
| 503 |
}, 500);
|
| 504 |
-
},
|
| 505 |
}
|
| 506 |
|
| 507 |
-
|
|
|
|
| 508 |
abortController = null;
|
| 509 |
isGenerating = false;
|
| 510 |
currentDraftItems = null;
|
| 511 |
-
|
| 512 |
-
// شستشوی کامل سوابق کلاینت
|
| 513 |
document.getElementById('messagesContainer').innerHTML = '';
|
| 514 |
document.getElementById('draftOrderContainer').classList.add('hidden');
|
| 515 |
-
|
| 516 |
-
// ا
|
| 517 |
-
|
| 518 |
-
triggerNilaGreeting();
|
| 519 |
-
} else {
|
| 520 |
-
selectedTableNumber = null;
|
| 521 |
-
const screen1 = document.getElementById('tableSelectorScreen');
|
| 522 |
-
const screen2 = document.getElementById('chatScreen');
|
| 523 |
-
|
| 524 |
-
document.querySelectorAll('.table-btn').forEach(btn => {
|
| 525 |
-
btn.classList.remove('border-brass', 'bg-brass/10', 'text-brass', 'shadow-glow');
|
| 526 |
-
btn.classList.add('border-cafeBorder', 'bg-coal/30', 'text-zinc-300');
|
| 527 |
-
});
|
| 528 |
-
|
| 529 |
-
const confirmBtn = document.getElementById('confirmTableBtn');
|
| 530 |
-
confirmBtn.disabled = true;
|
| 531 |
-
confirmBtn.classList.add('bg-zinc-800', 'text-zinc-500', 'cursor-not-allowed');
|
| 532 |
-
confirmBtn.classList.remove('bg-brass', 'text-obsidian', 'hover:bg-brassHover');
|
| 533 |
-
|
| 534 |
-
screen2.classList.add('opacity-0', 'translate-y-6');
|
| 535 |
-
setTimeout(() => {
|
| 536 |
-
screen2.classList.add('hidden');
|
| 537 |
-
screen1.classList.remove('hidden');
|
| 538 |
-
setTimeout(() => {
|
| 539 |
-
screen1.classList.remove('opacity-0', 'scale-95');
|
| 540 |
-
}, 50);
|
| 541 |
-
}, 300);
|
| 542 |
-
}
|
| 543 |
}
|
| 544 |
</script>
|
| 545 |
</body>
|
|
|
|
| 16 |
sans: ['Vazirmatn', 'sans-serif'],
|
| 17 |
},
|
| 18 |
colors: {
|
| 19 |
+
obsidian: '#09090b', /* مشکی مطلق لوکس */
|
| 20 |
+
coal: '#111114', /* زغالی غلیظ */
|
| 21 |
+
clay: '#18181c', /* دودی خاکستری */
|
| 22 |
+
brass: '#c5a880', /* برنز طلایی ویژه */
|
| 23 |
+
brassHover: '#e5ca9e', /* برنز طلایی روشن */
|
| 24 |
emeraldMuted: '#10b981', /* سبز تایید نهایی */
|
| 25 |
},
|
| 26 |
boxShadow: {
|
| 27 |
+
glow: '0 0 25px rgba(197, 168, 128, 0.18)',
|
| 28 |
+
goldRadial: '0 0 40px rgba(197, 168, 128, 0.1)',
|
| 29 |
+
emeraldGlow: '0 0 30px rgba(16, 185, 129, 0.25)',
|
| 30 |
+
},
|
| 31 |
+
animation: {
|
| 32 |
+
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
| 33 |
+
'bounce-slow': 'bounce 1.5s infinite',
|
| 34 |
}
|
| 35 |
}
|
| 36 |
}
|
|
|
|
| 41 |
background-color: #09090b;
|
| 42 |
color: #f4f4f5;
|
| 43 |
-webkit-tap-highlight-color: transparent;
|
| 44 |
+
font-feature-settings: "ss01", "ss02";
|
| 45 |
}
|
| 46 |
+
/* افکت شیشهای پیشرفته */
|
| 47 |
.glass-panel {
|
| 48 |
+
background: rgba(17, 17, 20, 0.75);
|
| 49 |
+
backdrop-filter: blur(20px);
|
| 50 |
+
-webkit-backdrop-filter: blur(20px);
|
| 51 |
+
border: 1px solid rgba(197, 168, 128, 0.08);
|
| 52 |
+
}
|
| 53 |
+
.glass-chip {
|
| 54 |
+
background: rgba(28, 28, 32, 0.65);
|
| 55 |
+
backdrop-filter: blur(10px);
|
| 56 |
+
border: 1px solid rgba(255, 255, 255, 0.04);
|
| 57 |
}
|
| 58 |
+
/* بافت رسید دیجیتال */
|
| 59 |
.receipt-texture {
|
| 60 |
+
background-color: #111114;
|
| 61 |
+
background-image: radial-gradient(rgba(197, 168, 128, 0.04) 1px, transparent 0);
|
| 62 |
+
background-size: 10px 10px;
|
| 63 |
}
|
| 64 |
+
/* حاشیه دندانهدار فاکتور حرارتی */
|
| 65 |
.jagged-edge {
|
| 66 |
+
background-image: linear-gradient(-135deg, #111114 4px, transparent 0), linear-gradient(135deg, #111114 4px, transparent 0);
|
| 67 |
background-position: left top;
|
| 68 |
background-repeat: repeat-x;
|
| 69 |
background-size: 8px 8px;
|
| 70 |
}
|
| 71 |
+
/* سفارشیسازی اسکرولبار مخفی و ظریف */
|
| 72 |
+
::-webkit-scrollbar {
|
| 73 |
+
width: 3px;
|
| 74 |
+
height: 3px;
|
| 75 |
+
}
|
| 76 |
+
::-webkit-scrollbar-track {
|
| 77 |
+
background: transparent;
|
| 78 |
+
}
|
| 79 |
+
::-webkit-scrollbar-thumb {
|
| 80 |
+
background: rgba(197, 168, 128, 0.2);
|
| 81 |
+
border-radius: 10px;
|
| 82 |
+
}
|
| 83 |
+
/* انیمیشن لودر صوتی تفکر */
|
| 84 |
+
.wave-bar {
|
| 85 |
+
animation: wave 1.2s ease-in-out infinite;
|
| 86 |
+
}
|
| 87 |
+
@keyframes wave {
|
| 88 |
+
0%, 100% { height: 4px; }
|
| 89 |
+
50% { height: 18px; }
|
| 90 |
+
}
|
| 91 |
</style>
|
| 92 |
</head>
|
| 93 |
+
<body class="min-h-screen flex items-center justify-center font-sans overflow-x-hidden antialiased bg-gradient-to-tr from-obsidian via-[#0c0d11] to-[#14110b] p-0 sm:p-4">
|
| 94 |
|
| 95 |
+
<!-- هالههای نوری شناور بکگراند در حالت دسکتاپ -->
|
| 96 |
+
<div class="hidden md:block absolute inset-0 overflow-hidden pointer-events-none z-0">
|
| 97 |
+
<div class="absolute top-[10%] left-[15%] w-[450px] h-[450px] rounded-full bg-brass/3 blur-[120px] animate-pulse-slow"></div>
|
| 98 |
+
<div class="absolute bottom-[10%] right-[15%] w-[450px] h-[450px] rounded-full bg-brass/2 blur-[140px] animate-pulse-slow" style="animation-delay: 1.5s"></div>
|
| 99 |
</div>
|
| 100 |
|
| 101 |
+
<!-- فریم موبایل شبیهسازیشده لوکس (در موبایل کاملاً تمامصفحه و در دسکتاپ به شکل بدنه گوشی لود میشود) -->
|
| 102 |
+
<div class="w-full h-screen sm:h-[840px] sm:max-w-[410px] sm:rounded-[48px] sm:border-[6px] sm:border-[#1d1d22] sm:shadow-[0_25px_60px_-15px_rgba(0,0,0,0.9)] sm:relative overflow-hidden flex flex-col bg-obsidian z-10 transition-all duration-500 border-none">
|
| 103 |
|
| 104 |
+
<!-- لایه نهایی انیمیشن فیدبک ثبت موفقیتآمیز سفارش -->
|
| 105 |
+
<div id="successOverlay" class="hidden absolute inset-0 bg-obsidian/98 z-50 flex flex-col items-center justify-center p-8 text-center space-y-6 transition-all duration-500 opacity-0">
|
| 106 |
+
<div class="w-24 h-24 rounded-full bg-emeraldMuted/10 border border-emeraldMuted/20 flex items-center justify-center animate-pulse shadow-emeraldGlow">
|
| 107 |
+
<svg class="w-12 h-12 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
| 108 |
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
| 109 |
</svg>
|
| 110 |
</div>
|
| 111 |
+
<div class="space-y-3">
|
| 112 |
+
<h2 class="text-xl font-black text-white tracking-wide">سفارش شما ارسال شد</h2>
|
| 113 |
+
<p class="text-xs text-zinc-400 max-w-xs leading-relaxed mx-auto">فاکتور انتخابی با موفقیت در صف آشپزخانه ثبت گردید. میز شما جهت ثبت سفارشات بعدی آماده و پاکسازی شد.</p>
|
| 114 |
</div>
|
| 115 |
+
<div class="w-16 h-[1px] bg-brass/20"></div>
|
| 116 |
+
<div class="text-[10px] text-brass/70 tracking-widest uppercase font-mono">CAFE AI SMART SYSTEM</div>
|
| 117 |
</div>
|
| 118 |
|
| 119 |
+
<!-- نوار وضعیت بالای گوشی شبیهسازی شده لوکس (Status Bar) -->
|
| 120 |
+
<div class="w-full bg-[#0c0d11]/80 backdrop-blur-md px-6 py-3 flex justify-between items-center z-40 border-b border-zinc-900/40 text-[11px] font-medium text-zinc-400">
|
| 121 |
+
<div id="statusBarClock" class="font-bold tracking-tight">۱۲:۰۰</div>
|
| 122 |
+
<!-- ناچ یا شکاف بالای صفحه گوشی دسکتاپ -->
|
| 123 |
+
<div class="hidden sm:block w-24 h-4 bg-black rounded-full absolute left-1/2 transform -translate-x-1/2 top-1.5 border border-zinc-800/20"></div>
|
| 124 |
+
<div class="flex items-center gap-1.5">
|
| 125 |
+
<span>5G</span>
|
| 126 |
+
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
| 127 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M8.284 16.284A3 3 0 0012 21a3 3 0 003.716-4.716m-7.432 0A11.963 11.963 0 0112 3c3.12 0 5.964 1.192 8.135 3.135m-16.27 0L21 21" />
|
| 128 |
+
</svg>
|
| 129 |
+
<div class="w-5 h-2.5 border border-zinc-500 rounded-[3px] p-[1px] flex items-center">
|
| 130 |
+
<div class="w-full h-full bg-zinc-300 rounded-[1px]"></div>
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
</div>
|
| 134 |
+
|
| 135 |
+
<!-- صفحه اول: گام لوکس انتخاب شماره میز (Table Selector) -->
|
| 136 |
+
<div id="tableSelectorScreen" class="flex-1 flex flex-col items-center justify-between p-6 pb-8 transition-all duration-500 ease-out z-10">
|
| 137 |
+
|
| 138 |
+
<div class="w-full flex-1 flex flex-col justify-center space-y-10">
|
| 139 |
|
| 140 |
+
<!-- لوگو تایپ و معرفی برند -->
|
| 141 |
+
<div class="text-center space-y-4">
|
| 142 |
+
<div class="relative inline-flex">
|
| 143 |
+
<div class="absolute inset-0 bg-brass/20 rounded-2xl blur-lg animate-pulse-slow"></div>
|
| 144 |
+
<div class="relative w-16 h-16 rounded-2xl bg-gradient-to-br from-brass/20 to-brass/5 border border-brass/30 flex items-center justify-center shadow-glow">
|
| 145 |
+
<svg class="w-8 h-8 text-brass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 146 |
+
<path d="M17 8h1a4 4 0 1 1 0 8h-1" />
|
| 147 |
+
<path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z" />
|
| 148 |
+
<line x1="6" y1="2" x2="6" y2="4" />
|
| 149 |
+
<line x1="10" y1="2" x2="10" y2="4" />
|
| 150 |
+
<line x1="14" y1="2" x2="14" y2="4" />
|
| 151 |
+
</svg>
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
<div class="space-y-1">
|
| 155 |
+
<h1 class="text-xl font-extrabold tracking-tight text-white">سفارش هوشمند <span class="text-brass">Cafe AI</span></h1>
|
| 156 |
+
<p class="text-[11px] text-zinc-500 leading-relaxed max-w-[260px] mx-auto">به دنیای پذیرایی اختصاصی خوش آمدید. لطفا ابتدا شماره میز خود را تایید نمایید.</p>
|
| 157 |
</div>
|
|
|
|
|
|
|
| 158 |
</div>
|
| 159 |
|
| 160 |
+
<!-- گرید دایرهای میزها -->
|
| 161 |
+
<div class="space-y-4">
|
| 162 |
+
<div class="text-[10px] text-zinc-500 font-extrabold tracking-wider uppercase text-center">انتخاب موقعیت استقرار شما</div>
|
| 163 |
+
<div class="grid grid-cols-4 gap-3 max-w-[290px] mx-auto">
|
| 164 |
<script>
|
| 165 |
+
for (let i = 1; i <= 8; i++) {
|
| 166 |
document.write(`
|
| 167 |
+
<button onclick="selectTable(${i})" id="tableBtn-${i}" class="table-btn aspect-square rounded-xl border border-zinc-800 bg-coal/40 hover:border-brass/30 text-zinc-300 font-extrabold text-xs transition-all duration-300 flex flex-col items-center justify-center gap-1 shadow-inner">
|
| 168 |
+
<span class="text-[10px] text-zinc-500 font-medium">میز</span>
|
| 169 |
+
<span class="text-sm font-black">${i}</span>
|
|
|
|
|
|
|
| 170 |
</button>
|
| 171 |
`);
|
| 172 |
}
|
|
|
|
| 176 |
|
| 177 |
</div>
|
| 178 |
|
| 179 |
+
<!-- دکمه فعالسازی و شروع ارتباط -->
|
| 180 |
+
<button id="confirmTableBtn" disabled onclick="confirmTableSelection()" class="w-full py-4 bg-zinc-800 text-zinc-500 font-bold rounded-2xl text-xs transition-all duration-300 flex items-center justify-center gap-2 cursor-not-allowed shadow-inner">
|
| 181 |
+
ثبت موقعیت و شروع گفتگو
|
| 182 |
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
| 183 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
| 184 |
+
</svg>
|
| 185 |
</button>
|
| 186 |
</div>
|
| 187 |
|
| 188 |
+
<!-- صفحه دوم: محیط چت هوشمند و مدرن با دستیار هوشمند نیلا -->
|
| 189 |
+
<div id="chatScreen" class="hidden flex-1 flex flex-col h-full bg-obsidian transition-all duration-500 opacity-0 transform translate-y-6 z-10 overflow-hidden">
|
| 190 |
|
| 191 |
+
<!-- هدر چت متنی / صوتی نیلا -->
|
| 192 |
+
<header class="px-5 py-4 border-b border-zinc-900/60 bg-[#0c0d11]/90 backdrop-blur-md flex items-center justify-between relative">
|
| 193 |
<div class="flex items-center gap-3">
|
| 194 |
+
<div class="relative flex-shrink-0">
|
| 195 |
+
<div class="absolute inset-0 bg-brass/30 rounded-full blur-sm animate-pulse"></div>
|
| 196 |
+
<div class="relative w-10 h-10 rounded-xl bg-gradient-to-br from-brass/20 to-brass/5 border border-brass/30 flex items-center justify-center">
|
| 197 |
+
<span class="font-bold text-brass text-sm">N</span>
|
| 198 |
+
</div>
|
| 199 |
</div>
|
| 200 |
<div>
|
| 201 |
+
<h2 class="text-xs font-extrabold text-white">دستیار سفارشگیر نیلا (Nila)</h2>
|
| 202 |
+
<p class="text-[9px] text-zinc-500 font-medium flex items-center gap-1">
|
| 203 |
+
<span>سشن اختصاصی</span>
|
| 204 |
+
<span class="w-1 h-1 rounded-full bg-brass/50"></span>
|
| 205 |
+
<span>میز شماره <span id="activeTableLabel" class="text-brass font-bold">-</span></span>
|
| 206 |
+
</p>
|
| 207 |
</div>
|
| 208 |
</div>
|
| 209 |
|
| 210 |
+
<!-- ویجت نشانگر پایداری زنده شبکه -->
|
| 211 |
+
<div class="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-[9px] text-emerald-400 font-black tracking-wider">
|
| 212 |
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
|
| 213 |
+
<span>آنلاین</span>
|
| 214 |
</div>
|
| 215 |
</header>
|
| 216 |
|
| 217 |
+
<!-- باکس چت اصلی حاوی کل پیامها -->
|
| 218 |
<div id="messagesContainer" class="flex-1 overflow-y-auto p-5 space-y-4">
|
| 219 |
+
<!-- پیامهای چت به صورت داینامیک از JS تزریق میشوند -->
|
| 220 |
+
</div>
|
| 221 |
+
|
| 222 |
+
<!-- بخش تراشهها (Prompt Suggestions) جهت سفارشگیری سریع -->
|
| 223 |
+
<div id="suggestionRow" class="px-5 py-2.5 bg-obsidian border-t border-zinc-900/40 flex gap-2 overflow-x-auto whitespace-nowrap scrollbar-none z-20">
|
| 224 |
+
<button onclick="sendQuickPrompt('یک فنجان لاته گرم به همراه کیک شکلاتی')" class="glass-chip px-3 py-1.5 rounded-full text-[10px] text-zinc-300 hover:text-brass hover:border-brass/30 transition-all duration-300">
|
| 225 |
+
☕ لاته + کیک شکلاتی
|
| 226 |
+
</button>
|
| 227 |
+
<button onclick="sendQuickPrompt('منوی کامل نوشیدنیهای سرد شما چیست؟')" class="glass-chip px-3 py-1.5 rounded-full text-[10px] text-zinc-300 hover:text-brass hover:border-brass/30 transition-all duration-300">
|
| 228 |
+
🥤 منوی نوشیدنی سرد
|
| 229 |
+
</button>
|
| 230 |
+
<button onclick="sendQuickPrompt('پیشنهاد ویژه نیلا امروز چیست؟')" class="glass-chip px-3 py-1.5 rounded-full text-[10px] text-zinc-300 hover:text-brass hover:border-brass/30 transition-all duration-300">
|
| 231 |
+
✨ پیشنهاد ویژه نیلا
|
| 232 |
+
</button>
|
| 233 |
+
<button onclick="sendQuickPrompt('کروسان ساده به همراه چای ماسالا')" class="glass-chip px-3 py-1.5 rounded-full text-[10px] text-zinc-300 hover:text-brass hover:border-brass/30 transition-all duration-300">
|
| 234 |
+
🥐 کروسان + چای ماسالا
|
| 235 |
+
</button>
|
| 236 |
</div>
|
| 237 |
|
| 238 |
+
<!-- فاکتور صورتحساب کشویی (Sleek Holographic Thermal Receipt) -->
|
| 239 |
+
<div id="draftOrderContainer" class="hidden border-t border-brass/10 bg-[#0e0e11]/95 backdrop-blur-xl transform transition-all duration-500 ease-in-out z-30">
|
| 240 |
<div class="jagged-edge h-2 w-full -mt-2"></div>
|
| 241 |
<div class="p-5 space-y-4 receipt-texture">
|
| 242 |
|
| 243 |
+
<div class="flex items-center justify-between border-b border-zinc-800 pb-3">
|
| 244 |
+
<span class="text-xs font-extrabold text-brass flex items-center gap-1.5">
|
| 245 |
+
<svg class="w-4 h-4 text-brass" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 246 |
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
| 247 |
</svg>
|
| 248 |
+
پیشنویس صورتحساب میز
|
| 249 |
</span>
|
| 250 |
+
<span class="text-[9px] font-mono text-zinc-500">ID: #0582</span>
|
| 251 |
</div>
|
| 252 |
|
| 253 |
+
<!-- لیست خطوط پیشنویس سفارش -->
|
| 254 |
+
<div id="draftItemsList" class="space-y-2 max-h-[140px] overflow-y-auto pr-1">
|
| 255 |
+
<!-- آیتمها به صورت اتوماتیک تزریق خواهند شد -->
|
| 256 |
+
</div>
|
| 257 |
+
|
| 258 |
+
<!-- بارکد دکوراتیو جهت افزایش حس تکنولوژیک فاکتور -->
|
| 259 |
+
<div class="pt-2 pb-1 flex flex-col items-center justify-center opacity-45">
|
| 260 |
+
<div class="flex space-x-[1.5px] space-x-reverse h-7 w-2/3 bg-transparent">
|
| 261 |
+
<div class="w-1 h-full bg-zinc-300"></div><div class="w-[1.5px] h-full bg-zinc-300"></div><div class="w-2 h-full bg-zinc-300"></div><div class="w-[1px] h-full bg-zinc-300"></div><div class="w-1.5 h-full bg-zinc-300"></div><div class="w-3 h-full bg-zinc-300"></div><div class="w-[1px] h-full bg-zinc-300"></div><div class="w-1 h-full bg-zinc-300"></div><div class="w-2 h-full bg-zinc-300"></div><div class="w-[1.5px] h-full bg-zinc-300"></div>
|
| 262 |
+
</div>
|
| 263 |
+
<span class="text-[7px] text-zinc-500 font-mono tracking-widest mt-1">NILLA DIGITAL SYSTEM INTEGRATION</span>
|
| 264 |
</div>
|
| 265 |
|
| 266 |
+
<!-- دکمه تایید نهایی سفارش و ارسال به داشبورد ادمین -->
|
| 267 |
+
<button onclick="submitFinalOrder()" class="w-full py-4 bg-emerald-600 hover:bg-emerald-500 text-white font-extrabold rounded-xl text-xs transition-all duration-300 flex items-center justify-center gap-2 shadow-emeraldGlow">
|
| 268 |
+
تایید نهایی و ارسال به آشپزخانه
|
| 269 |
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
| 270 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
| 271 |
+
</svg>
|
| 272 |
</button>
|
| 273 |
</div>
|
| 274 |
</div>
|
| 275 |
|
| 276 |
+
<!-- بخش افکت لودینگ و کنترل استریمینگ (Waveform Indicator) -->
|
| 277 |
+
<div id="streamingController" class="hidden px-5 py-3 border-t border-zinc-900/40 bg-[#0c0d11]/80 flex items-center justify-between z-20">
|
| 278 |
+
<div class="flex items-center gap-3">
|
| 279 |
+
<!-- لودر موج صوتی هوش مصنوعی -->
|
| 280 |
+
<div class="flex items-end gap-[2px] h-[18px]">
|
| 281 |
+
<div class="wave-bar w-[2px] bg-brass rounded-full" style="animation-delay: 0.1s"></div>
|
| 282 |
+
<div class="wave-bar w-[2px] bg-brass rounded-full" style="animation-delay: 0.3s"></div>
|
| 283 |
+
<div class="wave-bar w-[2px] bg-brass rounded-full" style="animation-delay: 0.5s"></div>
|
| 284 |
+
<div class="wave-bar w-[2px] bg-brass rounded-full" style="animation-delay: 0.2s"></div>
|
| 285 |
</div>
|
| 286 |
+
<span class="text-[10px] text-zinc-400 font-medium">نیلا در حال پردازش داده...</span>
|
| 287 |
</div>
|
| 288 |
|
| 289 |
+
<button onclick="stopStreaming()" class="flex items-center gap-1.5 px-3 py-1.5 bg-rose-950/10 border border-rose-900/30 hover:bg-rose-900/30 rounded-lg text-[9px] text-rose-400 font-bold transition-all">
|
| 290 |
<svg class="w-3 h-3" viewBox="0 0 24 24" fill="currentColor">
|
| 291 |
<rect x="4" y="4" width="16" height="16" rx="2" />
|
| 292 |
</svg>
|
| 293 |
+
توقف تولید پاسخ
|
| 294 |
</button>
|
| 295 |
</div>
|
| 296 |
|
| 297 |
+
<!-- فرم پایینی چت به همراه فیلد متنی و دکمه ارسال پیام -->
|
| 298 |
+
<form id="customerChatForm" onsubmit="sendCustomerMessage(event)" class="p-3.5 border-t border-zinc-900/60 bg-[#0c0d11]/90 flex gap-2.5 z-20">
|
| 299 |
+
<input type="text" id="customerChatInput" placeholder="مایل به سفارش چه مواردی هستید؟..." autocomplete="off" class="flex-1 bg-coal border border-zinc-800 text-xs text-zinc-100 placeholder-zinc-600 rounded-xl px-4 py-3.5 focus:outline-none focus:border-brass/30 focus:bg-coal/80 transition-all duration-300 shadow-inner">
|
| 300 |
+
<button type="submit" id="sendBtn" class="bg-brass hover:bg-brassHover text-obsidian font-black px-5 py-3.5 rounded-xl text-xs transition-all duration-300 flex items-center justify-center flex-shrink-0 shadow-glow">
|
| 301 |
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
| 302 |
<path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
| 303 |
</svg>
|
|
|
|
| 308 |
|
| 309 |
</div>
|
| 310 |
|
| 311 |
+
<!-- کلاینت جاوا اسکریپت پیشرفته و داینامیک وباپ مشتری -->
|
| 312 |
<script>
|
| 313 |
+
// دریافت داینامیک شماره میز از سرور Flask
|
| 314 |
+
const rawTableNumber = "{{ table_number }}";
|
| 315 |
+
const initialTableNumber = (rawTableNumber && rawTableNumber !== "None" && rawTableNumber !== "") ? parseInt(rawTableNumber) : null;
|
| 316 |
+
|
| 317 |
let selectedTableNumber = null;
|
| 318 |
let abortController = null;
|
| 319 |
let isGenerating = false;
|
| 320 |
let currentDraftItems = null;
|
| 321 |
|
| 322 |
+
// مدیریت زنده زمان در بالای گوشی شبیهسازیشده
|
| 323 |
+
function updateClock() {
|
| 324 |
+
const now = new Date();
|
| 325 |
+
const timeString = now.toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' });
|
| 326 |
+
document.getElementById('statusBarClock').innerText = timeString;
|
| 327 |
+
}
|
| 328 |
+
setInterval(updateClock, 1000);
|
| 329 |
+
updateClock();
|
| 330 |
+
|
| 331 |
+
// بررسی بارگذاری مستقیم از روی URL
|
| 332 |
window.addEventListener('DOMContentLoaded', () => {
|
| 333 |
+
if (initialTableNumber && !isNaN(initialTableNumber)) {
|
| 334 |
+
selectedTableNumber = initialTableNumber;
|
| 335 |
bypassTableSelector();
|
| 336 |
}
|
| 337 |
});
|
| 338 |
|
| 339 |
+
// انتخاب شماره میز در صفحه اول
|
| 340 |
function selectTable(num) {
|
| 341 |
document.querySelectorAll('.table-btn').forEach(btn => {
|
| 342 |
btn.classList.remove('border-brass', 'bg-brass/10', 'text-brass', 'shadow-glow');
|
| 343 |
+
btn.classList.add('border-zinc-800', 'bg-coal/40', 'text-zinc-300');
|
| 344 |
});
|
|
|
|
| 345 |
const activeBtn = document.getElementById(`tableBtn-${num}`);
|
| 346 |
+
activeBtn.classList.remove('border-zinc-800', 'bg-coal/40', 'text-zinc-300');
|
| 347 |
activeBtn.classList.add('border-brass', 'bg-brass/10', 'text-brass', 'shadow-glow');
|
| 348 |
+
|
| 349 |
selectedTableNumber = num;
|
|
|
|
| 350 |
const confirmBtn = document.getElementById('confirmTableBtn');
|
| 351 |
confirmBtn.disabled = false;
|
| 352 |
confirmBtn.classList.remove('bg-zinc-800', 'text-zinc-500', 'cursor-not-allowed');
|
| 353 |
+
confirmBtn.classList.add('bg-brass', 'text-obsidian', 'hover:bg-brassHover', 'shadow-glow');
|
| 354 |
}
|
| 355 |
|
| 356 |
+
// تایید انتخاب میز و هدایت به آدرس اختصاصی میز
|
| 357 |
function confirmTableSelection() {
|
| 358 |
if (!selectedTableNumber) return;
|
| 359 |
+
// ریدایرکت مرورگر به آدرس داینامیک میز انتخاب شده
|
| 360 |
+
window.location.href = `/customer/${selectedTableNumber}`;
|
| 361 |
}
|
| 362 |
|
| 363 |
+
// عبور شیک از لایه انتخاب میز به محیط چت هوش مصنوعی
|
| 364 |
function bypassTableSelector() {
|
| 365 |
const screen1 = document.getElementById('tableSelectorScreen');
|
| 366 |
const screen2 = document.getElementById('chatScreen');
|
|
|
|
| 367 |
screen1.classList.add('opacity-0', 'scale-95');
|
| 368 |
setTimeout(() => {
|
| 369 |
screen1.classList.add('hidden');
|
|
|
|
| 376 |
}, 300);
|
| 377 |
}
|
| 378 |
|
| 379 |
+
// فراخوانی پیام شروع بکار خودکار نیلا
|
|
|
|
|
|
|
| 380 |
async function triggerNilaGreeting() {
|
| 381 |
+
await fetchNilaStream("سلام. من روی صندلی خودم نشستم. خوش آمدگویی کن و منوی فعال امروز را معرفی کن.");
|
|
|
|
| 382 |
}
|
| 383 |
|
| 384 |
+
// ارسال از طریق چیپستهای آماده (تراشه پیشنهاد سریع)
|
| 385 |
+
async function sendQuickPrompt(text) {
|
| 386 |
+
if (isGenerating) return;
|
| 387 |
+
appendChatBubble('user', text);
|
| 388 |
+
await fetchNilaStream(text);
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
// ارسال پیام جدید متنی مشتری
|
| 392 |
async function sendCustomerMessage(e) {
|
| 393 |
e.preventDefault();
|
| 394 |
if (isGenerating) return;
|
|
|
|
| 395 |
const input = document.getElementById('customerChatInput');
|
| 396 |
const userText = input.value.trim();
|
| 397 |
if (!userText) return;
|
|
|
|
| 398 |
appendChatBubble('user', userText);
|
| 399 |
input.value = '';
|
|
|
|
| 400 |
await fetchNilaStream(userText);
|
| 401 |
}
|
| 402 |
|
| 403 |
+
// هسته مرکزی موتور پردازش استریم کلاینت (SSE Parser Engine)
|
| 404 |
async function fetchNilaStream(userText) {
|
| 405 |
if (isGenerating) return;
|
|
|
|
| 406 |
isGenerating = true;
|
| 407 |
toggleInputs(true);
|
| 408 |
+
|
|
|
|
| 409 |
abortController = new AbortController();
|
|
|
|
|
|
|
| 410 |
const botBubbleId = appendChatBubble('model', '');
|
| 411 |
const botBubble = document.getElementById(botBubbleId);
|
| 412 |
+
|
| 413 |
try {
|
| 414 |
const response = await fetch(`/api/customer/chat_stream/${selectedTableNumber}`, {
|
| 415 |
method: 'POST',
|
|
|
|
| 417 |
body: JSON.stringify({ message: userText }),
|
| 418 |
signal: abortController.signal
|
| 419 |
});
|
| 420 |
+
if (!response.ok) throw new Error("پایگاه داده ارتباط موقت سرور قطع شد.");
|
| 421 |
+
|
|
|
|
| 422 |
const reader = response.body.getReader();
|
| 423 |
const decoder = new TextDecoder("utf-8");
|
| 424 |
let buffer = "";
|
| 425 |
+
|
| 426 |
while (true) {
|
| 427 |
const { value, done } = await reader.read();
|
| 428 |
if (done) break;
|
|
|
|
| 429 |
buffer += decoder.decode(value, { stream: true });
|
| 430 |
const lines = buffer.split("\n");
|
| 431 |
+
buffer = lines.pop();
|
| 432 |
+
|
| 433 |
for (const line of lines) {
|
| 434 |
if (!line.trim() || !line.startsWith("data: ")) continue;
|
| 435 |
|
|
|
|
| 438 |
const data = JSON.parse(jsonStr);
|
| 439 |
|
| 440 |
if (data.type === 'text') {
|
|
|
|
| 441 |
botBubble.textContent += data.content;
|
| 442 |
} else if (data.type === 'draft') {
|
|
|
|
| 443 |
showReceipt(data.items);
|
| 444 |
} else if (data.type === 'error') {
|
| 445 |
botBubble.textContent = data.content;
|
| 446 |
}
|
| 447 |
} catch (e) {
|
| 448 |
+
console.error("خطا در ساختار استریم:", e);
|
| 449 |
}
|
| 450 |
}
|
| 451 |
|
|
|
|
| 452 |
const container = document.getElementById('messagesContainer');
|
| 453 |
container.scrollTop = container.scrollHeight;
|
| 454 |
}
|
|
|
|
| 455 |
finalizeStreamTurn();
|
|
|
|
| 456 |
} catch (err) {
|
| 457 |
if (err.name === 'AbortError') {
|
| 458 |
+
botBubble.textContent += ' [مکالمه توسط کاربر لغو گردید]';
|
| 459 |
} else {
|
| 460 |
+
botBubble.textContent = "بروز خطا در اتصال مرکزی: " + err.message;
|
| 461 |
}
|
| 462 |
finalizeStreamTurn();
|
| 463 |
}
|
| 464 |
}
|
| 465 |
|
| 466 |
+
// قطع جریان استریم لایو
|
| 467 |
async function stopStreaming() {
|
| 468 |
if (abortController) {
|
| 469 |
abortController.abort();
|
| 470 |
}
|
| 471 |
isGenerating = false;
|
| 472 |
toggleInputs(false);
|
|
|
|
|
|
|
| 473 |
try {
|
| 474 |
await fetch(`/api/customer/stop/${selectedTableNumber}`, { method: 'POST' });
|
| 475 |
} catch (e) {
|
|
|
|
| 486 |
const input = document.getElementById('customerChatInput');
|
| 487 |
const sendBtn = document.getElementById('sendBtn');
|
| 488 |
const streamController = document.getElementById('streamingController');
|
| 489 |
+
const sugRow = document.getElementById('suggestionRow');
|
| 490 |
+
|
| 491 |
if (generating) {
|
| 492 |
input.disabled = true;
|
| 493 |
sendBtn.disabled = true;
|
| 494 |
sendBtn.classList.add('opacity-40', 'cursor-not-allowed');
|
| 495 |
streamController.classList.remove('hidden');
|
| 496 |
+
sugRow.classList.add('opacity-40', 'pointer-events-none');
|
| 497 |
} else {
|
| 498 |
input.disabled = false;
|
| 499 |
sendBtn.disabled = false;
|
| 500 |
sendBtn.classList.remove('opacity-40', 'cursor-not-allowed');
|
| 501 |
streamController.classList.add('hidden');
|
| 502 |
+
sugRow.classList.remove('opacity-40', 'pointer-events-none');
|
| 503 |
}
|
| 504 |
}
|
| 505 |
|
| 506 |
+
// تزریق ساختار پیامهای چت به جعبه گفتگو
|
| 507 |
function appendChatBubble(role, content) {
|
| 508 |
const container = document.getElementById('messagesContainer');
|
| 509 |
const bubbleId = 'bubble-' + Date.now();
|
|
|
|
| 510 |
let bubbleHtml = '';
|
| 511 |
+
|
| 512 |
if (role === 'user') {
|
| 513 |
bubbleHtml = `
|
| 514 |
+
<div class="flex justify-end gap-2.5 max-w-[85%] mr-auto animate-fadeIn">
|
| 515 |
+
<div class="bg-brass/15 border border-brass/30 text-zinc-100 text-xs rounded-2xl rounded-tl-none px-4 py-3.5 leading-relaxed shadow-sm">
|
| 516 |
${content}
|
| 517 |
</div>
|
| 518 |
</div>
|
| 519 |
`;
|
| 520 |
} else {
|
| 521 |
bubbleHtml = `
|
| 522 |
+
<div class="flex gap-2.5 max-w-[88%] animate-fadeIn">
|
| 523 |
+
<div class="w-8 h-8 rounded-xl bg-brass/10 border border-brass/25 flex items-center justify-center flex-shrink-0 shadow-inner">
|
| 524 |
+
<span class="text-brass font-bold text-xs font-mono">AI</span>
|
|
|
|
|
|
|
|
|
|
| 525 |
</div>
|
| 526 |
+
<div id="${bubbleId}" class="bg-coal border border-zinc-800/80 text-zinc-200 text-xs rounded-2xl rounded-tr-none px-4 py-3.5 leading-relaxed shadow-inner">
|
| 527 |
${content}
|
| 528 |
</div>
|
| 529 |
</div>
|
| 530 |
`;
|
| 531 |
}
|
|
|
|
| 532 |
container.insertAdjacentHTML('beforeend', bubbleHtml);
|
| 533 |
container.scrollTop = container.scrollHeight;
|
| 534 |
return bubbleId;
|
| 535 |
}
|
| 536 |
|
| 537 |
+
// نمایش صورتحساب دندانهدار شکیل روی صفحه مشتری
|
|
|
|
|
|
|
| 538 |
function showReceipt(items) {
|
| 539 |
currentDraftItems = items;
|
| 540 |
const container = document.getElementById('draftOrderContainer');
|
| 541 |
const list = document.getElementById('draftItemsList');
|
|
|
|
| 542 |
list.innerHTML = '';
|
| 543 |
+
|
| 544 |
items.forEach(item => {
|
| 545 |
list.insertAdjacentHTML('beforeend', `
|
| 546 |
+
<div class="flex items-center justify-between text-xs bg-coal/80 px-4 py-3 rounded-xl border border-zinc-800/60 shadow-inner">
|
| 547 |
<span class="text-zinc-300 font-bold">${item.name}</span>
|
| 548 |
+
<span class="text-brass bg-brass/10 px-2.5 py-1 rounded-lg font-black text-[10px] border border-brass/15">${item.quantity} عدد</span>
|
| 549 |
</div>
|
| 550 |
`);
|
| 551 |
});
|
|
|
|
| 552 |
container.classList.remove('hidden');
|
| 553 |
+
// اسکرول انتهای جعبه چت برای نمایش کامل فاکتور کشویی
|
| 554 |
+
setTimeout(() => {
|
| 555 |
+
const chatContainer = document.getElementById('messagesContainer');
|
| 556 |
+
chatContainer.scrollTop = chatContainer.scrollHeight;
|
| 557 |
+
}, 100);
|
| 558 |
}
|
| 559 |
|
| 560 |
+
// نهاییسازی سفارش و فرستادن اطلاعات به ادمین داشبورد
|
| 561 |
async function submitFinalOrder() {
|
| 562 |
if (!currentDraftItems) return;
|
|
|
|
| 563 |
try {
|
| 564 |
const response = await fetch('/api/confirm_order', {
|
| 565 |
method: 'POST',
|
|
|
|
| 569 |
items: currentDraftItems
|
| 570 |
})
|
| 571 |
});
|
|
|
|
| 572 |
const result = await response.json();
|
| 573 |
if (response.ok && result.success) {
|
| 574 |
document.getElementById('draftOrderContainer').classList.add('hidden');
|
| 575 |
showSuccessTransition();
|
| 576 |
} else {
|
| 577 |
+
alert(result.error || "تاییدیه فاکتور ناموفق بود.");
|
| 578 |
}
|
| 579 |
} catch (err) {
|
| 580 |
+
alert("قطع ارتباط زنده پایگاه داده با سیستم مرکزی: " + err.message);
|
| 581 |
}
|
| 582 |
}
|
| 583 |
|
| 584 |
+
// انیمیشن ترانزیشن ثبت موفقیتآمیز سفارش
|
| 585 |
function showSuccessTransition() {
|
| 586 |
const overlay = document.getElementById('successOverlay');
|
| 587 |
overlay.classList.remove('hidden');
|
| 588 |
setTimeout(() => {
|
| 589 |
overlay.classList.remove('opacity-0');
|
| 590 |
}, 50);
|
| 591 |
+
|
| 592 |
+
// چرخه کامل بازسازی صفحه کلاینت بعد از ۵ ثانیه تایید نهایی
|
| 593 |
setTimeout(() => {
|
| 594 |
overlay.classList.add('opacity-0');
|
| 595 |
setTimeout(() => {
|
| 596 |
overlay.classList.add('hidden');
|
| 597 |
+
resetSessionAndRestart();
|
| 598 |
}, 500);
|
| 599 |
+
}, 4500);
|
| 600 |
}
|
| 601 |
|
| 602 |
+
// پاکسازی کامل نشست گفتگو و برگشت به فرم انتخاب میز
|
| 603 |
+
function resetSessionAndRestart() {
|
| 604 |
abortController = null;
|
| 605 |
isGenerating = false;
|
| 606 |
currentDraftItems = null;
|
| 607 |
+
|
|
|
|
| 608 |
document.getElementById('messagesContainer').innerHTML = '';
|
| 609 |
document.getElementById('draftOrderContainer').classList.add('hidden');
|
| 610 |
+
|
| 611 |
+
// هدایت خودکار کاربر به صفحه اصلی جهت تایید موقعیت جدید
|
| 612 |
+
window.location.href = '/';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 613 |
}
|
| 614 |
</script>
|
| 615 |
</body>
|