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_active_prompts # 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_active_prompts() # 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 # SADECE warehouse_stock_data kullan - improved_bot devre disi (tutarsizlik yaratiyor) # improved_bot farkli cache kullandigi icin bazen stok var bazen yok diyor product_found_improved = False # 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"KRITIK - GERCEK STOK VERISI (XML VERITABANINDAN):\n{warehouse_info}\n\nCOK ONEMLI: Bu veri gercek veritabanindan gelmektedir ve KESINLIKLE DOGRUDUR. Kullanicinin sordugu urun STOKTA MEVCUTTUR. ASLA 'boyle bir model yok', 'bulunamadi' veya 'Trek gaminda bulunmuyor' DEME. Yukardaki stok ve fiyat bilgisini kullanarak dogru cevap ver." }) 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.2-chat-latest", "messages": messages, "stream": True, "top_p": 1, "presence_penalty": 0, "frequency_penalty": 0, "n": 1, } headers = { "Content-Type": "application/json", "Authorization": f"Bearer {OPENAI_API_KEY}" } response = requests.post(API_URL, headers=headers, json=payload, stream=True) # 🔥 API ERROR DEBUG BLOĞU — ÇALIŞAN VERSİYON if response.status_code != 200: try: print("\n🔴 OPENAI API ERROR") print("Status Code:", response.status_code) print("Response Body:", response.text, "\n") except Exception as e: print("Error printing OpenAI error:", e) yield f"API hatası: {response.status_code}\n{response.text}" return # --- STREAMING BAŞLANGICI --- partial_response = "" 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 # Function to get conversations for API def get_conversations_api(): """Get all conversations as JSON for dashboard""" try: from conversation_tracker import load_conversations conversations = load_conversations() return conversations except Exception as e: return [{"error": str(e)}] 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 # Hidden API endpoint for conversations with gr.Row(visible=False): api_btn = gr.Button("Get Conversations", elem_id="api_btn") api_output = gr.JSON(label="Conversations") api_btn.click(fn=get_conversations_api, outputs=api_output, api_name="get_conversations") msg.submit(respond, [msg, chatbot], [msg, chatbot], show_progress=True) # API will be handled separately in production # FastAPI endpoints disabled for better HF Spaces compatibility # Use Gradio's built-in API instead # 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) # Use Gradio directly instead of mounting on FastAPI - Better for HF Spaces if __name__ == "__main__": demo.launch(allowed_paths=["/app/conversations.json", "/app/public"], server_name="0.0.0.0", server_port=7860, share=False, show_api=True )