|
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) |
|
|
|
|
|
from prompts import get_prompt_content_only |
|
|
|
|
|
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 |
|
|
|
|
|
from conversation_tracker import add_conversation |
|
|
|
|
|
|
|
|
|
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""" |
|
|
|
if get_warehouse_stock_smart: |
|
result = get_warehouse_stock_smart(product_name) |
|
if result: |
|
return result |
|
|
|
|
|
return get_warehouse_stock_old(product_name) |
|
|
|
|
|
def get_warehouse_stock_old(product_name): |
|
"""Smart warehouse stock finder with general algorithm""" |
|
try: |
|
import re |
|
import requests |
|
|
|
|
|
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) |
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
query = normalize(product_name.strip()).replace('(2026)', '').replace('(2025)', '').strip() |
|
words = query.split() |
|
|
|
|
|
sizes = ['s', 'm', 'l', 'xl', 'xs', 'xxl', 'ml'] |
|
size = next((w for w in words if w in sizes), None) |
|
|
|
|
|
product_words = [] |
|
|
|
|
|
if len(words) <= 2 and not any(w.isdigit() for w in words): |
|
|
|
pass |
|
else: |
|
|
|
for word in words: |
|
|
|
if word in sizes: |
|
continue |
|
|
|
|
|
if word.isdigit(): |
|
product_words.append(word) |
|
|
|
|
|
elif any(c.isdigit() for c in word) and any(c.isalpha() for c in word): |
|
product_words.append(word) |
|
|
|
|
|
elif len(word) in [2, 3] and word.isalpha(): |
|
|
|
if word in ['mi', 'mı', 'mu', 'mü', 'var', 'yok', 've', 'de', 'da']: |
|
continue |
|
|
|
if any(c not in 'aeiouı' for c in word): |
|
product_words.append(word) |
|
|
|
|
|
elif len(word) > 3: |
|
|
|
if any(word.endswith(suffix) for suffix in ['mi', 'mı', 'mu', 'mü']): |
|
continue |
|
|
|
consonants = sum(1 for c in word if c not in 'aeiouı') |
|
if consonants <= 2: |
|
continue |
|
|
|
product_words.append(word) |
|
|
|
print(f"DEBUG - Searching: {' '.join(product_words)}, Size: {size}") |
|
|
|
|
|
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)}") |
|
|
|
|
|
best_match = None |
|
|
|
for product_block in all_products: |
|
|
|
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) |
|
|
|
|
|
|
|
match = True |
|
for word in product_words: |
|
|
|
if word not in normalized_xml_name: |
|
|
|
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: |
|
|
|
|
|
if size: |
|
|
|
variant_match = re.search(r'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', product_block) |
|
if variant_match: |
|
variant = variant_match.group(1) |
|
|
|
if variant.upper().startswith(f'{size.upper()}-'): |
|
print(f"DEBUG - Found match: {product_name_in_xml} - {variant}") |
|
best_match = product_block |
|
break |
|
else: |
|
|
|
best_match = product_block |
|
break |
|
|
|
if best_match: |
|
|
|
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: |
|
|
|
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 |
|
|
|
|
|
def get_warehouse_stock_old_slow(product_name): |
|
"""B2B API'den mağaza stok bilgilerini çek - Optimize edilmiş versiyon""" |
|
try: |
|
import re |
|
|
|
|
|
use_signal = False |
|
try: |
|
import signal |
|
import threading |
|
|
|
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) |
|
|
|
if response.status_code != 200: |
|
return None |
|
|
|
|
|
xml_text = response.text |
|
|
|
|
|
turkish_map = {'ı': 'i', 'ğ': 'g', 'ü': 'u', 'ş': 's', 'ö': 'o', 'ç': 'c', 'İ': 'i', 'I': 'i'} |
|
|
|
def normalize_turkish(text): |
|
import unicodedata |
|
|
|
text = unicodedata.normalize('NFKD', text) |
|
text = ''.join(char for char in text if unicodedata.category(char) != 'Mn') |
|
|
|
for tr_char, en_char in turkish_map.items(): |
|
text = text.replace(tr_char, en_char) |
|
|
|
text = text.replace('İ', 'i').replace('I', 'i') |
|
return text.lower() |
|
|
|
|
|
search_name = normalize_turkish(product_name.strip()) |
|
|
|
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}") |
|
|
|
|
|
|
|
product_index = {} |
|
|
|
|
|
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()) |
|
|
|
|
|
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 |
|
}) |
|
|
|
|
|
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] |
|
|
|
|
|
product_key = ' '.join(product_words) |
|
|
|
print(f"DEBUG - Looking for key: {product_key}") |
|
print(f"DEBUG - Variant filter: {variant_words}") |
|
|
|
|
|
candidates = [] |
|
|
|
|
|
if product_key in product_index: |
|
print(f"DEBUG - Exact match found!") |
|
for item in product_index[product_key]: |
|
|
|
if variant_words: |
|
|
|
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: |
|
|
|
candidates.append((item['element'], item['original_name'], item['variant'])) |
|
else: |
|
|
|
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: |
|
break |
|
|
|
print(f"DEBUG - Found {len(candidates)} candidates instantly!") |
|
|
|
|
|
warehouse_stock_map = {} |
|
|
|
for product, xml_name, variant in candidates: |
|
|
|
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 |
|
|
|
|
|
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(): |
|
|
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
warnings.filterwarnings("ignore", category=UserWarning, module="gradio.components.chatbot") |
|
|
|
|
|
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_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ış!") |
|
|
|
|
|
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'): |
|
|
|
rootlabel_elem = item.find('rootlabel') |
|
stock_elem = item.find('stockAmount') |
|
|
|
if rootlabel_elem is None or stock_elem is None: |
|
continue |
|
|
|
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" |
|
|
|
|
|
if stock_amount == "stokta": |
|
|
|
price_elem = item.find('priceTaxWithCur') |
|
price_str = price_elem.text if price_elem is not None and price_elem.text else "Fiyat bilgisi yok" |
|
|
|
|
|
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 "" |
|
|
|
|
|
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 "" |
|
|
|
|
|
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 "" |
|
|
|
|
|
try: |
|
price_float = float(price_str) |
|
|
|
if price_float > 200000: |
|
price = str(round(price_float / 5000) * 5000) |
|
|
|
elif price_float > 30000: |
|
price = str(round(price_float / 1000) * 1000) |
|
|
|
elif price_float > 10000: |
|
price = str(round(price_float / 100) * 100) |
|
|
|
else: |
|
price = str(round(price_float / 10) * 10) |
|
except (ValueError, TypeError): |
|
price = price_str |
|
|
|
|
|
if price_eft_str: |
|
try: |
|
price_eft_float = float(price_eft_str) |
|
|
|
if price_eft_float > 200000: |
|
price_eft = str(round(price_eft_float / 5000) * 5000) |
|
|
|
elif price_eft_float > 30000: |
|
price_eft = str(round(price_eft_float / 1000) * 1000) |
|
|
|
elif price_eft_float > 10000: |
|
price_eft = str(round(price_eft_float / 100) * 100) |
|
|
|
else: |
|
price_eft = str(round(price_eft_float / 10) * 10) |
|
except (ValueError, TypeError): |
|
price_eft = price_eft_str |
|
else: |
|
price_eft = "" |
|
|
|
|
|
if price_rebate_str: |
|
try: |
|
price_rebate_float = float(price_rebate_str) |
|
|
|
if price_rebate_float > 200000: |
|
price_rebate = str(round(price_rebate_float / 5000) * 5000) |
|
|
|
elif price_rebate_float > 30000: |
|
price_rebate = str(round(price_rebate_float / 1000) * 1000) |
|
|
|
elif price_rebate_float > 10000: |
|
price_rebate = str(round(price_rebate_float / 100) * 100) |
|
|
|
else: |
|
price_rebate = str(round(price_rebate_float / 10) * 10) |
|
except (ValueError, TypeError): |
|
price_rebate = price_rebate_str |
|
else: |
|
price_rebate = "" |
|
|
|
|
|
if price_rebate_money_order_str: |
|
try: |
|
price_rebate_money_order_float = float(price_rebate_money_order_str) |
|
|
|
if price_rebate_money_order_float > 200000: |
|
price_rebate_money_order = str(round(price_rebate_money_order_float / 5000) * 5000) |
|
|
|
elif price_rebate_money_order_float > 30000: |
|
price_rebate_money_order = str(round(price_rebate_money_order_float / 1000) * 1000) |
|
|
|
elif price_rebate_money_order_float > 10000: |
|
price_rebate_money_order = str(round(price_rebate_money_order_float / 100) * 100) |
|
|
|
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 = "" |
|
|
|
|
|
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] |
|
|
|
|
|
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("") |
|
|
|
else: |
|
|
|
product_info = [stock_amount] |
|
|
|
products.append((name, product_info, full_name)) |
|
|
|
print(f"Toplam {len(products)} ürün yüklendi.") |
|
|
|
|
|
initialize_enhanced_features(OPENAI_API_KEY, products) |
|
|
|
|
|
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_CREDENTIALS_PATH = os.getenv("GOOGLE_CREDENTIALS_PATH") |
|
GOOGLE_FOLDER_ID = "1bE8aMj8-eFGftjMPOF8bKQJAhfHa0BN8" |
|
|
|
|
|
file_lock = threading.Lock() |
|
history_lock = threading.Lock() |
|
global_chat_history = [] |
|
document_content = "" |
|
|
|
|
|
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) |
|
|
|
|
|
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']}") |
|
|
|
|
|
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}") |
|
|
|
|
|
document_thread = threading.Thread(target=download_documents_from_drive, daemon=True) |
|
document_thread.start() |
|
|
|
|
|
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}") |
|
|
|
|
|
def run_scheduler(chat_history): |
|
schedule.every().day.at("03:00").do(clear_log_file) |
|
|
|
while True: |
|
schedule.run_pending() |
|
time.sleep(60) |
|
|
|
|
|
def chatbot_fn(user_message, history, image=None): |
|
if history is None: |
|
history = [] |
|
|
|
|
|
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]}...") |
|
else: |
|
print(f"DEBUG - No warehouse stock data returned") |
|
except Exception as e: |
|
print(f"DEBUG - Warehouse stock error at start: {e}") |
|
|
|
try: |
|
|
|
if image is not None: |
|
user_message = process_image_message(image, user_message) |
|
|
|
|
|
comparison_result = handle_comparison_request(user_message) |
|
if comparison_result: |
|
yield comparison_result |
|
return |
|
|
|
|
|
|
|
|
|
except Exception as e: |
|
print(f"Enhanced features error: {e}") |
|
|
|
|
|
|
|
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}") |
|
|
|
|
|
system_messages = get_prompt_content_only() |
|
|
|
|
|
if document_content: |
|
system_messages.append({"role": "system", "content": f"Dökümanlardan gelen bilgiler: {document_content}"}) |
|
|
|
|
|
|
|
|
|
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']: |
|
|
|
enhanced_response = product_result['response'] |
|
|
|
|
|
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}") |
|
|
|
|
|
if not product_found_improved: |
|
print(f"DEBUG chatbot_fn - Using warehouse stock data for: {user_message}") |
|
|
|
|
|
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'] |
|
|
|
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 |
|
|
|
|
|
final_response = extract_product_info_for_gallery(partial_response) |
|
yield final_response |
|
|
|
|
|
|
|
|
|
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}") |
|
|
|
|
|
with history_lock: |
|
global_chat_history.append({"role": "user", "content": user_message}) |
|
global_chat_history.append({"role": "assistant", "content": partial_response}) |
|
|
|
|
|
def slow_echo(message, history): |
|
for i in range(len(message)): |
|
time.sleep(0.05) |
|
yield "You typed: " + message[: i + 1] |
|
|
|
|
|
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_theme = gr.themes.Base( |
|
primary_hue="red", |
|
secondary_hue="slate", |
|
neutral_hue="slate", |
|
radius_size=gr.themes.sizes.radius_sm, |
|
spacing_size=gr.themes.sizes.spacing_md, |
|
text_size=gr.themes.sizes.text_sm |
|
) |
|
|
|
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 */ |
|
""" |
|
|
|
|
|
storage_js = "" |
|
|
|
|
|
def enhanced_chatbot_fn(message, history, image): |
|
return chatbot_fn(message, history, image) |
|
|
|
|
|
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.") |
|
|
|
|
|
|
|
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" |
|
) |
|
|
|
|
|
|
|
|
|
def respond(message, chat_history): |
|
if not message.strip(): |
|
return "", chat_history |
|
|
|
|
|
if chat_history is None: |
|
chat_history = [] |
|
|
|
|
|
chat_history.append({"role": "user", "content": message}) |
|
yield "", chat_history |
|
|
|
|
|
formatted_history = [] |
|
for msg in chat_history[:-1]: |
|
formatted_history.append(msg) |
|
|
|
try: |
|
|
|
response_generator = chatbot_fn(message, formatted_history, None) |
|
|
|
|
|
response = "" |
|
for partial in response_generator: |
|
response = partial |
|
|
|
|
|
if chat_history[-1]["role"] == "user": |
|
chat_history.append({"role": "assistant", "content": response}) |
|
else: |
|
|
|
chat_history[-1]["content"] = response |
|
yield "", chat_history |
|
|
|
|
|
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}") |
|
|
|
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) |
|
|
|
|
|
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 |
|
|
|
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) |
|
|
|
|
|
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() |
|
|
|
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() |
|
return gr.update(visible=True, value="temp_conversations.json") |
|
|
|
def open_dashboard(): |
|
return gr.HTML(""" |
|
<div style='padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; color: white;'> |
|
<h3>📊 Dashboard Kullanım Talimatları</h3> |
|
<ol style='margin: 15px 0;'> |
|
<li>Yukarıdaki "💾 JSON İndir" butonuna tıkla</li> |
|
<li>conversations.json dosyasını indir</li> |
|
<li>bf_dashboard_json.html dosyasını aç</li> |
|
<li>İndirdiğin JSON dosyasını dashboard'a yükle</li> |
|
</ol> |
|
<p style='margin-top: 15px; font-size: 14px;'> |
|
Dashboard HTML dosyasını almak için:<br> |
|
<a href='https://github.com/yourusername/bf-dashboard' target='_blank' style='color: white; text-decoration: underline;'> |
|
GitHub'dan indir |
|
</a> |
|
</p> |
|
</div> |
|
""") |
|
|
|
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) |
|
|
|
|
|
demo.load(get_conversations_json, outputs=json_display) |
|
|
|
|
|
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''' |
|
<div id="json-data" style="display:none;"> |
|
<script type="application/json" id="conversations-json"> |
|
{json_str} |
|
</script> |
|
</div> |
|
''' |
|
|
|
|
|
with gr.Row(visible=False): |
|
|
|
json_html = gr.HTML( |
|
value=create_json_html(), |
|
visible=False, |
|
elem_id="json_data_container" |
|
) |
|
|
|
|
|
demo.load(create_json_html, outputs=json_html) |
|
|
|
|
|
def get_all_conversations(): |
|
"""API endpoint to get all conversations""" |
|
from conversation_tracker import load_conversations |
|
conversations = load_conversations() |
|
|
|
|
|
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))) |
|
} |
|
|
|
|
|
|
|
|
|
from fastapi import FastAPI |
|
from fastapi.middleware.cors import CORSMiddleware |
|
from fastapi.responses import JSONResponse |
|
import json |
|
|
|
|
|
api = FastAPI() |
|
|
|
|
|
api.add_middleware( |
|
CORSMiddleware, |
|
allow_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) |
|
|
|
|
|
demo = gr.mount_gradio_app(api, demo, path="/") |
|
|
|
if __name__ == "__main__": |
|
import uvicorn |
|
|
|
uvicorn.run(api, host="0.0.0.0", port=7860) |