DivYonko commited on
Commit
cdf4792
·
1 Parent(s): e7565ac

Replace broken PDF with html2canvas screenshot export on Stats page

Browse files
Files changed (3) hide show
  1. app.py +1 -19
  2. frontend/streamlit_app.py +1 -19
  3. pages/stats.py +102 -0
app.py CHANGED
@@ -1091,25 +1091,7 @@ with st.sidebar:
1091
  mime="text/csv",
1092
  key=f"dl_{_rkey}",
1093
  )
1094
- # ── PDF Report button ──────────────────────────────
1095
- try:
1096
- from ml.report_generator import generate_report
1097
- _stream_title = _s.get("video_title") or _s.get("video_id") or _rkey
1098
- _pdf_bytes = generate_report(
1099
- all_data=_dl_rows,
1100
- stream_title=_stream_title,
1101
- msg_limit=st.session_state.get("msg_limit", 100),
1102
- )
1103
- _pdf_fname = f"livepulse_report_{_rkey}_{_ts}.pdf"
1104
- st.download_button(
1105
- label=f"\U0001f4cb {_slabel} PDF Report",
1106
- data=_pdf_bytes,
1107
- file_name=_pdf_fname,
1108
- mime="application/pdf",
1109
- key=f"pdf_{_rkey}",
1110
- )
1111
- except Exception as _pdf_err:
1112
- st.markdown(f'<div style="font-size:0.7rem;color:var(--text-3);">PDF unavailable: {_pdf_err}</div>', unsafe_allow_html=True)
1113
  else:
1114
  st.markdown(f'<div style="font-size:0.72rem;color:var(--text-3);">{_slabel}: no data yet</div>', unsafe_allow_html=True)
1115
  else:
 
1091
  mime="text/csv",
1092
  key=f"dl_{_rkey}",
1093
  )
1094
+ # PDF button removed — use the Export button on the Stats page instead
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1095
  else:
1096
  st.markdown(f'<div style="font-size:0.72rem;color:var(--text-3);">{_slabel}: no data yet</div>', unsafe_allow_html=True)
1097
  else:
frontend/streamlit_app.py CHANGED
@@ -752,25 +752,7 @@ with st.sidebar:
752
  mime="text/csv",
753
  key=f"dl_{_rkey}",
754
  )
755
- # ── PDF Report button ──────────────────────────────
756
- try:
757
- from ml.report_generator import generate_report
758
- _stream_title = _s.get("video_title") or _s.get("video_id") or _rkey
759
- _pdf_bytes = generate_report(
760
- all_data=_dl_rows,
761
- stream_title=_stream_title,
762
- msg_limit=msg_limit,
763
- )
764
- _pdf_fname = f"livepulse_report_{_rkey}_{_ts}.pdf"
765
- st.download_button(
766
- label=f"\U0001f4cb {_slabel} PDF Report",
767
- data=_pdf_bytes,
768
- file_name=_pdf_fname,
769
- mime="application/pdf",
770
- key=f"pdf_{_rkey}",
771
- )
772
- except Exception as _pdf_err:
773
- st.markdown(f'<div style="font-size:0.7rem;color:var(--text-3);">PDF unavailable: {_pdf_err}</div>', unsafe_allow_html=True)
774
  else:
775
  st.markdown(f'<div style="font-size:0.72rem;color:var(--text-3);">{_slabel}: no data yet</div>', unsafe_allow_html=True)
776
  else:
 
752
  mime="text/csv",
753
  key=f"dl_{_rkey}",
754
  )
755
+ # PDF button removed — use the Export button on the Stats page instead
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756
  else:
757
  st.markdown(f'<div style="font-size:0.72rem;color:var(--text-3);">{_slabel}: no data yet</div>', unsafe_allow_html=True)
758
  else:
pages/stats.py CHANGED
@@ -976,6 +976,108 @@ elif len(st.session_state.streams) > 1:
976
  st.divider()
977
  st.info("Add video IDs to your extra stream slots and click ▶ Start to enable multi-stream comparison.")
978
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
979
  # -- AUTO REFRESH ---------------------------------------------
980
  if auto_refresh:
981
  time.sleep(refresh_rate)
 
976
  st.divider()
977
  st.info("Add video IDs to your extra stream slots and click ▶ Start to enable multi-stream comparison.")
978
 
979
+ # -- SCREENSHOT / PDF EXPORT ----------------------------------
980
+ st.divider()
981
+ st.markdown(
982
+ '<div class="sec-hdr"><span class="sec-ttl">Export</span><span class="sec-pill">Stats Page</span></div>',
983
+ unsafe_allow_html=True
984
+ )
985
+
986
+ import streamlit.components.v1 as components
987
+ components.html("""
988
+ <div style="text-align:center; padding: 8px 0;">
989
+ <button id="screenshotBtn" style="
990
+ background: linear-gradient(135deg, #7c3aed, #4f46e5);
991
+ color: #fff;
992
+ border: none;
993
+ border-radius: 10px;
994
+ padding: 10px 28px;
995
+ font-size: 14px;
996
+ font-weight: 600;
997
+ font-family: 'Space Grotesk', sans-serif;
998
+ cursor: pointer;
999
+ box-shadow: 0 4px 16px rgba(124,58,237,0.4);
1000
+ transition: transform 0.2s;
1001
+ "
1002
+ onmouseover="this.style.transform='translateY(-2px)'"
1003
+ onmouseout="this.style.transform='translateY(0)'"
1004
+ onclick="captureAndDownload()">
1005
+ &#128247; Download Stats as PDF
1006
+ </button>
1007
+ <div id="statusMsg" style="margin-top:8px; font-size:12px; color:#94a3b8;"></div>
1008
+ </div>
1009
+
1010
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
1011
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
1012
+ <script>
1013
+ async function captureAndDownload() {
1014
+ const btn = document.getElementById('screenshotBtn');
1015
+ const msg = document.getElementById('statusMsg');
1016
+ btn.disabled = true;
1017
+ btn.textContent = 'Capturing...';
1018
+ msg.textContent = 'Please wait, capturing the page...';
1019
+
1020
+ try {
1021
+ // Target the main Streamlit content area in the parent frame
1022
+ const target = window.parent.document.querySelector('[data-testid="stMain"]')
1023
+ || window.parent.document.querySelector('.main')
1024
+ || window.parent.document.body;
1025
+
1026
+ const canvas = await window.parent.html2canvas(target, {
1027
+ scale: 1.5,
1028
+ useCORS: true,
1029
+ allowTaint: true,
1030
+ backgroundColor: '#07070f',
1031
+ logging: false,
1032
+ windowWidth: target.scrollWidth,
1033
+ windowHeight: target.scrollHeight,
1034
+ scrollX: 0,
1035
+ scrollY: 0,
1036
+ });
1037
+
1038
+ const imgData = canvas.toDataURL('image/png', 0.95);
1039
+ const { jsPDF } = window.parent.jspdf;
1040
+ const pdf = new jsPDF({
1041
+ orientation: canvas.width > canvas.height ? 'l' : 'p',
1042
+ unit: 'px',
1043
+ format: [canvas.width, canvas.height],
1044
+ compress: true,
1045
+ });
1046
+
1047
+ pdf.addImage(imgData, 'PNG', 0, 0, canvas.width, canvas.height);
1048
+ const ts = new Date().toISOString().slice(0,16).replace('T','_').replace(':','-');
1049
+ pdf.save('livepulse_stats_' + ts + '.pdf');
1050
+
1051
+ btn.textContent = 'Download Stats as PDF';
1052
+ btn.disabled = false;
1053
+ msg.textContent = 'Done! Check your downloads.';
1054
+ setTimeout(() => { msg.textContent = ''; }, 4000);
1055
+ } catch(e) {
1056
+ btn.textContent = 'Download Stats as PDF';
1057
+ btn.disabled = false;
1058
+ msg.textContent = 'Error: ' + e.message;
1059
+ console.error(e);
1060
+ }
1061
+ }
1062
+
1063
+ // Load libraries into parent frame if not already there
1064
+ function loadScript(src, globalName) {
1065
+ return new Promise((resolve) => {
1066
+ if (window.parent[globalName]) { resolve(); return; }
1067
+ const s = window.parent.document.createElement('script');
1068
+ s.src = src;
1069
+ s.onload = resolve;
1070
+ window.parent.document.head.appendChild(s);
1071
+ });
1072
+ }
1073
+
1074
+ (async () => {
1075
+ await loadScript('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js', 'html2canvas');
1076
+ await loadScript('https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js', 'jspdf');
1077
+ })();
1078
+ </script>
1079
+ """, height=90)
1080
+
1081
  # -- AUTO REFRESH ---------------------------------------------
1082
  if auto_refresh:
1083
  time.sleep(refresh_rate)