SamiKoen
Fix: Disable improved_bot to prevent inconsistent stock responses - use only warehouse_stock_data
056a346
| 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'<Product>(.*?)</Product>' | |
| 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'<ProductName><!\[CDATA\[(.*?)\]\]></ProductName>', 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'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', 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'<Warehouse>.*?<Name><!\[CDATA\[(.*?)\]\]></Name>.*?<Stock>(.*?)</Stock>.*?</Warehouse>' | |
| 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 | |
| ) | |