import streamlit as st import streamlit.components.v1 as components import base64 import json import urllib.parse from pathlib import Path def _qr_img_url(url: str, size: int = 110) -> str: """Return a QR code image URL via the public api.qrserver.com service. No external package required — the browser fetches the image directly.""" encoded = urllib.parse.quote(url, safe="") return f"https://api.qrserver.com/v1/create-qr-code/?size={size}x{size}&data={encoded}&margin=4" try: from huggingface_hub import hf_hub_download except Exception: hf_hub_download = None # import the loader from data collection so examples can populate session state try: from pages.data_collection import load_app_state except Exception: # fallback if import fails; define a noop loader to avoid crashes def load_app_state(state_json): try: st.session_state['processes'] = json.loads(state_json).get('processes', []) except Exception: st.session_state['processes'] = [] # Configure the app - this must be the first Streamlit command st.set_page_config( page_title="HeatTransPlan App", page_icon="", initial_sidebar_state="expanded", layout="wide", ) # Ensure layout renders correctly on first load: attempt a one-time rerun if supported, # otherwise perform a short sleep as a safe fallback to help with initial rendering. if 'home_initialized' not in st.session_state: st.session_state['home_initialized'] = True if hasattr(st, 'experimental_rerun'): try: st.experimental_rerun() except Exception: import time time.sleep(0.05) else: import time time.sleep(0.05) # Apply styles immediately to prevent flash st.markdown( """ """, unsafe_allow_html=True, ) # Small CSS tweak to encourage top-left alignment on initial render st.markdown("", unsafe_allow_html=True) # Layout: left-align main content in a wider left column left_col, right_col = st.columns([0.6, 0.4]) with left_col: # Display the logo (inline SVG to avoid media file errors on remote hosts) symbol_path = Path(__file__).resolve().parents[1] / "data" / "symbol.svg" if symbol_path.exists(): try: svg_text = symbol_path.read_text(encoding="utf-8") svg_b64 = base64.b64encode(svg_text.encode("utf-8")).decode("utf-8") st.markdown(f'', unsafe_allow_html=True) except Exception: # fallback: simple inline SVG placeholder placeholder_svg = 'HeatTransPlan' placeholder_b64 = base64.b64encode(placeholder_svg.encode('utf-8')).decode('utf-8') st.markdown(f'', unsafe_allow_html=True) else: # Try remote logo URL (HeatTransPlan site) before showing placeholder remote_logo_url = "https://www.heattransplan.de/fileadmin/_processed_/3/9/csm_HeatTransPlan_final_rgb_freiraum_af637d3f32.png" try: st.markdown(f'', unsafe_allow_html=True) except Exception: # show an inline SVG placeholder instead of a warning so remote hosts don't show errors placeholder_svg = 'HeatTransPlan' placeholder_b64 = base64.b64encode(placeholder_svg.encode('utf-8')).decode('utf-8') st.markdown(f'', unsafe_allow_html=True) # Home page content st.title("Home page") st.subheader("Information") # Display a clickable image from the HeatTransPlan website img_path = Path(__file__).resolve().parents[1] / "data" / "image_project.jpeg" def _is_lfs_pointer(p: Path) -> bool: try: txt = p.read_text(encoding='utf-8') return txt.startswith('version https://git-lfs.github.com/spec/v1') except Exception: return False # If file missing or is a Git LFS pointer, try to download from Hugging Face hub downloaded_path = None if not img_path.exists() or _is_lfs_pointer(img_path): if hf_hub_download is not None: try: # repo_type='space' so it fetches from the Spaces repo downloaded_path = hf_hub_download( repo_id="DAG-UPB/heattransplanapp", filename="data/image_project.jpeg", repo_type="space", ) img_path = Path(downloaded_path) except Exception: downloaded_path = None # Build QR code section using api.qrserver.com (no local package needed) def _qr_html_block(label: str, href: str, size: int = 110) -> str: img_url = _qr_img_url(href, size=size) return ( f'
' f'' f'' f'' f'
{label}
' f'
' ) _qr_section = ( '
' + _qr_html_block("Project website", "https://www.heattransplan.de/") + _qr_html_block("App", "https://heattransplan.uni-paderborn.de/") + '
' ) if img_path.exists() and not _is_lfs_pointer(img_path): try: with open(img_path, "rb") as f: image_data = f.read() encoded_image = base64.b64encode(image_data).decode() _img_html = ( f'' f'' ) st.markdown( f'
{_img_html}{_qr_section}
', unsafe_allow_html=True, ) except Exception: st.markdown("
Project image could not be read.
", unsafe_allow_html=True) else: # Try remote project image URL (HeatTransPlan site) before showing placeholder remote_img_url = "https://www.heattransplan.de/fileadmin/_processed_/4/c/csm_AdobeStock_880898724_d3e9ed3e63.jpeg" try: _img_html = ( f'' f'' ) st.markdown( f'
{_img_html}{_qr_section}
', unsafe_allow_html=True, ) except Exception: # show a textual placeholder rather than raising an error st.markdown("
Project image not available.
", unsafe_allow_html=True) st.markdown("About HeatTransPlan") st.subheader("About this app") st.markdown( """ HeatTransPlan helps collect industrail process energy data and analyze heat recovery potential and heat pump integration opportunities using pinch analysis. How to use: - Open **Energy Data Collection** to locate the facility, describe the process and add energy data. - Go to **Potential Analysis** to select streams (use the `S` checkbox or the supplied-stream selectors) and run pinch/HPI analyses. - Review composite curves, grand composite curve, heat recovery metrics, and suggested heat pumps. - Export recorded data and analysis results. """, unsafe_allow_html=True, ) # Examples section (below the How to use explanation) st.markdown("**Examples**") examples_dir = Path(__file__).resolve().parents[1] / "data" / "examples" example1_path = examples_dir / "heat_integration_example_1.json" # If missing or a Git LFS pointer on Spaces, try to fetch the example from the Hub def _is_lfs_pointer_file(p: Path) -> bool: try: return p.exists() and p.read_text(encoding='utf-8').startswith('version https://git-lfs.github.com/spec/v1') except Exception: return False # Diagnostic info for troubleshooting on Spaces local_exists = example1_path.exists() local_is_lfs = _is_lfs_pointer_file(example1_path) hf_available = hf_hub_download is not None fetched_path = None fetch_error = None if (not local_exists or local_is_lfs) and hf_available: try: fetched = hf_hub_download(repo_id="DAG-UPB/heattransplanapp", filename="data/examples/heat_integration_example_1.json", repo_type="space") fetched_path = Path(fetched) except Exception as e: fetch_error = str(e) resolved_path = None if fetched_path and fetched_path.exists() and not _is_lfs_pointer_file(fetched_path): resolved_path = fetched_path elif example1_path.exists() and not _is_lfs_pointer_file(example1_path): resolved_path = example1_path # Show diagnostics when examples not found to help debug Spaces behavior if resolved_path is None: st.markdown("**Examples diagnostics:**") st.write(f"Local path: {example1_path}") st.write(f"Local exists: {local_exists}") st.write(f"Local is LFS pointer: {local_is_lfs}") st.write(f"huggingface_hub available: {hf_available}") if fetch_error: st.write(f"hf_hub_download error: {fetch_error}") if fetched_path: st.write(f"Fetched path: {fetched_path} (exists={fetched_path.exists()})") st.info("No examples available to load. If files in `data/examples` are stored with Git LFS, ensure LFS objects are uploaded to the repo or enable `huggingface_hub` in the environment.") else: # resolved_path points to a usable file if st.button("Load Example 1: Heat integration example"): try: example_text = resolved_path.read_text(encoding='utf-8') load_app_state(example_text) st.success("Example 1 loaded into Data Collection state.") # Redirect to Data Collection page: prefer setting query params and rerunning, # fall back to JS redirect or a manual page link. redirected = False redirect_method = None # Preferred: st.set_query_params (stable API) try: if hasattr(st, 'set_query_params'): st.set_query_params(page='pages/data_collection.py') redirected = True redirect_method = 'set_query_params' except Exception: redirected = False # Fallback: try assigning to st.query_params (some versions allow this) if not redirected and hasattr(st, 'query_params'): try: params = dict(st.query_params) params['page'] = 'pages/data_collection.py' # some streamlit versions allow assignment try: st.query_params = params except Exception: # try set_query_params alternative if hasattr(st, 'set_query_params'): try: st.set_query_params(**params) except Exception: pass redirected = True redirect_method = 'assign_query_params' except Exception: redirected = False # Older fallback: experimental_set_query_params if not redirected and hasattr(st, 'experimental_set_query_params'): try: st.experimental_set_query_params(page='pages/data_collection.py') redirected = True redirect_method = 'experimental_set_query_params' except Exception: redirected = False # If we set query params, try to rerun so the app picks up the new page if redirected and hasattr(st, 'experimental_rerun'): try: st.experimental_rerun() except Exception: pass if redirected: try: st.info(f"Navigation attempted via: {redirect_method}") except Exception: pass # As a last resort try a JS redirect (with slight delay), then manual link if not redirected: try: # Try opening the page in the top window (some hosts block direct location changes) js = "" components.html(js, height=10) try: st.info("Navigation attempted via JS open") except Exception: pass except Exception: st.markdown("[Open Data Collection](/?page=pages/data_collection.py)") except Exception as e: st.error(f"Failed to load example: {e}")