CoolFace
Apppublic

CMacD/AIC_PHASE1_POC

sourceHugging Faceupdated 4mo agoView on Hugging Face
0likes
aic_utils.py118 linesDownload Raw Back to root
1"""2aic_utils.py — shared utilities for AIC Streamlit pages.3"""4from __future__ import annotations5 6import base647import streamlit as st8 9ACCENT = "#4E106F"10 11 12 13def save_as_button(14    label: str,15    data: bytes,16    filename: str,17    mime: str,18    key: str = "",19    height: int = 44,20) -> None:21    """22    Render a purple 'Save As…' button using the File System Access API.23 24    On Chrome / Edge the OS native Save-As dialog opens so the user can25    choose the exact save location and filename.  On other browsers (or if26    the API is blocked) it falls back to a regular browser download.27 28    Parameters29    ----------30    label    : button text (can include emoji)31    data     : raw bytes to save32    filename : suggested filename shown in the dialog33    mime     : MIME type string34    key      : optional unique suffix for the JS function name35    height   : iframe height in pixels — increase if button is clipped36    """37    b64 = base64.b64encode(data).decode()38    ext = ("." + filename.rsplit(".", 1)[-1]) if "." in filename else ""39    fn_name = f"saveAs_{key}" if key else "saveAsFile"40 41    # Escape special chars so they survive JS string literals42    safe_fn   = filename.replace("\\", "\\\\").replace("'", "\\'")43    safe_mime = mime.replace("'", "\\'")44    safe_ext  = ext.replace("'", "\\'")45 46    html = f"""<!doctype html>47<html>48<head>49<style>50  * {{ margin: 0; padding: 0; box-sizing: border-box; }}51  body {{ background: transparent; display: flex; align-items: center; }}52  .sa-btn {{53    background: {ACCENT};54    color: #fff;55    border: none;56    border-radius: 6px;57    padding: 8px 18px;58    font-size: 14px;59    font-family: "Source Sans Pro", sans-serif;60    font-weight: 500;61    cursor: pointer;62    display: inline-flex;63    align-items: center;64    gap: 6px;65    transition: background 0.15s;66    white-space: nowrap;67  }}68  .sa-btn:hover  {{ background: #3a0d54; }}69  .sa-btn:active {{ background: #2d0a42; }}70</style>71</head>72<body>73<button class="sa-btn" onclick="{fn_name}()">{label}</button>74<script>75const _b64_{key}  = '{b64}';76const _fn_{key}   = '{safe_fn}';77const _mime_{key} = '{safe_mime}';78const _ext_{key}  = '{safe_ext}';79 80async function {fn_name}() {{81  const bytes = Uint8Array.from(atob(_b64_{key}), c => c.charCodeAt(0));82 83  if (window.showSaveFilePicker) {{84    try {{85      const fh = await window.showSaveFilePicker({{86        suggestedName: _fn_{key},87        types: [{{88          description: 'File',89          accept: {{ [_mime_{key}]: [_ext_{key}] }}90        }}]91      }});92      const w = await fh.createWritable();93      await w.write(bytes);94      await w.close();95      return;96    }} catch (e) {{97      if (e.name === 'AbortError') return;   // user hit Cancel — do nothing98      // any other error: fall through to regular download99    }}100  }}101 102  // Fallback: regular browser download (non-Chrome/Edge or API blocked)103  const blob = new Blob([bytes], {{ type: _mime_{key} }});104  const url  = URL.createObjectURL(blob);105  const a    = Object.assign(document.createElement('a'), {{106    href: url, download: _fn_{key}107  }});108  document.body.appendChild(a);109  a.click();110  document.body.removeChild(a);111  setTimeout(() => URL.revokeObjectURL(url), 10000);112}}113</script>114</body>115</html>"""116 117    st.components.v1.html(html, height=height)118