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