import gradio as gr import feedparser import pandas as pd import numpy as np import faiss import matplotlib.pyplot as plt import re import os import json from collections import Counter from sentence_transformers import SentenceTransformer from huggingface_hub import InferenceClient import warnings warnings.filterwarnings('ignore') # --------------------------------------------------------- # AYARLAR VE GLOBAL DEĞİŞKENLER # --------------------------------------------------------- # HF token'ı Spaces Secrets / Environment üzerinden ver: # HF_TOKEN = os.getenv("HF_TOKEN") HF_TOKEN = os.getenv("HF_TOKEN") # Llama-3 Modeli (Serverless Inference API) LLM_MODEL_ID = "meta-llama/Meta-Llama-3-8B-Instruct" # Global değişkenler embedding_model = None llm_client = None df = None index = None embeddings = None # --------------------------------------------------------- # YARDIMCI FONKSİYONLAR # --------------------------------------------------------- def _extract_json_from_text(output_text: str): """LLM çıktısından JSON objesini yakala.""" if not output_text: return None # Markdown code block temizliği cleaned = output_text.replace("```json", "").replace("```", "").strip() m = re.search(r"\{.*\}", cleaned, re.DOTALL) if not m: return None try: return json.loads(m.group()) except Exception: return None def get_llama_sentiment(text: str, client: InferenceClient): """ Llama-3 ile title sentiment. return: (label, score) """ system_prompt = ( "You are a crypto sentiment analysis expert. Analyze the news title.\n" "You MUST return a valid JSON object. Do NOT write any introduction or explanation.\n\n" 'Format:\n{"label": "positive", "score": 0.9}\n\n' 'Labels can be: "positive", "negative", "neutral".\n' "Score is between 0.0 and 1.0." ) user_prompt = f"News Title: {text}" try: response = client.chat.completions.create( model=LLM_MODEL_ID, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ], max_tokens=120, temperature=0.1, ) output_text = (response.choices[0].message.content or "").strip() # ✅ HATA: text[:20]... yok -> böyle yap preview = (text[:20] + "...") if (text and len(text) > 20) else (text or "") print(f"Model Yanıtı ({preview}): {output_text}") data = _extract_json_from_text(output_text) if not data: print(f"⚠️ JSON Bulunamadı. Gelen ham veri: {output_text}") return "neutral", 0.5 label = str(data.get("label", "neutral")).lower().strip() score = float(data.get("score", 0.5)) # guardrails if label not in {"positive", "negative", "neutral"}: label = "neutral" score = max(0.0, min(1.0, score)) return label, score except Exception as e: print(f"❌ API/Bağlantı Hatası: {str(e)}") return "neutral", 0.5 # --------------------------------------------------------- # ANA FONKSİYONLAR # --------------------------------------------------------- def initialize_models(token_input): """Modelleri ve API İstemcisini Başlat""" global embedding_model, llm_client, HF_TOKEN # UI'den token girildiyse onu kullan if token_input and token_input.strip(): HF_TOKEN = token_input.strip() if not HF_TOKEN: return "❌ Hata: Hugging Face Token yok. (Space Secrets'e HF_TOKEN ekle veya buradan gir)" try: if embedding_model is None: embedding_model = SentenceTransformer("all-MiniLM-L6-v2") if llm_client is None: llm_client = InferenceClient(token=HF_TOKEN) return f"✅ Hazır: Embedding + Llama-3 Client ({LLM_MODEL_ID})" except Exception as e: return f"❌ Model/Client başlatma hatası: {str(e)}" def fetch_news(): """RSS'den haber çek ve Llama-3 ile analiz et""" global df, index, embeddings, llm_client, embedding_model if llm_client is None or embedding_model is None: return "⚠️ Önce 'Bağlantıyı Kur' ile modelleri başlat!", None RSS_URLS = [ "https://cointelegraph.com/rss", "https://cryptonews.com/news/feed", "https://www.coindesk.com/arc/outboundfeeds/rss/", ] all_entries = [] status_messages = [] for url in RSS_URLS: try: feed = feedparser.parse(url) # Demo hız için 5 haber for entry in feed.entries[:5]: all_entries.append( { "title": entry.get("title", ""), "link": entry.get("link", ""), "published": entry.get("published", ""), } ) status_messages.append(f"✓ {url.split('/')[2]} okundu.") except Exception: status_messages.append(f"✗ {url} hatası.") df = pd.DataFrame(all_entries).drop_duplicates(subset="title").reset_index(drop=True) if len(df) == 0: return "Haber bulunamadı.", None status_messages.append("\n🤖 Llama-3 ile analiz yapılıyor (bekleyin)...") labels = [] scores = [] for title in df["title"].tolist(): lbl, scr = get_llama_sentiment(title, llm_client) labels.append(lbl) scores.append(scr) df["sentiment_label"] = labels df["sentiment_score"] = scores # FAISS index (arama) corpus = df["title"].tolist() embeddings = embedding_model.encode(corpus, show_progress_bar=False) dim = embeddings.shape[1] index = faiss.IndexFlatL2(dim) index.add(embeddings.astype("float32")) final_msg = "\n".join(status_messages) + f"\n\n✅ {len(df)} haber analiz edildi." return final_msg, df[["title", "sentiment_label", "sentiment_score"]].head(10) def search_similar_news(query, top_k=3): """Semantik arama""" global df, index, embedding_model if df is None or index is None or embedding_model is None: return "⚠️ Önce haberleri toplayın!", None try: q_embedding = embedding_model.encode([query], show_progress_bar=False) distances, indices = index.search(q_embedding.astype("float32"), k=min(top_k, len(df))) results = [] for idx in indices[0]: news = df.iloc[int(idx)] results.append( { "Başlık": news["title"], "Llama-3 Görüşü": news["sentiment_label"], "Güven Skoru": float(news["sentiment_score"]), "Link": news["link"], } ) return f"🔎 '{query}' için sonuçlar:", pd.DataFrame(results) except Exception as e: return f"Hata: {str(e)}", None def analyze_coin_sentiment(coin_name): """Coin özel analizi""" global df if df is None: return "⚠️ Veri yok!", None, None filtered = df[df["title"].str.contains(coin_name, case=False, na=False)] if len(filtered) == 0: return f"⚠️ '{coin_name}' hakkında haber yok.", None, None sentiment_dist = filtered["sentiment_label"].value_counts() fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) color_map = {"positive": "#2ecc71", "negative": "#e74c3c", "neutral": "#95a5a6"} colors = [color_map.get(x, "#333") for x in sentiment_dist.index] ax1.bar(sentiment_dist.index, sentiment_dist.values, color=colors) ax1.set_title(f"{coin_name} Sentiment (Llama-3)") ax2.pie(sentiment_dist.values, labels=sentiment_dist.index, autopct="%1.1f%%", colors=colors) plt.tight_layout() avg_score = float(filtered["sentiment_score"].mean()) report = f""" ### 🤖 Llama-3 Analiz Raporu: {coin_name.upper()} - **Toplam Haber:** {len(filtered)} - **Ortalama Güven Skoru:** {avg_score:.2f} - **Baskın Duygu:** {sentiment_dist.idxmax().upper() if not sentiment_dist.empty else 'N/A'} """ return report, fig, filtered[["title", "sentiment_label", "sentiment_score", "link"]] def create_overview_chart(): """Genel piyasa durumu""" global df if df is None: return None fig, ax = plt.subplots(figsize=(8, 5)) counts = df["sentiment_label"].value_counts() colors = [{"positive": "green", "negative": "red", "neutral": "gray"}.get(x, "gray") for x in counts.index] ax.bar(counts.index, counts.values, color=colors) ax.set_title("Genel Piyasa Duygu Durumu (Llama-3 Analizi)") return fig # --------------------------------------------------------- # GRADIO ARAYÜZÜ # --------------------------------------------------------- with gr.Blocks(theme=gr.themes.Soft(), title="Crypto News AI (Llama-3)") as app: gr.Markdown("# 🦙 Kripto Haber Analizi (Llama-3 Destekli)") gr.Markdown("Bu uygulama, duygu analizi için **Meta-Llama-3-8B-Instruct** kullanır (HF Serverless API).") with gr.Tab("⚙️ Ayarlar & Başlat"): hf_token_input = gr.Textbox( label="Hugging Face Token (Gerekli)", type="password", placeholder="hf_xxxxx (ister Secrets->HF_TOKEN olarak da koyabilirsin)", ) init_btn = gr.Button("🚀 Bağlantıyı Kur", variant="primary") init_out = gr.Textbox(label="Sistem Durumu") gr.Markdown("---") fetch_btn = gr.Button("📰 Haberleri Çek ve Llama-3'e Sor", variant="secondary") fetch_out = gr.Textbox(label="Log", lines=8) fetch_table = gr.Dataframe(label="Analiz Sonuçları") init_btn.click(initialize_models, inputs=[hf_token_input], outputs=[init_out]) fetch_btn.click(fetch_news, outputs=[fetch_out, fetch_table]) with gr.Tab("📊 Coin Analizi"): coin_in = gr.Textbox(label="Coin İsmi (örn: Bitcoin)") coin_btn = gr.Button("Analiz Et") coin_report = gr.Markdown() coin_plot = gr.Plot() coin_data = gr.Dataframe() coin_btn.click(analyze_coin_sentiment, inputs=[coin_in], outputs=[coin_report, coin_plot, coin_data]) with gr.Tab("🔎 Arama"): search_in = gr.Textbox(label="Ne aramıştınız?") search_btn = gr.Button("Bul") search_res_txt = gr.Textbox(label="Sonuç") search_res_df = gr.Dataframe() search_btn.click(search_similar_news, inputs=[search_in], outputs=[search_res_txt, search_res_df]) with gr.Tab("📈 Genel Bakış"): overview_btn = gr.Button("Grafiği Güncelle") overview_plot = gr.Plot() overview_btn.click(create_overview_chart, outputs=[overview_plot]) if __name__ == "__main__": app.launch()