Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
| 1 |
-
# app.py — Diag Studio AI Frontend
|
|
|
|
|
|
|
|
|
|
| 2 |
import os
|
| 3 |
import json
|
| 4 |
import time
|
|
@@ -7,12 +10,12 @@ import streamlit as st
|
|
| 7 |
from requests.adapters import HTTPAdapter
|
| 8 |
from urllib3.util.retry import Retry
|
| 9 |
|
| 10 |
-
# ---- container-safe defaults
|
| 11 |
os.environ.setdefault("HOME", "/tmp")
|
| 12 |
os.environ.setdefault("STREAMLIT_BROWSER_GATHER_USAGE_STATS", "false")
|
| 13 |
os.environ.setdefault("STREAMLIT_SERVER_HEADLESS", "true")
|
| 14 |
|
| 15 |
-
# ================= Theme
|
| 16 |
try:
|
| 17 |
st._config.set_option("theme.base", "dark")
|
| 18 |
st._config.set_option("theme.primaryColor", "#ff7a1a")
|
|
@@ -26,6 +29,7 @@ except Exception:
|
|
| 26 |
def _env(k: str) -> str:
|
| 27 |
return (os.getenv(k) or "").strip().strip("'\"")
|
| 28 |
|
|
|
|
| 29 |
API_BASE = (
|
| 30 |
_env("CARDIAG_AI_API") or _env("AI_LIGHTBOX_API") or _env("LUXFIT_API")
|
| 31 |
).rstrip("/") if (_env("CARDIAG_AI_API") or _env("AI_LIGHTBOX_API") or _env("LUXFIT_API")) else ""
|
|
@@ -37,7 +41,7 @@ HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
|
|
| 37 |
L = {
|
| 38 |
"en": {
|
| 39 |
"title": "Diag Studio AI — Engine Sound Diagnosis",
|
| 40 |
-
"about": "Upload an engine sound (mp3/wav/ogg) or paste an audio URL. Backend
|
| 41 |
"backend_url": "Backend URL",
|
| 42 |
"lang": "Language",
|
| 43 |
"upload": "Upload audio file",
|
|
@@ -46,7 +50,7 @@ L = {
|
|
| 46 |
"adv": "Advanced",
|
| 47 |
"prompt": "Override FAL prompt (optional)",
|
| 48 |
"use_data_uri": "Send as data:URI (safer for private backends)",
|
| 49 |
-
"model": "OpenAI model
|
| 50 |
"analyze": "Analyze",
|
| 51 |
"reset": "Reset",
|
| 52 |
"result": "Result",
|
|
@@ -60,23 +64,24 @@ L = {
|
|
| 60 |
"health_ok": "Backend is reachable.",
|
| 61 |
"health_fail": "Backend not reachable.",
|
| 62 |
"upload_hint": "Max ~200 MB per Streamlit; use mp3/wav/ogg.",
|
| 63 |
-
"footer": "Tip: You can
|
| 64 |
"auto_retry": "Auto-retry on 429/500/502/503/504",
|
| 65 |
"warmup": "Warm up backend before analysis",
|
| 66 |
"max_wait": "Max total wait (sec) for retries",
|
| 67 |
"warming": "Warming up backend…",
|
| 68 |
"analyzing": "Analyzing…",
|
| 69 |
-
"lang_help": "
|
| 70 |
-
"ok_false": "
|
| 71 |
-
"try_again": "
|
| 72 |
"preview": "Audio preview",
|
| 73 |
"raw_text": "Raw model text",
|
| 74 |
"prompt_used": "Prompt used (first 240 chars)",
|
| 75 |
-
"api_used": "Backend audio URL (truncated)"
|
|
|
|
| 76 |
},
|
| 77 |
"tr": {
|
| 78 |
"title": "Diag Studio AI — Motor Sesi Teşhis",
|
| 79 |
-
"about": "Motor sesini (mp3/wav/ogg) yükleyin veya bir ses URL’si girin. Backend
|
| 80 |
"backend_url": "Backend URL",
|
| 81 |
"lang": "Dil",
|
| 82 |
"upload": "Ses dosyası yükle",
|
|
@@ -85,7 +90,7 @@ L = {
|
|
| 85 |
"adv": "Gelişmiş",
|
| 86 |
"prompt": "FAL prompt’u geçersiz kıl (opsiyonel)",
|
| 87 |
"use_data_uri": "data:URI olarak gönder (özel backend için güvenli)",
|
| 88 |
-
"model": "OpenAI modeli (
|
| 89 |
"analyze": "Analiz Et",
|
| 90 |
"reset": "Sıfırla",
|
| 91 |
"result": "Sonuç",
|
|
@@ -98,20 +103,21 @@ L = {
|
|
| 98 |
"no_backend": "Bir backend URL girin (örn: http://127.0.0.1:7860)",
|
| 99 |
"health_ok": "Backend erişilebilir.",
|
| 100 |
"health_fail": "Backend’e erişilemedi.",
|
| 101 |
-
"upload_hint": "Streamlit
|
| 102 |
-
"footer": "İpucu: Dosya yüklemek istemezseniz herkese açık bir ses URL’si
|
| 103 |
"auto_retry": "429/500/502/503/504 için otomatik yeniden dene",
|
| 104 |
"warmup": "Analizden önce backend’i ısıt",
|
| 105 |
"max_wait": "Retry için azami bekleme (sn)",
|
| 106 |
"warming": "Backend ısıtılıyor…",
|
| 107 |
"analyzing": "Analiz ediliyor…",
|
| 108 |
-
"lang_help": "
|
| 109 |
"ok_false": "Backend kontrollü bir hata döndürdü.",
|
| 110 |
-
"try_again": "
|
| 111 |
"preview": "Ses önizleme",
|
| 112 |
"raw_text": "Ham model metni",
|
| 113 |
"prompt_used": "Kullanılan prompt (ilk 240 karakter)",
|
| 114 |
-
"api_used": "Backend ses URL’si (kısaltılmış)"
|
|
|
|
| 115 |
}
|
| 116 |
}
|
| 117 |
|
|
@@ -122,7 +128,7 @@ if "last_response" not in st.session_state:
|
|
| 122 |
st.session_state.last_response = None
|
| 123 |
|
| 124 |
# ================= Header =================
|
| 125 |
-
col1, col2 = st.columns([0.
|
| 126 |
with col1:
|
| 127 |
st.title(L[st.session_state.lang]["title"])
|
| 128 |
with col2:
|
|
@@ -135,6 +141,7 @@ with col2:
|
|
| 135 |
|
| 136 |
t = L[st.session_state.lang]
|
| 137 |
st.caption(t["about"])
|
|
|
|
| 138 |
|
| 139 |
backend_default = API_BASE or "http://127.0.0.1:7860"
|
| 140 |
api_base = st.text_input(t["backend_url"], value=backend_default)
|
|
@@ -143,10 +150,10 @@ api_base = st.text_input(t["backend_url"], value=backend_default)
|
|
| 143 |
with st.expander(t["adv"], expanded=False):
|
| 144 |
use_data_uri = st.checkbox(t["use_data_uri"], value=True)
|
| 145 |
openai_model = st.text_input(t["model"], value="gpt-4o")
|
| 146 |
-
prompt_override = st.text_area(t["prompt"], value="", height=
|
| 147 |
auto_retry = st.checkbox(t["auto_retry"], value=True)
|
| 148 |
warmup = st.checkbox(t["warmup"], value=True)
|
| 149 |
-
max_wait = st.slider(t["max_wait"], 10,
|
| 150 |
st.caption(t["footer"])
|
| 151 |
|
| 152 |
st.markdown("---")
|
|
@@ -176,9 +183,7 @@ def build_session(_auto_retry: bool) -> requests.Session:
|
|
| 176 |
s.headers.update(HEADERS)
|
| 177 |
if _auto_retry:
|
| 178 |
retry = Retry(
|
| 179 |
-
total=6,
|
| 180 |
-
connect=6,
|
| 181 |
-
read=6,
|
| 182 |
backoff_factor=0.8,
|
| 183 |
status_forcelist=[429, 500, 502, 503, 504],
|
| 184 |
allowed_methods=frozenset(["GET", "POST"]),
|
|
@@ -215,7 +220,7 @@ def post_analyze(sess: requests.Session, base: str, _file, _audio_url: str, lang
|
|
| 215 |
"use_data_uri": "true" if use_data_uri else "false",
|
| 216 |
"openai_model": openai_model or "gpt-4o",
|
| 217 |
"lang": lang, # "tr" or "en"
|
| 218 |
-
# backend pins detailed_analysis=True
|
| 219 |
}
|
| 220 |
if prompt_override.strip():
|
| 221 |
data["prompt"] = prompt_override.strip()
|
|
@@ -231,7 +236,8 @@ def post_analyze(sess: requests.Session, base: str, _file, _audio_url: str, lang
|
|
| 231 |
return {"ok": False, "error": "No file or URL provided."}
|
| 232 |
|
| 233 |
deadline = time.time() + max_wait
|
| 234 |
-
attempts
|
|
|
|
| 235 |
while True:
|
| 236 |
attempts += 1
|
| 237 |
try:
|
|
@@ -241,12 +247,7 @@ def post_analyze(sess: requests.Session, base: str, _file, _audio_url: str, lang
|
|
| 241 |
except Exception as e:
|
| 242 |
last_err = e
|
| 243 |
if not auto_retry or time.time() > deadline:
|
| 244 |
-
return {
|
| 245 |
-
"ok": False,
|
| 246 |
-
"error": f"All attempts failed. Last error: {e}",
|
| 247 |
-
"attempts": attempts,
|
| 248 |
-
"api_base": base,
|
| 249 |
-
}
|
| 250 |
time.sleep(1.2)
|
| 251 |
|
| 252 |
# ================= Actions =================
|
|
@@ -256,10 +257,9 @@ if health_btn:
|
|
| 256 |
try:
|
| 257 |
r = sess.get(f"{api_base.rstrip('/')}/health", timeout=10)
|
| 258 |
st.success(t["health_ok"] if r.ok else t["health_fail"])
|
| 259 |
-
if r.ok:
|
| 260 |
payload = r.json()
|
| 261 |
-
|
| 262 |
-
st.code(json.dumps(payload, ensure_ascii=False, indent=2), language="json")
|
| 263 |
except Exception:
|
| 264 |
st.error(t["health_fail"])
|
| 265 |
|
|
@@ -291,19 +291,19 @@ if res:
|
|
| 291 |
else:
|
| 292 |
st.subheader(t["result"])
|
| 293 |
|
| 294 |
-
#
|
| 295 |
comm = res.get("commentary") or ""
|
| 296 |
if comm:
|
| 297 |
st.markdown(f"**{t['commentary']}**")
|
| 298 |
st.write(comm)
|
| 299 |
|
| 300 |
-
#
|
| 301 |
parsed = res.get("fal_parsed")
|
| 302 |
if parsed:
|
| 303 |
st.markdown(f"**{t['json']}**")
|
| 304 |
st.json(parsed)
|
| 305 |
|
| 306 |
-
#
|
| 307 |
extras_col1, extras_col2 = st.columns(2)
|
| 308 |
if res.get("prompt_used"):
|
| 309 |
with extras_col1:
|
|
@@ -314,7 +314,7 @@ if res:
|
|
| 314 |
st.caption(t["api_used"])
|
| 315 |
st.code(res["fal_audio_url"], language="text")
|
| 316 |
|
| 317 |
-
#
|
| 318 |
st.markdown(f"**{t['backend_artifacts']}**")
|
| 319 |
dls = res.get("downloads") or {}
|
| 320 |
col_a, col_b, col_c = st.columns(3)
|
|
@@ -341,4 +341,4 @@ if res:
|
|
| 341 |
col_c.error("raw.txt ✖")
|
| 342 |
|
| 343 |
# ===== Footer =====
|
| 344 |
-
st.caption("© 2025 Diag Studio AI — Streamlit frontend (TR/EN, retries, warm-up,
|
|
|
|
| 1 |
+
# app.py — Diag Studio AI Frontend
|
| 2 |
+
# Compatible with backend v1.8.0 (FAL open-world cue-rich → GPT-4o hard-discriminator labeling)
|
| 3 |
+
# TR/EN UI • retries • warm-up • audio preview • artifact downloads
|
| 4 |
+
|
| 5 |
import os
|
| 6 |
import json
|
| 7 |
import time
|
|
|
|
| 10 |
from requests.adapters import HTTPAdapter
|
| 11 |
from urllib3.util.retry import Retry
|
| 12 |
|
| 13 |
+
# ---- container-safe defaults
|
| 14 |
os.environ.setdefault("HOME", "/tmp")
|
| 15 |
os.environ.setdefault("STREAMLIT_BROWSER_GATHER_USAGE_STATS", "false")
|
| 16 |
os.environ.setdefault("STREAMLIT_SERVER_HEADLESS", "true")
|
| 17 |
|
| 18 |
+
# ================= Theme =================
|
| 19 |
try:
|
| 20 |
st._config.set_option("theme.base", "dark")
|
| 21 |
st._config.set_option("theme.primaryColor", "#ff7a1a")
|
|
|
|
| 29 |
def _env(k: str) -> str:
|
| 30 |
return (os.getenv(k) or "").strip().strip("'\"")
|
| 31 |
|
| 32 |
+
# Allow reusing existing envs from your other apps
|
| 33 |
API_BASE = (
|
| 34 |
_env("CARDIAG_AI_API") or _env("AI_LIGHTBOX_API") or _env("LUXFIT_API")
|
| 35 |
).rstrip("/") if (_env("CARDIAG_AI_API") or _env("AI_LIGHTBOX_API") or _env("LUXFIT_API")) else ""
|
|
|
|
| 41 |
L = {
|
| 42 |
"en": {
|
| 43 |
"title": "Diag Studio AI — Engine Sound Diagnosis",
|
| 44 |
+
"about": "Upload an engine sound (mp3/wav/ogg) or paste an audio URL. Backend extracts cue-rich acoustics with fal.ai, then GPT-4o applies HARD discriminators to avoid collapsing similar timbres.",
|
| 45 |
"backend_url": "Backend URL",
|
| 46 |
"lang": "Language",
|
| 47 |
"upload": "Upload audio file",
|
|
|
|
| 50 |
"adv": "Advanced",
|
| 51 |
"prompt": "Override FAL prompt (optional)",
|
| 52 |
"use_data_uri": "Send as data:URI (safer for private backends)",
|
| 53 |
+
"model": "OpenAI model (labeling/commentary)",
|
| 54 |
"analyze": "Analyze",
|
| 55 |
"reset": "Reset",
|
| 56 |
"result": "Result",
|
|
|
|
| 64 |
"health_ok": "Backend is reachable.",
|
| 65 |
"health_fail": "Backend not reachable.",
|
| 66 |
"upload_hint": "Max ~200 MB per Streamlit; use mp3/wav/ogg.",
|
| 67 |
+
"footer": "Tip: You can paste a public audio URL instead of uploading.",
|
| 68 |
"auto_retry": "Auto-retry on 429/500/502/503/504",
|
| 69 |
"warmup": "Warm up backend before analysis",
|
| 70 |
"max_wait": "Max total wait (sec) for retries",
|
| 71 |
"warming": "Warming up backend…",
|
| 72 |
"analyzing": "Analyzing…",
|
| 73 |
+
"lang_help": "Commentary language",
|
| 74 |
+
"ok_false": "Backend returned a controlled error.",
|
| 75 |
+
"try_again": "Service may be waking or rate-limited. Try again or increase ‘Max total wait’.",
|
| 76 |
"preview": "Audio preview",
|
| 77 |
"raw_text": "Raw model text",
|
| 78 |
"prompt_used": "Prompt used (first 240 chars)",
|
| 79 |
+
"api_used": "Backend audio URL (truncated)",
|
| 80 |
+
"hard_note": "HARD discriminators active: half-crank vs full-crank, load vs RPM, cold-start confinement, tonal vs broadband, deep vs light."
|
| 81 |
},
|
| 82 |
"tr": {
|
| 83 |
"title": "Diag Studio AI — Motor Sesi Teşhis",
|
| 84 |
+
"about": "Motor sesini (mp3/wav/ogg) yükleyin veya bir ses URL’si girin. Backend fal.ai ile ipucu-zengin akustik özet çıkarır; GPT-4o benzer tınıları karıştırmamak için SERT ayraçlar uygular.",
|
| 85 |
"backend_url": "Backend URL",
|
| 86 |
"lang": "Dil",
|
| 87 |
"upload": "Ses dosyası yükle",
|
|
|
|
| 90 |
"adv": "Gelişmiş",
|
| 91 |
"prompt": "FAL prompt’u geçersiz kıl (opsiyonel)",
|
| 92 |
"use_data_uri": "data:URI olarak gönder (özel backend için güvenli)",
|
| 93 |
+
"model": "OpenAI modeli (label/yorum)",
|
| 94 |
"analyze": "Analiz Et",
|
| 95 |
"reset": "Sıfırla",
|
| 96 |
"result": "Sonuç",
|
|
|
|
| 103 |
"no_backend": "Bir backend URL girin (örn: http://127.0.0.1:7860)",
|
| 104 |
"health_ok": "Backend erişilebilir.",
|
| 105 |
"health_fail": "Backend’e erişilemedi.",
|
| 106 |
+
"upload_hint": "Streamlit yükleme sınırı ~200 MB; mp3/wav/ogg kullanın.",
|
| 107 |
+
"footer": "İpucu: Dosya yüklemek istemezseniz herkese açık bir ses URL’si girebilirsiniz.",
|
| 108 |
"auto_retry": "429/500/502/503/504 için otomatik yeniden dene",
|
| 109 |
"warmup": "Analizden önce backend’i ısıt",
|
| 110 |
"max_wait": "Retry için azami bekleme (sn)",
|
| 111 |
"warming": "Backend ısıtılıyor…",
|
| 112 |
"analyzing": "Analiz ediliyor…",
|
| 113 |
+
"lang_help": "Yorum dilini seçin",
|
| 114 |
"ok_false": "Backend kontrollü bir hata döndürdü.",
|
| 115 |
+
"try_again": "Servis uyanıyor veya sınırlandı. Tekrar deneyin veya ‘Azami bekleme’ süresini artırın.",
|
| 116 |
"preview": "Ses önizleme",
|
| 117 |
"raw_text": "Ham model metni",
|
| 118 |
"prompt_used": "Kullanılan prompt (ilk 240 karakter)",
|
| 119 |
+
"api_used": "Backend ses URL’si (kısaltılmış)",
|
| 120 |
+
"hard_note": "SERT ayraçlar aktif: yarım-krank vs tam-krank, yük vs RPM, soğuk-başlangıç sınırı, tonal vs geniş bant, derin vs hafif."
|
| 121 |
}
|
| 122 |
}
|
| 123 |
|
|
|
|
| 128 |
st.session_state.last_response = None
|
| 129 |
|
| 130 |
# ================= Header =================
|
| 131 |
+
col1, col2 = st.columns([0.65, 0.35])
|
| 132 |
with col1:
|
| 133 |
st.title(L[st.session_state.lang]["title"])
|
| 134 |
with col2:
|
|
|
|
| 141 |
|
| 142 |
t = L[st.session_state.lang]
|
| 143 |
st.caption(t["about"])
|
| 144 |
+
st.info(t["hard_note"])
|
| 145 |
|
| 146 |
backend_default = API_BASE or "http://127.0.0.1:7860"
|
| 147 |
api_base = st.text_input(t["backend_url"], value=backend_default)
|
|
|
|
| 150 |
with st.expander(t["adv"], expanded=False):
|
| 151 |
use_data_uri = st.checkbox(t["use_data_uri"], value=True)
|
| 152 |
openai_model = st.text_input(t["model"], value="gpt-4o")
|
| 153 |
+
prompt_override = st.text_area(t["prompt"], value="", height=140, help="Leave empty to use backend’s cue-rich default")
|
| 154 |
auto_retry = st.checkbox(t["auto_retry"], value=True)
|
| 155 |
warmup = st.checkbox(t["warmup"], value=True)
|
| 156 |
+
max_wait = st.slider(t["max_wait"], 10, 240, 60, step=10)
|
| 157 |
st.caption(t["footer"])
|
| 158 |
|
| 159 |
st.markdown("---")
|
|
|
|
| 183 |
s.headers.update(HEADERS)
|
| 184 |
if _auto_retry:
|
| 185 |
retry = Retry(
|
| 186 |
+
total=6, connect=6, read=6,
|
|
|
|
|
|
|
| 187 |
backoff_factor=0.8,
|
| 188 |
status_forcelist=[429, 500, 502, 503, 504],
|
| 189 |
allowed_methods=frozenset(["GET", "POST"]),
|
|
|
|
| 220 |
"use_data_uri": "true" if use_data_uri else "false",
|
| 221 |
"openai_model": openai_model or "gpt-4o",
|
| 222 |
"lang": lang, # "tr" or "en"
|
| 223 |
+
# backend pins detailed_analysis=True
|
| 224 |
}
|
| 225 |
if prompt_override.strip():
|
| 226 |
data["prompt"] = prompt_override.strip()
|
|
|
|
| 236 |
return {"ok": False, "error": "No file or URL provided."}
|
| 237 |
|
| 238 |
deadline = time.time() + max_wait
|
| 239 |
+
attempts = 0
|
| 240 |
+
last_err = None
|
| 241 |
while True:
|
| 242 |
attempts += 1
|
| 243 |
try:
|
|
|
|
| 247 |
except Exception as e:
|
| 248 |
last_err = e
|
| 249 |
if not auto_retry or time.time() > deadline:
|
| 250 |
+
return {"ok": False, "error": f"All attempts failed. Last error: {e}", "attempts": attempts, "api_base": base}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
time.sleep(1.2)
|
| 252 |
|
| 253 |
# ================= Actions =================
|
|
|
|
| 257 |
try:
|
| 258 |
r = sess.get(f"{api_base.rstrip('/')}/health", timeout=10)
|
| 259 |
st.success(t["health_ok"] if r.ok else t["health_fail"])
|
| 260 |
+
if r.ok and r.headers.get("content-type","").startswith("application/json"):
|
| 261 |
payload = r.json()
|
| 262 |
+
st.code(json.dumps(payload, ensure_ascii=False, indent=2), language="json")
|
|
|
|
| 263 |
except Exception:
|
| 264 |
st.error(t["health_fail"])
|
| 265 |
|
|
|
|
| 291 |
else:
|
| 292 |
st.subheader(t["result"])
|
| 293 |
|
| 294 |
+
# Commentary
|
| 295 |
comm = res.get("commentary") or ""
|
| 296 |
if comm:
|
| 297 |
st.markdown(f"**{t['commentary']}**")
|
| 298 |
st.write(comm)
|
| 299 |
|
| 300 |
+
# Parsed JSON
|
| 301 |
parsed = res.get("fal_parsed")
|
| 302 |
if parsed:
|
| 303 |
st.markdown(f"**{t['json']}**")
|
| 304 |
st.json(parsed)
|
| 305 |
|
| 306 |
+
# Prompt used + fal audio url
|
| 307 |
extras_col1, extras_col2 = st.columns(2)
|
| 308 |
if res.get("prompt_used"):
|
| 309 |
with extras_col1:
|
|
|
|
| 314 |
st.caption(t["api_used"])
|
| 315 |
st.code(res["fal_audio_url"], language="text")
|
| 316 |
|
| 317 |
+
# Backend artifacts
|
| 318 |
st.markdown(f"**{t['backend_artifacts']}**")
|
| 319 |
dls = res.get("downloads") or {}
|
| 320 |
col_a, col_b, col_c = st.columns(3)
|
|
|
|
| 341 |
col_c.error("raw.txt ✖")
|
| 342 |
|
| 343 |
# ===== Footer =====
|
| 344 |
+
st.caption("© 2025 Diag Studio AI — Streamlit frontend (TR/EN, retries, warm-up, cue-rich + hard-discriminator flow)")
|