import io
import os
import subprocess
import time
from datetime import datetime
import pandas as pd
import requests
import streamlit as st
try:
import psutil
except ImportError:
psutil = None
st.set_page_config(
page_title="Sentinel — İçerik Moderasyon",
layout="wide",
initial_sidebar_state="expanded",
)
st.markdown(
"""
""",
unsafe_allow_html=True,
)
API_URL = os.getenv("SENTINEL_API_URL", "https://moztrk-sentinel-api.hf.space/analyze")
VERDICT_COLORS = {
"CRITICAL": "#e03030",
"HIGH": "#e07020",
"MEDIUM": "#d4a017",
"LOW": "#3a7bd4",
"NONE": "#2ea84a",
}
VERDICT_ICONS = {"CRITICAL": "🚨", "HIGH": "🤬", "MEDIUM": "◆", "LOW": "▲", "NONE": "✓"}
if "last_latency_ms" not in st.session_state:
st.session_state["last_latency_ms"] = None
if "last_metrics" not in st.session_state:
st.session_state["last_metrics"] = None
def get_gpu_info():
try:
result = subprocess.check_output(
[
"nvidia-smi",
"--query-gpu=name,utilization.gpu,temperature.gpu,memory.used,memory.total",
"--format=csv,noheader,nounits",
],
encoding="utf-8",
stderr=subprocess.STDOUT,
)
line = result.strip().splitlines()[0]
name, util, temp, mem_used, mem_total = [p.strip() for p in line.split(",", maxsplit=4)]
return {
"name": name,
"load": int(float(util)),
"temp": int(float(temp)),
"vram_used": int(float(mem_used)),
"vram_total": int(float(mem_total)),
}
except Exception:
return None
def capture_process_metrics():
gpu_data = get_gpu_info()
cpu_val = 0.0
ram_pct = 0.0
if psutil is not None:
cpu_val = psutil.cpu_percent(interval=0.1)
ram_pct = psutil.virtual_memory().percent
return {
"cpu": round(cpu_val, 1),
"ram_pct": round(ram_pct, 1),
"vram_used": str(gpu_data["vram_used"]) if gpu_data else "0",
"gpu_load": str(gpu_data["load"]) if gpu_data else "0",
"timestamp": time.strftime("%H:%M:%S"),
}
def resolve_api_endpoints(api_url_raw: str):
base = (api_url_raw or "").strip().rstrip("/")
if base.endswith("/analyze"):
root = base[: -len("/analyze")]
elif base.endswith("/analyze-batch"):
root = base[: -len("/analyze-batch")]
else:
root = base
analyze_url = f"{root}/analyze"
batch_url = f"{root}/analyze-batch"
return analyze_url, batch_url
def verdict_css_class(decision):
d = decision.upper()
if "TEMIZ" in d or "CLEAR" in d:
return "TEMIZ"
if "HAKARET" in d or "INSULT" in d:
return "SALDIRGAN"
if "NEFRET" in d or "IDENTITY" in d:
return "NEFRET"
if "KÜFÜR" in d or "KUFUR" in d or "PROFANITY" in d:
return "KUFUR"
if "SALDIRGAN" in d or "TOXIC" in d:
return "SALDIRGAN"
if "İNCELEME" in d or "INCELEME" in d or "REVIEW" in d:
return "INCELEME"
if "SPAM" in d or "GİBBERİSH" in d:
return "SPAM"
return "TEMIZ"
def risk_color(val):
if val > 0.7:
return "#e03030"
if val > 0.4:
return "#d4a017"
if val > 0.15:
return "#f0a020"
return "#2ea84a"
def score_bar(label, value, color="#1a6cf7"):
pct = min(max(value * 100, 0), 100)
return f"""
"""
def badge_html(risk):
cls = {
"CRITICAL": "badge-CRITICAL",
"HIGH": "badge-HIGH",
"MEDIUM": "badge-MEDIUM",
"LOW": "badge-LOW",
"NONE": "badge-NONE",
}.get(risk.upper(), "badge-NONE")
return f'{risk}'
def inline_bar_html(value, color):
w = min(max(value * 60, 0), 60)
return f'%{value * 100:.0f}'
def generate_docx_report(res_df, total_time, platform_dil):
try:
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Cm, Pt, RGBColor
except ImportError:
return None
doc = Document()
for section in doc.sections:
section.top_margin = Cm(1.8)
section.bottom_margin = Cm(1.8)
section.left_margin = Cm(2.0)
section.right_margin = Cm(2.0)
def set_cell_bg(cell, hex_color):
tc = cell._tc
tc_pr = tc.get_or_add_tcPr()
shd = OxmlElement("w:shd")
shd.set(qn("w:val"), "clear")
shd.set(qn("w:color"), "auto")
shd.set(qn("w:fill"), hex_color)
tc_pr.append(shd)
def add_run(para, text, bold=False, size=10, color="000000", italic=False):
run = para.add_run(text)
run.bold = bold
run.italic = italic
run.font.size = Pt(size)
run.font.color.rgb = RGBColor(int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16))
return run
title_para = doc.add_paragraph()
title_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_run(title_para, "SENTINEL AI - Moderasyon Analiz Raporu", bold=True, size=18, color="1F4E79")
sub_para = doc.add_paragraph()
sub_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
ts = datetime.now().strftime("%d.%m.%Y %H:%M")
add_run(
sub_para,
f"Platform: {platform_dil.upper()} | Olusturulma: {ts} | {len(res_df)} kayit | {total_time:.1f}s",
size=9,
color="888888",
)
doc.add_paragraph()
counts = res_df["Karar"].value_counts()
sum_para = doc.add_paragraph()
add_run(sum_para, "OZET", bold=True, size=11, color="1F4E79")
sum_tbl = doc.add_table(rows=1, cols=len(counts) + 1)
sum_tbl.style = "Table Grid"
hdr = sum_tbl.rows[0].cells
set_cell_bg(hdr[0], "1F4E79")
p = hdr[0].paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_run(p, "Metrik", bold=True, size=9, color="FFFFFF")
karar_colors = {
"TEMIZ": "2EA84A",
"KÜFÜR": "D4A017",
"KUFUR": "D4A017",
"PROFANITY": "D4A017",
"SALDIRGAN": "D4A017",
"TOXIC": "D4A017",
"NEFRET": "E07020",
"INCELEME": "3A7BD4",
"SPAM": "8030D4",
"GIBBERISH": "8030D4",
}
for i, (karar, cnt) in enumerate(counts.items()):
cell = hdr[i + 1]
set_cell_bg(cell, "0D1220")
p2 = cell.paragraphs[0]
p2.alignment = WD_ALIGN_PARAGRAPH.CENTER
c = next((v for k, v in karar_colors.items() if k in karar.upper()), "888888")
add_run(p2, f"{cnt}", bold=True, size=14, color=c)
p3 = cell.add_paragraph()
p3.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_run(p3, karar[:16], size=7, color="888888")
doc.add_paragraph()
detail_para = doc.add_paragraph()
add_run(detail_para, "DETAYLI ANALIZ SONUCLARI", bold=True, size=11, color="1F4E79")
cols = ["#", "Metin", "Normalize", "Karar", "Risk", "Saldirganlik", "Nefret", "Tehdit", "Hits"]
tbl = doc.add_table(rows=1, cols=len(cols))
tbl.style = "Table Grid"
for i, col_name in enumerate(cols):
cell = tbl.rows[0].cells[i]
set_cell_bg(cell, "1F4E79")
p = cell.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_run(p, col_name, bold=True, size=8, color="FFFFFF")
for idx, row in res_df.iterrows():
tr = tbl.add_row()
cells = tr.cells
risk_str = str(row.get("Risk", "")).upper()
row_colors = {
"CRITICAL": "1F0C0C",
"HIGH": "1A0E03",
"MEDIUM": "141002",
"LOW": "07091A",
"NONE": "050F07",
}
row_fill = row_colors.get(risk_str, "0D1220")
set_cell_bg(cells[0], row_fill)
p = cells[0].paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_run(p, str(idx + 1), size=8, color="4A6080")
set_cell_bg(cells[1], row_fill)
p = cells[1].paragraphs[0]
add_run(p, str(row.get("Metin", ""))[:120], size=8, color="C9D1E0")
set_cell_bg(cells[2], row_fill)
p = cells[2].paragraphs[0]
add_run(p, str(row.get("Normalize", ""))[:60], size=7, color="6A8CB0", italic=True)
set_cell_bg(cells[3], row_fill)
p = cells[3].paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
karar = str(row.get("Karar", ""))
c = next((v for k, v in karar_colors.items() if k in karar.upper()), "888888")
add_run(p, karar[:20], bold=True, size=8, color=c)
set_cell_bg(cells[4], row_fill)
p = cells[4].paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
risk_colors = {
"CRITICAL": "E03030",
"HIGH": "E07020",
"MEDIUM": "D4A017",
"LOW": "3A7BD4",
"NONE": "2EA84A",
}
rc = risk_colors.get(risk_str, "888888")
add_run(p, risk_str, bold=True, size=8, color=rc)
for col_i, field in [(5, "Saldırganlık"), (6, "Nefret"), (7, "Tehdit")]:
set_cell_bg(cells[col_i], row_fill)
p = cells[col_i].paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
score = float(row.get(field, 0.0))
add_run(p, f"%{score * 100:.1f}", size=8, color=risk_color(score).replace("#", ""))
set_cell_bg(cells[8], row_fill)
p = cells[8].paragraphs[0]
hits = str(row.get("Hits", "")).strip("[]'\"")
add_run(p, hits if hits else "-", size=7, color="E05050" if hits else "2A3D55")
widths_cm = [0.7, 4.5, 3.0, 2.8, 1.5, 1.4, 1.4, 1.4, 2.0]
for i, w in enumerate(widths_cm):
for row in tbl.rows:
row.cells[i].width = Cm(w)
doc.add_paragraph()
inceleme = res_df[res_df["Karar"].str.contains("İNCELEME|INCELEME|REVIEW", na=False)]
if len(inceleme):
q_para = doc.add_paragraph()
add_run(q_para, f"INCELEME KUYRUGU - {len(inceleme)} Icerik", bold=True, size=11, color="3A7BD4")
for _, row in inceleme.iterrows():
q_tbl = doc.add_table(rows=1, cols=1)
q_tbl.style = "Table Grid"
cell = q_tbl.rows[0].cells[0]
set_cell_bg(cell, "060A13")
p = cell.paragraphs[0]
add_run(p, str(row.get("Metin", ""))[:200], size=9, color="C9D1E0")
p2 = cell.add_paragraph()
add_run(
p2,
f"Risk: {row.get('Risk', '')} | Saldirganlik: %{float(row.get('Saldırganlık', 0)) * 100:.0f} | {row.get('Gerekçe', '')}",
size=8,
color="4A6080",
italic=True,
)
doc.add_paragraph()
footer_p = doc.add_paragraph()
footer_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_run(footer_p, "Sentinel AI - Dahili Kullanim - " + datetime.now().strftime("%Y"), size=8, color="2A3D55")
buf = io.BytesIO()
doc.save(buf)
buf.seek(0)
return buf
st.markdown(
"""
""",
unsafe_allow_html=True,
)
with st.sidebar:
st.markdown(
"""""",
unsafe_allow_html=True,
)
st.markdown(
"""Platform Dili
""",
unsafe_allow_html=True,
)
platform_dil = st.radio(
"Platform dili",
["tr", "en"],
format_func=lambda x: "Türkçe · TR Pipeline" if x == "tr" else "English · EN Pipeline",
label_visibility="collapsed",
)
st.markdown("
", unsafe_allow_html=True)
st.markdown(
"""API Endpoint
""",
unsafe_allow_html=True,
)
api_url = st.text_input("API", value=API_URL, label_visibility="collapsed")
st.markdown("
", unsafe_allow_html=True)
st.markdown(
"""
TR PIPELINE
──────────────
▸ is_spam() evrensel filtre
▸ Küfür listesi lookup
▸ BERTurk offensive 42K
▸ Detoxify multilingual
EN PIPELINE
──────────────
▸ is_spam() evrensel filtre
▸ Gibberish Detector
▸ Detoxify original 6-label
""",
unsafe_allow_html=True,
)
st.markdown("---")
st.markdown("### 🖥️ Sistem Monitörü")
if psutil is None:
st.warning("psutil yüklü değil. Kurulum: pip install psutil")
else:
cpu_load = psutil.cpu_percent(interval=0.2)
ram = psutil.virtual_memory()
ram_used_gb = ram.used / (1024**3)
col1, col2 = st.columns(2)
col1.metric("CPU Yükü", f"%{cpu_load:.0f}")
col2.metric("RAM", f"{ram_used_gb:.1f} GB", f"%{ram.percent:.0f}", delta_color="inverse")
gpu = get_gpu_info()
if gpu:
st.markdown(f"**GPU:** {gpu['name']}")
col3, col4 = st.columns(2)
col3.metric("GPU Yükü", f"%{gpu['load']}")
col4.metric("GPU Isı", f"{gpu['temp']}°C")
vram_pct = 0.0
if gpu["vram_total"] > 0:
vram_pct = min(max(gpu["vram_used"] / gpu["vram_total"], 0.0), 1.0)
st.write(f"VRAM: {gpu['vram_used']}MB / {gpu['vram_total']}MB")
st.progress(vram_pct)
else:
st.warning("GPU bilgisi alınamadı (nvidia-smi erişimi yok).")
st.markdown("---")
live_latency = st.session_state.get("last_latency_ms")
if live_latency is None:
st.info("🚀 **Model Latency:** N/A\n\n🛡️ **Sentinel v2.9 Active**")
else:
st.info(f"🚀 **Model Latency:** ~{live_latency:.0f}ms/req\n\n🛡️ **Sentinel v2.9 Active**")
st.markdown("---")
if st.session_state.get("last_metrics"):
m = st.session_state["last_metrics"]
st.markdown("### ⚡ Son İşlem Performansı")
st.caption(f"Saat: {m['timestamp']} (İstek anındaki veriler)")
col5, col6 = st.columns(2)
col5.metric("İşlem CPU", f"%{m['cpu']}")
col6.metric("İşlem RAM", f"%{m['ram_pct']}")
col7, col8 = st.columns(2)
col7.metric("GPU Yükü", f"%{m['gpu_load']}")
col8.metric("VRAM", f"{m['vram_used']} MB")
st.success("Analiz işlemi için performans verisi kaydedildi.")
else:
st.info("Performans verisi için analiz başlatın.")
tab1, tab2 = st.tabs([" Tek Metin Analizi ", " Toplu Analiz "])
with tab1:
st.markdown("
", unsafe_allow_html=True)
user_input = st.text_area(
"Analiz metni",
height=120,
placeholder="Analiz edilecek metni buraya yazın...",
label_visibility="collapsed",
)
col_btn, col_info = st.columns([2, 5])
with col_btn:
analyze_btn = st.button("Analiz Et", use_container_width=True)
with col_info:
st.markdown(
"""Spam → Dil → Küfür → Model → Karar
""",
unsafe_allow_html=True,
)
if analyze_btn:
if not user_input.strip():
st.warning("Analiz için metin gerekli.")
else:
with st.spinner(""):
try:
t0 = time.time()
analyze_url, _ = resolve_api_endpoints(api_url)
resp = requests.post(analyze_url, json={"text": user_input, "platform_dil": platform_dil}, timeout=30)
st.session_state["last_metrics"] = capture_process_metrics()
elapsed = (time.time() - t0) * 1000
except requests.RequestException as e:
st.error(f"API bağlantı hatası: {e}")
st.stop()
if resp.status_code != 200:
st.error(f"API {resp.status_code} döndü.")
st.stop()
r = resp.json()
decision = r.get("decision", "—")
reason = r.get("reason", "—")
risk = r.get("risk_level", "None")
risk_u = str(risk).upper()
lang = r.get("language", platform_dil).upper()
cleaned = r.get("cleaned_text", "")
details = r.get("details", {})
latency = r.get("latency_ms", round(elapsed, 1))
st.session_state["last_latency_ms"] = float(latency)
backend_perf = r.get("performance")
if isinstance(backend_perf, dict):
st.session_state["last_metrics"] = {
"cpu": backend_perf.get("cpu", 0),
"ram_pct": backend_perf.get("ram_pct", 0),
"vram_used": str(backend_perf.get("vram_used", 0)),
"gpu_load": str(backend_perf.get("gpu_load", 0)),
"timestamp": backend_perf.get("timestamp", time.strftime("%H:%M:%S")),
}
vcls = verdict_css_class(decision)
vcolor = VERDICT_COLORS.get(risk_u, "#2ea84a")
vicon = VERDICT_ICONS.get(risk_u, "✓")
st.markdown(
f"""
{vicon} {decision}
[{lang}]
{reason}
""",
unsafe_allow_html=True,
)
lat_class = "low" if latency < 200 else ("med" if latency < 500 else "high")
risk_class = {
"CRITICAL": "high",
"HIGH": "high",
"MEDIUM": "med",
"LOW": "med",
"NONE": "low",
}.get(risk_u, "low")
st.markdown(
f"""
Normalize Edilen Metin
{cleaned}
""",
unsafe_allow_html=True,
)
hits = details.get("hits", []) or []
insult_hits = details.get("insult_hits", []) or []
if hits or insult_hits:
tags = "".join(f'⚡ {h}' for h in hits)
tags += "".join(
f'⚠ {h}'
for h in insult_hits
)
st.markdown(
f"""
Kara Liste Eşleşmeleri
{tags}
""",
unsafe_allow_html=True,
)
col_scores, col_models = st.columns([1, 1.2])
with col_scores:
st.markdown(
"""Sinyal Analizi
""",
unsafe_allow_html=True,
)
bars = ""
if lang == "TR":
off = details.get("off_score", 0.0)
ia = details.get("detox", {}).get("identity_attack", 0.0)
thr = details.get("threat", 0.0)
bars += score_bar("Saldırganlık", off, risk_color(off))
bars += score_bar("Nefret (identity_attack)", ia, risk_color(ia))
bars += score_bar("Tehdit", thr, risk_color(thr))
else:
dtx = details.get("detox", {})
for key, lbl in [
("toxicity", "Toxicity"),
("threat", "Threat"),
("insult", "Insult"),
("identity_attack", "Identity Attack"),
("severe_toxicity", "Severe Toxicity"),
("obscene", "Obscene"),
]:
v = dtx.get(key, 0.0)
bars += score_bar(lbl, v, risk_color(v))
st.markdown(bars, unsafe_allow_html=True)
with col_models:
st.markdown(
"""Model Kaynak Analizi (Source)
""",
unsafe_allow_html=True,
)
rows_html = ""
if lang == "TR":
m_list = [
("BERTurk Offensive", "N/A", details.get("off_score", 0.0)),
("Detoxify (TR)", "Analyzed", details.get("detox", {}).get("toxicity", 0.0)),
]
else:
m_list = [
("Detoxify (Original)", "Analyzed", details.get("detox", {}).get("toxicity", 0.0)),
(
"Gibberish Detector",
details.get("gibberish_label", "N/A"),
details.get("gibberish_score", 0.0) or 0.0,
),
]
for m_name, m_dec, m_score in m_list:
try:
m_score = float(m_score)
except (TypeError, ValueError):
m_score = 0.0
c = risk_color(m_score)
rows_html += f"""
{m_name}
{m_dec} (%{m_score * 100:.1f})
"""
st.markdown(rows_html, unsafe_allow_html=True)
with tab2:
st.markdown("
", unsafe_allow_html=True)
st.markdown(
"""Veri Seti Yükle
""",
unsafe_allow_html=True,
)
uploaded = st.file_uploader("Dosya", type=["csv", "xlsx"], label_visibility="collapsed")
if uploaded:
df = pd.read_csv(uploaded) if uploaded.name.endswith(".csv") else pd.read_excel(uploaded)
if len(df) == 0:
st.warning("Dosya boş.")
st.stop()
st.markdown(
f"""{len(df)} satır yüklendi
""",
unsafe_allow_html=True,
)
col_name = st.selectbox("Analiz sütunu:", df.columns)
if st.button("Toplu Analizi Başlat", use_container_width=False):
progress = st.progress(0)
status_text = st.empty()
results = []
t0 = time.time()
texts_payload = [str(text) for text in df[col_name]]
_, batch_url = resolve_api_endpoints(api_url)
progress.progress(0.2)
status_text.markdown(
f"""Batch isteği gönderiliyor... ({len(texts_payload)} satır)""",
unsafe_allow_html=True,
)
try:
resp = requests.post(
batch_url,
json={"texts": texts_payload, "platform_dil": platform_dil, "batch_size": 16},
timeout=300,
)
payload = resp.json() if resp.status_code == 200 else {}
except requests.RequestException as exc:
st.error(f"Batch API bağlantı hatası: {exc}")
st.stop()
if resp.status_code != 200:
st.error(f"Batch API {resp.status_code} döndü.")
st.stop()
items = payload.get("results", []) if isinstance(payload, dict) else []
if len(items) != len(texts_payload):
st.warning(f"Batch sonuç sayısı beklenenden farklı: {len(items)} / {len(texts_payload)}")
for text, r in zip(texts_payload, items):
details = r.get("details", {})
hits_all = list(details.get("hits", []) or []) + list(details.get("insult_hits", []) or [])
results.append(
{
"Metin": text,
"Normalize": r.get("cleaned_text", ""),
"Dil": r.get("language", "—").upper(),
"Karar": r.get("decision", "—"),
"Risk": r.get("risk_level", "—"),
"Gerekçe": r.get("reason", "—"),
"Saldırganlık": round(float(details.get("off_score", 0.0)), 4),
"Nefret": round(float(details.get("detox", {}).get("identity_attack", 0.0)), 4),
"Tehdit": round(float(details.get("threat", details.get("detox", {}).get("threat", 0.0))), 4),
"Hits": ", ".join(hits_all) if hits_all else "",
}
)
progress.progress(1.0)
status_text.markdown(
f"""{len(results)} / {len(df)} işlendi""",
unsafe_allow_html=True,
)
elapsed = time.time() - t0
res_df = pd.DataFrame(results)
if len(df) > 0:
st.session_state["last_latency_ms"] = (elapsed * 1000.0) / len(df)
status_text.empty()
progress.empty()
st.markdown(
f"""
{len(df)} satır {elapsed:.1f}s içinde analiz edildi
""",
unsafe_allow_html=True,
)
counts = res_df["Karar"].value_counts()
karar_colors_ui = {
"TEMIZ": "#2ea84a",
"CLEAR": "#2ea84a",
"KÜFÜR": "#d4a017",
"KUFUR": "#d4a017",
"PROFANITY": "#d4a017",
"SALDIRGAN": "#d4a017",
"TOXIC": "#d4a017",
"NEFRET": "#e07020",
"IDENTITY": "#e07020",
"İNCELEME": "#3a7bd4",
"INCELEME": "#3a7bd4",
"REVIEW": "#3a7bd4",
"SPAM": "#8030d4",
"GİBBERİSH": "#8030d4",
}
cols_summary = st.columns(min(len(counts), 6))
for i, (karar, cnt) in enumerate(counts.items()):
if i < 6:
vc = next((v for k, v in karar_colors_ui.items() if k in karar.upper()), "#888888")
with cols_summary[i]:
st.markdown(
f"""""",
unsafe_allow_html=True,
)
st.markdown("
", unsafe_allow_html=True)
st.markdown(
"""Detaylı Analiz Tablosu
""",
unsafe_allow_html=True,
)
table_rows = ""
for idx, row in res_df.iterrows():
risk_str = str(row.get("Risk", "")).upper()
row_bg = {
"CRITICAL": "#1f0c0c",
"HIGH": "#1a0e03",
"MEDIUM": "#141002",
"LOW": "#07091a",
"NONE": "#050f07",
}.get(risk_str, "#0d1220")
karar_str = str(row.get("Karar", ""))
kc = next((v for k, v in karar_colors_ui.items() if k in karar_str.upper()), "#888888")
sal = float(row.get("Saldırganlık", 0.0))
nef = float(row.get("Nefret", 0.0))
thr = float(row.get("Tehdit", 0.0))
hits_str = str(row.get("Hits", "")).strip()
hits_html = ""
if hits_str:
for h in hits_str.split(","):
h = h.strip()
if h:
hits_html += f'{h}'
else:
hits_html = '—'
metin_full = str(row.get("Metin", ""))
metin_short = metin_full[:60] + "..." if len(metin_full) > 60 else metin_full
normalize = str(row.get("Normalize", ""))[:50]
table_rows += f"""
| {idx + 1} |
{metin_short} |
{normalize} |
{karar_str[:22]} |
{badge_html(risk_str)} |
{inline_bar_html(sal, risk_color(sal))} |
{inline_bar_html(nef, risk_color(nef))} |
{inline_bar_html(thr, risk_color(thr))} |
{hits_html} |
{str(row.get("Gerekçe", ""))[:60]} |
"""
st.markdown(
f"""
| # | Metin | Normalize | Karar |
Risk | Saldırganlık | Nefret | Tehdit |
Hits | Gerekçe |
{table_rows}
""",
unsafe_allow_html=True,
)
st.markdown("
", unsafe_allow_html=True)
col_chart, col_stats = st.columns([1, 1])
with col_chart:
st.markdown(
"""Dağılım
""",
unsafe_allow_html=True,
)
st.bar_chart(counts)
with col_stats:
st.markdown(
"""İstatistikler
""",
unsafe_allow_html=True,
)
total = len(res_df)
zararli = total - len(res_df[res_df["Karar"].str.contains("TEMİZ|CLEAR", na=False)])
st.markdown(
f"""
Toplam kayıt {total}
Zararlı içerik {zararli} (%{zararli / total * 100:.1f})
Ortalama süre {elapsed / total * 1000:.0f}ms / satır
Hits bulundu {len(res_df[res_df['Hits'].str.len() > 0])} kayıt
İnceleme kuyruğu {len(res_df[res_df['Karar'].str.contains('İNCELEME|INCELEME', na=False)])} içerik
""",
unsafe_allow_html=True,
)
st.markdown("
", unsafe_allow_html=True)
inceleme = res_df[res_df["Karar"].str.contains("İNCELEME|INCELEME|REVIEW", na=False)]
if len(inceleme):
st.markdown(
f"""İnceleme Kuyruğu — {len(inceleme)} İçerik
""",
unsafe_allow_html=True,
)
for i, (_, row) in enumerate(inceleme.iterrows()):
sal = float(row.get("Saldırganlık", 0.0))
st.markdown(
f"""
{i + 1:02d}
{str(row.get('Metin', ''))}
Risk: {row.get('Risk', '')} |
Saldırganlık: %{sal * 100:.0f} |
{row.get('Gerekçe', '')}
""",
unsafe_allow_html=True,
)
st.markdown("
", unsafe_allow_html=True)
st.markdown(
"""Raporu İndir
""",
unsafe_allow_html=True,
)
col_dl1, col_dl2, _ = st.columns([1, 1, 4])
with col_dl1:
csv_bytes = res_df.to_csv(index=False).encode("utf-8")
st.download_button(
"⬇ CSV",
data=csv_bytes,
file_name=f"sentinel_raporu_{datetime.now().strftime('%Y%m%d_%H%M')}.csv",
mime="text/csv",
use_container_width=True,
)
with col_dl2:
docx_buf = generate_docx_report(res_df, elapsed, platform_dil)
if docx_buf:
st.download_button(
"⬇ DOCX",
data=docx_buf,
file_name=f"sentinel_raporu_{datetime.now().strftime('%Y%m%d_%H%M')}.docx",
mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
use_container_width=True,
)
else:
st.warning("python-docx yüklü değil: pip install python-docx")