SamiKoen commited on
Commit
cafea30
·
verified ·
1 Parent(s): 3da3a1c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +512 -365
app.py CHANGED
@@ -6,414 +6,561 @@ import xml.etree.ElementTree as ET
6
  import schedule
7
  import time
8
  import threading
9
- from huggingface_hub import HfApi
10
  import warnings
11
  import pandas as pd
12
  from docx import Document
 
13
  from google.oauth2.service_account import Credentials
14
  from googleapiclient.discovery import build
15
  from googleapiclient.http import MediaIoBaseDownload
16
  import io
17
- from datetime import datetime
18
- from typing import List, Dict, Tuple, Optional
 
19
 
20
- warnings.filterwarnings("ignore")
 
21
 
22
- # ==================== GLOBAL DEĞİŞKENLER ====================
23
  LOG_FILE = '/data/chat_logs.txt' if os.path.exists('/data') else 'chat_logs.txt'
 
 
 
24
  API_URL = "https://api.openai.com/v1/chat/completions"
25
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
26
- HF_TOKEN = os.getenv("hfapi")
 
 
 
 
 
 
27
 
28
- # Global değişkenler
29
  products = []
30
- global_chat_history = []
31
- history_lock = threading.Lock()
32
- file_lock = threading.Lock()
33
- last_logged_index = 0
34
-
35
- # ==================== ÜRÜN YÜKLEMESİ ====================
36
- def load_products():
37
- """Trek ürünlerini yükle"""
38
- global products
39
- try:
40
- url = 'https://www.trekbisiklet.com.tr/output/8582384479'
41
- response = requests.get(url, verify=False)
42
- root = ET.fromstring(response.content)
43
 
44
- for item in root.findall('item'):
45
- name_words = item.find('rootlabel').text.lower().split()
46
- name = name_words[0]
47
- full_name = ' '.join(name_words)
48
-
49
- stock_amount = "stokta" if item.find('stockAmount').text > '0' else "stokta değil"
50
-
51
- if stock_amount == "stokta":
52
- # Fiyat bilgileri
53
- price_str = item.find('priceTaxWithCur').text if item.find('priceTaxWithCur') is not None else "0"
54
- price_eft_str = item.find('priceEft').text if item.find('priceEft') is not None else ""
55
- price_rebate_str = item.find('priceRebateWithTax').text if item.find('priceRebateWithTax') is not None else ""
56
- price_rebate_money_order_str = item.find('priceRebateWithMoneyOrderWithTax').text if item.find('priceRebateWithMoneyOrderWithTax') is not None else ""
57
-
58
- # Fiyat yuvarlama
59
- def round_price(price_str):
60
- try:
61
- price_float = float(price_str)
62
- if price_float > 200000:
63
- return str(round(price_float / 5000) * 5000)
64
- elif price_float > 30000:
65
- return str(round(price_float / 1000) * 1000)
66
- elif price_float > 10000:
67
- return str(round(price_float / 100) * 100)
68
- else:
69
- return str(round(price_float / 10) * 10)
70
- except:
71
- return price_str
72
-
73
- price = round_price(price_str)
74
- price_eft = round_price(price_eft_str) if price_eft_str else ""
75
- price_rebate = round_price(price_rebate_str) if price_rebate_str else ""
76
- price_rebate_money_order = round_price(price_rebate_money_order_str) if price_rebate_money_order_str else ""
77
-
78
- if not price_eft:
79
- try:
80
- price_eft = str(round(float(price) * 0.975))
81
- except:
82
- price_eft = ""
83
-
84
- product_link = item.find('productLink').text if item.find('productLink') is not None else ""
85
-
86
- item_info = (stock_amount, price, product_link, price_eft, price_rebate, price_rebate_money_order)
87
  else:
88
- item_info = (stock_amount, "", "", "", "", "")
89
-
90
- products.append((name, item_info, full_name))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
- except Exception as e:
93
- print(f"Ürün yükleme hatası: {e}")
94
-
95
- # ==================== KADRO BOYU HESAPLAYICI ====================
96
- FRAME_SIZE_CHARTS = {
97
- "road": [
98
- (155, 165, "XS"),
99
- (165, 172, "S"),
100
- (172, 180, "M"),
101
- (180, 185, "ML"),
102
- (185, 190, "L"),
103
- (190, 200, "XL"),
104
- ],
105
- "gravel": [ # Eskiden 52-54-56-58 gibi cm yazardık, artık XS-XL!
106
- (155, 165, "XS"),
107
- (165, 172, "S"),
108
- (172, 180, "M"),
109
- (180, 185, "ML"),
110
- (185, 190, "L"),
111
- (190, 200, "XL"),
112
- ],
113
- "mtb": [
114
- (155, 165, "XS"),
115
- (165, 172, "S"),
116
- (172, 180, "M"),
117
- (180, 185, "ML"),
118
- (185, 190, "L"),
119
- (190, 200, "XL"),
120
- ],
121
- "hybrid": [
122
- (155, 165, "XS"),
123
- (165, 172, "S"),
124
- (172, 180, "M"),
125
- (180, 185, "ML"),
126
- (185, 190, "L"),
127
- (190, 200, "XL"),
128
- ],
129
- }
130
 
 
 
 
 
 
 
 
 
 
131
 
132
- def suggest_frame_size(height_cm: int, inseam_cm: Optional[int], bike_type: str) -> str:
133
- chart = FRAME_SIZE_CHARTS.get(bike_type, FRAME_SIZE_CHARTS["road"])
134
- for lower, upper, size in chart:
135
- if lower <= height_cm <= upper:
136
- return size
137
- return "Lütfen doğru boy aralığını giriniz."
138
-
139
- def kadro_boyu_hesapla(height: int, inseam: Optional[int], bike_type: str) -> str:
140
- if not height:
141
- return "Boyunuzu giriniz."
142
- size = suggest_frame_size(height, inseam, bike_type)
143
- explanation = f"{bike_type.title()} (ör: {bike_type.title()} bisikleti) için önerilen kadro boyu: **{size}**"
144
- if inseam and inseam > 0:
145
- if bike_type == "road":
146
- calculated = round(inseam * 0.67)
147
- explanation += f"\n\nİç bacak boyuna göre matematiksel kadro boyu: **{calculated} cm**"
148
- elif bike_type == "mtb":
149
- calculated = round(inseam * 0.226)
150
- explanation += f"\n\nİç bacak boyuna göre matematiksel kadro boyu: **{calculated} inç**"
151
- return explanation
152
-
153
- # ==================== SİSTEM MESAJLARI ====================
154
- system_messages = [
155
- {"role": "system", "content": "Bir önceki sohbeti unut. Vereceğin ürün bilgisi, bu bilginin içinde yan yana yazmıyorsa veya arada başka bilgiler yazıyor ise, o bilgiyi vermeyeceksin çünkü o bilgi yanlıştır. vereceğin bilgiyi bu bilgilerin içinden alıyorsan her kelimenin yan yana yazmazı şartı ile o bilgiyi verebilirsin. Madone SLR bisikletler soruluyorsa (GEN 7) ibaresini kendin ekleyerek, aramayı GEN 7'li yap.Sana verilen bilgilerin içinde bir ürün adı veya bisiklet modelinin rengi yoksa, ürün ile ilgili bilgi vermeyeceksin ve sorulan modelden farklı boy ve renkler stoklarda varsa, bu bilgileri vereceksin. Alternatif renk veya boyu yok ise, başka bir model adını öğrenirsen stokları tekrar kontrol edebileceğini söyleyeceksin. Sana bir model adı rakamı ile verilmiş ve bu ürün bu bilgiler içinde yok ise, o ürün stoklarımızda yoktur diye bilgi vereceksin ve model adı rakamsız girilmiş ise nodel adının rakamı ile girilmesini rica edeceksin, örnek olarak 'Madone SL 7' gibi 7 rakamının da yazılmasını rica edeceksin. Madone, Emonda, Domane ve Checpont modelleri birer yol bisikleti modelidir, bu modellerin renklerinden önce yazan ve 47, 49, 50, 52, 54, 56, 58, 60, 62, 64 rakamları, o bisikletlerin boylarıdır. Bu bilgi içindeki renkler ise o ürünlerin renkleridir. Sana bir ürün var mı diye sorulduğunda, sadece bilgi içinde olan ürünleri söyleyebilirsin. Stoklarımızda yok ise o ürün ile ilgili bilgi vermeyeceksin. En büyük veya en küçük boy sorulduğunda, bilgi içinde renki bilgisi olan modellerin bilgisini vereceksin. Gerçek zamanlı stok bilgilerine erişme yeteneğin var. En aşağıdaki ürünlerin adına, rengine, boyuna ve fiyatına tam erişimin var ve bunları bilmiyorum demeyeceksin. Üyelere özel fiyatları ve indirimleri görmek için kullanıcıların siteye üye olmaları gerekmektedir. Sen bir AI Trek marka bisiklet uzmanı, bilir kişisi ve asistanısın.Trek ve Electra bisikletler konusunda uzmanım.4cü şubemizi İzmirde açtık.İzmir adresi: Sezer Doğan Sok. The Kar Suits 14A Alsancak Konak İzmir. İzmir şubesinin telefon numarası:0543 936 2335 . İstanbul'da üç Trek mağazamız var: Caddebostan, Ortaköy ve Sarıyer. Ortaköy mağazası 10.00-19.00 saatleri arasında açık. ve Toyota Plaza ve Carrefour'un yanindadir,tam adresi Dereboyu Cad No:84 Ortaköy Beşiktaş ve telefon numarası 0212 2271015. Caddebostan mağazası, Prof. Dr. Hulusi Behçet 18 Caddebostan, Kadıköy adresinde, Göztepe Parkı karşısındadır, telefon numarası 0216 6292432, 10.00-19.00 saatleri arasında açık. Tüm mağazalar Pazar günü kapalıdır. Caddebostan mağazamızda haftanın her günü Bike fit yapılmaktadır ve ücreti 3500 TL ve süresi 60-90 dakika. Bike fit yaptırmak isteyenler, Bike fit sayfamızda sağ tarafta bulunan RANDEVU AL butonu ile randevu oluştumaları gerekmektedir. Sarıyer mağazamızın adresi şöyledir: Mareşal Fevzi Çakmak Cad. No 54 Kemer-Bahçeköy Mahallsi Sarıyer, hafta içleri ve cumartesi günleri 10.00 ile 19.00 saatleri arasında hizmet vermektedir. Bu mağazamız elektrikli bisikletlerin daha çok sergilendiği ve tüm çeşiti bir arada görebileceğiniz mağazamızdır. Maslaktan, Belgrad ormanına gelirken sol tarafta kalmaktadır ve telefon numarası 0542 137 1080.."},
156
- # ... Diğer sistem mesajları aynen yukarıdaki gibi devam ...
157
- ]
158
-
159
- # ==================== GOOGLE DRIVE ====================
160
  def authenticate_google_drive():
161
- """Google Drive kimlik doğrulama"""
 
 
 
162
  try:
163
- service_account_json = os.getenv("SERVICE_ACCOUNT_JSON")
164
- if not service_account_json:
165
- return None
166
-
167
  json_data = json.loads(service_account_json)
168
- creds = Credentials.from_service_account_info(
169
- json_data,
170
- scopes=['https://www.googleapis.com/auth/drive.readonly']
171
- )
172
- return build('drive', 'v3', credentials=creds)
173
  except Exception as e:
174
- print(f"Google Drive auth hatası: {e}")
175
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
- # ==================== LOG FONKSİYONLARI ====================
178
- def log_message(role, content):
179
- """Mesajları logla"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  try:
181
  with file_lock:
182
  with open(LOG_FILE, 'a', encoding='utf-8') as f:
183
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
184
- f.write(f"[{timestamp}] {role.upper()}: {content}\n")
185
  except Exception as e:
186
- print(f"Log hatası: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
- def upload_logs_to_hf():
189
- """Logları HF'ye yükle"""
190
- if not HF_TOKEN:
191
- return
192
-
193
- try:
194
- api = HfApi(token=HF_TOKEN)
195
- api.upload_file(
196
- path_or_fileobj=LOG_FILE,
197
- path_in_repo="chat_logs.txt",
198
- repo_id="SamiKoen/BF",
199
- repo_type="space",
200
- commit_message=f"Log güncelleme - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
201
- )
202
- print("Loglar HF'ye yüklendi")
203
- except Exception as e:
204
- print(f"HF yükleme hatası: {e}")
205
-
206
- # ==================== CHATBOT FONKSİYONLARI ====================
207
- def search_products(query):
208
- """Ürün ara"""
209
- query_lower = query.lower()
210
- results = []
211
- for product in products:
212
- if query_lower in product[2].lower() or query_lower in product[0].lower():
213
- results.append(product)
214
- return results
215
-
216
- def format_product_info(product):
217
- """Ürün bilgilerini formatla"""
218
- name, info, full_name = product
219
- if info[0] != "stokta":
220
- return f"❌ **{full_name}** - Stokta değil\n"
221
- response = f"✅ **{full_name}**\n"
222
- response += f"📦 Durum: {info[0]}\n"
223
- has_campaign = info[4] and info[4] != ""
224
- if has_campaign:
225
- response += f"💰 ~~{info[1]} TL~~ → **{info[4]} TL** (Kampanyalı!)\n"
226
- try:
227
- discount = float(info[1]) - float(info[4])
228
- response += f"🎯 İndirim: {discount:.0f} TL\n"
229
- except:
230
- pass
231
- else:
232
- response += f"💰 Fiyat: **{info[1]} TL**\n"
233
- if info[3]:
234
- response += f"💳 Havale: {info[3]} TL (%2.5 indirim)\n"
235
- if info[2]:
236
- response += f"🔗 [Ürün Detayları]({info[2]})\n"
237
- return response
238
-
239
- def get_ai_response(message, history):
240
- """OpenAI API'den yanıt al"""
241
- if not OPENAI_API_KEY:
242
- return "API anahtarı bulunamadı. Lütfen sistem yöneticisiyle iletişime geçin."
243
- messages = system_messages.copy()
244
- for msg in history:
245
- if isinstance(msg, list) and len(msg) == 2:
246
- if msg[0]:
247
- messages.append({"role": "user", "content": msg[0]})
248
- if msg[1]:
249
- messages.append({"role": "assistant", "content": msg[1]})
250
- input_words = message.lower().split()
251
- for product in products:
252
- if product[0] in input_words:
253
- product_info = format_product_info(product)
254
- messages.append({"role": "system", "content": f"Ürün bilgisi: {product_info}"})
255
- messages.append({"role": "user", "content": message})
256
  payload = {
257
  "model": "gpt-4.1",
258
  "messages": messages,
259
- "temperature": 0.7,
260
- "max_tokens": 1000,
261
- "stream": True
 
 
 
262
  }
263
  headers = {
264
  "Content-Type": "application/json",
265
  "Authorization": f"Bearer {OPENAI_API_KEY}"
266
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  try:
268
- response = requests.post(API_URL, headers=headers, json=payload, stream=True)
269
- if response.status_code != 200:
270
- return " API isteği başarısız oldu. Lütfen daha sonra tekrar deneyin."
271
- full_response = ""
272
- for chunk in response.iter_lines():
273
- if chunk:
274
- chunk_str = chunk.decode('utf-8')
275
- if chunk_str.startswith("data: ") and chunk_str != "data: [DONE]":
276
- try:
277
- chunk_data = json.loads(chunk_str[6:])
278
- delta = chunk_data['choices'][0]['delta']
279
- if 'content' in delta:
280
- full_response += delta['content']
281
- yield full_response
282
- except:
283
- continue
284
  except Exception as e:
285
- print(f"API hatası: {e}")
286
- yield "❌ Bir hata oluştu. Lütfen daha sonra tekrar deneyin."
287
-
288
- def process_message(message, history):
289
- """Ana mesaj işleme fonksiyonu"""
290
- if not message:
291
- return "", history
292
- log_message("user", message)
293
- products_found = search_products(message)
294
- if products_found:
295
- response = "🔍 **Arama Sonuçları:**\n\n"
296
- for product in products_found[:5]:
297
- response += format_product_info(product) + "\n"
298
- log_message("assistant", response)
299
- with history_lock:
300
- global_chat_history.append({"role": "user", "content": message})
301
- global_chat_history.append({"role": "assistant", "content": response})
302
- return "", history + [[message, response]]
303
- history = history + [[message, None]]
304
- full_response = ""
305
- for partial_response in get_ai_response(message, history[:-1]):
306
- full_response = partial_response
307
- history[-1][1] = partial_response
308
- yield "", history
309
- log_message("assistant", full_response)
310
  with history_lock:
311
- global_chat_history.append({"role": "user", "content": message})
312
- global_chat_history.append({"role": "assistant", "content": full_response})
313
-
314
- # ==================== ZAMANLAYICI ====================
315
- def run_scheduler():
316
- """Zamanlayıcıyı çalıştır"""
317
- def scheduled_upload():
318
- upload_logs_to_hf()
319
- schedule.every().day.at("11:32").do(scheduled_upload)
320
- schedule.every().day.at("15:30").do(scheduled_upload)
321
- schedule.every().day.at("19:30").do(scheduled_upload)
322
- schedule.every().day.at("21:30").do(scheduled_upload)
323
- while True:
324
- schedule.run_pending()
325
- time.sleep(60)
326
 
327
- # ==================== CSS ====================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  custom_css = """
329
- /* ... CSS aynı şekilde buraya gelsin ... */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  """
331
 
332
- # ==================== ANA UYGULAMA ====================
333
- load_products()
334
- scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
335
- scheduler_thread.start()
336
-
337
- with gr.Blocks(
338
- title="Trek Bisiklet - AI Asistan",
339
- css=custom_css,
340
- theme=gr.themes.Soft()
341
- ) as demo:
342
- # Header
343
- gr.HTML("""
344
- <div class="main-header">
345
- <h1>🚴 Trek Bisiklet AI Asistanı</h1>
346
- <p>Türkiye'nin en güvenilir bisiklet markası - 2000'den beri Alatin Bisiklet güvencesiyle</p>
347
- </div>
348
- """)
349
- # Kadro Boyu Hesaplayıcı
350
- with gr.Row():
351
- with gr.Column(scale=2):
352
- gr.Markdown("### 🚲 Kadro Boyu Hesaplayıcı\n\nEn doğru kadro boyunu öğrenmek için aşağıdaki formu doldurun:")
353
- height_input = gr.Number(label="Boyunuz (cm)", value=175)
354
- inseam_input = gr.Number(label="İç bacak boyu (cm, opsiyonel)", value=None)
355
- bike_type_input = gr.Dropdown(
356
- choices=["road", "mtb", "hybrid", "gravel"],
357
- label="Bisiklet Türü",
358
- value="road"
359
- )
360
- frame_calc_btn = gr.Button("Kadro Boyunu Hesapla", variant="primary")
361
- frame_calc_output = gr.Markdown()
362
- frame_calc_btn.click(
363
- kadro_boyu_hesapla,
364
- [height_input, inseam_input, bike_type_input],
365
- frame_calc_output
366
- )
367
- # Chat ve yan panel
368
- with gr.Row():
369
- with gr.Column(scale=3):
370
- chatbot = gr.Chatbot(
371
- elem_id="chatbot",
372
- show_label=False,
373
- height=500
374
- )
375
- with gr.Row():
376
- msg = gr.Textbox(
377
- show_label=False,
378
- placeholder="Sorunuzu yazın... (Örn: Madone SL 6 stokta var mı?)",
379
- lines=2
380
- )
381
- submit = gr.Button("Gönder", variant="primary")
382
- with gr.Column(scale=1):
383
- gr.HTML("""
384
- <div class="side-panel">
385
- <h3>🌟 Hızlı Bilgiler</h3>
386
- <p>🏆 Ömür Boyu Garanti</p>
387
- <p>🔧 Bike Fit Hizmeti</p>
388
- <p>📱 7/24 Canlı Destek</p>
389
- <p>🚚 24 Saat Kargo</p>
390
- </div>
391
- <div class="side-panel">
392
- <h3>📍 Mağazalarımız</h3>
393
- <p><strong>İstanbul:</strong> Ortaköy, Caddebostan, Sarıyer</p>
394
- <p><strong>İzmir:</strong> Alsancak</p>
395
- <p>💡 Pazar günleri kapalıyız</p>
396
- </div>
397
- """)
398
- clear = gr.Button("🗑️ Sohbeti Temizle", variant="secondary")
399
- gr.HTML("""
400
- <div style="text-align: center; margin-top: 2rem; padding: 1rem; color: #718096;">
401
- <p>© 2024 Trek Bisiklet Türkiye | Alatin Bisiklet</p>
402
- <p>
403
- <a href="https://www.trekbisiklet.com.tr" target="_blank">Web Sitemiz</a> |
404
- <a href="tel:02122271015">Bizi Arayın</a>
405
- </p>
406
- </div>
407
- """)
408
- # Event handlers
409
- submit.click(process_message, [msg, chatbot], [msg, chatbot])
410
- msg.submit(process_message, [msg, chatbot], [msg, chatbot])
411
- clear.click(lambda: None, None, chatbot)
412
 
413
  if __name__ == "__main__":
414
- demo.queue()
415
- demo.launch(
416
- server_name="0.0.0.0",
417
- server_port=7860,
418
- share=False
419
- )
 
6
  import schedule
7
  import time
8
  import threading
9
+ from huggingface_hub import HfApi, create_repo, hf_hub_download
10
  import warnings
11
  import pandas as pd
12
  from docx import Document
13
+ import spaces
14
  from google.oauth2.service_account import Credentials
15
  from googleapiclient.discovery import build
16
  from googleapiclient.http import MediaIoBaseDownload
17
  import io
18
+ import warnings
19
+ from requests.packages.urllib3.exceptions import InsecureRequestWarning
20
+ warnings.simplefilter('ignore', InsecureRequestWarning)
21
 
22
+ # Gradio uyarılarını bastır
23
+ warnings.filterwarnings("ignore", category=UserWarning, module="gradio.components.chatbot")
24
 
25
+ # Log dosyası adı ve yolu
26
  LOG_FILE = '/data/chat_logs.txt' if os.path.exists('/data') else 'chat_logs.txt'
27
+ print(f"Dosya yolu: {os.path.abspath(LOG_FILE)}")
28
+
29
+ # API ayarları
30
  API_URL = "https://api.openai.com/v1/chat/completions"
31
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
32
+ if not OPENAI_API_KEY:
33
+ print("Hata: OPENAI_API_KEY çevre değişkeni ayarlanmamış!")
34
+
35
+ # Trek bisiklet ürünlerini çekme
36
+ url = 'https://www.trekbisiklet.com.tr/output/8582384479'
37
+ response = requests.get(url, verify=False)
38
+ root = ET.fromstring(response.content)
39
 
 
40
  products = []
41
+ for item in root.findall('item'):
42
+ # Tüm ürünleri al, sonra stokta olma durumunu kontrol et
43
+ name_words = item.find('rootlabel').text.lower().split()
44
+ name = name_words[0]
45
+ full_name = ' '.join(name_words)
46
+
47
+ stock_amount = "stokta" if item.find('stockAmount').text > '0' else "stokta değil"
48
+
49
+ # Stokta olmayan ürünler için fiyat/link bilgisi eklemiyoruz
50
+ if stock_amount == "stokta":
51
+ # Normal fiyat bilgisini al
52
+ price_str = item.find('priceTaxWithCur').text if item.find('priceTaxWithCur') is not None else "Fiyat bilgisi yok"
 
53
 
54
+ # EFT fiyatını al (havale indirimli orijinal fiyat)
55
+ price_eft_str = item.find('priceEft').text if item.find('priceEft') is not None else ""
56
+
57
+ # İndirimli fiyatı al (kampanyalı fiyat)
58
+ price_rebate_str = item.find('priceRebateWithTax').text if item.find('priceRebateWithTax') is not None else ""
59
+
60
+ # Havale indirimi fiyatını al (havale indirimli kampanyalı fiyat)
61
+ price_rebate_money_order_str = item.find('priceRebateWithMoneyOrderWithTax').text if item.find('priceRebateWithMoneyOrderWithTax') is not None else ""
62
+
63
+ # Normal fiyatı yuvarla
64
+ try:
65
+ price_float = float(price_str)
66
+ # Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla
67
+ if price_float > 200000:
68
+ price = str(round(price_float / 5000) * 5000) # En yakın 5000'e yuvarla
69
+ # Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla
70
+ elif price_float > 30000:
71
+ price = str(round(price_float / 1000) * 1000) # En yakın 1000'e yuvarla
72
+ # Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla
73
+ elif price_float > 10000:
74
+ price = str(round(price_float / 100) * 100) # En yakın 100'e yuvarla
75
+ # Diğer durumlarda en yakın 10'luk basamağa yuvarla
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  else:
77
+ price = str(round(price_float / 10) * 10) # En yakın 10'a yuvarla
78
+ except (ValueError, TypeError):
79
+ price = price_str # Sayıya dönüştürülemezse olduğu gibi bırak
80
+
81
+ # Havale indirimli orijinal fiyatı yuvarla (varsa)
82
+ if price_eft_str:
83
+ try:
84
+ price_eft_float = float(price_eft_str)
85
+ # Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla
86
+ if price_eft_float > 200000:
87
+ price_eft = str(round(price_eft_float / 5000) * 5000)
88
+ # Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla
89
+ elif price_eft_float > 30000:
90
+ price_eft = str(round(price_eft_float / 1000) * 1000)
91
+ # Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla
92
+ elif price_eft_float > 10000:
93
+ price_eft = str(round(price_eft_float / 100) * 100)
94
+ # Diğer durumlarda en yakın 10'luk basamağa yuvarla
95
+ else:
96
+ price_eft = str(round(price_eft_float / 10) * 10)
97
+ except (ValueError, TypeError):
98
+ price_eft = price_eft_str
99
+ else:
100
+ # Havale indirimli fiyat verilmemişse, orijinal fiyattan %2.5 indirim hesapla
101
+ try:
102
+ price_eft_float = price_float * 0.975 # %2.5 indirim
103
+ # Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla
104
+ if price_eft_float > 200000:
105
+ price_eft = str(round(price_eft_float / 5000) * 5000)
106
+ # Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla
107
+ elif price_eft_float > 30000:
108
+ price_eft = str(round(price_eft_float / 1000) * 1000)
109
+ # Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla
110
+ elif price_eft_float > 10000:
111
+ price_eft = str(round(price_eft_float / 100) * 100)
112
+ # Diğer durumlarda en yakın 10'luk basamağa yuvarla
113
+ else:
114
+ price_eft = str(round(price_eft_float / 10) * 10)
115
+ except (ValueError, TypeError):
116
+ price_eft = ""
117
+
118
+ # İndirimli fiyatı yuvarla
119
+ try:
120
+ if price_rebate_str:
121
+ price_rebate_float = float(price_rebate_str)
122
+ # Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla
123
+ if price_rebate_float > 200000:
124
+ price_rebate = str(round(price_rebate_float / 5000) * 5000)
125
+ # Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla
126
+ elif price_rebate_float > 30000:
127
+ price_rebate = str(round(price_rebate_float / 1000) * 1000)
128
+ # Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla
129
+ elif price_rebate_float > 10000:
130
+ price_rebate = str(round(price_rebate_float / 100) * 100)
131
+ # Diğer durumlarda en yakın 10'luk basamağa yuvarla
132
+ else:
133
+ price_rebate = str(round(price_rebate_float / 10) * 10)
134
+ else:
135
+ price_rebate = ""
136
+ except (ValueError, TypeError):
137
+ price_rebate = price_rebate_str
138
+
139
+ # Havale indirimi kampanyalı fiyatı yuvarla
140
+ try:
141
+ if price_rebate_money_order_str:
142
+ price_rebate_money_order_float = float(price_rebate_money_order_str)
143
+ # Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla
144
+ if price_rebate_money_order_float > 200000:
145
+ price_rebate_money_order = str(round(price_rebate_money_order_float / 5000) * 5000)
146
+ # Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla
147
+ elif price_rebate_money_order_float > 30000:
148
+ price_rebate_money_order = str(round(price_rebate_money_order_float / 1000) * 1000)
149
+ # Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla
150
+ elif price_rebate_money_order_float > 10000:
151
+ price_rebate_money_order = str(round(price_rebate_money_order_float / 100) * 100)
152
+ # Diğer durumlarda en yakın 10'luk basamağa yuvarla
153
+ else:
154
+ price_rebate_money_order = str(round(price_rebate_money_order_float / 10) * 10)
155
+ else:
156
+ price_rebate_money_order = ""
157
+ except (ValueError, TypeError):
158
+ price_rebate_money_order = price_rebate_money_order_str
159
+
160
+ # Sadece ürün linkini al, resim linkini alma
161
+ product_link = item.find('productLink').text if item.find('productLink') is not None else ""
162
+
163
+ # Tüm fiyat bilgilerini birleştir
164
+ item_info = (stock_amount, price, product_link, price_eft, price_rebate, price_rebate_money_order)
165
+ else:
166
+ # Stokta olmayan ürünler için sadece stok durumunu belirt, fiyat ve link bilgisi verme
167
+ item_info = (stock_amount, "", "", "", "", "")
168
 
169
+ products.append((name, item_info, full_name))
170
+
171
+ # Hugging Face token
172
+ hfapi = os.getenv("hfapi")
173
+ if not hfapi:
174
+ raise ValueError("hfapi ortam değişkeni ayarlanmamış!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
+ create_repo("BF", token=hfapi, repo_type="space", space_sdk="gradio", exist_ok=True)
177
+
178
+ global_chat_history = [] # Tüm sohbet geçmişi
179
+ history_lock = threading.Lock() # Global geçmiş için kilit
180
+ file_lock = threading.Lock() # Dosya yazma için kilit
181
+ last_logged_index = 0 # Son kaydedilen mesaj indeksi
182
+
183
+ # Google Drive kimlik doğrulama (Hizmet Hesabı Secrets'tan)
184
+ SCOPES = ['https://www.googleapis.com/auth/drive.readonly']
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  def authenticate_google_drive():
187
+ service_account_json = os.getenv("SERVICE_ACCOUNT_JSON")
188
+ if not service_account_json:
189
+ raise ValueError("SERVICE_ACCOUNT_JSON ortam değişkeni bulunamadı!")
190
+
191
  try:
 
 
 
 
192
  json_data = json.loads(service_account_json)
193
+ print("JSON başarıyla ayrıştırıldı:", json_data.keys())
194
+ print("Private key içeriği:", json_data.get("private_key"))
195
+ creds = Credentials.from_service_account_info(json_data, scopes=SCOPES)
196
+ except json.JSONDecodeError as e:
197
+ raise ValueError(f"Secrets'tan alınan JSON geçersiz: {e}")
198
  except Exception as e:
199
+ raise ValueError(f"Kimlik doğrulama hatası: {e}")
200
+
201
+ return build('drive', 'v3', credentials=creds)
202
+
203
+ def download_file_from_drive(service, file_id, file_name):
204
+ request = service.files().get_media(fileId=file_id)
205
+ fh = io.BytesIO()
206
+ downloader = MediaIoBaseDownload(fh, request)
207
+ done = False
208
+ while done is False:
209
+ status, done = downloader.next_chunk()
210
+ fh.seek(0)
211
+ with open(file_name, 'wb') as f:
212
+ f.write(fh.read())
213
+ return file_name
214
 
215
+ # Dökümanları Google Drive'dan çekme ve işleme
216
+ document_content = ""
217
+ service = authenticate_google_drive()
218
+
219
+ try:
220
+ # Google Drive'dan Excel dosyası
221
+ excel_file_id = "test10rgiGp5y5ZYU0dpRvps2t0t1dEzjW8LK" # Örnek Excel dosya ID'si, kendi ID'nizi ekleyin
222
+ excel_file = download_file_from_drive(service, excel_file_id, "veriler.xlsx")
223
+ df = pd.read_excel(excel_file)
224
+ excel_text = df.to_string()
225
+ document_content += excel_text + "\n"
226
+ except Exception as e:
227
+ print(f"Google Drive Excel okuma hatası: {e}")
228
+
229
+ try:
230
+ # Google Drive'dan Word dosyası
231
+ word_file_id = "9I8H7G6F5E4D3C2B1A" # Örnek Word dosya ID'si, kendi ID'nizi ekleyin
232
+ word_file = download_file_from_drive(service, word_file_id, "aciklamalar.docx")
233
+ doc = Document(word_file)
234
+ word_text = "\n".join([para.text for para in doc.paragraphs])
235
+ document_content += word_text
236
+ except Exception as e:
237
+ print(f"Google Drive Word okuma hatası: {e}")
238
+
239
+ def run_scheduler(chat_history_ref):
240
+ def scheduled_save_and_upload():
241
+ global last_logged_index
242
+ if chat_history_ref:
243
+ print(f"Zamanlayıcı tetiklendi: {time.strftime('%Y-%m-%d %H:%M:%S')}")
244
+ try:
245
+ with file_lock: # Dosya yazma kilidi
246
+ with open(LOG_FILE, 'a', encoding='utf-8') as f:
247
+ f.write("\n--- Zamanlanmış Kayıt: {} ---\n".format(time.strftime("%Y-%m-%d %H:%M:%S")))
248
+ with history_lock: # Geçmiş kilidi
249
+ new_messages = chat_history_ref[last_logged_index:]
250
+ for msg in new_messages:
251
+ if msg["role"] in ["user", "assistant"]:
252
+ f.write(f"{msg['role'].capitalize()}: {msg['content']}\n")
253
+ last_logged_index = len(chat_history_ref) # Son indeksi güncelle
254
+ print(f"Sohbet dosyaya kaydedildi: {os.path.abspath(LOG_FILE)}")
255
+ except Exception as e:
256
+ print(f"Kayıt hatası: {e}")
257
+ time.sleep(5) # Hata sonrası tekrar denemeden önce bekle
258
+ return # Tekrar deneme için erken çıkış
259
+
260
+ HF_REPO_ID = "SamiKoen/BF"
261
+ api = HfApi(token=hfapi)
262
+ for attempt in range(3): # 3 kez tekrar deneme
263
+ try:
264
+ with file_lock:
265
+ api.upload_file(
266
+ path_or_fileobj=LOG_FILE,
267
+ path_in_repo="chat_logs.txt",
268
+ repo_id=HF_REPO_ID,
269
+ repo_type="space",
270
+ commit_message="Otomatik log güncellemesi - {}".format(time.strftime("%Y-%m-%d %H:%M:%S"))
271
+ )
272
+ print(f"Log dosyası HF'ye yüklendi: {LOG_FILE}")
273
+ break
274
+ except Exception as e:
275
+ print(f"HF yükleme hatası (deneme {attempt+1}/3): {e}")
276
+ time.sleep(5)
277
+ else:
278
+ print("HF yükleme başarısız, tüm denemeler tamamlandı.")
279
+ print(f"Zamanlanmış işlem tamamlandı: {time.strftime('%H:%M:%S')}")
280
+
281
+ schedule.every().day.at("11:32").do(scheduled_save_and_upload)
282
+ schedule.every().day.at("15:15").do(scheduled_save_and_upload)
283
+ schedule.every().day.at("15:30").do(scheduled_save_and_upload)
284
+ schedule.every().day.at("17:32").do(scheduled_save_and_upload)
285
+ schedule.every().day.at("19:15").do(scheduled_save_and_upload)
286
+ schedule.every().day.at("21:30").do(scheduled_save_and_upload)
287
+ print("Zamanlayıcı başlatıldı")
288
+ while True:
289
+ schedule.run_pending()
290
+ time.sleep(60)
291
+
292
+ @spaces.GPU(duration=1200)
293
+ def chatbot_fn(user_message, history):
294
+ if history is None:
295
+ history = []
296
+
297
+ # Log: Kullanıcı mesajını ekle
298
  try:
299
  with file_lock:
300
  with open(LOG_FILE, 'a', encoding='utf-8') as f:
301
+ f.write(f"User: {user_message}\n")
 
302
  except Exception as e:
303
+ print(f"Dosya yazma hatası (Kullanıcı): {e}")
304
+
305
+ # Sistem mesajları
306
+ system_messages = [
307
+ {"role": "system", "content": "Bir önceki sohbeti unut. Vereceğin ürün bilgisi, bu bilginin içinde yan yana yazmıyorsa veya arada başka bilgiler yazıyor ise, o bilgiyi vermeyeceksin çünkü o bilgi yanlıştır. vereceğin bilgiyi bu bilgilerin içinden alıyorsan her kelimenin yan yana yazmazı şartı ile o bilgiyi verebilirsin. Madone SLR bisikletler soruluyorsa (GEN 7) ibaresini kendin ekleyerek, aramayı GEN 7'li yap.Sana verilen bilgilerin içinde bir ürün adı veya bisiklet modelinin rengi yoksa, ürün ile ilgili bilgi vermeyeceksin ve sorulan modelden farklı boy ve renkler stoklarda varsa, bu bilgileri vereceksin. Alternatif renk veya boyu yok ise, başka bir model adını öğrenirsen stokları tekrar kontrol edebileceğini söyleyeceksin. Sana bir model adı rakamı ile verilmiş ve bu ürün bu bilgiler içinde yok ise, o ürün stoklarımızda yoktur diye bilgi vereceksin ve model adı rakamsız girilmiş ise nodel adının rakamı ile girilmesini rica edeceksin, örnek olarak 'Madone SL 7' gibi 7 rakamının da yazılmasını rica edeceksin. Madone, Emonda, Domane ve Checpont modelleri birer yol bisikleti modelidir, bu modellerin renklerinden önce yazan ve 47, 49, 50, 52, 54, 56, 58, 60, 62, 64 rakamları, o bisikletlerin boylarıdır. Bu bilgi içindeki renkler ise o ürünlerin renkleridir. Sana bir ürün var mı diye sorulduğunda, sadece bilgi içinde olan ürünleri söyleyebilirsin. Stoklarımızda yok ise o ürün ile ilgili bilgi vermeyeceksin. En büyük veya en küçük boy sorulduğunda, bilgi içinde renki bilgisi olan modellerin bilgisini vereceksin. Gerçek zamanlı stok bilgilerine erişme yeteneğin var. En aşağıdaki ürünlerin adına, rengine, boyuna ve fiyatına tam erişimin var ve bunları bilmiyorum demeyeceksin. Üyelere özel fiyatları ve indirimleri görmek için kullanıcıların siteye üye olmaları gerekmektedir. Sen bir AI Trek marka bisiklet uzmanı, bilir kişisi ve asistanısın.Trek ve Electra bisikletler konusunda uzmanım.4cü şubemizi İzmirde açtık.İzmir adresi: Sezer Doğan Sok. The Kar Suits 14A Alsancak Konak İzmir. İzmir şubesinin telefon numarası:0543 936 2335 . İstanbul'da üç Trek mağazamız var: Caddebostan, Ortaköy ve Sarıyer. Ortaköy mağazası 10.00-19.00 saatleri arasında açık. ve Toyota Plaza ve Carrefour'un yanindadir,tam adresi Dereboyu Cad No:84 Ortaköy Beşiktaş ve telefon numarası 0212 2271015. Caddebostan mağazası, Prof. Dr. Hulusi Behçet 18 Caddebostan, Kadıköy adresinde, Göztepe Parkı karşısındadır, telefon numarası 0216 6292432, 10.00-19.00 saatleri arasında açık. Tüm mağazalar Pazar günü kapalıdır. Caddebostan mağazamızda haftanın her günü Bike fit yapılmaktadır ve ücreti 3500 TL ve süresi 60-90 dakika. Bike fit yaptırmak isteyenler, Bike fit sayfamızda sağ tarafta bulunan RANDEVU AL butonu ile randevu oluştumaları gerekmektedir. Sarıyer mağazamızın adresi şöyledir: Mareşal Fevzi Çakmak Cad. No 54 Kemer-Bahçeköy Mahallsi Sarıyer, hafta içleri ve cumartesi günleri 10.00 ile 19.00 saatleri arasında hizmet vermektedir. Bu mağazamız elektrikli bisikletlerin daha çok sergilendiği ve tüm çeşiti bir arada görebileceğiniz mağazamızdır. Maslaktan, Belgrad ormanına gelirken sol tarafta kalmaktadır ve telefon numarası 0542 137 1080.."},
308
+ {"role": "system", "content": "Dağ bisikletleri modelleri: Marlin, Roscoe, Procaliber, Supercaliber, Fuel Ex. Şehir bisikletleri: FX ve DS (Dual Sport). Elektrikli Bisiklet modelleri: Powerfly, Powerfly FS, Rail, Fuel Exe, Domane SLR +, Verve +, Townie +, Fx +, DS +. Dağ bisikletlerinin boyları XXS, XS, S, M, ML, L, XL'dir. Canlı sohbet için sitemizdeki YEŞİL düğmeye basabilirsiniz. Web adresimiz: https://www.alatin.com.tr. Bayi bilgileri için https://www.alatin.com.tr/sayfa/bayilerimiz/ adresine bakabilirsiniz."},
309
+ {"role": "system", "content": "Başka marka bisikletleri önermeyeceksin. Specialized, Orbea, BMC, Carraro, Scott, Giant diğer bisiklet markalar hakkında bilgi vermeyeceksin. Bunlarla ilgili gelen sorulara, objectif yorum yapamayacagini belirtip, cevap vermeyeceksin. Trek bu kadrolara ömür boyu garanti veriyor."},
310
+ {"role": "system", "content": "Bisiklet aksesuar ve yedek parçalarının marka adı Bontrager dir. Bryton marka yol bilgisayarını da ithal ediyoruz. Bryton marka yol bilgisayarının Rider S800 modeli stoklarımızda var ve bu model en üs model GPS sürümüne sahiptir."},
311
+ {"role": "system", "content": "Trek, Electra, Bontrager, Saris, Brtyon ve Trieye markalarından başka marka adı yazamaz ve yorum yapamazsın. Trek'in uluslararası web sitesini 'www.trekbikes.com'kullanıcıya göstermeyeceksin. Web sitesi linki vermen gerekirse sadece 'www.trekbisiklet.com.tr' linkini ile başlayan linkleri göstereceksin."},
312
+ {"role": "system", "content": "Trieye gözlükler, arkanızı görebilmeniz için gözlük camının içine entegre edilmiş bir geri görüş aynasıyla beraber gelir. Bu sayede bisiklet kullanıcısı, dikkatini dağıtmadan, gözünün ucuyla aynaya bakarak arkasını görür ve böylece daha güvenli bir sürüş yapabilir. Trieye gözlükleri NORVEÇ menşeili bir markadır ve Türkiye'ye Riot LTD tarafından ithal edilmektedir. Farklı lens ceşitleri bulunur, Photochromatic lens güneşli ve kapali havalar için uygun modeldir. Renkli camlar güneşli günler için daha uygundur."},
313
+ {"role": "system", "content": "Trek markası, 2000 yılından beri Alatin Bisiklet tarafından Türkiye'de dağıtılmaktadır. Alatin Bisiklet en iyi satış sonrası hizmeti veren firmadır. Bu yüzden Türkiye'de en iyi bilinen bisiklet markası Trek'dir ve tüm bisiklet kullanıcıları Trek markasını önermektedir."},
314
+ {"role": "system", "content": "Trek Türkiye, ASLA DURMA markasının yaratıcısı Fatih Topçu ve TREK RMK DYNAMIS bisiklet takımına sponsordur. Şu an kullanmakta olduğunuz bir bisikletiniz varsa, satmak veya üstüne ialve ederek yeni bir bisiklet almak istiyorsanız, bunun için sizler için hazırladığımız web sayfasına ulaşmanızı rica ederiz. Web adresi şöyledir: https://www.bikeexchangehub.com/ . Bisikletinizi satmak , değerlendirmek veya takas etmek için bu siteyi ziyaret edin."},
315
+ {"role": "system", "content": "cevap yazarken vereceğin bilginin tek bir bilgi içinde yan yana yazdığını iki kere kontrol edeceksin. Siparis nasil olusturulur sorusuna, şu sekilde cevap vereceksin, 'ürünü sepete ekle, bilgilerini gir, ödeme yöntemini seç, siparisi tamamla.'"},
316
+ {"role": "system", "content": "Şimdi sana 27 Haziran 2024 günü açıklanan yeni Trek modeli MADONE GEN 8 i öğretiyorum. özellikler şöyle, Émonda kadar hafif. Madone kadar hızlı. Dünyalar çarpışıyor: En hızlı ve en hafiflerimiz birleşiyor. Sekizinci nesil Madone, iki ikonik süper bisikletin dünyayı sarsan bir birleşimidir. Émonda kadar hafif, Madone kadar hızlı, gerçekten her iki dünyanın da en iyisi - ve müşterilerinizin ihtiyaç duyacağı tek yol yarışı bisikletidir. Evet, Madone Gen 8, Madone Gen 7 ve Émonda'nın yerini alıyor. Ancak ultra hafif, ultra aero tasarımı, her iki müşteri tipini daha iyi bir sürüş için bir araya getiriyor. Tırmanışlarda uçun. Düzlüklerde sprint atın. Neden aero ve hafiflik arasında seçim yapmak zorunda kalasınız? Aero ve hafiflik bir arada."},
317
+ {"role": "system", "content": "Émonda kadar hafif. Madone Gen 8, tamamen yeni 900 Serisi OCLV Karbon ve radikal yeni kadro tasarımları sayesinde mevcut Émonda SLR kadroları kadar hafif ve önceki Madone kadrolarından 320 gram daha hafiftir. Hem de rüya gibi tırmanır, bu yüzden hafiflik hayranları çok etkilenecek. Madone Gen 7 kadar hızlı. Madone Gen 8, Émonda'dan saatte 77 saniye daha hızlıdır ve hızın nasıl göründüğünü yeniden tanımlayan yeni Full System Foil aero şekilleriyle önceki Madone kadar hızlıdır. Ayrıca, yeni RSL Aero suluklar ve Kafesler (Madone SLR ile birlikte verilir) tüm sistemini her bisiklette daha hızlı hale getirmeye yardımcı olur. Daha hafif ve daha konforlu IsoFlow teknolojisi. Madone Gen 8'in güncellenmiş IsoFlow teknolojisi eskisinden daha hafif ve dikey olarak %80'e kadar daha uyumludur, yol titreşimlerini emerek sürücülerin daha uzun süre daha güçlü sürüş yapmasını sağlar. Yeni daha hafif ve daha ergonomik gidon. Madone SLR modelleri, son derece hafif OCLV Karbondan üretilen güncellenmiş Aero RSL Yol Entegre gidonu, ultra hızlı aero şekilleri ile sürücüleri en üst aero pozisyonuna getirir. En iyi fiyata en iyi yarış bisikleti. Tamamen yeni Madone SL, SLR modeliyle aynı devrim niteliğindeki kadro teknolojisini paylaşıyor, ancak daha ekonomik 500 Serisi OCLV Karbon ve iki parçalı gidon/boğaz kombinasyonları ile maliyetleri düşürüyor. SL modelleri ayrıca mekanik vites uyumludur. Aero ve Hafiflik arasında seçim yapmanıza gerek yok. Artık hızlı ve hafif, sprint veya tırmanış arasında seçim yapmak zorunda değil. Madone Gen 8 ile Émonda müşterileri daha da aerodinamik bir sürüşe, Madone müşterileri ise her zamankinden daha hızlı tırmanmalarına yardımcı olan ultra hafif bir bisiklete sahip oluyor."},
318
+ {"role": "system", "content": "Sen aynı zamanda bir Bıke Finder asistanısın. Görevin insanların doğru bisiklet modeli seçimini sağlamak. Kişilere sorucağın bir takım sorularala, kişileri tanıyıp, onlara stoklarımızda bulunan bisikletlerin model, boylarını tavsiye edeceksin."},
319
+ {"role": "system", "content": "Size en uygun Trek bisiklet modelini belirleyebilmem için birkaç sorum olacak. Sorucağın soruların tümünü aynı anda sormayacaksın. her soruyu bir kerede sor. kullanıcı istediğin tip bir cevap verirse, bir sonraki soruyu sor. Verdiğiniz bilgiler doğrultusunda, elimizdeki güncel stoklardan ihtiyaçlarınıza en uygun modeli seçip, satin alma sürecine yonlendirecegim. Unutmayin, tum Trek bisikletlerimiz omur boyu garantilidir! Adim 1: Bisiklet Kategorisi - Hangi tur Trek bisikletiyle ilgileniyorsunuz? Lutfen asagidaki seceneklerden birini belirtiniz: Yol Bisikleti ornek: Trek Emonda, Trek Domane; Dag Bisikleti ornek: Trek Fuel EX, Trek Remedy, Trek Procaliber; Hibrit Sehir Bisikleti; Gravel Bisikleti ornek: Trek Checkpoint; Elektrikli Bisikleti ornek: Trek Powerfly, Trek Allant+. Adim 2: Kullanim Amaci - Bisikletinizi hangi amaclarla kullanmayi planliyorsunuz? ornek: gunluk ulasim, spor, uzun mesafe turlari, yaris, offroad maceralari, dag yollari. Adim 3: Beklentiler - Trek bisikletinizde hangi ozellikler sizin icin en onemli? ornek: performans, konfor, dayaniklilik, teknoloji yenilik, estetk, diger. Adim 4: Zemin Kosullari - Bisikletinizi hangi zeminlerde kullanacaksiniz? ornek: sehir ici asfalt, hafif engebeli parkurlar, orman dogal parkurlar, zorlu dag yollari offroad, karisik kullanim. Adim 5: Fiziksel Olculer - Dogru model ve cerceve boyutunu belirleyebilmem icin lutfen boyunuzu ve ic bacak boyunuzu paylasir misiniz? ornek: Boyum 180 cm, ic bacak boyum 85 cm. Adim 6: Ek Tercihler - Ek olarak, bisikletinizde tercih ettiginiz baska ozellikler var mi? ornek: ekstra donanim, ozel renk, aksesuar tercihi veya butce araliginiz. Adim 7: Oneri ve Satisa Yonlendirme - Verdigimiz bilgiler dogrultusunda, stoklarimizda bulunan ve ihtiyaclariniza en uygun olan Onerilen Model modelini oneriyorum. Bu model, kisa teknik ozellikler, kullanim avantajlari ve hedeflenen zemin alan ile beklentilerinize hitap ediyor. Ustelik, tum Trek bisikletlerimiz omur boyu garantilidir! Su an stok durumumuz: Stokta mevcut, Sinirli stok. Urun detaylari ve satin alma islemi icin lutfen su linke tiklayin: Trek Onerilen Model Urun Sayfasi https:orneksite.comtrek-ornek-model. Kargo ve Teslimat - Siparisiniz odeme onayindan sonra en gec 24 saat icinde paketlenip kargoya verilir, Aras Kargo ile gonderim yapilir. Kargo takip numarasi SMS ve eposta ile iletilecek; gonderim sureci 3-5 is gunu surer. Belirli tutar uzerindeki siparislerde ucretsiz kargo kampanyasi uygulanir. Lutfen yukaridaki linke tiklayarak satin alma isleminizi tamamlayin. Herhangi bir sorunuz veya ek isteginiz olursa, ben buradayim. Hadi, siparisinizi tamamlayalim ve maceraya baslayalim!" },
320
+ {"role": "system", "content": "Stokları ve fiyatları https://www.trekbisiklet.com.tr den bakacaksın, diğer markalarla ilgigi soru gelirse kibarca cevaplayamayacağını ve trek in neden farklı olduğunu anlat. Yol bisikletlerinde Türkiye stoklarımızda, Madone, Domane, Emonda, CheckMate, CheckPoint ve SpeedConcept modelleri bulunuyor. Dağ bisikletlerinde ise Marlin, Procaliber, Supercaliber modeller, full amortisörlülerde ise Fuel Ex modeli bulunmakta. Şehir kullanımı için FX ve DS modelini stoklarda bulunduruyoruz."},
321
+ {"role": "system", "content": "Drivedan çektiğin veriler.xlsx dosyasındaki datalar bilgi amaçlıdır ve bu bisikletler stoklarımızda yoktur. Bu veriler 2026 model bisikletlere aittir ve henüz stoklarımızda yoktur. Tüm modellerimizin ağırlıkları: Madone SL 5 Gen 8 8.70 kg, Madone SL 6 Gen 8 8.16 kg, Madone SL 7 Gen 8 7.88 kg, Madone SLR 7 Gen 8 7.30 kg, Madone SLR 7 AXS Gen 8 7.44 kg, Madone SLR 8 AXS Gen 8 7.18 kg, Madone SLR 9 Gen 8 7.00 kg, Madone SLR 9 AXS Gen 8 7.00 kg, Madone SLR 9 Etap Gen 7 7.36 kg, Madone SLR 9 Gen 7 7.10 kg, Madone SLR 7 Etap Gen 7 7.76 kg, Madone SLR 7 Gen 7 7.48 kg, Émonda SLR 9 6.72 kg, Émonda SLR 7 Etap 7.37 kg, Émonda SL 9 7.44 kg, Émonda SLR 7 7.10 kg, Émonda SLR 6 7.35 kg, Émonda SL 7 Etap 7.95 kg, Émonda SL 7 7.95 kg, Émonda SL 6 Pro Di2 8.25 kg, Émonda SL 5 9.15 kg, Émonda ALR 5 9.15 kg, Émonda ALR 4 9.50 kg, Domane SLR 7 Gen 4 7.25 kg, Domane SLR 7 AXS Gen 4 8.48 kg, Domane SL 6 Gen 4 8.90 kg, Domane SL 6 9.30 kg, Domane SL 5 Gen 4 8.93 kg, Domane AL 2 Gen 4 10.55 kg, Domane AL 2 Rim 9.57 kg, Checkmate SLR 7 AXS 9.00 kg, Checkpoint SL 7 AXS Gen 3 9.05 kg, Checkpoint SL 5 AXS Gen 3 9.30 kg, Domane+ SLR 7 AXS 12.40 kg, Speed Concept SLR 9 Etap 8.60 kg, Speed Concept SLR 9 8.70 kg, Speed Concept SLR 7 Etap 9.35 kg, Speed Concept SLR 7 8.97 kg, Fuel EXe 9.8 GX AXS T-Type 18.1 kg, Fuel EXe 9.7 SLX/XT 19.00 kg, Fuel EXe 5 20.8 kg, Rail 9.8 GX AXS T-Type 22.9 kg, Rail 5 21.53 kg, Rail 5 Gen 3 23.53 kg, Powerfly Gen 4 23.37 kg, Marlin+ 8 21.30 kg, Marlin+ 6 22.45 kg, Domane+ SLR 7 AXS 12.40 kg, Dual Sport+ 2 17.41 kg, FX+ 2 18.20 kg, Verve+ 3 24.70 kg, Townie Go 7D EQ Step Thru 20.41 kg, Marlin 8 13.2 kg, Marlin 7 13.77 kg, Marlin 5 13.90 kg, Marlin 4 14.60 kg, Procaliber 9.7 AXS Gen 3 10.58 kg, Procaliber 9.5 Gen 3 11.74 kg, Procaliber 9.5 11.74 kg, Supercaliber SL 9.7 Gen 2 11.98 kg, Fuel EX 8 GX AXS 13.77 kg, FX Sport 6 9.30 kg, FX 3 11.50 kg, FX 2 12.30 kg, DS 3 Gen 5 12.05 kg, DS 3 Gen 4 13.00 kg, DS 2 Gen 5 12.79 kg, Verve 3 Low Step 14.20 kg, Verve 3 14.30 kg"}
322
+ ]
323
+
324
+ # Döküman verilerini sistem mesajlarına ekle
325
+ if document_content:
326
+ system_messages.append({"role": "system", "content": f"Dökümanlardan gelen bilgiler: {document_content}"})
327
+
328
+ # Kullanıcı mesajında ürün ismi geçiyorsa ekle
329
+ input_words = user_message.lower().split()
330
+ for product_info in products:
331
+ if product_info[0] in input_words:
332
+ if product_info[1][0] == "stokta":
333
+ # Kampanyalı fiyat kontrolü
334
+ has_campaign = product_info[1][4] and product_info[1][4] != ""
335
+
336
+ # Orijinal fiyat her zaman gösterilir
337
+ normal_price = f"Orijinal fiyat: {product_info[1][1]} TL"
338
+
339
+ # Kampanyalı fiyat bilgisi
340
+ rebate_price = ""
341
+ discount_info = ""
342
+ eft_price = ""
343
+ rebate_money_order_price = ""
344
+
345
+ if has_campaign:
346
+ # Ürün kampanyalıysa, kampanyalı fiyat gösterilir
347
+ rebate_price = f"\nKampanyalı fiyat: {product_info[1][4]} TL"
348
+
349
+ # İndirim miktarı hesaplanır
350
+ try:
351
+ original_price = float(product_info[1][1].replace(',', '.'))
352
+ campaign_price = float(product_info[1][4].replace(',', '.'))
353
+ discount_amount = original_price - campaign_price
354
+
355
+ # İndirim miktarı 0'dan büyükse göster
356
+ if discount_amount > 0:
357
+ # İndirim miktarı için yuvarlama kurallarını uygula
358
+ if discount_amount > 200000:
359
+ discount_amount_rounded = round(discount_amount / 5000) * 5000
360
+ elif discount_amount > 30000:
361
+ discount_amount_rounded = round(discount_amount / 1000) * 1000
362
+ elif discount_amount > 10000:
363
+ discount_amount_rounded = round(discount_amount / 100) * 100
364
+ else:
365
+ discount_amount_rounded = round(discount_amount / 10) * 10
366
+
367
+ # İndirim bilgisi
368
+ discount_info = f"\nYapılan indirim: {discount_amount_rounded:.0f} TL"
369
+ except (ValueError, TypeError):
370
+ discount_info = ""
371
+
372
+ # Havale indirimli kampanyalı fiyat bilgisini gösterme
373
+ rebate_money_order_price = ""
374
+ else:
375
+ # Kampanyalı değilse, havale indirimli normal fiyatı göster
376
+ if product_info[1][3] and product_info[1][3] != "":
377
+ eft_price = f"\nHavale indirimli fiyat: {product_info[1][3]} TL"
378
+
379
+ # Ürün linki
380
+ product_link = f"\nÜrün linki: {product_info[1][2]}"
381
+
382
+ # Tüm bilgileri birleştir
383
+ new_msg = f"{product_info[2]} {product_info[1][0]}\n{normal_price}{rebate_price}{discount_info}{eft_price}{rebate_money_order_price}{product_link}"
384
+ else:
385
+ # Ürün stokta yoksa sadece stok durumunu bildir
386
+ new_msg = f"{product_info[2]} {product_info[1][0]}"
387
+ system_messages.append({"role": "system", "content": new_msg})
388
+
389
+ messages = system_messages + history + [{"role": "user", "content": user_message}]
390
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  payload = {
392
  "model": "gpt-4.1",
393
  "messages": messages,
394
+ "temperature": 0.2,
395
+ "top_p": 1,
396
+ "n": 1,
397
+ "stream": True,
398
+ "presence_penalty": 0,
399
+ "frequency_penalty": 0,
400
  }
401
  headers = {
402
  "Content-Type": "application/json",
403
  "Authorization": f"Bearer {OPENAI_API_KEY}"
404
  }
405
+
406
+ response = requests.post(API_URL, headers=headers, json=payload, stream=True)
407
+ if response.status_code != 200:
408
+ yield "Bir hata oluştu."
409
+ return
410
+
411
+ partial_response = ""
412
+
413
+ for chunk in response.iter_lines():
414
+ if not chunk:
415
+ continue
416
+ chunk_str = chunk.decode('utf-8')
417
+ if chunk_str.startswith("data: ") and chunk_str != "data: [DONE]":
418
+ try:
419
+ chunk_data = json.loads(chunk_str[6:])
420
+ delta = chunk_data['choices'][0]['delta']
421
+ if 'content' in delta:
422
+ partial_response += delta['content']
423
+ yield partial_response
424
+ except json.JSONDecodeError as e:
425
+ print(f"JSON parse hatası: {e} - Chunk: {chunk_str}")
426
+ elif chunk_str == "data: [DONE]":
427
+ break
428
+
429
+ # Log: Asistan cevabını ekle
430
  try:
431
+ with file_lock:
432
+ with open(LOG_FILE, 'a', encoding='utf-8') as f:
433
+ f.write(f"Bot: {partial_response}\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  except Exception as e:
435
+ print(f"Dosya yazma hatası (Bot): {e}")
436
+
437
+ # Global geçmişi güncelle
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  with history_lock:
439
+ global_chat_history.append({"role": "user", "content": user_message})
440
+ global_chat_history.append({"role": "assistant", "content": partial_response})
441
+
442
+ # Slow echo (test için)
443
+ def slow_echo(message, history):
444
+ for i in range(len(message)):
445
+ time.sleep(0.05)
446
+ yield "You typed: " + message[: i + 1]
 
 
 
 
 
 
 
447
 
448
+ # Kullanım modu
449
+ USE_SLOW_ECHO = False
450
+ chat_fn = slow_echo if USE_SLOW_ECHO else chatbot_fn
451
+
452
+ if not USE_SLOW_ECHO:
453
+ scheduler_thread = threading.Thread(target=run_scheduler, args=(global_chat_history,), daemon=True)
454
+ scheduler_thread.start()
455
+
456
+ # Trek markasına özel tema oluştur (düzeltilmiş sürüm)
457
+ trek_theme = gr.themes.Base(
458
+ primary_hue="red", # Trek kırmızısı için
459
+ secondary_hue="slate", # Gri tonları için
460
+ neutral_hue="slate",
461
+ radius_size=gr.themes.sizes.radius_sm, # Köşe yuvarlatma değerleri
462
+ spacing_size=gr.themes.sizes.spacing_md, # Aralık değerleri
463
+ text_size=gr.themes.sizes.text_sm # Yazı boyutları (small)
464
+ )
465
+ # Chatbot kartları için arka plan renkleri değiştiren CSS
466
  custom_css = """
467
+ /* Genel font ayarları */
468
+ .gradio-container, .gradio-container *, .message-wrap, .message-wrap p, .message-wrap div,
469
+ button, input, select, textarea {
470
+ font-family: 'Segoe UI', 'SF Pro Text', 'Roboto', -apple-system, BlinkMacSystemFont,
471
+ 'Helvetica Neue', Arial, sans-serif !important;
472
+ font-size: 0.6rem !important;
473
+ }
474
+
475
+ /* Input alanı için de aynı boyut */
476
+ .message-textbox textarea {
477
+ font-size: 0.6rem !important;
478
+ }
479
+
480
+ /* Başlıklar için özel boyutlar */
481
+ h1 {
482
+ font-size: 1.4rem !important;
483
+ font-weight: 800 !important;
484
+ }
485
+
486
+ h2 {
487
+ font-size: 1.2rem !important;
488
+ font-weight: 600 !important;
489
+ }
490
+
491
+ h3 {
492
+ font-size: 1rem !important;
493
+ font-weight: 600 !important;
494
+ }
495
+
496
+ /* Kart arka plan renkleri - görseldeki gibi */
497
+ /* Kullanıcı mesajları için mavi tonda arka plan */
498
+ .user-message, .user-message-highlighted {
499
+ background-color: #e9f5fe !important;
500
+ border-bottom-right-radius: 0 !important;
501
+ box-shadow: 0 1px 2px rgba(0,0,0,0.1) !important;
502
+ }
503
+
504
+ /* Bot mesajları için beyaz arka plan ve hafif kenarlık */
505
+ .bot-message, .bot-message-highlighted {
506
+ background-color: white !important;
507
+ border: 1px solid #e0e0e0 !important;
508
+ border-bottom-left-radius: 0 !important;
509
+ box-shadow: 0 1px 2px rgba(0,0,0,0.05) !important;
510
+ }
511
+
512
+ /* Mesaj baloncuklarının köşe yuvarlatma değerleri */
513
+ .message-wrap {
514
+ border-radius: 12px !important;
515
+ margin: 0.5rem 0 !important;
516
+ max-width: 90% !important;
517
+ }
518
+
519
+ /* Sohbet alanının genel arka planı */
520
+ .chat-container, .gradio-container {
521
+ background-color: #f7f7f7 !important;
522
+ }
523
+
524
+ /* Daha net yazılar için text rendering */
525
+ * {
526
+ -webkit-font-smoothing: antialiased;
527
+ -moz-osx-font-smoothing: grayscale;
528
+ text-rendering: optimizeLegibility;
529
+ }
530
+
531
+ /* JavaScript ile placeholder eklemek için */
532
+ </style>
533
+ <script>
534
+ document.addEventListener('DOMContentLoaded', function() {
535
+ // Textbox'u bul
536
+ setTimeout(function() {
537
+ const textareas = document.querySelectorAll('textarea');
538
+ textareas.forEach(textarea => {
539
+ textarea.placeholder = "Sorunuzu buraya yazın... (örn: Kadromun boyu ne olmalı?)";
540
+
541
+ // Placeholder stillemesi
542
+ textarea.style.fontStyle = "italic";
543
+ textarea.style.color = "#999";
544
+ });
545
+ }, 1000); // Sayfa yüklendiğinde biraz bekleyerek elementlerin oluşmasını sağla
546
+ });
547
+ </script>
548
+ <style>
549
  """
550
 
551
+
552
+ # Demo arayüzüne CSS'i ekleyin
553
+ demo = gr.ChatInterface(
554
+ fn=chat_fn,
555
+ title="Trek Asistanı",
556
+ theme="soft",
557
+ type="messages",
558
+ flagging_mode="manual",
559
+ flagging_options=["Doğru", "Yanlış", "Emin değilim", "Diğer"],
560
+ save_history=True,
561
+ autoscroll=True,
562
+ css=custom_css
563
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
 
565
  if __name__ == "__main__":
566
+ demo.launch(debug=True)