humanvprojectceo commited on
Commit
e198e3b
·
verified ·
1 Parent(s): 373bb0b

Update customer.html

Browse files
Files changed (1) hide show
  1. customer.html +275 -205
customer.html CHANGED
@@ -16,16 +16,21 @@
16
  sans: ['Vazirmatn', 'sans-serif'],
17
  },
18
  colors: {
19
- obsidian: '#09090b', /* مشکی تیره خالص */
20
- coal: '#121215', /* خاکستری ذغالی */
21
- clay: '#1c1c22', /* خاکستری روشن‌تر */
22
- brass: '#c5a880', /* برنز گرم */
23
- brassHover: '#dcc6a8', /* برنز روشن */
24
  emeraldMuted: '#10b981', /* سبز تایید نهایی */
25
  },
26
  boxShadow: {
27
- glow: '0 0 20px rgba(197, 168, 128, 0.15)',
28
- greenGlow: '0 0 25px rgba(16, 185, 129, 0.25)',
 
 
 
 
 
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(18, 18, 21, 0.7);
43
- backdrop-filter: blur(16px);
44
- border: 1px border rgba(197, 168, 128, 0.08);
 
 
 
 
 
 
45
  }
46
- /* بافت رسید کاغذی انتهای سفارش */
47
  .receipt-texture {
48
- background-color: #121215;
49
- background-image: radial-gradient(rgba(197, 168, 128, 0.03) 1px, transparent 0);
50
- background-size: 8px 8px;
51
  }
52
- /* حاشیه دندانه‌دار پایین فاکتور */
53
  .jagged-edge {
54
- background-image: linear-gradient(-135deg, #121215 4px, transparent 0), linear-gradient(135deg, #121215 4px, transparent 0);
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-[#0d0e12] to-[#120f0a]">
62
 
63
- <!-- تصویر زمینه تزئینی هاله‌های نوری در دسکتاپ -->
64
- <div class="hidden lg:block absolute inset-0 overflow-hidden pointer-events-none z-0">
65
- <div class="absolute -top-[20%] -left-[10%] w-[500px] h-[500px] rounded-full bg-brass/5 blur-[120px]"></div>
66
- <div class="absolute -bottom-[20%] -right-[10%] w-[500px] h-[500px] rounded-full bg-brass/3 blur-[120px]"></div>
67
  </div>
68
 
69
- <!-- فریم اصلی برنامه (در دسکتاپ به صورت گوشی موبایل هوشمند و در موبایل تمام‌صفحه لود می‌شود) -->
70
- <div class="w-full h-screen lg:h-[880px] lg:max-w-[430px] lg:rounded-[40px] lg:border-4 lg:border-[#202025] lg:shadow-[0_0_80px_rgba(0,0,0,0.8)] lg:relative overflow-hidden flex flex-col bg-obsidian z-10 transition-all duration-300">
71
 
72
- <!-- لایه نهایی ثبت موفقیت‌آمیز سفارش -->
73
- <div id="successOverlay" class="hidden absolute inset-0 bg-obsidian/95 z-50 flex flex-col items-center justify-center p-8 text-center space-y-6 transition-all duration-500 opacity-0">
74
- <div class="w-20 h-20 rounded-full bg-emeraldMuted/10 border border-emeraldMuted/20 flex items-center justify-center animate-pulse shadow-greenGlow">
75
- <svg class="w-10 h-10 text-emeraldMuted" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
76
  <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
77
  </svg>
78
  </div>
79
- <div class="space-y-2">
80
- <h2 class="text-lg font-bold text-white tracking-wide">سفارش ثبت نهایی شد</h2>
81
- <p class="text-xs text-zinc-400 max-w-xs leading-relaxed mx-auto">فاکتور شما به سیستم آشپزخانه ارسال گردید. گفتگو با نیلا و حافظه این سشن با موفقیت آزاد و ریست شد.</p>
82
  </div>
 
 
83
  </div>
84
 
85
- <!-- صفحه اول: گام انتخاب شماره میز -->
86
- <div id="tableSelectorScreen" class="flex-1 flex flex-col items-center justify-between p-6 transition-all duration-500 ease-out z-10">
87
- <div class="w-full flex-1 flex flex-col justify-center space-y-8">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
- <!-- لوگو و برند هدر بخش اول -->
90
- <div class="text-center space-y-3">
91
- <div class="inline-flex w-14 h-14 rounded-2xl bg-brass/10 border border-brass/20 items-center justify-center mb-1">
92
- <svg class="w-7 h-7 text-brass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
93
- <path d="M17 8h1a4 4 0 1 1 0 8h-1" />
94
- <path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z" />
95
- <line x1="6" y1="2" x2="6" y2="4" />
96
- <line x1="10" y1="2" x2="10" y2="4" />
97
- <line x1="14" y1="2" x2="14" y2="4" />
98
- </svg>
 
 
 
 
 
 
 
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-3">
106
- <div class="text-[10px] text-zinc-500 font-bold uppercase tracking-wider text-center">انتخاب شماره میز</div>
107
- <div class="grid grid-cols-5 gap-2.5">
108
  <script>
109
- for (let i = 1; i <= 5; i++) {
110
  document.write(`
111
- <button onclick="selectTable(${i})" id="tableBtn-${i}" class="table-btn aspect-square rounded-xl border border-cafeBorder bg-coal/30 hover:border-brass/40 hover:bg-brass/5 text-zinc-300 font-extrabold text-sm transition-all duration-300 flex flex-col items-center justify-center gap-1">
112
- <span class="text-xs">${i}</span>
113
- <svg class="w-3.5 h-3.5 opacity-40 text-zinc-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
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-cafeBorder bg-coal/40 backdrop-blur-md flex items-center justify-between">
136
  <div class="flex items-center gap-3">
137
- <div class="w-10 h-10 rounded-xl bg-brass/10 border border-brass/20 flex items-center justify-center">
138
- <svg class="w-5 h-5 text-brass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
139
- <path d="M12 2a10 10 0 0 1 10 10c0 5.523-4.477 10-10 10S2 17.523 2 12A10 10 0 0 1 12 2z"/>
140
- <path d="M12 8v4l3 3"/>
141
- </svg>
142
  </div>
143
  <div>
144
- <h2 class="text-xs font-extrabold text-white">دستیار صوتی و متنی (Nila)</h2>
145
- <p class="text-[9px] text-zinc-500 font-medium">سشن اختصاصی میز شماره <span id="activeTableLabel" class="text-brass font-bold">-</span></p>
 
 
 
 
146
  </div>
147
  </div>
148
 
149
- <div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/20">
 
150
  <span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
151
- <span class="text-[9px] text-emerald-400 font-bold uppercase tracking-wider"ماده</span>
152
  </div>
153
  </header>
154
 
155
- <!-- باکس چت اصلی حاوی پیام‌ها -->
156
  <div id="messagesContainer" class="flex-1 overflow-y-auto p-5 space-y-4">
157
- <!-- المان‌های چت از طریق JS تزریق می‌شوند -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  </div>
159
 
160
- <!-- پنل لوکس صورت‌حساب دندانه‌دار کشویی (thermal receipt look) -->
161
- <div id="draftOrderContainer" class="hidden border-t border-brass/20 bg-coal/90 backdrop-blur-lg transform transition-all duration-500 ease-in-out">
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-cafeBorder pb-2">
166
- <span class="text-xs font-bold text-brass flex items-center gap-1.5">
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 font-medium">نیاز به تایید ادمین دارد</span>
173
  </div>
174
 
175
- <!-- لیست خطوط صورتحساب -->
176
- <div id="draftItemsList" class="space-y-2 max-h-[120px] overflow-y-auto pr-1">
177
- <!-- اقلام لیست -->
 
 
 
 
 
 
 
 
178
  </div>
179
 
180
- <!-- دکمه تایید و بستن کشو -->
181
- <button onclick="submitFinalOrder()" class="w-full py-3.5 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-greenGlow">
182
- تایید و ارسال نهایی به آشپزخانه
 
 
 
183
  </button>
184
  </div>
185
  </div>
186
 
187
- <!-- بخش کنترلر انیمیشن نوشتن کلمات و دکمه قطع ارتباط -->
188
- <div id="streamingController" class="hidden px-5 py-2.5 border-t border-cafeBorder/40 bg-coal/30 flex items-center justify-between">
189
- <div class="flex items-center gap-2 text-[10px] text-zinc-500 font-medium">
190
- <div class="flex space-x-1 space-x-reverse">
191
- <span class="w-1.5 h-1.5 bg-brass rounded-full animate-bounce" style="animation-delay: 0.1s"></span>
192
- <span class="w-1.5 h-1.5 bg-brass rounded-full animate-bounce" style="animation-delay: 0.2s"></span>
193
- <span class="w-1.5 h-1.5 bg-brass rounded-full animate-bounce" style="animation-delay: 0.3s"></span>
 
 
194
  </div>
195
- <span>نیلا در حال پردازش جریان کلمات...</span>
196
  </div>
197
 
198
- <button onclick="stopStreaming()" class="flex items-center gap-1 px-3 py-1 bg-rose-950/20 border border-rose-800/30 hover:bg-rose-900/40 rounded-lg text-[9px] text-rose-400 font-bold transition-all">
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-cafeBorder bg-coal/30 flex gap-2">
208
- <input type="text" id="customerChatInput" placeholder="چه طعم و نوشیدنی خاصی مد نظرتان است؟..." autocomplete="off" class="flex-1 bg-coal border border-cafeBorder text-xs text-zinc-100 placeholder-zinc-600 rounded-xl px-4 py-3.5 focus:outline-none focus:border-brass/40 transition-all duration-300">
209
- <button type="submit" id="sendBtn" class="bg-brass hover:bg-brassHover text-obsidian font-extrabold px-5 py-3.5 rounded-xl text-xs transition-all duration-300 flex items-center justify-center flex-shrink-0 shadow-glow">
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
- // آدرس‌دهی هوشمند بر اساس ورودی مستقیم Flask
223
- const initialTableNumber = "{{ table_number }}";
224
-
 
225
  let selectedTableNumber = null;
226
  let abortController = null;
227
  let isGenerating = false;
228
  let currentDraftItems = null;
229
 
230
- // بارگذاری اولیه: بای‌پاس صفحه انتخاب در صورت تعریف شماره میز در روت url
 
 
 
 
 
 
 
 
 
231
  window.addEventListener('DOMContentLoaded', () => {
232
- if (initialTableNumber && initialTableNumber !== "" && initialTableNumber !== "None" && !isNaN(initialTableNumber)) {
233
- selectedTableNumber = parseInt(initialTableNumber);
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-cafeBorder', 'bg-coal/30', 'text-zinc-300');
242
  });
243
-
244
  const activeBtn = document.getElementById(`tableBtn-${num}`);
245
- activeBtn.classList.remove('border-cafeBorder', 'bg-coal/30', 'text-zinc-300');
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
- bypassTableSelector();
 
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
- if (!response.ok) throw new Error("قطع ارتباط سرور مرکزی");
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("خطا در پارس استریم داده:", e);
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 = "بروز اختلال در شبکه: " + err.message;
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/10 border border-brass/20 text-zinc-100 text-xs rounded-2xl rounded-tl-none px-4 py-3.5 leading-relaxed">
419
  ${content}
420
  </div>
421
  </div>
422
  `;
423
  } else {
424
  bubbleHtml = `
425
- <div class="flex gap-2.5 max-w-[85%] animate-fadeIn">
426
- <div class="w-8 h-8 rounded-xl bg-brass/10 border border-brass/20 flex items-center justify-center flex-shrink-0">
427
- <svg class="w-4 h-4 text-brass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
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-cafeBorder text-zinc-200 text-xs rounded-2xl rounded-tr-none px-4 py-3.5 leading-relaxed">
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-obsidian/60 px-3.5 py-2.5 rounded-lg border border-cafeBorder">
456
  <span class="text-zinc-300 font-bold">${item.name}</span>
457
- <span class="text-brass font-bold">${item.quantity} عدد</span>
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("قطع اتصال به سرور جهت ثبت سفارش: " + err.message);
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
- resetSesssionAndRestart();
503
  }, 500);
504
- }, 4000);
505
  }
506
 
507
- function resetSesssionAndRestart() {
 
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
- if (initialTableNumber && initialTableNumber !== "" && initialTableNumber !== "None" && !isNaN(initialTableNumber)) {
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>