JS6969 commited on
Commit
b52e85e
·
verified ·
1 Parent(s): a85a2ba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +99 -31
app.py CHANGED
@@ -34,7 +34,7 @@ APP_DIR = os.getcwd()
34
  SESSION_FILE = "/tmp/forge_session.json"
35
 
36
  # Branding: fixed logo height
37
- LOGO_HEIGHT_PX = int(os.getenv("FORGE_LOGO_PX", 60))
38
 
39
  # Settings live in a user cache dir (persists better than /tmp)
40
  CONFIG_DIR = os.path.expanduser("~/.cache/forgecaptions")
@@ -707,7 +707,7 @@ def _render_header_html(px: int) -> str:
707
  display: block;
708
  max-width: 320px; /* cap very wide logos */
709
  }}
710
- @media (max-width: 640px) {{
711
  .cf-logo {{ height: {max(48, int(px) - 8)}px; }}
712
  }}
713
  </style>
@@ -915,7 +915,7 @@ with gr.Blocks(css=BASE_CSS, title="ForgeCaptions") as demo:
915
  gallery = gr.Gallery(
916
  label="Results",
917
  show_label=True,
918
- columns=3,
919
  elem_id="cfGal",
920
  elem_classes=["cf-scroll"]
921
  )
@@ -949,43 +949,111 @@ with gr.Blocks(css=BASE_CSS, title="ForgeCaptions") as demo:
949
  export_txt_btn = gr.Button("Export captions as .txt (zip)")
950
  txt_zip = gr.File(label="TXT zip", visible=False)
951
 
952
- # ---- Robust scroll sync
953
- gr.HTML("""
954
  <script>
955
- (function () {
956
- function findGal() {
957
- const host = document.querySelector("#cfGal");
958
- if (!host) return null;
959
- return host.querySelector('[data-testid="gallery"]') || host;
 
 
 
 
 
 
 
 
960
  }
961
- function findTbl() { return document.querySelector("#cfTableWrap"); }
962
- function syncScroll(a, b) {
963
- if (!a || !b) return;
 
 
 
 
 
 
 
 
 
 
 
 
964
  let lock = false;
965
- const onA = () => { if (lock) return; lock = true; b.scrollTop = a.scrollTop; lock = false; };
966
- const onB = () => { if (lock) return; lock = true; a.scrollTop = b.scrollTop; lock = false; };
 
 
 
 
 
 
 
 
 
 
 
 
 
967
  a.addEventListener("scroll", onA, { passive: true });
968
  b.addEventListener("scroll", onB, { passive: true });
 
 
 
 
 
 
969
  }
970
- let tries = 0;
971
- const t = setInterval(() => {
972
- tries++;
973
- const gal = findGal();
974
- const tab = findTbl();
975
- if (gal && tab) {
976
- const H = Math.min(520, Math.max(360, tab.clientHeight || 520));
977
- gal.style.maxHeight = H + "px";
978
- gal.style.overflowY = "auto";
979
- tab.style.maxHeight = H + "px";
980
- tab.style.overflowY = "auto";
981
- syncScroll(gal, tab);
982
- clearInterval(t);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
983
  }
984
- if (tries > 30) clearInterval(t);
985
- }, 120);
 
 
 
 
 
 
 
 
 
 
 
986
  })();
987
  </script>
988
- """)
989
 
990
  # ---- Chunking logic
991
  def _split_chunks(files, csize: int):
 
34
  SESSION_FILE = "/tmp/forge_session.json"
35
 
36
  # Branding: fixed logo height
37
+ LOGO_HEIGHT_PX = int(os.getenv("FORGE_LOGO_PX", 50))
38
 
39
  # Settings live in a user cache dir (persists better than /tmp)
40
  CONFIG_DIR = os.path.expanduser("~/.cache/forgecaptions")
 
707
  display: block;
708
  max-width: 320px; /* cap very wide logos */
709
  }}
710
+ @media (max-width: 500px) {{
711
  .cf-logo {{ height: {max(48, int(px) - 8)}px; }}
712
  }}
713
  </style>
 
915
  gallery = gr.Gallery(
916
  label="Results",
917
  show_label=True,
918
+ columns=1,
919
  elem_id="cfGal",
920
  elem_classes=["cf-scroll"]
921
  )
 
949
  export_txt_btn = gr.Button("Export captions as .txt (zip)")
950
  txt_zip = gr.File(label="TXT zip", visible=False)
951
 
 
 
952
  <script>
953
+ (() => {
954
+ // ---- Config: your wrapper IDs
955
+ const GAL_WRAP_SEL = "#cfGal"; // wrapper around the gallery
956
+ const TABLE_WRAP_SEL = "#cfTableWrap"; // wrapper around your table
957
+
958
+ // ---- Helpers
959
+ const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
960
+
961
+ function findGalleryHost() {
962
+ const wrap = document.querySelector(GAL_WRAP_SEL);
963
+ if (!wrap) return null;
964
+ // Prefer explicit gallery node if present; otherwise use wrapper
965
+ return wrap.querySelector('[data-testid="gallery"], [data-testid="image-gallery"]') || wrap;
966
  }
967
+
968
+ function findTableHost() {
969
+ return document.querySelector(TABLE_WRAP_SEL);
970
+ }
971
+
972
+ function setMaxHeights(gal, tab) {
973
+ const targetH = clamp(tab.clientHeight || 520, 360, 520);
974
+ gal.style.maxHeight = targetH + "px";
975
+ gal.style.overflowY = "auto";
976
+ tab.style.maxHeight = targetH + "px";
977
+ tab.style.overflowY = "auto";
978
+ }
979
+
980
+ function attachScrollSync(a, b) {
981
+ if (!a || !b) return () => {};
982
  let lock = false;
983
+
984
+ // Use scroll ratio so different scrollHeights stay aligned
985
+ const sync = (src, dst) => {
986
+ const maxSrc = Math.max(1, src.scrollHeight - src.clientHeight);
987
+ const r = src.scrollTop / maxSrc;
988
+ const maxDst = Math.max(1, dst.scrollHeight - dst.clientHeight);
989
+ const next = r * maxDst;
990
+ if (Math.abs(dst.scrollTop - next) > 1) {
991
+ dst.scrollTop = next;
992
+ }
993
+ };
994
+
995
+ const onA = () => { if (lock) return; lock = true; requestAnimationFrame(() => { sync(a, b); lock = false; }); };
996
+ const onB = () => { if (lock) return; lock = true; requestAnimationFrame(() => { sync(b, a); lock = false; }); };
997
+
998
  a.addEventListener("scroll", onA, { passive: true });
999
  b.addEventListener("scroll", onB, { passive: true });
1000
+
1001
+ // Return cleanup
1002
+ return () => {
1003
+ a.removeEventListener("scroll", onA);
1004
+ b.removeEventListener("scroll", onB);
1005
+ };
1006
  }
1007
+
1008
+ let cleanupScroll = null;
1009
+ let resizeObs = null;
1010
+
1011
+ function wireUp() {
1012
+ const gal = findGalleryHost();
1013
+ const tab = findTableHost();
1014
+ if (!gal || !tab) return false;
1015
+
1016
+ // (Re)apply heights & listeners
1017
+ setMaxHeights(gal, tab);
1018
+ if (cleanupScroll) cleanupScroll();
1019
+ cleanupScroll = attachScrollSync(gal, tab);
1020
+
1021
+ // Keep heights in sync on resize/content changes
1022
+ if (resizeObs) resizeObs.disconnect();
1023
+ resizeObs = new ResizeObserver(() => setMaxHeights(gal, tab));
1024
+ resizeObs.observe(tab);
1025
+ resizeObs.observe(gal);
1026
+
1027
+ return true;
1028
+ }
1029
+
1030
+ // First attempt immediately (fast path)
1031
+ if (wireUp()) return;
1032
+
1033
+ // Observe DOM for late mounts / Gradio re-renders
1034
+ const mo = new MutationObserver(() => {
1035
+ if (wireUp()) {
1036
+ // Once wired, continue watching for *re-renders* to re-wire
1037
+ // If you prefer, remove this `disconnect()` to keep watching forever
1038
+ // but then you should also detect detach and clean up.
1039
+ // Here we keep observing to handle re-renders:
1040
  }
1041
+ });
1042
+
1043
+ mo.observe(document.documentElement || document.body, {
1044
+ childList: true,
1045
+ subtree: true,
1046
+ });
1047
+
1048
+ // Optional: cleanup on page unload
1049
+ window.addEventListener("beforeunload", () => {
1050
+ mo.disconnect();
1051
+ if (resizeObs) resizeObs.disconnect();
1052
+ if (cleanupScroll) cleanupScroll();
1053
+ });
1054
  })();
1055
  </script>
1056
+
1057
 
1058
  # ---- Chunking logic
1059
  def _split_chunks(files, csize: int):