Update app.py
Browse files
app.py
CHANGED
@@ -1,100 +1,256 @@
|
|
1 |
-
import gradio as gr
|
2 |
import os
|
3 |
-
import
|
4 |
-
import
|
5 |
-
|
6 |
-
import
|
|
|
|
|
|
|
7 |
|
8 |
-
# OpenAI
|
9 |
-
|
10 |
-
client = OpenAI(api_key=OPENAI_API_KEY)
|
11 |
|
12 |
-
#
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
history = []
|
17 |
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
"messages": messages,
|
34 |
-
"temperature": 0.2,
|
35 |
-
"stream": True,
|
36 |
-
}
|
37 |
-
headers = {
|
38 |
-
"Content-Type": "application/json",
|
39 |
-
"Authorization": f"Bearer {OPENAI_API_KEY}"
|
40 |
-
}
|
41 |
-
|
42 |
-
response = requests.post(API_URL, headers=headers, json=payload, stream=True)
|
43 |
-
if response.status_code != 200:
|
44 |
-
yield "Bir hata oluştu.", None
|
45 |
-
return
|
46 |
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
|
|
62 |
break
|
|
|
63 |
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
|
72 |
-
|
|
|
|
|
|
|
|
|
73 |
try:
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
except Exception as e:
|
78 |
-
print(f"
|
79 |
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
|
85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
|
99 |
-
|
100 |
-
|
|
|
|
|
1 |
import os
|
2 |
+
import openai
|
3 |
+
import gradio as gr
|
4 |
+
import xml.etree.ElementTree as ET
|
5 |
+
import pandas as pd # Excel okuma için pandas
|
6 |
+
import docx # Word (.docx) dosyalarını okuma
|
7 |
+
import threading
|
8 |
+
import time
|
9 |
|
10 |
+
# OpenAI API anahtarını ortam değişkeninden al
|
11 |
+
openai.api_key = os.getenv("OPENAI_API_KEY")
|
|
|
12 |
|
13 |
+
# OpenAI modeli ayarları
|
14 |
+
OPENAI_CHAT_MODEL = "gpt-4" # Metin tabanlı GPT modeli
|
15 |
+
TTS_MODEL = "tts-1" # OpenAI TTS modeli
|
16 |
+
TTS_VOICE = "nova" # Tercih edilen ses
|
|
|
17 |
|
18 |
+
# Trek ürün bilgileri XML dosyasını yükle (stok ve fiyat kontrolü için)
|
19 |
+
try:
|
20 |
+
trek_tree = ET.parse("trek_products.xml")
|
21 |
+
trek_root = trek_tree.getroot()
|
22 |
+
except Exception as e:
|
23 |
+
trek_root = None
|
24 |
+
print(f"XML yüklenirken hata oluştu: {e}")
|
25 |
|
26 |
+
# Sistem mesajı ve özel kurallar (sohbetin her başında yer alacak temel yönergeler)
|
27 |
+
SYSTEM_PROMPT = (
|
28 |
+
"You are Trek Chatbot, an AI assistant that provides information about Trek products and performs various tasks. "
|
29 |
+
"Follow the given rules and guidelines. If the user asks for Trek product info, provide stock and price from the XML database. "
|
30 |
+
"If the user requests to read a file, fetch its content from Google Drive. "
|
31 |
+
"Always respond helpfully in the same language as the user's question, and keep a friendly tone."
|
32 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
|
34 |
+
def check_trek_product_info(product_id):
|
35 |
+
"""
|
36 |
+
Verilen ürün kodu/ID için Trek XML veritabanından fiyat ve stok bilgisini döndürür.
|
37 |
+
Bulunamazsa (None, None) döndürür.
|
38 |
+
"""
|
39 |
+
price = None
|
40 |
+
stock = None
|
41 |
+
if trek_root is None:
|
42 |
+
return price, stock
|
43 |
+
# XML yapısında <product><id>...</id><price>...</price><stock>...</stock></product> şeklinde olduğunu varsayıyoruz
|
44 |
+
for product in trek_root.findall("product"):
|
45 |
+
pid = product.find("id").text if product.find("id") is not None else ""
|
46 |
+
if pid and product_id.lower() == pid.lower():
|
47 |
+
# Ürün eşleşti, fiyat ve stok bilgilerini al
|
48 |
+
price_elem = product.find("price")
|
49 |
+
stock_elem = product.find("stock")
|
50 |
+
price = price_elem.text if price_elem is not None else None
|
51 |
+
stock = stock_elem.text if stock_elem is not None else None
|
52 |
break
|
53 |
+
return price, stock
|
54 |
|
55 |
+
def read_file_from_drive(file_path):
|
56 |
+
"""
|
57 |
+
Google Drive üzerinde bulunan bir Excel (.xlsx) veya Word (.docx) dosyasını okuyup içeriğini döndürür.
|
58 |
+
"""
|
59 |
+
content = ""
|
60 |
+
try:
|
61 |
+
if file_path.lower().endswith((".xlsx", ".xls")):
|
62 |
+
# Excel dosyasını oku
|
63 |
+
df = pd.read_excel(file_path)
|
64 |
+
# Excel içeriğini CSV formatında metne dönüştür (alternatif: istenen şekilde işlenebilir)
|
65 |
+
content = df.to_csv(index=False)
|
66 |
+
elif file_path.lower().endswith(".docx"):
|
67 |
+
# Word dosyasını oku
|
68 |
+
doc = docx.Document(file_path)
|
69 |
+
full_text = [para.text for para in doc.paragraphs]
|
70 |
+
content = "\n".join(full_text).strip()
|
71 |
+
else:
|
72 |
+
# Metin dosyası olarak oku (Excel/Word dışındaki dosyalar için genel durum)
|
73 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
74 |
+
content = f.read()
|
75 |
+
except Exception as e:
|
76 |
+
content = f"Hata: Dosya okunurken sorun oluştu - {e}"
|
77 |
+
return content
|
78 |
|
79 |
+
def log_to_huggingface(log_content):
|
80 |
+
"""
|
81 |
+
Konuşma geçmişini veya belirli bir log bilgisini Hugging Face'e yükler.
|
82 |
+
Bu örnekte işlev gövdesi boş bırakılmıştır; gerçek uygulamada API çağrısı yapılmalıdır.
|
83 |
+
"""
|
84 |
try:
|
85 |
+
# TODO: Hugging Face'e yükleme kodunu buraya entegre edin (örneğin huggingface_hub kullanarak).
|
86 |
+
# Örnek: HfApi().upload_file(...) ile log_content içeriğini bir depo/dosya olarak yükleyebilirsiniz.
|
87 |
+
pass
|
88 |
except Exception as e:
|
89 |
+
print(f"Hugging Face log yüklemede hata: {e}")
|
90 |
|
91 |
+
def periodic_log(interval=300):
|
92 |
+
"""
|
93 |
+
Sohbet geçmişini belirli aralıklarla (interval saniye) Hugging Face'e gönderen arkaplan görevi.
|
94 |
+
"""
|
95 |
+
while True:
|
96 |
+
time.sleep(interval)
|
97 |
+
try:
|
98 |
+
# Sistem mesajı haricindeki konuşmaları derle
|
99 |
+
conversation_text = "\n".join(
|
100 |
+
[f"{m['role'].capitalize()}: {m['content']}"
|
101 |
+
for m in conversation_log if m.get('role') != 'system']
|
102 |
+
)
|
103 |
+
if conversation_text:
|
104 |
+
log_to_huggingface(conversation_text)
|
105 |
+
except Exception as e:
|
106 |
+
print(f"Periyodik loglama hatası: {e}")
|
107 |
|
108 |
+
def generate_text_response(conversation):
|
109 |
+
"""
|
110 |
+
OpenAI ChatCompletion API ile güncel konuşma geçmişine göre metin yanıtı üretir.
|
111 |
+
"""
|
112 |
+
try:
|
113 |
+
response = openai.ChatCompletion.create(
|
114 |
+
model=OPENAI_CHAT_MODEL,
|
115 |
+
messages=conversation
|
116 |
+
)
|
117 |
+
answer = response['choices'][0]['message']['content'].strip()
|
118 |
+
except Exception as e:
|
119 |
+
# Hata durumunda özür dileyen bir mesaj döndür (hata mesajını da içerebilir)
|
120 |
+
answer = f"Üzgünüm, yanıt üretirken bir hata oluştu: {e}"
|
121 |
+
return answer
|
122 |
|
123 |
+
def generate_audio_from_text(text):
|
124 |
+
"""
|
125 |
+
Verilen metni OpenAI TTS modeliyle sese dönüştürür ve MP3 dosya yolunu döndürür.
|
126 |
+
TTS başarısız olursa None döndürür.
|
127 |
+
"""
|
128 |
+
try:
|
129 |
+
audio_response = openai.Audio.create(
|
130 |
+
model=TTS_MODEL,
|
131 |
+
voice=TTS_VOICE,
|
132 |
+
text=text,
|
133 |
+
response_format="audio/mp3"
|
134 |
+
)
|
135 |
+
# audio_response içeriğinden ses verisini al (varsayılan anahtar adı 'audio' olarak düşünülmüştür)
|
136 |
+
audio_content = audio_response.get('audio')
|
137 |
+
if audio_content is None:
|
138 |
+
# Bazı durumlarda audio_response doğrudan binary içerebilir, o durumda audio_content None olacaktır.
|
139 |
+
audio_content = audio_response # Mümkünse direkt kullan
|
140 |
+
|
141 |
+
# Elde edilen ses verisini bir MP3 dosyasına kaydet
|
142 |
+
filename = f"response_{int(time.time())}.mp3"
|
143 |
+
with open(filename, "wb") as f:
|
144 |
+
f.write(audio_content)
|
145 |
+
return filename
|
146 |
+
except Exception as e:
|
147 |
+
# Hata durumunda konsola logla ve None döndür
|
148 |
+
print(f"TTS generation failed: {e}")
|
149 |
+
return None
|
150 |
+
|
151 |
+
# Gradio arayüzünü tanımla
|
152 |
+
with gr.Blocks() as demo:
|
153 |
+
gr.Markdown("## Trek Chatbot (Metin + Sesli Yanıt)") # Başlık
|
154 |
+
|
155 |
+
chatbot = gr.Chatbot(label="Sohbet Geçmişi") # Sohbet geçmişini gösterecek bileşen
|
156 |
+
user_input = gr.Textbox(label="Mesajınız") # Kullanıcıdan metin girişi
|
157 |
+
send_btn = gr.Button("Gönder") # Mesaj gönderme butonu
|
158 |
+
clear_btn = gr.Button("Konuşmayı Sıfırla") # Sohbeti temizleme butonu
|
159 |
+
|
160 |
+
# Durum (state) değişkenleri: tam konuşma geçmişi (sistem mesajı dahil) ve sadece kullanıcı-asistan çiftleri (görüntüleme için)
|
161 |
+
conversation_state = gr.State([{"role": "system", "content": SYSTEM_PROMPT}]) # Sistem mesajıyla başlat
|
162 |
+
chat_history_state = gr.State([]) # Kullanıcı ve asistan mesaj çiftlerini tutacak (görüntüleme amacıyla)
|
163 |
+
|
164 |
+
audio_output = gr.Audio(label="Sesli Yanıt", elem_id="audio_out") # Sesli yanıt için çıktı bileşeni
|
165 |
+
|
166 |
+
# Global değişken (referans): konuşma geçmişi listesi (sistem dahil) - sohbet geçmişini kilitleyerek yönetiyoruz
|
167 |
+
conversation_log = conversation_state.value # conversation_state içindeki liste nesnesine referans
|
168 |
+
|
169 |
+
def respond(user_message, conversation_state_value, chat_history_value):
|
170 |
+
"""
|
171 |
+
Kullanıcının mesajına yanıt üretir (metin ve ses).
|
172 |
+
conversation_state_value: tam geçmiş (sistem mesajı dahil),
|
173 |
+
chat_history_value: sohbet geçmişi (görüntüleme için kullanıcı-bot çiftleri).
|
174 |
+
"""
|
175 |
+
# Kullanıcı mesajını konuşma geçmişine ekle (OpenAI modeline tam geçmişi vereceğiz)
|
176 |
+
conversation_state_value.append({"role": "user", "content": user_message})
|
177 |
+
|
178 |
+
bot_answer = None # Asistan yanıtı (metin)
|
179 |
+
|
180 |
+
# Özel kurallar: Trek ürün stok/fiyat sorgusu
|
181 |
+
if "stok" in user_message.lower() or "fiyat" in user_message.lower():
|
182 |
+
# Mesajda stok veya fiyat kelimesi geçiyorsa, ürünü bulmaya çalış
|
183 |
+
words = user_message.split()
|
184 |
+
product_id = None
|
185 |
+
for w in words:
|
186 |
+
# Basit yaklaşım: mesajdaki ilk alfasayısal kelimeyi ürün kodu varsayıyoruz
|
187 |
+
if w.isalnum():
|
188 |
+
product_id = w
|
189 |
+
break
|
190 |
+
if product_id:
|
191 |
+
price, stock = check_trek_product_info(product_id)
|
192 |
+
if price is not None:
|
193 |
+
# Ürün bulundu, stok ve fiyat bilgisi ile yanıt oluştur
|
194 |
+
stock_status = "stokta var" if stock and stock != "0" else "stokta yok"
|
195 |
+
bot_answer = f"{product_id} ürünü {stock_status}, fiyatı {price}."
|
196 |
+
else:
|
197 |
+
# Ürün bulunamadı
|
198 |
+
bot_answer = "Üzgünüm, bu ürünün bilgisine ulaşılamadı."
|
199 |
+
|
200 |
+
# Özel kurallar: Google Drive dosya okuma isteği
|
201 |
+
if bot_answer is None and "drive" in user_message.lower():
|
202 |
+
# Mesajda 'drive' geçiyorsa, son kelimeyi dosya yolu olarak al (örnek basit yaklaşım)
|
203 |
+
file_path = user_message.split()[-1]
|
204 |
+
file_content = read_file_from_drive(file_path)
|
205 |
+
bot_answer = f"{file_path} dosyasının içeriği:\n{file_content}"
|
206 |
+
|
207 |
+
# Genel durum: Özel bir durum yoksa, GPT modelinden yanıt al
|
208 |
+
if bot_answer is None:
|
209 |
+
bot_answer = generate_text_response(conversation_state_value)
|
210 |
+
|
211 |
+
# Asistanın metin yanıtını konuşma geçmişine ekle (model bağlamı için)
|
212 |
+
conversation_state_value.append({"role": "assistant", "content": bot_answer})
|
213 |
+
|
214 |
+
# Hugging Face'e log kaydet (arkaplanda, asenkron şekilde)
|
215 |
+
log_text = f"User: {user_message}\nAssistant: {bot_answer}\n"
|
216 |
+
threading.Thread(target=log_to_huggingface, args=(log_text,)).start()
|
217 |
+
|
218 |
+
# Metin yanıtını TTS ile sese dönüştür
|
219 |
+
audio_file = generate_audio_from_text(bot_answer)
|
220 |
+
|
221 |
+
# Sohbet geçmişini güncelle (görsel için kullanıcı-bot çiftini ekle)
|
222 |
+
chat_history_value = chat_history_value or [] # None ise boş liste yap
|
223 |
+
chat_history_value.append((user_message, bot_answer))
|
224 |
+
|
225 |
+
# conversation_state_value ve chat_history_value, Gradio state'lerini güncellemek için döndürülür
|
226 |
+
# audio_file ise ses dosyasının yolu ya da None (Gradio audio bileşenini güncellemek için)
|
227 |
+
return conversation_state_value, chat_history_value, audio_file
|
228 |
+
|
229 |
+
# Gönder butonuna tıklandığında veya Enter'a basıldığında respond fonksiyonunu çağır
|
230 |
+
send_btn.click(
|
231 |
+
respond,
|
232 |
+
inputs=[user_input, conversation_state, chat_history_state],
|
233 |
+
outputs=[conversation_state, chatbot, audio_output]
|
234 |
+
)
|
235 |
+
user_input.submit(
|
236 |
+
respond,
|
237 |
+
inputs=[user_input, conversation_state, chat_history_state],
|
238 |
+
outputs=[conversation_state, chatbot, audio_output]
|
239 |
+
)
|
240 |
+
|
241 |
+
# Konuşmayı sıfırla (temizle) butonu işlevi
|
242 |
+
def clear_history():
|
243 |
+
"""Sohbet geçmişini ve durumu sıfırlar."""
|
244 |
+
return [{"role": "system", "content": SYSTEM_PROMPT}], [], None
|
245 |
+
clear_btn.click(
|
246 |
+
clear_history,
|
247 |
+
inputs=None,
|
248 |
+
outputs=[conversation_state, chatbot, audio_output],
|
249 |
+
queue=False
|
250 |
+
)
|
251 |
+
|
252 |
+
# Arkaplanda periyodik loglama işlemini başlat (daemon thread, uygulamayı engellemez)
|
253 |
+
threading.Thread(target=periodic_log, args=(300,), daemon=True).start()
|
254 |
|
255 |
+
# Gradio arayüzünü başlat
|
256 |
+
demo.launch()
|