Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -1,22 +1,21 @@
|
|
| 1 |
# app.py — DiagStudio AI (Streamlit frontend)
|
| 2 |
-
# Pairs with the FastAPI backend
|
| 3 |
-
#
|
| 4 |
-
# CARDIAG_AI_API
|
| 5 |
-
# Optional bearer
|
| 6 |
# CARDIAG_AI_TOKEN | AI_LIGHTBOX_TOKEN | LUXFIT_TOKEN
|
| 7 |
#
|
| 8 |
-
# Run
|
| 9 |
# CARDIAG_AI_API="https://<your-private-backend>" streamlit run app.py --server.port 7860
|
| 10 |
|
| 11 |
-
import os,
|
|
|
|
| 12 |
|
| 13 |
-
#
|
| 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 |
-
import streamlit as st
|
| 19 |
-
|
| 20 |
# ================= Theme (no extra files) =================
|
| 21 |
try:
|
| 22 |
st._config.set_option("theme.base", "dark")
|
|
@@ -38,12 +37,13 @@ API_BASE = (
|
|
| 38 |
HF_TOKEN = _env("CARDIAG_AI_TOKEN") or _env("AI_LIGHTBOX_TOKEN") or _env("LUXFIT_TOKEN")
|
| 39 |
HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
|
| 40 |
|
|
|
|
| 41 |
st.set_page_config(page_title="DiagStudio AI", layout="wide")
|
| 42 |
st.title("DiagStudio AI")
|
| 43 |
st.caption("Upload a short engine recording and get a clear diagnosis with visuals and next steps.")
|
| 44 |
|
| 45 |
# ================= Helpers =================
|
| 46 |
-
@st.cache_data(show_spinner=False, ttl=
|
| 47 |
def _health(url: str, headers: dict) -> bool:
|
| 48 |
try:
|
| 49 |
r = requests.get(f"{url}/health", headers=headers, timeout=10)
|
|
@@ -70,7 +70,7 @@ def _post_analyze(audio_file, rpm_file, sr_target, n_fft, hop, env_band, run_llm
|
|
| 70 |
"env_band": env_band,
|
| 71 |
"run_llm": "true" if run_llm else "false",
|
| 72 |
"extra_text": notes or "",
|
| 73 |
-
"nonce": str(time.time()) # cache-bypass hint
|
| 74 |
}
|
| 75 |
r = requests.post(f"{API_BASE}/analyze", headers=HEADERS, files=files, data=data, timeout=240)
|
| 76 |
r.raise_for_status()
|
|
@@ -111,14 +111,17 @@ with st.sidebar:
|
|
| 111 |
hop = st.select_slider("Hop length", options=[128, 256, 512, 1024], value=512)
|
| 112 |
env_band = st.text_input("Impact band (Hz low,high or 'auto')", value="auto")
|
| 113 |
|
|
|
|
| 114 |
if not API_BASE:
|
| 115 |
-
st.
|
| 116 |
ok = _health(API_BASE, HEADERS) if API_BASE else False
|
| 117 |
-
if not
|
| 118 |
-
st.
|
|
|
|
|
|
|
| 119 |
analyze_btn = st.button("Analyze now", type="primary", disabled=(audio_up is None or not ok))
|
| 120 |
|
| 121 |
-
# ================= Reset state on file change
|
| 122 |
if "last_audio_id" not in st.session_state:
|
| 123 |
st.session_state["last_audio_id"] = None
|
| 124 |
if audio_up is not None:
|
|
@@ -150,11 +153,9 @@ if res:
|
|
| 150 |
|
| 151 |
params = res.get("params", {})
|
| 152 |
llm = res.get("llm_report") or {}
|
|
|
|
| 153 |
|
| 154 |
-
|
| 155 |
-
st.caption(f"AI model: **{model_name}**")
|
| 156 |
-
|
| 157 |
-
# Input uniqueness proof + quick features
|
| 158 |
if params.get("audio_id"):
|
| 159 |
st.caption(f"Audio ID: **{params['audio_id']}**")
|
| 160 |
if params.get("features"):
|
|
@@ -168,7 +169,6 @@ if res:
|
|
| 168 |
except Exception:
|
| 169 |
st.caption(f"Features — {feat}")
|
| 170 |
|
| 171 |
-
# Show meta proving Vision read the image
|
| 172 |
if isinstance(llm, dict) and llm.get("meta"):
|
| 173 |
meta = llm["meta"]
|
| 174 |
st.caption(f"Vision meta — seen_images: {meta.get('seen_images')}, views: {meta.get('views')}")
|
|
@@ -208,13 +208,10 @@ if res:
|
|
| 208 |
"Signal envelope",
|
| 209 |
"Order view",
|
| 210 |
])
|
| 211 |
-
imgs = res.get("images", {}) if isinstance(res.get("images", {}), dict) else {}
|
| 212 |
|
| 213 |
with tabs[0]:
|
| 214 |
-
if imgs.get("composite"):
|
| 215 |
-
|
| 216 |
-
else:
|
| 217 |
-
st.caption("Composite image is not available.")
|
| 218 |
|
| 219 |
with tabs[1]:
|
| 220 |
if imgs.get("stft"): st.image(imgs["stft"], caption="Energy distribution across time.")
|
|
@@ -251,4 +248,4 @@ if res:
|
|
| 251 |
except Exception as e:
|
| 252 |
st.error(f"ZIP build failed: {e}")
|
| 253 |
else:
|
| 254 |
-
st.info("Upload an engine recording, optionally add an RPM CSV, then click Analyze.")
|
|
|
|
| 1 |
# app.py — DiagStudio AI (Streamlit frontend)
|
| 2 |
+
# Pairs with the FastAPI backend (composite image + JSON mode).
|
| 3 |
+
# Backend URL via one of:
|
| 4 |
+
# CARDIAG_AI_API | AI_LIGHTBOX_API | LUXFIT_API
|
| 5 |
+
# Optional bearer token passthrough:
|
| 6 |
# CARDIAG_AI_TOKEN | AI_LIGHTBOX_TOKEN | LUXFIT_TOKEN
|
| 7 |
#
|
| 8 |
+
# Run:
|
| 9 |
# CARDIAG_AI_API="https://<your-private-backend>" streamlit run app.py --server.port 7860
|
| 10 |
|
| 11 |
+
import os, json, time, hashlib, requests, io, base64
|
| 12 |
+
import streamlit as st
|
| 13 |
|
| 14 |
+
# ---- container-safe defaults to avoid '/.streamlit' permission errors
|
| 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 |
# ================= Theme (no extra files) =================
|
| 20 |
try:
|
| 21 |
st._config.set_option("theme.base", "dark")
|
|
|
|
| 37 |
HF_TOKEN = _env("CARDIAG_AI_TOKEN") or _env("AI_LIGHTBOX_TOKEN") or _env("LUXFIT_TOKEN")
|
| 38 |
HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
|
| 39 |
|
| 40 |
+
# ================= Page =================
|
| 41 |
st.set_page_config(page_title="DiagStudio AI", layout="wide")
|
| 42 |
st.title("DiagStudio AI")
|
| 43 |
st.caption("Upload a short engine recording and get a clear diagnosis with visuals and next steps.")
|
| 44 |
|
| 45 |
# ================= Helpers =================
|
| 46 |
+
@st.cache_data(show_spinner=False, ttl=20)
|
| 47 |
def _health(url: str, headers: dict) -> bool:
|
| 48 |
try:
|
| 49 |
r = requests.get(f"{url}/health", headers=headers, timeout=10)
|
|
|
|
| 70 |
"env_band": env_band,
|
| 71 |
"run_llm": "true" if run_llm else "false",
|
| 72 |
"extra_text": notes or "",
|
| 73 |
+
"nonce": str(time.time()) # cache-bypass hint (backend already sets no-store)
|
| 74 |
}
|
| 75 |
r = requests.post(f"{API_BASE}/analyze", headers=HEADERS, files=files, data=data, timeout=240)
|
| 76 |
r.raise_for_status()
|
|
|
|
| 111 |
hop = st.select_slider("Hop length", options=[128, 256, 512, 1024], value=512)
|
| 112 |
env_band = st.text_input("Impact band (Hz low,high or 'auto')", value="auto")
|
| 113 |
|
| 114 |
+
# Allow manual URL override if env not set
|
| 115 |
if not API_BASE:
|
| 116 |
+
API_BASE = st.text_input("Backend URL", value="", placeholder="https://<your-private-backend>").strip().rstrip("/")
|
| 117 |
ok = _health(API_BASE, HEADERS) if API_BASE else False
|
| 118 |
+
if not API_BASE:
|
| 119 |
+
st.error("Backend URL missing. Set CARDIAG_AI_API env or enter it above.")
|
| 120 |
+
elif not ok:
|
| 121 |
+
st.warning("Backend not reachable. Check URL or token.")
|
| 122 |
analyze_btn = st.button("Analyze now", type="primary", disabled=(audio_up is None or not ok))
|
| 123 |
|
| 124 |
+
# ================= Reset state on file change =================
|
| 125 |
if "last_audio_id" not in st.session_state:
|
| 126 |
st.session_state["last_audio_id"] = None
|
| 127 |
if audio_up is not None:
|
|
|
|
| 153 |
|
| 154 |
params = res.get("params", {})
|
| 155 |
llm = res.get("llm_report") or {}
|
| 156 |
+
imgs = res.get("images", {}) if isinstance(res.get("images", {}), dict) else {}
|
| 157 |
|
| 158 |
+
st.caption(f"AI model: **{params.get('vision_model','gpt-4o')}**")
|
|
|
|
|
|
|
|
|
|
| 159 |
if params.get("audio_id"):
|
| 160 |
st.caption(f"Audio ID: **{params['audio_id']}**")
|
| 161 |
if params.get("features"):
|
|
|
|
| 169 |
except Exception:
|
| 170 |
st.caption(f"Features — {feat}")
|
| 171 |
|
|
|
|
| 172 |
if isinstance(llm, dict) and llm.get("meta"):
|
| 173 |
meta = llm["meta"]
|
| 174 |
st.caption(f"Vision meta — seen_images: {meta.get('seen_images')}, views: {meta.get('views')}")
|
|
|
|
| 208 |
"Signal envelope",
|
| 209 |
"Order view",
|
| 210 |
])
|
|
|
|
| 211 |
|
| 212 |
with tabs[0]:
|
| 213 |
+
if imgs.get("composite"): st.image(imgs["composite"], caption="Composite diagnostic figure")
|
| 214 |
+
else: st.caption("Composite image is not available.")
|
|
|
|
|
|
|
| 215 |
|
| 216 |
with tabs[1]:
|
| 217 |
if imgs.get("stft"): st.image(imgs["stft"], caption="Energy distribution across time.")
|
|
|
|
| 248 |
except Exception as e:
|
| 249 |
st.error(f"ZIP build failed: {e}")
|
| 250 |
else:
|
| 251 |
+
st.info("Upload an engine recording, optionally add an RPM CSV, then click Analyze.")
|