Upload 3 files
Browse files- app.py +58 -24
- enhanced_features.py +31 -3
- image_renderer.py +143 -0
app.py
CHANGED
@@ -27,6 +27,7 @@ from enhanced_features import (
|
|
27 |
initialize_enhanced_features, process_image_message,
|
28 |
handle_comparison_request, get_user_recommendations, profile_manager
|
29 |
)
|
|
|
30 |
|
31 |
# Gradio uyarılarını bastır
|
32 |
warnings.filterwarnings("ignore", category=UserWarning, module="gradio.components.chatbot")
|
@@ -171,14 +172,15 @@ if root is not None:
|
|
171 |
except (ValueError, TypeError):
|
172 |
price_rebate_money_order = price_rebate_money_order_str
|
173 |
|
174 |
-
#
|
175 |
product_link = item.find('productLink').text if item.find('productLink') is not None else ""
|
|
|
176 |
|
177 |
-
# Tüm fiyat bilgilerini birleştir
|
178 |
-
item_info = (stock_amount, price, product_link, price_eft, price_rebate, price_rebate_money_order)
|
179 |
else:
|
180 |
# Stokta olmayan ürünler için sadece stok durumunu belirt, fiyat ve link bilgisi verme
|
181 |
-
item_info = (stock_amount, "", "", "", "", "")
|
182 |
|
183 |
products.append((name, item_info, full_name))
|
184 |
|
@@ -412,11 +414,14 @@ def chatbot_fn(user_message, history, image=None):
|
|
412 |
if product_info[1][3] and product_info[1][3] != "":
|
413 |
eft_price = f"\nHavale indirimli fiyat: {product_info[1][3]} TL"
|
414 |
|
415 |
-
# Ürün linki
|
416 |
product_link = f"\nÜrün linki: {product_info[1][2]}"
|
|
|
|
|
|
|
417 |
|
418 |
# Tüm bilgileri birleştir
|
419 |
-
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}"
|
420 |
else:
|
421 |
# Ürün stokta yoksa sadece stok durumunu bildir
|
422 |
new_msg = f"{product_info[2]} {product_info[1][0]}"
|
@@ -456,11 +461,17 @@ def chatbot_fn(user_message, history, image=None):
|
|
456 |
delta = chunk_data['choices'][0]['delta']
|
457 |
if 'content' in delta:
|
458 |
partial_response += delta['content']
|
459 |
-
|
|
|
|
|
460 |
except json.JSONDecodeError as e:
|
461 |
print(f"JSON parse hatası: {e} - Chunk: {chunk_str}")
|
462 |
elif chunk_str == "data: [DONE]":
|
463 |
break
|
|
|
|
|
|
|
|
|
464 |
|
465 |
# Log: Asistan cevabını ekle
|
466 |
try:
|
@@ -564,6 +575,35 @@ h3 {
|
|
564 |
text-rendering: optimizeLegibility;
|
565 |
}
|
566 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
567 |
/* JavaScript ile placeholder eklemek için */
|
568 |
</style>
|
569 |
<script>
|
@@ -592,33 +632,27 @@ def enhanced_chatbot_fn(message, history, image):
|
|
592 |
# Demo arayüzü - Özel layout ile image upload destekli
|
593 |
with gr.Blocks(css=custom_css, theme="soft", title="Trek Asistanı") as demo:
|
594 |
gr.Markdown("# 🚲 Trek Asistanı AI")
|
595 |
-
gr.Markdown("**
|
596 |
|
597 |
with gr.Row():
|
598 |
with gr.Column(scale=4):
|
599 |
-
chatbot = gr.Chatbot(height=500)
|
600 |
|
601 |
with gr.Column(scale=1):
|
602 |
-
gr.Markdown("###
|
603 |
-
|
604 |
-
gr.Markdown("### 🔍 Özel Komutlar")
|
605 |
-
gr.Markdown("""
|
606 |
-
• **Karşılaştırma:** "Émonda ve Madone karşılaştır"
|
607 |
-
• **Bütçe:** "50-100K TL bütçem var"
|
608 |
-
• **Görsel:** Bisiklet fotoğrafı yükle
|
609 |
-
""")
|
610 |
|
611 |
with gr.Row():
|
612 |
msg = gr.Textbox(
|
613 |
-
placeholder="
|
614 |
show_label=False,
|
615 |
scale=4
|
616 |
)
|
617 |
submit_btn = gr.Button("Gönder", scale=1)
|
618 |
|
619 |
-
def respond(message, chat_history
|
620 |
-
# Enhanced chatbot fonksiyonunu çağır
|
621 |
-
response_generator = chatbot_fn(message, chat_history,
|
622 |
|
623 |
# Generator'dan son cevabı al
|
624 |
response = ""
|
@@ -627,10 +661,10 @@ with gr.Blocks(css=custom_css, theme="soft", title="Trek Asistanı") as demo:
|
|
627 |
|
628 |
# Chat history güncelle
|
629 |
chat_history.append((message, response))
|
630 |
-
return "", chat_history
|
631 |
|
632 |
-
submit_btn.click(respond, [msg, chatbot
|
633 |
-
msg.submit(respond, [msg, chatbot
|
634 |
|
635 |
if __name__ == "__main__":
|
636 |
demo.launch(debug=True)
|
|
|
27 |
initialize_enhanced_features, process_image_message,
|
28 |
handle_comparison_request, get_user_recommendations, profile_manager
|
29 |
)
|
30 |
+
from image_renderer import extract_product_info_for_gallery, format_message_with_images
|
31 |
|
32 |
# Gradio uyarılarını bastır
|
33 |
warnings.filterwarnings("ignore", category=UserWarning, module="gradio.components.chatbot")
|
|
|
172 |
except (ValueError, TypeError):
|
173 |
price_rebate_money_order = price_rebate_money_order_str
|
174 |
|
175 |
+
# Ürün linkini ve resim URL'sini al
|
176 |
product_link = item.find('productLink').text if item.find('productLink') is not None else ""
|
177 |
+
image_url = item.find('picture1Path').text if item.find('picture1Path') is not None else ""
|
178 |
|
179 |
+
# Tüm fiyat bilgilerini birleştir (resim URL'si eklendi)
|
180 |
+
item_info = (stock_amount, price, product_link, price_eft, price_rebate, price_rebate_money_order, image_url)
|
181 |
else:
|
182 |
# Stokta olmayan ürünler için sadece stok durumunu belirt, fiyat ve link bilgisi verme
|
183 |
+
item_info = (stock_amount, "", "", "", "", "", "")
|
184 |
|
185 |
products.append((name, item_info, full_name))
|
186 |
|
|
|
414 |
if product_info[1][3] and product_info[1][3] != "":
|
415 |
eft_price = f"\nHavale indirimli fiyat: {product_info[1][3]} TL"
|
416 |
|
417 |
+
# Ürün linki ve resim
|
418 |
product_link = f"\nÜrün linki: {product_info[1][2]}"
|
419 |
+
product_image = ""
|
420 |
+
if len(product_info[1]) > 6 and product_info[1][6]: # Resim URL'si varsa
|
421 |
+
product_image = f"\nÜrün resmi: {product_info[1][6]}"
|
422 |
|
423 |
# Tüm bilgileri birleştir
|
424 |
+
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}{product_image}"
|
425 |
else:
|
426 |
# Ürün stokta yoksa sadece stok durumunu bildir
|
427 |
new_msg = f"{product_info[2]} {product_info[1][0]}"
|
|
|
461 |
delta = chunk_data['choices'][0]['delta']
|
462 |
if 'content' in delta:
|
463 |
partial_response += delta['content']
|
464 |
+
# Resim formatlaması uygula
|
465 |
+
formatted_response = extract_product_info_for_gallery(partial_response)
|
466 |
+
yield formatted_response
|
467 |
except json.JSONDecodeError as e:
|
468 |
print(f"JSON parse hatası: {e} - Chunk: {chunk_str}")
|
469 |
elif chunk_str == "data: [DONE]":
|
470 |
break
|
471 |
+
|
472 |
+
# Son resim formatlaması
|
473 |
+
final_response = extract_product_info_for_gallery(partial_response)
|
474 |
+
yield final_response
|
475 |
|
476 |
# Log: Asistan cevabını ekle
|
477 |
try:
|
|
|
575 |
text-rendering: optimizeLegibility;
|
576 |
}
|
577 |
|
578 |
+
/* Resim gösterimi için özel stiller */
|
579 |
+
#chatbot .message img {
|
580 |
+
max-width: 100%;
|
581 |
+
height: auto;
|
582 |
+
border-radius: 8px;
|
583 |
+
margin: 8px 0;
|
584 |
+
}
|
585 |
+
|
586 |
+
#chatbot .message {
|
587 |
+
word-wrap: break-word;
|
588 |
+
}
|
589 |
+
|
590 |
+
/* Ürün galeri stilleri */
|
591 |
+
.product-gallery {
|
592 |
+
display: flex;
|
593 |
+
flex-wrap: wrap;
|
594 |
+
gap: 10px;
|
595 |
+
margin: 10px 0;
|
596 |
+
}
|
597 |
+
|
598 |
+
.product-card {
|
599 |
+
border: 1px solid #ddd;
|
600 |
+
border-radius: 8px;
|
601 |
+
padding: 8px;
|
602 |
+
max-width: 180px;
|
603 |
+
text-align: center;
|
604 |
+
background: white;
|
605 |
+
}
|
606 |
+
|
607 |
/* JavaScript ile placeholder eklemek için */
|
608 |
</style>
|
609 |
<script>
|
|
|
632 |
# Demo arayüzü - Özel layout ile image upload destekli
|
633 |
with gr.Blocks(css=custom_css, theme="soft", title="Trek Asistanı") as demo:
|
634 |
gr.Markdown("# 🚲 Trek Asistanı AI")
|
635 |
+
gr.Markdown("**Akıllı özellikler:** Ürün karşılaştırması, kişisel öneriler ve detaylı ürün bilgileri sunuyorum.")
|
636 |
|
637 |
with gr.Row():
|
638 |
with gr.Column(scale=4):
|
639 |
+
chatbot = gr.Chatbot(height=500, elem_id="chatbot", show_label=False)
|
640 |
|
641 |
with gr.Column(scale=1):
|
642 |
+
gr.Markdown("### ℹ️ Bilgi")
|
643 |
+
gr.Markdown("Trek bisiklet uzmanı AI asistanınız. Ürün bilgileri, fiyatlar ve öneriler için sorularınızı yazın.")
|
|
|
|
|
|
|
|
|
|
|
|
|
644 |
|
645 |
with gr.Row():
|
646 |
msg = gr.Textbox(
|
647 |
+
placeholder="Trek bisikletleri hakkında soru sorun...",
|
648 |
show_label=False,
|
649 |
scale=4
|
650 |
)
|
651 |
submit_btn = gr.Button("Gönder", scale=1)
|
652 |
|
653 |
+
def respond(message, chat_history):
|
654 |
+
# Enhanced chatbot fonksiyonunu çağır (image=None)
|
655 |
+
response_generator = chatbot_fn(message, chat_history, None)
|
656 |
|
657 |
# Generator'dan son cevabı al
|
658 |
response = ""
|
|
|
661 |
|
662 |
# Chat history güncelle
|
663 |
chat_history.append((message, response))
|
664 |
+
return "", chat_history
|
665 |
|
666 |
+
submit_btn.click(respond, [msg, chatbot], [msg, chatbot])
|
667 |
+
msg.submit(respond, [msg, chatbot], [msg, chatbot])
|
668 |
|
669 |
if __name__ == "__main__":
|
670 |
demo.launch(debug=True)
|
enhanced_features.py
CHANGED
@@ -181,6 +181,25 @@ class ProductComparison:
|
|
181 |
def __init__(self, products_data):
|
182 |
self.products = products_data
|
183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
184 |
def find_products_by_name(self, product_names):
|
185 |
"""İsimlere göre ürünleri bul"""
|
186 |
found_products = []
|
@@ -205,14 +224,23 @@ class ProductComparison:
|
|
205 |
|
206 |
# Ürün bilgilerini parse et
|
207 |
stock_status = item_info[0] if len(item_info) > 0 else "Bilgi yok"
|
208 |
-
|
209 |
product_link = item_info[2] if len(item_info) > 2 else ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
210 |
|
211 |
comparison_data.append({
|
212 |
"Ürün": full_name,
|
213 |
"Stok": stock_status,
|
214 |
-
"Fiyat":
|
215 |
-
"Link": product_link
|
|
|
216 |
})
|
217 |
|
218 |
# DataFrame oluştur
|
|
|
181 |
def __init__(self, products_data):
|
182 |
self.products = products_data
|
183 |
|
184 |
+
def round_price(self, price_str):
|
185 |
+
"""Fiyatı yuvarlama formülüne göre yuvarla"""
|
186 |
+
try:
|
187 |
+
price_float = float(price_str)
|
188 |
+
# Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla
|
189 |
+
if price_float > 200000:
|
190 |
+
return str(round(price_float / 5000) * 5000)
|
191 |
+
# Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla
|
192 |
+
elif price_float > 30000:
|
193 |
+
return str(round(price_float / 1000) * 1000)
|
194 |
+
# Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla
|
195 |
+
elif price_float > 10000:
|
196 |
+
return str(round(price_float / 100) * 100)
|
197 |
+
# Diğer durumlarda en yakın 10'luk basamağa yuvarla
|
198 |
+
else:
|
199 |
+
return str(round(price_float / 10) * 10)
|
200 |
+
except (ValueError, TypeError):
|
201 |
+
return price_str
|
202 |
+
|
203 |
def find_products_by_name(self, product_names):
|
204 |
"""İsimlere göre ürünleri bul"""
|
205 |
found_products = []
|
|
|
224 |
|
225 |
# Ürün bilgilerini parse et
|
226 |
stock_status = item_info[0] if len(item_info) > 0 else "Bilgi yok"
|
227 |
+
price_raw = item_info[1] if len(item_info) > 1 and item_info[1] else "Fiyat yok"
|
228 |
product_link = item_info[2] if len(item_info) > 2 else ""
|
229 |
+
image_url = item_info[6] if len(item_info) > 6 and item_info[6] else ""
|
230 |
+
|
231 |
+
# Fiyatı yuvarlama formülüne göre yuvarla
|
232 |
+
if price_raw != "Fiyat yok":
|
233 |
+
price = self.round_price(price_raw)
|
234 |
+
price_display = f"{price} TL"
|
235 |
+
else:
|
236 |
+
price_display = price_raw
|
237 |
|
238 |
comparison_data.append({
|
239 |
"Ürün": full_name,
|
240 |
"Stok": stock_status,
|
241 |
+
"Fiyat": price_display,
|
242 |
+
"Link": product_link,
|
243 |
+
"Resim": image_url if image_url else "Resim yok"
|
244 |
})
|
245 |
|
246 |
# DataFrame oluştur
|
image_renderer.py
ADDED
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""
|
3 |
+
Ürün Resimlerini Sohbette Gösterme Sistemi
|
4 |
+
"""
|
5 |
+
|
6 |
+
def round_price(price_str):
|
7 |
+
"""Fiyatı yuvarlama formülüne göre yuvarla"""
|
8 |
+
try:
|
9 |
+
# TL ve diğer karakterleri temizle
|
10 |
+
price_clean = price_str.replace(' TL', '').replace(',', '.')
|
11 |
+
price_float = float(price_clean)
|
12 |
+
|
13 |
+
# Fiyat 200000 üzerindeyse en yakın 5000'lik basamağa yuvarla
|
14 |
+
if price_float > 200000:
|
15 |
+
return str(round(price_float / 5000) * 5000)
|
16 |
+
# Fiyat 30000 üzerindeyse en yakın 1000'lik basamağa yuvarla
|
17 |
+
elif price_float > 30000:
|
18 |
+
return str(round(price_float / 1000) * 1000)
|
19 |
+
# Fiyat 10000 üzerindeyse en yakın 100'lük basamağa yuvarla
|
20 |
+
elif price_float > 10000:
|
21 |
+
return str(round(price_float / 100) * 100)
|
22 |
+
# Diğer durumlarda en yakın 10'luk basamağa yuvarla
|
23 |
+
else:
|
24 |
+
return str(round(price_float / 10) * 10)
|
25 |
+
except (ValueError, TypeError):
|
26 |
+
return price_str
|
27 |
+
|
28 |
+
def format_message_with_images(message):
|
29 |
+
"""Mesajdaki resim URL'lerini HTML formatına çevir"""
|
30 |
+
if "Ürün resmi:" not in message:
|
31 |
+
return message
|
32 |
+
|
33 |
+
lines = message.split('\n')
|
34 |
+
formatted_lines = []
|
35 |
+
|
36 |
+
for line in lines:
|
37 |
+
if line.startswith("Ürün resmi:"):
|
38 |
+
image_url = line.replace("Ürün resmi:", "").strip()
|
39 |
+
if image_url:
|
40 |
+
# HTML img tag'i oluştur
|
41 |
+
img_html = f"""
|
42 |
+
<div style="margin: 10px 0;">
|
43 |
+
<img src="{image_url}"
|
44 |
+
alt="Ürün Resmi"
|
45 |
+
style="max-width: 300px; max-height: 200px; border-radius: 8px; border: 1px solid #ddd; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"
|
46 |
+
onerror="this.style.display='none'">
|
47 |
+
</div>"""
|
48 |
+
formatted_lines.append(img_html)
|
49 |
+
else:
|
50 |
+
formatted_lines.append(line)
|
51 |
+
else:
|
52 |
+
formatted_lines.append(line)
|
53 |
+
|
54 |
+
return '\n'.join(formatted_lines)
|
55 |
+
|
56 |
+
def create_product_gallery(products_with_images):
|
57 |
+
"""Birden fazla ürün için galeri oluştur"""
|
58 |
+
if not products_with_images:
|
59 |
+
return ""
|
60 |
+
|
61 |
+
gallery_html = """
|
62 |
+
<div style="display: flex; flex-wrap: wrap; gap: 15px; margin: 15px 0;">
|
63 |
+
"""
|
64 |
+
|
65 |
+
for product in products_with_images:
|
66 |
+
name = product.get('name', 'Bilinmeyen')
|
67 |
+
price = product.get('price', 'Fiyat yok')
|
68 |
+
image_url = product.get('image_url', '')
|
69 |
+
product_url = product.get('product_url', '')
|
70 |
+
|
71 |
+
if image_url:
|
72 |
+
card_html = f"""
|
73 |
+
<div style="border: 1px solid #ddd; border-radius: 8px; padding: 10px; max-width: 200px; text-align: center; background: white; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
74 |
+
<img src="{image_url}"
|
75 |
+
alt="{name}"
|
76 |
+
style="max-width: 180px; max-height: 120px; border-radius: 4px; margin-bottom: 8px;"
|
77 |
+
onerror="this.style.display='none'">
|
78 |
+
<div style="font-size: 12px; font-weight: bold; margin-bottom: 4px;">{name}</div>
|
79 |
+
<div style="font-size: 11px; color: #666; margin-bottom: 8px;">{price}</div>
|
80 |
+
{f'<a href="{product_url}" target="_blank" style="font-size: 10px; color: #007bff; text-decoration: none;">Detaylı Bilgi</a>' if product_url else ''}
|
81 |
+
</div>"""
|
82 |
+
gallery_html += card_html
|
83 |
+
|
84 |
+
gallery_html += "</div>"
|
85 |
+
return gallery_html
|
86 |
+
|
87 |
+
def extract_product_info_for_gallery(message):
|
88 |
+
"""Mesajdan ürün bilgilerini çıkarıp galeri formatına çevir"""
|
89 |
+
if "karşılaştır" in message.lower() or "öneri" in message.lower():
|
90 |
+
# Bu durumda galeri formatı kullan
|
91 |
+
lines = message.split('\n')
|
92 |
+
products = []
|
93 |
+
|
94 |
+
current_product = {}
|
95 |
+
for line in lines:
|
96 |
+
line = line.strip()
|
97 |
+
if line.startswith('•') and any(keyword in line.lower() for keyword in ['marlin', 'émonda', 'madone', 'domane', 'fuel', 'powerfly', 'fx']):
|
98 |
+
# Yeni ürün başladı
|
99 |
+
if current_product:
|
100 |
+
products.append(current_product)
|
101 |
+
|
102 |
+
# Ürün adı ve fiyatı parse et
|
103 |
+
parts = line.split(' - ')
|
104 |
+
name = parts[0].replace('•', '').strip()
|
105 |
+
price_raw = parts[1] if len(parts) > 1 else 'Fiyat yok'
|
106 |
+
|
107 |
+
# Fiyatı yuvarlama formülüne göre yuvarla
|
108 |
+
if price_raw != 'Fiyat yok':
|
109 |
+
price = round_price(price_raw) + ' TL'
|
110 |
+
else:
|
111 |
+
price = price_raw
|
112 |
+
|
113 |
+
current_product = {
|
114 |
+
'name': name,
|
115 |
+
'price': price,
|
116 |
+
'image_url': '',
|
117 |
+
'product_url': ''
|
118 |
+
}
|
119 |
+
elif "Ürün resmi:" in line and current_product:
|
120 |
+
current_product['image_url'] = line.replace("Ürün resmi:", "").strip()
|
121 |
+
elif "Ürün linki:" in line and current_product:
|
122 |
+
current_product['product_url'] = line.replace("Ürün linki:", "").strip()
|
123 |
+
|
124 |
+
# Son ürünü ekle
|
125 |
+
if current_product:
|
126 |
+
products.append(current_product)
|
127 |
+
|
128 |
+
if products:
|
129 |
+
gallery = create_product_gallery(products)
|
130 |
+
# Orijinal mesajdaki resim linklerini temizle
|
131 |
+
cleaned_message = message
|
132 |
+
for line in message.split('\n'):
|
133 |
+
if line.startswith("Ürün resmi:") or line.startswith("Ürün linki:"):
|
134 |
+
cleaned_message = cleaned_message.replace(line, "")
|
135 |
+
|
136 |
+
return cleaned_message.strip() + "\n\n" + gallery
|
137 |
+
|
138 |
+
return format_message_with_images(message)
|
139 |
+
|
140 |
+
def should_use_gallery_format(message):
|
141 |
+
"""Mesajın galeri formatı kullanması gerekip gerekmediğini kontrol et"""
|
142 |
+
gallery_keywords = ["karşılaştır", "öneri", "seçenek", "alternatif", "bütçe"]
|
143 |
+
return any(keyword in message.lower() for keyword in gallery_keywords)
|