SamiKoen Claude commited on
Commit
1c57887
·
1 Parent(s): ab4139b

Add real-time conversation dashboard for BF

Browse files

- Created API endpoints for fetching conversations (/api/conversations, /api/stats)
- Built responsive HTML dashboard similar to WhatsApp version
- Real-time conversation tracking with auto-refresh (10 sec intervals)
- FastAPI integration with Gradio for API access
- Mobile responsive design with sidebar toggle
- Search functionality across all conversations
- Connection status indicator
- Product highlighting (MADONE, MARLIN, etc)

Features:
- Live conversation monitoring
- Statistics dashboard (total, today, active)
- Persistent API endpoint configuration
- Session-based conversation grouping
- Beautiful UI matching WhatsApp dashboard style

Usage:
1. Deploy BF to Hugging Face Space
2. Open bf_dashboard.html locally
3. Enter your HF Space URL (e.g., https://your-username-bf.hf.space)
4. Monitor conversations in real-time

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>

__pycache__/api_server.cpython-312.pyc ADDED
Binary file (4.18 kB). View file
 
api_server.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """API server for BF conversation tracking"""
2
+
3
+ from fastapi import FastAPI, Response
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+ import json
6
+ import os
7
+ from datetime import datetime
8
+ from typing import List, Dict, Any
9
+ import uvicorn
10
+
11
+ app = FastAPI()
12
+
13
+ # CORS middleware - HTML'den erişim için
14
+ app.add_middleware(
15
+ CORSMiddleware,
16
+ allow_origins=["*"], # Production'da bunu kısıtla
17
+ allow_credentials=True,
18
+ allow_methods=["*"],
19
+ allow_headers=["*"],
20
+ )
21
+
22
+ CONVERSATIONS_FILE = "conversations.json"
23
+
24
+ def load_conversations():
25
+ """Load conversation history from file"""
26
+ if os.path.exists(CONVERSATIONS_FILE):
27
+ try:
28
+ with open(CONVERSATIONS_FILE, 'r', encoding='utf-8') as f:
29
+ return json.load(f)
30
+ except:
31
+ return []
32
+ return []
33
+
34
+ @app.get("/api/conversations")
35
+ async def get_conversations():
36
+ """Get all conversations"""
37
+ conversations = load_conversations()
38
+
39
+ # Format for dashboard
40
+ formatted = {}
41
+ for conv in conversations:
42
+ # Create a session ID from timestamp
43
+ session_id = f"session_{conv['timestamp'].replace(':', '').replace('-', '').replace('T', '_')[:15]}"
44
+
45
+ if session_id not in formatted:
46
+ formatted[session_id] = {
47
+ "customer": "Kullanıcı",
48
+ "phone": session_id,
49
+ "messages": []
50
+ }
51
+
52
+ # Add user message
53
+ formatted[session_id]["messages"].append({
54
+ "type": "received",
55
+ "text": conv["user"],
56
+ "time": conv["timestamp"]
57
+ })
58
+
59
+ # Add bot response
60
+ formatted[session_id]["messages"].append({
61
+ "type": "sent",
62
+ "text": conv["bot"],
63
+ "time": conv["timestamp"]
64
+ })
65
+
66
+ return formatted
67
+
68
+ @app.get("/api/stats")
69
+ async def get_stats():
70
+ """Get conversation statistics"""
71
+ conversations = load_conversations()
72
+
73
+ today = datetime.now().date()
74
+ today_count = sum(1 for conv in conversations
75
+ if datetime.fromisoformat(conv["timestamp"]).date() == today)
76
+
77
+ # Find most asked questions
78
+ questions = {}
79
+ for conv in conversations:
80
+ q = conv["user"].lower()
81
+ # Simple categorization
82
+ if "madone" in q:
83
+ questions["Madone"] = questions.get("Madone", 0) + 1
84
+ elif "marlin" in q:
85
+ questions["Marlin"] = questions.get("Marlin", 0) + 1
86
+ elif "fiyat" in q or "kaç" in q:
87
+ questions["Fiyat"] = questions.get("Fiyat", 0) + 1
88
+ elif "stok" in q or "var mı" in q:
89
+ questions["Stok"] = questions.get("Stok", 0) + 1
90
+
91
+ return {
92
+ "total": len(conversations),
93
+ "today": today_count,
94
+ "categories": questions
95
+ }
96
+
97
+ @app.get("/api/health")
98
+ async def health_check():
99
+ """Health check endpoint"""
100
+ return {"status": "ok", "timestamp": datetime.now().isoformat()}
101
+
102
+ if __name__ == "__main__":
103
+ # This will be imported by Gradio app, not run directly
104
+ pass
app.py CHANGED
@@ -6,6 +6,8 @@ import xml.etree.ElementTree as ET
6
  import schedule
7
  import time
8
  import threading
 
 
9
  from huggingface_hub import HfApi, create_repo, hf_hub_download
10
  import warnings
11
  import pandas as pd
@@ -32,6 +34,9 @@ from image_renderer import extract_product_info_for_gallery, format_message_with
32
  # Import conversation tracker
33
  from conversation_tracker import add_conversation, export_html_file
34
 
 
 
 
35
  # Import smart warehouse with GPT intelligence and price
36
  try:
37
  from smart_warehouse_with_price import get_warehouse_stock_smart_with_price
@@ -1143,5 +1148,8 @@ with gr.Blocks(css=custom_css, theme="soft", title="Trek Asistanı", head=storag
1143
 
1144
  export_btn.click(export_conversations, outputs=[download_file, export_status])
1145
 
 
 
 
1146
  if __name__ == "__main__":
1147
  demo.launch(debug=True)
 
6
  import schedule
7
  import time
8
  import threading
9
+ from fastapi import FastAPI
10
+ from fastapi.middleware.cors import CORSMiddleware
11
  from huggingface_hub import HfApi, create_repo, hf_hub_download
12
  import warnings
13
  import pandas as pd
 
34
  # Import conversation tracker
35
  from conversation_tracker import add_conversation, export_html_file
36
 
37
+ # Import API endpoints
38
+ from api_server import app as api_app
39
+
40
  # Import smart warehouse with GPT intelligence and price
41
  try:
42
  from smart_warehouse_with_price import get_warehouse_stock_smart_with_price
 
1148
 
1149
  export_btn.click(export_conversations, outputs=[download_file, export_status])
1150
 
1151
+ # Mount FastAPI to Gradio for API access
1152
+ app = gr.mount_gradio_app(api_app, demo, path="/")
1153
+
1154
  if __name__ == "__main__":
1155
  demo.launch(debug=True)
bf_dashboard.html ADDED
@@ -0,0 +1,682 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="tr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>BF Chatbot Yöneticisi - Trek Bisiklet</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ height: 100vh;
18
+ display: flex;
19
+ overflow: hidden;
20
+ }
21
+
22
+ .sidebar {
23
+ width: 350px;
24
+ background: white;
25
+ border-right: 1px solid #e4e6ea;
26
+ display: flex;
27
+ flex-direction: column;
28
+ box-shadow: 2px 0 10px rgba(0,0,0,0.1);
29
+ }
30
+
31
+ .header {
32
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
33
+ color: white;
34
+ padding: 20px;
35
+ text-align: center;
36
+ position: relative;
37
+ }
38
+
39
+ .header h1 {
40
+ font-size: 20px;
41
+ margin-bottom: 5px;
42
+ }
43
+
44
+ .header .subtitle {
45
+ font-size: 12px;
46
+ opacity: 0.9;
47
+ }
48
+
49
+ .refresh-indicator {
50
+ position: absolute;
51
+ top: 10px;
52
+ right: 15px;
53
+ width: 12px;
54
+ height: 12px;
55
+ border-radius: 50%;
56
+ background: #4CAF50;
57
+ animation: pulse 2s infinite;
58
+ }
59
+
60
+ .refresh-indicator.loading {
61
+ background: #ff9800;
62
+ animation: spin 1s linear infinite;
63
+ }
64
+
65
+ @keyframes pulse {
66
+ 0% { opacity: 1; }
67
+ 50% { opacity: 0.5; }
68
+ 100% { opacity: 1; }
69
+ }
70
+
71
+ @keyframes spin {
72
+ 0% { transform: rotate(0deg); }
73
+ 100% { transform: rotate(360deg); }
74
+ }
75
+
76
+ .stats-bar {
77
+ background: #f8f9fa;
78
+ padding: 15px;
79
+ border-bottom: 1px solid #e4e6ea;
80
+ }
81
+
82
+ .stats {
83
+ display: flex;
84
+ justify-content: space-around;
85
+ }
86
+
87
+ .stat-item {
88
+ text-align: center;
89
+ }
90
+
91
+ .stat-value {
92
+ font-size: 24px;
93
+ font-weight: bold;
94
+ color: #667eea;
95
+ }
96
+
97
+ .stat-label {
98
+ font-size: 11px;
99
+ color: #666;
100
+ margin-top: 2px;
101
+ }
102
+
103
+ .search-bar {
104
+ padding: 15px;
105
+ border-bottom: 1px solid #e4e6ea;
106
+ background: white;
107
+ }
108
+
109
+ .search-input {
110
+ width: 100%;
111
+ padding: 10px 15px;
112
+ border: 1px solid #ddd;
113
+ border-radius: 20px;
114
+ outline: none;
115
+ font-size: 14px;
116
+ }
117
+
118
+ .search-input:focus {
119
+ border-color: #667eea;
120
+ box-shadow: 0 0 5px rgba(102, 126, 234, 0.3);
121
+ }
122
+
123
+ .api-config {
124
+ padding: 15px;
125
+ border-bottom: 1px solid #e4e6ea;
126
+ background: #f0f2f5;
127
+ }
128
+
129
+ .api-config input {
130
+ width: 100%;
131
+ padding: 8px;
132
+ margin: 5px 0;
133
+ border: 1px solid #ddd;
134
+ border-radius: 4px;
135
+ font-size: 12px;
136
+ }
137
+
138
+ .customer-list {
139
+ flex: 1;
140
+ overflow-y: auto;
141
+ background: white;
142
+ }
143
+
144
+ .customer-item {
145
+ padding: 15px 20px;
146
+ border-bottom: 1px solid #f0f2f5;
147
+ cursor: pointer;
148
+ transition: background 0.2s;
149
+ position: relative;
150
+ }
151
+
152
+ .customer-item:hover {
153
+ background: #f8f9fa;
154
+ }
155
+
156
+ .customer-item.active {
157
+ background: #e3f2fd;
158
+ }
159
+
160
+ .customer-name {
161
+ font-size: 15px;
162
+ font-weight: 600;
163
+ margin-bottom: 4px;
164
+ color: #333;
165
+ }
166
+
167
+ .customer-message {
168
+ font-size: 13px;
169
+ color: #666;
170
+ overflow: hidden;
171
+ white-space: nowrap;
172
+ text-overflow: ellipsis;
173
+ }
174
+
175
+ .customer-time {
176
+ position: absolute;
177
+ top: 15px;
178
+ right: 20px;
179
+ font-size: 11px;
180
+ color: #999;
181
+ }
182
+
183
+ .unread-badge {
184
+ position: absolute;
185
+ bottom: 15px;
186
+ right: 20px;
187
+ background: #667eea;
188
+ color: white;
189
+ font-size: 11px;
190
+ padding: 2px 6px;
191
+ border-radius: 10px;
192
+ font-weight: bold;
193
+ }
194
+
195
+ .chat-area {
196
+ flex: 1;
197
+ display: flex;
198
+ flex-direction: column;
199
+ background: #e5ddd5;
200
+ }
201
+
202
+ .chat-header {
203
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
204
+ color: white;
205
+ padding: 15px 20px;
206
+ display: flex;
207
+ align-items: center;
208
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
209
+ }
210
+
211
+ .back-button {
212
+ display: none;
213
+ background: none;
214
+ border: none;
215
+ color: white;
216
+ font-size: 24px;
217
+ cursor: pointer;
218
+ margin-right: 15px;
219
+ padding: 5px;
220
+ border-radius: 50%;
221
+ transition: background 0.2s;
222
+ }
223
+
224
+ .back-button:hover {
225
+ background: rgba(255,255,255,0.1);
226
+ }
227
+
228
+ .chat-header-info {
229
+ flex: 1;
230
+ }
231
+
232
+ .chat-header .customer-name {
233
+ font-size: 16px;
234
+ font-weight: 600;
235
+ margin-bottom: 2px;
236
+ }
237
+
238
+ .chat-header .customer-phone {
239
+ font-size: 13px;
240
+ opacity: 0.8;
241
+ }
242
+
243
+ .chat-messages {
244
+ flex: 1;
245
+ padding: 20px;
246
+ overflow-y: auto;
247
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%23e5ddd5"/><circle cx="50" cy="50" r="1" fill="%23d4cfc7" opacity="0.5"/></svg>');
248
+ }
249
+
250
+ .message {
251
+ margin-bottom: 10px;
252
+ display: flex;
253
+ align-items: flex-end;
254
+ animation: fadeIn 0.3s ease-in;
255
+ }
256
+
257
+ @keyframes fadeIn {
258
+ from { opacity: 0; transform: translateY(10px); }
259
+ to { opacity: 1; transform: translateY(0); }
260
+ }
261
+
262
+ .message.sent {
263
+ justify-content: flex-end;
264
+ }
265
+
266
+ .message.received {
267
+ justify-content: flex-start;
268
+ }
269
+
270
+ .message-bubble {
271
+ max-width: 65%;
272
+ padding: 8px 12px;
273
+ border-radius: 18px;
274
+ box-shadow: 0 1px 0.5px rgba(0,0,0,0.13);
275
+ }
276
+
277
+ .message.sent .message-bubble {
278
+ background: white;
279
+ margin-left: auto;
280
+ border-bottom-right-radius: 4px;
281
+ }
282
+
283
+ .message.received .message-bubble {
284
+ background: #d9fdd3;
285
+ margin-right: auto;
286
+ border-bottom-left-radius: 4px;
287
+ }
288
+
289
+ .message-text {
290
+ margin-bottom: 4px;
291
+ line-height: 1.4;
292
+ white-space: pre-wrap;
293
+ }
294
+
295
+ .message-time {
296
+ font-size: 11px;
297
+ color: #667781;
298
+ text-align: right;
299
+ }
300
+
301
+ .empty-state {
302
+ flex: 1;
303
+ display: flex;
304
+ align-items: center;
305
+ justify-content: center;
306
+ flex-direction: column;
307
+ color: #999;
308
+ }
309
+
310
+ .empty-state svg {
311
+ width: 100px;
312
+ height: 100px;
313
+ margin-bottom: 20px;
314
+ opacity: 0.5;
315
+ }
316
+
317
+ /* Product highlighting */
318
+ .product-mention {
319
+ color: #667eea;
320
+ font-weight: bold;
321
+ }
322
+
323
+ /* Mobile responsive */
324
+ @media (max-width: 768px) {
325
+ .sidebar {
326
+ width: 100%;
327
+ position: absolute;
328
+ z-index: 10;
329
+ transition: transform 0.3s;
330
+ }
331
+
332
+ .sidebar.hidden {
333
+ transform: translateX(-100%);
334
+ }
335
+
336
+ .chat-area {
337
+ width: 100%;
338
+ }
339
+
340
+ .back-button {
341
+ display: block !important;
342
+ }
343
+ }
344
+
345
+ /* Scrollbar styling */
346
+ ::-webkit-scrollbar {
347
+ width: 6px;
348
+ height: 6px;
349
+ }
350
+
351
+ ::-webkit-scrollbar-track {
352
+ background: #f1f1f1;
353
+ }
354
+
355
+ ::-webkit-scrollbar-thumb {
356
+ background: #888;
357
+ border-radius: 3px;
358
+ }
359
+
360
+ ::-webkit-scrollbar-thumb:hover {
361
+ background: #555;
362
+ }
363
+
364
+ .connection-status {
365
+ position: fixed;
366
+ bottom: 20px;
367
+ right: 20px;
368
+ background: white;
369
+ padding: 10px 20px;
370
+ border-radius: 20px;
371
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
372
+ display: flex;
373
+ align-items: center;
374
+ gap: 10px;
375
+ font-size: 12px;
376
+ z-index: 1000;
377
+ }
378
+
379
+ .connection-status.connected {
380
+ background: #4CAF50;
381
+ color: white;
382
+ }
383
+
384
+ .connection-status.disconnected {
385
+ background: #f44336;
386
+ color: white;
387
+ }
388
+
389
+ .connection-status.connecting {
390
+ background: #ff9800;
391
+ color: white;
392
+ }
393
+ </style>
394
+ </head>
395
+ <body>
396
+ <!-- Sidebar -->
397
+ <div class="sidebar" id="sidebar">
398
+ <div class="header">
399
+ <div class="refresh-indicator" id="refreshIndicator"></div>
400
+ <h1>🚴 Trek BF Chatbot</h1>
401
+ <div class="subtitle">Konuşma Yöneticisi</div>
402
+ </div>
403
+
404
+ <div class="stats-bar">
405
+ <div class="stats">
406
+ <div class="stat-item">
407
+ <div class="stat-value" id="totalConversations">0</div>
408
+ <div class="stat-label">Toplam</div>
409
+ </div>
410
+ <div class="stat-item">
411
+ <div class="stat-value" id="todayConversations">0</div>
412
+ <div class="stat-label">Bugün</div>
413
+ </div>
414
+ <div class="stat-item">
415
+ <div class="stat-value" id="activeConversations">0</div>
416
+ <div class="stat-label">Aktif</div>
417
+ </div>
418
+ </div>
419
+ </div>
420
+
421
+ <div class="search-bar">
422
+ <input type="text" class="search-input" placeholder="Konuşmalarda ara..." id="searchInput">
423
+ </div>
424
+
425
+ <div class="api-config">
426
+ <input type="text" id="apiEndpoint" placeholder="API Endpoint (örn: https://your-space.hf.space)" value="">
427
+ <small style="color: #666;">Hugging Face Space URL'nizi girin</small>
428
+ </div>
429
+
430
+ <div class="customer-list" id="customerList">
431
+ <!-- Customer items will be added here -->
432
+ </div>
433
+ </div>
434
+
435
+ <!-- Chat Area -->
436
+ <div class="chat-area">
437
+ <div class="chat-header" id="chatHeader" style="display: none;">
438
+ <button class="back-button" onclick="showSidebar()">←</button>
439
+ <div class="chat-header-info">
440
+ <div class="customer-name" id="chatCustomerName">Müşteri</div>
441
+ <div class="customer-phone" id="chatCustomerPhone">Oturum</div>
442
+ </div>
443
+ </div>
444
+
445
+ <div class="chat-messages" id="chatMessages">
446
+ <div class="empty-state">
447
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
448
+ <path d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
449
+ </svg>
450
+ <div>Bir konuşma seçin</div>
451
+ </div>
452
+ </div>
453
+ </div>
454
+
455
+ <!-- Connection Status -->
456
+ <div class="connection-status" id="connectionStatus">
457
+ <span id="statusText">Bağlanıyor...</span>
458
+ </div>
459
+
460
+ <script>
461
+ // Configuration
462
+ let API_ENDPOINT = localStorage.getItem('bf_api_endpoint') || '';
463
+
464
+ let conversations = {};
465
+ let selectedCustomer = null;
466
+ let refreshInterval;
467
+ let filteredConversations = {};
468
+
469
+ // Load API endpoint from input
470
+ document.getElementById('apiEndpoint').value = API_ENDPOINT;
471
+ document.getElementById('apiEndpoint').addEventListener('change', function() {
472
+ API_ENDPOINT = this.value.trim();
473
+ if (!API_ENDPOINT.endsWith('/')) {
474
+ API_ENDPOINT += '/';
475
+ }
476
+ localStorage.setItem('bf_api_endpoint', API_ENDPOINT);
477
+ loadConversations();
478
+ });
479
+
480
+ function updateConnectionStatus(status) {
481
+ const statusEl = document.getElementById('connectionStatus');
482
+ const statusText = document.getElementById('statusText');
483
+
484
+ statusEl.className = 'connection-status';
485
+
486
+ switch(status) {
487
+ case 'connected':
488
+ statusEl.classList.add('connected');
489
+ statusText.textContent = '✓ Bağlandı';
490
+ setTimeout(() => statusEl.style.display = 'none', 3000);
491
+ break;
492
+ case 'disconnected':
493
+ statusEl.classList.add('disconnected');
494
+ statusText.textContent = '✗ Bağlantı koptu';
495
+ statusEl.style.display = 'flex';
496
+ break;
497
+ case 'connecting':
498
+ statusEl.classList.add('connecting');
499
+ statusText.textContent = '⟳ Bağlanıyor...';
500
+ statusEl.style.display = 'flex';
501
+ break;
502
+ }
503
+ }
504
+
505
+ async function loadConversations() {
506
+ if (!API_ENDPOINT) {
507
+ updateConnectionStatus('disconnected');
508
+ return;
509
+ }
510
+
511
+ const indicator = document.getElementById('refreshIndicator');
512
+ indicator.classList.add('loading');
513
+ updateConnectionStatus('connecting');
514
+
515
+ try {
516
+ const response = await fetch(API_ENDPOINT + 'api/conversations');
517
+ if (!response.ok) throw new Error('API yanıt vermedi');
518
+
519
+ conversations = await response.json();
520
+ filteredConversations = {...conversations};
521
+
522
+ // Load stats
523
+ const statsResponse = await fetch(API_ENDPOINT + 'api/stats');
524
+ if (statsResponse.ok) {
525
+ const stats = await statsResponse.json();
526
+ document.getElementById('totalConversations').textContent = stats.total || 0;
527
+ document.getElementById('todayConversations').textContent = stats.today || 0;
528
+ document.getElementById('activeConversations').textContent = Object.keys(conversations).length;
529
+ }
530
+
531
+ renderCustomerList();
532
+ updateConnectionStatus('connected');
533
+ } catch (error) {
534
+ console.error('API Hatası:', error);
535
+ updateConnectionStatus('disconnected');
536
+ } finally {
537
+ indicator.classList.remove('loading');
538
+ }
539
+ }
540
+
541
+ function formatTime(dateString) {
542
+ const date = new Date(dateString);
543
+ const now = new Date();
544
+ const diff = now - date;
545
+
546
+ if (diff < 60000) return 'şimdi';
547
+ if (diff < 3600000) return Math.floor(diff / 60000) + ' dk';
548
+ if (diff < 86400000) return Math.floor(diff / 3600000) + ' sa';
549
+
550
+ return date.toLocaleDateString('tr-TR');
551
+ }
552
+
553
+ function highlightProducts(text) {
554
+ const products = ['MADONE', 'MARLIN', 'DOMANE', 'CHECKPOINT', 'EMONDA', 'FX', 'RAIL', 'POWERFLY'];
555
+ let result = text;
556
+ products.forEach(product => {
557
+ const regex = new RegExp(`(${product}[^\\s]*)`, 'gi');
558
+ result = result.replace(regex, '<span class="product-mention">$1</span>');
559
+ });
560
+ return result;
561
+ }
562
+
563
+ function renderCustomerList() {
564
+ const listEl = document.getElementById('customerList');
565
+ listEl.innerHTML = '';
566
+
567
+ Object.entries(filteredConversations).forEach(([phone, data]) => {
568
+ const lastMessage = data.messages[data.messages.length - 1];
569
+ if (!lastMessage) return;
570
+
571
+ const item = document.createElement('div');
572
+ item.className = 'customer-item';
573
+ if (phone === selectedCustomer) {
574
+ item.classList.add('active');
575
+ }
576
+
577
+ item.innerHTML = `
578
+ <div class="customer-name">Kullanıcı ${phone.substr(-4)}</div>
579
+ <div class="customer-message">${lastMessage.text.substring(0, 50)}...</div>
580
+ <div class="customer-time">${formatTime(lastMessage.time)}</div>
581
+ `;
582
+
583
+ item.onclick = () => selectCustomer(phone);
584
+ listEl.appendChild(item);
585
+ });
586
+ }
587
+
588
+ function selectCustomer(phone) {
589
+ selectedCustomer = phone;
590
+ const customerData = conversations[phone];
591
+
592
+ // Update header
593
+ document.getElementById('chatHeader').style.display = 'flex';
594
+ document.getElementById('chatCustomerName').textContent = `Kullanıcı ${phone.substr(-4)}`;
595
+ document.getElementById('chatCustomerPhone').textContent = phone;
596
+
597
+ // Render messages
598
+ const messagesEl = document.getElementById('chatMessages');
599
+ messagesEl.innerHTML = '';
600
+
601
+ customerData.messages.forEach(msg => {
602
+ const messageDiv = document.createElement('div');
603
+ messageDiv.className = `message ${msg.type}`;
604
+
605
+ messageDiv.innerHTML = `
606
+ <div class="message-bubble">
607
+ <div class="message-text">${highlightProducts(msg.text)}</div>
608
+ <div class="message-time">${formatTime(msg.time)}</div>
609
+ </div>
610
+ `;
611
+
612
+ messagesEl.appendChild(messageDiv);
613
+ });
614
+
615
+ // Scroll to bottom
616
+ messagesEl.scrollTop = messagesEl.scrollHeight;
617
+
618
+ // Update active state
619
+ renderCustomerList();
620
+
621
+ // Mobile: hide sidebar
622
+ if (window.innerWidth <= 768) {
623
+ document.getElementById('sidebar').classList.add('hidden');
624
+ }
625
+ }
626
+
627
+ function showSidebar() {
628
+ document.getElementById('sidebar').classList.remove('hidden');
629
+ }
630
+
631
+ // Search functionality
632
+ document.getElementById('searchInput').addEventListener('input', function() {
633
+ const searchTerm = this.value.toLowerCase();
634
+
635
+ if (!searchTerm) {
636
+ filteredConversations = {...conversations};
637
+ } else {
638
+ filteredConversations = {};
639
+ Object.entries(conversations).forEach(([phone, data]) => {
640
+ const hasMatch = data.messages.some(msg =>
641
+ msg.text.toLowerCase().includes(searchTerm)
642
+ );
643
+ if (hasMatch) {
644
+ filteredConversations[phone] = data;
645
+ }
646
+ });
647
+ }
648
+
649
+ renderCustomerList();
650
+ });
651
+
652
+ // Auto refresh
653
+ function startAutoRefresh() {
654
+ refreshInterval = setInterval(loadConversations, 10000); // Her 10 saniyede bir
655
+ }
656
+
657
+ function stopAutoRefresh() {
658
+ if (refreshInterval) {
659
+ clearInterval(refreshInterval);
660
+ }
661
+ }
662
+
663
+ // Initial load
664
+ if (API_ENDPOINT) {
665
+ loadConversations();
666
+ startAutoRefresh();
667
+ } else {
668
+ updateConnectionStatus('disconnected');
669
+ }
670
+
671
+ // Page visibility API
672
+ document.addEventListener('visibilitychange', function() {
673
+ if (document.hidden) {
674
+ stopAutoRefresh();
675
+ } else {
676
+ loadConversations();
677
+ startAutoRefresh();
678
+ }
679
+ });
680
+ </script>
681
+ </body>
682
+ </html>
requirements.txt CHANGED
@@ -12,3 +12,5 @@ google-auth-httplib2
12
  google-api-python-client
13
  tabulate
14
  Pillow
 
 
 
12
  google-api-python-client
13
  tabulate
14
  Pillow
15
+ fastapi
16
+ uvicorn