import gradio as gr import os import json import requests import xml.etree.ElementTree as ET import schedule import time import threading from huggingface_hub import HfApi, create_repo, hf_hub_download import warnings import pandas as pd from docx import Document import spaces from google.oauth2.service_account import Credentials from googleapiclient.discovery import build from googleapiclient.http import MediaIoBaseDownload import io import warnings from requests.packages.urllib3.exceptions import InsecureRequestWarning warnings.simplefilter('ignore', InsecureRequestWarning) # Prompt dosyasını import et from prompts import get_prompt_content_only # Enhanced features import et (sadece temel özellikler) from enhanced_features import ( initialize_enhanced_features, process_image_message, handle_comparison_request ) from image_renderer import extract_product_info_for_gallery, format_message_with_images # Import conversation tracker from conversation_tracker import add_conversation # API endpoints removed - will use Gradio's built-in API # Import smart warehouse with GPT intelligence and price try: from smart_warehouse_with_price import get_warehouse_stock_smart_with_price get_warehouse_stock_smart = get_warehouse_stock_smart_with_price except ImportError: try: from smart_warehouse import get_warehouse_stock_smart except ImportError: get_warehouse_stock_smart = None def get_warehouse_stock(product_name): """Use GPT intelligence to find warehouse stock""" # First try GPT-powered search if get_warehouse_stock_smart: result = get_warehouse_stock_smart(product_name) if result: return result # Fallback to old method return get_warehouse_stock_old(product_name) # OLD warehouse stock finder - general algorithm def get_warehouse_stock_old(product_name): """Smart warehouse stock finder with general algorithm""" try: import re import requests # Get XML with retry xml_text = None for attempt in range(3): try: url = 'https://video.trek-turkey.com/bizimhesap-warehouse-xml-b2b-api-v2.php' timeout_val = 10 + (attempt * 5) # 10, 15, 20 seconds response = requests.get(url, verify=False, timeout=timeout_val) xml_text = response.text break except requests.exceptions.Timeout: if attempt == 2: return None except Exception: return None # Turkish normalize def normalize(text): tr_map = {'ı': 'i', 'ğ': 'g', 'ü': 'u', 'ş': 's', 'ö': 'o', 'ç': 'c', 'İ': 'i', 'I': 'i'} text = text.lower() for tr, en in tr_map.items(): text = text.replace(tr, en) return text # Parse query query = normalize(product_name.strip()).replace('(2026)', '').replace('(2025)', '').strip() words = query.split() # Find size markers (S, M, L, etc.) sizes = ['s', 'm', 'l', 'xl', 'xs', 'xxl', 'ml'] size = next((w for w in words if w in sizes), None) # Smart filtering: Keep only meaningful product identifiers product_words = [] # If query is very short (like "hangi boyu"), skip it if len(words) <= 2 and not any(w.isdigit() for w in words): # Likely just a question, not a product search pass else: # Extract product-like terms for word in words: # Skip if it's a size marker if word in sizes: continue # Always keep numbers (model numbers) if word.isdigit(): product_words.append(word) # Keep alphanumeric codes elif any(c.isdigit() for c in word) and any(c.isalpha() for c in word): product_words.append(word) # Keep 2-3 letter codes that look like product codes elif len(word) in [2, 3] and word.isalpha(): # Skip common particles if word in ['mi', 'mı', 'mu', 'mü', 'var', 'yok', 've', 'de', 'da']: continue # Must have at least one consonant if any(c not in 'aeiouı' for c in word): product_words.append(word) # For longer words, be selective elif len(word) > 3: # Skip if ends with Turkish question suffixes if any(word.endswith(suffix) for suffix in ['mi', 'mı', 'mu', 'mü']): continue # Skip if only 1-2 consonants (likely a particle/question word) consonants = sum(1 for c in word if c not in 'aeiouı') if consonants <= 2: # "var" has 2 consonants, skip it continue # Keep it product_words.append(word) print(f"DEBUG - Searching: {' '.join(product_words)}, Size: {size}") # Find all Product blocks in XML product_pattern = r'(.*?)' all_products = re.findall(product_pattern, xml_text, re.DOTALL) print(f"DEBUG - Total products in XML: {len(all_products)}") # Search through products best_match = None for product_block in all_products: # Extract product name name_match = re.search(r'', product_block) if not name_match: continue product_name_in_xml = name_match.group(1) normalized_xml_name = normalize(product_name_in_xml) # Check if all product words are in the name (as separate words or part of words) # This handles cases like "gen 8" where it might be "gen8" or "gen 8" in XML match = True for word in product_words: # Check if word exists as-is or without spaces (for numbers after text) if word not in normalized_xml_name: # Also check if it's a number that might be attached to previous word 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())): match = False break if match: # Product name matches, now check variant if needed if size: # Check if variant matches the size variant_match = re.search(r'', product_block) if variant_match: variant = variant_match.group(1) # Check if variant starts with the size (e.g., "S-BEYAZ") if variant.upper().startswith(f'{size.upper()}-'): print(f"DEBUG - Found match: {product_name_in_xml} - {variant}") best_match = product_block break # Found exact match, stop searching else: # No size specified, take first match best_match = product_block break if best_match: # Extract warehouse info from the matched product warehouse_info = [] warehouse_regex = r'.*?.*?(.*?).*?' warehouses = re.findall(warehouse_regex, best_match, re.DOTALL) for wh_name, wh_stock in warehouses: try: stock = int(wh_stock.strip()) if stock > 0: # Format store names if "CADDEBOSTAN" in wh_name: display = "Caddebostan mağazası" elif "ORTAKÖY" in wh_name: display = "Ortaköy mağazası" elif "ALSANCAK" in wh_name: display = "İzmir Alsancak mağazası" elif "BAHCEKOY" in wh_name or "BAHÇEKÖY" in wh_name: display = "Bahçeköy mağazası" else: display = wh_name warehouse_info.append(f"{display}: Mevcut") except: pass return warehouse_info if warehouse_info else ["Hiçbir mağazada mevcut değil"] else: print(f"DEBUG - No match found for {' '.join(product_words)}") return ["Hiçbir mağazada mevcut değil"] except Exception as e: print(f"Warehouse error: {e}") return None # OLD SLOW VERSION - KEEP FOR REFERENCE def get_warehouse_stock_old_slow(product_name): """B2B API'den mağaza stok bilgilerini çek - Optimize edilmiş versiyon""" try: import re # Hugging Face'de signal çalışmadığı için try-except kullan use_signal = False try: import signal import threading # Test if we're in main thread if threading.current_thread() is threading.main_thread(): def timeout_handler(signum, frame): raise TimeoutError("Warehouse API timeout") signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(8) use_signal = True except Exception as e: print(f"Signal not available: {e}") use_signal = False try: warehouse_url = 'https://video.trek-turkey.com/bizimhesap-warehouse-xml-b2b-api-v2.php' response = requests.get(warehouse_url, verify=False, timeout=7) # Increased from 2 if response.status_code != 200: return None # ULTRA FAST: Use regex instead of XML parsing for speed xml_text = response.text # Turkish character normalization function turkish_map = {'ı': 'i', 'ğ': 'g', 'ü': 'u', 'ş': 's', 'ö': 'o', 'ç': 'c', 'İ': 'i', 'I': 'i'} def normalize_turkish(text): import unicodedata # First normalize unicode to handle combining characters text = unicodedata.normalize('NFKD', text) text = ''.join(char for char in text if unicodedata.category(char) != 'Mn') # Replace Turkish characters for tr_char, en_char in turkish_map.items(): text = text.replace(tr_char, en_char) # Also handle dotted i (İ with dot above) text = text.replace('İ', 'i').replace('I', 'i') return text.lower() # Normalize search product name search_name = normalize_turkish(product_name.strip()) # Gen 8, Gen 7 vs. ifadelerini koruyalım search_name = search_name.replace('(2026)', '').replace('(2025)', '').strip() search_words = search_name.split() print(f"DEBUG - Searching for: {product_name}") print(f"DEBUG - Normalized (gen kept): {search_name}") # SUPER FAST SEARCH - Build index once # Create a dictionary for O(1) lookup instead of O(n) search product_index = {} # Build index in one pass for product in root.findall('Product'): name_elem = product.find('ProductName') if name_elem is not None and name_elem.text: product_name_key = normalize_turkish(name_elem.text.strip()) # Store all products with same name in a list if product_name_key not in product_index: product_index[product_name_key] = [] variant_elem = product.find('ProductVariant') variant_text = "" if variant_elem is not None and variant_elem.text: variant_text = normalize_turkish(variant_elem.text.strip()) product_index[product_name_key].append({ 'element': product, 'original_name': name_elem.text.strip(), 'variant': variant_text }) # Separate size/variant words from product words size_color_words = ['s', 'm', 'l', 'xl', 'xs', 'xxl', 'ml'] size_indicators = ['beden', 'size', 'boy'] variant_words = [word for word in search_words if word in size_color_words] product_words = [word for word in search_words if word not in size_color_words and word not in size_indicators] # Build the product key to search product_key = ' '.join(product_words) print(f"DEBUG - Looking for key: {product_key}") print(f"DEBUG - Variant filter: {variant_words}") # INSTANT LOOKUP - O(1) candidates = [] # Try exact match first if product_key in product_index: print(f"DEBUG - Exact match found!") for item in product_index[product_key]: # Check variant if variant_words: # For size codes like S, M, L if len(variant_words) == 1 and len(variant_words[0]) <= 2: if item['variant'].startswith(variant_words[0] + '-') or item['variant'].startswith(variant_words[0] + ' '): candidates.append((item['element'], item['original_name'], item['variant'])) else: if all(word in item['variant'] for word in variant_words): candidates.append((item['element'], item['original_name'], item['variant'])) else: # No variant filter, add all candidates.append((item['element'], item['original_name'], item['variant'])) else: # Fallback: search all keys that contain all product words print(f"DEBUG - No exact match, searching partial matches...") for key, items in product_index.items(): if all(word in key for word in product_words): for item in items: if variant_words: if len(variant_words) == 1 and len(variant_words[0]) <= 2: if item['variant'].startswith(variant_words[0] + '-'): candidates.append((item['element'], item['original_name'], item['variant'])) else: candidates.append((item['element'], item['original_name'], item['variant'])) if len(candidates) >= 5: # Found enough break print(f"DEBUG - Found {len(candidates)} candidates instantly!") # Collect stock info from new structure warehouse_stock_map = {} for product, xml_name, variant in candidates: # New structure: Warehouse elements are direct children of Product for warehouse in product.findall('Warehouse'): name_elem = warehouse.find('Name') stock_elem = warehouse.find('Stock') if name_elem is not None and stock_elem is not None: warehouse_name = name_elem.text if name_elem.text else "Bilinmeyen" try: stock_count = int(stock_elem.text) if stock_elem.text else 0 if stock_count > 0: if warehouse_name in warehouse_stock_map: warehouse_stock_map[warehouse_name] += stock_count else: warehouse_stock_map[warehouse_name] = stock_count except (ValueError, TypeError): pass # Cancel alarm signal.alarm(0) print(f"DEBUG - Found {len(candidates)} candidates") if candidates: for product, name, variant in candidates[:3]: print(f"DEBUG - Candidate: {name} - {variant}") if warehouse_stock_map: all_warehouse_info = [] for warehouse_name, total_stock in warehouse_stock_map.items(): # Mağaza isimlerini daha anlaşılır hale getir if "Caddebostan" in warehouse_name: display_name = "Caddebostan mağazası" elif "Ortaköy" in warehouse_name: display_name = "Ortaköy mağazası" elif "Sarıyer" in warehouse_name: display_name = "Sarıyer mağazası" elif "Alsancak" in warehouse_name or "İzmir" in warehouse_name: display_name = "İzmir Alsancak mağazası" else: display_name = warehouse_name all_warehouse_info.append(f"{display_name}: Mevcut") return all_warehouse_info else: return ["Hiçbir mağazada mevcut değil"] except TimeoutError: if use_signal: try: signal.alarm(0) except: pass print("Warehouse API timeout - skipping") return None finally: if use_signal: try: signal.alarm(0) except: pass except Exception as e: print(f"Mağaza stok bilgisi çekme hatası: {e}") return None # Import improved product search try: from improved_chatbot import ImprovedChatbot USE_IMPROVED_SEARCH = True print("DEBUG - Improved chatbot loaded successfully") except ImportError as e: print(f"DEBUG - Improved chatbot not available: {e}, using basic search") USE_IMPROVED_SEARCH = False # Gradio uyarılarını bastır warnings.filterwarnings("ignore", category=UserWarning, module="gradio.components.chatbot") # Log dosyası adı ve yolu LOG_FILE = '/data/chat_logs.txt' if os.path.exists('/data') else 'chat_logs.txt' print(f"Dosya yolu: {os.path.abspath(LOG_FILE)}") # API ayarları API_URL = "https://api.openai.com/v1/chat/completions" OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") if not OPENAI_API_KEY: print("Hata: OPENAI_API_KEY çevre değişkeni ayarlanmamış!") # Trek bisiklet ürünlerini çekme url = 'https://www.trekbisiklet.com.tr/output/8582384479' response = requests.get(url, verify=False, timeout=30) if response.status_code == 200 and response.content: root = ET.fromstring(response.content) else: print(f"HTTP hatası: {response.status_code}") root = None products = [] if root is not None: for item in root.findall('item'): # Tüm ürünleri al, sonra stokta olma durumunu kontrol et - None kontrolü rootlabel_elem = item.find('rootlabel') stock_elem = item.find('stockAmount') if rootlabel_elem is None or stock_elem is None: continue # Eksik veri varsa bu ürünü atla name_words = rootlabel_elem.text.lower().split() name = name_words[0] full_name = ' '.join(name_words) stock_amount = "stokta" if stock_elem.text and stock_elem.text > '0' else "stokta değil" # Stokta olmayan ürünler için fiyat/link bilgisi eklemiyoruz if stock_amount == "stokta": # Normal fiyat bilgisini al - Güvenli versiyon price_elem = item.find('priceTaxWithCur') price_str = price_elem.text if price_elem is not None and price_elem.text else "Fiyat bilgisi yok" # EFT fiyatını al (havale indirimli orijinal fiyat) - Güvenli versiyon price_eft_elem = item.find('priceEft') price_eft_str = price_eft_elem.text if price_eft_elem is not None and price_eft_elem.text else "" # İndirimli fiyatı al (kampanyalı fiyat) - Güvenli versiyon price_rebate_elem = item.find('priceRebateWithTax') price_rebate_str = price_rebate_elem.text if price_rebate_elem is not None and price_rebate_elem.text else "" # Havale indirimi fiyatını al (havale indirimli kampanyalı fiyat) - Güvenli versiyon price_rebate_money_order_elem = item.find('priceRebateWithMoneyOrderWithTax') 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 "" # Normal fiyatı yuvarla try: price_float = float(price_str) # Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla if price_float > 200000: price = str(round(price_float / 5000) * 5000) # En yakın 5000'e yuvarla # Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla elif price_float > 30000: price = str(round(price_float / 1000) * 1000) # En yakın 1000'e yuvarla # Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla elif price_float > 10000: price = str(round(price_float / 100) * 100) # En yakın 100'e yuvarla # Diğer durumlarda en yakın 10'luk basamağa yuvarla else: price = str(round(price_float / 10) * 10) # En yakın 10'a yuvarla except (ValueError, TypeError): price = price_str # Sayıya dönüştürülemezse olduğu gibi bırak # Havale indirimli orijinal fiyatı yuvarla (varsa) if price_eft_str: try: price_eft_float = float(price_eft_str) # Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla if price_eft_float > 200000: price_eft = str(round(price_eft_float / 5000) * 5000) # Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla elif price_eft_float > 30000: price_eft = str(round(price_eft_float / 1000) * 1000) # Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla elif price_eft_float > 10000: price_eft = str(round(price_eft_float / 100) * 100) # Diğer durumlarda en yakın 10'luk basamağa yuvarla else: price_eft = str(round(price_eft_float / 10) * 10) except (ValueError, TypeError): price_eft = price_eft_str else: price_eft = "" # İndirimli fiyatı yuvarla (varsa) if price_rebate_str: try: price_rebate_float = float(price_rebate_str) # Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla if price_rebate_float > 200000: price_rebate = str(round(price_rebate_float / 5000) * 5000) # Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla elif price_rebate_float > 30000: price_rebate = str(round(price_rebate_float / 1000) * 1000) # Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla elif price_rebate_float > 10000: price_rebate = str(round(price_rebate_float / 100) * 100) # Diğer durumlarda en yakın 10'luk basamağa yuvarla else: price_rebate = str(round(price_rebate_float / 10) * 10) except (ValueError, TypeError): price_rebate = price_rebate_str else: price_rebate = "" # Havale indirimli kampanyalı fiyatı yuvarla (varsa) if price_rebate_money_order_str: try: price_rebate_money_order_float = float(price_rebate_money_order_str) # Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla if price_rebate_money_order_float > 200000: price_rebate_money_order = str(round(price_rebate_money_order_float / 5000) * 5000) # Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla elif price_rebate_money_order_float > 30000: price_rebate_money_order = str(round(price_rebate_money_order_float / 1000) * 1000) # Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla elif price_rebate_money_order_float > 10000: price_rebate_money_order = str(round(price_rebate_money_order_float / 100) * 100) # Diğer durumlarda en yakın 10'luk basamağa yuvarla else: price_rebate_money_order = str(round(price_rebate_money_order_float / 10) * 10) except (ValueError, TypeError): price_rebate_money_order = price_rebate_money_order_str else: price_rebate_money_order = "" # Ürün bilgilerini al - None kontrolü ekle product_url_elem = item.find('productLink') product_url = product_url_elem.text if product_url_elem is not None and product_url_elem.text else "" product_info = [stock_amount, price, product_url, price_eft, price_rebate, price_rebate_money_order] # Resim URL'si ekle (varsa) - Güvenli versiyon image_elem = item.find('picture1Path') if image_elem is not None and image_elem.text: product_info.append(image_elem.text) else: product_info.append("") # Boş resim URL'si else: # Stokta olmayan ürün için sadece stok durumu product_info = [stock_amount] products.append((name, product_info, full_name)) print(f"Toplam {len(products)} ürün yüklendi.") # Initialize enhanced features initialize_enhanced_features(OPENAI_API_KEY, products) # Initialize improved chatbot if available improved_bot = None if USE_IMPROVED_SEARCH: try: improved_bot = ImprovedChatbot(products) print("Improved product search initialized successfully") except Exception as e: print(f"Failed to initialize improved search: {e}") USE_IMPROVED_SEARCH = False # Google Drive API ayarları GOOGLE_CREDENTIALS_PATH = os.getenv("GOOGLE_CREDENTIALS_PATH") GOOGLE_FOLDER_ID = "1bE8aMj8-eFGftjMPOF8bKQJAhfHa0BN8" # Global değişkenler file_lock = threading.Lock() history_lock = threading.Lock() global_chat_history = [] document_content = "" # Google Drive'dan döküman indirme fonksiyonu def download_documents_from_drive(): global document_content if not GOOGLE_CREDENTIALS_PATH: print("Google credentials dosyası bulunamadı.") return try: credentials = Credentials.from_service_account_file(GOOGLE_CREDENTIALS_PATH) service = build('drive', 'v3', credentials=credentials) # Klasördeki dosyaları listele results = service.files().list( q=f"'{GOOGLE_FOLDER_ID}' in parents", fields="files(id, name, mimeType)" ).execute() files = results.get('files', []) all_content = [] for file in files: print(f"İndiriliyor: {file['name']}") # DOCX dosyaları için if file['mimeType'] == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': request = service.files().get_media(fileId=file['id']) file_io = io.BytesIO() downloader = MediaIoBaseDownload(file_io, request) done = False while done is False: status, done = downloader.next_chunk() file_io.seek(0) doc = Document(file_io) content = f"\\n=== {file['name']} ===\\n" for paragraph in doc.paragraphs: if paragraph.text.strip(): content += paragraph.text + "\\n" all_content.append(content) document_content = "\\n".join(all_content) print(f"Toplam {len(files)} döküman yüklendi.") except Exception as e: print(f"Google Drive'dan döküman indirme hatası: {e}") # Döküman indirme işlemini arka planda çalıştır document_thread = threading.Thread(target=download_documents_from_drive, daemon=True) document_thread.start() # Log dosyasını zamanla temizleme fonksiyonu def clear_log_file(): try: if os.path.exists(LOG_FILE): with file_lock: with open(LOG_FILE, 'w', encoding='utf-8') as f: f.write("Log dosyası temizlendi.\\n") print("Log dosyası temizlendi.") except Exception as e: print(f"Log dosyası temizleme hatası: {e}") # Zamanlanmış görevleri çalıştırma fonksiyonu def run_scheduler(chat_history): schedule.every().day.at("03:00").do(clear_log_file) while True: schedule.run_pending() time.sleep(60) # Chatbot fonksiyonu def chatbot_fn(user_message, history, image=None): if history is None: history = [] # ÖNCELİKLE warehouse stock bilgisini al (streaming başlamadan önce) warehouse_stock_data = None print(f"DEBUG - Getting warehouse stock FIRST for: {user_message}") try: warehouse_stock_data = get_warehouse_stock(user_message) if warehouse_stock_data: print(f"DEBUG - Warehouse stock found: {warehouse_stock_data[:2]}...") # İlk 2 mağaza else: print(f"DEBUG - No warehouse stock data returned") except Exception as e: print(f"DEBUG - Warehouse stock error at start: {e}") try: # Enhanced features - Görsel işleme if image is not None: user_message = process_image_message(image, user_message) # Enhanced features - Karşılaştırma kontrolü comparison_result = handle_comparison_request(user_message) if comparison_result: yield comparison_result return # Enhanced features - Basit karşılaştırma ve görsel işleme # Profil sistemi kaldırıldı - daha hızlı çalışma için except Exception as e: print(f"Enhanced features error: {e}") # Enhanced features hata verirse normal chatbot'a devam et # Log: Kullanıcı mesajını ekle try: with file_lock: with open(LOG_FILE, 'a', encoding='utf-8') as f: f.write(f"User: {user_message}\\n") except Exception as e: print(f"Dosya yazma hatası (Kullanıcı): {e}") # Sistem mesajlarını external dosyadan yükle system_messages = get_prompt_content_only() # Döküman verilerini sistem mesajlarına ekle if document_content: system_messages.append({"role": "system", "content": f"Dökümanlardan gelen bilgiler: {document_content}"}) # Profil sistemi kaldırıldı - daha hızlı çalışma için # Try improved search first if available product_found_improved = False if USE_IMPROVED_SEARCH and improved_bot: try: product_result = improved_bot.process_message(user_message) if product_result['is_product_query'] and product_result['response']: # Extract product name from improved search result for warehouse stock enhanced_response = product_result['response'] # Önceden alınmış warehouse stock bilgisini kullan if warehouse_stock_data and warehouse_stock_data != ["Hiçbir mağazada mevcut değil"]: warehouse_info = f"\n\n🏪 MAĞAZA STOK BİLGİLERİ:\n" for store_info in warehouse_stock_data: warehouse_info += f"• {store_info}\n" enhanced_response += warehouse_info print(f"DEBUG - Added warehouse stock to improved search response") elif warehouse_stock_data == ["Hiçbir mağazada mevcut değil"]: enhanced_response += f"\n\n🏪 MAĞAZA STOK BİLGİLERİ: Hiçbir mağazada mevcut değil" print(f"DEBUG - No stock available (improved search)") system_messages.append({ "role": "system", "content": f"ÜRÜN BİLGİSİ:\n{enhanced_response}\n\nBu bilgileri kullanarak kullanıcıya yardımcı ol." }) product_found_improved = True except Exception as e: print(f"Improved search error: {e}") # Only use warehouse stock data if available if not product_found_improved: print(f"DEBUG chatbot_fn - Using warehouse stock data for: {user_message}") # Warehouse stock bilgisi varsa kullan if warehouse_stock_data and warehouse_stock_data != ["Hiçbir mağazada mevcut değil"]: warehouse_info = f"🏪 MAĞAZA STOK BİLGİLERİ:\n" for store_info in warehouse_stock_data: warehouse_info += f"• {store_info}\n" system_messages.append({ "role": "system", "content": f"GÜNCEL STOK DURUMU:\n{warehouse_info}\n\nBu bilgileri kullanarak kullanıcıya hangi mağazada stok olduğunu söyle." }) print(f"DEBUG - Using warehouse stock data") elif warehouse_stock_data == ["Hiçbir mağazada mevcut değil"]: system_messages.append({ "role": "system", "content": "🏪 MAĞAZA STOK BİLGİLERİ: Sorduğunuz ürün hiçbir mağazada mevcut değil." }) print(f"DEBUG - Product not available in any store") else: print(f"DEBUG - No warehouse stock data available") messages = system_messages + history + [{"role": "user", "content": user_message}] payload = { "model": "gpt-5-chat-latest", "messages": messages, "temperature": 0.2, "top_p": 1, "n": 1, "stream": True, "presence_penalty": 0, "frequency_penalty": 0, } headers = { "Content-Type": "application/json", "Authorization": f"Bearer {OPENAI_API_KEY}" } response = requests.post(API_URL, headers=headers, json=payload, stream=True) if response.status_code != 200: yield "Bir hata oluştu." return partial_response = "" for chunk in response.iter_lines(): if not chunk: continue chunk_str = chunk.decode('utf-8') if chunk_str.startswith("data: ") and chunk_str != "data: [DONE]": try: chunk_data = json.loads(chunk_str[6:]) delta = chunk_data['choices'][0]['delta'] if 'content' in delta: partial_response += delta['content'] # Resim formatlaması uygula formatted_response = extract_product_info_for_gallery(partial_response) yield formatted_response except json.JSONDecodeError as e: print(f"JSON parse hatası: {e} - Chunk: {chunk_str}") elif chunk_str == "data: [DONE]": break # Son resim formatlaması final_response = extract_product_info_for_gallery(partial_response) yield final_response # Profil kaydetme kaldırıldı - daha hızlı çalışma için # Log: Asistan cevabını ekle try: with file_lock: with open(LOG_FILE, 'a', encoding='utf-8') as f: f.write(f"Bot: {partial_response}\\n") except Exception as e: print(f"Dosya yazma hatası (Bot): {e}") # Global geçmişi güncelle with history_lock: global_chat_history.append({"role": "user", "content": user_message}) global_chat_history.append({"role": "assistant", "content": partial_response}) # Slow echo (test için) def slow_echo(message, history): for i in range(len(message)): time.sleep(0.05) yield "You typed: " + message[: i + 1] # Kullanım modu USE_SLOW_ECHO = False chat_fn = slow_echo if USE_SLOW_ECHO else chatbot_fn if not USE_SLOW_ECHO: scheduler_thread = threading.Thread(target=run_scheduler, args=(global_chat_history,), daemon=True) scheduler_thread.start() # Trek markasına özel tema oluştur (düzeltilmiş sürüm) trek_theme = gr.themes.Base( primary_hue="red", # Trek kırmızısı için secondary_hue="slate", # Gri tonları için neutral_hue="slate", radius_size=gr.themes.sizes.radius_sm, # Köşe yuvarlatma değerleri spacing_size=gr.themes.sizes.spacing_md, # Aralık değerleri text_size=gr.themes.sizes.text_sm # Yazı boyutları (small) ) # Chatbot kartları için arka plan renkleri değiştiren CSS custom_css = """ /* Genel font ayarları */ .gradio-container, .gradio-container *, .message-wrap, .message-wrap p, .message-wrap div, button, input, select, textarea { font-family: 'Segoe UI', 'SF Pro Text', 'Roboto', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif !important; font-size: 0.6rem !important; } /* Mobil responsive başlangıç */ @media (max-width: 768px) { .gradio-container, .gradio-container *, .message-wrap, .message-wrap p, .message-wrap div { font-size: 0.8rem !important; } h1 { font-size: 1.8rem !important; text-align: center; margin: 10px 0; } h2 { font-size: 1.4rem !important; } } /* Input alanı için de aynı boyut */ .message-textbox textarea { font-size: 0.6rem !important; } /* Başlıklar için özel boyutlar */ h1 { font-size: 1.4rem !important; font-weight: 800 !important; } h2 { font-size: 1.2rem !important; font-weight: 600 !important; } h3 { font-size: 1rem !important; font-weight: 600 !important; } /* Kart arka plan renkleri - görseldeki gibi */ /* Kullanıcı mesajları için mavi tonda arka plan */ .user-message, .user-message-highlighted { background-color: #e9f5fe !important; border-bottom-right-radius: 0 !important; box-shadow: 0 1px 2px rgba(0,0,0,0.1) !important; } /* Bot mesajları için beyaz arka plan ve hafif kenarlık */ .bot-message, .bot-message-highlighted { background-color: white !important; border: 1px solid #e0e0e0 !important; border-bottom-left-radius: 0 !important; box-shadow: 0 1px 2px rgba(0,0,0,0.05) !important; } /* Mesaj baloncuklarının köşe yuvarlatma değerleri */ .message-wrap { border-radius: 12px !important; margin: 0.5rem 0 !important; max-width: 90% !important; } /* Sohbet alanının genel arka planı */ .chat-container, .gradio-container { background-color: #f7f7f7 !important; } /* Daha net yazılar için text rendering */ * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; } /* Restore butonu stilleri kaldırıldı */ /* Responsive mobil tasarım - iOS benzeri */ @media (max-width: 768px) { /* Daha büyük ve dokunmatik dostu boyutlar */ .gradio-container { padding: 0 !important; margin: 0 !important; } /* Mesaj baloncukları iOS tarzı */ .message-wrap { margin: 0.3rem 0.5rem !important; max-width: 85% !important; border-radius: 18px !important; padding: 10px 15px !important; font-size: 0.9rem !important; } /* Kullanıcı mesajları */ .user-message, .user-message-highlighted { background-color: #007AFF !important; color: white !important; margin-left: auto !important; margin-right: 8px !important; } /* Bot mesajları */ .bot-message, .bot-message-highlighted { background-color: #f1f1f1 !important; color: #333 !important; margin-left: 8px !important; margin-right: auto !important; } } /* Input alanına uçan kağıt ikonu ekle */ #msg-input { position: relative; } #msg-input textarea { padding-right: 40px !important; } .input-icon { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); font-size: 16px; pointer-events: none; z-index: 10; color: #666; } /* Mobil responsive - Input alanı */ @media (max-width: 768px) { #msg-input { margin: 10px 0; } #msg-input textarea { padding: 12px 45px 12px 15px !important; font-size: 1rem !important; border-radius: 20px !important; border: 1px solid #ddd !important; box-shadow: 0 1px 3px rgba(0,0,0,0.1) !important; } .input-icon { right: 15px; font-size: 18px; } } /* Genel mobil iyileştirmeler */ @media (max-width: 768px) { /* Chatbot alanı tam ekran */ .gradio-container .main { padding: 0 !important; } /* Başlık alanı küçült */ .gradio-container header { padding: 8px !important; } /* Tab alanlarını küçült */ .tab-nav { padding: 5px !important; } /* Scroll bar'ı gizle */ .scroll-hide { scrollbar-width: none; -ms-overflow-style: none; } .scroll-hide::-webkit-scrollbar { display: none; } } /* CSS Bitişi */ """ # Chat persistence sistemi tamamen kaldırıldı storage_js = "" # Enhanced chatbot fonksiyonu image destekli def enhanced_chatbot_fn(message, history, image): return chatbot_fn(message, history, image) # Demo arayüzü - Mobil responsive with gr.Blocks(css=custom_css, theme="soft", title="Trek Asistanı", head=storage_js) as demo: gr.Markdown("# 🚲 Trek Asistanı AI") gr.Markdown("**Akıllı özellikler:** Ürün karşılaştırması ve detaylı ürün bilgileri sunuyorum.") # LocalStorage fonksiyonu kaldırıldı chatbot = gr.Chatbot(height=600, elem_id="chatbot", show_label=False, type="messages") msg = gr.Textbox( placeholder="Trek bisikletleri hakkında soru sorun...", show_label=False, elem_id="msg-input" ) # Session ve profil sistemi tamamen kaldırıldı def respond(message, chat_history): if not message.strip(): return "", chat_history # Kullanıcı mesajını hemen göster if chat_history is None: chat_history = [] # Messages format için kullanıcı mesajını ekle chat_history.append({"role": "user", "content": message}) yield "", chat_history # Chat history'yi chatbot_fn için uygun formata çevir formatted_history = [] for msg in chat_history[:-1]: # Son mesajı hariç tut formatted_history.append(msg) try: # Enhanced chatbot fonksiyonunu çağır (image=None) response_generator = chatbot_fn(message, formatted_history, None) # Generator'dan streaming cevap al response = "" for partial in response_generator: response = partial # Bot cevabını ekle veya güncelle # Eğer son mesaj user ise, bot mesajı ekle if chat_history[-1]["role"] == "user": chat_history.append({"role": "assistant", "content": response}) else: # Son bot mesajını güncelle chat_history[-1]["content"] = response yield "", chat_history # Konuşmayı kaydet try: add_conversation(message, response) except Exception as e: print(f"Error saving conversation: {e}") except Exception as e: error_msg = f"Üzgünüm, bir hata oluştu: {str(e)}" print(f"Chat error: {e}") # Hata mesajı ekle if chat_history[-1]["role"] == "user": chat_history.append({"role": "assistant", "content": error_msg}) else: chat_history[-1]["content"] = error_msg yield "", chat_history msg.submit(respond, [msg, chatbot], [msg, chatbot], show_progress=True) # Add chat logs viewer with gr.Accordion("📄 Chat Logs (Text)", open=False): with gr.Row(): refresh_logs_btn = gr.Button("🔄 Yenile", scale=1) download_logs_btn = gr.Button("💾 Logları İndir", scale=1) logs_display = gr.Textbox( label="Chat Logs", lines=10, max_lines=20, interactive=False ) logs_file = gr.File(visible=False) def get_chat_logs(): """Get chat logs content""" try: if os.path.exists(LOG_FILE): with open(LOG_FILE, 'r', encoding='utf-8') as f: return f.read() return "Henüz log kaydı yok." except Exception as e: return f"Log okuma hatası: {e}" def download_chat_logs(): """Download chat logs file""" if os.path.exists(LOG_FILE): return LOG_FILE # Create empty file if doesn't exist with open(LOG_FILE, 'w') as f: f.write("Chat Logs\n") return LOG_FILE refresh_logs_btn.click(get_chat_logs, outputs=logs_display) download_logs_btn.click(download_chat_logs, outputs=logs_file) demo.load(get_chat_logs, outputs=logs_display) # Add conversation viewer with gr.Accordion("📊 Konuşma Geçmişi (JSON)", open=False): with gr.Row(): refresh_json_btn = gr.Button("🔄 Yenile", scale=1) download_json_btn = gr.Button("💾 JSON İndir", scale=1) view_dashboard_btn = gr.Button("📈 Dashboard'u Aç", scale=1) json_display = gr.JSON(label="Konuşmalar", elem_id="json_viewer") download_file = gr.File(label="İndir", visible=False) def get_conversations_json(): from conversation_tracker import load_conversations convs = load_conversations() # Also save to file for download import json as json_module with open("temp_conversations.json", "w", encoding="utf-8") as f: json_module.dump(convs, f, ensure_ascii=False, indent=2) return convs def download_conversations(): get_conversations_json() # Ensure file is updated return gr.update(visible=True, value="temp_conversations.json") def open_dashboard(): return gr.HTML("""

📊 Dashboard Kullanım Talimatları

  1. Yukarıdaki "💾 JSON İndir" butonuna tıkla
  2. conversations.json dosyasını indir
  3. bf_dashboard_json.html dosyasını aç
  4. İndirdiğin JSON dosyasını dashboard'a yükle

Dashboard HTML dosyasını almak için:
GitHub'dan indir

""") refresh_json_btn.click(get_conversations_json, outputs=json_display) download_json_btn.click(download_conversations, outputs=download_file) view_dashboard_btn.click(open_dashboard, outputs=json_display) # Auto-load on start demo.load(get_conversations_json, outputs=json_display) # Define function for JSON HTML before using it def create_json_html(): """Create HTML with embedded JSON data""" from conversation_tracker import load_conversations import json conversations = load_conversations() json_str = json.dumps(conversations, ensure_ascii=False) return f''' ''' # Add HTML component with embedded JSON for external access with gr.Row(visible=False): # Create an HTML element that contains the JSON data json_html = gr.HTML( value=create_json_html(), # Call directly, not as lambda visible=False, elem_id="json_data_container" ) # Update JSON HTML on page load demo.load(create_json_html, outputs=json_html) # API endpoints for dashboard def get_all_conversations(): """API endpoint to get all conversations""" from conversation_tracker import load_conversations conversations = load_conversations() # Format for dashboard formatted = {} for conv in conversations: session_id = f"session_{conv['timestamp'].replace(':', '').replace('-', '').replace('T', '_')[:15]}" if session_id not in formatted: formatted[session_id] = { "customer": "Kullanıcı", "phone": session_id, "messages": [] } formatted[session_id]["messages"].append({ "type": "received", "text": conv["user"], "time": conv["timestamp"] }) formatted[session_id]["messages"].append({ "type": "sent", "text": conv["bot"], "time": conv["timestamp"] }) return formatted def get_conversation_stats(): """Get conversation statistics""" from conversation_tracker import load_conversations from datetime import datetime conversations = load_conversations() today = datetime.now().date() today_count = sum(1 for conv in conversations if datetime.fromisoformat(conv["timestamp"]).date() == today) return { "total": len(conversations), "today": today_count, "active": len(set(conv.get("session_id", i) for i, conv in enumerate(conversations))) } # API will be handled separately in production # Add FastAPI endpoints for CORS-enabled API access from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse import json # Create FastAPI app api = FastAPI() # Add CORS middleware api.add_middleware( CORSMiddleware, allow_origins=["*"], # Allow all origins allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @api.get("/api/conversations") async def get_conversations(): """API endpoint to get conversations with CORS""" try: from conversation_tracker import load_conversations conversations = load_conversations() return JSONResponse(content=conversations) except Exception as e: return JSONResponse(content={"error": str(e)}, status_code=500) @api.get("/api/conversations/latest") async def get_latest_conversations(): """Get last 50 conversations""" try: from conversation_tracker import load_conversations conversations = load_conversations() return JSONResponse(content=conversations[-50:]) except Exception as e: return JSONResponse(content={"error": str(e)}, status_code=500) # Mount FastAPI to Gradio demo = gr.mount_gradio_app(api, demo, path="/") if __name__ == "__main__": import uvicorn # Run with uvicorn for FastAPI support uvicorn.run(api, host="0.0.0.0", port=7860)