SamiKoen commited on
Commit
b9110ca
·
verified ·
1 Parent(s): 6a4857c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +459 -29
app.py CHANGED
@@ -1,46 +1,476 @@
1
  import gradio as gr
2
- import requests
3
  import os
4
- from openai import OpenAI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- # OpenAI API istemcisi
7
- client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
 
 
 
 
 
 
 
8
 
9
- # Chatbot fonksiyonunuzu ses üretimi ile birlikte güncelleyin
10
- def chatbot_fn_tts(user_message, history):
11
- text_response = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  payload = {
14
- "model": "gpt-4o",
15
- "messages": history + [{"role": "user", "content": user_message}],
16
  "temperature": 0.2,
17
- "stream": False
 
 
 
 
18
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
- chat_response = client.chat.completions.create(**payload)
 
 
 
 
 
 
21
 
22
- text_response = chat_response.choices[0].message.content
 
 
 
 
 
23
 
24
- # TTS ses üretimi
25
- response = client.audio.speech.create(
26
- model="tts-1",
27
- voice="nova",
28
- input=text_response
29
- )
30
 
31
- audio_file_path = "tts_output.mp3"
32
- response.stream_to_file(audio_file_path)
 
33
 
34
- return text_response, audio_file_path
 
 
35
 
36
- # Gradio arayüzünü oluştur
37
- iface = gr.Interface(
38
- fn=chatbot_fn_tts,
39
- inputs=["text", "state"],
40
- outputs=["text", gr.Audio(type="filepath")],
41
- title="Trek GPT-4o Mini TTS Chatbot",
42
- description="Yazdığınız mesaja hem metin hem sesli yanıt alırsınız."
 
43
  )
44
 
45
  if __name__ == "__main__":
46
- iface.launch(debug=True)
 
1
  import gradio as gr
 
2
  import os
3
+ import json
4
+ import requests
5
+ import xml.etree.ElementTree as ET
6
+ import schedule
7
+ import time
8
+ import threading
9
+ from huggingface_hub import HfApi, create_repo
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 base64
19
+ from gtts import gTTS # Google Text-to-Speech
20
+
21
+ # Suppress Gradio warnings
22
+ warnings.filterwarnings("ignore", category=UserWarning, module="gradio.components.chatbot")
23
+
24
+ # Log file name and path
25
+ LOG_FILE = '/data/chat_logs.txt' if os.path.exists('/data') else 'chat_logs.txt'
26
+ print(f"File path: {os.path.abspath(LOG_FILE)}")
27
+
28
+ # API settings
29
+ API_URL = "https://api.openai.com/v1/chat/completions"
30
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
31
+ if not OPENAI_API_KEY:
32
+ print("Error: OPENAI_API_KEY environment variable not set!")
33
+
34
+ # Fetch Trek bicycle products
35
+ url = 'https://www.trekbisiklet.com.tr/output/8582384479'
36
+ response = requests.get(url)
37
+ root = ET.fromstring(response.content)
38
+
39
+ products = []
40
+ for item in root.findall('item'):
41
+ # Get all products, then check for stock availability
42
+ name_words = item.find('rootlabel').text.lower().split()
43
+ name = name_words[0]
44
+ full_name = ' '.join(name_words)
45
+
46
+ stock_amount = "stokta" if item.find('stockAmount').text > '0' else "stokta değil"
47
+
48
+ # Don't add price/link info for out-of-stock products
49
+ if stock_amount == "stokta":
50
+ # Get normal price info
51
+ price_str = item.find('priceTaxWithCur').text if item.find('priceTaxWithCur') is not None else "Fiyat bilgisi yok"
52
+
53
+ # Get EFT price (money transfer discounted original price)
54
+ price_eft_str = item.find('priceEft').text if item.find('priceEft') is not None else ""
55
+
56
+ # Get discounted price (promotional price)
57
+ price_rebate_str = item.find('priceRebateWithTax').text if item.find('priceRebateWithTax') is not None else ""
58
+
59
+ # Get money transfer discounted promotional price
60
+ price_rebate_money_order_str = item.find('priceRebateWithMoneyOrderWithTax').text if item.find('priceRebateWithMoneyOrderWithTax') is not None else ""
61
+
62
+ # Round normal price
63
+ try:
64
+ price_float = float(price_str)
65
+ # Round to nearest 5000 if price is above 200000
66
+ if price_float > 200000:
67
+ price = str(round(price_float / 5000) * 5000)
68
+ # Round to nearest 1000 if price is above 30000
69
+ elif price_float > 30000:
70
+ price = str(round(price_float / 1000) * 1000)
71
+ # Round to nearest 100 if price is above 10000
72
+ elif price_float > 10000:
73
+ price = str(round(price_float / 100) * 100)
74
+ # Otherwise round to nearest 10
75
+ else:
76
+ price = str(round(price_float / 10) * 10)
77
+ except (ValueError, TypeError):
78
+ price = price_str # Keep as is if can't convert to number
79
+
80
+ # Round money transfer discounted original price (if any)
81
+ if price_eft_str:
82
+ try:
83
+ price_eft_float = float(price_eft_str)
84
+ # Round to nearest 5000 if price is above 200000
85
+ if price_eft_float > 200000:
86
+ price_eft = str(round(price_eft_float / 5000) * 5000)
87
+ # Round to nearest 1000 if price is above 30000
88
+ elif price_eft_float > 30000:
89
+ price_eft = str(round(price_eft_float / 1000) * 1000)
90
+ # Round to nearest 100 if price is above 10000
91
+ elif price_eft_float > 10000:
92
+ price_eft = str(round(price_eft_float / 100) * 100)
93
+ # Otherwise round to nearest 10
94
+ else:
95
+ price_eft = str(round(price_eft_float / 10) * 10)
96
+ except (ValueError, TypeError):
97
+ price_eft = price_eft_str
98
+ else:
99
+ # If no money transfer discounted price is given, calculate 2.5% discount from original price
100
+ try:
101
+ price_eft_float = price_float * 0.975 # 2.5% discount
102
+ # Round to nearest 5000 if price is above 200000
103
+ if price_eft_float > 200000:
104
+ price_eft = str(round(price_eft_float / 5000) * 5000)
105
+ # Round to nearest 1000 if price is above 30000
106
+ elif price_eft_float > 30000:
107
+ price_eft = str(round(price_eft_float / 1000) * 1000)
108
+ # Round to nearest 100 if price is above 10000
109
+ elif price_eft_float > 10000:
110
+ price_eft = str(round(price_eft_float / 100) * 100)
111
+ # Otherwise round to nearest 10
112
+ else:
113
+ price_eft = str(round(price_eft_float / 10) * 10)
114
+ except (ValueError, TypeError):
115
+ price_eft = ""
116
+
117
+ # Round discounted price
118
+ try:
119
+ if price_rebate_str:
120
+ price_rebate_float = float(price_rebate_str)
121
+ # Round to nearest 5000 if price is above 200000
122
+ if price_rebate_float > 200000:
123
+ price_rebate = str(round(price_rebate_float / 5000) * 5000)
124
+ # Round to nearest 1000 if price is above 30000
125
+ elif price_rebate_float > 30000:
126
+ price_rebate = str(round(price_rebate_float / 1000) * 1000)
127
+ # Round to nearest 100 if price is above 10000
128
+ elif price_rebate_float > 10000:
129
+ price_rebate = str(round(price_rebate_float / 100) * 100)
130
+ # Otherwise round to nearest 10
131
+ else:
132
+ price_rebate = str(round(price_rebate_float / 10) * 10)
133
+ else:
134
+ price_rebate = ""
135
+ except (ValueError, TypeError):
136
+ price_rebate = price_rebate_str
137
+
138
+ # Round money transfer discounted promotional price
139
+ try:
140
+ if price_rebate_money_order_str:
141
+ price_rebate_money_order_float = float(price_rebate_money_order_str)
142
+ # Round to nearest 5000 if price is above 200000
143
+ if price_rebate_money_order_float > 200000:
144
+ price_rebate_money_order = str(round(price_rebate_money_order_float / 5000) * 5000)
145
+ # Round to nearest 1000 if price is above 30000
146
+ elif price_rebate_money_order_float > 30000:
147
+ price_rebate_money_order = str(round(price_rebate_money_order_float / 1000) * 1000)
148
+ # Round to nearest 100 if price is above 10000
149
+ elif price_rebate_money_order_float > 10000:
150
+ price_rebate_money_order = str(round(price_rebate_money_order_float / 100) * 100)
151
+ # Otherwise round to nearest 10
152
+ else:
153
+ price_rebate_money_order = str(round(price_rebate_money_order_float / 10) * 10)
154
+ else:
155
+ price_rebate_money_order = ""
156
+ except (ValueError, TypeError):
157
+ price_rebate_money_order = price_rebate_money_order_str
158
+
159
+ # Get only product link, not image link
160
+ product_link = item.find('productLink').text if item.find('productLink') is not None else ""
161
+
162
+ # Combine all price information
163
+ item_info = (stock_amount, price, product_link, price_eft, price_rebate, price_rebate_money_order)
164
+ else:
165
+ # For out-of-stock products, only indicate stock status, don't provide price and link info
166
+ item_info = (stock_amount, "", "", "", "", "")
167
+
168
+ products.append((name, item_info, full_name))
169
+
170
+ # Hugging Face token
171
+ hfapi = os.getenv("hfapi")
172
+ if not hfapi:
173
+ raise ValueError("hfapi environment variable not set!")
174
+
175
+ create_repo("BF", token=hfapi, repo_type="space", space_sdk="gradio", exist_ok=True)
176
+
177
+ global_chat_history = [] # All chat history
178
+ history_lock = threading.Lock() # Lock for global history
179
+ file_lock = threading.Lock() # Lock for file writing
180
+ last_logged_index = 0 # Last logged message index
181
+
182
+ # Google Drive authentication (Service Account from Secrets)
183
+ SCOPES = ['https://www.googleapis.com/auth/drive.readonly']
184
+
185
+ def authenticate_google_drive():
186
+ service_account_json = os.getenv("SERVICE_ACCOUNT_JSON")
187
+ if not service_account_json:
188
+ raise ValueError("SERVICE_ACCOUNT_JSON environment variable not found!")
189
+
190
+ try:
191
+ json_data = json.loads(service_account_json)
192
+ print("JSON parsed successfully:", json_data.keys())
193
+ print("Private key content:", json_data.get("private_key"))
194
+ creds = Credentials.from_service_account_info(json_data, scopes=SCOPES)
195
+ except json.JSONDecodeError as e:
196
+ raise ValueError(f"JSON from Secrets is invalid: {e}")
197
+ except Exception as e:
198
+ raise ValueError(f"Authentication error: {e}")
199
+
200
+ return build('drive', 'v3', credentials=creds)
201
+
202
+ def download_file_from_drive(service, file_id, file_name):
203
+ request = service.files().get_media(fileId=file_id)
204
+ fh = io.BytesIO()
205
+ downloader = MediaIoBaseDownload(fh, request)
206
+ done = False
207
+ while done is False:
208
+ status, done = downloader.next_chunk()
209
+ fh.seek(0)
210
+ with open(file_name, 'wb') as f:
211
+ f.write(fh.read())
212
+ return file_name
213
+
214
+ # Fetch and process documents from Google Drive
215
+ document_content = ""
216
+ service = authenticate_google_drive()
217
 
218
+ try:
219
+ # Excel file from Google Drive
220
+ excel_file_id = "test10rgiGp5y5ZYU0dpRvps2t0t1dEzjW8LK" # Example Excel file ID, add your own ID
221
+ excel_file = download_file_from_drive(service, excel_file_id, "data.xlsx")
222
+ df = pd.read_excel(excel_file)
223
+ excel_text = df.to_string()
224
+ document_content += excel_text + "\n"
225
+ except Exception as e:
226
+ print(f"Google Drive Excel reading error: {e}")
227
 
228
+ try:
229
+ # Word file from Google Drive
230
+ word_file_id = "9I8H7G6F5E4D3C2B1A" # Example Word file ID, add your own ID
231
+ word_file = download_file_from_drive(service, word_file_id, "descriptions.docx")
232
+ doc = Document(word_file)
233
+ word_text = "\n".join([para.text for para in doc.paragraphs])
234
+ document_content += word_text
235
+ except Exception as e:
236
+ print(f"Google Drive Word reading error: {e}")
237
+
238
+ # Function to generate TTS audio from text
239
+ def text_to_speech(text, lang='tr'):
240
+ try:
241
+ tts = gTTS(text=text, lang=lang, slow=False)
242
+ audio_file = io.BytesIO()
243
+ tts.write_to_fp(audio_file)
244
+ audio_file.seek(0)
245
+ audio_base64 = base64.b64encode(audio_file.read()).decode('utf-8')
246
+ audio_html = f'<audio autoplay><source src="data:audio/mp3;base64,{audio_base64}" type="audio/mp3"></audio>'
247
+ return audio_html
248
+ except Exception as e:
249
+ print(f"TTS error: {e}")
250
+ return None
251
+
252
+ def run_scheduler(chat_history_ref):
253
+ def scheduled_save_and_upload():
254
+ global last_logged_index
255
+ if chat_history_ref:
256
+ print(f"Scheduler triggered: {time.strftime('%Y-%m-%d %H:%M:%S')}")
257
+ try:
258
+ with file_lock: # File writing lock
259
+ with open(LOG_FILE, 'a', encoding='utf-8') as f:
260
+ f.write("\n--- Scheduled Save: {} ---\n".format(time.strftime("%Y-%m-%d %H:%M:%S")))
261
+ with history_lock: # History lock
262
+ new_messages = chat_history_ref[last_logged_index:]
263
+ for msg in new_messages:
264
+ if msg["role"] in ["user", "assistant"]:
265
+ f.write(f"{msg['role'].capitalize()}: {msg['content']}\n")
266
+ last_logged_index = len(chat_history_ref) # Update last index
267
+ print(f"Chat saved to file: {os.path.abspath(LOG_FILE)}")
268
+ except Exception as e:
269
+ print(f"Save error: {e}")
270
+ time.sleep(5) # Wait before retrying
271
+ return # Early exit for retry
272
+
273
+ HF_REPO_ID = "SamiKoen/BF"
274
+ api = HfApi(token=hfapi)
275
+ for attempt in range(3): # Try 3 times
276
+ try:
277
+ with file_lock:
278
+ api.upload_file(
279
+ path_or_fileobj=LOG_FILE,
280
+ path_in_repo="chat_logs.txt",
281
+ repo_id=HF_REPO_ID,
282
+ repo_type="space",
283
+ commit_message="Automatic log update - {}".format(time.strftime("%Y-%m-%d %H:%M:%S"))
284
+ )
285
+ print(f"Log file uploaded to HF: {LOG_FILE}")
286
+ break
287
+ except Exception as e:
288
+ print(f"HF upload error (attempt {attempt+1}/3): {e}")
289
+ time.sleep(5)
290
+ else:
291
+ print("HF upload failed, all attempts completed.")
292
+ print(f"Scheduled job completed: {time.strftime('%H:%M:%S')}")
293
+
294
+ schedule.every().day.at("11:32").do(scheduled_save_and_upload)
295
+ schedule.every().day.at("15:15").do(scheduled_save_and_upload)
296
+ schedule.every().day.at("15:30").do(scheduled_save_and_upload)
297
+ schedule.every().day.at("17:32").do(scheduled_save_and_upload)
298
+ schedule.every().day.at("19:15").do(scheduled_save_and_upload)
299
+ schedule.every().day.at("21:30").do(scheduled_save_and_upload)
300
+ print("Scheduler started")
301
+ while True:
302
+ schedule.run_pending()
303
+ time.sleep(60)
304
+
305
+ @spaces.GPU(duration=1200)
306
+ def chatbot_fn(user_message, history):
307
+ if history is None:
308
+ history = []
309
+
310
+ # Log: Add user message
311
+ try:
312
+ with file_lock:
313
+ with open(LOG_FILE, 'a', encoding='utf-8') as f:
314
+ f.write(f"User: {user_message}\n")
315
+ except Exception as e:
316
+ print(f"File writing error (User): {e}")
317
+
318
+ # System messages (prompt) - just keeping the structure without showing actual prompts
319
+ system_messages = [
320
+ {"role": "system", "content": "You are an AI assistant for Trek bicycles."}
321
+ ]
322
+
323
+ # Add document data to system messages
324
+ if document_content:
325
+ system_messages.append({"role": "system", "content": f"Document information: {document_content}"})
326
+
327
+ # Check if product name is mentioned in user message
328
+ input_words = user_message.lower().split()
329
+ for product_info in products:
330
+ if product_info[0] in input_words:
331
+ if product_info[1][0] == "stokta":
332
+ # Check for campaign price
333
+ has_campaign = product_info[1][4] and product_info[1][4] != ""
334
+
335
+ # Original price is always shown
336
+ normal_price = f"Original price: {product_info[1][1]} TL"
337
+
338
+ # Campaign price info
339
+ rebate_price = ""
340
+ discount_info = ""
341
+ eft_price = ""
342
+ rebate_money_order_price = ""
343
+
344
+ if has_campaign:
345
+ # If product has campaign, show campaign price
346
+ rebate_price = f"\nCampaign price: {product_info[1][4]} TL"
347
+
348
+ # Calculate discount amount
349
+ try:
350
+ original_price = float(product_info[1][1].replace(',', '.'))
351
+ campaign_price = float(product_info[1][4].replace(',', '.'))
352
+ discount_amount = original_price - campaign_price
353
+
354
+ # Show discount amount if greater than 0
355
+ if discount_amount > 0:
356
+ # Apply rounding rules for discount amount
357
+ if discount_amount > 200000:
358
+ discount_amount_rounded = round(discount_amount / 5000) * 5000
359
+ elif discount_amount > 30000:
360
+ discount_amount_rounded = round(discount_amount / 1000) * 1000
361
+ elif discount_amount > 10000:
362
+ discount_amount_rounded = round(discount_amount / 100) * 100
363
+ else:
364
+ discount_amount_rounded = round(discount_amount / 10) * 10
365
+
366
+ # Discount info
367
+ discount_info = f"\nDiscount amount: {discount_amount_rounded:.0f} TL"
368
+ except (ValueError, TypeError):
369
+ discount_info = ""
370
+
371
+ # Don't show money transfer discounted campaign price
372
+ rebate_money_order_price = ""
373
+ else:
374
+ # If not campaign, show money transfer discounted normal price
375
+ if product_info[1][3] and product_info[1][3] != "":
376
+ eft_price = f"\nMoney transfer discounted price: {product_info[1][3]} TL"
377
+
378
+ # Product link
379
+ product_link = f"\nProduct link: {product_info[1][2]}"
380
+
381
+ # Combine all information
382
+ 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}"
383
+ else:
384
+ # If product is out of stock, just show stock status
385
+ new_msg = f"{product_info[2]} {product_info[1][0]}"
386
+ system_messages.append({"role": "system", "content": new_msg})
387
+
388
+ messages = system_messages + history + [{"role": "user", "content": user_message}]
389
 
390
  payload = {
391
+ "model": "gpt-4.5-preview",
392
+ "messages": messages,
393
  "temperature": 0.2,
394
+ "top_p": 1,
395
+ "n": 1,
396
+ "stream": True,
397
+ "presence_penalty": 0,
398
+ "frequency_penalty": 0,
399
  }
400
+ headers = {
401
+ "Content-Type": "application/json",
402
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
403
+ }
404
+
405
+ response = requests.post(API_URL, headers=headers, json=payload, stream=True)
406
+ if response.status_code != 200:
407
+ yield "An error occurred."
408
+ return
409
+
410
+ partial_response = ""
411
+
412
+ for chunk in response.iter_lines():
413
+ if not chunk:
414
+ continue
415
+ chunk_str = chunk.decode('utf-8')
416
+ if chunk_str.startswith("data: ") and chunk_str != "data: [DONE]":
417
+ try:
418
+ chunk_data = json.loads(chunk_str[6:])
419
+ delta = chunk_data['choices'][0]['delta']
420
+ if 'content' in delta:
421
+ partial_response += delta['content']
422
+ yield partial_response
423
+ except json.JSONDecodeError as e:
424
+ print(f"JSON parse error: {e} - Chunk: {chunk_str}")
425
+ elif chunk_str == "data: [DONE]":
426
+ break
427
+
428
+ # Generate TTS audio
429
+ audio_html = text_to_speech(partial_response)
430
+
431
+ # Add audio to response if available
432
+ final_response = partial_response
433
+ if audio_html:
434
+ final_response += f'\n\n{audio_html}'
435
 
436
+ # Log: Add assistant response
437
+ try:
438
+ with file_lock:
439
+ with open(LOG_FILE, 'a', encoding='utf-8') as f:
440
+ f.write(f"Bot: {partial_response}\n")
441
+ except Exception as e:
442
+ print(f"File writing error (Bot): {e}")
443
 
444
+ # Update global history
445
+ with history_lock:
446
+ global_chat_history.append({"role": "user", "content": user_message})
447
+ global_chat_history.append({"role": "assistant", "content": partial_response})
448
+
449
+ yield final_response
450
 
451
+ # Slow echo (for testing)
452
+ def slow_echo(message, history):
453
+ for i in range(len(message)):
454
+ time.sleep(0.05)
455
+ yield "You typed: " + message[: i + 1]
 
456
 
457
+ # Usage mode
458
+ USE_SLOW_ECHO = False
459
+ chat_fn = slow_echo if USE_SLOW_ECHO else chatbot_fn
460
 
461
+ if not USE_SLOW_ECHO:
462
+ scheduler_thread = threading.Thread(target=run_scheduler, args=(global_chat_history,), daemon=True)
463
+ scheduler_thread.start()
464
 
465
+ demo = gr.ChatInterface(
466
+ fn=chat_fn,
467
+ title="Trek Assistant with Text-to-Speech",
468
+ theme="default",
469
+ type="messages",
470
+ flagging_mode="manual",
471
+ flagging_options=["Correct", "Incorrect", "Not sure", "Other"],
472
+ save_history=True
473
  )
474
 
475
  if __name__ == "__main__":
476
+ demo.launch(debug=True)