Spaces:
Running on Zero
Running on Zero
app.py eklendi
Browse files
app.py
ADDED
|
@@ -0,0 +1,943 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ders5 — Tool Calling / Function Calling Demosu
|
| 2 |
+
#
|
| 3 |
+
# Tek soru, üç panel:
|
| 4 |
+
# A. Güçlü model + araçlar açık -> doğru ve güncel cevap
|
| 5 |
+
# B. Güçlü model + araçlar kapalı -> güncel veriyi bilemez
|
| 6 |
+
# C. Zayıf model + araçlar açık -> araç çağırmayı dener ama bozuk üretir
|
| 7 |
+
#
|
| 8 |
+
# A/B karşılaştırması tool calling'in kattığını gösterir; A/C karşılaştırması
|
| 9 |
+
# model kapasitesinin (araç olsa bile) belirleyici olduğunu gösterir.
|
| 10 |
+
#
|
| 11 |
+
# Çalıştırma: python3 app.py (OPENROUTER_API_KEY .env veya ortam değişkeninden okunur)
|
| 12 |
+
|
| 13 |
+
import json
|
| 14 |
+
import os
|
| 15 |
+
import queue
|
| 16 |
+
import threading
|
| 17 |
+
import time
|
| 18 |
+
|
| 19 |
+
import gradio as gr
|
| 20 |
+
import httpx
|
| 21 |
+
from openai import OpenAI
|
| 22 |
+
|
| 23 |
+
# Bu uygulama gerçek GPU işi yapmıyor (sadece OpenRouter API + CPU üzerinde küçük
|
| 24 |
+
# hesaplar), ama hesabın ücretsiz Hugging Face Spaces hakkı yalnızca ZeroGPU
|
| 25 |
+
# donanımında geçerli. ZeroGPU çalışma zamanı, en az bir @spaces.GPU fonksiyonu
|
| 26 |
+
# görmeden Space'i başlatmayı reddediyor; bu yüzden zararsız bir dummy fonksiyon
|
| 27 |
+
# tanımlıyoruz. Yerelde `spaces` paketi kurulu değilse sessizce no-op'a düşer.
|
| 28 |
+
try:
|
| 29 |
+
import spaces
|
| 30 |
+
_gpu_dekoratoru = spaces.GPU
|
| 31 |
+
except ImportError:
|
| 32 |
+
def _gpu_dekoratoru(fn):
|
| 33 |
+
return fn
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@_gpu_dekoratoru
|
| 37 |
+
def _zero_gpu_kontrolu():
|
| 38 |
+
"""ZeroGPU çalışma zamanının aradığı işaret fonksiyon; gerçek iş yapmaz."""
|
| 39 |
+
return True
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
# --------------------------------------------------------------------------------------
|
| 43 |
+
# Ortam
|
| 44 |
+
# --------------------------------------------------------------------------------------
|
| 45 |
+
|
| 46 |
+
def _env_yukle(yol=".env"):
|
| 47 |
+
if not os.path.exists(yol):
|
| 48 |
+
return
|
| 49 |
+
with open(yol, "r", encoding="utf-8") as f:
|
| 50 |
+
for satir in f:
|
| 51 |
+
satir = satir.strip()
|
| 52 |
+
if not satir or satir.startswith("#") or "=" not in satir:
|
| 53 |
+
continue
|
| 54 |
+
k, _, v = satir.partition("=")
|
| 55 |
+
k, v = k.strip(), v.strip().strip("'\"")
|
| 56 |
+
if v and not os.environ.get(k):
|
| 57 |
+
os.environ[k] = v
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
_env_yukle()
|
| 61 |
+
|
| 62 |
+
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
| 63 |
+
OPENROUTER_BASE_URL = os.environ.get("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1").strip()
|
| 64 |
+
|
| 65 |
+
MODEL_GUCLU = os.environ.get("OPENROUTER_MODEL", "google/gemma-4-26b-a4b-it:nitro").strip()
|
| 66 |
+
# Küçük bir açık model: tool endpoint'i destekliyor (404 vermiyor) ama çok adımlı
|
| 67 |
+
# zincirlerde gerçek bir tool_calls üretmek yerine düz metin içine sahte
|
| 68 |
+
# <arac_adi>{...}</arac_adi> etiketleri ve UYDURMA sayılar yazıyor — ölçülmüş,
|
| 69 |
+
# tekrarlanabilir bir başarısızlık modu.
|
| 70 |
+
MODEL_ZAYIF = "meta-llama/llama-3.1-8b-instruct"
|
| 71 |
+
|
| 72 |
+
_istemci = None
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def istemci():
|
| 76 |
+
global _istemci
|
| 77 |
+
if _istemci is None:
|
| 78 |
+
if not OPENROUTER_API_KEY:
|
| 79 |
+
raise RuntimeError(
|
| 80 |
+
"OPENROUTER_API_KEY tanımlı değil. Yerelde .env dosyasına, "
|
| 81 |
+
"HF Spaces'te Settings > Secrets kısmına ekleyin."
|
| 82 |
+
)
|
| 83 |
+
_istemci = OpenAI(api_key=OPENROUTER_API_KEY, base_url=OPENROUTER_BASE_URL)
|
| 84 |
+
return _istemci
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
# --------------------------------------------------------------------------------------
|
| 88 |
+
# Araçlar (3 adet: 2 canlı API + 1 zincirleme hesap)
|
| 89 |
+
# --------------------------------------------------------------------------------------
|
| 90 |
+
|
| 91 |
+
HAVA_KODLARI = {
|
| 92 |
+
0: "açık", 1: "az bulutlu", 2: "parçalı bulutlu", 3: "kapalı",
|
| 93 |
+
45: "puslu", 51: "hafif çisenti", 61: "hafif yağmur", 63: "yağmur",
|
| 94 |
+
65: "şiddetli yağmur", 71: "kar", 80: "sağanak", 95: "gök gürültülü fırtına",
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def hava_durumu(sehir):
|
| 99 |
+
"""Bir şehrin anlık hava durumunu döner (Open-Meteo, anahtarsız)."""
|
| 100 |
+
try:
|
| 101 |
+
geo = httpx.get(
|
| 102 |
+
"https://geocoding-api.open-meteo.com/v1/search",
|
| 103 |
+
params={"name": sehir, "count": 1, "language": "tr"}, timeout=15,
|
| 104 |
+
).json()
|
| 105 |
+
sonuclar = geo.get("results") or []
|
| 106 |
+
if not sonuclar:
|
| 107 |
+
return {"hata": f"'{sehir}' için konum bulunamadı."}
|
| 108 |
+
konum = sonuclar[0]
|
| 109 |
+
|
| 110 |
+
veri = httpx.get(
|
| 111 |
+
"https://api.open-meteo.com/v1/forecast",
|
| 112 |
+
params={
|
| 113 |
+
"latitude": konum["latitude"], "longitude": konum["longitude"],
|
| 114 |
+
"current": "temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code",
|
| 115 |
+
"timezone": "auto",
|
| 116 |
+
}, timeout=15,
|
| 117 |
+
).json()
|
| 118 |
+
anlik = veri.get("current", {})
|
| 119 |
+
|
| 120 |
+
return {
|
| 121 |
+
"sehir": f"{konum['name']}, {konum.get('country', '')}".strip(", "),
|
| 122 |
+
"sicaklik_c": anlik.get("temperature_2m"),
|
| 123 |
+
"nem_yuzde": anlik.get("relative_humidity_2m"),
|
| 124 |
+
"ruzgar_kmh": anlik.get("wind_speed_10m"),
|
| 125 |
+
"durum": HAVA_KODLARI.get(anlik.get("weather_code"), "bilinmiyor"),
|
| 126 |
+
"kaynak": "Open-Meteo",
|
| 127 |
+
}
|
| 128 |
+
except Exception as e:
|
| 129 |
+
return {"hata": f"Hava durumu alınamadı: {e}"}
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def doviz_cevir(miktar, kaynak_birim, hedef_birim):
|
| 133 |
+
"""Güncel ECB kuruyla para birimi çevirir (Frankfurter, anahtarsız)."""
|
| 134 |
+
try:
|
| 135 |
+
kaynak_birim, hedef_birim = kaynak_birim.strip().upper(), hedef_birim.strip().upper()
|
| 136 |
+
miktar = float(miktar)
|
| 137 |
+
if kaynak_birim == hedef_birim:
|
| 138 |
+
return {"miktar": miktar, "sonuc": miktar, "kaynak_birim": kaynak_birim, "hedef_birim": hedef_birim}
|
| 139 |
+
|
| 140 |
+
veri = httpx.get(
|
| 141 |
+
"https://api.frankfurter.dev/v1/latest",
|
| 142 |
+
params={"base": kaynak_birim, "symbols": hedef_birim, "amount": miktar}, timeout=15,
|
| 143 |
+
).json()
|
| 144 |
+
oranlar = veri.get("rates", {})
|
| 145 |
+
if hedef_birim not in oranlar:
|
| 146 |
+
return {"hata": f"{kaynak_birim} -> {hedef_birim} kuru bulunamadı."}
|
| 147 |
+
|
| 148 |
+
return {
|
| 149 |
+
"miktar": miktar, "kaynak_birim": kaynak_birim, "hedef_birim": hedef_birim,
|
| 150 |
+
"sonuc": round(oranlar[hedef_birim], 4), "kur_tarihi": veri.get("date"),
|
| 151 |
+
"kaynak": "Frankfurter / ECB",
|
| 152 |
+
}
|
| 153 |
+
except Exception as e:
|
| 154 |
+
return {"hata": f"Döviz çevrilemedi: {e}"}
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def sicaklik_cevir(deger, kaynak_birim, hedef_birim):
|
| 158 |
+
"""C/F/K arası sıcaklık çevirir (yerel hesap, API'ye gitmez)."""
|
| 159 |
+
try:
|
| 160 |
+
k, h = kaynak_birim.strip().lower()[0], hedef_birim.strip().lower()[0]
|
| 161 |
+
deger = float(deger)
|
| 162 |
+
celsius = {"c": deger, "f": (deger - 32) / 1.8, "k": deger - 273.15}.get(k)
|
| 163 |
+
if celsius is None or h not in "cfk":
|
| 164 |
+
return {"hata": f"'{kaynak_birim}' -> '{hedef_birim}' desteklenmiyor. Sadece C/F/K."}
|
| 165 |
+
sonuc = {"c": celsius, "f": celsius * 1.8 + 32, "k": celsius + 273.15}[h]
|
| 166 |
+
return {
|
| 167 |
+
"girdi": deger, "kaynak_birim": kaynak_birim, "hedef_birim": hedef_birim,
|
| 168 |
+
"sonuc": round(sonuc, 2), "kaynak": "yerel hesap",
|
| 169 |
+
}
|
| 170 |
+
except Exception as e:
|
| 171 |
+
return {"hata": f"Sıcaklık çevrilemedi: {e}"}
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
KRIPTO_TAKMA_ADLARI = {
|
| 175 |
+
"btc": "bitcoin", "bitcoin": "bitcoin", "eth": "ethereum", "ethereum": "ethereum",
|
| 176 |
+
"sol": "solana", "solana": "solana", "doge": "dogecoin", "dogecoin": "dogecoin",
|
| 177 |
+
"xrp": "ripple", "ripple": "ripple",
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def kripto_fiyat(kripto, para_birimi="usd"):
|
| 182 |
+
"""Bir kripto paranın güncel fiyatını döner (CoinGecko, anahtarsız)."""
|
| 183 |
+
try:
|
| 184 |
+
kimlik = KRIPTO_TAKMA_ADLARI.get(kripto.strip().lower(), kripto.strip().lower())
|
| 185 |
+
para_birimi = para_birimi.strip().lower()
|
| 186 |
+
|
| 187 |
+
veri = httpx.get(
|
| 188 |
+
"https://api.coingecko.com/api/v3/simple/price",
|
| 189 |
+
params={"ids": kimlik, "vs_currencies": para_birimi, "include_24hr_change": "true"},
|
| 190 |
+
timeout=15,
|
| 191 |
+
).json()
|
| 192 |
+
if kimlik not in veri:
|
| 193 |
+
return {"hata": f"'{kripto}' CoinGecko'da bulunamadı."}
|
| 194 |
+
|
| 195 |
+
kayit = veri[kimlik]
|
| 196 |
+
degisim = kayit.get(f"{para_birimi}_24h_change")
|
| 197 |
+
return {
|
| 198 |
+
"kripto": kimlik, "para_birimi": para_birimi.upper(), "fiyat": kayit.get(para_birimi),
|
| 199 |
+
"degisim_24s_yuzde": round(degisim, 2) if degisim is not None else None,
|
| 200 |
+
"kaynak": "CoinGecko",
|
| 201 |
+
}
|
| 202 |
+
except Exception as e:
|
| 203 |
+
return {"hata": f"Kripto fiyatı alınamadı: {e}"}
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
WIKI_BASLIKLARI = {"User-Agent": "magibu-ders5-tool-calling/1.0 (egitim odevi; iletisim: salih12dede@gmail.com)"}
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def wikipedia_ara(sorgu, dil="tr"):
|
| 210 |
+
"""Wikipedia'da arayıp en alakalı maddenin özetini döner (anahtarsız)."""
|
| 211 |
+
try:
|
| 212 |
+
dil = dil.strip().lower() or "tr"
|
| 213 |
+
arama = httpx.get(
|
| 214 |
+
f"https://{dil}.wikipedia.org/w/api.php",
|
| 215 |
+
params={"action": "query", "list": "search", "srsearch": sorgu, "srlimit": 1, "format": "json"},
|
| 216 |
+
headers=WIKI_BASLIKLARI, timeout=15,
|
| 217 |
+
).json()
|
| 218 |
+
bulunanlar = arama.get("query", {}).get("search", [])
|
| 219 |
+
if not bulunanlar:
|
| 220 |
+
return {"hata": f"'{sorgu}' için Wikipedia'da sonuç bulunamadı."}
|
| 221 |
+
baslik = bulunanlar[0]["title"]
|
| 222 |
+
|
| 223 |
+
ozet = httpx.get(
|
| 224 |
+
f"https://{dil}.wikipedia.org/w/api.php",
|
| 225 |
+
params={"action": "query", "prop": "extracts", "exintro": 1, "explaintext": 1,
|
| 226 |
+
"titles": baslik, "format": "json", "redirects": 1},
|
| 227 |
+
headers=WIKI_BASLIKLARI, timeout=15,
|
| 228 |
+
).json()
|
| 229 |
+
sayfalar = ozet.get("query", {}).get("pages", {})
|
| 230 |
+
metin = next(iter(sayfalar.values()), {}).get("extract", "").strip()
|
| 231 |
+
if len(metin) > 800:
|
| 232 |
+
metin = metin[:800].rsplit(" ", 1)[0] + "..."
|
| 233 |
+
|
| 234 |
+
return {"baslik": baslik, "ozet": metin or "Özet bulunamadı.", "kaynak": f"Wikipedia ({dil})"}
|
| 235 |
+
except Exception as e:
|
| 236 |
+
return {"hata": f"Wikipedia sorgusu başarısız: {e}"}
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
def son_depremler(min_buyukluk=4.5, son_kac_gun=1):
|
| 240 |
+
"""Dünya genelinde son depremleri büyüklüğe göre listeler (USGS, anahtarsız)."""
|
| 241 |
+
try:
|
| 242 |
+
import datetime as dt
|
| 243 |
+
|
| 244 |
+
veri = httpx.get(
|
| 245 |
+
"https://earthquake.usgs.gov/fdsnws/event/1/query",
|
| 246 |
+
params={
|
| 247 |
+
"format": "geojson", "minmagnitude": float(min_buyukluk),
|
| 248 |
+
"starttime": f"now-{max(1, min(int(son_kac_gun), 30))}days",
|
| 249 |
+
"orderby": "magnitude", "limit": 5,
|
| 250 |
+
}, timeout=15,
|
| 251 |
+
).json()
|
| 252 |
+
|
| 253 |
+
depremler = []
|
| 254 |
+
for oge in veri.get("features", []):
|
| 255 |
+
ozellik = oge.get("properties", {})
|
| 256 |
+
zaman_ms = ozellik.get("time")
|
| 257 |
+
depremler.append({
|
| 258 |
+
"buyukluk": ozellik.get("mag"), "yer": ozellik.get("place"),
|
| 259 |
+
"zaman_utc": (
|
| 260 |
+
dt.datetime.fromtimestamp(zaman_ms / 1000, dt.timezone.utc).strftime("%Y-%m-%d %H:%M")
|
| 261 |
+
if zaman_ms else None
|
| 262 |
+
),
|
| 263 |
+
})
|
| 264 |
+
return {"kriter": f"son {son_kac_gun} gün, büyüklük >= {min_buyukluk}",
|
| 265 |
+
"toplam": len(depremler), "depremler": depremler, "kaynak": "USGS"}
|
| 266 |
+
except Exception as e:
|
| 267 |
+
return {"hata": f"Deprem verisi alınamadı: {e}"}
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
ARAC_FONKSIYONLARI = {
|
| 271 |
+
"hava_durumu": hava_durumu,
|
| 272 |
+
"doviz_cevir": doviz_cevir,
|
| 273 |
+
"sicaklik_cevir": sicaklik_cevir,
|
| 274 |
+
"kripto_fiyat": kripto_fiyat,
|
| 275 |
+
"wikipedia_ara": wikipedia_ara,
|
| 276 |
+
"son_depremler": son_depremler,
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
ARAC_UST_VERI = {
|
| 280 |
+
"hava_durumu": {"etiket": "WX", "saglayici": "Open-Meteo",
|
| 281 |
+
"aciklama": "Bir şehrin anlık sıcaklık, nem, rüzgar ve hava durumunu getirir."},
|
| 282 |
+
"doviz_cevir": {"etiket": "FX", "saglayici": "Frankfurter / ECB",
|
| 283 |
+
"aciklama": "Güncel kurla iki para birimi arasında çevrim yapar."},
|
| 284 |
+
"sicaklik_cevir":{"etiket": "TEMP", "saglayici": "yerel hesap",
|
| 285 |
+
"aciklama": "Sıcaklığı Celsius, Fahrenheit ve Kelvin arasında çevirir."},
|
| 286 |
+
"kripto_fiyat": {"etiket": "BTC", "saglayici": "CoinGecko",
|
| 287 |
+
"aciklama": "Bir kripto paranın güncel fiyatını ve 24 saatlik değişimini getirir."},
|
| 288 |
+
"wikipedia_ara": {"etiket": "WIKI", "saglayici": "Wikipedia",
|
| 289 |
+
"aciklama": "Wikipedia'da arayıp en alakalı maddenin özetini getirir."},
|
| 290 |
+
"son_depremler": {"etiket": "EQ", "saglayici": "USGS",
|
| 291 |
+
"aciklama": "Son depremleri büyüklüğe göre sıralı listeler."},
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
ARAC_SEMALARI = [
|
| 295 |
+
{
|
| 296 |
+
"type": "function",
|
| 297 |
+
"function": {
|
| 298 |
+
"name": "hava_durumu",
|
| 299 |
+
"description": "Bir şehrin anlık sıcaklık (Celsius), nem, rüzgar ve hava durumunu verir.",
|
| 300 |
+
"parameters": {
|
| 301 |
+
"type": "object",
|
| 302 |
+
"properties": {"sehir": {"type": "string", "description": "Şehir adı, örn: Ankara"}},
|
| 303 |
+
"required": ["sehir"],
|
| 304 |
+
},
|
| 305 |
+
},
|
| 306 |
+
},
|
| 307 |
+
{
|
| 308 |
+
"type": "function",
|
| 309 |
+
"function": {
|
| 310 |
+
"name": "doviz_cevir",
|
| 311 |
+
"description": "Güncel kurla bir para biriminden diğerine çevirir. Örn: 250 EUR kaç TL.",
|
| 312 |
+
"parameters": {
|
| 313 |
+
"type": "object",
|
| 314 |
+
"properties": {
|
| 315 |
+
"miktar": {"type": "number", "description": "Çevrilecek miktar"},
|
| 316 |
+
"kaynak_birim": {"type": "string", "description": "Kaynak para birimi kodu, örn: USD"},
|
| 317 |
+
"hedef_birim": {"type": "string", "description": "Hedef para birimi kodu, örn: TRY"},
|
| 318 |
+
},
|
| 319 |
+
"required": ["miktar", "kaynak_birim", "hedef_birim"],
|
| 320 |
+
},
|
| 321 |
+
},
|
| 322 |
+
},
|
| 323 |
+
{
|
| 324 |
+
"type": "function",
|
| 325 |
+
"function": {
|
| 326 |
+
"name": "sicaklik_cevir",
|
| 327 |
+
"description": (
|
| 328 |
+
"Sıcaklığı Celsius/Fahrenheit/Kelvin arasında çevirir. "
|
| 329 |
+
"hava_durumu her zaman Celsius döner; kullanıcı başka birim istiyorsa "
|
| 330 |
+
"dönen değeri bu araca ikinci adım olarak ver."
|
| 331 |
+
),
|
| 332 |
+
"parameters": {
|
| 333 |
+
"type": "object",
|
| 334 |
+
"properties": {
|
| 335 |
+
"deger": {"type": "number", "description": "Çevrilecek sıcaklık değeri"},
|
| 336 |
+
"kaynak_birim": {"type": "string", "description": "C, F veya K"},
|
| 337 |
+
"hedef_birim": {"type": "string", "description": "C, F veya K"},
|
| 338 |
+
},
|
| 339 |
+
"required": ["deger", "kaynak_birim", "hedef_birim"],
|
| 340 |
+
},
|
| 341 |
+
},
|
| 342 |
+
},
|
| 343 |
+
{
|
| 344 |
+
"type": "function",
|
| 345 |
+
"function": {
|
| 346 |
+
"name": "kripto_fiyat",
|
| 347 |
+
"description": "Bir kripto paranın güncel fiyatını ve 24 saatlik yüzde değişimini döner.",
|
| 348 |
+
"parameters": {
|
| 349 |
+
"type": "object",
|
| 350 |
+
"properties": {
|
| 351 |
+
"kripto": {"type": "string", "description": "Kripto adı/sembolü, örn: bitcoin, ETH"},
|
| 352 |
+
"para_birimi": {"type": "string", "description": "Fiyatın gösterileceği para birimi, örn: usd. Varsayılan usd."},
|
| 353 |
+
},
|
| 354 |
+
"required": ["kripto"],
|
| 355 |
+
},
|
| 356 |
+
},
|
| 357 |
+
},
|
| 358 |
+
{
|
| 359 |
+
"type": "function",
|
| 360 |
+
"function": {
|
| 361 |
+
"name": "wikipedia_ara",
|
| 362 |
+
"description": "Wikipedia'da arama yapıp en alakalı maddenin özetini döner. Kişi, yer veya kavram sorularında kullan.",
|
| 363 |
+
"parameters": {
|
| 364 |
+
"type": "object",
|
| 365 |
+
"properties": {
|
| 366 |
+
"sorgu": {"type": "string", "description": "Aranacak konu"},
|
| 367 |
+
"dil": {"type": "string", "description": "Wikipedia dil kodu: tr veya en. Varsayılan tr."},
|
| 368 |
+
},
|
| 369 |
+
"required": ["sorgu"],
|
| 370 |
+
},
|
| 371 |
+
},
|
| 372 |
+
},
|
| 373 |
+
{
|
| 374 |
+
"type": "function",
|
| 375 |
+
"function": {
|
| 376 |
+
"name": "son_depremler",
|
| 377 |
+
"description": "USGS kataloğundan son depremleri büyüklüğe göre sıralı listeler.",
|
| 378 |
+
"parameters": {
|
| 379 |
+
"type": "object",
|
| 380 |
+
"properties": {
|
| 381 |
+
"min_buyukluk": {"type": "number", "description": "Minimum deprem büyüklüğü. Varsayılan 4.5."},
|
| 382 |
+
"son_kac_gun": {"type": "integer", "description": "Kaç günlük geçmişe bakılacağı (1-30). Varsayılan 1."},
|
| 383 |
+
},
|
| 384 |
+
"required": [],
|
| 385 |
+
},
|
| 386 |
+
},
|
| 387 |
+
},
|
| 388 |
+
]
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
def araci_calistir(ad, argumanlar):
|
| 392 |
+
fonksiyon = ARAC_FONKSIYONLARI.get(ad)
|
| 393 |
+
if fonksiyon is None:
|
| 394 |
+
return {"hata": f"'{ad}' adında bir araç yok."}
|
| 395 |
+
try:
|
| 396 |
+
return fonksiyon(**(argumanlar or {}))
|
| 397 |
+
except TypeError as e:
|
| 398 |
+
return {"hata": f"'{ad}' hatalı argümanlarla çağrıldı: {e}"}
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
# --------------------------------------------------------------------------------------
|
| 402 |
+
# Ajan döngüsü — generator, her adımda olay üretir
|
| 403 |
+
# --------------------------------------------------------------------------------------
|
| 404 |
+
|
| 405 |
+
SISTEM_PROMPTU = (
|
| 406 |
+
"Türkçe konuşan bir asistansın. Hava durumu, döviz kuru, kripto fiyatı, deprem "
|
| 407 |
+
"ve ansiklopedik bilgi gibi GÜNCEL/OLGUSAL sorularda tahmin yürütme; elindeki "
|
| 408 |
+
"araçları çağır. Bir soru birim çevrimi de gerektiriyorsa (örn. Fahrenheit) önce "
|
| 409 |
+
"veriyi al, sonra çevirme aracını ayrı bir adımda çağır. Araç hata döndürürse "
|
| 410 |
+
"kullanıcıya dürüstçe söyle, veri uydurma. Araç gerektirmeyen sorularda araç "
|
| 411 |
+
"çağırmadan doğrudan yanıtla."
|
| 412 |
+
)
|
| 413 |
+
|
| 414 |
+
TOOLSUZ_PROMPTU = (
|
| 415 |
+
"Türkçe konuşan bir asistansın. Hiçbir dış araca/internete erişimin yok. Güncel "
|
| 416 |
+
"hava durumu, döviz kuru, kripto fiyatı, deprem gibi anlık bilgi istenirse bunu "
|
| 417 |
+
"bilemeyeceğini açıkça söyle; uydurma sayı verme."
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
MAKS_TUR = 4
|
| 421 |
+
MAKS_DENEME = 2
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
def _hata_kisalt(e):
|
| 425 |
+
"""Sağlayıcı hatalarının uzun ham JSON'ını UI'da okunabilir tek satıra indirger."""
|
| 426 |
+
metin = str(e)
|
| 427 |
+
for ayrac in ('"message":', "'message':"):
|
| 428 |
+
if ayrac in metin:
|
| 429 |
+
parca = metin.split(ayrac, 1)[1].strip().strip("'\"")
|
| 430 |
+
return parca.split('", "code"')[0].split("', 'code'")[0][:180]
|
| 431 |
+
return metin[:180]
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
def _arguman_coz(cagri):
|
| 435 |
+
try:
|
| 436 |
+
cozulen = json.loads(cagri.function.arguments or "{}")
|
| 437 |
+
return cozulen if isinstance(cozulen, dict) else {}
|
| 438 |
+
except json.JSONDecodeError:
|
| 439 |
+
return {"_cozulemeyen": cagri.function.arguments}
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
def ajan_calistir(soru, model, araclar_acik):
|
| 443 |
+
"""Tool-calling döngüsünü çalıştırır; olayları generator olarak üretir."""
|
| 444 |
+
mesajlar = [
|
| 445 |
+
{"role": "system", "content": SISTEM_PROMPTU if araclar_acik else TOOLSUZ_PROMPTU},
|
| 446 |
+
{"role": "user", "content": soru},
|
| 447 |
+
]
|
| 448 |
+
semalar = ARAC_SEMALARI if araclar_acik else []
|
| 449 |
+
|
| 450 |
+
for tur in range(1, MAKS_TUR + 1):
|
| 451 |
+
yield {"tip": "tur", "tur": tur}
|
| 452 |
+
|
| 453 |
+
mesaj, hata = None, None
|
| 454 |
+
for deneme in range(1, MAKS_DENEME + 1):
|
| 455 |
+
try:
|
| 456 |
+
istek = {"model": model, "messages": mesajlar, "temperature": 0.2 if deneme == 1 else 0.6}
|
| 457 |
+
if semalar:
|
| 458 |
+
istek["tools"] = semalar
|
| 459 |
+
istek["tool_choice"] = "auto"
|
| 460 |
+
yanit = istemci().chat.completions.create(**istek)
|
| 461 |
+
except Exception as e:
|
| 462 |
+
hata = _hata_kisalt(e)
|
| 463 |
+
yield {"tip": "deneme_hata", "deneme": deneme, "detay": hata}
|
| 464 |
+
continue
|
| 465 |
+
|
| 466 |
+
secenekler = getattr(yanit, "choices", None) or []
|
| 467 |
+
if not secenekler:
|
| 468 |
+
hata = str(getattr(yanit, "error", None) or "model boş yanıt döndü")
|
| 469 |
+
yield {"tip": "deneme_hata", "deneme": deneme, "detay": hata}
|
| 470 |
+
continue
|
| 471 |
+
|
| 472 |
+
aday = secenekler[0]
|
| 473 |
+
var_arac = bool(getattr(aday.message, "tool_calls", None))
|
| 474 |
+
var_icerik = bool((aday.message.content or "").strip())
|
| 475 |
+
if var_arac or var_icerik:
|
| 476 |
+
mesaj = aday.message
|
| 477 |
+
hata = None
|
| 478 |
+
break
|
| 479 |
+
|
| 480 |
+
hata = f"finish_reason={aday.finish_reason}, native={getattr(aday, 'native_finish_reason', None)}"
|
| 481 |
+
yield {"tip": "deneme_hata", "deneme": deneme, "detay": hata}
|
| 482 |
+
|
| 483 |
+
if mesaj is None:
|
| 484 |
+
yield {"tip": "hata", "mesaj": f"Model geçerli bir yanıt üretemedi ({hata})."}
|
| 485 |
+
return
|
| 486 |
+
|
| 487 |
+
arac_cagrilari = list(getattr(mesaj, "tool_calls", None) or [])
|
| 488 |
+
if not arac_cagrilari:
|
| 489 |
+
yield {"tip": "final", "metin": (mesaj.content or "").strip() or "(boş yanıt)", "tur": tur}
|
| 490 |
+
return
|
| 491 |
+
|
| 492 |
+
mesajlar.append({
|
| 493 |
+
"role": "assistant",
|
| 494 |
+
"content": mesaj.content or "",
|
| 495 |
+
"tool_calls": [
|
| 496 |
+
{"id": c.id, "type": "function",
|
| 497 |
+
"function": {"name": c.function.name, "arguments": c.function.arguments}}
|
| 498 |
+
for c in arac_cagrilari
|
| 499 |
+
],
|
| 500 |
+
})
|
| 501 |
+
|
| 502 |
+
for cagri in arac_cagrilari:
|
| 503 |
+
argumanlar = _arguman_coz(cagri)
|
| 504 |
+
yield {"tip": "arac_basladi", "id": cagri.id, "arac": cagri.function.name, "argumanlar": argumanlar}
|
| 505 |
+
|
| 506 |
+
baslangic = time.time()
|
| 507 |
+
sonuc = araci_calistir(cagri.function.name, argumanlar)
|
| 508 |
+
sure = time.time() - baslangic
|
| 509 |
+
|
| 510 |
+
yield {
|
| 511 |
+
"tip": "arac_bitti", "id": cagri.id, "arac": cagri.function.name,
|
| 512 |
+
"argumanlar": argumanlar, "sonuc": sonuc, "sure": sure,
|
| 513 |
+
"basarili": not (isinstance(sonuc, dict) and "hata" in sonuc),
|
| 514 |
+
}
|
| 515 |
+
mesajlar.append({
|
| 516 |
+
"role": "tool", "tool_call_id": cagri.id, "name": cagri.function.name,
|
| 517 |
+
"content": json.dumps(sonuc, ensure_ascii=False),
|
| 518 |
+
})
|
| 519 |
+
|
| 520 |
+
yield {"tip": "hata", "mesaj": f"{MAKS_TUR} tur sonunda nihai cevaba ulaşılamadı."}
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
# --------------------------------------------------------------------------------------
|
| 524 |
+
# Arayüz — 3 panel, aynı soruya paralel canlı akış
|
| 525 |
+
# --------------------------------------------------------------------------------------
|
| 526 |
+
|
| 527 |
+
PANEL_AYARLARI = [
|
| 528 |
+
{"anahtar": "A", "baslik": "Güçlü model", "mod": "araçlar açık", "model": MODEL_GUCLU, "araclar": True},
|
| 529 |
+
{"anahtar": "B", "baslik": "Güçlü model", "mod": "araçlar kapalı", "model": MODEL_GUCLU, "araclar": False},
|
| 530 |
+
{"anahtar": "C", "baslik": "Küçük model", "mod": "araçlar açık", "model": MODEL_ZAYIF, "araclar": True},
|
| 531 |
+
]
|
| 532 |
+
|
| 533 |
+
# --------------------------------------------------------------------------------------
|
| 534 |
+
# Tasarım tokenleri — sade, tek aileden 3 renk:
|
| 535 |
+
# 1) vurgu — marka/etkileşim rengi (bağlantı, aktif pil, gönder düğmesi, tooltip)
|
| 536 |
+
# 2) basarili — araç çağrısı başarılı
|
| 537 |
+
# 3) tehlike — araç/model hatası (yeniden deneme uyarıları da bu tona düşer)
|
| 538 |
+
# Geri kalan her şey Gradio'nun kendi nötr tema değişkenlerinden gelir; böylece
|
| 539 |
+
# açık/koyu tema otomatik uyum sağlar. Vurgu rengi teknik/ölçüm hissi için sıcak
|
| 540 |
+
# bir bakır tonu — jenerik indigo/mor değil.
|
| 541 |
+
# --------------------------------------------------------------------------------------
|
| 542 |
+
|
| 543 |
+
CSS = """
|
| 544 |
+
:root, .gradio-container {
|
| 545 |
+
--vurgu: #a35a1d; --vurgu-zayif: rgba(163,90,29,.10);
|
| 546 |
+
--basarili: #187a3c; --tehlike: #c22626;
|
| 547 |
+
--basarili-zemin: rgba(24,122,60,.10); --tehlike-zemin: rgba(194,38,38,.10);
|
| 548 |
+
--font-metin: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
| 549 |
+
--font-veri: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", Menlo, Consolas, monospace;
|
| 550 |
+
}
|
| 551 |
+
.dark, .dark .gradio-container {
|
| 552 |
+
--vurgu: #d99a52; --vurgu-zayif: rgba(217,154,82,.16);
|
| 553 |
+
--basarili: #4ade80; --tehlike: #f87171;
|
| 554 |
+
--basarili-zemin: rgba(74,222,128,.12); --tehlike-zemin: rgba(248,113,113,.12);
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
.gecit-baslik { display:flex; align-items:baseline; gap:10px; margin: 0; }
|
| 558 |
+
.gecit-baslik h1 { font-family: var(--font-metin); font-size: 1.22em; font-weight: 700; margin: 0; text-wrap: balance; }
|
| 559 |
+
.gecit-tez { font-family: var(--font-metin); font-size: 0.84em; opacity: .68; white-space: nowrap;
|
| 560 |
+
overflow: hidden; text-overflow: ellipsis; line-height: 1.4; margin: 3px 0 12px; }
|
| 561 |
+
|
| 562 |
+
.eyebrow { font-family: var(--font-metin); font-size: 0.68em; font-weight: 600; letter-spacing: .07em;
|
| 563 |
+
text-transform: uppercase; opacity: .5; margin: 0 0 6px; }
|
| 564 |
+
.arac-kayit { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-bottom: 12px; }
|
| 565 |
+
.arac-etiket { position: relative; display: flex; align-items: center; gap: 7px;
|
| 566 |
+
border: 1px solid var(--border-color-primary); border-radius: 7px; padding: 6px 10px;
|
| 567 |
+
font-family: var(--font-metin); cursor: default; overflow: hidden; }
|
| 568 |
+
.arac-etiket .rozet { font-family: var(--font-veri); font-size: 0.68em; font-weight: 700; color: var(--vurgu);
|
| 569 |
+
background: var(--vurgu-zayif); border-radius: 4px; padding: 2px 6px; letter-spacing: .02em;
|
| 570 |
+
flex-shrink: 0; }
|
| 571 |
+
.arac-etiket .ad { font-weight: 600; font-size: 0.82em; flex-shrink: 0; }
|
| 572 |
+
.arac-etiket .aciklama { font-size: 0.78em; opacity: .55; line-height: 1.3;
|
| 573 |
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| 574 |
+
.arac-etiket .ipucu {
|
| 575 |
+
position: absolute; left: 12px; bottom: calc(100% + 8px);
|
| 576 |
+
width: max-content; max-width: 240px; background: var(--body-text-color); color: var(--background-fill-primary);
|
| 577 |
+
padding: 7px 10px; border-radius: 7px; font-size: 0.82em; line-height: 1.4; text-align: left;
|
| 578 |
+
opacity: 0; pointer-events: none; transition: opacity .12s ease, transform .12s ease; z-index: 30;
|
| 579 |
+
transform: translateY(4px); box-shadow: 0 6px 18px rgba(0,0,0,.18);
|
| 580 |
+
}
|
| 581 |
+
.arac-etiket .ipucu b { display: block; margin-bottom: 2px; }
|
| 582 |
+
.arac-etiket:hover .ipucu { opacity: .96; transform: translateY(0); }
|
| 583 |
+
@media (max-width: 900px) { .arac-kayit { grid-template-columns: repeat(2, 1fr); } }
|
| 584 |
+
|
| 585 |
+
.panel-kutu { border: 1px solid var(--border-color-primary); border-radius: 10px;
|
| 586 |
+
padding: 12px 14px; height: min(52vh, 620px); min-height: 230px;
|
| 587 |
+
display: flex; flex-direction: column; font-family: var(--font-metin); overflow: hidden; }
|
| 588 |
+
.panel-ust { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px;
|
| 589 |
+
padding-bottom: 8px; margin-bottom: 2px; border-bottom: 1px solid var(--border-color-primary);
|
| 590 |
+
flex-shrink: 0; }
|
| 591 |
+
.panel-ad { font-weight: 700; font-size: 0.92em; }
|
| 592 |
+
.panel-model { font-family: var(--font-veri); font-size: 0.7em; opacity: .6; margin-top: 1px; }
|
| 593 |
+
.pil { font-size: 0.64em; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
|
| 594 |
+
border-radius: 999px; padding: 2px 8px; white-space: nowrap; }
|
| 595 |
+
.pil.acik { background: var(--vurgu-zayif); color: var(--vurgu); }
|
| 596 |
+
.pil.kapali { background: var(--background-fill-secondary); opacity: .6; }
|
| 597 |
+
|
| 598 |
+
.panel-govde { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-top: 6px; scroll-behavior: smooth; }
|
| 599 |
+
.panel-bos { opacity: .4; font-size: 0.8em; padding-top: 4px; }
|
| 600 |
+
.panel-alt-bilgi { font-size: 0.68em; opacity: .5; margin-top: 4px; padding-top: 4px; flex-shrink: 0;
|
| 601 |
+
border-top: 1px solid var(--border-color-primary); font-variant-numeric: tabular-nums; }
|
| 602 |
+
|
| 603 |
+
.tur-etiketi { font-size: 0.72em; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
|
| 604 |
+
opacity: .45; margin: 14px 0 6px; }
|
| 605 |
+
.tur-etiketi:first-child { margin-top: 0; }
|
| 606 |
+
|
| 607 |
+
.arac-kart { border: 1px solid var(--border-color-primary); border-radius: 8px; padding: 10px 11px; margin-bottom: 8px; }
|
| 608 |
+
.arac-kart.tamam { border-color: color-mix(in srgb, var(--basarili) 35%, var(--border-color-primary)); }
|
| 609 |
+
.arac-kart.hata { border-color: color-mix(in srgb, var(--tehlike) 35%, var(--border-color-primary)); }
|
| 610 |
+
.arac-kart-ust { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
| 611 |
+
.arac-kart-ad { display: flex; align-items: center; gap: 7px; font-size: 0.86em; }
|
| 612 |
+
.arac-kart-ad .rozet { font-family: var(--font-veri); font-size: 0.7em; font-weight: 700; color: var(--vurgu);
|
| 613 |
+
background: var(--vurgu-zayif); border-radius: 4px; padding: 1px 5px; }
|
| 614 |
+
.arac-kart-ad .saglayici { opacity: .55; font-size: 0.85em; }
|
| 615 |
+
.durum-pil { font-size: 0.66em; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
|
| 616 |
+
border-radius: 5px; padding: 2px 7px; font-variant-numeric: tabular-nums; }
|
| 617 |
+
.durum-pil.calisiyor { background: var(--background-fill-secondary); opacity: .7; }
|
| 618 |
+
.durum-pil.tamam { background: var(--basarili-zemin); color: var(--basarili); }
|
| 619 |
+
.durum-pil.hata { background: var(--tehlike-zemin); color: var(--tehlike); }
|
| 620 |
+
.veri-blok { margin-top: 8px; }
|
| 621 |
+
.veri-etiket { font-size: 0.66em; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
|
| 622 |
+
opacity: .45; margin-bottom: 3px; }
|
| 623 |
+
.veri-satirlar { font-family: var(--font-veri); font-size: 0.78em; background: var(--background-fill-secondary);
|
| 624 |
+
border-radius: 6px; padding: 7px 9px; line-height: 1.55; word-break: break-word;
|
| 625 |
+
font-variant-numeric: tabular-nums; }
|
| 626 |
+
.veri-satirlar .anahtar { opacity: .55; }
|
| 627 |
+
.veri-satirlar .hata-satir { color: var(--tehlike); }
|
| 628 |
+
.kaynak-notu { font-size: 0.72em; opacity: .5; margin-top: 4px; font-family: var(--font-metin); }
|
| 629 |
+
|
| 630 |
+
.deneme-satir { font-size: 0.78em; background: var(--background-fill-secondary);
|
| 631 |
+
border-radius: 6px; padding: 5px 9px; margin-bottom: 8px; font-family: var(--font-metin); }
|
| 632 |
+
.deneme-satir b { color: var(--tehlike); }
|
| 633 |
+
.deneme-satir .veri-satirlar { margin-top: 4px; background: transparent; padding: 0; opacity: .75; }
|
| 634 |
+
|
| 635 |
+
.sonuc-blok { padding-top: 10px; }
|
| 636 |
+
.sonuc-kutu { border-radius: 8px; padding: 11px 12px; font-size: 0.9em; line-height: 1.55;
|
| 637 |
+
white-space: pre-wrap; background: var(--vurgu-zayif); border: 1px solid var(--vurgu-zayif); }
|
| 638 |
+
.hata-kutu { border-radius: 8px; padding: 11px 12px; font-size: 0.86em; line-height: 1.5;
|
| 639 |
+
background: var(--tehlike-zemin); color: var(--tehlike); }
|
| 640 |
+
|
| 641 |
+
.ornek-baslik { font-family: var(--font-metin); font-size: 0.68em; font-weight: 600; letter-spacing: .07em;
|
| 642 |
+
text-transform: uppercase; opacity: .5; margin: 2px 0 6px; }
|
| 643 |
+
#ornek-sorular { display: grid !important; grid-template-columns: repeat(6, 1fr); gap: 6px; flex-wrap: unset; }
|
| 644 |
+
#ornek-sorular > * { min-width: 0; }
|
| 645 |
+
#ornek-sorular button {
|
| 646 |
+
font-size: 0.74em !important; white-space: normal !important; text-align: left !important;
|
| 647 |
+
justify-content: flex-start !important; line-height: 1.25 !important; min-height: 40px;
|
| 648 |
+
padding: 7px 9px !important; height: auto !important;
|
| 649 |
+
}
|
| 650 |
+
@media (max-width: 1100px) { #ornek-sorular { grid-template-columns: repeat(3, 1fr); } }
|
| 651 |
+
@media (max-width: 640px) { #ornek-sorular { grid-template-columns: repeat(2, 1fr); } }
|
| 652 |
+
|
| 653 |
+
/* Paneller sabit yükseklikte iç kaydırmalı olduğu için toplam sayfa yüksekliği
|
| 654 |
+
sorulara göre değişmiyor; composer bu nedenle normal akışta, sayfanın en altında
|
| 655 |
+
sade bir çubuk olarak durabiliyor (fixed/sticky konumlandırma Gradio'nun kendi
|
| 656 |
+
transform'lu sarmalayıcısıyla çakışıp bozuk konumlanmaya yol açıyordu). */
|
| 657 |
+
#composer-disi { border-top: 1px solid var(--border-color-primary); margin-top: 10px; padding-top: 8px; }
|
| 658 |
+
#composer { gap: 8px; }
|
| 659 |
+
|
| 660 |
+
/* Gradio'nun bloklar arası varsayılan dikey boşluğunu sıkılaştırır. */
|
| 661 |
+
.gradio-container .block { margin-bottom: 0 !important; }
|
| 662 |
+
.gradio-container > .main, .gradio-container .contain { gap: 6px !important; padding-top: 8px !important; }
|
| 663 |
+
|
| 664 |
+
/* İçerik viewport'tan kısa olduğunda kalan boşluk hep alta yığılıyordu (gradio-app
|
| 665 |
+
body içinde flex-grow:1 ile tüm yüksekliği dolduruyor). Bunun yerine tüm
|
| 666 |
+
kompozisyonu ekranda hem yatayda hem dikeyde ortalıyoruz; genişliği de cihaza göre
|
| 667 |
+
makul bir üst sınırla tutuyoruz. */
|
| 668 |
+
html, body { height: 100%; margin: 0; }
|
| 669 |
+
body { display: flex !important; align-items: center !important; justify-content: center !important; }
|
| 670 |
+
gradio-app { flex-grow: 0 !important; width: 100%; max-width: 1400px; }
|
| 671 |
+
.gradio-container { height: auto !important; min-height: 0 !important; width: 100% !important; margin: 0 auto !important; }
|
| 672 |
+
"""
|
| 673 |
+
|
| 674 |
+
AUTO_KAYDIR_JS = """
|
| 675 |
+
<script>
|
| 676 |
+
(function () {
|
| 677 |
+
// Panel içeriği her güncellemede yeniden çizildiği için DOM elemanı sıfırdan
|
| 678 |
+
// oluşuyor ve kaydırma konumu kayboluyor. Kullanıcı yukarı kaydırıp geçmişi
|
| 679 |
+
// okumak isterse "takip modu" kapanır ve otomatik kaydırma o panele dokunmaz;
|
| 680 |
+
// kullanıcı tekrar en alta inerse takip modu kendiliğinden açılır.
|
| 681 |
+
var takipEt = {};
|
| 682 |
+
|
| 683 |
+
function altaYakinMi(el) {
|
| 684 |
+
return el.scrollHeight - el.scrollTop - el.clientHeight < 24;
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
function elemanBul(hedef) {
|
| 688 |
+
return hedef && hedef.closest ? hedef.closest(".panel-govde") : null;
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
["wheel", "touchmove"].forEach(function (tur) {
|
| 692 |
+
document.addEventListener(tur, function (e) {
|
| 693 |
+
var el = elemanBul(e.target);
|
| 694 |
+
if (!el) return;
|
| 695 |
+
takipEt[el.id] = altaYakinMi(el);
|
| 696 |
+
}, { passive: true });
|
| 697 |
+
});
|
| 698 |
+
|
| 699 |
+
setInterval(function () {
|
| 700 |
+
document.querySelectorAll(".panel-govde").forEach(function (el) {
|
| 701 |
+
if (takipEt[el.id] === false) return;
|
| 702 |
+
el.scrollTop = el.scrollHeight;
|
| 703 |
+
});
|
| 704 |
+
}, 350);
|
| 705 |
+
})();
|
| 706 |
+
</script>
|
| 707 |
+
"""
|
| 708 |
+
|
| 709 |
+
|
| 710 |
+
def _kacir(metin):
|
| 711 |
+
return str(metin).replace("&", "&").replace("<", "<").replace(">", ">")
|
| 712 |
+
|
| 713 |
+
|
| 714 |
+
def _deger_yaz(v):
|
| 715 |
+
if isinstance(v, float):
|
| 716 |
+
return f"{v:,.2f}".rstrip("0").rstrip(".") if v != int(v) else f"{int(v):,}"
|
| 717 |
+
if isinstance(v, list):
|
| 718 |
+
if v and isinstance(v[0], dict):
|
| 719 |
+
return f"[{len(v)} kayıt]"
|
| 720 |
+
return ", ".join(str(x) for x in v) if v else "—"
|
| 721 |
+
return str(v)
|
| 722 |
+
|
| 723 |
+
|
| 724 |
+
def _veri_satirlari(d, haric=()):
|
| 725 |
+
satirlar = []
|
| 726 |
+
for k, v in d.items():
|
| 727 |
+
if k in haric or v is None:
|
| 728 |
+
continue
|
| 729 |
+
satirlar.append(f'<span class="anahtar">{_kacir(k)}:</span> {_kacir(_deger_yaz(v))}')
|
| 730 |
+
return "<br>".join(satirlar) if satirlar else "—"
|
| 731 |
+
|
| 732 |
+
|
| 733 |
+
def _arac_kart_render(blok):
|
| 734 |
+
ust = ARAC_UST_VERI.get(blok["arac"], {"etiket": "?", "saglayici": "—"})
|
| 735 |
+
durum = blok["durum"]
|
| 736 |
+
durum_metni = {"calisiyor": "çalışıyor", "tamam": "tamamlandı", "hata": "hata"}[durum]
|
| 737 |
+
sure_metni = f'{blok["sure"]:.2f}s' if blok.get("sure") is not None else ""
|
| 738 |
+
|
| 739 |
+
parcalar = [
|
| 740 |
+
f'<div class="arac-kart {durum}">'
|
| 741 |
+
f'<div class="arac-kart-ust"><span class="arac-kart-ad">'
|
| 742 |
+
f'<span class="rozet">{ust["etiket"]}</span><b>{_kacir(blok["arac"])}</b>'
|
| 743 |
+
f'<span class="saglayici">{_kacir(ust["saglayici"])}</span></span>'
|
| 744 |
+
f'<span class="durum-pil {durum}">{durum_metni}{" · " + sure_metni if sure_metni else ""}</span>'
|
| 745 |
+
f'</div>'
|
| 746 |
+
f'<div class="veri-blok"><div class="veri-etiket">girdi</div>'
|
| 747 |
+
f'<div class="veri-satirlar">{_veri_satirlari(blok["argumanlar"]) if blok["argumanlar"] else "—"}</div></div>'
|
| 748 |
+
]
|
| 749 |
+
|
| 750 |
+
if durum == "tamam":
|
| 751 |
+
sonuc = blok["sonuc"] or {}
|
| 752 |
+
if isinstance(sonuc, dict) and "hata" in sonuc:
|
| 753 |
+
parcalar.append(
|
| 754 |
+
f'<div class="veri-blok"><div class="veri-etiket">çıktı</div>'
|
| 755 |
+
f'<div class="veri-satirlar hata-satir">{_kacir(sonuc["hata"])}</div></div>'
|
| 756 |
+
)
|
| 757 |
+
else:
|
| 758 |
+
kaynak = sonuc.get("kaynak") if isinstance(sonuc, dict) else None
|
| 759 |
+
parcalar.append(
|
| 760 |
+
f'<div class="veri-blok"><div class="veri-etiket">çıktı</div>'
|
| 761 |
+
f'<div class="veri-satirlar">{_veri_satirlari(sonuc, haric=("kaynak",))}</div>'
|
| 762 |
+
+ (f'<div class="kaynak-notu">kaynak: {_kacir(kaynak)}</div>' if kaynak else "")
|
| 763 |
+
+ '</div>'
|
| 764 |
+
)
|
| 765 |
+
elif durum == "hata":
|
| 766 |
+
parcalar.append(
|
| 767 |
+
f'<div class="veri-blok"><div class="veri-etiket">çıktı</div>'
|
| 768 |
+
f'<div class="veri-satirlar hata-satir">{_kacir((blok.get("sonuc") or {}).get("hata", "bilinmeyen hata"))}</div></div>'
|
| 769 |
+
)
|
| 770 |
+
|
| 771 |
+
parcalar.append("</div>")
|
| 772 |
+
return "".join(parcalar)
|
| 773 |
+
|
| 774 |
+
|
| 775 |
+
def _durum_baslangic():
|
| 776 |
+
return {"bloklar": [], "id_indeks": {}, "final": None, "hata": None, "sure": None}
|
| 777 |
+
|
| 778 |
+
|
| 779 |
+
def _durum_guncelle(durum, olay):
|
| 780 |
+
tip = olay["tip"]
|
| 781 |
+
if tip == "tur":
|
| 782 |
+
durum["bloklar"].append({"tip": "tur", "tur": olay["tur"]})
|
| 783 |
+
elif tip == "arac_basladi":
|
| 784 |
+
durum["id_indeks"][olay["id"]] = len(durum["bloklar"])
|
| 785 |
+
durum["bloklar"].append({
|
| 786 |
+
"tip": "arac", "arac": olay["arac"], "argumanlar": olay["argumanlar"],
|
| 787 |
+
"durum": "calisiyor", "sonuc": None, "sure": None,
|
| 788 |
+
})
|
| 789 |
+
elif tip == "arac_bitti":
|
| 790 |
+
i = durum["id_indeks"].get(olay["id"])
|
| 791 |
+
if i is not None:
|
| 792 |
+
blok = durum["bloklar"][i]
|
| 793 |
+
blok.update(durum="tamam" if olay["basarili"] else "hata", sonuc=olay["sonuc"], sure=olay["sure"])
|
| 794 |
+
elif tip == "deneme_hata":
|
| 795 |
+
durum["bloklar"].append({"tip": "deneme_hata", "deneme": olay["deneme"], "detay": olay["detay"]})
|
| 796 |
+
elif tip == "final":
|
| 797 |
+
durum["final"] = olay["metin"]
|
| 798 |
+
elif tip == "hata":
|
| 799 |
+
durum["hata"] = olay["mesaj"]
|
| 800 |
+
|
| 801 |
+
|
| 802 |
+
def _panel_render(ayar, durum):
|
| 803 |
+
pil_sinif = "acik" if ayar["araclar"] else "kapali"
|
| 804 |
+
parcalar = [
|
| 805 |
+
'<div class="panel-kutu"><div class="panel-ust">'
|
| 806 |
+
f'<div><div class="panel-ad">{_kacir(ayar["baslik"])}</div>'
|
| 807 |
+
f'<div class="panel-model">{_kacir(ayar["model"])}</div></div>'
|
| 808 |
+
f'<span class="pil {pil_sinif}">{_kacir(ayar["mod"])}</span>'
|
| 809 |
+
'</div>'
|
| 810 |
+
f'<div class="panel-govde" id="panel-govde-{ayar["anahtar"]}">'
|
| 811 |
+
]
|
| 812 |
+
|
| 813 |
+
if not durum["bloklar"] and not durum["final"] and not durum["hata"]:
|
| 814 |
+
parcalar.append('<div class="panel-bos">Bir soru sorduğunuzda burada akacak.</div>')
|
| 815 |
+
|
| 816 |
+
for blok in durum["bloklar"]:
|
| 817 |
+
if blok["tip"] == "tur":
|
| 818 |
+
parcalar.append(f'<div class="tur-etiketi">Tur {blok["tur"]}</div>')
|
| 819 |
+
elif blok["tip"] == "arac":
|
| 820 |
+
parcalar.append(_arac_kart_render(blok))
|
| 821 |
+
elif blok["tip"] == "deneme_hata":
|
| 822 |
+
parcalar.append(
|
| 823 |
+
f'<div class="deneme-satir"><b>Deneme {blok["deneme"]}</b> geçersiz yanıt üretti'
|
| 824 |
+
f'<div class="veri-satirlar">{_kacir(blok["detay"])}</div></div>'
|
| 825 |
+
)
|
| 826 |
+
|
| 827 |
+
if durum["final"] or durum["hata"]:
|
| 828 |
+
parcalar.append('<div class="sonuc-blok">')
|
| 829 |
+
if durum["final"]:
|
| 830 |
+
parcalar.append(f'<div class="sonuc-kutu">{_kacir(durum["final"])}</div>')
|
| 831 |
+
if durum["hata"]:
|
| 832 |
+
parcalar.append(f'<div class="hata-kutu">{_kacir(durum["hata"])}</div>')
|
| 833 |
+
parcalar.append("</div>")
|
| 834 |
+
|
| 835 |
+
parcalar.append("</div>") # .panel-govde kapanışı
|
| 836 |
+
|
| 837 |
+
if durum["sure"] is not None:
|
| 838 |
+
arac_sayisi = sum(1 for b in durum["bloklar"] if b["tip"] == "arac")
|
| 839 |
+
parcalar.append(f'<div class="panel-alt-bilgi">{durum["sure"]:.1f}s · {arac_sayisi} araç çağrısı</div>')
|
| 840 |
+
|
| 841 |
+
parcalar.append("</div>") # .panel-kutu kapanışı
|
| 842 |
+
return "".join(parcalar)
|
| 843 |
+
|
| 844 |
+
|
| 845 |
+
def _panel_calistir(anahtar, soru, model, araclar_acik, kuyruk):
|
| 846 |
+
baslangic = time.time()
|
| 847 |
+
try:
|
| 848 |
+
for olay in ajan_calistir(soru, model, araclar_acik):
|
| 849 |
+
kuyruk.put((anahtar, olay, time.time() - baslangic))
|
| 850 |
+
except Exception as e:
|
| 851 |
+
kuyruk.put((anahtar, {"tip": "hata", "mesaj": f"Beklenmeyen hata: {e}"}, time.time() - baslangic))
|
| 852 |
+
kuyruk.put((anahtar, {"tip": "bitti"}, time.time() - baslangic))
|
| 853 |
+
|
| 854 |
+
|
| 855 |
+
def uc_paneli_calistir(soru):
|
| 856 |
+
"""Panelleri render eder; son çıktı olarak soru kutusunu da yönetir.
|
| 857 |
+
|
| 858 |
+
İlk yield'de kutu boşaltılır (kullanıcı gönderir göndermez temizlenir); sonraki
|
| 859 |
+
yield'lerde kutuya dokunulmaz, böylece kullanıcı akış sürerken sıradaki soruyu
|
| 860 |
+
yazmaya başlarsa üzerine yazılmaz.
|
| 861 |
+
"""
|
| 862 |
+
soru = (soru or "").strip()
|
| 863 |
+
if not soru:
|
| 864 |
+
bos = [_panel_render(a, _durum_baslangic()) for a in PANEL_AYARLARI]
|
| 865 |
+
yield (*bos, gr.update())
|
| 866 |
+
return
|
| 867 |
+
|
| 868 |
+
kuyruk = queue.Queue()
|
| 869 |
+
durumlar = {a["anahtar"]: _durum_baslangic() for a in PANEL_AYARLARI}
|
| 870 |
+
|
| 871 |
+
threadler = [
|
| 872 |
+
threading.Thread(target=_panel_calistir, args=(a["anahtar"], soru, a["model"], a["araclar"], kuyruk), daemon=True)
|
| 873 |
+
for a in PANEL_AYARLARI
|
| 874 |
+
]
|
| 875 |
+
for th in threadler:
|
| 876 |
+
th.start()
|
| 877 |
+
|
| 878 |
+
bitenler = set()
|
| 879 |
+
ilk_yield = True
|
| 880 |
+
while len(bitenler) < len(PANEL_AYARLARI):
|
| 881 |
+
anahtar, olay, gecen_sure = kuyruk.get()
|
| 882 |
+
durum = durumlar[anahtar]
|
| 883 |
+
|
| 884 |
+
if olay["tip"] == "bitti":
|
| 885 |
+
bitenler.add(anahtar)
|
| 886 |
+
durum["sure"] = gecen_sure
|
| 887 |
+
else:
|
| 888 |
+
_durum_guncelle(durum, olay)
|
| 889 |
+
|
| 890 |
+
panel_htmlleri = tuple(_panel_render(a, durumlar[a["anahtar"]]) for a in PANEL_AYARLARI)
|
| 891 |
+
yield (*panel_htmlleri, ("" if ilk_yield else gr.update()))
|
| 892 |
+
ilk_yield = False
|
| 893 |
+
|
| 894 |
+
|
| 895 |
+
ORNEK_SORULAR = [
|
| 896 |
+
"Ankara mı daha sıcak Londra mı, bu değerler Fahrenheit olarak kaç eder?",
|
| 897 |
+
"İstanbul'da hava kaç derece, Kelvin olarak da söyler misin?",
|
| 898 |
+
"250 Euro kaç TL eder?",
|
| 899 |
+
"Bitcoin şu an kaç dolar, 24 saatte ne kadar değişti?",
|
| 900 |
+
"Mimar Sinan kimdir kısaca anlatır mısın?",
|
| 901 |
+
"Son 3 günde 5'ten büyük deprem oldu mu?",
|
| 902 |
+
]
|
| 903 |
+
|
| 904 |
+
with gr.Blocks(title="Tool Calling Karşılaştırması") as demo:
|
| 905 |
+
gr.HTML(
|
| 906 |
+
'<div class="gecit-baslik"><h1>Tool Calling Karşılaştırması</h1></div>'
|
| 907 |
+
'<p class="gecit-tez">Aynı soru üçüne birden gider: güçlü model + araçlar, aynı model araçsız, '
|
| 908 |
+
'küçük model + araçlar — fark, tool calling ve model kapasitesinin gerçek katkısını gösterir.</p>'
|
| 909 |
+
'<div class="eyebrow">Kullanılabilir araçlar</div>'
|
| 910 |
+
'<div class="arac-kayit">' + "".join(
|
| 911 |
+
f'<div class="arac-etiket"><span class="rozet">{u["etiket"]}</span>'
|
| 912 |
+
f'<span class="ad">{ad}</span>'
|
| 913 |
+
f'<span class="aciklama">{u["aciklama"]}</span>'
|
| 914 |
+
f'<span class="ipucu"><b>{u["saglayici"]}</b>{u["aciklama"]}</span></div>'
|
| 915 |
+
for ad, u in ARAC_UST_VERI.items()
|
| 916 |
+
) + '</div>'
|
| 917 |
+
)
|
| 918 |
+
|
| 919 |
+
with gr.Row():
|
| 920 |
+
paneller = [gr.HTML(_panel_render(a, _durum_baslangic())) for a in PANEL_AYARLARI]
|
| 921 |
+
|
| 922 |
+
gr.HTML('<div class="ornek-baslik">Örnek sorular</div>')
|
| 923 |
+
with gr.Row(elem_id="ornek-sorular"):
|
| 924 |
+
ornek_butonlar = [gr.Button(s, size="sm") for s in ORNEK_SORULAR]
|
| 925 |
+
|
| 926 |
+
with gr.Group(elem_id="composer-disi"):
|
| 927 |
+
with gr.Row(elem_id="composer"):
|
| 928 |
+
soru_kutusu = gr.Textbox(
|
| 929 |
+
placeholder="Bir soru sorun — örn. Ankara mı daha sıcak Londra mı...",
|
| 930 |
+
scale=6, show_label=False, container=False,
|
| 931 |
+
)
|
| 932 |
+
gonder = gr.Button("Gönder", variant="primary", scale=1)
|
| 933 |
+
|
| 934 |
+
for buton, s in zip(ornek_butonlar, ORNEK_SORULAR):
|
| 935 |
+
buton.click(lambda s=s: s, outputs=soru_kutusu)
|
| 936 |
+
|
| 937 |
+
cikislar = paneller + [soru_kutusu]
|
| 938 |
+
gonder.click(uc_paneli_calistir, inputs=soru_kutusu, outputs=cikislar)
|
| 939 |
+
soru_kutusu.submit(uc_paneli_calistir, inputs=soru_kutusu, outputs=cikislar)
|
| 940 |
+
|
| 941 |
+
|
| 942 |
+
if __name__ == "__main__":
|
| 943 |
+
demo.queue().launch(css=CSS, head=AUTO_KAYDIR_JS)
|