Sbboss commited on
Commit
34c53e0
·
1 Parent(s): 5d36f24

Improve Streamlit sidebar collapse and processing state flow

Browse files
Files changed (1) hide show
  1. app.py +105 -22
app.py CHANGED
@@ -15,6 +15,7 @@ if str(_PROJECT_ROOT) not in sys.path:
15
 
16
  import numpy as np
17
  import streamlit as st
 
18
 
19
  from photo_editor.config import get_settings
20
  from photo_editor.images import dng_to_rgb
@@ -46,9 +47,66 @@ def _load_original_for_display(image_path: Path):
46
  return str(path)
47
 
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  def main() -> None:
51
  st.set_page_config(page_title="LumiGrade AI", page_icon="📷", layout="wide")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  st.markdown(
53
  """
54
  <style>
@@ -120,6 +178,9 @@ h1 {
120
  [data-testid="stSidebar"] [data-testid="stSidebarContent"] {
121
  padding-top: 0 !important;
122
  }
 
 
 
123
  </style>
124
  """,
125
  unsafe_allow_html=True,
@@ -127,6 +188,10 @@ h1 {
127
 
128
  st.title("📷 LumiGrade AI")
129
  st.caption("Upload an image to get expert-informed edit recommendations and an instant enhanced result.")
 
 
 
 
130
 
131
  # Config check
132
  s = get_settings()
@@ -142,7 +207,7 @@ h1 {
142
  # control and wire it to this flag.
143
  use_editing_api = False
144
 
145
- image_path = None
146
 
147
  with st.sidebar:
148
  # Reliable spacing so only the Pipeline Inputs card moves down.
@@ -152,7 +217,7 @@ h1 {
152
  uploaded = st.file_uploader(
153
  "Upload JPEG, PNG, DNG, HEIC, or HEIF",
154
  type=["jpg", "jpeg", "png", "dng", "heic", "heif"],
155
- help="You can upload DNG/HEIC directly, or use a local path below.",
156
  )
157
  if uploaded is not None:
158
  suffix = Path(uploaded.name).suffix.lower()
@@ -168,25 +233,30 @@ h1 {
168
  target = _STREAMLIT_INPUT_JPG_PATH
169
  target.write_bytes(uploaded.getvalue())
170
  image_path = target
171
- else:
172
- path_str = st.text_input(
173
- "Or enter path to image (e.g. for DNG)",
174
- placeholder="/path/to/image.dng or image.jpg",
175
- )
176
- path_str = (path_str or "").strip()
177
- if path_str:
178
- p = Path(path_str)
179
- if p.exists():
180
- image_path = p
181
- else:
182
- st.warning("File not found. Use a full path that exists, or upload a file.")
183
-
184
- run_clicked = st.button("▶ Run Pipeline", type="primary", use_container_width=True)
185
  status = st.empty()
186
  if image_path is None:
187
  status.info("Provide an image to run.")
188
 
189
- if run_clicked and image_path is not None:
 
 
 
 
 
 
 
 
 
 
 
190
  loading_box = st.empty()
191
 
192
  def _render_loading(current_stage: str, state: str = "running") -> None:
@@ -264,6 +334,19 @@ h1 {
264
  st.session_state.pop("pipeline_input_path", None)
265
  st.session_state.pop("pipeline_current_params", None)
266
  _render_loading("consulting", "failed")
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
  with st.container(border=True):
269
  st.subheader("Results Dashboard")
@@ -271,9 +354,9 @@ h1 {
271
 
272
  # Show result if available
273
  if (
274
- image_path is not None
275
  and st.session_state.get("pipeline_result")
276
- and st.session_state.get("pipeline_input_path") == str(image_path)
277
  ):
278
  result = st.session_state["pipeline_result"]
279
  out_path = st.session_state["pipeline_output_path"]
@@ -331,9 +414,9 @@ h1 {
331
 
332
  # Keep this full-width and at the bottom, per request.
333
  if (
334
- image_path is not None
335
  and st.session_state.get("pipeline_result")
336
- and st.session_state.get("pipeline_input_path") == str(image_path)
337
  ):
338
  result = st.session_state["pipeline_result"]
339
  out_path = st.session_state["pipeline_output_path"]
@@ -342,7 +425,7 @@ h1 {
342
  st.subheader("Original vs Result")
343
  col_orig, col_result = st.columns(2)
344
  with col_orig:
345
- st.image(_load_original_for_display(image_path), caption="Original", use_container_width=True)
346
  with col_result:
347
  st.image(str(out_path), caption="Edited", use_container_width=True)
348
 
 
15
 
16
  import numpy as np
17
  import streamlit as st
18
+ import streamlit.components.v1 as components
19
 
20
  from photo_editor.config import get_settings
21
  from photo_editor.images import dng_to_rgb
 
47
  return str(path)
48
 
49
 
50
+ def _collapse_sidebar() -> None:
51
+ """Collapse Streamlit sidebar via whichever toggle exists in this version."""
52
+ components.html(
53
+ """
54
+ <script>
55
+ const doc = window.parent.document;
56
+
57
+ function collapseIfOpen() {
58
+ // Newer Streamlit versions expose a dedicated collapse button when sidebar is open.
59
+ const closeBtn =
60
+ doc.querySelector('[data-testid="stSidebarCollapseButton"]') ||
61
+ doc.querySelector('button[aria-label="Close sidebar"]');
62
+ if (closeBtn) {
63
+ closeBtn.click();
64
+ return true;
65
+ }
66
+ return false;
67
+ }
68
+
69
+ // Try immediately, then briefly retry in case elements mount after rerun.
70
+ if (!collapseIfOpen()) {
71
+ let tries = 0;
72
+ const interval = setInterval(() => {
73
+ tries += 1;
74
+ if (collapseIfOpen() || tries > 20) {
75
+ clearInterval(interval);
76
+ }
77
+ }, 100);
78
+ }
79
+ </script>
80
+ """,
81
+ height=0,
82
+ width=0,
83
+ )
84
+
85
+
86
 
87
  def main() -> None:
88
  st.set_page_config(page_title="LumiGrade AI", page_icon="📷", layout="wide")
89
+
90
+ if "sidebar_collapsed" not in st.session_state:
91
+ st.session_state["sidebar_collapsed"] = False
92
+ if "run_pipeline_requested" not in st.session_state:
93
+ st.session_state["run_pipeline_requested"] = False
94
+ if "selected_image_path" not in st.session_state:
95
+ st.session_state["selected_image_path"] = ""
96
+ if "is_processing" not in st.session_state:
97
+ st.session_state["is_processing"] = False
98
+ if "refresh_after_run" not in st.session_state:
99
+ st.session_state["refresh_after_run"] = False
100
+
101
+ collapse_css = ""
102
+ if st.session_state["sidebar_collapsed"]:
103
+ collapse_css = """
104
+ /* Force-hide sidebar after run is triggered; avoids version-specific JS toggles. */
105
+ [data-testid="stSidebar"] {
106
+ display: none !important;
107
+ }
108
+ """
109
+
110
  st.markdown(
111
  """
112
  <style>
 
178
  [data-testid="stSidebar"] [data-testid="stSidebarContent"] {
179
  padding-top: 0 !important;
180
  }
181
+ """
182
+ + collapse_css
183
+ + """
184
  </style>
185
  """,
186
  unsafe_allow_html=True,
 
188
 
189
  st.title("📷 LumiGrade AI")
190
  st.caption("Upload an image to get expert-informed edit recommendations and an instant enhanced result.")
191
+ if st.session_state["sidebar_collapsed"]:
192
+ if st.button("⚙️ Show Inputs", key="show_inputs_btn", disabled=st.session_state["is_processing"]):
193
+ st.session_state["sidebar_collapsed"] = False
194
+ st.rerun()
195
 
196
  # Config check
197
  s = get_settings()
 
207
  # control and wire it to this flag.
208
  use_editing_api = False
209
 
210
+ image_path = Path(st.session_state["selected_image_path"]) if st.session_state["selected_image_path"] else None
211
 
212
  with st.sidebar:
213
  # Reliable spacing so only the Pipeline Inputs card moves down.
 
217
  uploaded = st.file_uploader(
218
  "Upload JPEG, PNG, DNG, HEIC, or HEIF",
219
  type=["jpg", "jpeg", "png", "dng", "heic", "heif"],
220
+ help="Upload JPEG/PNG/DNG/HEIC/HEIF to run the edit recommendation pipeline.",
221
  )
222
  if uploaded is not None:
223
  suffix = Path(uploaded.name).suffix.lower()
 
233
  target = _STREAMLIT_INPUT_JPG_PATH
234
  target.write_bytes(uploaded.getvalue())
235
  image_path = target
236
+ st.session_state["selected_image_path"] = str(target)
237
+
238
+ run_clicked = st.button(
239
+ " Generate Edit Recommendations",
240
+ type="primary",
241
+ use_container_width=True,
242
+ disabled=st.session_state["is_processing"],
243
+ )
 
 
 
 
 
 
244
  status = st.empty()
245
  if image_path is None:
246
  status.info("Provide an image to run.")
247
 
248
+ if run_clicked and image_path is not None:
249
+ # Mark busy before rerun so any control rendered on next pass is disabled.
250
+ st.session_state["is_processing"] = True
251
+ st.session_state["sidebar_collapsed"] = True
252
+ st.session_state["run_pipeline_requested"] = True
253
+ st.rerun()
254
+ should_run_pipeline = st.session_state.pop("run_pipeline_requested", False)
255
+ if should_run_pipeline and st.session_state["selected_image_path"]:
256
+ image_path = Path(st.session_state["selected_image_path"])
257
+ if should_run_pipeline and image_path is not None:
258
+ st.session_state["is_processing"] = True
259
+ _collapse_sidebar()
260
  loading_box = st.empty()
261
 
262
  def _render_loading(current_stage: str, state: str = "running") -> None:
 
334
  st.session_state.pop("pipeline_input_path", None)
335
  st.session_state.pop("pipeline_current_params", None)
336
  _render_loading("consulting", "failed")
337
+ finally:
338
+ st.session_state["is_processing"] = False
339
+ # Button states are computed at render time; rerun once so controls
340
+ # immediately reflect processing completion (re-enable Show Inputs).
341
+ st.session_state["refresh_after_run"] = True
342
+
343
+ if st.session_state.get("refresh_after_run"):
344
+ st.session_state["refresh_after_run"] = False
345
+ st.rerun()
346
+
347
+ display_input_path = image_path
348
+ if display_input_path is None and st.session_state.get("pipeline_input_path"):
349
+ display_input_path = Path(st.session_state["pipeline_input_path"])
350
 
351
  with st.container(border=True):
352
  st.subheader("Results Dashboard")
 
354
 
355
  # Show result if available
356
  if (
357
+ display_input_path is not None
358
  and st.session_state.get("pipeline_result")
359
+ and st.session_state.get("pipeline_input_path") == str(display_input_path)
360
  ):
361
  result = st.session_state["pipeline_result"]
362
  out_path = st.session_state["pipeline_output_path"]
 
414
 
415
  # Keep this full-width and at the bottom, per request.
416
  if (
417
+ display_input_path is not None
418
  and st.session_state.get("pipeline_result")
419
+ and st.session_state.get("pipeline_input_path") == str(display_input_path)
420
  ):
421
  result = st.session_state["pipeline_result"]
422
  out_path = st.session_state["pipeline_output_path"]
 
425
  st.subheader("Original vs Result")
426
  col_orig, col_result = st.columns(2)
427
  with col_orig:
428
+ st.image(_load_original_for_display(display_input_path), caption="Original", use_container_width=True)
429
  with col_result:
430
  st.image(str(out_path), caption="Edited", use_container_width=True)
431