Update app.py
Browse files
app.py
CHANGED
@@ -15,7 +15,6 @@ from google.oauth2.service_account import Credentials
|
|
15 |
from googleapiclient.discovery import build
|
16 |
from googleapiclient.http import MediaIoBaseDownload
|
17 |
import io
|
18 |
-
import warnings
|
19 |
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
20 |
warnings.simplefilter('ignore', InsecureRequestWarning)
|
21 |
|
@@ -32,7 +31,26 @@ from image_renderer import extract_product_info_for_gallery, format_message_with
|
|
32 |
# Import conversation tracker
|
33 |
from conversation_tracker import add_conversation
|
34 |
|
35 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
|
37 |
# Import smart warehouse with GPT intelligence and price
|
38 |
try:
|
@@ -60,7 +78,6 @@ def get_warehouse_stock_old(product_name):
|
|
60 |
"""Smart warehouse stock finder with general algorithm"""
|
61 |
try:
|
62 |
import re
|
63 |
-
import requests
|
64 |
|
65 |
# Get XML with retry
|
66 |
xml_text = None
|
@@ -98,42 +115,26 @@ def get_warehouse_stock_old(product_name):
|
|
98 |
|
99 |
# If query is very short (like "hangi boyu"), skip it
|
100 |
if len(words) <= 2 and not any(w.isdigit() for w in words):
|
101 |
-
# Likely just a question, not a product search
|
102 |
pass
|
103 |
else:
|
104 |
-
# Extract product-like terms
|
105 |
for word in words:
|
106 |
-
# Skip if it's a size marker
|
107 |
if word in sizes:
|
108 |
continue
|
109 |
-
|
110 |
-
# Always keep numbers (model numbers)
|
111 |
if word.isdigit():
|
112 |
product_words.append(word)
|
113 |
-
|
114 |
-
# Keep alphanumeric codes
|
115 |
elif any(c.isdigit() for c in word) and any(c.isalpha() for c in word):
|
116 |
product_words.append(word)
|
117 |
-
|
118 |
-
# Keep 2-3 letter codes that look like product codes
|
119 |
elif len(word) in [2, 3] and word.isalpha():
|
120 |
-
# Skip common particles
|
121 |
if word in ['mi', 'mı', 'mu', 'mü', 'var', 'yok', 've', 'de', 'da']:
|
122 |
continue
|
123 |
-
# Must have at least one consonant
|
124 |
if any(c not in 'aeiouı' for c in word):
|
125 |
product_words.append(word)
|
126 |
-
|
127 |
-
# For longer words, be selective
|
128 |
elif len(word) > 3:
|
129 |
-
# Skip if ends with Turkish question suffixes
|
130 |
if any(word.endswith(suffix) for suffix in ['mi', 'mı', 'mu', 'mü']):
|
131 |
continue
|
132 |
-
# Skip if only 1-2 consonants (likely a particle/question word)
|
133 |
consonants = sum(1 for c in word if c not in 'aeiouı')
|
134 |
-
if consonants <= 2:
|
135 |
continue
|
136 |
-
# Keep it
|
137 |
product_words.append(word)
|
138 |
|
139 |
print(f"DEBUG - Searching: {' '.join(product_words)}, Size: {size}")
|
@@ -144,11 +145,8 @@ def get_warehouse_stock_old(product_name):
|
|
144 |
|
145 |
print(f"DEBUG - Total products in XML: {len(all_products)}")
|
146 |
|
147 |
-
# Search through products
|
148 |
best_match = None
|
149 |
-
|
150 |
for product_block in all_products:
|
151 |
-
# Extract product name
|
152 |
name_match = re.search(r'<ProductName><!\[CDATA\[(.*?)\]\]></ProductName>', product_block)
|
153 |
if not name_match:
|
154 |
continue
|
@@ -156,37 +154,27 @@ def get_warehouse_stock_old(product_name):
|
|
156 |
product_name_in_xml = name_match.group(1)
|
157 |
normalized_xml_name = normalize(product_name_in_xml)
|
158 |
|
159 |
-
# Check if all product words are in the name (as separate words or part of words)
|
160 |
-
# This handles cases like "gen 8" where it might be "gen8" or "gen 8" in XML
|
161 |
match = True
|
162 |
for word in product_words:
|
163 |
-
# Check if word exists as-is or without spaces (for numbers after text)
|
164 |
if word not in normalized_xml_name:
|
165 |
-
# Also check if it's a number that might be attached to previous word
|
166 |
if not (word.isdigit() and any(f"{prev}{word}" in normalized_xml_name or f"{prev} {word}" in normalized_xml_name for prev in product_words if not prev.isdigit())):
|
167 |
match = False
|
168 |
break
|
169 |
|
170 |
if match:
|
171 |
-
# Product name matches, now check variant if needed
|
172 |
-
|
173 |
if size:
|
174 |
-
# Check if variant matches the size
|
175 |
variant_match = re.search(r'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', product_block)
|
176 |
if variant_match:
|
177 |
variant = variant_match.group(1)
|
178 |
-
# Check if variant starts with the size (e.g., "S-BEYAZ")
|
179 |
if variant.upper().startswith(f'{size.upper()}-'):
|
180 |
print(f"DEBUG - Found match: {product_name_in_xml} - {variant}")
|
181 |
best_match = product_block
|
182 |
-
break
|
183 |
else:
|
184 |
-
# No size specified, take first match
|
185 |
best_match = product_block
|
186 |
break
|
187 |
|
188 |
if best_match:
|
189 |
-
# Extract warehouse info from the matched product
|
190 |
warehouse_info = []
|
191 |
warehouse_regex = r'<Warehouse>.*?<Name><!\[CDATA\[(.*?)\]\]></Name>.*?<Stock>(.*?)</Stock>.*?</Warehouse>'
|
192 |
warehouses = re.findall(warehouse_regex, best_match, re.DOTALL)
|
@@ -195,7 +183,6 @@ def get_warehouse_stock_old(product_name):
|
|
195 |
try:
|
196 |
stock = int(wh_stock.strip())
|
197 |
if stock > 0:
|
198 |
-
# Format store names
|
199 |
if "CADDEBOSTAN" in wh_name:
|
200 |
display = "Caddebostan mağazası"
|
201 |
elif "ORTAKÖY" in wh_name:
|
@@ -206,7 +193,6 @@ def get_warehouse_stock_old(product_name):
|
|
206 |
display = "Bahçeköy mağazası"
|
207 |
else:
|
208 |
display = wh_name
|
209 |
-
|
210 |
warehouse_info.append(f"{display}: Mevcut")
|
211 |
except:
|
212 |
pass
|
@@ -220,205 +206,6 @@ def get_warehouse_stock_old(product_name):
|
|
220 |
print(f"Warehouse error: {e}")
|
221 |
return None
|
222 |
|
223 |
-
# OLD SLOW VERSION - KEEP FOR REFERENCE
|
224 |
-
def get_warehouse_stock_old_slow(product_name):
|
225 |
-
"""B2B API'den mağaza stok bilgilerini çek - Optimize edilmiş versiyon"""
|
226 |
-
try:
|
227 |
-
import re
|
228 |
-
|
229 |
-
# Hugging Face'de signal çalışmadığı için try-except kullan
|
230 |
-
use_signal = False
|
231 |
-
try:
|
232 |
-
import signal
|
233 |
-
import threading
|
234 |
-
# Test if we're in main thread
|
235 |
-
if threading.current_thread() is threading.main_thread():
|
236 |
-
def timeout_handler(signum, frame):
|
237 |
-
raise TimeoutError("Warehouse API timeout")
|
238 |
-
signal.signal(signal.SIGALRM, timeout_handler)
|
239 |
-
signal.alarm(8)
|
240 |
-
use_signal = True
|
241 |
-
except Exception as e:
|
242 |
-
print(f"Signal not available: {e}")
|
243 |
-
use_signal = False
|
244 |
-
|
245 |
-
try:
|
246 |
-
warehouse_url = 'https://video.trek-turkey.com/bizimhesap-warehouse-xml-b2b-api-v2.php'
|
247 |
-
response = requests.get(warehouse_url, verify=False, timeout=7) # Increased from 2
|
248 |
-
|
249 |
-
if response.status_code != 200:
|
250 |
-
return None
|
251 |
-
|
252 |
-
# ULTRA FAST: Use regex instead of XML parsing for speed
|
253 |
-
xml_text = response.text
|
254 |
-
|
255 |
-
# Turkish character normalization function
|
256 |
-
turkish_map = {'ı': 'i', 'ğ': 'g', 'ü': 'u', 'ş': 's', 'ö': 'o', 'ç': 'c', 'İ': 'i', 'I': 'i'}
|
257 |
-
|
258 |
-
def normalize_turkish(text):
|
259 |
-
import unicodedata
|
260 |
-
# First normalize unicode to handle combining characters
|
261 |
-
text = unicodedata.normalize('NFKD', text)
|
262 |
-
text = ''.join(char for char in text if unicodedata.category(char) != 'Mn')
|
263 |
-
# Replace Turkish characters
|
264 |
-
for tr_char, en_char in turkish_map.items():
|
265 |
-
text = text.replace(tr_char, en_char)
|
266 |
-
# Also handle dotted i (İ with dot above)
|
267 |
-
text = text.replace('İ', 'i').replace('I', 'i')
|
268 |
-
return text.lower()
|
269 |
-
|
270 |
-
# Normalize search product name
|
271 |
-
search_name = normalize_turkish(product_name.strip())
|
272 |
-
# Gen 8, Gen 7 vs. ifadelerini koruyalım
|
273 |
-
search_name = search_name.replace('(2026)', '').replace('(2025)', '').strip()
|
274 |
-
search_words = search_name.split()
|
275 |
-
|
276 |
-
print(f"DEBUG - Searching for: {product_name}")
|
277 |
-
print(f"DEBUG - Normalized (gen kept): {search_name}")
|
278 |
-
|
279 |
-
# SUPER FAST SEARCH - Build index once
|
280 |
-
# Create a dictionary for O(1) lookup instead of O(n) search
|
281 |
-
product_index = {}
|
282 |
-
|
283 |
-
# Build index in one pass
|
284 |
-
for product in root.findall('Product'):
|
285 |
-
name_elem = product.find('ProductName')
|
286 |
-
if name_elem is not None and name_elem.text:
|
287 |
-
product_name_key = normalize_turkish(name_elem.text.strip())
|
288 |
-
|
289 |
-
# Store all products with same name in a list
|
290 |
-
if product_name_key not in product_index:
|
291 |
-
product_index[product_name_key] = []
|
292 |
-
|
293 |
-
variant_elem = product.find('ProductVariant')
|
294 |
-
variant_text = ""
|
295 |
-
if variant_elem is not None and variant_elem.text:
|
296 |
-
variant_text = normalize_turkish(variant_elem.text.strip())
|
297 |
-
|
298 |
-
product_index[product_name_key].append({
|
299 |
-
'element': product,
|
300 |
-
'original_name': name_elem.text.strip(),
|
301 |
-
'variant': variant_text
|
302 |
-
})
|
303 |
-
|
304 |
-
# Separate size/variant words from product words
|
305 |
-
size_color_words = ['s', 'm', 'l', 'xl', 'xs', 'xxl', 'ml']
|
306 |
-
size_indicators = ['beden', 'size', 'boy']
|
307 |
-
|
308 |
-
variant_words = [word for word in search_words if word in size_color_words]
|
309 |
-
product_words = [word for word in search_words if word not in size_color_words and word not in size_indicators]
|
310 |
-
|
311 |
-
# Build the product key to search
|
312 |
-
product_key = ' '.join(product_words)
|
313 |
-
|
314 |
-
print(f"DEBUG - Looking for key: {product_key}")
|
315 |
-
print(f"DEBUG - Variant filter: {variant_words}")
|
316 |
-
|
317 |
-
# INSTANT LOOKUP - O(1)
|
318 |
-
candidates = []
|
319 |
-
|
320 |
-
# Try exact match first
|
321 |
-
if product_key in product_index:
|
322 |
-
print(f"DEBUG - Exact match found!")
|
323 |
-
for item in product_index[product_key]:
|
324 |
-
# Check variant
|
325 |
-
if variant_words:
|
326 |
-
# For size codes like S, M, L
|
327 |
-
if len(variant_words) == 1 and len(variant_words[0]) <= 2:
|
328 |
-
if item['variant'].startswith(variant_words[0] + '-') or item['variant'].startswith(variant_words[0] + ' '):
|
329 |
-
candidates.append((item['element'], item['original_name'], item['variant']))
|
330 |
-
else:
|
331 |
-
if all(word in item['variant'] for word in variant_words):
|
332 |
-
candidates.append((item['element'], item['original_name'], item['variant']))
|
333 |
-
else:
|
334 |
-
# No variant filter, add all
|
335 |
-
candidates.append((item['element'], item['original_name'], item['variant']))
|
336 |
-
else:
|
337 |
-
# Fallback: search all keys that contain all product words
|
338 |
-
print(f"DEBUG - No exact match, searching partial matches...")
|
339 |
-
for key, items in product_index.items():
|
340 |
-
if all(word in key for word in product_words):
|
341 |
-
for item in items:
|
342 |
-
if variant_words:
|
343 |
-
if len(variant_words) == 1 and len(variant_words[0]) <= 2:
|
344 |
-
if item['variant'].startswith(variant_words[0] + '-'):
|
345 |
-
candidates.append((item['element'], item['original_name'], item['variant']))
|
346 |
-
else:
|
347 |
-
candidates.append((item['element'], item['original_name'], item['variant']))
|
348 |
-
|
349 |
-
if len(candidates) >= 5: # Found enough
|
350 |
-
break
|
351 |
-
|
352 |
-
print(f"DEBUG - Found {len(candidates)} candidates instantly!")
|
353 |
-
|
354 |
-
# Collect stock info from new structure
|
355 |
-
warehouse_stock_map = {}
|
356 |
-
|
357 |
-
for product, xml_name, variant in candidates:
|
358 |
-
# New structure: Warehouse elements are direct children of Product
|
359 |
-
for warehouse in product.findall('Warehouse'):
|
360 |
-
name_elem = warehouse.find('Name')
|
361 |
-
stock_elem = warehouse.find('Stock')
|
362 |
-
|
363 |
-
if name_elem is not None and stock_elem is not None:
|
364 |
-
warehouse_name = name_elem.text if name_elem.text else "Bilinmeyen"
|
365 |
-
try:
|
366 |
-
stock_count = int(stock_elem.text) if stock_elem.text else 0
|
367 |
-
if stock_count > 0:
|
368 |
-
if warehouse_name in warehouse_stock_map:
|
369 |
-
warehouse_stock_map[warehouse_name] += stock_count
|
370 |
-
else:
|
371 |
-
warehouse_stock_map[warehouse_name] = stock_count
|
372 |
-
except (ValueError, TypeError):
|
373 |
-
pass
|
374 |
-
|
375 |
-
# Cancel alarm
|
376 |
-
signal.alarm(0)
|
377 |
-
|
378 |
-
print(f"DEBUG - Found {len(candidates)} candidates")
|
379 |
-
if candidates:
|
380 |
-
for product, name, variant in candidates[:3]:
|
381 |
-
print(f"DEBUG - Candidate: {name} - {variant}")
|
382 |
-
|
383 |
-
if warehouse_stock_map:
|
384 |
-
all_warehouse_info = []
|
385 |
-
for warehouse_name, total_stock in warehouse_stock_map.items():
|
386 |
-
# Mağaza isimlerini daha anlaşılır hale getir
|
387 |
-
if "Caddebostan" in warehouse_name:
|
388 |
-
display_name = "Caddebostan mağazası"
|
389 |
-
elif "Ortaköy" in warehouse_name:
|
390 |
-
display_name = "Ortaköy mağazası"
|
391 |
-
elif "Sarıyer" in warehouse_name:
|
392 |
-
display_name = "Sarıyer mağazası"
|
393 |
-
elif "Alsancak" in warehouse_name or "İzmir" in warehouse_name:
|
394 |
-
display_name = "İzmir Alsancak mağazası"
|
395 |
-
else:
|
396 |
-
display_name = warehouse_name
|
397 |
-
|
398 |
-
all_warehouse_info.append(f"{display_name}: Mevcut")
|
399 |
-
return all_warehouse_info
|
400 |
-
else:
|
401 |
-
return ["Hiçbir mağazada mevcut değil"]
|
402 |
-
|
403 |
-
except TimeoutError:
|
404 |
-
if use_signal:
|
405 |
-
try:
|
406 |
-
signal.alarm(0)
|
407 |
-
except:
|
408 |
-
pass
|
409 |
-
print("Warehouse API timeout - skipping")
|
410 |
-
return None
|
411 |
-
finally:
|
412 |
-
if use_signal:
|
413 |
-
try:
|
414 |
-
signal.alarm(0)
|
415 |
-
except:
|
416 |
-
pass
|
417 |
-
|
418 |
-
except Exception as e:
|
419 |
-
print(f"Mağaza stok bilgisi çekme hatası: {e}")
|
420 |
-
return None
|
421 |
-
|
422 |
# Import improved product search
|
423 |
try:
|
424 |
from improved_chatbot import ImprovedChatbot
|
@@ -453,12 +240,10 @@ else:
|
|
453 |
products = []
|
454 |
if root is not None:
|
455 |
for item in root.findall('item'):
|
456 |
-
# Tüm ürünleri al, sonra stokta olma durumunu kontrol et - None kontrolü
|
457 |
rootlabel_elem = item.find('rootlabel')
|
458 |
stock_elem = item.find('stockAmount')
|
459 |
-
|
460 |
if rootlabel_elem is None or stock_elem is None:
|
461 |
-
continue
|
462 |
|
463 |
name_words = rootlabel_elem.text.lower().split()
|
464 |
name = name_words[0]
|
@@ -466,56 +251,38 @@ if root is not None:
|
|
466 |
|
467 |
stock_amount = "stokta" if stock_elem.text and stock_elem.text > '0' else "stokta değil"
|
468 |
|
469 |
-
# Stokta olmayan ürünler için fiyat/link bilgisi eklemiyoruz
|
470 |
if stock_amount == "stokta":
|
471 |
-
# Normal fiyat bilgisini al - Güvenli versiyon
|
472 |
price_elem = item.find('priceTaxWithCur')
|
473 |
price_str = price_elem.text if price_elem is not None and price_elem.text else "Fiyat bilgisi yok"
|
474 |
-
|
475 |
-
# EFT fiyatını al (havale indirimli orijinal fiyat) - Güvenli versiyon
|
476 |
price_eft_elem = item.find('priceEft')
|
477 |
price_eft_str = price_eft_elem.text if price_eft_elem is not None and price_eft_elem.text else ""
|
478 |
-
|
479 |
-
# İndirimli fiyatı al (kampanyalı fiyat) - Güvenli versiyon
|
480 |
price_rebate_elem = item.find('priceRebateWithTax')
|
481 |
price_rebate_str = price_rebate_elem.text if price_rebate_elem is not None and price_rebate_elem.text else ""
|
482 |
-
|
483 |
-
# Havale indirimi fiyatını al (havale indirimli kampanyalı fiyat) - Güvenli versiyon
|
484 |
price_rebate_money_order_elem = item.find('priceRebateWithMoneyOrderWithTax')
|
485 |
price_rebate_money_order_str = price_rebate_money_order_elem.text if price_rebate_money_order_elem is not None and price_rebate_money_order_elem.text else ""
|
486 |
|
487 |
-
# Normal fiyatı yuvarla
|
488 |
try:
|
489 |
price_float = float(price_str)
|
490 |
-
# Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla
|
491 |
if price_float > 200000:
|
492 |
-
price = str(round(price_float / 5000) * 5000)
|
493 |
-
# Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla
|
494 |
elif price_float > 30000:
|
495 |
-
price = str(round(price_float / 1000) * 1000)
|
496 |
-
# Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla
|
497 |
elif price_float > 10000:
|
498 |
-
price = str(round(price_float / 100) * 100)
|
499 |
-
# Diğer durumlarda en yakın 10'luk basamağa yuvarla
|
500 |
else:
|
501 |
-
price = str(round(price_float / 10) * 10)
|
502 |
except (ValueError, TypeError):
|
503 |
-
price = price_str
|
504 |
|
505 |
-
# Havale indirimli orijinal fiyatı yuvarla (varsa)
|
506 |
if price_eft_str:
|
507 |
try:
|
508 |
price_eft_float = float(price_eft_str)
|
509 |
-
# Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla
|
510 |
if price_eft_float > 200000:
|
511 |
price_eft = str(round(price_eft_float / 5000) * 5000)
|
512 |
-
# Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla
|
513 |
elif price_eft_float > 30000:
|
514 |
price_eft = str(round(price_eft_float / 1000) * 1000)
|
515 |
-
# Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla
|
516 |
elif price_eft_float > 10000:
|
517 |
price_eft = str(round(price_eft_float / 100) * 100)
|
518 |
-
# Diğer durumlarda en yakın 10'luk basamağa yuvarla
|
519 |
else:
|
520 |
price_eft = str(round(price_eft_float / 10) * 10)
|
521 |
except (ValueError, TypeError):
|
@@ -523,20 +290,15 @@ if root is not None:
|
|
523 |
else:
|
524 |
price_eft = ""
|
525 |
|
526 |
-
# İndirimli fiyatı yuvarla (varsa)
|
527 |
if price_rebate_str:
|
528 |
try:
|
529 |
price_rebate_float = float(price_rebate_str)
|
530 |
-
# Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla
|
531 |
if price_rebate_float > 200000:
|
532 |
price_rebate = str(round(price_rebate_float / 5000) * 5000)
|
533 |
-
# Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla
|
534 |
elif price_rebate_float > 30000:
|
535 |
price_rebate = str(round(price_rebate_float / 1000) * 1000)
|
536 |
-
# Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla
|
537 |
elif price_rebate_float > 10000:
|
538 |
price_rebate = str(round(price_rebate_float / 100) * 100)
|
539 |
-
# Diğer durumlarda en yakın 10'luk basamağa yuvarla
|
540 |
else:
|
541 |
price_rebate = str(round(price_rebate_float / 10) * 10)
|
542 |
except (ValueError, TypeError):
|
@@ -544,20 +306,15 @@ if root is not None:
|
|
544 |
else:
|
545 |
price_rebate = ""
|
546 |
|
547 |
-
# Havale indirimli kampanyalı fiyatı yuvarla (varsa)
|
548 |
if price_rebate_money_order_str:
|
549 |
try:
|
550 |
price_rebate_money_order_float = float(price_rebate_money_order_str)
|
551 |
-
# Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla
|
552 |
if price_rebate_money_order_float > 200000:
|
553 |
price_rebate_money_order = str(round(price_rebate_money_order_float / 5000) * 5000)
|
554 |
-
# Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla
|
555 |
elif price_rebate_money_order_float > 30000:
|
556 |
price_rebate_money_order = str(round(price_rebate_money_order_float / 1000) * 1000)
|
557 |
-
# Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla
|
558 |
elif price_rebate_money_order_float > 10000:
|
559 |
price_rebate_money_order = str(round(price_rebate_money_order_float / 100) * 100)
|
560 |
-
# Diğer durumlarda en yakın 10'luk basamağa yuvarla
|
561 |
else:
|
562 |
price_rebate_money_order = str(round(price_rebate_money_order_float / 10) * 10)
|
563 |
except (ValueError, TypeError):
|
@@ -565,20 +322,16 @@ if root is not None:
|
|
565 |
else:
|
566 |
price_rebate_money_order = ""
|
567 |
|
568 |
-
# Ürün bilgilerini al - None kontrolü ekle
|
569 |
product_url_elem = item.find('productLink')
|
570 |
product_url = product_url_elem.text if product_url_elem is not None and product_url_elem.text else ""
|
571 |
product_info = [stock_amount, price, product_url, price_eft, price_rebate, price_rebate_money_order]
|
572 |
|
573 |
-
# Resim URL'si ekle (varsa) - Güvenli versiyon
|
574 |
image_elem = item.find('picture1Path')
|
575 |
if image_elem is not None and image_elem.text:
|
576 |
product_info.append(image_elem.text)
|
577 |
else:
|
578 |
-
product_info.append("")
|
579 |
-
|
580 |
else:
|
581 |
-
# Stokta olmayan ürün için sadece stok durumu
|
582 |
product_info = [stock_amount]
|
583 |
|
584 |
products.append((name, product_info, full_name))
|
@@ -588,6 +341,9 @@ print(f"Toplam {len(products)} ürün yüklendi.")
|
|
588 |
# Initialize enhanced features
|
589 |
initialize_enhanced_features(OPENAI_API_KEY, products)
|
590 |
|
|
|
|
|
|
|
591 |
# Initialize improved chatbot if available
|
592 |
improved_bot = None
|
593 |
if USE_IMPROVED_SEARCH:
|
@@ -620,7 +376,6 @@ def download_documents_from_drive():
|
|
620 |
credentials = Credentials.from_service_account_file(GOOGLE_CREDENTIALS_PATH)
|
621 |
service = build('drive', 'v3', credentials=credentials)
|
622 |
|
623 |
-
# Klasördeki dosyaları listele
|
624 |
results = service.files().list(
|
625 |
q=f"'{GOOGLE_FOLDER_ID}' in parents",
|
626 |
fields="files(id, name, mimeType)"
|
@@ -631,8 +386,6 @@ def download_documents_from_drive():
|
|
631 |
|
632 |
for file in files:
|
633 |
print(f"İndiriliyor: {file['name']}")
|
634 |
-
|
635 |
-
# DOCX dosyaları için
|
636 |
if file['mimeType'] == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
637 |
request = service.files().get_media(fileId=file['id'])
|
638 |
file_io = io.BytesIO()
|
@@ -645,14 +398,14 @@ def download_documents_from_drive():
|
|
645 |
file_io.seek(0)
|
646 |
doc = Document(file_io)
|
647 |
|
648 |
-
content = f"
|
649 |
for paragraph in doc.paragraphs:
|
650 |
if paragraph.text.strip():
|
651 |
-
content += paragraph.text + "
|
652 |
|
653 |
all_content.append(content)
|
654 |
|
655 |
-
document_content = "
|
656 |
print(f"Toplam {len(files)} döküman yüklendi.")
|
657 |
|
658 |
except Exception as e:
|
@@ -668,7 +421,7 @@ def clear_log_file():
|
|
668 |
if os.path.exists(LOG_FILE):
|
669 |
with file_lock:
|
670 |
with open(LOG_FILE, 'w', encoding='utf-8') as f:
|
671 |
-
f.write("Log dosyası temizlendi
|
672 |
print("Log dosyası temizlendi.")
|
673 |
except Exception as e:
|
674 |
print(f"Log dosyası temizleme hatası: {e}")
|
@@ -676,7 +429,6 @@ def clear_log_file():
|
|
676 |
# Zamanlanmış görevleri çalıştırma fonksiyonu
|
677 |
def run_scheduler(chat_history):
|
678 |
schedule.every().day.at("03:00").do(clear_log_file)
|
679 |
-
|
680 |
while True:
|
681 |
schedule.run_pending()
|
682 |
time.sleep(60)
|
@@ -686,63 +438,46 @@ def chatbot_fn(user_message, history, image=None):
|
|
686 |
if history is None:
|
687 |
history = []
|
688 |
|
689 |
-
# ÖNCELİKLE warehouse stock bilgisini al (streaming başlamadan önce)
|
690 |
warehouse_stock_data = None
|
691 |
print(f"DEBUG - Getting warehouse stock FIRST for: {user_message}")
|
692 |
try:
|
693 |
warehouse_stock_data = get_warehouse_stock(user_message)
|
694 |
if warehouse_stock_data:
|
695 |
-
print(f"DEBUG - Warehouse stock found: {warehouse_stock_data[:2]}...")
|
696 |
else:
|
697 |
print(f"DEBUG - No warehouse stock data returned")
|
698 |
except Exception as e:
|
699 |
print(f"DEBUG - Warehouse stock error at start: {e}")
|
700 |
|
701 |
try:
|
702 |
-
# Enhanced features - Görsel işleme
|
703 |
if image is not None:
|
704 |
user_message = process_image_message(image, user_message)
|
705 |
|
706 |
-
# Enhanced features - Karşılaştırma kontrolü
|
707 |
comparison_result = handle_comparison_request(user_message)
|
708 |
if comparison_result:
|
709 |
yield comparison_result
|
710 |
return
|
711 |
-
|
712 |
-
# Enhanced features - Basit karşılaştırma ve görsel işleme
|
713 |
-
# Profil sistemi kaldırıldı - daha hızlı çalışma için
|
714 |
-
|
715 |
except Exception as e:
|
716 |
print(f"Enhanced features error: {e}")
|
717 |
-
# Enhanced features hata verirse normal chatbot'a devam et
|
718 |
|
719 |
-
# Log: Kullanıcı mesajını ekle
|
720 |
try:
|
721 |
with file_lock:
|
722 |
with open(LOG_FILE, 'a', encoding='utf-8') as f:
|
723 |
-
f.write(f"User: {user_message}
|
724 |
except Exception as e:
|
725 |
print(f"Dosya yazma hatası (Kullanıcı): {e}")
|
726 |
|
727 |
-
# Sistem mesajlarını external dosyadan yükle
|
728 |
system_messages = get_prompt_content_only()
|
729 |
|
730 |
-
# Döküman verilerini sistem mesajlarına ekle
|
731 |
if document_content:
|
732 |
system_messages.append({"role": "system", "content": f"Dökümanlardan gelen bilgiler: {document_content}"})
|
733 |
|
734 |
-
# Profil sistemi kaldırıldı - daha hızlı çalışma için
|
735 |
-
|
736 |
-
# Try improved search first if available
|
737 |
product_found_improved = False
|
738 |
if USE_IMPROVED_SEARCH and improved_bot:
|
739 |
try:
|
740 |
product_result = improved_bot.process_message(user_message)
|
741 |
if product_result['is_product_query'] and product_result['response']:
|
742 |
-
# Extract product name from improved search result for warehouse stock
|
743 |
enhanced_response = product_result['response']
|
744 |
-
|
745 |
-
# Önceden alınmış warehouse stock bilgisini kullan
|
746 |
if warehouse_stock_data and warehouse_stock_data != ["Hiçbir mağazada mevcut değil"]:
|
747 |
warehouse_info = f"\n\n🏪 MAĞAZA STOK BİLGİLERİ:\n"
|
748 |
for store_info in warehouse_stock_data:
|
@@ -761,11 +496,8 @@ def chatbot_fn(user_message, history, image=None):
|
|
761 |
except Exception as e:
|
762 |
print(f"Improved search error: {e}")
|
763 |
|
764 |
-
# Only use warehouse stock data if available
|
765 |
if not product_found_improved:
|
766 |
print(f"DEBUG chatbot_fn - Using warehouse stock data for: {user_message}")
|
767 |
-
|
768 |
-
# Warehouse stock bilgisi varsa kullan
|
769 |
if warehouse_stock_data and warehouse_stock_data != ["Hiçbir mağazada mevcut değil"]:
|
770 |
warehouse_info = f"🏪 MAĞAZA STOK BİLGİLERİ:\n"
|
771 |
for store_info in warehouse_stock_data:
|
@@ -787,7 +519,7 @@ def chatbot_fn(user_message, history, image=None):
|
|
787 |
messages = system_messages + history + [{"role": "user", "content": user_message}]
|
788 |
|
789 |
payload = {
|
790 |
-
"model": "gpt-
|
791 |
"messages": messages,
|
792 |
"temperature": 0.2,
|
793 |
"top_p": 1,
|
@@ -818,7 +550,6 @@ def chatbot_fn(user_message, history, image=None):
|
|
818 |
delta = chunk_data['choices'][0]['delta']
|
819 |
if 'content' in delta:
|
820 |
partial_response += delta['content']
|
821 |
-
# Resim formatlaması uygula
|
822 |
formatted_response = extract_product_info_for_gallery(partial_response)
|
823 |
yield formatted_response
|
824 |
except json.JSONDecodeError as e:
|
@@ -826,21 +557,16 @@ def chatbot_fn(user_message, history, image=None):
|
|
826 |
elif chunk_str == "data: [DONE]":
|
827 |
break
|
828 |
|
829 |
-
# Son resim formatlaması
|
830 |
final_response = extract_product_info_for_gallery(partial_response)
|
831 |
yield final_response
|
832 |
|
833 |
-
# Profil kaydetme kaldırıldı - daha hızlı çalışma için
|
834 |
-
|
835 |
-
# Log: Asistan cevabını ekle
|
836 |
try:
|
837 |
with file_lock:
|
838 |
with open(LOG_FILE, 'a', encoding='utf-8') as f:
|
839 |
-
f.write(f"Bot: {partial_response}
|
840 |
except Exception as e:
|
841 |
print(f"Dosya yazma hatası (Bot): {e}")
|
842 |
|
843 |
-
# Global geçmişi güncelle
|
844 |
with history_lock:
|
845 |
global_chat_history.append({"role": "user", "content": user_message})
|
846 |
global_chat_history.append({"role": "assistant", "content": partial_response})
|
@@ -859,219 +585,29 @@ if not USE_SLOW_ECHO:
|
|
859 |
scheduler_thread = threading.Thread(target=run_scheduler, args=(global_chat_history,), daemon=True)
|
860 |
scheduler_thread.start()
|
861 |
|
862 |
-
# Trek markasına özel tema
|
863 |
trek_theme = gr.themes.Base(
|
864 |
-
primary_hue="red",
|
865 |
-
secondary_hue="slate",
|
866 |
neutral_hue="slate",
|
867 |
-
radius_size=gr.themes.sizes.radius_sm,
|
868 |
-
spacing_size=gr.themes.sizes.spacing_md,
|
869 |
-
text_size=gr.themes.sizes.text_sm
|
870 |
)
|
871 |
-
# Chatbot kartları için arka plan renkleri değiştiren CSS
|
872 |
custom_css = """
|
873 |
-
/*
|
874 |
-
.gradio-container, .gradio-container
|
875 |
-
button, input, select, textarea {
|
876 |
-
font-family: 'Segoe UI', 'SF Pro Text', 'Roboto', -apple-system, BlinkMacSystemFont,
|
877 |
-
'Helvetica Neue', Arial, sans-serif !important;
|
878 |
-
font-size: 0.6rem !important;
|
879 |
-
}
|
880 |
-
|
881 |
-
/* Mobil responsive başlangıç */
|
882 |
-
@media (max-width: 768px) {
|
883 |
-
.gradio-container, .gradio-container *, .message-wrap, .message-wrap p, .message-wrap div {
|
884 |
-
font-size: 0.8rem !important;
|
885 |
-
}
|
886 |
-
|
887 |
-
h1 {
|
888 |
-
font-size: 1.8rem !important;
|
889 |
-
text-align: center;
|
890 |
-
margin: 10px 0;
|
891 |
-
}
|
892 |
-
|
893 |
-
h2 {
|
894 |
-
font-size: 1.4rem !important;
|
895 |
-
}
|
896 |
-
}
|
897 |
-
|
898 |
-
/* Input alanı için de aynı boyut */
|
899 |
-
.message-textbox textarea {
|
900 |
-
font-size: 0.6rem !important;
|
901 |
-
}
|
902 |
-
|
903 |
-
/* Başlıklar için özel boyutlar */
|
904 |
-
h1 {
|
905 |
-
font-size: 1.4rem !important;
|
906 |
-
font-weight: 800 !important;
|
907 |
-
}
|
908 |
-
|
909 |
-
h2 {
|
910 |
-
font-size: 1.2rem !important;
|
911 |
-
font-weight: 600 !important;
|
912 |
-
}
|
913 |
-
|
914 |
-
h3 {
|
915 |
-
font-size: 1rem !important;
|
916 |
-
font-weight: 600 !important;
|
917 |
-
}
|
918 |
-
|
919 |
-
/* Kart arka plan renkleri - görseldeki gibi */
|
920 |
-
/* Kullanıcı mesajları için mavi tonda arka plan */
|
921 |
-
.user-message, .user-message-highlighted {
|
922 |
-
background-color: #e9f5fe !important;
|
923 |
-
border-bottom-right-radius: 0 !important;
|
924 |
-
box-shadow: 0 1px 2px rgba(0,0,0,0.1) !important;
|
925 |
-
}
|
926 |
-
|
927 |
-
/* Bot mesajları için beyaz arka plan ve hafif kenarlık */
|
928 |
-
.bot-message, .bot-message-highlighted {
|
929 |
-
background-color: white !important;
|
930 |
-
border: 1px solid #e0e0e0 !important;
|
931 |
-
border-bottom-left-radius: 0 !important;
|
932 |
-
box-shadow: 0 1px 2px rgba(0,0,0,0.05) !important;
|
933 |
-
}
|
934 |
-
|
935 |
-
/* Mesaj baloncuklarının köşe yuvarlatma değerleri */
|
936 |
-
.message-wrap {
|
937 |
-
border-radius: 12px !important;
|
938 |
-
margin: 0.5rem 0 !important;
|
939 |
-
max-width: 90% !important;
|
940 |
-
}
|
941 |
-
|
942 |
-
/* Sohbet alanının genel arka planı */
|
943 |
-
.chat-container, .gradio-container {
|
944 |
-
background-color: #f7f7f7 !important;
|
945 |
-
}
|
946 |
-
|
947 |
-
/* Daha net yazılar için text rendering */
|
948 |
-
* {
|
949 |
-
-webkit-font-smoothing: antialiased;
|
950 |
-
-moz-osx-font-smoothing: grayscale;
|
951 |
-
text-rendering: optimizeLegibility;
|
952 |
-
}
|
953 |
-
|
954 |
-
/* Restore butonu stilleri kaldırıldı */
|
955 |
-
|
956 |
-
/* Responsive mobil tasarım - iOS benzeri */
|
957 |
-
@media (max-width: 768px) {
|
958 |
-
/* Daha büyük ve dokunmatik dostu boyutlar */
|
959 |
-
.gradio-container {
|
960 |
-
padding: 0 !important;
|
961 |
-
margin: 0 !important;
|
962 |
-
}
|
963 |
-
|
964 |
-
/* Mesaj baloncukları iOS tarzı */
|
965 |
-
.message-wrap {
|
966 |
-
margin: 0.3rem 0.5rem !important;
|
967 |
-
max-width: 85% !important;
|
968 |
-
border-radius: 18px !important;
|
969 |
-
padding: 10px 15px !important;
|
970 |
-
font-size: 0.9rem !important;
|
971 |
-
}
|
972 |
-
|
973 |
-
/* Kullanıcı mesajları */
|
974 |
-
.user-message, .user-message-highlighted {
|
975 |
-
background-color: #007AFF !important;
|
976 |
-
color: white !important;
|
977 |
-
margin-left: auto !important;
|
978 |
-
margin-right: 8px !important;
|
979 |
-
}
|
980 |
-
|
981 |
-
/* Bot mesajları */
|
982 |
-
.bot-message, .bot-message-highlighted {
|
983 |
-
background-color: #f1f1f1 !important;
|
984 |
-
color: #333 !important;
|
985 |
-
margin-left: 8px !important;
|
986 |
-
margin-right: auto !important;
|
987 |
-
}
|
988 |
-
}
|
989 |
-
|
990 |
-
/* Input alanına uçan kağıt ikonu ekle */
|
991 |
-
#msg-input {
|
992 |
-
position: relative;
|
993 |
-
}
|
994 |
-
|
995 |
-
#msg-input textarea {
|
996 |
-
padding-right: 40px !important;
|
997 |
-
}
|
998 |
-
|
999 |
-
.input-icon {
|
1000 |
-
position: absolute;
|
1001 |
-
right: 12px;
|
1002 |
-
top: 50%;
|
1003 |
-
transform: translateY(-50%);
|
1004 |
-
font-size: 16px;
|
1005 |
-
pointer-events: none;
|
1006 |
-
z-index: 10;
|
1007 |
-
color: #666;
|
1008 |
-
}
|
1009 |
-
|
1010 |
-
/* Mobil responsive - Input alanı */
|
1011 |
-
@media (max-width: 768px) {
|
1012 |
-
#msg-input {
|
1013 |
-
margin: 10px 0;
|
1014 |
-
}
|
1015 |
-
|
1016 |
-
#msg-input textarea {
|
1017 |
-
padding: 12px 45px 12px 15px !important;
|
1018 |
-
font-size: 1rem !important;
|
1019 |
-
border-radius: 20px !important;
|
1020 |
-
border: 1px solid #ddd !important;
|
1021 |
-
box-shadow: 0 1px 3px rgba(0,0,0,0.1) !important;
|
1022 |
-
}
|
1023 |
-
|
1024 |
-
.input-icon {
|
1025 |
-
right: 15px;
|
1026 |
-
font-size: 18px;
|
1027 |
-
}
|
1028 |
-
}
|
1029 |
-
|
1030 |
-
/* Genel mobil iyileştirmeler */
|
1031 |
-
@media (max-width: 768px) {
|
1032 |
-
/* Chatbot alanı tam ekran */
|
1033 |
-
.gradio-container .main {
|
1034 |
-
padding: 0 !important;
|
1035 |
-
}
|
1036 |
-
|
1037 |
-
/* Başlık alanı küçült */
|
1038 |
-
.gradio-container header {
|
1039 |
-
padding: 8px !important;
|
1040 |
-
}
|
1041 |
-
|
1042 |
-
/* Tab alanlarını küçült */
|
1043 |
-
.tab-nav {
|
1044 |
-
padding: 5px !important;
|
1045 |
-
}
|
1046 |
-
|
1047 |
-
/* Scroll bar'ı gizle */
|
1048 |
-
.scroll-hide {
|
1049 |
-
scrollbar-width: none;
|
1050 |
-
-ms-overflow-style: none;
|
1051 |
-
}
|
1052 |
-
|
1053 |
-
.scroll-hide::-webkit-scrollbar {
|
1054 |
-
display: none;
|
1055 |
-
}
|
1056 |
-
}
|
1057 |
-
|
1058 |
-
/* CSS Bitişi */
|
1059 |
"""
|
1060 |
|
1061 |
-
# Chat persistence sistemi tamamen kaldırıldı
|
1062 |
storage_js = ""
|
1063 |
|
1064 |
-
# Enhanced chatbot fonksiyonu image destekli
|
1065 |
def enhanced_chatbot_fn(message, history, image):
|
1066 |
return chatbot_fn(message, history, image)
|
1067 |
|
1068 |
-
# Demo arayüzü - Mobil responsive
|
1069 |
with gr.Blocks(css=custom_css, theme="soft", title="Trek Asistanı", head=storage_js) as demo:
|
1070 |
gr.Markdown("# 🚲 Trek Asistanı AI")
|
1071 |
gr.Markdown("**Akıllı özellikler:** Ürün karşılaştırması ve detaylı ürün bilgileri sunuyorum.")
|
1072 |
|
1073 |
-
# LocalStorage fonksiyonu kaldırıldı
|
1074 |
-
|
1075 |
chatbot = gr.Chatbot(height=600, elem_id="chatbot", show_label=False, type="messages")
|
1076 |
|
1077 |
msg = gr.Textbox(
|
@@ -1080,53 +616,38 @@ with gr.Blocks(css=custom_css, theme="soft", title="Trek Asistanı", head=storag
|
|
1080 |
elem_id="msg-input"
|
1081 |
)
|
1082 |
|
1083 |
-
|
1084 |
-
# Session ve profil sistemi tamamen kaldırıldı
|
1085 |
-
|
1086 |
def respond(message, chat_history):
|
1087 |
if not message.strip():
|
1088 |
return "", chat_history
|
1089 |
-
|
1090 |
-
# Kullanıcı mesajını hemen göster
|
1091 |
if chat_history is None:
|
1092 |
chat_history = []
|
1093 |
-
|
1094 |
-
# Messages format için kullanıcı mesajını ekle
|
1095 |
chat_history.append({"role": "user", "content": message})
|
1096 |
yield "", chat_history
|
1097 |
|
1098 |
-
# Chat history'yi chatbot_fn için uygun formata çevir
|
1099 |
formatted_history = []
|
1100 |
-
for
|
1101 |
-
formatted_history.append(
|
1102 |
|
1103 |
try:
|
1104 |
-
# Enhanced chatbot fonksiyonunu çağır (image=None)
|
1105 |
response_generator = chatbot_fn(message, formatted_history, None)
|
1106 |
-
|
1107 |
-
# Generator'dan streaming cevap al
|
1108 |
response = ""
|
1109 |
for partial in response_generator:
|
1110 |
response = partial
|
1111 |
-
# Bot cevabını ekle veya güncelle
|
1112 |
-
# Eğer son mesaj user ise, bot mesajı ekle
|
1113 |
if chat_history[-1]["role"] == "user":
|
1114 |
chat_history.append({"role": "assistant", "content": response})
|
1115 |
else:
|
1116 |
-
# Son bot mesajını güncelle
|
1117 |
chat_history[-1]["content"] = response
|
1118 |
yield "", chat_history
|
1119 |
|
1120 |
-
# Konuşmayı kaydet
|
1121 |
try:
|
1122 |
add_conversation(message, response)
|
|
|
1123 |
except Exception as e:
|
1124 |
print(f"Error saving conversation: {e}")
|
1125 |
|
1126 |
except Exception as e:
|
1127 |
error_msg = f"Üzgünüm, bir hata oluştu: {str(e)}"
|
1128 |
print(f"Chat error: {e}")
|
1129 |
-
# Hata mesajı ekle
|
1130 |
if chat_history[-1]["role"] == "user":
|
1131 |
chat_history.append({"role": "assistant", "content": error_msg})
|
1132 |
else:
|
@@ -1135,7 +656,6 @@ with gr.Blocks(css=custom_css, theme="soft", title="Trek Asistanı", head=storag
|
|
1135 |
|
1136 |
msg.submit(respond, [msg, chatbot], [msg, chatbot], show_progress=True)
|
1137 |
|
1138 |
-
# Add conversation viewer
|
1139 |
with gr.Accordion("📊 Konuşma Geçmişi (JSON)", open=False):
|
1140 |
with gr.Row():
|
1141 |
refresh_json_btn = gr.Button("🔄 Yenile", scale=1)
|
@@ -1148,14 +668,13 @@ with gr.Blocks(css=custom_css, theme="soft", title="Trek Asistanı", head=storag
|
|
1148 |
def get_conversations_json():
|
1149 |
from conversation_tracker import load_conversations
|
1150 |
convs = load_conversations()
|
1151 |
-
# Also save to file for download
|
1152 |
import json as json_module
|
1153 |
with open("temp_conversations.json", "w", encoding="utf-8") as f:
|
1154 |
json_module.dump(convs, f, ensure_ascii=False, indent=2)
|
1155 |
return convs
|
1156 |
|
1157 |
def download_conversations():
|
1158 |
-
get_conversations_json()
|
1159 |
return gr.update(visible=True, value="temp_conversations.json")
|
1160 |
|
1161 |
def open_dashboard():
|
@@ -1163,111 +682,56 @@ with gr.Blocks(css=custom_css, theme="soft", title="Trek Asistanı", head=storag
|
|
1163 |
<div style='padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; color: white;'>
|
1164 |
<h3>📊 Dashboard Kullanım Talimatları</h3>
|
1165 |
<ol style='margin: 15px 0;'>
|
1166 |
-
<li>
|
1167 |
-
<li>
|
1168 |
-
<li>bf_dashboard_json.html dosyasını aç</li>
|
1169 |
-
<li>İndirdiğin JSON dosyasını dashboard'a yükle</li>
|
1170 |
</ol>
|
1171 |
-
<p style='margin-top: 15px; font-size: 14px;'>
|
1172 |
-
Dashboard HTML dosyasını almak için:<br>
|
1173 |
-
<a href='https://github.com/yourusername/bf-dashboard' target='_blank' style='color: white; text-decoration: underline;'>
|
1174 |
-
GitHub'dan indir
|
1175 |
-
</a>
|
1176 |
-
</p>
|
1177 |
</div>
|
1178 |
""")
|
1179 |
|
1180 |
refresh_json_btn.click(get_conversations_json, outputs=json_display)
|
1181 |
-
|
|
|
1182 |
view_dashboard_btn.click(open_dashboard, outputs=json_display)
|
1183 |
-
|
1184 |
-
# Auto-load on start
|
1185 |
demo.load(get_conversations_json, outputs=json_display)
|
1186 |
-
|
1187 |
-
# Define function for JSON HTML before using it
|
1188 |
-
def create_json_html():
|
1189 |
-
"""Create HTML with embedded JSON data"""
|
1190 |
-
from conversation_tracker import load_conversations
|
1191 |
-
import json
|
1192 |
-
conversations = load_conversations()
|
1193 |
-
json_str = json.dumps(conversations, ensure_ascii=False)
|
1194 |
-
return f'''
|
1195 |
-
<div id="json-data" style="display:none;">
|
1196 |
-
<script type="application/json" id="conversations-json">
|
1197 |
-
{json_str}
|
1198 |
-
</script>
|
1199 |
-
</div>
|
1200 |
-
'''
|
1201 |
-
|
1202 |
-
# Add HTML component with embedded JSON for external access
|
1203 |
-
with gr.Row(visible=False):
|
1204 |
-
# Create an HTML element that contains the JSON data
|
1205 |
-
json_html = gr.HTML(
|
1206 |
-
value=create_json_html(), # Call directly, not as lambda
|
1207 |
-
visible=False,
|
1208 |
-
elem_id="json_data_container"
|
1209 |
-
)
|
1210 |
-
|
1211 |
-
# Update JSON HTML on page load
|
1212 |
-
demo.load(create_json_html, outputs=json_html)
|
1213 |
-
|
1214 |
-
# API endpoints for dashboard
|
1215 |
-
def get_all_conversations():
|
1216 |
-
"""API endpoint to get all conversations"""
|
1217 |
-
from conversation_tracker import load_conversations
|
1218 |
-
conversations = load_conversations()
|
1219 |
-
|
1220 |
-
# Format for dashboard
|
1221 |
-
formatted = {}
|
1222 |
-
for conv in conversations:
|
1223 |
-
session_id = f"session_{conv['timestamp'].replace(':', '').replace('-', '').replace('T', '_')[:15]}"
|
1224 |
-
|
1225 |
-
if session_id not in formatted:
|
1226 |
-
formatted[session_id] = {
|
1227 |
-
"customer": "Kullanıcı",
|
1228 |
-
"phone": session_id,
|
1229 |
-
"messages": []
|
1230 |
-
}
|
1231 |
-
|
1232 |
-
formatted[session_id]["messages"].append({
|
1233 |
-
"type": "received",
|
1234 |
-
"text": conv["user"],
|
1235 |
-
"time": conv["timestamp"]
|
1236 |
-
})
|
1237 |
-
|
1238 |
-
formatted[session_id]["messages"].append({
|
1239 |
-
"type": "sent",
|
1240 |
-
"text": conv["bot"],
|
1241 |
-
"time": conv["timestamp"]
|
1242 |
-
})
|
1243 |
-
|
1244 |
-
return formatted
|
1245 |
-
|
1246 |
-
def get_conversation_stats():
|
1247 |
-
"""Get conversation statistics"""
|
1248 |
-
from conversation_tracker import load_conversations
|
1249 |
-
from datetime import datetime
|
1250 |
-
|
1251 |
-
conversations = load_conversations()
|
1252 |
-
today = datetime.now().date()
|
1253 |
-
today_count = sum(1 for conv in conversations
|
1254 |
-
if datetime.fromisoformat(conv["timestamp"]).date() == today)
|
1255 |
-
|
1256 |
-
return {
|
1257 |
-
"total": len(conversations),
|
1258 |
-
"today": today_count,
|
1259 |
-
"active": len(set(conv.get("session_id", i) for i, conv in enumerate(conversations)))
|
1260 |
-
}
|
1261 |
|
1262 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1263 |
|
1264 |
-
#
|
1265 |
-
|
1266 |
-
# But we need to ensure it's served with proper content type
|
1267 |
|
1268 |
if __name__ == "__main__":
|
1269 |
-
#
|
1270 |
-
|
1271 |
-
debug=True,
|
1272 |
-
allowed_paths=["conversations.json", "public/conversations.json"]
|
1273 |
-
)
|
|
|
15 |
from googleapiclient.discovery import build
|
16 |
from googleapiclient.http import MediaIoBaseDownload
|
17 |
import io
|
|
|
18 |
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
19 |
warnings.simplefilter('ignore', InsecureRequestWarning)
|
20 |
|
|
|
31 |
# Import conversation tracker
|
32 |
from conversation_tracker import add_conversation
|
33 |
|
34 |
+
# === JSON dashboard için yardımcılar ===
|
35 |
+
def save_conversations_json():
|
36 |
+
"""
|
37 |
+
conversation_tracker.load_conversations() çıktısını
|
38 |
+
hem köke hem public/ altına yazar. Dashboard bunları aynı origin'den çeker
|
39 |
+
ya da standalone HTML CORS açık /api uçlarından alır.
|
40 |
+
"""
|
41 |
+
try:
|
42 |
+
from conversation_tracker import load_conversations
|
43 |
+
convs = load_conversations()
|
44 |
+
# kök
|
45 |
+
with open("conversations.json", "w", encoding="utf-8") as f:
|
46 |
+
json.dump(convs, f, ensure_ascii=False, indent=2)
|
47 |
+
# public/
|
48 |
+
os.makedirs("public", exist_ok=True)
|
49 |
+
with open("public/conversations.json", "w", encoding="utf-8") as f:
|
50 |
+
json.dump(convs, f, ensure_ascii=False, indent=2)
|
51 |
+
print("conversations.json ve public/conversations.json güncellendi.")
|
52 |
+
except Exception as e:
|
53 |
+
print(f"conversations.json yazma hatası: {e}")
|
54 |
|
55 |
# Import smart warehouse with GPT intelligence and price
|
56 |
try:
|
|
|
78 |
"""Smart warehouse stock finder with general algorithm"""
|
79 |
try:
|
80 |
import re
|
|
|
81 |
|
82 |
# Get XML with retry
|
83 |
xml_text = None
|
|
|
115 |
|
116 |
# If query is very short (like "hangi boyu"), skip it
|
117 |
if len(words) <= 2 and not any(w.isdigit() for w in words):
|
|
|
118 |
pass
|
119 |
else:
|
|
|
120 |
for word in words:
|
|
|
121 |
if word in sizes:
|
122 |
continue
|
|
|
|
|
123 |
if word.isdigit():
|
124 |
product_words.append(word)
|
|
|
|
|
125 |
elif any(c.isdigit() for c in word) and any(c.isalpha() for c in word):
|
126 |
product_words.append(word)
|
|
|
|
|
127 |
elif len(word) in [2, 3] and word.isalpha():
|
|
|
128 |
if word in ['mi', 'mı', 'mu', 'mü', 'var', 'yok', 've', 'de', 'da']:
|
129 |
continue
|
|
|
130 |
if any(c not in 'aeiouı' for c in word):
|
131 |
product_words.append(word)
|
|
|
|
|
132 |
elif len(word) > 3:
|
|
|
133 |
if any(word.endswith(suffix) for suffix in ['mi', 'mı', 'mu', 'mü']):
|
134 |
continue
|
|
|
135 |
consonants = sum(1 for c in word if c not in 'aeiouı')
|
136 |
+
if consonants <= 2:
|
137 |
continue
|
|
|
138 |
product_words.append(word)
|
139 |
|
140 |
print(f"DEBUG - Searching: {' '.join(product_words)}, Size: {size}")
|
|
|
145 |
|
146 |
print(f"DEBUG - Total products in XML: {len(all_products)}")
|
147 |
|
|
|
148 |
best_match = None
|
|
|
149 |
for product_block in all_products:
|
|
|
150 |
name_match = re.search(r'<ProductName><!\[CDATA\[(.*?)\]\]></ProductName>', product_block)
|
151 |
if not name_match:
|
152 |
continue
|
|
|
154 |
product_name_in_xml = name_match.group(1)
|
155 |
normalized_xml_name = normalize(product_name_in_xml)
|
156 |
|
|
|
|
|
157 |
match = True
|
158 |
for word in product_words:
|
|
|
159 |
if word not in normalized_xml_name:
|
|
|
160 |
if not (word.isdigit() and any(f"{prev}{word}" in normalized_xml_name or f"{prev} {word}" in normalized_xml_name for prev in product_words if not prev.isdigit())):
|
161 |
match = False
|
162 |
break
|
163 |
|
164 |
if match:
|
|
|
|
|
165 |
if size:
|
|
|
166 |
variant_match = re.search(r'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', product_block)
|
167 |
if variant_match:
|
168 |
variant = variant_match.group(1)
|
|
|
169 |
if variant.upper().startswith(f'{size.upper()}-'):
|
170 |
print(f"DEBUG - Found match: {product_name_in_xml} - {variant}")
|
171 |
best_match = product_block
|
172 |
+
break
|
173 |
else:
|
|
|
174 |
best_match = product_block
|
175 |
break
|
176 |
|
177 |
if best_match:
|
|
|
178 |
warehouse_info = []
|
179 |
warehouse_regex = r'<Warehouse>.*?<Name><!\[CDATA\[(.*?)\]\]></Name>.*?<Stock>(.*?)</Stock>.*?</Warehouse>'
|
180 |
warehouses = re.findall(warehouse_regex, best_match, re.DOTALL)
|
|
|
183 |
try:
|
184 |
stock = int(wh_stock.strip())
|
185 |
if stock > 0:
|
|
|
186 |
if "CADDEBOSTAN" in wh_name:
|
187 |
display = "Caddebostan mağazası"
|
188 |
elif "ORTAKÖY" in wh_name:
|
|
|
193 |
display = "Bahçeköy mağazası"
|
194 |
else:
|
195 |
display = wh_name
|
|
|
196 |
warehouse_info.append(f"{display}: Mevcut")
|
197 |
except:
|
198 |
pass
|
|
|
206 |
print(f"Warehouse error: {e}")
|
207 |
return None
|
208 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
# Import improved product search
|
210 |
try:
|
211 |
from improved_chatbot import ImprovedChatbot
|
|
|
240 |
products = []
|
241 |
if root is not None:
|
242 |
for item in root.findall('item'):
|
|
|
243 |
rootlabel_elem = item.find('rootlabel')
|
244 |
stock_elem = item.find('stockAmount')
|
|
|
245 |
if rootlabel_elem is None or stock_elem is None:
|
246 |
+
continue
|
247 |
|
248 |
name_words = rootlabel_elem.text.lower().split()
|
249 |
name = name_words[0]
|
|
|
251 |
|
252 |
stock_amount = "stokta" if stock_elem.text and stock_elem.text > '0' else "stokta değil"
|
253 |
|
|
|
254 |
if stock_amount == "stokta":
|
|
|
255 |
price_elem = item.find('priceTaxWithCur')
|
256 |
price_str = price_elem.text if price_elem is not None and price_elem.text else "Fiyat bilgisi yok"
|
|
|
|
|
257 |
price_eft_elem = item.find('priceEft')
|
258 |
price_eft_str = price_eft_elem.text if price_eft_elem is not None and price_eft_elem.text else ""
|
|
|
|
|
259 |
price_rebate_elem = item.find('priceRebateWithTax')
|
260 |
price_rebate_str = price_rebate_elem.text if price_rebate_elem is not None and price_rebate_elem.text else ""
|
|
|
|
|
261 |
price_rebate_money_order_elem = item.find('priceRebateWithMoneyOrderWithTax')
|
262 |
price_rebate_money_order_str = price_rebate_money_order_elem.text if price_rebate_money_order_elem is not None and price_rebate_money_order_elem.text else ""
|
263 |
|
|
|
264 |
try:
|
265 |
price_float = float(price_str)
|
|
|
266 |
if price_float > 200000:
|
267 |
+
price = str(round(price_float / 5000) * 5000)
|
|
|
268 |
elif price_float > 30000:
|
269 |
+
price = str(round(price_float / 1000) * 1000)
|
|
|
270 |
elif price_float > 10000:
|
271 |
+
price = str(round(price_float / 100) * 100)
|
|
|
272 |
else:
|
273 |
+
price = str(round(price_float / 10) * 10)
|
274 |
except (ValueError, TypeError):
|
275 |
+
price = price_str
|
276 |
|
|
|
277 |
if price_eft_str:
|
278 |
try:
|
279 |
price_eft_float = float(price_eft_str)
|
|
|
280 |
if price_eft_float > 200000:
|
281 |
price_eft = str(round(price_eft_float / 5000) * 5000)
|
|
|
282 |
elif price_eft_float > 30000:
|
283 |
price_eft = str(round(price_eft_float / 1000) * 1000)
|
|
|
284 |
elif price_eft_float > 10000:
|
285 |
price_eft = str(round(price_eft_float / 100) * 100)
|
|
|
286 |
else:
|
287 |
price_eft = str(round(price_eft_float / 10) * 10)
|
288 |
except (ValueError, TypeError):
|
|
|
290 |
else:
|
291 |
price_eft = ""
|
292 |
|
|
|
293 |
if price_rebate_str:
|
294 |
try:
|
295 |
price_rebate_float = float(price_rebate_str)
|
|
|
296 |
if price_rebate_float > 200000:
|
297 |
price_rebate = str(round(price_rebate_float / 5000) * 5000)
|
|
|
298 |
elif price_rebate_float > 30000:
|
299 |
price_rebate = str(round(price_rebate_float / 1000) * 1000)
|
|
|
300 |
elif price_rebate_float > 10000:
|
301 |
price_rebate = str(round(price_rebate_float / 100) * 100)
|
|
|
302 |
else:
|
303 |
price_rebate = str(round(price_rebate_float / 10) * 10)
|
304 |
except (ValueError, TypeError):
|
|
|
306 |
else:
|
307 |
price_rebate = ""
|
308 |
|
|
|
309 |
if price_rebate_money_order_str:
|
310 |
try:
|
311 |
price_rebate_money_order_float = float(price_rebate_money_order_str)
|
|
|
312 |
if price_rebate_money_order_float > 200000:
|
313 |
price_rebate_money_order = str(round(price_rebate_money_order_float / 5000) * 5000)
|
|
|
314 |
elif price_rebate_money_order_float > 30000:
|
315 |
price_rebate_money_order = str(round(price_rebate_money_order_float / 1000) * 1000)
|
|
|
316 |
elif price_rebate_money_order_float > 10000:
|
317 |
price_rebate_money_order = str(round(price_rebate_money_order_float / 100) * 100)
|
|
|
318 |
else:
|
319 |
price_rebate_money_order = str(round(price_rebate_money_order_float / 10) * 10)
|
320 |
except (ValueError, TypeError):
|
|
|
322 |
else:
|
323 |
price_rebate_money_order = ""
|
324 |
|
|
|
325 |
product_url_elem = item.find('productLink')
|
326 |
product_url = product_url_elem.text if product_url_elem is not None and product_url_elem.text else ""
|
327 |
product_info = [stock_amount, price, product_url, price_eft, price_rebate, price_rebate_money_order]
|
328 |
|
|
|
329 |
image_elem = item.find('picture1Path')
|
330 |
if image_elem is not None and image_elem.text:
|
331 |
product_info.append(image_elem.text)
|
332 |
else:
|
333 |
+
product_info.append("")
|
|
|
334 |
else:
|
|
|
335 |
product_info = [stock_amount]
|
336 |
|
337 |
products.append((name, product_info, full_name))
|
|
|
341 |
# Initialize enhanced features
|
342 |
initialize_enhanced_features(OPENAI_API_KEY, products)
|
343 |
|
344 |
+
# İlk başta dosyaları oluştur (boşsa)
|
345 |
+
save_conversations_json()
|
346 |
+
|
347 |
# Initialize improved chatbot if available
|
348 |
improved_bot = None
|
349 |
if USE_IMPROVED_SEARCH:
|
|
|
376 |
credentials = Credentials.from_service_account_file(GOOGLE_CREDENTIALS_PATH)
|
377 |
service = build('drive', 'v3', credentials=credentials)
|
378 |
|
|
|
379 |
results = service.files().list(
|
380 |
q=f"'{GOOGLE_FOLDER_ID}' in parents",
|
381 |
fields="files(id, name, mimeType)"
|
|
|
386 |
|
387 |
for file in files:
|
388 |
print(f"İndiriliyor: {file['name']}")
|
|
|
|
|
389 |
if file['mimeType'] == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
390 |
request = service.files().get_media(fileId=file['id'])
|
391 |
file_io = io.BytesIO()
|
|
|
398 |
file_io.seek(0)
|
399 |
doc = Document(file_io)
|
400 |
|
401 |
+
content = f"\n=== {file['name']} ===\n"
|
402 |
for paragraph in doc.paragraphs:
|
403 |
if paragraph.text.strip():
|
404 |
+
content += paragraph.text + "\n"
|
405 |
|
406 |
all_content.append(content)
|
407 |
|
408 |
+
document_content = "\n".join(all_content)
|
409 |
print(f"Toplam {len(files)} döküman yüklendi.")
|
410 |
|
411 |
except Exception as e:
|
|
|
421 |
if os.path.exists(LOG_FILE):
|
422 |
with file_lock:
|
423 |
with open(LOG_FILE, 'w', encoding='utf-8') as f:
|
424 |
+
f.write("Log dosyası temizlendi.\n")
|
425 |
print("Log dosyası temizlendi.")
|
426 |
except Exception as e:
|
427 |
print(f"Log dosyası temizleme hatası: {e}")
|
|
|
429 |
# Zamanlanmış görevleri çalıştırma fonksiyonu
|
430 |
def run_scheduler(chat_history):
|
431 |
schedule.every().day.at("03:00").do(clear_log_file)
|
|
|
432 |
while True:
|
433 |
schedule.run_pending()
|
434 |
time.sleep(60)
|
|
|
438 |
if history is None:
|
439 |
history = []
|
440 |
|
|
|
441 |
warehouse_stock_data = None
|
442 |
print(f"DEBUG - Getting warehouse stock FIRST for: {user_message}")
|
443 |
try:
|
444 |
warehouse_stock_data = get_warehouse_stock(user_message)
|
445 |
if warehouse_stock_data:
|
446 |
+
print(f"DEBUG - Warehouse stock found: {warehouse_stock_data[:2]}...")
|
447 |
else:
|
448 |
print(f"DEBUG - No warehouse stock data returned")
|
449 |
except Exception as e:
|
450 |
print(f"DEBUG - Warehouse stock error at start: {e}")
|
451 |
|
452 |
try:
|
|
|
453 |
if image is not None:
|
454 |
user_message = process_image_message(image, user_message)
|
455 |
|
|
|
456 |
comparison_result = handle_comparison_request(user_message)
|
457 |
if comparison_result:
|
458 |
yield comparison_result
|
459 |
return
|
|
|
|
|
|
|
|
|
460 |
except Exception as e:
|
461 |
print(f"Enhanced features error: {e}")
|
|
|
462 |
|
|
|
463 |
try:
|
464 |
with file_lock:
|
465 |
with open(LOG_FILE, 'a', encoding='utf-8') as f:
|
466 |
+
f.write(f"User: {user_message}\n")
|
467 |
except Exception as e:
|
468 |
print(f"Dosya yazma hatası (Kullanıcı): {e}")
|
469 |
|
|
|
470 |
system_messages = get_prompt_content_only()
|
471 |
|
|
|
472 |
if document_content:
|
473 |
system_messages.append({"role": "system", "content": f"Dökümanlardan gelen bilgiler: {document_content}"})
|
474 |
|
|
|
|
|
|
|
475 |
product_found_improved = False
|
476 |
if USE_IMPROVED_SEARCH and improved_bot:
|
477 |
try:
|
478 |
product_result = improved_bot.process_message(user_message)
|
479 |
if product_result['is_product_query'] and product_result['response']:
|
|
|
480 |
enhanced_response = product_result['response']
|
|
|
|
|
481 |
if warehouse_stock_data and warehouse_stock_data != ["Hiçbir mağazada mevcut değil"]:
|
482 |
warehouse_info = f"\n\n🏪 MAĞAZA STOK BİLGİLERİ:\n"
|
483 |
for store_info in warehouse_stock_data:
|
|
|
496 |
except Exception as e:
|
497 |
print(f"Improved search error: {e}")
|
498 |
|
|
|
499 |
if not product_found_improved:
|
500 |
print(f"DEBUG chatbot_fn - Using warehouse stock data for: {user_message}")
|
|
|
|
|
501 |
if warehouse_stock_data and warehouse_stock_data != ["Hiçbir mağazada mevcut değil"]:
|
502 |
warehouse_info = f"🏪 MAĞAZA STOK BİLGİLERİ:\n"
|
503 |
for store_info in warehouse_stock_data:
|
|
|
519 |
messages = system_messages + history + [{"role": "user", "content": user_message}]
|
520 |
|
521 |
payload = {
|
522 |
+
"model": "gpt-4.1",
|
523 |
"messages": messages,
|
524 |
"temperature": 0.2,
|
525 |
"top_p": 1,
|
|
|
550 |
delta = chunk_data['choices'][0]['delta']
|
551 |
if 'content' in delta:
|
552 |
partial_response += delta['content']
|
|
|
553 |
formatted_response = extract_product_info_for_gallery(partial_response)
|
554 |
yield formatted_response
|
555 |
except json.JSONDecodeError as e:
|
|
|
557 |
elif chunk_str == "data: [DONE]":
|
558 |
break
|
559 |
|
|
|
560 |
final_response = extract_product_info_for_gallery(partial_response)
|
561 |
yield final_response
|
562 |
|
|
|
|
|
|
|
563 |
try:
|
564 |
with file_lock:
|
565 |
with open(LOG_FILE, 'a', encoding='utf-8') as f:
|
566 |
+
f.write(f"Bot: {partial_response}\n")
|
567 |
except Exception as e:
|
568 |
print(f"Dosya yazma hatası (Bot): {e}")
|
569 |
|
|
|
570 |
with history_lock:
|
571 |
global_chat_history.append({"role": "user", "content": user_message})
|
572 |
global_chat_history.append({"role": "assistant", "content": partial_response})
|
|
|
585 |
scheduler_thread = threading.Thread(target=run_scheduler, args=(global_chat_history,), daemon=True)
|
586 |
scheduler_thread.start()
|
587 |
|
588 |
+
# Trek markasına özel tema (kısaltılmış)
|
589 |
trek_theme = gr.themes.Base(
|
590 |
+
primary_hue="red",
|
591 |
+
secondary_hue="slate",
|
592 |
neutral_hue="slate",
|
593 |
+
radius_size=gr.themes.sizes.radius_sm,
|
594 |
+
spacing_size=gr.themes.sizes.spacing_md,
|
595 |
+
text_size=gr.themes.sizes.text_sm
|
596 |
)
|
|
|
597 |
custom_css = """
|
598 |
+
/* temel stiller (kısaltılmış) */
|
599 |
+
.gradio-container, .gradio-container * { font-family: 'Segoe UI', 'SF Pro Text', 'Roboto', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif !important; font-size: 0.6rem !important; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
600 |
"""
|
601 |
|
|
|
602 |
storage_js = ""
|
603 |
|
|
|
604 |
def enhanced_chatbot_fn(message, history, image):
|
605 |
return chatbot_fn(message, history, image)
|
606 |
|
|
|
607 |
with gr.Blocks(css=custom_css, theme="soft", title="Trek Asistanı", head=storage_js) as demo:
|
608 |
gr.Markdown("# 🚲 Trek Asistanı AI")
|
609 |
gr.Markdown("**Akıllı özellikler:** Ürün karşılaştırması ve detaylı ürün bilgileri sunuyorum.")
|
610 |
|
|
|
|
|
611 |
chatbot = gr.Chatbot(height=600, elem_id="chatbot", show_label=False, type="messages")
|
612 |
|
613 |
msg = gr.Textbox(
|
|
|
616 |
elem_id="msg-input"
|
617 |
)
|
618 |
|
|
|
|
|
|
|
619 |
def respond(message, chat_history):
|
620 |
if not message.strip():
|
621 |
return "", chat_history
|
|
|
|
|
622 |
if chat_history is None:
|
623 |
chat_history = []
|
|
|
|
|
624 |
chat_history.append({"role": "user", "content": message})
|
625 |
yield "", chat_history
|
626 |
|
|
|
627 |
formatted_history = []
|
628 |
+
for m in chat_history[:-1]:
|
629 |
+
formatted_history.append(m)
|
630 |
|
631 |
try:
|
|
|
632 |
response_generator = chatbot_fn(message, formatted_history, None)
|
|
|
|
|
633 |
response = ""
|
634 |
for partial in response_generator:
|
635 |
response = partial
|
|
|
|
|
636 |
if chat_history[-1]["role"] == "user":
|
637 |
chat_history.append({"role": "assistant", "content": response})
|
638 |
else:
|
|
|
639 |
chat_history[-1]["content"] = response
|
640 |
yield "", chat_history
|
641 |
|
|
|
642 |
try:
|
643 |
add_conversation(message, response)
|
644 |
+
save_conversations_json() # JSON’u Space kökü ve public/ altına yaz
|
645 |
except Exception as e:
|
646 |
print(f"Error saving conversation: {e}")
|
647 |
|
648 |
except Exception as e:
|
649 |
error_msg = f"Üzgünüm, bir hata oluştu: {str(e)}"
|
650 |
print(f"Chat error: {e}")
|
|
|
651 |
if chat_history[-1]["role"] == "user":
|
652 |
chat_history.append({"role": "assistant", "content": error_msg})
|
653 |
else:
|
|
|
656 |
|
657 |
msg.submit(respond, [msg, chatbot], [msg, chatbot], show_progress=True)
|
658 |
|
|
|
659 |
with gr.Accordion("📊 Konuşma Geçmişi (JSON)", open=False):
|
660 |
with gr.Row():
|
661 |
refresh_json_btn = gr.Button("🔄 Yenile", scale=1)
|
|
|
668 |
def get_conversations_json():
|
669 |
from conversation_tracker import load_conversations
|
670 |
convs = load_conversations()
|
|
|
671 |
import json as json_module
|
672 |
with open("temp_conversations.json", "w", encoding="utf-8") as f:
|
673 |
json_module.dump(convs, f, ensure_ascii=False, indent=2)
|
674 |
return convs
|
675 |
|
676 |
def download_conversations():
|
677 |
+
get_conversations_json()
|
678 |
return gr.update(visible=True, value="temp_conversations.json")
|
679 |
|
680 |
def open_dashboard():
|
|
|
682 |
<div style='padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; color: white;'>
|
683 |
<h3>📊 Dashboard Kullanım Talimatları</h3>
|
684 |
<ol style='margin: 15px 0;'>
|
685 |
+
<li>Standalone HTML'i (bf_dashboard_auto.html) bilgisayarında aç</li>
|
686 |
+
<li>HTML, Space'teki CORS açık API'den JSON'u otomatik çekecek</li>
|
|
|
|
|
687 |
</ol>
|
|
|
|
|
|
|
|
|
|
|
|
|
688 |
</div>
|
689 |
""")
|
690 |
|
691 |
refresh_json_btn.click(get_conversations_json, outputs=json_display)
|
692 |
+
download_conversations_btn = download_json_btn
|
693 |
+
download_conversations_btn.click(download_conversations, outputs=download_file)
|
694 |
view_dashboard_btn.click(open_dashboard, outputs=json_display)
|
|
|
|
|
695 |
demo.load(get_conversations_json, outputs=json_display)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
696 |
|
697 |
+
# -------------------------
|
698 |
+
# CORS açık API + Gradio mount
|
699 |
+
# -------------------------
|
700 |
+
from fastapi import FastAPI, Response
|
701 |
+
from fastapi.middleware.cors import CORSMiddleware
|
702 |
+
import uvicorn
|
703 |
+
|
704 |
+
api = FastAPI()
|
705 |
+
api.add_middleware(
|
706 |
+
CORSMiddleware,
|
707 |
+
allow_origins=["*"], # Standalone HTML için tüm originlere izin
|
708 |
+
allow_credentials=False,
|
709 |
+
allow_methods=["GET"],
|
710 |
+
allow_headers=["*"],
|
711 |
+
)
|
712 |
+
|
713 |
+
def _read_json_safely(path):
|
714 |
+
try:
|
715 |
+
with open(path, "r", encoding="utf-8") as f:
|
716 |
+
return f.read(), 200
|
717 |
+
except FileNotFoundError:
|
718 |
+
return json.dumps([]), 200
|
719 |
+
except Exception as e:
|
720 |
+
return json.dumps({"error": str(e)}), 500
|
721 |
+
|
722 |
+
@api.get("/api/conversations")
|
723 |
+
def api_conversations():
|
724 |
+
body, code = _read_json_safely("conversations.json")
|
725 |
+
return Response(content=body, media_type="application/json", status_code=code)
|
726 |
+
|
727 |
+
@api.get("/api/public/conversations")
|
728 |
+
def api_public_conversations():
|
729 |
+
body, code = _read_json_safely("public/conversations.json")
|
730 |
+
return Response(content=body, media_type="application/json", status_code=code)
|
731 |
|
732 |
+
# Gradio arayüzünü FastAPI altına mount et
|
733 |
+
app = gr.mount_gradio_app(api, demo, path="/")
|
|
|
734 |
|
735 |
if __name__ == "__main__":
|
736 |
+
# Space ortamında da çalışır
|
737 |
+
uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", "7860")))
|
|
|
|
|
|