Mrttbn commited on
Commit
a31df29
·
verified ·
1 Parent(s): 3a34019

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +156 -153
app.py CHANGED
@@ -18,13 +18,14 @@ warnings.filterwarnings('ignore')
18
  # AYARLAR VE GLOBAL DEĞİŞKENLER
19
  # ---------------------------------------------------------
20
 
21
- # BURAYA HUGGING FACE TOKEN'INIZI YAZIN (Settings -> Access Tokens -> Write yetkili)
22
- HF_TOKEN = os.getenv("HF_TOKEN") # veya direkt buraya "hf_..." yazın
 
23
 
24
- # Llama-3 Modeli (Serverless API üzerinden)
25
  LLM_MODEL_ID = "meta-llama/Meta-Llama-3-8B-Instruct"
26
 
27
- # Global Değişkenler
28
  embedding_model = None
29
  llm_client = None
30
  df = None
@@ -35,57 +36,68 @@ embeddings = None
35
  # YARDIMCI FONKSİYONLAR
36
  # ---------------------------------------------------------
37
 
38
- def get_llama_sentiment(text, client):
39
- """
40
- Daha sağlam ve hataları gösteren analiz fonksiyonu.
 
 
 
 
 
 
 
 
 
 
 
 
41
  """
42
- # Prompt'u Llama-3'ün daha iyi anlayacağı hale getirdik ve örnek verdik.
43
- system_prompt = """
44
- You are a crypto sentiment analysis expert. Analyze the news title.
45
- You MUST return a valid JSON object. Do NOT write any introduction or explanation.
46
-
47
- Format:
48
- {"label": "positive", "score": 0.9}
49
-
50
- Labels can be: "positive", "negative", "neutral".
51
- Score is between 0.0 and 1.0.
52
  """
53
-
 
 
 
 
 
 
 
54
  user_prompt = f"News Title: {text}"
55
-
56
  try:
57
  response = client.chat.completions.create(
58
  model=LLM_MODEL_ID,
59
  messages=[
60
  {"role": "system", "content": system_prompt},
61
- {"role": "user", "content": user_prompt}
62
  ],
63
- max_tokens=100,
64
- temperature=0.1 # Daha tutarlı olması için düşürdük
65
  )
66
-
67
- # Yanıtı al
68
- output_text = response.choices[0].message.content.strip()
69
-
70
- # HATA AYIKLAMA: Modelin ne cevap verdiğini konsola basalım
71
- # (Çalıştığında burayı kapatabilirsin ama şimdilik kalsın)
72
- print(f"Model Yanıtı ({text[:20]...}): {output_text}")
73
-
74
- # Temizlik: Markdown kod bloklarını (```json ... ```) temizle
75
- output_text = output_text.replace("```json", "").replace("```", "").strip()
76
-
77
- # JSON'u bul ve ayıkla
78
- json_match = re.search(r'\{.*\}', output_text, re.DOTALL)
79
-
80
- if json_match:
81
- data = json.loads(json_match.group())
82
- return data.get("label", "neutral"), float(data.get("score", 0.5))
83
- else:
84
  print(f"⚠️ JSON Bulunamadı. Gelen ham veri: {output_text}")
85
  return "neutral", 0.5
86
-
 
 
 
 
 
 
 
 
 
 
87
  except Exception as e:
88
- # HATA BASMA: İşte hatanın asıl sebebini burada göreceksin
89
  print(f"❌ API/Bağlantı Hatası: {str(e)}")
90
  return "neutral", 0.5
91
 
@@ -96,163 +108,151 @@ def get_llama_sentiment(text, client):
96
  def initialize_models(token_input):
97
  """Modelleri ve API İstemcisini Başlat"""
98
  global embedding_model, llm_client, HF_TOKEN
99
-
100
- # Eğer arayüzden token girildiyse onu kullan
101
- if token_input:
102
- HF_TOKEN = token_input
103
-
104
  if not HF_TOKEN:
105
- return "❌ Hata: Lütfen bir Hugging Face Token giriniz!"
106
 
107
  try:
108
  if embedding_model is None:
109
- # Arama için hafif embedding modeli (Llama-3 embedding için ağır kaçar)
110
- embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
111
-
112
  if llm_client is None:
113
- # Llama-3 için Inference Client başlat
114
  llm_client = InferenceClient(token=HF_TOKEN)
115
-
116
- # Bağlantı testi
117
- try:
118
- llm_client.get_model_status(LLM_MODEL_ID)
119
- except:
120
- pass # Bazen status 403 dönebilir ama model çalışır
121
-
122
- return f"✅ Llama-3 API ({LLM_MODEL_ID}) ve Embedding modeli hazır!"
123
  except Exception as e:
124
- return f"❌ Model yükleme hatası: {str(e)}"
125
 
126
  def fetch_news():
127
  """RSS'den haber çek ve Llama-3 ile analiz et"""
128
- global df, index, embeddings, llm_client
129
-
130
- if llm_client is None:
131
- return "⚠️ Önce Başlangıç sekmesinden modeli başlatın!", None
132
 
133
  RSS_URLS = [
134
  "https://cointelegraph.com/rss",
135
  "https://cryptonews.com/news/feed",
136
- "https://www.coindesk.com/arc/outboundfeeds/rss/"
137
  ]
138
-
139
  all_entries = []
140
  status_messages = []
141
-
142
- # Haberleri Topla
143
  for url in RSS_URLS:
144
  try:
145
  feed = feedparser.parse(url)
146
- # Demo hızı için her kaynaktan sadece 5 haber alalım (API limitleri için)
147
- for entry in feed.entries[:5]:
148
- all_entries.append({
149
- "title": entry.get("title", ""),
150
- "link": entry.get("link", ""),
151
- "published": entry.get("published", "")
152
- })
 
 
153
  status_messages.append(f"✓ {url.split('/')[2]} okundu.")
154
- except Exception as e:
155
  status_messages.append(f"✗ {url} hatası.")
156
-
157
  df = pd.DataFrame(all_entries).drop_duplicates(subset="title").reset_index(drop=True)
158
-
159
  if len(df) == 0:
160
  return "Haber bulunamadı.", None
161
 
162
- # Llama-3 ile Analiz Döngüsü
163
- status_messages.append("\n🤖 Llama-3 ile analiz yapılıyor (Lütfen bekleyin)...")
164
-
165
  labels = []
166
  scores = []
167
-
168
- # İlerleme çubuğu olmadığı için basit döngü
169
- for title in df["title"]:
170
  lbl, scr = get_llama_sentiment(title, llm_client)
171
  labels.append(lbl)
172
  scores.append(scr)
173
-
174
  df["sentiment_label"] = labels
175
  df["sentiment_score"] = scores
176
-
177
- # FAISS Index Oluştur (Arama için)
178
- corpus = df['title'].tolist()
179
- embeddings = embedding_model.encode(corpus)
180
- dimension = embeddings.shape[1]
181
- index = faiss.IndexFlatL2(dimension)
182
- index.add(embeddings.astype('float32'))
183
-
184
- final_msg = "\n".join(status_messages) + f"\n\n✅ {len(df)} haber Llama-3-8B-Instruct tarafından analiz edildi."
185
-
186
  return final_msg, df[["title", "sentiment_label", "sentiment_score"]].head(10)
187
 
188
  def search_similar_news(query, top_k=3):
189
- """Semantik Arama"""
190
  global df, index, embedding_model
191
-
192
- if df is None or index is None:
193
  return "⚠️ Önce haberleri toplayın!", None
194
-
195
  try:
196
- q_embedding = embedding_model.encode([query])
197
- distances, indices = index.search(q_embedding.astype('float32'), k=min(top_k, len(df)))
198
-
199
  results = []
200
  for idx in indices[0]:
201
- news = df.iloc[idx]
202
- results.append({
203
- "Başlık": news['title'],
204
- "Llama-3 Görüşü": news['sentiment_label'],
205
- "Güven Skoru": news['sentiment_score'],
206
- "Link": news['link']
207
- })
208
-
 
 
209
  return f"🔎 '{query}' için sonuçlar:", pd.DataFrame(results)
210
  except Exception as e:
211
  return f"Hata: {str(e)}", None
212
 
213
  def analyze_coin_sentiment(coin_name):
214
- """Coin Özel Analizi"""
215
  global df
216
- if df is None: return "⚠️ Veri yok!", None, None
217
-
 
218
  filtered = df[df["title"].str.contains(coin_name, case=False, na=False)]
219
- if len(filtered) == 0: return f"⚠️ '{coin_name}' hakkında haber yok.", None, None
220
-
221
- # Grafikler
222
  sentiment_dist = filtered["sentiment_label"].value_counts()
223
-
224
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
225
-
226
- color_map = {'positive': '#2ecc71', 'negative': '#e74c3c', 'neutral': '#95a5a6'}
227
- colors = [color_map.get(x, '#333') for x in sentiment_dist.index]
228
-
229
  ax1.bar(sentiment_dist.index, sentiment_dist.values, color=colors)
230
- ax1.set_title(f'{coin_name} Sentiment (Llama-3)')
231
-
232
- ax2.pie(sentiment_dist.values, labels=sentiment_dist.index, autopct='%1.1f%%', colors=colors)
233
-
234
  plt.tight_layout()
235
-
236
- # Rapor
237
- avg_score = filtered["sentiment_score"].mean()
238
  report = f"""
239
- ### 🤖 Llama-3 Analiz Raporu: {coin_name.upper()}
240
- - **Toplam Haber:** {len(filtered)}
241
- - **Ortalama Güven Skoru:** {avg_score:.2f}
242
- - **Baskın Duygu:** {sentiment_dist.idxmax().upper() if not sentiment_dist.empty else 'N/A'}
243
- """
244
-
245
- return report, fig, filtered[["title", "sentiment_label", "sentiment_score"]]
246
 
247
  def create_overview_chart():
248
- """Genel Piyasa Durumu"""
249
  global df
250
- if df is None: return None
251
-
 
252
  fig, ax = plt.subplots(figsize=(8, 5))
253
  counts = df["sentiment_label"].value_counts()
254
- colors = [{'positive':'green','negative':'red','neutral':'gray'}.get(x,'gray') for x in counts.index]
255
-
256
  ax.bar(counts.index, counts.values, color=colors)
257
  ax.set_title("Genel Piyasa Duygu Durumu (Llama-3 Analizi)")
258
  return fig
@@ -262,38 +262,41 @@ def create_overview_chart():
262
  # ---------------------------------------------------------
263
 
264
  with gr.Blocks(theme=gr.themes.Soft(), title="Crypto News AI (Llama-3)") as app:
265
-
266
  gr.Markdown("# 🦙 Kripto Haber Analizi (Llama-3 Destekli)")
267
- gr.Markdown("Bu uygulama, haberlerin duygu analizini yapmak için **Meta-Llama-3-8B-Instruct** modelini kullanır.")
268
-
269
  with gr.Tab("⚙️ Ayarlar & Başlat"):
270
- hf_token_input = gr.Textbox(label="Hugging Face Token (Gerekli)", type="password", placeholder="hf_xxxxx")
 
 
 
 
271
  init_btn = gr.Button("🚀 Bağlantıyı Kur", variant="primary")
272
  init_out = gr.Textbox(label="Sistem Durumu")
273
-
274
  gr.Markdown("---")
275
  fetch_btn = gr.Button("📰 Haberleri Çek ve Llama-3'e Sor", variant="secondary")
276
- fetch_out = gr.Textbox(label="Log")
277
  fetch_table = gr.Dataframe(label="Analiz Sonuçları")
278
-
279
  init_btn.click(initialize_models, inputs=[hf_token_input], outputs=[init_out])
280
  fetch_btn.click(fetch_news, outputs=[fetch_out, fetch_table])
281
-
282
  with gr.Tab("📊 Coin Analizi"):
283
  coin_in = gr.Textbox(label="Coin İsmi (örn: Bitcoin)")
284
  coin_btn = gr.Button("Analiz Et")
285
  coin_report = gr.Markdown()
286
  coin_plot = gr.Plot()
287
  coin_data = gr.Dataframe()
288
-
289
  coin_btn.click(analyze_coin_sentiment, inputs=[coin_in], outputs=[coin_report, coin_plot, coin_data])
290
-
291
  with gr.Tab("🔎 Arama"):
292
  search_in = gr.Textbox(label="Ne aramıştınız?")
293
  search_btn = gr.Button("Bul")
294
  search_res_txt = gr.Textbox(label="Sonuç")
295
  search_res_df = gr.Dataframe()
296
-
297
  search_btn.click(search_similar_news, inputs=[search_in], outputs=[search_res_txt, search_res_df])
298
 
299
  with gr.Tab("📈 Genel Bakış"):
@@ -302,4 +305,4 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Crypto News AI (Llama-3)") as app:
302
  overview_btn.click(create_overview_chart, outputs=[overview_plot])
303
 
304
  if __name__ == "__main__":
305
- app.launch()
 
18
  # AYARLAR VE GLOBAL DEĞİŞKENLER
19
  # ---------------------------------------------------------
20
 
21
+ # HF token'ı Spaces Secrets / Environment üzerinden ver:
22
+ # HF_TOKEN = os.getenv("HF_TOKEN")
23
+ HF_TOKEN = os.getenv("HF_TOKEN")
24
 
25
+ # Llama-3 Modeli (Serverless Inference API)
26
  LLM_MODEL_ID = "meta-llama/Meta-Llama-3-8B-Instruct"
27
 
28
+ # Global değişkenler
29
  embedding_model = None
30
  llm_client = None
31
  df = None
 
36
  # YARDIMCI FONKSİYONLAR
37
  # ---------------------------------------------------------
38
 
39
+ def _extract_json_from_text(output_text: str):
40
+ """LLM çıktısından JSON objesini yakala."""
41
+ if not output_text:
42
+ return None
43
+ # Markdown code block temizliği
44
+ cleaned = output_text.replace("```json", "").replace("```", "").strip()
45
+ m = re.search(r"\{.*\}", cleaned, re.DOTALL)
46
+ if not m:
47
+ return None
48
+ try:
49
+ return json.loads(m.group())
50
+ except Exception:
51
+ return None
52
+
53
+ def get_llama_sentiment(text: str, client: InferenceClient):
54
  """
55
+ Llama-3 ile title sentiment.
56
+ return: (label, score)
 
 
 
 
 
 
 
 
57
  """
58
+ system_prompt = (
59
+ "You are a crypto sentiment analysis expert. Analyze the news title.\n"
60
+ "You MUST return a valid JSON object. Do NOT write any introduction or explanation.\n\n"
61
+ 'Format:\n{"label": "positive", "score": 0.9}\n\n'
62
+ 'Labels can be: "positive", "negative", "neutral".\n'
63
+ "Score is between 0.0 and 1.0."
64
+ )
65
+
66
  user_prompt = f"News Title: {text}"
67
+
68
  try:
69
  response = client.chat.completions.create(
70
  model=LLM_MODEL_ID,
71
  messages=[
72
  {"role": "system", "content": system_prompt},
73
+ {"role": "user", "content": user_prompt},
74
  ],
75
+ max_tokens=120,
76
+ temperature=0.1,
77
  )
78
+
79
+ output_text = (response.choices[0].message.content or "").strip()
80
+
81
+ # ✅ HATA: text[:20]... yok -> böyle yap
82
+ preview = (text[:20] + "...") if (text and len(text) > 20) else (text or "")
83
+ print(f"Model Yanıtı ({preview}): {output_text}")
84
+
85
+ data = _extract_json_from_text(output_text)
86
+ if not data:
 
 
 
 
 
 
 
 
 
87
  print(f"⚠️ JSON Bulunamadı. Gelen ham veri: {output_text}")
88
  return "neutral", 0.5
89
+
90
+ label = str(data.get("label", "neutral")).lower().strip()
91
+ score = float(data.get("score", 0.5))
92
+
93
+ # guardrails
94
+ if label not in {"positive", "negative", "neutral"}:
95
+ label = "neutral"
96
+ score = max(0.0, min(1.0, score))
97
+
98
+ return label, score
99
+
100
  except Exception as e:
 
101
  print(f"❌ API/Bağlantı Hatası: {str(e)}")
102
  return "neutral", 0.5
103
 
 
108
  def initialize_models(token_input):
109
  """Modelleri ve API İstemcisini Başlat"""
110
  global embedding_model, llm_client, HF_TOKEN
111
+
112
+ # UI'den token girildiyse onu kullan
113
+ if token_input and token_input.strip():
114
+ HF_TOKEN = token_input.strip()
115
+
116
  if not HF_TOKEN:
117
+ return "❌ Hata: Hugging Face Token yok. (Space Secrets'e HF_TOKEN ekle veya buradan gir)"
118
 
119
  try:
120
  if embedding_model is None:
121
+ embedding_model = SentenceTransformer("all-MiniLM-L6-v2")
122
+
 
123
  if llm_client is None:
 
124
  llm_client = InferenceClient(token=HF_TOKEN)
125
+
126
+ return f"✅ Hazır: Embedding + Llama-3 Client ({LLM_MODEL_ID})"
 
 
 
 
 
 
127
  except Exception as e:
128
+ return f"❌ Model/Client başlatma hatası: {str(e)}"
129
 
130
  def fetch_news():
131
  """RSS'den haber çek ve Llama-3 ile analiz et"""
132
+ global df, index, embeddings, llm_client, embedding_model
133
+
134
+ if llm_client is None or embedding_model is None:
135
+ return "⚠️ Önce 'Bağlantıyı Kur' ile modelleri başlat!", None
136
 
137
  RSS_URLS = [
138
  "https://cointelegraph.com/rss",
139
  "https://cryptonews.com/news/feed",
140
+ "https://www.coindesk.com/arc/outboundfeeds/rss/",
141
  ]
142
+
143
  all_entries = []
144
  status_messages = []
145
+
 
146
  for url in RSS_URLS:
147
  try:
148
  feed = feedparser.parse(url)
149
+ # Demo hız için 5 haber
150
+ for entry in feed.entries[:5]:
151
+ all_entries.append(
152
+ {
153
+ "title": entry.get("title", ""),
154
+ "link": entry.get("link", ""),
155
+ "published": entry.get("published", ""),
156
+ }
157
+ )
158
  status_messages.append(f"✓ {url.split('/')[2]} okundu.")
159
+ except Exception:
160
  status_messages.append(f"✗ {url} hatası.")
161
+
162
  df = pd.DataFrame(all_entries).drop_duplicates(subset="title").reset_index(drop=True)
163
+
164
  if len(df) == 0:
165
  return "Haber bulunamadı.", None
166
 
167
+ status_messages.append("\n🤖 Llama-3 ile analiz yapılıyor (bekleyin)...")
168
+
 
169
  labels = []
170
  scores = []
171
+ for title in df["title"].tolist():
 
 
172
  lbl, scr = get_llama_sentiment(title, llm_client)
173
  labels.append(lbl)
174
  scores.append(scr)
175
+
176
  df["sentiment_label"] = labels
177
  df["sentiment_score"] = scores
178
+
179
+ # FAISS index (arama)
180
+ corpus = df["title"].tolist()
181
+ embeddings = embedding_model.encode(corpus, show_progress_bar=False)
182
+ dim = embeddings.shape[1]
183
+ index = faiss.IndexFlatL2(dim)
184
+ index.add(embeddings.astype("float32"))
185
+
186
+ final_msg = "\n".join(status_messages) + f"\n\n✅ {len(df)} haber analiz edildi."
 
187
  return final_msg, df[["title", "sentiment_label", "sentiment_score"]].head(10)
188
 
189
  def search_similar_news(query, top_k=3):
190
+ """Semantik arama"""
191
  global df, index, embedding_model
192
+
193
+ if df is None or index is None or embedding_model is None:
194
  return "⚠️ Önce haberleri toplayın!", None
195
+
196
  try:
197
+ q_embedding = embedding_model.encode([query], show_progress_bar=False)
198
+ distances, indices = index.search(q_embedding.astype("float32"), k=min(top_k, len(df)))
199
+
200
  results = []
201
  for idx in indices[0]:
202
+ news = df.iloc[int(idx)]
203
+ results.append(
204
+ {
205
+ "Başlık": news["title"],
206
+ "Llama-3 Görüşü": news["sentiment_label"],
207
+ "Güven Skoru": float(news["sentiment_score"]),
208
+ "Link": news["link"],
209
+ }
210
+ )
211
+
212
  return f"🔎 '{query}' için sonuçlar:", pd.DataFrame(results)
213
  except Exception as e:
214
  return f"Hata: {str(e)}", None
215
 
216
  def analyze_coin_sentiment(coin_name):
217
+ """Coin özel analizi"""
218
  global df
219
+ if df is None:
220
+ return "⚠️ Veri yok!", None, None
221
+
222
  filtered = df[df["title"].str.contains(coin_name, case=False, na=False)]
223
+ if len(filtered) == 0:
224
+ return f"⚠️ '{coin_name}' hakkında haber yok.", None, None
225
+
226
  sentiment_dist = filtered["sentiment_label"].value_counts()
227
+
228
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
229
+ color_map = {"positive": "#2ecc71", "negative": "#e74c3c", "neutral": "#95a5a6"}
230
+ colors = [color_map.get(x, "#333") for x in sentiment_dist.index]
231
+
 
232
  ax1.bar(sentiment_dist.index, sentiment_dist.values, color=colors)
233
+ ax1.set_title(f"{coin_name} Sentiment (Llama-3)")
234
+
235
+ ax2.pie(sentiment_dist.values, labels=sentiment_dist.index, autopct="%1.1f%%", colors=colors)
 
236
  plt.tight_layout()
237
+
238
+ avg_score = float(filtered["sentiment_score"].mean())
 
239
  report = f"""
240
+ ### 🤖 Llama-3 Analiz Raporu: {coin_name.upper()}
241
+ - **Toplam Haber:** {len(filtered)}
242
+ - **Ortalama Güven Skoru:** {avg_score:.2f}
243
+ - **Baskın Duygu:** {sentiment_dist.idxmax().upper() if not sentiment_dist.empty else 'N/A'}
244
+ """
245
+ return report, fig, filtered[["title", "sentiment_label", "sentiment_score", "link"]]
 
246
 
247
  def create_overview_chart():
248
+ """Genel piyasa durumu"""
249
  global df
250
+ if df is None:
251
+ return None
252
+
253
  fig, ax = plt.subplots(figsize=(8, 5))
254
  counts = df["sentiment_label"].value_counts()
255
+ colors = [{"positive": "green", "negative": "red", "neutral": "gray"}.get(x, "gray") for x in counts.index]
 
256
  ax.bar(counts.index, counts.values, color=colors)
257
  ax.set_title("Genel Piyasa Duygu Durumu (Llama-3 Analizi)")
258
  return fig
 
262
  # ---------------------------------------------------------
263
 
264
  with gr.Blocks(theme=gr.themes.Soft(), title="Crypto News AI (Llama-3)") as app:
 
265
  gr.Markdown("# 🦙 Kripto Haber Analizi (Llama-3 Destekli)")
266
+ gr.Markdown("Bu uygulama, duygu analizi için **Meta-Llama-3-8B-Instruct** kullanır (HF Serverless API).")
267
+
268
  with gr.Tab("⚙️ Ayarlar & Başlat"):
269
+ hf_token_input = gr.Textbox(
270
+ label="Hugging Face Token (Gerekli)",
271
+ type="password",
272
+ placeholder="hf_xxxxx (ister Secrets->HF_TOKEN olarak da koyabilirsin)",
273
+ )
274
  init_btn = gr.Button("🚀 Bağlantıyı Kur", variant="primary")
275
  init_out = gr.Textbox(label="Sistem Durumu")
276
+
277
  gr.Markdown("---")
278
  fetch_btn = gr.Button("📰 Haberleri Çek ve Llama-3'e Sor", variant="secondary")
279
+ fetch_out = gr.Textbox(label="Log", lines=8)
280
  fetch_table = gr.Dataframe(label="Analiz Sonuçları")
281
+
282
  init_btn.click(initialize_models, inputs=[hf_token_input], outputs=[init_out])
283
  fetch_btn.click(fetch_news, outputs=[fetch_out, fetch_table])
284
+
285
  with gr.Tab("📊 Coin Analizi"):
286
  coin_in = gr.Textbox(label="Coin İsmi (örn: Bitcoin)")
287
  coin_btn = gr.Button("Analiz Et")
288
  coin_report = gr.Markdown()
289
  coin_plot = gr.Plot()
290
  coin_data = gr.Dataframe()
291
+
292
  coin_btn.click(analyze_coin_sentiment, inputs=[coin_in], outputs=[coin_report, coin_plot, coin_data])
293
+
294
  with gr.Tab("🔎 Arama"):
295
  search_in = gr.Textbox(label="Ne aramıştınız?")
296
  search_btn = gr.Button("Bul")
297
  search_res_txt = gr.Textbox(label="Sonuç")
298
  search_res_df = gr.Dataframe()
299
+
300
  search_btn.click(search_similar_news, inputs=[search_in], outputs=[search_res_txt, search_res_df])
301
 
302
  with gr.Tab("📈 Genel Bakış"):
 
305
  overview_btn.click(create_overview_chart, outputs=[overview_plot])
306
 
307
  if __name__ == "__main__":
308
+ app.launch()