Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
| 1 |
-
# app.py — DiagStudio AI (Streamlit frontend
|
| 2 |
-
#
|
| 3 |
-
#
|
| 4 |
-
#
|
| 5 |
-
#
|
| 6 |
-
#
|
| 7 |
|
| 8 |
import os, io, json, time, base64, requests, hashlib
|
|
|
|
| 9 |
os.environ.setdefault("HOME", "/tmp")
|
| 10 |
os.environ.setdefault("STREAMLIT_BROWSER_GATHER_USAGE_STATS", "false")
|
| 11 |
os.environ.setdefault("STREAMLIT_SERVER_HEADLESS", "true")
|
|
@@ -34,7 +35,7 @@ HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
|
|
| 34 |
|
| 35 |
st.set_page_config(page_title="DiagStudio AI", layout="wide")
|
| 36 |
st.title("DiagStudio AI")
|
| 37 |
-
st.caption("Upload a short engine recording and get a clear diagnosis with visuals and
|
| 38 |
|
| 39 |
# ================= Helpers =================
|
| 40 |
@st.cache_data(show_spinner=False, ttl=20)
|
|
@@ -65,7 +66,7 @@ def _post_analyze(audio_file, rpm_file, sr_target, n_fft, hop, env_band, run_llm
|
|
| 65 |
"env_band": env_band,
|
| 66 |
"run_llm": "true" if run_llm else "false",
|
| 67 |
"extra_text": notes or "",
|
| 68 |
-
# cache-bypass hint for
|
| 69 |
"nonce": str(time.time())
|
| 70 |
}
|
| 71 |
r = requests.post(f"{API_BASE}/analyze", headers=HEADERS, files=files, data=data, timeout=240)
|
|
@@ -98,7 +99,7 @@ with st.sidebar:
|
|
| 98 |
rpm_up = st.file_uploader("Optional RPM CSV (time_sec,rpm)", type=["csv"])
|
| 99 |
|
| 100 |
st.header("Options")
|
| 101 |
-
run_llm = st.toggle("Explain with AI", value=True, help="
|
| 102 |
user_notes = st.text_area("Context (optional)", placeholder="Vehicle, engine type, driving condition, symptoms…")
|
| 103 |
|
| 104 |
with st.expander("Advanced (defaults are fine)"):
|
|
@@ -114,7 +115,7 @@ with st.sidebar:
|
|
| 114 |
st.warning("Could not reach the backend. Verify your private Space URL.")
|
| 115 |
analyze_btn = st.button("Analyze now", type="primary", disabled=(audio_up is None or not ok))
|
| 116 |
|
| 117 |
-
# Clear old result
|
| 118 |
if "last_audio_id" not in st.session_state:
|
| 119 |
st.session_state["last_audio_id"] = None
|
| 120 |
if audio_up is not None:
|
|
@@ -144,26 +145,33 @@ res = st.session_state.get("last_result")
|
|
| 144 |
if res:
|
| 145 |
st.subheader("Result")
|
| 146 |
|
| 147 |
-
llm = res.get("llm_report") or {}
|
| 148 |
-
model_name = llm.get("model", "unknown")
|
| 149 |
-
usage = llm.get("usage") or {}
|
| 150 |
params = res.get("params", {})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
st.caption(f"AI model: **{model_name}**")
|
| 152 |
-
|
| 153 |
-
|
| 154 |
if params.get("audio_id"):
|
| 155 |
st.caption(f"Audio ID: **{params['audio_id']}**")
|
| 156 |
if params.get("features"):
|
| 157 |
feat = params["features"]
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
st.caption(f"Vision meta — seen_images: {meta.get('seen_images')}, views: {meta.get('views')}")
|
| 166 |
|
|
|
|
| 167 |
col1, col2 = st.columns([1, 1])
|
| 168 |
diag_list = llm.get("diagnosis", [])
|
| 169 |
notes = llm.get("notes", "")
|
|
@@ -171,10 +179,12 @@ if res:
|
|
| 171 |
|
| 172 |
with col1:
|
| 173 |
st.markdown("**Likely issues**")
|
| 174 |
-
if diag_list:
|
| 175 |
for item in diag_list:
|
| 176 |
-
|
| 177 |
-
|
|
|
|
|
|
|
| 178 |
if item.get("evidence"):
|
| 179 |
st.caption(item["evidence"])
|
| 180 |
else:
|
|
@@ -189,8 +199,15 @@ if res:
|
|
| 189 |
st.write(next_tests)
|
| 190 |
|
| 191 |
st.subheader("Visual checks")
|
| 192 |
-
tabs = st.tabs([
|
| 193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
with tabs[0]:
|
| 196 |
if imgs.get("composite"):
|
|
@@ -224,7 +241,12 @@ if res:
|
|
| 224 |
if st.button("Prepare ZIP"):
|
| 225 |
try:
|
| 226 |
zbytes = _post_analyze_zip(audio_up, rpm_up, sr_target, n_fft, hop, env_band, run_llm, user_notes)
|
| 227 |
-
st.download_button(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
except Exception as e:
|
| 229 |
st.error(f"ZIP build failed: {e}")
|
| 230 |
else:
|
|
|
|
| 1 |
+
# app.py — DiagStudio AI (Streamlit frontend)
|
| 2 |
+
# Works with the FastAPI backend you just built (single composite image + JSON mode).
|
| 3 |
+
# Configure the backend URL via:
|
| 4 |
+
# CARDIAG_AI_API (or AI_LIGHTBOX_API / LUXFIT_API)
|
| 5 |
+
# Optional auth header passthrough:
|
| 6 |
+
# CARDIAG_AI_TOKEN (or AI_LIGHTBOX_TOKEN / LUXFIT_TOKEN)
|
| 7 |
|
| 8 |
import os, io, json, time, base64, requests, hashlib
|
| 9 |
+
# Prevent /.streamlit permission issues on HF Spaces/containers
|
| 10 |
os.environ.setdefault("HOME", "/tmp")
|
| 11 |
os.environ.setdefault("STREAMLIT_BROWSER_GATHER_USAGE_STATS", "false")
|
| 12 |
os.environ.setdefault("STREAMLIT_SERVER_HEADLESS", "true")
|
|
|
|
| 35 |
|
| 36 |
st.set_page_config(page_title="DiagStudio AI", layout="wide")
|
| 37 |
st.title("DiagStudio AI")
|
| 38 |
+
st.caption("Upload a short engine recording and get a clear diagnosis with visuals and next steps.")
|
| 39 |
|
| 40 |
# ================= Helpers =================
|
| 41 |
@st.cache_data(show_spinner=False, ttl=20)
|
|
|
|
| 66 |
"env_band": env_band,
|
| 67 |
"run_llm": "true" if run_llm else "false",
|
| 68 |
"extra_text": notes or "",
|
| 69 |
+
# cache-bypass hint for intermediaries (backend already sets no-store)
|
| 70 |
"nonce": str(time.time())
|
| 71 |
}
|
| 72 |
r = requests.post(f"{API_BASE}/analyze", headers=HEADERS, files=files, data=data, timeout=240)
|
|
|
|
| 99 |
rpm_up = st.file_uploader("Optional RPM CSV (time_sec,rpm)", type=["csv"])
|
| 100 |
|
| 101 |
st.header("Options")
|
| 102 |
+
run_llm = st.toggle("Explain with AI", value=True, help="Adds an easy-to-read diagnosis and next steps.")
|
| 103 |
user_notes = st.text_area("Context (optional)", placeholder="Vehicle, engine type, driving condition, symptoms…")
|
| 104 |
|
| 105 |
with st.expander("Advanced (defaults are fine)"):
|
|
|
|
| 115 |
st.warning("Could not reach the backend. Verify your private Space URL.")
|
| 116 |
analyze_btn = st.button("Analyze now", type="primary", disabled=(audio_up is None or not ok))
|
| 117 |
|
| 118 |
+
# ================= Clear old result on file change =================
|
| 119 |
if "last_audio_id" not in st.session_state:
|
| 120 |
st.session_state["last_audio_id"] = None
|
| 121 |
if audio_up is not None:
|
|
|
|
| 145 |
if res:
|
| 146 |
st.subheader("Result")
|
| 147 |
|
|
|
|
|
|
|
|
|
|
| 148 |
params = res.get("params", {})
|
| 149 |
+
llm = res.get("llm_report") or {}
|
| 150 |
+
|
| 151 |
+
# Model name is known from backend params (JSON mode removes auto-added model field)
|
| 152 |
+
model_name = params.get("vision_model", "gpt-4o")
|
| 153 |
st.caption(f"AI model: **{model_name}**")
|
| 154 |
+
|
| 155 |
+
# Show audio hash and basic features to prove input is unique
|
| 156 |
if params.get("audio_id"):
|
| 157 |
st.caption(f"Audio ID: **{params['audio_id']}**")
|
| 158 |
if params.get("features"):
|
| 159 |
feat = params["features"]
|
| 160 |
+
try:
|
| 161 |
+
st.caption(
|
| 162 |
+
f"Features — rms: {feat.get('rms'):.4f}, zcr: {feat.get('zcr'):.4f}, "
|
| 163 |
+
f"centroid: {feat.get('centroid'):.1f} Hz, rolloff: {feat.get('rolloff'):.1f} Hz, "
|
| 164 |
+
f"flux: {feat.get('flux'):.2f}"
|
| 165 |
+
)
|
| 166 |
+
except Exception:
|
| 167 |
+
st.caption(f"Features — {feat}")
|
| 168 |
+
|
| 169 |
+
# If model returned meta, show it (proof it saw the image)
|
| 170 |
+
if isinstance(llm, dict) and llm.get("meta"):
|
| 171 |
+
meta = llm["meta"]
|
| 172 |
st.caption(f"Vision meta — seen_images: {meta.get('seen_images')}, views: {meta.get('views')}")
|
| 173 |
|
| 174 |
+
# Layout
|
| 175 |
col1, col2 = st.columns([1, 1])
|
| 176 |
diag_list = llm.get("diagnosis", [])
|
| 177 |
notes = llm.get("notes", "")
|
|
|
|
| 179 |
|
| 180 |
with col1:
|
| 181 |
st.markdown("**Likely issues**")
|
| 182 |
+
if isinstance(diag_list, list) and len(diag_list) > 0:
|
| 183 |
for item in diag_list:
|
| 184 |
+
label = str(item.get("label", "Issue"))
|
| 185 |
+
conf = _nice_conf(item.get("likelihood", 0))
|
| 186 |
+
st.text(label)
|
| 187 |
+
st.progress(conf)
|
| 188 |
if item.get("evidence"):
|
| 189 |
st.caption(item["evidence"])
|
| 190 |
else:
|
|
|
|
| 199 |
st.write(next_tests)
|
| 200 |
|
| 201 |
st.subheader("Visual checks")
|
| 202 |
+
tabs = st.tabs([
|
| 203 |
+
"Composite",
|
| 204 |
+
"Energy over time",
|
| 205 |
+
"Perceptual view",
|
| 206 |
+
"Impact pattern",
|
| 207 |
+
"Signal envelope",
|
| 208 |
+
"Order view",
|
| 209 |
+
])
|
| 210 |
+
imgs = res.get("images", {}) if isinstance(res.get("images", {}), dict) else {}
|
| 211 |
|
| 212 |
with tabs[0]:
|
| 213 |
if imgs.get("composite"):
|
|
|
|
| 241 |
if st.button("Prepare ZIP"):
|
| 242 |
try:
|
| 243 |
zbytes = _post_analyze_zip(audio_up, rpm_up, sr_target, n_fft, hop, env_band, run_llm, user_notes)
|
| 244 |
+
st.download_button(
|
| 245 |
+
"Download results (ZIP)",
|
| 246 |
+
data=zbytes,
|
| 247 |
+
file_name="diagstudio_results.zip",
|
| 248 |
+
mime="application/zip"
|
| 249 |
+
)
|
| 250 |
except Exception as e:
|
| 251 |
st.error(f"ZIP build failed: {e}")
|
| 252 |
else:
|