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 = ''
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 = ''
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'
'
)
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'
',
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}")