humanvprojectceo commited on
Commit
2e1a881
·
verified ·
1 Parent(s): e198e3b

Update cafe.html

Browse files
Files changed (1) hide show
  1. cafe.html +263 -245
cafe.html CHANGED
@@ -16,16 +16,16 @@
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.12)',
28
- greenGlow: '0 0 25px rgba(16, 185, 129, 0.2)',
29
  }
30
  }
31
  }
@@ -37,33 +37,34 @@
37
  color: #f4f4f5;
38
  -webkit-tap-highlight-color: transparent;
39
  }
40
- /* تنظیمات اسکرول‌بار اختصاصی باریک */
41
  ::-webkit-scrollbar {
42
  width: 4px;
 
43
  }
44
  ::-webkit-scrollbar-track {
45
- background: #121215;
46
  }
47
  ::-webkit-scrollbar-thumb {
48
- background: #2c2c35;
49
- border-radius: 2px;
50
  }
51
  </style>
52
  </head>
53
- <body class="min-h-screen flex flex-col font-sans selection:bg-amber-500 selection:text-black overflow-x-hidden bg-gradient-to-tr from-obsidian via-[#0d0e12] to-[#120f0a] antialiased">
54
 
55
- <!-- تصویر زمینه هاله‌های نوری در دسکتاپ -->
56
  <div class="hidden lg:block absolute inset-0 overflow-hidden pointer-events-none z-0">
57
  <div class="absolute -top-[10%] -left-[5%] w-[600px] h-[600px] rounded-full bg-brass/3 blur-[140px]"></div>
58
  <div class="absolute -bottom-[10%] -right-[5%] w-[600px] h-[600px] rounded-full bg-brass/2 blur-[140px]"></div>
59
  </div>
60
 
61
- <!-- هدر پنل مدیریت -->
62
- <header class="border-b border-[#2c2c35]/40 bg-coal/50 backdrop-blur-md sticky top-0 z-50 px-4 py-4 md:px-8">
63
  <div class="max-w-7xl mx-auto flex items-center justify-between">
64
  <div class="flex items-center gap-3">
65
- <div class="w-10 h-10 rounded-xl bg-brass/10 border border-brass/20 flex items-center justify-center">
66
- <svg class="w-6 h-6 text-brass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
67
  <path d="M17 8h1a4 4 0 1 1 0 8h-1" />
68
  <path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z" />
69
  <line x1="6" y1="2" x2="6" y2="4" />
@@ -75,12 +76,12 @@
75
  <h1 class="text-sm font-extrabold text-white flex items-center gap-2">
76
  پنل مدیریت ادمین <span class="text-brass">Cafe AI</span>
77
  </h1>
78
- <p class="text-[10px] text-zinc-500 font-medium">پایش لحظه‌ای سفارشات آشپزخانه و منوی فعال هوش مصنوعی</p>
79
  </div>
80
  </div>
81
 
82
  <div class="flex items-center gap-3">
83
- <span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[10px] font-bold bg-emeraldMuted/10 text-emerald-400 border border-emeraldMuted/20">
84
  <span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
85
  نظارت زنده فعال
86
  </span>
@@ -88,30 +89,30 @@
88
  </div>
89
  </header>
90
 
91
- <!-- بدنه چیدمان اصلی پنل ادمین -->
92
  <main class="flex-1 max-w-7xl w-full mx-auto p-4 md:p-6 lg:p-8 grid grid-cols-1 lg:grid-cols-12 gap-6 z-10 relative">
93
 
94
- <!-- ستون سمت راست: پایش سفارشات فعال و آپلود فاکتور منو (۷ از ۱۲) -->
95
  <section class="lg:col-span-7 flex flex-col gap-6">
96
 
97
- <!-- باکس مانیتورینگ سفارشات آشپزخانه -->
98
- <div class="bg-coal/60 border border-[#2c2c35]/40 rounded-2xl p-5 shadow-xl backdrop-blur-md">
99
- <div class="flex items-center justify-between mb-4 border-b border-[#2c2c35]/30 pb-3">
100
- <h2 class="text-xs font-extrabold text-white flex items-center gap-2">
101
- <svg class="w-4 h-4 text-brass" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
102
- <path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
103
- </svg>
104
- سفارشات جدید آشپزخانه
105
- </h2>
106
  <button onclick="fetchActiveOrders()" class="text-[10px] text-zinc-500 hover:text-brass flex items-center gap-1 transition-all duration-300">
107
  <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
108
  <path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 1121.21 15H15" />
109
  </svg>
110
- بروزرسانی دستی
111
  </button>
112
  </div>
113
 
114
- <!-- ظرف دربرگیرنده لیست سفارشات -->
115
  <div id="ordersContainer" class="space-y-4 max-h-[350px] overflow-y-auto pr-1">
116
  <div class="text-center py-12 text-xs text-zinc-500">
117
  در حال بارگذاری لیست سفارشات جاری...
@@ -119,48 +120,53 @@
119
  </div>
120
  </div>
121
 
122
- <!-- بخش آپلود و استخراج خودکار عکس منو -->
123
- <div class="bg-coal/60 border border-[#2c2c35]/40 rounded-2xl p-5 shadow-xl backdrop-blur-md space-y-4">
124
- <h2 class="text-xs font-extrabold text-white flex items-center gap-2 border-b border-[#2c2c35]/30 pb-3">
125
  <svg class="w-4 h-4 text-brass" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
126
  <path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
127
  </svg>
128
- استخراج منوی جدید با تصویر
129
  </h2>
130
 
131
  <p class="text-[11px] text-zinc-500 leading-relaxed">
132
- با کشیدن و رها کردن یا انتخاب عکس منوی جدید، هوش مصنوعی نیلا ساختار قیمت‌ها و محصولات را فوراً استخراج می‌کند تا پس از ویرایش برای مشتریان منتشر کنید.
133
  </p>
134
 
135
- <!-- زون آپلود فایل -->
136
- <div class="border-2 border-dashed border-[#2c2c35] hover:border-brass/30 rounded-xl p-6 text-center cursor-pointer transition-all bg-obsidian/30" onclick="document.getElementById('menuFileInput').click()">
 
 
 
 
 
137
  <input type="file" id="menuFileInput" class="hidden" accept="image/*,application/pdf" onchange="uploadMenuFile(event)">
138
  <div class="flex flex-col items-center gap-2.5">
139
- <svg class="w-7 h-7 text-zinc-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
140
  <path stroke-linecap="round" stroke-linejoin="round" d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
141
  </svg>
142
- <span class="text-xs text-zinc-300 font-medium">عکس منوی جدید را جهت بارگذاری انتخاب کنید</span>
143
- <span class="text-[9px] text-zinc-500 uppercase tracking-wider">فرمت‌های تصویری یا فایل PDF</span>
144
  </div>
145
  </div>
146
 
147
- <!-- افکت لودینگ تحلیل هوش مصنوعی -->
148
- <div id="uploadLoader" class="hidden text-center py-6 bg-obsidian/40 rounded-xl border border-[#2c2c35]/40 animate-pulse">
149
  <div class="inline-block animate-spin rounded-full h-7 w-7 border-t-2 border-b-2 border-brass mb-2"></div>
150
- <p class="text-[10px] text-brass font-bold">نیلا در حال پردازش متون و فاکتور منوی جدید است. لطفاً چند لحظه شکیبا باشید...</p>
151
  </div>
152
 
153
- <!-- جدول ویرایش اقلام استخراج شده -->
154
- <div id="menuEditorContainer" class="hidden space-y-4 border-t border-[#2c2c35]/30 pt-4">
155
  <h3 class="text-xs font-bold text-brass flex items-center gap-1.5">
156
- <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
157
  <path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
158
  </svg>
159
- بازبینی و بازسازی منوی جاری پیش از انتشار
160
  </h3>
161
 
162
- <div class="overflow-x-auto max-h-[250px] border border-[#2c2c35]/40 rounded-xl bg-obsidian/20">
163
- <table class="min-w-full divide-y divide-[#2c2c35]/40 text-[11px]">
164
  <thead class="bg-coal">
165
  <tr>
166
  <th class="px-3 py-2.5 text-right font-medium text-zinc-400">عنوان محصول</th>
@@ -169,18 +175,18 @@
169
  <th class="px-3 py-2.5 text-center font-medium text-zinc-400 w-16">عملیات</th>
170
  </tr>
171
  </thead>
172
- <tbody id="menuEditorTableBody" class="divide-y divide-[#2c2c35]/40">
173
- <!-- سطرها -->
174
  </tbody>
175
  </table>
176
  </div>
177
 
178
  <div class="flex justify-end gap-2">
179
- <button onclick="addEmptyMenuRow()" class="px-3 py-1.5 border border-[#2c2c35] hover:border-zinc-500 rounded-lg text-[10px] text-zinc-300 transition-all duration-300">
180
- افزودن محصول جدید
181
  </button>
182
- <button onclick="saveAndPublishMenu()" class="px-4 py-1.5 bg-emerald-600 hover:bg-emerald-500 rounded-lg text-[11px] font-extrabold text-white transition-all duration-300 flex items-center gap-1.5 shadow-greenGlow">
183
- تایید و فعالسازی منوی زنده مشتریان
184
  </button>
185
  </div>
186
  </div>
@@ -188,58 +194,55 @@
188
  </div>
189
  </section>
190
 
191
- <!-- ستون سمت چپ: چت مدیریت با هوش مصنوعی نیلا جهت تغییر موجودی (۵ از ۱۲) -->
192
- <section class="lg:col-span-5 flex flex-col bg-coal/60 border border-[#2c2c35]/40 rounded-2xl shadow-xl h-[630px] overflow-hidden backdrop-blur-md">
193
 
194
- <!-- سربرگ چت ادمین -->
195
- <div class="px-5 py-4 border-b border-[#2c2c35]/40 flex items-center justify-between bg-obsidian/20">
196
  <div class="flex items-center gap-3">
197
- <div class="w-10 h-10 rounded-xl bg-brass/10 border border-brass/20 flex items-center justify-center">
198
- <svg class="w-5 h-5 text-brass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
199
  <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"/>
200
  <path d="M12 8v4l3 3"/>
201
  </svg>
202
  </div>
203
  <div>
204
- <h3 class="text-xs font-extrabold text-white">دستیار مدیریت منو (Nila)</h3>
205
- <p class="text-[9px] text-zinc-500 font-medium">تغییر زنده وضعیت موجودی محصولات با مکالمه</p>
206
  </div>
207
  </div>
208
  </div>
209
 
210
- <!-- بدنه چت باکس ادمین -->
211
  <div id="chatMessages" class="flex-1 overflow-y-auto p-4 space-y-4">
212
  <div class="flex gap-2.5 max-w-[85%]">
213
- <div class="w-7 h-7 rounded-lg bg-brass/10 border border-brass/20 flex items-center justify-center flex-shrink-0">
214
- <svg class="w-4 h-4 text-brass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
215
- <circle cx="12" cy="12" r="10"/>
216
- <path d="M12 8v4l3 3"/>
217
- </svg>
218
  </div>
219
- <div class="bg-obsidian/50 border border-[#2c2c35]/40 text-zinc-300 text-xs rounded-2xl rounded-tr-none px-3.5 py-2.5 leading-relaxed font-sans">
220
- سلام ادمین گرامی! من نیلا هستم، دستیار مدیریت منوی Cafe AI. شما می‌توانید از همین‌جا اقلام منو را با گفتگوی ساده کنترل کنید؛ برای مثال بنویسید: "کروسان ساده تمام شد" یا "چای ماسالا را موجود کن" تا سریعاً تغییرات روی سیستم مشتریان نیز اعمال گردد.
221
  </div>
222
  </div>
223
  </div>
224
 
225
- <!-- لودر تایپ ادمین چت -->
226
- <div id="chatLoader" class="hidden px-4 py-2 flex items-center justify-between bg-obsidian/30 text-[9px] text-zinc-500 font-bold border-t border-[#2c2c35]/40">
227
  <div class="flex items-center gap-2">
228
  <div class="flex space-x-1 space-x-reverse">
229
  <span class="w-1.5 h-1.5 bg-brass rounded-full animate-bounce" style="animation-delay: 0.1s"></span>
230
  <span class="w-1.5 h-1.5 bg-brass rounded-full animate-bounce" style="animation-delay: 0.2s"></span>
231
  <span class="w-1.5 h-1.5 bg-brass rounded-full animate-bounce" style="animation-delay: 0.3s"></span>
232
  </div>
233
- <span>نیلا در حال پردازش دستور مدیریت...</span>
234
  </div>
235
- <button onclick="stopAdminChat()" class="px-2.5 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">
236
- توقف پاسخ
237
  </button>
238
  </div>
239
 
240
- <!-- فرم ارسال پیام در چت ادمین -->
241
- <form id="chatForm" onsubmit="sendAdminMessage(event)" class="p-3 border-t border-[#2c2c35]/40 bg-obsidian/30 flex gap-2">
242
- <input type="text" id="chatInput" placeholder="دستور جدید را به صورت متنی بنویسید..." autocomplete="off" class="flex-1 bg-coal border border-[#2c2c35]/40 text-xs text-zinc-200 placeholder-zinc-600 rounded-lg px-3.5 py-2.5 focus:outline-none focus:border-brass/30 transition-all duration-300">
243
  <button type="submit" id="sendBtn" class="bg-brass hover:bg-brassHover text-obsidian font-extrabold px-4 py-2.5 rounded-lg text-xs transition-all duration-300 flex items-center justify-center flex-shrink-0 shadow-glow">
244
  <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
245
  <path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3" />
@@ -251,65 +254,63 @@
251
 
252
  </main>
253
 
254
- <!-- فوتر پایین پنل -->
255
- <footer class="border-t border-[#2c2c35]/30 py-4 text-center text-[10px] text-zinc-600 bg-obsidian/90">
256
  طراحی و توسعه توسط الگوریتم داده نسترن | کلیه حقوق محفوظ است © ۲۰۲۶
257
  </footer>
258
 
259
- <!-- منطق جاوا اسکریپت پنل مدیریت ادمین -->
260
  <script>
261
  let chatHistory = [
262
- { role: 'model', content: 'سلام ادمین گرامی! من نیلا هستم، دستیار مدیریت منوی Cafe AI. شما می‌توانید از همین‌جا اقلام منو را با گفتگوی ساده کنترل کنید؛ برای مثال بنویسید: "کروسان ساده تمام شد" یا "چای ماسالا را موجود کن" تا سریعاً تغییرات روی سیستم مشتریان نیز اعمال گردد.' }
263
  ];
264
  let abortController = null;
265
  let isGenerating = false;
266
 
267
  window.addEventListener('DOMContentLoaded', () => {
268
  fetchActiveOrders();
269
- // بروزرسانی خودکار سفارشات هر ۱۰ ثانیه
270
  setInterval(fetchActiveOrders, 10000);
271
  });
272
 
273
- // --- مدیریت پایش سفارشات زنده ---
274
-
275
  async function fetchActiveOrders() {
276
  const container = document.getElementById('ordersContainer');
277
  try {
278
  const response = await fetch('/api/admin/orders');
279
  const orders = await response.json();
280
-
281
  if (response.ok) {
282
  if (orders.length === 0) {
283
  container.innerHTML = `
284
- <div class="text-center py-16 text-zinc-500 border border-dashed border-[#2c2c35]/40 rounded-2xl bg-obsidian/10">
285
- <svg class="w-9 h-9 text-zinc-600 mx-auto mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
286
  <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
287
  </svg>
288
- هیچ سفارش معلق و فعالی در صف آشپزخانه نیست.
289
  </div>
290
  `;
291
  return;
292
  }
293
-
294
  container.innerHTML = '';
295
  orders.forEach(order => {
296
  let itemsHtml = '';
297
  order.items.forEach(item => {
298
  itemsHtml += `
299
- <div class="flex items-center justify-between text-[11px] bg-obsidian/60 px-3 py-2 rounded-lg border border-[#2c2c35]/40">
300
  <span class="text-zinc-300 font-bold">${item.name}</span>
301
- <span class="text-brass bg-brass/10 px-2 py-0.5 rounded-md font-extrabold">تعداد: ${item.quantity}</span>
302
  </div>
303
  `;
304
  });
305
-
306
  const timeString = new Date(order.timestamp * 1000).toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' });
307
-
308
  const cardHtml = `
309
- <div class="border border-[#2c2c35]/40 hover:border-brass/20 rounded-xl p-4 bg-obsidian/30 transition-all duration-300 flex flex-col md:flex-row justify-between items-start md:items-center gap-3">
310
  <div class="flex-1 space-y-2 w-full">
311
  <div class="flex items-center justify-between md:justify-start gap-3 w-full">
312
- <span class="text-xs font-bold text-white bg-brass/10 border border-brass/20 px-2.5 py-1 rounded-lg">میز شماره ${order.table}</span>
313
  <span class="text-[9px] text-zinc-500 font-medium">زمان ثبت: ${timeString}</span>
314
  </div>
315
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-1.5 mt-2">
@@ -320,17 +321,17 @@
320
  <svg class="w-3.5 h-3.5 text-zinc-400 group-hover:text-white transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
321
  <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
322
  </svg>
323
- آماده و تحویل شد
324
  </button>
325
  </div>
326
  `;
327
  container.insertAdjacentHTML('beforeend', cardHtml);
328
  });
329
  } else {
330
- container.innerHTML = `<div class="text-center py-6 text-rose-500 text-xs">خطا در دریافت سفارشات از پایگاه داده مرکزی.</div>`;
331
  }
332
  } catch (err) {
333
- container.innerHTML = `<div class="text-center py-6 text-rose-500 text-xs">عدم اتصال پایدار با سرور اصلی کافه: ${err.message}</div>`;
334
  }
335
  }
336
 
@@ -346,187 +347,92 @@
346
  if (response.ok && result.success) {
347
  fetchActiveOrders();
348
  } else {
349
- alert(result.error || "خطایی در فرآیند تغییر وضعیت رخ داد.");
350
  }
351
  } catch (err) {
352
- alert("بروز خطای نامشخص شبکه: " + err.message);
353
  }
354
  }
355
 
356
-
357
- // --- سیستم مکالمه مدیریت جهت تغییر موجودی منو ---
358
-
359
- async function sendAdminMessage(e) {
360
  e.preventDefault();
361
- if (isGenerating) return;
362
-
363
- const input = document.getElementById('chatInput');
364
- const text = input.value.trim();
365
- if (!text) return;
366
-
367
- appendChatMessage('user', text);
368
- input.value = '';
369
-
370
- chatHistory.push({ role: 'user', content: text });
371
-
372
- isGenerating = true;
373
- toggleChatLoading(true);
374
-
375
- abortController = new AbortController();
376
-
377
- try {
378
- const response = await fetch('/api/admin/chat', {
379
- method: 'POST',
380
- headers: { 'Content-Type': 'application/json' },
381
- body: JSON.stringify({ messages: chatHistory }),
382
- signal: abortController.signal
383
- });
384
-
385
- const data = await response.json();
386
- toggleChatLoading(false);
387
- isGenerating = false;
388
-
389
- if (response.ok && data.success) {
390
- // شبیه‌سازی انیمیشن نگارش کلمات جهت پایداری ظاهر بصری
391
- const botBubbleId = appendChatMessage('model', '');
392
- const botBubble = document.getElementById(botBubbleId);
393
- let i = 0;
394
- const textResponse = data.response;
395
-
396
- function typeText() {
397
- if (i < textResponse.length && !abortController.signal.aborted) {
398
- botBubble.textContent += textResponse.charAt(i);
399
- i++;
400
- document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
401
- setTimeout(typeText, 6);
402
- } else {
403
- chatHistory.push({ role: 'model', content: textResponse });
404
- }
405
- }
406
- typeText();
407
-
408
- } else {
409
- appendChatMessage('model', data.error || 'پاسخی از سمت دستیار هوشمند دریافت نشد.');
410
- }
411
- } catch (err) {
412
- toggleChatLoading(false);
413
- isGenerating = false;
414
- if (err.name !== 'AbortError') {
415
- appendChatMessage('model', 'خطا در ارتباط زنده با دستیار ادمین: ' + err.message);
416
- }
417
- }
418
- }
419
-
420
- function stopAdminChat() {
421
- if (abortController) {
422
- abortController.abort();
423
- }
424
- toggleChatLoading(false);
425
- isGenerating = false;
426
  }
427
 
428
- function toggleChatLoading(loading) {
429
- const loader = document.getElementById('chatLoader');
430
- const sendBtn = document.getElementById('sendBtn');
431
- const input = document.getElementById('chatInput');
432
-
433
- if (loading) {
434
- loader.classList.remove('hidden');
435
- sendBtn.disabled = true;
436
- input.disabled = true;
437
- } else {
438
- loader.classList.add('hidden');
439
- sendBtn.disabled = false;
440
- input.disabled = false;
441
- }
442
  }
443
 
444
- function appendChatMessage(role, content) {
445
- const container = document.getElementById('chatMessages');
446
- const bubbleId = 'bubble-admin-' + Date.now();
447
 
448
- let html = '';
449
- if (role === 'user') {
450
- html = `
451
- <div class="flex justify-end gap-2.5 max-w-[85%] mr-auto">
452
- <div class="bg-brass/10 border border-brass/20 text-zinc-100 text-xs rounded-2xl rounded-tl-none px-3.5 py-2.5 leading-relaxed">
453
- ${content}
454
- </div>
455
- </div>
456
- `;
457
- } else {
458
- html = `
459
- <div class="flex gap-2.5 max-w-[85%]">
460
- <div class="w-7 h-7 rounded-lg bg-brass/10 border border-brass/20 flex items-center justify-center flex-shrink-0">
461
- <svg class="w-4 h-4 text-brass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
462
- <circle cx="12" cy="12" r="10"/>
463
- <path d="M12 8v4l3 3"/>
464
- </svg>
465
- </div>
466
- <div id="${bubbleId}" class="bg-obsidian/50 border border-[#2c2c35]/40 text-zinc-200 text-xs rounded-2xl rounded-tr-none px-3.5 py-2.5 leading-relaxed font-sans">
467
- ${content}
468
- </div>
469
- </div>
470
- `;
471
  }
472
-
473
- container.insertAdjacentHTML('beforeend', html);
474
- container.scrollTop = container.scrollHeight;
475
- return bubbleId;
476
  }
477
 
478
-
479
- // --- سیستم بارگذاری، استخراج و ویرایش منوی جدید ---
480
-
481
  async function uploadMenuFile(e) {
482
  const file = e.target.files[0];
483
  if (!file) return;
484
-
485
  const loader = document.getElementById('uploadLoader');
486
  const editor = document.getElementById('menuEditorContainer');
487
 
488
  loader.classList.remove('hidden');
489
  editor.classList.add('hidden');
490
-
491
  const formData = new FormData();
492
  formData.append('file', file);
493
-
494
  try {
495
  const response = await fetch('/api/admin/extract_menu', {
496
  method: 'POST',
497
  body: formData
498
  });
499
-
500
  const data = await response.json();
 
501
  loader.classList.add('hidden');
502
-
503
  if (response.ok && data.success) {
504
  renderMenuEditor(data.extracted_menu);
505
  editor.classList.remove('hidden');
506
  } else {
507
- alert(data.error || 'هوش مصنوعی موفق به پردازش تصویر ارسالی نشد.');
508
  }
509
  } catch (err) {
510
  loader.classList.add('hidden');
511
- alert('خطا در فرستادن اطلاعات تصویر منو: ' + err.message);
512
  }
513
  }
514
 
515
  function renderMenuEditor(menuItems) {
516
  const tbody = document.getElementById('menuEditorTableBody');
517
  tbody.innerHTML = '';
518
-
519
  menuItems.forEach((item, index) => {
520
  const row = `
521
- <tr class="hover:bg-coal/40 transition-colors duration-200">
522
  <td class="px-2 py-2">
523
- <input type="text" class="menu-name w-full bg-coal border border-[#2c2c35]/50 rounded-lg px-2.5 py-1.5 text-zinc-200 focus:border-brass/30 focus:outline-none" value="${item.name || ''}">
524
  </td>
525
  <td class="px-2 py-2">
526
- <input type="text" class="menu-desc w-full bg-coal border border-[#2c2c35]/50 rounded-lg px-2.5 py-1.5 text-zinc-200 focus:border-brass/30 focus:outline-none" value="${item.description || ''}">
527
  </td>
528
  <td class="px-2 py-2">
529
- <input type="text" class="menu-price w-full bg-coal border border-[#2c2c35]/50 rounded-lg px-2.5 py-1.5 text-zinc-200 focus:border-brass/30 focus:outline-none" value="${item.price || ''}">
530
  </td>
531
  <td class="px-2 py-2 text-center">
532
  <button onclick="removeMenuRow(this)" class="p-1 text-zinc-600 hover:text-rose-500 transition-colors">
@@ -544,15 +450,15 @@
544
  function addEmptyMenuRow() {
545
  const tbody = document.getElementById('menuEditorTableBody');
546
  const row = `
547
- <tr class="hover:bg-coal/40 transition-colors duration-200">
548
  <td class="px-2 py-2">
549
- <input type="text" class="menu-name w-full bg-coal border border-[#2c2c35]/50 rounded-lg px-2.5 py-1.5 text-zinc-200 focus:border-brass/30 focus:outline-none" placeholder="مثال: لاته ماکیاتو">
550
  </td>
551
  <td class="px-2 py-2">
552
- <input type="text" class="menu-desc w-full bg-coal border border-[#2c2c35]/50 rounded-lg px-2.5 py-1.5 text-zinc-200 focus:border-brass/30 focus:outline-none" placeholder="ترکیب لاته آرت با سس اسپرسو">
553
  </td>
554
  <td class="px-2 py-2">
555
- <input type="text" class="menu-price w-full bg-coal border border-[#2c2c35]/50 rounded-lg px-2.5 py-1.5 text-zinc-200 focus:border-brass/30 focus:outline-none" placeholder="۷۵,۰۰۰">
556
  </td>
557
  <td class="px-2 py-2 text-center">
558
  <button onclick="removeMenuRow(this)" class="p-1 text-zinc-600 hover:text-rose-500 transition-colors">
@@ -574,12 +480,11 @@
574
  const tbody = document.getElementById('menuEditorTableBody');
575
  const rows = tbody.querySelectorAll('tr');
576
  const menuData = [];
577
-
578
  rows.forEach(row => {
579
  const name = row.querySelector('.menu-name').value.trim();
580
  const description = row.querySelector('.menu-desc').value.trim();
581
  const price = row.querySelector('.menu-price').value.trim();
582
-
583
  if (name) {
584
  menuData.push({
585
  name: name,
@@ -589,30 +494,143 @@
589
  });
590
  }
591
  });
592
-
593
  if (menuData.length === 0) {
594
- alert("امکان انتشار یک لیست خالی وجود ندارد.");
595
  return;
596
  }
597
-
598
  try {
599
  const response = await fetch('/api/admin/save_menu', {
600
  method: 'POST',
601
  headers: { 'Content-Type': 'application/json' },
602
  body: JSON.stringify({ menu: menuData })
603
  });
604
-
605
  const data = await response.json();
 
606
  if (response.ok && data.success) {
607
  alert("منوی جدید با موفقیت ثبت و روی وب‌سایت همگام‌سازی شد!");
608
  document.getElementById('menuEditorContainer').classList.add('hidden');
609
- appendChatMessage('model', 'منوی ارسال شده با موفقیت تایید و روی وب‌سایت همگام‌سازی شد. از این پس برای میزهای مشتریان منوی جدید را مبنای کار قرار خواهم داد.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
610
  } else {
611
- alert(data.error || 'بروز خطا در همگام‌سازی داده‌های جدید.');
612
  }
613
  } catch (err) {
614
- alert('خطا در دسترسی به پایگاه داده مرکزی منوها: ' + err.message);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
615
  }
 
 
 
 
616
  }
617
  </script>
618
  </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.15)',
28
+ greenGlow: '0 0 25px rgba(16, 185, 129, 0.20)',
29
  }
30
  }
31
  }
 
37
  color: #f4f4f5;
38
  -webkit-tap-highlight-color: transparent;
39
  }
40
+ /* سفارشی‌سازی اسکرول‌بار ظریف */
41
  ::-webkit-scrollbar {
42
  width: 4px;
43
+ height: 4px;
44
  }
45
  ::-webkit-scrollbar-track {
46
+ background: #111114;
47
  }
48
  ::-webkit-scrollbar-thumb {
49
+ background: #2a2a30;
50
+ border-radius: 4px;
51
  }
52
  </style>
53
  </head>
54
+ <body class="min-h-screen flex flex-col font-sans selection:bg-amber-500 selection:text-black overflow-x-hidden bg-gradient-to-tr from-obsidian via-[#0c0d11] to-[#14110b] antialiased">
55
 
56
+ <!-- افکت هاله‌های نوری در پس‌زمینه دسکتاپ -->
57
  <div class="hidden lg:block absolute inset-0 overflow-hidden pointer-events-none z-0">
58
  <div class="absolute -top-[10%] -left-[5%] w-[600px] h-[600px] rounded-full bg-brass/3 blur-[140px]"></div>
59
  <div class="absolute -bottom-[10%] -right-[5%] w-[600px] h-[600px] rounded-full bg-brass/2 blur-[140px]"></div>
60
  </div>
61
 
62
+ <!-- هدر پنل ادمین -->
63
+ <header class="border-b border-zinc-900 bg-coal/60 backdrop-blur-md sticky top-0 z-50 px-4 py-4 md:px-8">
64
  <div class="max-w-7xl mx-auto flex items-center justify-between">
65
  <div class="flex items-center gap-3">
66
+ <div class="w-10 h-10 rounded-xl bg-brass/10 border border-brass/25 flex items-center justify-center">
67
+ <svg class="w-6 h-6 text-brass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
68
  <path d="M17 8h1a4 4 0 1 1 0 8h-1" />
69
  <path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z" />
70
  <line x1="6" y1="2" x2="6" y2="4" />
 
76
  <h1 class="text-sm font-extrabold text-white flex items-center gap-2">
77
  پنل مدیریت ادمین <span class="text-brass">Cafe AI</span>
78
  </h1>
79
+ <p class="text-[10px] text-zinc-500 font-medium">پایش برخط سفارشات آشپزخانه و منوی فعال هوش مصنوعی</p>
80
  </div>
81
  </div>
82
 
83
  <div class="flex items-center gap-3">
84
+ <span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[10px] font-bold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
85
  <span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
86
  نظارت زنده فعال
87
  </span>
 
89
  </div>
90
  </header>
91
 
92
+ <!-- بدنه اصلی چیدمان پنل مدیریت -->
93
  <main class="flex-1 max-w-7xl w-full mx-auto p-4 md:p-6 lg:p-8 grid grid-cols-1 lg:grid-cols-12 gap-6 z-10 relative">
94
 
95
+ <!-- ستون سمت راست: پایش سفارشات و بارگذاری تصاویر منو (۷ از ۱۲) -->
96
  <section class="lg:col-span-7 flex flex-col gap-6">
97
 
98
+ <!-- مانیتورینگ سفارشات ورودی آشپزخانه -->
99
+ <div class="bg-coal/65 border border-zinc-900 rounded-2xl p-5 shadow-xl backdrop-blur-md">
100
+ <div class="flex items-center justify-between mb-4 border-b border-zinc-800 pb-3">
101
+ <div class="flex items-center gap-2">
102
+ <div class="w-2 h-2 rounded-full bg-brass animate-pulse"></div>
103
+ <h2 class="text-xs font-extrabold text-white">
104
+ سفارشات جدید آشپزخانه
105
+ </h2>
106
+ </div>
107
  <button onclick="fetchActiveOrders()" class="text-[10px] text-zinc-500 hover:text-brass flex items-center gap-1 transition-all duration-300">
108
  <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
109
  <path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 1121.21 15H15" />
110
  </svg>
111
+ بروزرسانی جریان سفارشات
112
  </button>
113
  </div>
114
 
115
+ <!-- جدول مانیتورینگ سفارشات -->
116
  <div id="ordersContainer" class="space-y-4 max-h-[350px] overflow-y-auto pr-1">
117
  <div class="text-center py-12 text-xs text-zinc-500">
118
  در حال بارگذاری لیست سفارشات جاری...
 
120
  </div>
121
  </div>
122
 
123
+ <!-- بخش بارگذاری و استخراج منو با تصویر یا فایل PDF -->
124
+ <div class="bg-coal/65 border border-zinc-900 rounded-2xl p-5 shadow-xl backdrop-blur-md space-y-4">
125
+ <h2 class="text-xs font-extrabold text-white flex items-center gap-2 border-b border-zinc-800 pb-3">
126
  <svg class="w-4 h-4 text-brass" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
127
  <path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
128
  </svg>
129
+ استخراج خودکار ساختار منو با هوش مصنوعی
130
  </h2>
131
 
132
  <p class="text-[11px] text-zinc-500 leading-relaxed">
133
+ تصویر منو یا فاکتور جدید ک��فه را بارگذاری کنید. هوش مصنوعی نیلا متون و قیمت‌ها را استخراج کرده و جهت تایید نهایی برای شما ساختاربندی می‌کند.
134
  </p>
135
 
136
+ <!-- باکس آپلود فایل با قابلیت Drag & Drop کشیدن و رها کردن -->
137
+ <div id="dropZone" class="border-2 border-dashed border-zinc-800 hover:border-brass/35 rounded-xl p-6 text-center cursor-pointer transition-all bg-obsidian/30"
138
+ onclick="document.getElementById('menuFileInput').click()"
139
+ ondragover="handleDragOver(event)"
140
+ ondragleave="handleDragLeave(event)"
141
+ ondrop="handleDrop(event)">
142
+
143
  <input type="file" id="menuFileInput" class="hidden" accept="image/*,application/pdf" onchange="uploadMenuFile(event)">
144
  <div class="flex flex-col items-center gap-2.5">
145
+ <svg id="uploadIcon" class="w-8 h-8 text-zinc-600 transition-colors duration-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
146
  <path stroke-linecap="round" stroke-linejoin="round" d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
147
  </svg>
148
+ <span class="text-xs text-zinc-300 font-medium">فایل یا تصویر منو را به این بخش بکشید یا انتخاب کنید</span>
149
+ <span class="text-[9px] text-zinc-500 uppercase tracking-wider">فرمت‌های تصویری یا سند PDF</span>
150
  </div>
151
  </div>
152
 
153
+ <!-- انیمیشن تحلیل فرآیند استخراج -->
154
+ <div id="uploadLoader" class="hidden text-center py-6 bg-obsidian/40 rounded-xl border border-zinc-800 animate-pulse">
155
  <div class="inline-block animate-spin rounded-full h-7 w-7 border-t-2 border-b-2 border-brass mb-2"></div>
156
+ <p class="text-[10px] text-brass font-extrabold">نیلا در حال ساختارسازی و آنالیز متون منوی جدید است. لطفاً شکیبا باشید...</p>
157
  </div>
158
 
159
+ <!-- ویرایشگر و جدول اقلام استخراج شده -->
160
+ <div id="menuEditorContainer" class="hidden space-y-4 border-t border-zinc-800/40 pt-4">
161
  <h3 class="text-xs font-bold text-brass flex items-center gap-1.5">
162
+ <svg class="w-3.5 h-3.5 text-brass" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
163
  <path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
164
  </svg>
165
+ بازبینی اقلام منو پیش از انتشار زنده
166
  </h3>
167
 
168
+ <div class="overflow-x-auto max-h-[250px] border border-zinc-900 rounded-xl bg-obsidian/20">
169
+ <table class="min-w-full divide-y divide-zinc-900 text-[11px]">
170
  <thead class="bg-coal">
171
  <tr>
172
  <th class="px-3 py-2.5 text-right font-medium text-zinc-400">عنوان محصول</th>
 
175
  <th class="px-3 py-2.5 text-center font-medium text-zinc-400 w-16">عملیات</th>
176
  </tr>
177
  </thead>
178
+ <tbody id="menuEditorTableBody" class="divide-y divide-zinc-900/60">
179
+ <!-- ردیف‌های منو داینامیک اضافه می‌شوند -->
180
  </tbody>
181
  </table>
182
  </div>
183
 
184
  <div class="flex justify-end gap-2">
185
+ <button onclick="addEmptyMenuRow()" class="px-3 py-2 border border-zinc-800 hover:border-zinc-500 rounded-lg text-[10px] text-zinc-300 transition-all duration-300">
186
+ افزودن دستی محصول جدید
187
  </button>
188
+ <button onclick="saveAndPublishMenu()" class="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 rounded-lg text-[11px] font-extrabold text-white transition-all duration-300 flex items-center gap-1.5 shadow-greenGlow">
189
+ ثبت و همگام‌سازی زنده منوی مشتریان
190
  </button>
191
  </div>
192
  </div>
 
194
  </div>
195
  </section>
196
 
197
+ <!-- ستون سمت چپ: چت ادمین با هوش مصنوعی جهت کنترل موجودی اقلام (۵ از ۱۲) -->
198
+ <section class="lg:col-span-5 flex flex-col bg-coal/65 border border-zinc-900 rounded-2xl shadow-xl h-[630px] overflow-hidden backdrop-blur-md">
199
 
200
+ <!-- سربرگ پنل چت ادمین -->
201
+ <div class="px-5 py-4 border-b border-zinc-900 flex items-center justify-between bg-obsidian/20">
202
  <div class="flex items-center gap-3">
203
+ <div class="w-10 h-10 rounded-xl bg-brass/10 border border-brass/25 flex items-center justify-center">
204
+ <svg class="w-5 h-5 text-brass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
205
  <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"/>
206
  <path d="M12 8v4l3 3"/>
207
  </svg>
208
  </div>
209
  <div>
210
+ <h3 class="text-xs font-extrabold text-white">دستیار صوتی و متنی ادمین</h3>
211
+ <p class="text-[9px] text-zinc-500 font-medium">کنترل همگام موجودی و منو با زبان طبیعی گفتگو</p>
212
  </div>
213
  </div>
214
  </div>
215
 
216
+ <!-- بدنه پیام‌های چت ادمین -->
217
  <div id="chatMessages" class="flex-1 overflow-y-auto p-4 space-y-4">
218
  <div class="flex gap-2.5 max-w-[85%]">
219
+ <div class="w-7 h-7 rounded-lg bg-brass/15 border border-brass/20 flex items-center justify-center flex-shrink-0">
220
+ <span class="text-brass font-extrabold text-[10px] font-mono">AI</span>
 
 
 
221
  </div>
222
+ <div class="bg-obsidian/50 border border-zinc-800 text-zinc-300 text-xs rounded-2xl rounded-tr-none px-3.5 py-2.5 leading-relaxed font-sans">
223
+ سلام همکار گرامی! من نیلا هستم، دستیار مدیریت منوی Cafe AI. شما می‌توانید از همین‌جا اقلام منو را با گفتگوی ساده کنترل کنید؛ برای مثال بنویسید: "کروسان ساده تمام شد" یا "چای ماسالا را موجود کن" تا سریعاً تغییرات روی سیستم مشتریان نیز اعمال گردد.
224
  </div>
225
  </div>
226
  </div>
227
 
228
+ <!-- لودر تفکر و جریان پاسخ نیلا -->
229
+ <div id="chatLoader" class="hidden px-4 py-2.5 flex items-center justify-between bg-obsidian/30 text-[9px] text-zinc-500 font-extrabold border-t border-zinc-900">
230
  <div class="flex items-center gap-2">
231
  <div class="flex space-x-1 space-x-reverse">
232
  <span class="w-1.5 h-1.5 bg-brass rounded-full animate-bounce" style="animation-delay: 0.1s"></span>
233
  <span class="w-1.5 h-1.5 bg-brass rounded-full animate-bounce" style="animation-delay: 0.2s"></span>
234
  <span class="w-1.5 h-1.5 bg-brass rounded-full animate-bounce" style="animation-delay: 0.3s"></span>
235
  </div>
236
+ <span>نیلا در حال پردازش و اعمال دستور مدیریت...</span>
237
  </div>
238
+ <button onclick="stopAdminChat()" class="px-2.5 py-1.5 bg-rose-950/10 border border-rose-900/30 hover:bg-rose-900/40 rounded-lg text-[9px] text-rose-400 font-bold transition-all">
239
+ توقف فرآیند
240
  </button>
241
  </div>
242
 
243
+ <!-- فرم پایینی ارسال درخواست متنی -->
244
+ <form id="chatForm" onsubmit="sendAdminMessage(event)" class="p-3 border-t border-zinc-900 bg-obsidian/30 flex gap-2">
245
+ <input type="text" id="chatInput" placeholder="تغییر وضعیت یا ویرایش منو را به صورت متنی بنویسید..." autocomplete="off" class="flex-1 bg-coal border border-zinc-850 text-xs text-zinc-200 placeholder-zinc-600 rounded-lg px-3.5 py-2.5 focus:outline-none focus:border-brass/30 transition-all duration-300">
246
  <button type="submit" id="sendBtn" class="bg-brass hover:bg-brassHover text-obsidian font-extrabold px-4 py-2.5 rounded-lg text-xs transition-all duration-300 flex items-center justify-center flex-shrink-0 shadow-glow">
247
  <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
248
  <path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3" />
 
254
 
255
  </main>
256
 
257
+ <!-- فوتر پایینی پنل ادمین -->
258
+ <footer class="border-t border-zinc-900/40 py-4 text-center text-[10px] text-zinc-600 bg-obsidian/90">
259
  طراحی و توسعه توسط الگوریتم داده نسترن | کلیه حقوق محفوظ است © ۲۰۲۶
260
  </footer>
261
 
262
+ <!-- کدهای منطق جاوااسکریپت پنل مدیریت ادمین -->
263
  <script>
264
  let chatHistory = [
265
+ { role: 'model', content: 'سلام همکار گرامی! من نیلا هستم، دستیار مدیریت منوی Cafe AI. شما می‌توانید از همین‌جا اقلام منو را با گفتگوی ساده کنترل کنید؛ برای مثال بنویسید: "کروسان ساده تمام شد" یا "چای ماسالا را موجود کن" تا سریعاً تغییرات روی سیستم مشتریان نیز اعمال گردد.' }
266
  ];
267
  let abortController = null;
268
  let isGenerating = false;
269
 
270
  window.addEventListener('DOMContentLoaded', () => {
271
  fetchActiveOrders();
272
+ // بروزرسانی داینامیک سفارشات در بازه‌های ۱۰ ثانیه‌ای منظم
273
  setInterval(fetchActiveOrders, 10000);
274
  });
275
 
276
+ // --- مدیریت پایش سفارشات زنده آشپزخانه ---
 
277
  async function fetchActiveOrders() {
278
  const container = document.getElementById('ordersContainer');
279
  try {
280
  const response = await fetch('/api/admin/orders');
281
  const orders = await response.json();
282
+
283
  if (response.ok) {
284
  if (orders.length === 0) {
285
  container.innerHTML = `
286
+ <div class="text-center py-16 text-zinc-500 border border-dashed border-zinc-800 rounded-2xl bg-obsidian/10">
287
+ <svg class="w-8 h-8 text-zinc-600 mx-auto mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
288
  <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
289
  </svg>
290
+ در حال حاضر سفارش معلق و فعالی در صف آشپزخانه نیست.
291
  </div>
292
  `;
293
  return;
294
  }
295
+
296
  container.innerHTML = '';
297
  orders.forEach(order => {
298
  let itemsHtml = '';
299
  order.items.forEach(item => {
300
  itemsHtml += `
301
+ <div class="flex items-center justify-between text-[11px] bg-obsidian/60 px-3 py-2 rounded-lg border border-zinc-850">
302
  <span class="text-zinc-300 font-bold">${item.name}</span>
303
+ <span class="text-brass bg-brass/10 px-2 py-0.5 rounded-md font-extrabold text-[10px]">تعداد: ${item.quantity}</span>
304
  </div>
305
  `;
306
  });
307
+
308
  const timeString = new Date(order.timestamp * 1000).toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' });
 
309
  const cardHtml = `
310
+ <div class="border border-zinc-850 hover:border-brass/15 rounded-xl p-4 bg-obsidian/30 transition-all duration-300 flex flex-col md:flex-row justify-between items-start md:items-center gap-3">
311
  <div class="flex-1 space-y-2 w-full">
312
  <div class="flex items-center justify-between md:justify-start gap-3 w-full">
313
+ <span class="text-xs font-bold text-white bg-brass/10 border border-brass/25 px-2.5 py-1 rounded-lg">میز شماره ${order.table}</span>
314
  <span class="text-[9px] text-zinc-500 font-medium">زمان ثبت: ${timeString}</span>
315
  </div>
316
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-1.5 mt-2">
 
321
  <svg class="w-3.5 h-3.5 text-zinc-400 group-hover:text-white transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
322
  <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
323
  </svg>
324
+ تحویل نهایی شد
325
  </button>
326
  </div>
327
  `;
328
  container.insertAdjacentHTML('beforeend', cardHtml);
329
  });
330
  } else {
331
+ container.innerHTML = `<div class="text-center py-6 text-rose-500 text-xs">خطا در دریافت لیست سفارشات آشپزخانه.</div>`;
332
  }
333
  } catch (err) {
334
+ container.innerHTML = `<div class="text-center py-6 text-rose-500 text-xs">عدم برقراری ارتباط زنده با سرور: ${err.message}</div>`;
335
  }
336
  }
337
 
 
347
  if (response.ok && result.success) {
348
  fetchActiveOrders();
349
  } else {
350
+ alert(result.error || "بروز اختلال در نهایی‌سازی فاکتور.");
351
  }
352
  } catch (err) {
353
+ alert("عدم پاسخگویی در ارتباطات شبکه: " + err.message);
354
  }
355
  }
356
 
357
+ // --- مدیریت درگ و دراپ (Drag & Drop) فاکتور یا تصویر منو ---
358
+ function handleDragOver(e) {
 
 
359
  e.preventDefault();
360
+ const dropZone = document.getElementById('dropZone');
361
+ const icon = document.getElementById('uploadIcon');
362
+ dropZone.classList.add('border-brass', 'bg-brass/5');
363
+ icon.classList.add('text-brass');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  }
365
 
366
+ function handleDragLeave(e) {
367
+ e.preventDefault();
368
+ const dropZone = document.getElementById('dropZone');
369
+ const icon = document.getElementById('uploadIcon');
370
+ dropZone.classList.remove('border-brass', 'bg-brass/5');
371
+ icon.classList.remove('text-brass');
 
 
 
 
 
 
 
 
372
  }
373
 
374
+ function handleDrop(e) {
375
+ e.preventDefault();
376
+ handleDragLeave(e);
377
 
378
+ const files = e.dataTransfer.files;
379
+ if (files.length > 0) {
380
+ const fileInput = document.getElementById('menuFileInput');
381
+ fileInput.files = files;
382
+ // تریگر دستی ارسال رویداد فایل
383
+ const event = { target: { files: files } };
384
+ uploadMenuFile(event);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  }
 
 
 
 
386
  }
387
 
388
+ // --- ارسال و آنالیز فایلهای منو با هوش مصنوعی ---
 
 
389
  async function uploadMenuFile(e) {
390
  const file = e.target.files[0];
391
  if (!file) return;
392
+
393
  const loader = document.getElementById('uploadLoader');
394
  const editor = document.getElementById('menuEditorContainer');
395
 
396
  loader.classList.remove('hidden');
397
  editor.classList.add('hidden');
398
+
399
  const formData = new FormData();
400
  formData.append('file', file);
401
+
402
  try {
403
  const response = await fetch('/api/admin/extract_menu', {
404
  method: 'POST',
405
  body: formData
406
  });
 
407
  const data = await response.json();
408
+
409
  loader.classList.add('hidden');
 
410
  if (response.ok && data.success) {
411
  renderMenuEditor(data.extracted_menu);
412
  editor.classList.remove('hidden');
413
  } else {
414
+ alert(data.error || 'ساختار فایل ارسالی توسط هوش مصنوعی قابل آنالیز نبود.');
415
  }
416
  } catch (err) {
417
  loader.classList.add('hidden');
418
+ alert('خطا در دسترسی و بارگذاری اطلاعات در سرور: ' + err.message);
419
  }
420
  }
421
 
422
  function renderMenuEditor(menuItems) {
423
  const tbody = document.getElementById('menuEditorTableBody');
424
  tbody.innerHTML = '';
 
425
  menuItems.forEach((item, index) => {
426
  const row = `
427
+ <tr class="hover:bg-coal/45 transition-colors duration-200">
428
  <td class="px-2 py-2">
429
+ <input type="text" class="menu-name w-full bg-coal border border-zinc-800 rounded-lg px-2.5 py-1.5 text-zinc-200 focus:border-brass/30 focus:outline-none text-[11px]" value="${item.name || ''}">
430
  </td>
431
  <td class="px-2 py-2">
432
+ <input type="text" class="menu-desc w-full bg-coal border border-zinc-800 rounded-lg px-2.5 py-1.5 text-zinc-200 focus:border-brass/30 focus:outline-none text-[11px]" value="${item.description || ''}">
433
  </td>
434
  <td class="px-2 py-2">
435
+ <input type="text" class="menu-price w-full bg-coal border border-zinc-800 rounded-lg px-2.5 py-1.5 text-zinc-200 focus:border-brass/30 focus:outline-none text-[11px]" value="${item.price || ''}">
436
  </td>
437
  <td class="px-2 py-2 text-center">
438
  <button onclick="removeMenuRow(this)" class="p-1 text-zinc-600 hover:text-rose-500 transition-colors">
 
450
  function addEmptyMenuRow() {
451
  const tbody = document.getElementById('menuEditorTableBody');
452
  const row = `
453
+ <tr class="hover:bg-coal/45 transition-colors duration-200">
454
  <td class="px-2 py-2">
455
+ <input type="text" class="menu-name w-full bg-coal border border-zinc-800 rounded-lg px-2.5 py-1.5 text-zinc-200 focus:border-brass/30 focus:outline-none text-[11px]" placeholder="مثال: لاته کارامل">
456
  </td>
457
  <td class="px-2 py-2">
458
+ <input type="text" class="menu-desc w-full bg-coal border border-zinc-800 rounded-lg px-2.5 py-1.5 text-zinc-200 focus:border-brass/30 focus:outline-none text-[11px]" placeholder="ترکیب اسپرسو دبل، سس کارامل و فوم شیر">
459
  </td>
460
  <td class="px-2 py-2">
461
+ <input type="text" class="menu-price w-full bg-coal border border-zinc-800 rounded-lg px-2.5 py-1.5 text-zinc-200 focus:border-brass/30 focus:outline-none text-[11px]" placeholder="۸۵,۰۰۰">
462
  </td>
463
  <td class="px-2 py-2 text-center">
464
  <button onclick="removeMenuRow(this)" class="p-1 text-zinc-600 hover:text-rose-500 transition-colors">
 
480
  const tbody = document.getElementById('menuEditorTableBody');
481
  const rows = tbody.querySelectorAll('tr');
482
  const menuData = [];
483
+
484
  rows.forEach(row => {
485
  const name = row.querySelector('.menu-name').value.trim();
486
  const description = row.querySelector('.menu-desc').value.trim();
487
  const price = row.querySelector('.menu-price').value.trim();
 
488
  if (name) {
489
  menuData.push({
490
  name: name,
 
494
  });
495
  }
496
  });
497
+
498
  if (menuData.length === 0) {
499
+ alert("انتشار لیست فاقد محصول امکان‌پذیر نیست.");
500
  return;
501
  }
502
+
503
  try {
504
  const response = await fetch('/api/admin/save_menu', {
505
  method: 'POST',
506
  headers: { 'Content-Type': 'application/json' },
507
  body: JSON.stringify({ menu: menuData })
508
  });
 
509
  const data = await response.json();
510
+
511
  if (response.ok && data.success) {
512
  alert("منوی جدید با موفقیت ثبت و روی وب‌سایت همگام‌سازی شد!");
513
  document.getElementById('menuEditorContainer').classList.add('hidden');
514
+ appendChatMessage('model', 'منوی زنده همگام‌سازی شد. سیستم سفارش‌گیری مشتریان اکنون بر مبنای آخرین لیست ویرایش‌شده کالیبره شده است.');
515
+ } else {
516
+ alert(data.error || 'بروز خطا در همگام‌سازی ساختار داده‌های جدید.');
517
+ }
518
+ } catch (err) {
519
+ alert('عدم اتصال به پایگاه داده همگام‌سازی منوها: ' + err.message);
520
+ }
521
+ }
522
+
523
+ // --- سیستم مکالمه ادمین جهت کنترل موجودی اقلام منو ---
524
+ async function sendAdminMessage(e) {
525
+ e.preventDefault();
526
+ if (isGenerating) return;
527
+
528
+ const input = document.getElementById('chatInput');
529
+ const text = input.value.trim();
530
+ if (!text) return;
531
+
532
+ appendChatMessage('user', text);
533
+ input.value = '';
534
+ chatHistory.push({ role: 'user', content: text });
535
+
536
+ isGenerating = true;
537
+ toggleChatLoading(true);
538
+ abortController = new AbortController();
539
+
540
+ try {
541
+ const response = await fetch('/api/admin/chat', {
542
+ method: 'POST',
543
+ headers: { 'Content-Type': 'application/json' },
544
+ body: JSON.stringify({ messages: chatHistory }),
545
+ signal: abortController.signal
546
+ });
547
+ const data = await response.json();
548
+
549
+ toggleChatLoading(false);
550
+ isGenerating = false;
551
+
552
+ if (response.ok && data.success) {
553
+ const botBubbleId = appendChatMessage('model', '');
554
+ const botBubble = document.getElementById(botBubbleId);
555
+ let i = 0;
556
+ const textResponse = data.response;
557
+
558
+ function typeText() {
559
+ if (i < textResponse.length && !abortController.signal.aborted) {
560
+ botBubble.textContent += textResponse.charAt(i);
561
+ i++;
562
+ document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
563
+ setTimeout(typeText, 6);
564
+ } else {
565
+ chatHistory.push({ role: 'model', content: textResponse });
566
+ }
567
+ }
568
+ typeText();
569
  } else {
570
+ appendChatMessage('model', data.error || 'عدم پاسخگویی مطلوب از سمت دستیار هوشمند ادمین.');
571
  }
572
  } catch (err) {
573
+ toggleChatLoading(false);
574
+ isGenerating = false;
575
+ if (err.name !== 'AbortError') {
576
+ appendChatMessage('model', 'عدم دریافت موفق پاسخ در بستر شبکه: ' + err.message);
577
+ }
578
+ }
579
+ }
580
+
581
+ function stopAdminChat() {
582
+ if (abortController) {
583
+ abortController.abort();
584
+ }
585
+ toggleChatLoading(false);
586
+ isGenerating = false;
587
+ }
588
+
589
+ function toggleChatLoading(loading) {
590
+ const loader = document.getElementById('chatLoader');
591
+ const sendBtn = document.getElementById('sendBtn');
592
+ const input = document.getElementById('chatInput');
593
+
594
+ if (loading) {
595
+ loader.classList.remove('hidden');
596
+ sendBtn.disabled = true;
597
+ input.disabled = true;
598
+ } else {
599
+ loader.classList.add('hidden');
600
+ sendBtn.disabled = false;
601
+ input.disabled = false;
602
+ }
603
+ }
604
+
605
+ function appendChatMessage(role, content) {
606
+ const container = document.getElementById('chatMessages');
607
+ const bubbleId = 'bubble-admin-' + Date.now();
608
+ let html = '';
609
+
610
+ if (role === 'user') {
611
+ html = `
612
+ <div class="flex justify-end gap-2.5 max-w-[85%] mr-auto animate-fadeIn">
613
+ <div class="bg-brass/10 border border-brass/25 text-zinc-100 text-xs rounded-2xl rounded-tl-none px-3.5 py-2.5 leading-relaxed">
614
+ ${content}
615
+ </div>
616
+ </div>
617
+ `;
618
+ } else {
619
+ html = `
620
+ <div class="flex gap-2.5 max-w-[85%] animate-fadeIn">
621
+ <div class="w-7 h-7 rounded-lg bg-brass/15 border border-brass/20 flex items-center justify-center flex-shrink-0">
622
+ <span class="text-brass font-extrabold text-[10px] font-mono">AI</span>
623
+ </div>
624
+ <div id="${bubbleId}" class="bg-obsidian/50 border border-zinc-800 text-zinc-200 text-xs rounded-2xl rounded-tr-none px-3.5 py-2.5 leading-relaxed font-sans">
625
+ ${content}
626
+ </div>
627
+ </div>
628
+ `;
629
  }
630
+
631
+ container.insertAdjacentHTML('beforeend', html);
632
+ container.scrollTop = container.scrollHeight;
633
+ return bubbleId;
634
  }
635
  </script>
636
  </body>