renderfy commited on
Commit
8c3ccf1
·
verified ·
1 Parent(s): a3b116a

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +50 -28
app.py CHANGED
@@ -1,11 +1,12 @@
1
- # app.py — DiagStudio AI (Streamlit frontend for the FastAPI backend with a single composite image)
2
- # UI is end-user friendly (English). It calls your private backend's /analyze and /analyze_zip.
3
- # Notes:
4
- # - Set CARDIAG_AI_API (or AI_LIGHTBOX_API / LUXFIT_API) to your private FastAPI base URL.
5
- # - Optional: set CARDIAG_AI_TOKEN (or AI_LIGHTBOX_TOKEN / LUXFIT_TOKEN) for auth header passthrough.
6
- # - We disable Streamlit's telemetry writes and force a writable HOME to avoid /.streamlit permission issues.
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 notes.")
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 proxies/CDNs (backend already sets no-store)
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="Get a readable diagnosis and next steps.")
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 if user picks a different file (prevents “same result” confusion)
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
- if usage:
153
- st.caption(f"Tokens prompt: {usage.get('prompt_tokens')}, completion: {usage.get('completion_tokens')}")
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
- st.caption(
159
- f"Features — rms: {feat.get('rms'):.4f}, zcr: {feat.get('zcr'):.4f}, "
160
- f"centroid: {feat.get('centroid'):.1f} Hz, rolloff: {feat.get('rolloff'):.1f} Hz, "
161
- f"flux: {feat.get('flux'):.2f}"
162
- )
163
- meta = llm.get("meta", {})
164
- if meta:
 
 
 
 
 
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
- st.text(str(item.get("label", "Issue")))
177
- st.progress(_nice_conf(item.get("likelihood", 0)))
 
 
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(["Composite", "Energy over time", "Perceptual view", "Impact pattern", "Signal envelope", "Order view"])
193
- imgs = res.get("images", {})
 
 
 
 
 
 
 
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("Download results (ZIP)", data=zbytes, file_name="diagstudio_results.zip", mime="application/zip")
 
 
 
 
 
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: