|
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 |
|
import warnings |
|
|
|
|
|
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) |
|
root = ET.fromstring(response.content) |
|
|
|
products = [] |
|
for item in root.findall('item'): |
|
if item.find('isOptionOfAProduct').text == '1' and item.find('stockAmount').text > '0': |
|
name_words = item.find('rootlabel').text.lower().split() |
|
name = name_words[0] |
|
full_name = ' '.join(name_words) |
|
stock_amount = "stokta" |
|
price = item.find('priceWithTax').text |
|
item_info = (stock_amount, price) |
|
products.append((name, item_info, full_name)) |
|
|
|
|
|
hfapi = os.getenv("hfapi") |
|
if not hfapi: |
|
raise ValueError("hfapi ortam değişkeni ayarlanmamış!") |
|
|
|
create_repo("BF", token=hfapi, repo_type="space", space_sdk="gradio", exist_ok=True) |
|
|
|
global_chat_history = [] |
|
history_lock = threading.Lock() |
|
file_lock = threading.Lock() |
|
last_logged_index = 0 |
|
|
|
def run_scheduler(chat_history_ref): |
|
def scheduled_save_and_upload(): |
|
global last_logged_index |
|
if chat_history_ref: |
|
print(f"Zamanlayıcı tetiklendi: {time.strftime('%Y-%m-%d %H:%M:%S')}") |
|
try: |
|
with file_lock: |
|
with open(LOG_FILE, 'a', encoding='utf-8') as f: |
|
f.write("\n--- Zamanlanmış Kayıt: {} ---\n".format(time.strftime("%Y-%m-%d %H:%M:%S"))) |
|
with history_lock: |
|
new_messages = chat_history_ref[last_logged_index:] |
|
for msg in new_messages: |
|
if msg["role"] in ["user", "assistant"]: |
|
f.write(f"{msg['role'].capitalize()}: {msg['content']}\n") |
|
last_logged_index = len(chat_history_ref) |
|
print(f"Sohbet dosyaya kaydedildi: {os.path.abspath(LOG_FILE)}") |
|
except Exception as e: |
|
print(f"Kayıt hatası: {e}") |
|
time.sleep(5) |
|
return |
|
|
|
HF_REPO_ID = "SamiKoen/BF" |
|
api = HfApi(token=hfapi) |
|
for attempt in range(3): |
|
try: |
|
with file_lock: |
|
api.upload_file( |
|
path_or_fileobj=LOG_FILE, |
|
path_in_repo="chat_logs.txt", |
|
repo_id=HF_REPO_ID, |
|
repo_type="space", |
|
commit_message="Otomatik log güncellemesi - {}".format(time.strftime("%Y-%m-%d %H:%M:%S")) |
|
) |
|
print(f"Log dosyası HF'ye yüklendi: {LOG_FILE}") |
|
break |
|
except Exception as e: |
|
print(f"HF yükleme hatası (deneme {attempt+1}/3): {e}") |
|
time.sleep(5) |
|
else: |
|
print("HF yükleme başarısız, tüm denemeler tamamlandı.") |
|
print(f"Zamanlanmış işlem tamamlandı: {time.strftime('%H:%M:%S')}") |
|
|
|
schedule.every().day.at("11:32").do(scheduled_save_and_upload) |
|
schedule.every().day.at("15:15").do(scheduled_save_and_upload) |
|
schedule.every().day.at("15:30").do(scheduled_save_and_upload) |
|
schedule.every().day.at("17:32").do(scheduled_save_and_upload) |
|
schedule.every().day.at("19:15").do(scheduled_save_and_upload) |
|
schedule.every().day.at("21:30").do(scheduled_save_and_upload) |
|
print("Zamanlayıcı başlatıldı") |
|
while True: |
|
schedule.run_pending() |
|
time.sleep(60) |
|
|
|
def chatbot_fn(user_message, history): |
|
if history is None: |
|
history = [] |
|
|
|
|
|
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 = [ |
|
{"role": "system", "content": "Sen bir Trek bisiklet satış ve danışman asistanısın; Trek, Electra bisikletler, Bontrager aksesuarlar, Bryton yol bilgisayarları ve Trieye gözlükler konusunda uzmanım."}, |
|
{"role": "system", "content": "Başka markalar (ör. Specialized, Giant) hakkında bilgi vermem ve yorum yapmam."}, |
|
{"role": "system", "content": "Bir önceki sohbeti unuturum ve yalnızca aşağıda yan yana yazılan bilgilerden cevap veririm."}, |
|
{"role": "system", "content": "Stok kontrolüm gerçek zamanlıdır; stokta yoksa 'yok' derim."}, |
|
{"role": "system", "content": "Model adı rakamsız gelirse (ör. Madone SL), rakam eklenmesini rica ederim (ör. Madone SL 7)."}, |
|
{"role": "system", "content": "Madone SLR sorulursa Gen 7 kabul ederim."}, |
|
{"role": "system", "content": "Yol bisikletleri (Madone, Émonda, Domane, Checkpoint, Speed Concept) boyları 47-64 cm'dir."}, |
|
{"role": "system", "content": "Dağ bisikletleri (Marlin, Roscoe, Procaliber, Supercaliber, Fuel EX) boyları XXS-XL'dir."}, |
|
{"role": "system", "content": "Şehir bisikletleri FX ve DS’dir; elektrikli modeller Powerfly, Rail, Fuel EXe, Domane+ SLR, Verve+’dır; gravel için Checkpoint vardır."}, |
|
{"role": "system", "content": "Yeni Madone Gen 8 (27 Haziran 2024): Émonda kadar hafif (900 Serisi OCLV Karbon, 320g hafif) ve Madone Gen 7 kadar hızlıdır."}, |
|
{"role": "system", "content": "IsoFlow ile %80 konforludur; SL ekonomik, SLR üst seviyedir."}, |
|
{"role": "system", "content": "Stok ve fiyatlar için www.trekbisiklet.com.tr’ye bakarım; farklı boy/renk varsa söylerim, yoksa başka model öneririm."}, |
|
{"role": "system", "content": "En büyük/küçük boy sorusuna stoktan cevap veririm; üyelere özel fiyatlar için siteye üye olun derim."}, |
|
{"role": "system", "content": "İstanbul’da Caddebostan mağazamız (0216 6292432, 10:00-19:00, Bike Fit 3500 TL) ve Ortaköy mağazamız (0212 2271015, 10:00-19:00) bulunmaktadır."}, |
|
{"role": "system", "content": "Sarıyer mağazamız (0542 1371080, 10:00-19:00) elektrikli bisiklet odaklıdır."}, |
|
{"role": "system", "content": "İzmir Alsancak mağazası Nisan 2025’te açılacak ve Pazar günleri kapalı olacak."}, |
|
{"role": "system", "content": "Bontrager aksesuarlar, Bryton Rider S800 stokta ve Trieye gözlükler Norveç menşeli geri görüş aynalıdır."}, |
|
{"role": "system", "content": "Bike Finder olarak adım adım sorarım: 1) kategori (yol, dağ, hibrit, gravel, elektrikli), 2) amaç (ulaşım, spor, yarış), 3) özellik (performans, konfor)."}, |
|
{"role": "system", "content": "4) zemin (asfalt, off-road), 5) boy ve iç bacak ölçüsü, 6) ek tercih (renk, bütçe) sorularını sorarım; sonra stoktan öneri yapar www.trekbisiklet.com.tr’ye yönlendiririm."}, |
|
{"role": "system", "content": "Sipariş süreci: sepete ekle, bilgi gir, ödeme yap, tamamla; Trek kadroları ömür boyu garantilidir; 2000’den beri Alatin Bisiklet dağıtıyor; ASLA DURMA ve TREK RMK DYNAMIS’e sponsoruz; takas için www.bikeexchangehub.com, canlı sohbet için sitedeki yeşil düğme, bayiler için www.alatin.com.tr/sayfa/bayilerimiz/ kullanılır."} |
|
] |
|
|
|
input_words = user_message.lower().split() |
|
for product_info in products: |
|
if product_info[0] in input_words: |
|
new_msg = f"{product_info[2]} {product_info[1][0]} ve fiyatı EURO {product_info[1][1]}" |
|
system_messages.append({"role": "system", "content": new_msg}) |
|
|
|
messages = system_messages + history + [{"role": "user", "content": user_message}] |
|
|
|
payload = { |
|
"model": "gpt-4o", |
|
"messages": messages, |
|
"temperature": 0.7, |
|
"top_p": 0.9, |
|
"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 [{"role": "user", "content": user_message}, {"role": "assistant", "content": "Bir hata oluştu."}] |
|
return |
|
|
|
partial_response = "" |
|
current_pair = [{"role": "user", "content": user_message}, {"role": "assistant", "content": ""}] |
|
|
|
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'] |
|
current_pair[1]["content"] = partial_response |
|
yield current_pair |
|
except json.JSONDecodeError as e: |
|
print(f"JSON parse hatası: {e} - Chunk: {chunk_str}") |
|
elif chunk_str == "data: [DONE]": |
|
break |
|
|
|
|
|
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}) |
|
|
|
yield current_pair |
|
|
|
|
|
def slow_echo(message, history): |
|
for i in range(len(message)): |
|
time.sleep(0.05) |
|
yield [{"role": "user", "content": message}, {"role": "assistant", "content": "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() |
|
|
|
demo = gr.ChatInterface( |
|
fn=chat_fn, |
|
title="Trek Asistanı", |
|
|
|
theme="default", |
|
type="messages", |
|
flagging_mode="manual", |
|
flagging_options=["Doğru", "Yanlış", "Emin değilim", "Diğer"], |
|
save_history=True |
|
) |
|
|
|
if __name__ == "__main__": |
|
demo.launch(debug=True, share=True) |