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 # STOK API ENTEGRASYONU - YENİ EKLENEN KISIM STOCK_API_BASE = "https://video.trek-turkey.com/bizimhesap-proxy.php" def normalize_turkish(text): """Türkçe karakterleri normalize et""" if not text: return "" replacements = { 'ı': 'i', 'İ': 'i', 'ş': 's', 'Ş': 's', 'ğ': 'g', 'Ğ': 'g', 'ü': 'u', 'Ü': 'u', 'ö': 'o', 'Ö': 'o', 'ç': 'c', 'Ç': 'c' } text = text.lower() for tr_char, eng_char in replacements.items(): text = text.replace(tr_char, eng_char) return text def get_realtime_stock(product_name): """API'den gerçek zamanlı stok bilgisini çek - Minimal versiyon""" try: # Önce mağaza listesini al - endpoint parametresi eklendi warehouses_url = f"{STOCK_API_BASE}?action=warehouses&endpoint=warehouses" warehouses_response = requests.get(warehouses_url, timeout=5, verify=False) if warehouses_response.status_code != 200: print(f"Mağaza listesi alınamadı: {warehouses_response.status_code}") return None warehouses_data = warehouses_response.json() # API yanıtını kontrol et if 'data' in warehouses_data and 'warehouses' in warehouses_data['data']: warehouses = warehouses_data['data']['warehouses'] else: print("Mağaza verisi bulunamadı") return None # Ürün adını normalize et search_terms = normalize_turkish(product_name).lower().split() print(f"Aranan ürün: {product_name} -> {search_terms}") # Her mağazanın stok bilgisini topla stock_info = {} total_stock = 0 dsw_stock = 0 # DSW deposundaki gelecek stok for warehouse in warehouses: warehouse_id = warehouse['id'] warehouse_name = warehouse['title'] # DSW'yi ayrı tut (gelecek stok için) is_dsw = 'DSW' in warehouse_name or 'ÖN SİPARİŞ' in warehouse_name.upper() # Mağaza stoklarını al - endpoint parametresi eklendi inventory_url = f"{STOCK_API_BASE}?action=inventory&warehouse={warehouse_id}&endpoint=inventory/{warehouse_id}" inventory_response = requests.get(inventory_url, timeout=5, verify=False) if inventory_response.status_code != 200: continue inventory_data = inventory_response.json() # API yanıtını kontrol et if 'data' in inventory_data and 'inventory' in inventory_data['data']: products = inventory_data['data']['inventory'] else: continue # Ürünü ara - TÜM VARYANTLARI TOPLA VE DETAYLARI KAYDET warehouse_variants = [] for product in products: product_title = normalize_turkish(product.get('title', '')).lower() original_title = product.get('title', '') # Tüm arama terimlerinin ürün başlığında olup olmadığını kontrol et if all(term in product_title for term in search_terms): # qty ve stock değerlerini kontrol et qty = int(product.get('qty', 0)) stock = int(product.get('stock', 0)) actual_stock = max(qty, stock) # İkisinden büyük olanı al if actual_stock > 0: # DSW ise gelecek stok olarak say if is_dsw: dsw_stock += actual_stock continue # DSW'yi mağaza listesine ekleme # Normal mağaza stoğu # Varyant detayını sakla (beden/renk bilgisi için) # Ürün adını temizle - Türkçe karakterleri de dikkate al variant_info = original_title # Hem normal hem de Türkçe karakterli versiyonları dene possible_names = [ product_name.upper(), product_name.lower(), product_name.title(), product_name.upper().replace('I', 'İ'), # Türkçe İ product_name.upper().replace('İ', 'I'), # İngilizce I ] # FX Sport AL 3 gibi özel durumlar için if 'fx sport' in product_name.lower(): possible_names.extend(['FX Sport AL 3', 'FX SPORT AL 3', 'Fx Sport Al 3']) for possible_name in possible_names: variant_info = variant_info.replace(possible_name, '').strip() # Gereksiz boşlukları temizle variant_info = ' '.join(variant_info.split()) if variant_info and variant_info != original_title: # Güvenlik: Adet yerine sadece stokta bilgisi warehouse_variants.append(f"{variant_info}: ✓ Stokta") else: # Eğer temizleme başarısız olduysa tam ismi göster warehouse_variants.append(f"{original_title}: ✓ Stokta") total_stock += actual_stock # DSW değilse mağaza listesine ekle if warehouse_variants and not is_dsw: stock_info[warehouse_name] = warehouse_variants if not stock_info: # Mağazada yok ama DSW'de varsa if dsw_stock > 0: return f"{product_name}: Şu anda mağazalarda stokta yok, ancak yakında gelecek. Ön sipariş verebilirsiniz." else: return f"{product_name}: Şu anda hiçbir mağazada stokta bulunmuyor." # Minimal prompt oluştur - varyant detaylarıyla prompt_lines = [f"{product_name} stok durumu:"] for warehouse, variants in stock_info.items(): if isinstance(variants, list): # Varyant detayları varsa prompt_lines.append(f"- {warehouse}:") for variant in variants: prompt_lines.append(f" • {variant}") else: # Eski format (eğer hata olursa) prompt_lines.append(f"- {warehouse}: {variants} adet") # Güvenlik: Toplam adet bilgisi gösterme if total_stock > 0: prompt_lines.append(f"✓ Ürün stokta mevcut") # Mağazada varsa DSW bilgisi verme else: prompt_lines.append(f"✗ Stokta yok") return "\n".join(prompt_lines) except Exception as e: print(f"Stok API hatası: {e}") return None def is_stock_query(message): """Mesajın stok sorgusu olup olmadığını kontrol et""" stock_keywords = [ 'stok', 'stock', 'kaç adet', 'kaç tane', 'var mı', 'mevcut mu', 'bulunuyor mu', 'hangi mağaza', 'nerede var', 'stok durumu', 'stoklarda', 'stokta', 'adet', 'kaç tane var' ] message_lower = message.lower() return any(keyword in message_lower for keyword in stock_keywords) # STOK API ENTEGRASYONU SONU # 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=60) 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 de fiyat bilgilerini sakla (API için gerekli) # Normal fiyat bilgisini al price_elem = item.find('priceTaxWithCur') price_str = price_elem.text if price_elem is not None and price_elem.text else "Fiyat bilgisi yok" # Ürün URL'si 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 "" # Resim URL'si image_elem = item.find('picture1Path') image_url = image_elem.text if image_elem is not None and image_elem.text else "" # Stokta olmayan ürün için de tüm bilgileri sakla product_info = [stock_amount, price_str, product_url, "", "", "", image_url] 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) # 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 - STOK API ENTEGRASYONU EKLENDİ def chatbot_fn(user_message, history, image=None): if history is None: history = [] 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 except Exception as e: print(f"Enhanced features error: {e}") # 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[:3000]}"}) # Token limiti için kısalt # STOK SORGUSU KONTROLÜ - YENİ EKLENEN KISIM if is_stock_query(user_message): print("Stok sorgusu algılandı, API'den veri çekiliyor...") # Mesajdan ürün adını çıkarmaya çalış # Basit bir yaklaşım: stok kelimelerini temizleyip kalan kelimeleri ürün adı olarak kullan product_words = [] skip_words = ['stok', 'stock', 'kaç', 'adet', 'tane', 'var', 'mı', 'mi', 'mevcut', 'mu', 'bulunuyor', 'hangi', 'mağaza', 'nerede', 'durumu', 'stoklarda', 'stokta', 'için', 've', 'ile', 'toplam', 'toplamda', 'fiyat', 'fiyatı', 'ne', 'nedir', 'kadar'] for word in user_message.lower().split(): if word not in skip_words: product_words.append(word) if product_words: product_name = ' '.join(product_words) # FX Sport AL 3 gibi özel ürün isimlerini düzelt # "fx sport 3 al" -> "fx sport al 3" if 'fx sport' in product_name and '3' in product_name: if 'al' in product_name: # Kelime sırasını düzelt product_name = 'fx sport al 3' elif 'carbon' in product_name: product_name = 'fx sport carbon' stock_info = get_realtime_stock(product_name) if stock_info: # Stok bilgisini sistem mesajına ekle system_messages.append({ "role": "system", "content": f"GÜNCEL STOK BİLGİSİ (API'den alındı):\n{stock_info}" }) print(f"Stok bilgisi eklendi: {stock_info}") # XML'DEN FİYAT/GÖRSEL/LİNK BİLGİLERİ - Her zaman çalışır # Stok bilgisi API'den alındıysa, sadece fiyat/görsel/link için XML'e bak has_stock_from_api = is_stock_query(user_message) and 'stock_info' in locals() and stock_info is not None # XML'den ürün bilgilerini al (fiyat, görsel, link için) # Her zaman orijinal mesajdaki kelimeleri kullan input_words = user_message.lower().split() added_products_count = 0 # Eğer API'den stok alındıysa, önce tam ürün adıyla ara if has_stock_from_api and 'product_name' in locals(): for product_info in products: product_full_name = product_info[2].lower() # FX Sport AL 3 tam eşleşme kontrolü if product_name.lower().replace(' ', '') in product_full_name.replace(' ', ''): print(f"XML tam eşleşme bulundu: {product_info[2]}") # Ürün bulundu, bilgileri al if len(product_info[1]) > 1 and product_info[1][1]: system_messages.append({ "role": "system", "content": f"ÜRÜN BİLGİLERİ (XML):\nFiyat: {product_info[1][1]} TL\nLink: {product_info[1][2] if len(product_info[1]) > 2 else ''}\nResim: {product_info[1][6] if len(product_info[1]) > 6 else ''}" }) added_products_count = 1 break # Tam eşleşme bulunamadıysa normal kelime araması yap if added_products_count == 0: for word in input_words: if added_products_count >= 5: # Token limiti için maksimum 5 ürün break for product_info in products: # Basit kelime eşleştirme - eskisi gibi if word in product_info[0] or word in product_info[2].lower(): # API'den stok alındıysa veya ürün stokta ise bilgileri al if has_stock_from_api or product_info[1][0] == "stokta": # Debug: hangi ürün bulundu print(f"XML'de bulunan ürün: {product_info[2]}") # Fiyat bilgisi varsa al if len(product_info[1]) > 1 and product_info[1][1]: normal_price = f"\\nFiyat: {product_info[1][1]} TL" print(f"Fiyat bulundu: {product_info[1][1]}") else: normal_price = "" print("Fiyat bilgisi yok") rebate_price = "" discount_info = "" eft_price = "" rebate_money_order_price = "" if len(product_info[1]) > 4 and product_info[1][4] and product_info[1][4] != "": rebate_price = f"\\nKampanyalı fiyat: {product_info[1][4]} TL" try: normal_price_float = float(product_info[1][1]) rebate_price_float = float(product_info[1][4]) discount_amount = normal_price_float - rebate_price_float if discount_amount > 0: if discount_amount > 200000: discount_amount_rounded = round(discount_amount / 5000) * 5000 elif discount_amount > 30000: discount_amount_rounded = round(discount_amount / 1000) * 1000 elif discount_amount > 10000: discount_amount_rounded = round(discount_amount / 100) * 100 else: discount_amount_rounded = round(discount_amount / 10) * 10 discount_info = f"\\nYapılan indirim: {discount_amount_rounded:.0f} TL" except (ValueError, TypeError): discount_info = "" rebate_money_order_price = "" else: if product_info[1][3] and product_info[1][3] != "": eft_price = f"\\nHavale indirimli fiyat: {product_info[1][3]} TL" product_link = f"\\nÜrün linki: {product_info[1][2]}" product_image = "" if len(product_info[1]) > 6 and product_info[1][6]: product_image = f"\\nÜrün resmi: {product_info[1][6]}" # API'den stok alındıysa sadece fiyat/görsel/link ekle if has_stock_from_api: new_msg = f"ÜRÜN BİLGİLERİ (Fiyat/Link):\\n{normal_price}{rebate_price}{discount_info}{eft_price}{rebate_money_order_price}{product_link}{product_image}" else: # API'den stok alınmadıysa hem stok hem fiyat bilgisi ekle new_msg = f"{product_info[2]} {product_info[1][0]}\\n{normal_price}{rebate_price}{discount_info}{eft_price}{rebate_money_order_price}{product_link}{product_image}" else: # Stokta değilse if has_stock_from_api: # API'den stok bilgisi varsa XML'den stok bilgisi ekleme new_msg = None else: new_msg = f"{product_info[2]} {product_info[1][0]}" # Token limiti için mesaj boyutunu kontrol et if new_msg and len(new_msg) < 5000: # Maksimum 5000 karakter system_messages.append({"role": "system", "content": new_msg}) added_products_count += 1 break # Son 10 mesajla sınırla (token limiti için) if len(history) > 10: history = history[-10:] messages = system_messages + history + [{"role": "user", "content": user_message}] payload = { "model": "gpt-5-chat-latest", "messages": messages, "temperature": 0.1, "top_p": 0.9, "n": 1, "stream": True, "presence_penalty": 0.1, "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, timeout=120) if response.status_code != 200: print(f"API Hatası: {response.status_code} - {response.text}") yield f"API Hatası ({response.status_code}): {response.text[:200]}" 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 # 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:** Gerçek zamanlı stok bilgisi, ürün karşılaştırması ve detaylı ürün bilgileri sunuyorum.") chatbot = gr.Chatbot(height=600, elem_id="chatbot", show_label=False) msg = gr.Textbox( placeholder="Trek bisikletleri hakkında soru sorun...", show_label=False, elem_id="msg-input" ) 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 = [] # Kullanıcı mesajını ekle ve hemen yield et chat_history.append((message, None)) yield "", chat_history # Chat history'yi chatbot_fn için uygun formata çevir (son mesaj hariç) formatted_history = [] if len(chat_history) > 1: # Son mesajı hariç tut for user_msg, bot_msg in chat_history[:-1]: if bot_msg: # Sadece tamamlanmış mesajlar formatted_history.append({"role": "user", "content": user_msg}) formatted_history.append({"role": "assistant", "content": bot_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 # Son mesajı güncelle ve yield et chat_history[-1] = (message, response) yield "", chat_history except Exception as e: error_msg = f"Üzgünüm, bir hata oluştu: {str(e)}" print(f"Chat error: {e}") # Hata mesajıyla güncelle chat_history[-1] = (message, error_msg) yield "", chat_history msg.submit(respond, [msg, chatbot], [msg, chatbot], show_progress=True) if __name__ == "__main__": demo.launch(debug=True)