BF / app.py
SamiKoen's picture
Update app.py
7982c39 verified
raw
history blame
11.7 kB
import gradio as gr
import os
import json
import requests
import schedule
import time
import threading
from huggingface_hub import HfApi, create_repo
import warnings
from bs4 import BeautifulSoup # HTML ayrıştırma için
import spaces
# 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ış!")
# Hugging Face token
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 = [] # Tüm sohbet geçmişi
history_lock = threading.Lock() # Global geçmiş için kilit
file_lock = threading.Lock() # Dosya yazma için kilit
last_logged_index = 0 # Son kaydedilen mesaj indeksi
# Web sayfasından içerik çekme fonksiyonu (dosyaya kaydetmeden)
def scrape_web_page(url):
try:
response = requests.get(url)
response.raise_for_status() # Hata varsa exception fırlatır
soup = BeautifulSoup(response.content, 'html.parser')
# Tüm metni çek (örneğin, <p> etiketlerinden)
paragraphs = soup.find_all('p')
web_content = "Web Sayfasından Çekilen İçerik:\n"
for i, p in enumerate(paragraphs, 1):
web_content += f"Paragraf {i}: {p.get_text().strip()}\n"
return web_content
except Exception as e:
print(f"Web scraping hatası ({url}): {e}")
return "Web sayfasından veri çekilemedi."
# Web’den doğrudan veri çekme (dosyaya kaydetmeden)
web_url = "https://example.com" # Vereceğiniz gerçek URL’yi buraya ekleyin
document_content = scrape_web_page(web_url)
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)
@spaces.GPU(duration=120)
def chatbot_fn(user_message, history):
if history is None:
history = []
# 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ı
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": "Web sayfasından gelen bilgilere tamamen sadık kal. Bu veriler dışında bilgi uydurma veya tahminde bulunma. Eğer sorunun cevabı verilerde yoksa, 'Bilgi mevcut değil' de."},
{"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."}
]
# Döküman verilerini sistem mesajlarına ekle
if document_content:
system_messages.append({"role": "system", "content": f"Web’den gelen bilgiler: {document_content}"})
messages = system_messages + history + [{"role": "user", "content": user_message}]
payload = {
"model": "gpt-4o",
"messages": messages,
"temperature": 0.2, # Daha az yaratıcılık, daha fazla doğruluk
"top_p": 0.5, # Daha odaklanmış cevaplar
"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
# 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})
yield current_pair
# Slow echo (test için)
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]}]
# 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()
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)