Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
-
# app.py — Diag Studio AI Frontend (TR/EN, retries, warm-up, artifacts
|
| 2 |
import os
|
|
|
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
import requests
|
|
@@ -48,6 +49,8 @@ L = {
|
|
| 48 |
"detailed": "Detailed analysis (always on)",
|
| 49 |
"use_data_uri": "Send as data:URI (safer for private backends)",
|
| 50 |
"model": "Commentary model (OpenAI)",
|
|
|
|
|
|
|
| 51 |
"analyze": "Analyze",
|
| 52 |
"reset": "Reset",
|
| 53 |
"result": "Result",
|
|
@@ -69,7 +72,11 @@ L = {
|
|
| 69 |
"analyzing": "Analyzing…",
|
| 70 |
"lang_help": "Output language for GPT-4o commentary",
|
| 71 |
"ok_false": "Request returned a controlled error from backend.",
|
| 72 |
-
"try_again": "Backend might be waking up or rate-limited. Try Analyze again or increase ‘Max total wait’."
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
},
|
| 74 |
"tr": {
|
| 75 |
"title": "Diag Studio AI — Motor Sesi Teşhis",
|
|
@@ -84,6 +91,8 @@ L = {
|
|
| 84 |
"detailed": "Ayrıntılı analiz (her zaman açık)",
|
| 85 |
"use_data_uri": "data:URI olarak gönder (özel backend için güvenli)",
|
| 86 |
"model": "Yorum modeli (OpenAI)",
|
|
|
|
|
|
|
| 87 |
"analyze": "Analiz Et",
|
| 88 |
"reset": "Sıfırla",
|
| 89 |
"result": "Sonuç",
|
|
@@ -96,7 +105,7 @@ L = {
|
|
| 96 |
"no_backend": "Bir backend URL girin (örn: http://127.0.0.1:7860)",
|
| 97 |
"health_ok": "Backend erişilebilir.",
|
| 98 |
"health_fail": "Backend’e erişilemedi.",
|
| 99 |
-
"upload_hint": "Streamlit
|
| 100 |
"footer": "İpucu: Dosya yüklemek istemezseniz herkese açık bir ses URL’si de girebilirsiniz.",
|
| 101 |
"auto_retry": "429/500/502/503/504 için otomatik yeniden dene",
|
| 102 |
"warmup": "Analizden önce backend’i ısıt",
|
|
@@ -105,7 +114,11 @@ L = {
|
|
| 105 |
"analyzing": "Analiz ediliyor…",
|
| 106 |
"lang_help": "GPT-4o yorum çıktısının dili",
|
| 107 |
"ok_false": "Backend kontrollü bir hata döndürdü.",
|
| 108 |
-
"try_again": "Backend uyanıyor veya sınırlandı. Yeniden Dene’ye basın ya da ‘Azami bekleme’ değerini artırın."
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
}
|
| 110 |
}
|
| 111 |
|
|
@@ -114,6 +127,8 @@ if "lang" not in st.session_state:
|
|
| 114 |
st.session_state.lang = "tr"
|
| 115 |
if "last_response" not in st.session_state:
|
| 116 |
st.session_state.last_response = None
|
|
|
|
|
|
|
| 117 |
|
| 118 |
# ================= Header =================
|
| 119 |
col1, col2 = st.columns([0.6, 0.4])
|
|
@@ -135,11 +150,12 @@ api_base = st.text_input(t["backend_url"], value=backend_default)
|
|
| 135 |
|
| 136 |
# ================= Advanced options =================
|
| 137 |
with st.expander(t["adv"], expanded=False):
|
| 138 |
-
# Detailed analysis backend’te zorunlu True; burada yalnızca bilgi amaçlı gösteriyoruz.
|
| 139 |
st.checkbox(t["detailed"], value=True, disabled=True)
|
| 140 |
use_data_uri = st.checkbox(t["use_data_uri"], value=True)
|
| 141 |
openai_model = st.text_input(t["model"], value="gpt-4o")
|
| 142 |
prompt_override = st.text_area(t["prompt"], value="", height=120)
|
|
|
|
|
|
|
| 143 |
auto_retry = st.checkbox(t["auto_retry"], value=True)
|
| 144 |
warmup = st.checkbox(t["warmup"], value=True)
|
| 145 |
max_wait = st.slider(t["max_wait"], 10, 180, 60, step=10)
|
|
@@ -153,6 +169,19 @@ file = st.file_uploader(t["upload"], type=["mp3", "wav", "ogg"])
|
|
| 153 |
st.write(f"**{t['or']}**")
|
| 154 |
audio_url = st.text_input(t["paste_url"], value="", placeholder="https://...")
|
| 155 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
# ================= Controls =================
|
| 157 |
c1, c2, c3 = st.columns([0.2, 0.2, 0.6])
|
| 158 |
go = c1.button(f"▶ {t['analyze']}", type="primary")
|
|
@@ -198,13 +227,14 @@ def fetch_bytes(sess: requests.Session, base: str, url: str) -> bytes:
|
|
| 198 |
r.raise_for_status()
|
| 199 |
return r.content
|
| 200 |
|
| 201 |
-
def post_analyze(sess: requests.Session, base: str, _file, _audio_url: str, lang: str) -> dict:
|
| 202 |
url = f"{base.rstrip('/')}/analyze"
|
| 203 |
data = {
|
| 204 |
"detailed_analysis": "true", # backend yine de True’ya sabitliyor
|
| 205 |
"use_data_uri": "true" if use_data_uri else "false",
|
| 206 |
"openai_model": openai_model or "gpt-4o",
|
| 207 |
-
"lang": lang, #
|
|
|
|
| 208 |
}
|
| 209 |
if prompt_override.strip():
|
| 210 |
data["prompt"] = prompt_override.strip()
|
|
@@ -225,7 +255,6 @@ def post_analyze(sess: requests.Session, base: str, _file, _audio_url: str, lang
|
|
| 225 |
attempts += 1
|
| 226 |
try:
|
| 227 |
resp = sess.post(url, data=data, files=files, timeout=180)
|
| 228 |
-
# Başarısız statüleri Retry mekanizması yönetir; yine de raise edelim:
|
| 229 |
resp.raise_for_status()
|
| 230 |
return resp.json()
|
| 231 |
except Exception as e:
|
|
@@ -266,7 +295,13 @@ if go:
|
|
| 266 |
if warmup:
|
| 267 |
warm_up_backend(sess, api_base, total_budget=min(max_wait, 40))
|
| 268 |
with st.spinner(t["analyzing"]):
|
| 269 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
st.session_state.last_response = res
|
| 271 |
|
| 272 |
# ================= Results =================
|
|
@@ -281,40 +316,66 @@ if res:
|
|
| 281 |
st.expander("Debug").code(json.dumps(res["fal_debug"], ensure_ascii=False, indent=2), language="json")
|
| 282 |
else:
|
| 283 |
st.subheader(t["result"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
comm = res.get("commentary") or ""
|
| 285 |
if comm:
|
| 286 |
st.markdown(f"**{t['commentary']}**")
|
| 287 |
st.write(comm)
|
| 288 |
|
| 289 |
-
|
| 290 |
if parsed:
|
| 291 |
st.markdown(f"**{t['json']}**")
|
| 292 |
st.json(parsed)
|
| 293 |
|
|
|
|
| 294 |
st.markdown(f"**{t['backend_artifacts']}**")
|
| 295 |
dls = res.get("downloads") or {}
|
| 296 |
col_a, col_b, col_c = st.columns(3)
|
| 297 |
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
data = fetch_bytes(sess, api_base, dls["fal_parsed_json"])
|
| 308 |
-
col_b.download_button(label=t["download_parsed"], data=data, file_name="parsed.json", mime="application/json")
|
| 309 |
-
except Exception:
|
| 310 |
-
col_b.error("parsed.json ✖")
|
| 311 |
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
col_c.download_button(label=t["download_raw"], data=data, file_name="raw.txt")
|
| 316 |
-
except Exception:
|
| 317 |
-
col_c.error("raw.txt ✖")
|
| 318 |
|
| 319 |
# ===== Footer =====
|
| 320 |
-
st.caption("© 2025 Diag Studio AI — Streamlit frontend (TR/EN, retries, warm-up)")
|
|
|
|
| 1 |
+
# app.py — Diag Studio AI Frontend (TR/EN, retries, warm-up, artifacts, Strict/Full, anomaly gate, adjudication)
|
| 2 |
import os
|
| 3 |
+
import io
|
| 4 |
import json
|
| 5 |
import time
|
| 6 |
import requests
|
|
|
|
| 49 |
"detailed": "Detailed analysis (always on)",
|
| 50 |
"use_data_uri": "Send as data:URI (safer for private backends)",
|
| 51 |
"model": "Commentary model (OpenAI)",
|
| 52 |
+
"mode": "Prompt Mode",
|
| 53 |
+
"mode_help": "Strict groups similar timbres to reduce mislabels; Full uses detailed taxonomy.",
|
| 54 |
"analyze": "Analyze",
|
| 55 |
"reset": "Reset",
|
| 56 |
"result": "Result",
|
|
|
|
| 72 |
"analyzing": "Analyzing…",
|
| 73 |
"lang_help": "Output language for GPT-4o commentary",
|
| 74 |
"ok_false": "Request returned a controlled error from backend.",
|
| 75 |
+
"try_again": "Backend might be waking up or rate-limited. Try Analyze again or increase ‘Max total wait’.",
|
| 76 |
+
"gate": "Anomaly Gate",
|
| 77 |
+
"taxonomy": "Taxonomy",
|
| 78 |
+
"applied_rules": "Adjudication rules applied",
|
| 79 |
+
"confusions": "Common confusion pairs",
|
| 80 |
},
|
| 81 |
"tr": {
|
| 82 |
"title": "Diag Studio AI — Motor Sesi Teşhis",
|
|
|
|
| 91 |
"detailed": "Ayrıntılı analiz (her zaman açık)",
|
| 92 |
"use_data_uri": "data:URI olarak gönder (özel backend için güvenli)",
|
| 93 |
"model": "Yorum modeli (OpenAI)",
|
| 94 |
+
"mode": "Prompt Modu",
|
| 95 |
+
"mode_help": "Strict benzer tınıları gruplayıp karışmayı azaltır; Full detaylı taksonomi kullanır.",
|
| 96 |
"analyze": "Analiz Et",
|
| 97 |
"reset": "Sıfırla",
|
| 98 |
"result": "Sonuç",
|
|
|
|
| 105 |
"no_backend": "Bir backend URL girin (örn: http://127.0.0.1:7860)",
|
| 106 |
"health_ok": "Backend erişilebilir.",
|
| 107 |
"health_fail": "Backend’e erişilemedi.",
|
| 108 |
+
"upload_hint": "Streamlit yükleme sınırı ~200 MB; mp3/wav/ogg kullanın.",
|
| 109 |
"footer": "İpucu: Dosya yüklemek istemezseniz herkese açık bir ses URL’si de girebilirsiniz.",
|
| 110 |
"auto_retry": "429/500/502/503/504 için otomatik yeniden dene",
|
| 111 |
"warmup": "Analizden önce backend’i ısıt",
|
|
|
|
| 114 |
"analyzing": "Analiz ediliyor…",
|
| 115 |
"lang_help": "GPT-4o yorum çıktısının dili",
|
| 116 |
"ok_false": "Backend kontrollü bir hata döndürdü.",
|
| 117 |
+
"try_again": "Backend uyanıyor veya sınırlandı. Yeniden Dene’ye basın ya da ‘Azami bekleme’ değerini artırın.",
|
| 118 |
+
"gate": "Arıza Kapısı (Anomaly Gate)",
|
| 119 |
+
"taxonomy": "Taksonomi",
|
| 120 |
+
"applied_rules": "Uygulanan adjudication kuralları",
|
| 121 |
+
"confusions": "Sık karışan çiftler",
|
| 122 |
}
|
| 123 |
}
|
| 124 |
|
|
|
|
| 127 |
st.session_state.lang = "tr"
|
| 128 |
if "last_response" not in st.session_state:
|
| 129 |
st.session_state.last_response = None
|
| 130 |
+
if "audio_mem" not in st.session_state:
|
| 131 |
+
st.session_state.audio_mem = None # keep last uploaded audio bytes for player
|
| 132 |
|
| 133 |
# ================= Header =================
|
| 134 |
col1, col2 = st.columns([0.6, 0.4])
|
|
|
|
| 150 |
|
| 151 |
# ================= Advanced options =================
|
| 152 |
with st.expander(t["adv"], expanded=False):
|
|
|
|
| 153 |
st.checkbox(t["detailed"], value=True, disabled=True)
|
| 154 |
use_data_uri = st.checkbox(t["use_data_uri"], value=True)
|
| 155 |
openai_model = st.text_input(t["model"], value="gpt-4o")
|
| 156 |
prompt_override = st.text_area(t["prompt"], value="", height=120)
|
| 157 |
+
# NEW: Full/Strict selector
|
| 158 |
+
mode = st.selectbox(t["mode"], ["FULL", "STRICT"], index=0, help=t["mode_help"])
|
| 159 |
auto_retry = st.checkbox(t["auto_retry"], value=True)
|
| 160 |
warmup = st.checkbox(t["warmup"], value=True)
|
| 161 |
max_wait = st.slider(t["max_wait"], 10, 180, 60, step=10)
|
|
|
|
| 169 |
st.write(f"**{t['or']}**")
|
| 170 |
audio_url = st.text_input(t["paste_url"], value="", placeholder="https://...")
|
| 171 |
|
| 172 |
+
# If a file was uploaded, stash bytes for inline audio preview
|
| 173 |
+
if file is not None:
|
| 174 |
+
try:
|
| 175 |
+
b = file.read()
|
| 176 |
+
st.session_state.audio_mem = (b, file.type or "audio/mpeg")
|
| 177 |
+
# Reset file pointer for requests
|
| 178 |
+
file.seek(0)
|
| 179 |
+
except Exception:
|
| 180 |
+
st.session_state.audio_mem = None
|
| 181 |
+
|
| 182 |
+
if st.session_state.audio_mem:
|
| 183 |
+
st.audio(st.session_state.audio_mem[0], format=st.session_state.audio_mem[1])
|
| 184 |
+
|
| 185 |
# ================= Controls =================
|
| 186 |
c1, c2, c3 = st.columns([0.2, 0.2, 0.6])
|
| 187 |
go = c1.button(f"▶ {t['analyze']}", type="primary")
|
|
|
|
| 227 |
r.raise_for_status()
|
| 228 |
return r.content
|
| 229 |
|
| 230 |
+
def post_analyze(sess: requests.Session, base: str, _file, _audio_url: str, lang: str, mode: str) -> dict:
|
| 231 |
url = f"{base.rstrip('/')}/analyze"
|
| 232 |
data = {
|
| 233 |
"detailed_analysis": "true", # backend yine de True’ya sabitliyor
|
| 234 |
"use_data_uri": "true" if use_data_uri else "false",
|
| 235 |
"openai_model": openai_model or "gpt-4o",
|
| 236 |
+
"lang": lang, # "tr" | "en"
|
| 237 |
+
"prompt_mode": mode, # "FULL" | "STRICT"
|
| 238 |
}
|
| 239 |
if prompt_override.strip():
|
| 240 |
data["prompt"] = prompt_override.strip()
|
|
|
|
| 255 |
attempts += 1
|
| 256 |
try:
|
| 257 |
resp = sess.post(url, data=data, files=files, timeout=180)
|
|
|
|
| 258 |
resp.raise_for_status()
|
| 259 |
return resp.json()
|
| 260 |
except Exception as e:
|
|
|
|
| 295 |
if warmup:
|
| 296 |
warm_up_backend(sess, api_base, total_budget=min(max_wait, 40))
|
| 297 |
with st.spinner(t["analyzing"]):
|
| 298 |
+
# streamlit's UploadedFile can be used directly in requests; make sure pointer is at 0
|
| 299 |
+
if file is not None:
|
| 300 |
+
try:
|
| 301 |
+
file.seek(0)
|
| 302 |
+
except Exception:
|
| 303 |
+
pass
|
| 304 |
+
res = post_analyze(sess, api_base, file, audio_url, st.session_state.lang, mode)
|
| 305 |
st.session_state.last_response = res
|
| 306 |
|
| 307 |
# ================= Results =================
|
|
|
|
| 316 |
st.expander("Debug").code(json.dumps(res["fal_debug"], ensure_ascii=False, indent=2), language="json")
|
| 317 |
else:
|
| 318 |
st.subheader(t["result"])
|
| 319 |
+
|
| 320 |
+
# Anomaly Gate & Taxonomy quick view
|
| 321 |
+
parsed = res.get("fal_parsed") or {}
|
| 322 |
+
if isinstance(parsed, dict):
|
| 323 |
+
gate = parsed.get("anomaly_gate") or {}
|
| 324 |
+
colg1, colg2, colg3 = st.columns([0.33, 0.33, 0.34])
|
| 325 |
+
with colg1:
|
| 326 |
+
st.markdown(f"**{t['gate']}**")
|
| 327 |
+
is_anom = gate.get("is_anomaly")
|
| 328 |
+
score = gate.get("score")
|
| 329 |
+
ev = gate.get("evidence")
|
| 330 |
+
st.write(f"• is_anomaly: `{is_anom}`")
|
| 331 |
+
st.write(f"• score: `{score}`")
|
| 332 |
+
if ev: st.write(f"• evidence: `{ev}`")
|
| 333 |
+
with colg2:
|
| 334 |
+
st.markdown(f"**{t['taxonomy']}**")
|
| 335 |
+
tax = parsed.get("taxonomy_version") or res.get("taxonomy_version")
|
| 336 |
+
st.write(f"`{tax or '—'}`")
|
| 337 |
+
pmode = res.get("prompt_mode")
|
| 338 |
+
if pmode:
|
| 339 |
+
st.write(f"mode: `{pmode}`")
|
| 340 |
+
with colg3:
|
| 341 |
+
rules = (parsed.get("adjudication") or {}).get("applied_rules") or (res.get("adjudication") or {}).get("applied_rules")
|
| 342 |
+
st.markdown(f"**{t['applied_rules']}**")
|
| 343 |
+
if rules:
|
| 344 |
+
st.code(json.dumps(rules, ensure_ascii=False, indent=2), language="json")
|
| 345 |
+
conf_pairs = (parsed.get("notes") or {}).get("confusion_pairs")
|
| 346 |
+
if conf_pairs:
|
| 347 |
+
st.markdown(f"**{t['confusions']}**")
|
| 348 |
+
st.code(json.dumps(conf_pairs, ensure_ascii=False, indent=2), language="json")
|
| 349 |
+
|
| 350 |
+
# Driver-friendly commentary
|
| 351 |
comm = res.get("commentary") or ""
|
| 352 |
if comm:
|
| 353 |
st.markdown(f"**{t['commentary']}**")
|
| 354 |
st.write(comm)
|
| 355 |
|
| 356 |
+
# Raw parsed JSON
|
| 357 |
if parsed:
|
| 358 |
st.markdown(f"**{t['json']}**")
|
| 359 |
st.json(parsed)
|
| 360 |
|
| 361 |
+
# Backend artifacts
|
| 362 |
st.markdown(f"**{t['backend_artifacts']}**")
|
| 363 |
dls = res.get("downloads") or {}
|
| 364 |
col_a, col_b, col_c = st.columns(3)
|
| 365 |
|
| 366 |
+
def fetch_and_button(label: str, key: str, file_name: str, column):
|
| 367 |
+
if dls.get(key):
|
| 368 |
+
try:
|
| 369 |
+
data = fetch_bytes(sess, api_base, dls[key])
|
| 370 |
+
column.download_button(label=label, data=data, file_name=file_name)
|
| 371 |
+
except Exception:
|
| 372 |
+
column.error(f"{file_name} ✖")
|
| 373 |
+
else:
|
| 374 |
+
column.write("—")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 375 |
|
| 376 |
+
fetch_and_button(t["download_summary"], "summary_txt", "summary.txt", col_a)
|
| 377 |
+
fetch_and_button(t["download_parsed"], "fal_parsed_json", "parsed.json", col_b)
|
| 378 |
+
fetch_and_button(t["download_raw"], "fal_raw_txt", "raw.txt", col_c)
|
|
|
|
|
|
|
|
|
|
| 379 |
|
| 380 |
# ===== Footer =====
|
| 381 |
+
st.caption("© 2025 Diag Studio AI — Streamlit frontend (TR/EN, retries, warm-up, Strict/Full, anomaly gate, adjudication)")
|