Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
| 1 |
# app.py — Diag Studio (Customer UI)
|
| 2 |
-
#
|
| 3 |
-
#
|
| 4 |
-
#
|
|
|
|
| 5 |
|
| 6 |
import os
|
|
|
|
| 7 |
import json
|
| 8 |
import time
|
| 9 |
import requests
|
|
@@ -11,12 +13,12 @@ import streamlit as st
|
|
| 11 |
from requests.adapters import HTTPAdapter
|
| 12 |
from urllib3.util.retry import Retry
|
| 13 |
|
| 14 |
-
#
|
| 15 |
os.environ.setdefault("HOME", "/tmp")
|
| 16 |
os.environ.setdefault("STREAMLIT_BROWSER_GATHER_USAGE_STATS", "false")
|
| 17 |
os.environ.setdefault("STREAMLIT_SERVER_HEADLESS", "true")
|
| 18 |
|
| 19 |
-
#
|
| 20 |
try:
|
| 21 |
st._config.set_option("theme.base", "dark")
|
| 22 |
st._config.set_option("theme.primaryColor", "#ff7a1a")
|
|
@@ -26,19 +28,16 @@ try:
|
|
| 26 |
except Exception:
|
| 27 |
pass
|
| 28 |
|
| 29 |
-
#
|
| 30 |
def _env(k: str) -> str:
|
| 31 |
return (os.getenv(k) or "").strip().strip("'\"")
|
| 32 |
|
| 33 |
DEV_MODE = (_env("DEV_MODE") in {"1", "true", "yes", "on"})
|
| 34 |
-
|
| 35 |
-
# Tek bir sabit backend anahtarı (kullanıcıya gösterilmez)
|
| 36 |
-
API_BASE = (_env("CARDIAG_AI_API") or _env("APP_API") or "http://127.0.0.1:7860").rstrip("/")
|
| 37 |
-
|
| 38 |
TOKEN = _env("CARDIAG_AI_TOKEN") or _env("APP_TOKEN")
|
| 39 |
HEADERS = {"Authorization": f"Bearer {TOKEN}"} if TOKEN else {}
|
| 40 |
|
| 41 |
-
#
|
| 42 |
LANGS = {
|
| 43 |
"tr": {
|
| 44 |
"title": "AI Destekli Araç Motor Ses Analizi",
|
|
@@ -48,6 +47,7 @@ LANGS = {
|
|
| 48 |
"paste_url": "Ses bağlantısı (opsiyonel)",
|
| 49 |
"analyze": "Analiz Et",
|
| 50 |
"reset": "Sıfırla",
|
|
|
|
| 51 |
"result": "Sonuç",
|
| 52 |
"commentary": "Özet ve Öneriler",
|
| 53 |
"json": "Detaylı Bulgular",
|
|
@@ -55,7 +55,7 @@ LANGS = {
|
|
| 55 |
"download_summary": "Özet indir",
|
| 56 |
"download_parsed": "Detay indir (JSON)",
|
| 57 |
"download_raw": "Ham metin indir",
|
| 58 |
-
"upload_hint": "mp3/wav/ogg
|
| 59 |
"warming": "Hazırlanıyor…",
|
| 60 |
"analyzing": "Analiz ediliyor…",
|
| 61 |
"health_ok": "Sistem hazır.",
|
|
@@ -63,15 +63,20 @@ LANGS = {
|
|
| 63 |
"need_backend": "Sistem adresi yapılandırılmadı.",
|
| 64 |
"need_input": "Lütfen bir dosya yükleyin veya bağlantı girin.",
|
| 65 |
"error_generic": "İşlem başarısız. Lütfen tekrar deneyin.",
|
| 66 |
-
"try_again": "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
"admin": "Yönetici",
|
| 68 |
"admin_desc": "Bu bölüm yalnızca yetkili kullanım içindir.",
|
| 69 |
"backend_url": "Sistem adresi",
|
| 70 |
-
"use_data_uri": "
|
| 71 |
-
"warmup": "
|
| 72 |
"auto_retry": "Otomatik yeniden dene",
|
| 73 |
"max_wait": "Azami bekleme (sn)",
|
| 74 |
-
"show_debug": "Hata ayıklama
|
| 75 |
},
|
| 76 |
"en": {
|
| 77 |
"title": "AI-Powered Engine Sound Analysis",
|
|
@@ -81,6 +86,7 @@ LANGS = {
|
|
| 81 |
"paste_url": "Audio URL (optional)",
|
| 82 |
"analyze": "Analyze",
|
| 83 |
"reset": "Reset",
|
|
|
|
| 84 |
"result": "Result",
|
| 85 |
"commentary": "Summary & Recommendations",
|
| 86 |
"json": "Detailed Findings",
|
|
@@ -88,7 +94,7 @@ LANGS = {
|
|
| 88 |
"download_summary": "Download summary",
|
| 89 |
"download_parsed": "Download details (JSON)",
|
| 90 |
"download_raw": "Download raw text",
|
| 91 |
-
"upload_hint": "
|
| 92 |
"warming": "Preparing…",
|
| 93 |
"analyzing": "Analyzing…",
|
| 94 |
"health_ok": "Service is ready.",
|
|
@@ -97,44 +103,40 @@ LANGS = {
|
|
| 97 |
"need_input": "Please upload a file or enter a URL.",
|
| 98 |
"error_generic": "Request failed. Please try again.",
|
| 99 |
"try_again": "Service may be busy. Try again shortly.",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
"admin": "Admin",
|
| 101 |
"admin_desc": "This section is for authorized use only.",
|
| 102 |
"backend_url": "Service URL",
|
| 103 |
"use_data_uri": "Private upload",
|
| 104 |
-
"warmup": "Warm up
|
| 105 |
"auto_retry": "Auto-retry",
|
| 106 |
"max_wait": "Max wait (sec)",
|
| 107 |
"show_debug": "Show debug info",
|
| 108 |
},
|
| 109 |
}
|
| 110 |
|
| 111 |
-
#
|
| 112 |
-
if "lang" not in st.session_state:
|
| 113 |
-
|
| 114 |
-
if "
|
| 115 |
-
|
| 116 |
-
if "
|
| 117 |
-
|
| 118 |
-
if "
|
| 119 |
-
|
| 120 |
-
if "
|
| 121 |
-
st.session_state.warmup = True
|
| 122 |
-
if "auto_retry" not in st.session_state:
|
| 123 |
-
st.session_state.auto_retry = True
|
| 124 |
-
if "max_wait" not in st.session_state:
|
| 125 |
-
st.session_state.max_wait = 60
|
| 126 |
-
if "show_debug" not in st.session_state:
|
| 127 |
-
st.session_state.show_debug = False
|
| 128 |
-
if "prompt_override" not in st.session_state:
|
| 129 |
-
st.session_state.prompt_override = "" # UI’da gösterilmez; sadece admin
|
| 130 |
|
| 131 |
T = LANGS[st.session_state.lang]
|
| 132 |
|
| 133 |
-
#
|
| 134 |
-
|
| 135 |
-
with
|
| 136 |
st.title(T["title"])
|
| 137 |
-
with
|
| 138 |
st.session_state.lang = st.selectbox(
|
| 139 |
"Language / Dil", ["tr", "en"],
|
| 140 |
index=(0 if st.session_state.lang == "tr" else 1),
|
|
@@ -142,7 +144,7 @@ with c2:
|
|
| 142 |
T = LANGS[st.session_state.lang]
|
| 143 |
st.caption(T["about"])
|
| 144 |
|
| 145 |
-
#
|
| 146 |
if DEV_MODE:
|
| 147 |
with st.expander(f"🛠 {T['admin']}", expanded=False):
|
| 148 |
st.write(T["admin_desc"])
|
|
@@ -152,24 +154,31 @@ if DEV_MODE:
|
|
| 152 |
st.session_state.auto_retry = st.checkbox(T["auto_retry"], value=st.session_state.auto_retry)
|
| 153 |
st.session_state.max_wait = st.slider(T["max_wait"], 10, 240, st.session_state.max_wait, step=10)
|
| 154 |
st.session_state.show_debug = st.checkbox(T["show_debug"], value=st.session_state.show_debug)
|
| 155 |
-
# İsteğe bağlı gelişmiş prompt (son kullanıcıya görünmez)
|
| 156 |
st.session_state.prompt_override = st.text_area("Advanced prompt (optional)", value=st.session_state.prompt_override, height=120)
|
| 157 |
|
| 158 |
st.markdown("---")
|
| 159 |
|
| 160 |
-
#
|
| 161 |
st.write(T["upload_hint"])
|
| 162 |
file = st.file_uploader(T["upload"], type=["mp3", "wav", "ogg"])
|
| 163 |
st.write(f"**{T['or']}**")
|
| 164 |
audio_url = st.text_input(T["paste_url"], value="", placeholder="https://...")
|
| 165 |
|
| 166 |
-
#
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
def build_session(auto_retry: bool) -> requests.Session:
|
| 174 |
s = requests.Session()
|
| 175 |
s.headers.update(HEADERS)
|
|
@@ -186,39 +195,42 @@ def build_session(auto_retry: bool) -> requests.Session:
|
|
| 186 |
s.mount("https://", adapter)
|
| 187 |
return s
|
| 188 |
|
| 189 |
-
def warm_up(sess: requests.Session, base: str,
|
| 190 |
start = time.time()
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
|
| 203 |
def fetch_bytes(sess: requests.Session, base: str, url: str) -> bytes:
|
| 204 |
full = url if url.startswith("http") else f"{base.rstrip('/')}{url}"
|
| 205 |
-
r = sess.get(full, timeout=
|
| 206 |
r.raise_for_status()
|
| 207 |
return r.content
|
| 208 |
|
| 209 |
-
def post_analyze(sess: requests.Session, base: str, _file, _url: str) -> dict:
|
| 210 |
data = {
|
| 211 |
"use_data_uri": "true" if st.session_state.use_data_uri else "false",
|
| 212 |
"lang": st.session_state.lang,
|
| 213 |
}
|
| 214 |
-
# prompt_override yalnızca admin kullanımında backend’e gönderilir
|
| 215 |
if DEV_MODE and st.session_state.prompt_override.strip():
|
| 216 |
data["prompt"] = st.session_state.prompt_override.strip()
|
| 217 |
|
| 218 |
files = None
|
| 219 |
if _file is not None:
|
|
|
|
|
|
|
|
|
|
| 220 |
mime = getattr(_file, "type", None) or "application/octet-stream"
|
| 221 |
-
files = {"file": (_file.name,
|
|
|
|
| 222 |
elif _url.strip():
|
| 223 |
data["audio_url"] = _url.strip()
|
| 224 |
|
|
@@ -227,28 +239,30 @@ def post_analyze(sess: requests.Session, base: str, _file, _url: str) -> dict:
|
|
| 227 |
|
| 228 |
url = f"{base.rstrip('/')}/analyze"
|
| 229 |
deadline = time.time() + st.session_state.max_wait
|
|
|
|
| 230 |
while True:
|
| 231 |
try:
|
| 232 |
-
|
|
|
|
|
|
|
| 233 |
resp.raise_for_status()
|
|
|
|
| 234 |
return resp.json()
|
| 235 |
except Exception as e:
|
| 236 |
-
|
| 237 |
-
|
|
|
|
| 238 |
time.sleep(1.2)
|
| 239 |
|
| 240 |
-
#
|
| 241 |
sess = build_session(st.session_state.auto_retry)
|
| 242 |
|
| 243 |
if health:
|
| 244 |
try:
|
| 245 |
r = sess.get(f"{st.session_state.api_base.rstrip('/')}/health", timeout=10)
|
| 246 |
-
if r.ok
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
st.code(json.dumps(r.json(), ensure_ascii=False, indent=2), language="json")
|
| 250 |
-
else:
|
| 251 |
-
st.error(T["health_fail"])
|
| 252 |
except Exception:
|
| 253 |
st.error(T["health_fail"])
|
| 254 |
|
|
@@ -256,18 +270,45 @@ if reset:
|
|
| 256 |
st.session_state.res = None
|
| 257 |
st.rerun()
|
| 258 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
if go:
|
| 260 |
if not st.session_state.api_base:
|
| 261 |
st.error(T["need_backend"])
|
| 262 |
elif not file and not audio_url.strip():
|
| 263 |
st.error(T["need_input"])
|
| 264 |
else:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
if st.session_state.warmup:
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
|
| 270 |
-
#
|
| 271 |
res = st.session_state.res
|
| 272 |
if res:
|
| 273 |
if not res.get("ok", True) and res.get("error"):
|
|
@@ -279,45 +320,44 @@ if res:
|
|
| 279 |
else:
|
| 280 |
st.subheader(T["result"])
|
| 281 |
|
| 282 |
-
#
|
| 283 |
comm = (res.get("commentary") or "").strip()
|
| 284 |
if comm:
|
| 285 |
st.markdown(f"**{T['commentary']}**")
|
| 286 |
st.write(comm)
|
| 287 |
|
| 288 |
-
#
|
| 289 |
parsed = res.get("fal_parsed")
|
| 290 |
if parsed:
|
| 291 |
st.markdown(f"**{T['json']}**")
|
| 292 |
st.json(parsed)
|
| 293 |
|
| 294 |
-
#
|
| 295 |
dls = res.get("downloads") or {}
|
| 296 |
if any(dls.get(k) for k in ("summary_txt", "fal_parsed_json", "fal_raw_txt")):
|
| 297 |
st.markdown(f"**{T['artifacts']}**")
|
| 298 |
-
|
| 299 |
|
| 300 |
if dls.get("summary_txt"):
|
| 301 |
try:
|
| 302 |
data = fetch_bytes(sess, st.session_state.api_base, dls["summary_txt"])
|
| 303 |
-
|
| 304 |
except Exception:
|
| 305 |
-
|
| 306 |
|
| 307 |
if dls.get("fal_parsed_json"):
|
| 308 |
try:
|
| 309 |
data = fetch_bytes(sess, st.session_state.api_base, dls["fal_parsed_json"])
|
| 310 |
-
|
| 311 |
except Exception:
|
| 312 |
-
|
| 313 |
|
| 314 |
if dls.get("fal_raw_txt"):
|
| 315 |
-
# “Ham metin” geliştirici içindir; kullanıcı isterse indirebilir.
|
| 316 |
try:
|
| 317 |
data = fetch_bytes(sess, st.session_state.api_base, dls["fal_raw_txt"])
|
| 318 |
-
|
| 319 |
except Exception:
|
| 320 |
-
|
| 321 |
|
| 322 |
-
#
|
| 323 |
st.caption("© 2025 Diag Studio — Araç Motor Ses Analizi")
|
|
|
|
| 1 |
# app.py — Diag Studio (Customer UI)
|
| 2 |
+
# AI Destekli Araç Motor Ses Analizi — sade ticari arayüz
|
| 3 |
+
# İstekler: Ses oynatıcı, ilerleme çubuğu, kullanıcı dostu akış.
|
| 4 |
+
# Teknik sağlayıcı/kitaplık adları ve loglar UI’da gösterilmez.
|
| 5 |
+
# Yönetici ayarları yalnızca DEV_MODE=1 iken görünür.
|
| 6 |
|
| 7 |
import os
|
| 8 |
+
import io
|
| 9 |
import json
|
| 10 |
import time
|
| 11 |
import requests
|
|
|
|
| 13 |
from requests.adapters import HTTPAdapter
|
| 14 |
from urllib3.util.retry import Retry
|
| 15 |
|
| 16 |
+
# ---------------- Safe defaults ----------------
|
| 17 |
os.environ.setdefault("HOME", "/tmp")
|
| 18 |
os.environ.setdefault("STREAMLIT_BROWSER_GATHER_USAGE_STATS", "false")
|
| 19 |
os.environ.setdefault("STREAMLIT_SERVER_HEADLESS", "true")
|
| 20 |
|
| 21 |
+
# ---------------- Theme ----------------
|
| 22 |
try:
|
| 23 |
st._config.set_option("theme.base", "dark")
|
| 24 |
st._config.set_option("theme.primaryColor", "#ff7a1a")
|
|
|
|
| 28 |
except Exception:
|
| 29 |
pass
|
| 30 |
|
| 31 |
+
# ---------------- Env helpers ----------------
|
| 32 |
def _env(k: str) -> str:
|
| 33 |
return (os.getenv(k) or "").strip().strip("'\"")
|
| 34 |
|
| 35 |
DEV_MODE = (_env("DEV_MODE") in {"1", "true", "yes", "on"})
|
| 36 |
+
API_BASE_DEFAULT = (_env("CARDIAG_AI_API") or _env("APP_API") or "http://127.0.0.1:7860").rstrip("/")
|
|
|
|
|
|
|
|
|
|
| 37 |
TOKEN = _env("CARDIAG_AI_TOKEN") or _env("APP_TOKEN")
|
| 38 |
HEADERS = {"Authorization": f"Bearer {TOKEN}"} if TOKEN else {}
|
| 39 |
|
| 40 |
+
# ---------------- i18n ----------------
|
| 41 |
LANGS = {
|
| 42 |
"tr": {
|
| 43 |
"title": "AI Destekli Araç Motor Ses Analizi",
|
|
|
|
| 47 |
"paste_url": "Ses bağlantısı (opsiyonel)",
|
| 48 |
"analyze": "Analiz Et",
|
| 49 |
"reset": "Sıfırla",
|
| 50 |
+
"preview": "Önizleme",
|
| 51 |
"result": "Sonuç",
|
| 52 |
"commentary": "Özet ve Öneriler",
|
| 53 |
"json": "Detaylı Bulgular",
|
|
|
|
| 55 |
"download_summary": "Özet indir",
|
| 56 |
"download_parsed": "Detay indir (JSON)",
|
| 57 |
"download_raw": "Ham metin indir",
|
| 58 |
+
"upload_hint": "mp3 / wav / ogg önerilir. İsterseniz herkese açık bir bağlantı da girebilirsiniz.",
|
| 59 |
"warming": "Hazırlanıyor…",
|
| 60 |
"analyzing": "Analiz ediliyor…",
|
| 61 |
"health_ok": "Sistem hazır.",
|
|
|
|
| 63 |
"need_backend": "Sistem adresi yapılandırılmadı.",
|
| 64 |
"need_input": "Lütfen bir dosya yükleyin veya bağlantı girin.",
|
| 65 |
"error_generic": "İşlem başarısız. Lütfen tekrar deneyin.",
|
| 66 |
+
"try_again": "Yoğunluk olabilir. Biraz sonra tekrar deneyin.",
|
| 67 |
+
"timeline_title": "İşlem Durumu",
|
| 68 |
+
"step_prepare": "Hazırlık",
|
| 69 |
+
"step_upload": "Yükleme",
|
| 70 |
+
"step_process": "Analiz",
|
| 71 |
+
"step_summarize": "Özet oluşturma",
|
| 72 |
"admin": "Yönetici",
|
| 73 |
"admin_desc": "Bu bölüm yalnızca yetkili kullanım içindir.",
|
| 74 |
"backend_url": "Sistem adresi",
|
| 75 |
+
"use_data_uri": "Gizli yükleme",
|
| 76 |
+
"warmup": "Ön ısıtma",
|
| 77 |
"auto_retry": "Otomatik yeniden dene",
|
| 78 |
"max_wait": "Azami bekleme (sn)",
|
| 79 |
+
"show_debug": "Hata ayıklama bilgileri",
|
| 80 |
},
|
| 81 |
"en": {
|
| 82 |
"title": "AI-Powered Engine Sound Analysis",
|
|
|
|
| 86 |
"paste_url": "Audio URL (optional)",
|
| 87 |
"analyze": "Analyze",
|
| 88 |
"reset": "Reset",
|
| 89 |
+
"preview": "Preview",
|
| 90 |
"result": "Result",
|
| 91 |
"commentary": "Summary & Recommendations",
|
| 92 |
"json": "Detailed Findings",
|
|
|
|
| 94 |
"download_summary": "Download summary",
|
| 95 |
"download_parsed": "Download details (JSON)",
|
| 96 |
"download_raw": "Download raw text",
|
| 97 |
+
"upload_hint": "mp3 / wav / ogg recommended. You may paste a public URL instead of uploading.",
|
| 98 |
"warming": "Preparing…",
|
| 99 |
"analyzing": "Analyzing…",
|
| 100 |
"health_ok": "Service is ready.",
|
|
|
|
| 103 |
"need_input": "Please upload a file or enter a URL.",
|
| 104 |
"error_generic": "Request failed. Please try again.",
|
| 105 |
"try_again": "Service may be busy. Try again shortly.",
|
| 106 |
+
"timeline_title": "Progress",
|
| 107 |
+
"step_prepare": "Preparing",
|
| 108 |
+
"step_upload": "Uploading",
|
| 109 |
+
"step_process": "Processing",
|
| 110 |
+
"step_summarize": "Summarizing",
|
| 111 |
"admin": "Admin",
|
| 112 |
"admin_desc": "This section is for authorized use only.",
|
| 113 |
"backend_url": "Service URL",
|
| 114 |
"use_data_uri": "Private upload",
|
| 115 |
+
"warmup": "Warm up",
|
| 116 |
"auto_retry": "Auto-retry",
|
| 117 |
"max_wait": "Max wait (sec)",
|
| 118 |
"show_debug": "Show debug info",
|
| 119 |
},
|
| 120 |
}
|
| 121 |
|
| 122 |
+
# ---------------- Session ----------------
|
| 123 |
+
if "lang" not in st.session_state: st.session_state.lang = "tr"
|
| 124 |
+
if "api_base" not in st.session_state: st.session_state.api_base = API_BASE_DEFAULT
|
| 125 |
+
if "use_data_uri" not in st.session_state: st.session_state.use_data_uri = True
|
| 126 |
+
if "warmup" not in st.session_state: st.session_state.warmup = True
|
| 127 |
+
if "auto_retry" not in st.session_state: st.session_state.auto_retry = True
|
| 128 |
+
if "max_wait" not in st.session_state: st.session_state.max_wait = 60
|
| 129 |
+
if "show_debug" not in st.session_state: st.session_state.show_debug = False
|
| 130 |
+
if "prompt_override" not in st.session_state: st.session_state.prompt_override = ""
|
| 131 |
+
if "res" not in st.session_state: st.session_state.res = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
T = LANGS[st.session_state.lang]
|
| 134 |
|
| 135 |
+
# ---------------- Header ----------------
|
| 136 |
+
left, right = st.columns([0.7, 0.3])
|
| 137 |
+
with left:
|
| 138 |
st.title(T["title"])
|
| 139 |
+
with right:
|
| 140 |
st.session_state.lang = st.selectbox(
|
| 141 |
"Language / Dil", ["tr", "en"],
|
| 142 |
index=(0 if st.session_state.lang == "tr" else 1),
|
|
|
|
| 144 |
T = LANGS[st.session_state.lang]
|
| 145 |
st.caption(T["about"])
|
| 146 |
|
| 147 |
+
# ---------------- Admin (DEV only) ----------------
|
| 148 |
if DEV_MODE:
|
| 149 |
with st.expander(f"🛠 {T['admin']}", expanded=False):
|
| 150 |
st.write(T["admin_desc"])
|
|
|
|
| 154 |
st.session_state.auto_retry = st.checkbox(T["auto_retry"], value=st.session_state.auto_retry)
|
| 155 |
st.session_state.max_wait = st.slider(T["max_wait"], 10, 240, st.session_state.max_wait, step=10)
|
| 156 |
st.session_state.show_debug = st.checkbox(T["show_debug"], value=st.session_state.show_debug)
|
|
|
|
| 157 |
st.session_state.prompt_override = st.text_area("Advanced prompt (optional)", value=st.session_state.prompt_override, height=120)
|
| 158 |
|
| 159 |
st.markdown("---")
|
| 160 |
|
| 161 |
+
# ---------------- Upload / URL ----------------
|
| 162 |
st.write(T["upload_hint"])
|
| 163 |
file = st.file_uploader(T["upload"], type=["mp3", "wav", "ogg"])
|
| 164 |
st.write(f"**{T['or']}**")
|
| 165 |
audio_url = st.text_input(T["paste_url"], value="", placeholder="https://...")
|
| 166 |
|
| 167 |
+
# Audio preview block (player)
|
| 168 |
+
if file is not None:
|
| 169 |
+
st.markdown(f"**{T['preview']}**")
|
| 170 |
+
st.audio(file, format=file.type or "audio/mpeg")
|
| 171 |
+
elif audio_url.strip():
|
| 172 |
+
st.markdown(f"**{T['preview']}**")
|
| 173 |
+
st.audio(audio_url)
|
| 174 |
+
|
| 175 |
+
# ---------------- Controls ----------------
|
| 176 |
+
c1, c2, c3 = st.columns([0.22, 0.22, 0.56])
|
| 177 |
+
go = c1.button(f"▶ {T['analyze']}", type="primary")
|
| 178 |
+
reset = c2.button(f"↩ {T['reset']}")
|
| 179 |
+
health = c3.button("🔄 Health")
|
| 180 |
+
|
| 181 |
+
# ---------------- HTTP helpers ----------------
|
| 182 |
def build_session(auto_retry: bool) -> requests.Session:
|
| 183 |
s = requests.Session()
|
| 184 |
s.headers.update(HEADERS)
|
|
|
|
| 195 |
s.mount("https://", adapter)
|
| 196 |
return s
|
| 197 |
|
| 198 |
+
def warm_up(sess: requests.Session, base: str, progress_cb=None, total_budget: int = 25):
|
| 199 |
start = time.time()
|
| 200 |
+
paths = ["/", "/health"]
|
| 201 |
+
for i, path in enumerate(paths, 1):
|
| 202 |
+
try:
|
| 203 |
+
_ = sess.get(f"{base.rstrip('/')}{path}", timeout=8)
|
| 204 |
+
except Exception:
|
| 205 |
+
pass
|
| 206 |
+
if progress_cb:
|
| 207 |
+
progress_cb(min(int(i/len(paths)*18), 18)) # 0-18%
|
| 208 |
+
if time.time() - start > total_budget:
|
| 209 |
+
break
|
| 210 |
+
time.sleep(0.4)
|
| 211 |
|
| 212 |
def fetch_bytes(sess: requests.Session, base: str, url: str) -> bytes:
|
| 213 |
full = url if url.startswith("http") else f"{base.rstrip('/')}{url}"
|
| 214 |
+
r = sess.get(full, timeout=90)
|
| 215 |
r.raise_for_status()
|
| 216 |
return r.content
|
| 217 |
|
| 218 |
+
def post_analyze(sess: requests.Session, base: str, _file, _url: str, progress_cb=None) -> dict:
|
| 219 |
data = {
|
| 220 |
"use_data_uri": "true" if st.session_state.use_data_uri else "false",
|
| 221 |
"lang": st.session_state.lang,
|
| 222 |
}
|
|
|
|
| 223 |
if DEV_MODE and st.session_state.prompt_override.strip():
|
| 224 |
data["prompt"] = st.session_state.prompt_override.strip()
|
| 225 |
|
| 226 |
files = None
|
| 227 |
if _file is not None:
|
| 228 |
+
# read into BytesIO for repeatable upload
|
| 229 |
+
content = _file.read()
|
| 230 |
+
buf = io.BytesIO(content)
|
| 231 |
mime = getattr(_file, "type", None) or "application/octet-stream"
|
| 232 |
+
files = {"file": (_file.name, buf, mime)}
|
| 233 |
+
if progress_cb: progress_cb(30) # after upload prep
|
| 234 |
elif _url.strip():
|
| 235 |
data["audio_url"] = _url.strip()
|
| 236 |
|
|
|
|
| 239 |
|
| 240 |
url = f"{base.rstrip('/')}/analyze"
|
| 241 |
deadline = time.time() + st.session_state.max_wait
|
| 242 |
+
last_err = None
|
| 243 |
while True:
|
| 244 |
try:
|
| 245 |
+
if progress_cb: progress_cb(35) # request start
|
| 246 |
+
resp = sess.post(url, data=data, files=files, timeout=240)
|
| 247 |
+
if progress_cb: progress_cb(78) # server responded
|
| 248 |
resp.raise_for_status()
|
| 249 |
+
if progress_cb: progress_cb(92) # parse
|
| 250 |
return resp.json()
|
| 251 |
except Exception as e:
|
| 252 |
+
last_err = e
|
| 253 |
+
if time.time() > deadline or not st.session_state.auto_retry:
|
| 254 |
+
return {"ok": False, "error": f"{T['error_generic']} ({last_err})"}
|
| 255 |
time.sleep(1.2)
|
| 256 |
|
| 257 |
+
# ---------------- Actions ----------------
|
| 258 |
sess = build_session(st.session_state.auto_retry)
|
| 259 |
|
| 260 |
if health:
|
| 261 |
try:
|
| 262 |
r = sess.get(f"{st.session_state.api_base.rstrip('/')}/health", timeout=10)
|
| 263 |
+
st.success(T["health_ok"] if r.ok else T["health_fail"])
|
| 264 |
+
if DEV_MODE and r.ok:
|
| 265 |
+
st.code(json.dumps(r.json(), ensure_ascii=False, indent=2), language="json")
|
|
|
|
|
|
|
|
|
|
| 266 |
except Exception:
|
| 267 |
st.error(T["health_fail"])
|
| 268 |
|
|
|
|
| 270 |
st.session_state.res = None
|
| 271 |
st.rerun()
|
| 272 |
|
| 273 |
+
# Progress widgets (player stays above)
|
| 274 |
+
progress_box = st.container()
|
| 275 |
+
timeline = progress_box.expander(T["timeline_title"], expanded=True)
|
| 276 |
+
p_text = progress_box.empty()
|
| 277 |
+
p_bar = progress_box.progress(0)
|
| 278 |
+
|
| 279 |
+
def progress_update(pct: int, label: str = ""):
|
| 280 |
+
p_bar.progress(min(max(pct, 0), 100))
|
| 281 |
+
if label:
|
| 282 |
+
p_text.write(label)
|
| 283 |
+
|
| 284 |
if go:
|
| 285 |
if not st.session_state.api_base:
|
| 286 |
st.error(T["need_backend"])
|
| 287 |
elif not file and not audio_url.strip():
|
| 288 |
st.error(T["need_input"])
|
| 289 |
else:
|
| 290 |
+
# timeline
|
| 291 |
+
with timeline:
|
| 292 |
+
st.write(f"1) {T['step_prepare']}")
|
| 293 |
+
st.write(f"2) {T['step_upload']}")
|
| 294 |
+
st.write(f"3) {T['step_process']}")
|
| 295 |
+
st.write(f"4) {T['step_summarize']}")
|
| 296 |
+
|
| 297 |
+
# warmup (optional)
|
| 298 |
if st.session_state.warmup:
|
| 299 |
+
progress_update(5, T["warming"])
|
| 300 |
+
warm_up(sess, st.session_state.api_base, progress_cb=lambda v: progress_update(v, T["warming"]))
|
| 301 |
+
|
| 302 |
+
# analyze
|
| 303 |
+
progress_update(22, T["analyzing"])
|
| 304 |
+
res = post_analyze(
|
| 305 |
+
sess, st.session_state.api_base, file, audio_url,
|
| 306 |
+
progress_cb=lambda v: progress_update(v, T["analyzing"])
|
| 307 |
+
)
|
| 308 |
+
st.session_state.res = res
|
| 309 |
+
progress_update(100, "")
|
| 310 |
|
| 311 |
+
# ---------------- Results ----------------
|
| 312 |
res = st.session_state.res
|
| 313 |
if res:
|
| 314 |
if not res.get("ok", True) and res.get("error"):
|
|
|
|
| 320 |
else:
|
| 321 |
st.subheader(T["result"])
|
| 322 |
|
| 323 |
+
# Summary for drivers
|
| 324 |
comm = (res.get("commentary") or "").strip()
|
| 325 |
if comm:
|
| 326 |
st.markdown(f"**{T['commentary']}**")
|
| 327 |
st.write(comm)
|
| 328 |
|
| 329 |
+
# Detailed findings (JSON). This is the structured diagnosis — no provider names.
|
| 330 |
parsed = res.get("fal_parsed")
|
| 331 |
if parsed:
|
| 332 |
st.markdown(f"**{T['json']}**")
|
| 333 |
st.json(parsed)
|
| 334 |
|
| 335 |
+
# Downloads
|
| 336 |
dls = res.get("downloads") or {}
|
| 337 |
if any(dls.get(k) for k in ("summary_txt", "fal_parsed_json", "fal_raw_txt")):
|
| 338 |
st.markdown(f"**{T['artifacts']}**")
|
| 339 |
+
col_a, col_b, col_c = st.columns(3)
|
| 340 |
|
| 341 |
if dls.get("summary_txt"):
|
| 342 |
try:
|
| 343 |
data = fetch_bytes(sess, st.session_state.api_base, dls["summary_txt"])
|
| 344 |
+
col_a.download_button(label=T["download_summary"], data=data, file_name="ozet.txt" if st.session_state.lang=="tr" else "summary.txt")
|
| 345 |
except Exception:
|
| 346 |
+
col_a.error("✖")
|
| 347 |
|
| 348 |
if dls.get("fal_parsed_json"):
|
| 349 |
try:
|
| 350 |
data = fetch_bytes(sess, st.session_state.api_base, dls["fal_parsed_json"])
|
| 351 |
+
col_b.download_button(label=T["download_parsed"], data=data, file_name="detay.json" if st.session_state.lang=="tr" else "details.json", mime="application/json")
|
| 352 |
except Exception:
|
| 353 |
+
col_b.error("✖")
|
| 354 |
|
| 355 |
if dls.get("fal_raw_txt"):
|
|
|
|
| 356 |
try:
|
| 357 |
data = fetch_bytes(sess, st.session_state.api_base, dls["fal_raw_txt"])
|
| 358 |
+
col_c.download_button(label=T["download_raw"], data=data, file_name="ham.txt" if st.session_state.lang=="tr" else "raw.txt")
|
| 359 |
except Exception:
|
| 360 |
+
col_c.error("✖")
|
| 361 |
|
| 362 |
+
# ---------------- Footer ----------------
|
| 363 |
st.caption("© 2025 Diag Studio — Araç Motor Ses Analizi")
|