Spaces:
Sleeping
Sleeping
File size: 9,990 Bytes
8be5524 fbc91e5 8be5524 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | import asyncio
import os
from playwright.async_api import async_playwright
from PIL import Image
from fpdf import FPDF
OUTPUT_DIR = "static/outputs"
async def capture_workflows(public_url: str, pdf_filename: str = "workflow_screens.pdf"):
os.makedirs(OUTPUT_DIR, exist_ok=True)
pdf_path = os.path.join(OUTPUT_DIR, pdf_filename)
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
print(f"Opening page: {public_url}")
await page.goto(public_url, wait_until="load")
# --- Utility: wait for stable layout ---
async def wait_for_layout_stable():
await page.evaluate("""
(async () => {
let stableCount = 0;
let lastHeight = document.body.scrollHeight;
await new Promise((resolve) => {
const check = setInterval(() => {
const current = document.body.scrollHeight;
if (Math.abs(current - lastHeight) < 1) {
stableCount++;
if (stableCount >= 5) {
clearInterval(check);
setTimeout(resolve, 300);
}
} else {
stableCount = 0;
lastHeight = current;
}
}, 100);
});
})();
""")
# --- Fix only sidebar; keep top bar dynamic per screen ---
async def fix_layout_dynamically():
try:
await page.evaluate("""
const sidebar = document.querySelector('aside, .sidebar, nav');
const topbar = document.querySelector('.top-bar, header, .app-header, .navbar, .page-header');
// Reset old fixed styles
[sidebar, topbar].forEach(el => {
if (!el) return;
el.style.position = '';
el.style.top = '';
el.style.left = '';
el.style.width = '';
el.style.height = '';
el.style.zIndex = '';
el.style.overflow = '';
el.style.transition = '';
});
// Fix sidebar only (topbar remains dynamic)
if (sidebar) {
const rect = sidebar.getBoundingClientRect();
const sidebarWidth = rect.width || sidebar.offsetWidth || 220;
const topOffset = topbar ? topbar.offsetHeight || 60 : 60;
sidebar.style.position = 'fixed';
sidebar.style.top = topOffset + 'px';
sidebar.style.left = '0';
sidebar.style.height = `calc(100vh - ${topOffset}px)`;
sidebar.style.width = sidebarWidth + 'px';
sidebar.style.zIndex = '1000';
sidebar.style.overflow = 'auto';
sidebar.style.transition = 'none';
sidebar.dataset.locked = 'true';
}
const main = document.querySelector('main, .main-content, .app-body, .content');
if (main && sidebar) {
main.style.marginLeft = sidebar.offsetWidth + 'px';
main.style.marginTop = topbar ? (topbar.offsetHeight || 60) + 'px' : '0';
main.style.position = 'relative';
}
window.scrollTo(0, 0);
""")
except Exception as e:
print(f"[WARN] Layout fix failed: {e}")
# --- Wait until sidebar exists ---
await page.wait_for_selector(
"aside, .sidebar, nav, .side-panel, .left-menu, .menu",
timeout=20000
)
await wait_for_layout_stable()
await fix_layout_dynamically()
await asyncio.sleep(1)
# --- JS Logic: detect workflows and tabs dynamically ---
js_logic = """
(function(){
const menus=[...document.querySelectorAll('.menu-item')];
const screens=[...document.querySelectorAll('.screen')];
window.__ordered=[];
const seen=new Set();
for(const m of menus){
let id=m.dataset.screen||m.dataset.target;
if(!id){
const href=m.getAttribute('href');
if(href && href.startsWith('#')) id=href.substring(1);
}
const s=screens.find(x=>x.id===id);
if(s && !seen.has(s)){seen.add(s);window.__ordered.push({menu:m,screen:s});}
}
for(const s of screens)
if(!seen.has(s))
window.__ordered.push({menu:null,screen:s});
window.__visitedWorkflows=[];
window.__currentIndex=0;
window.__done=false;
window.__getSubScreens = function(screen){
const tabs=[...screen.querySelectorAll('.tab, .nav-link, [role="tab"], [data-tab], .sub-tab, .tab-item')];
const list=[];
for(const t of tabs){
const sub=t.textContent?.trim();
if(sub && !list.includes(sub)) list.push(sub);
}
return list;
};
window.__captureNext=async function(){
if(window.__done) return false;
if(window.__currentIndex>=window.__ordered.length){window.__done=true;return false;}
const pair=window.__ordered[window.__currentIndex];
const {menu,screen}=pair;
if(!screen){window.__done=true;return false;}
const wfName=screen.id || screen.getAttribute('data-name') || ('screen_'+window.__currentIndex);
if(window.__visitedWorkflows.includes(wfName)){
window.__currentIndex++;
return window.__captureNext();
}
window.__visitedWorkflows.push(wfName);
document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
document.querySelectorAll('.menu-item').forEach(m=>m.classList.remove('active'));
screen.classList.add('active');
if(menu) menu.classList.add('active');
screen.scrollIntoView({behavior:'smooth',block:'center'});
window.__currentIndex++;
const subs = window.__getSubScreens(screen);
return {screenName:wfName, subScreens:subs};
};
window.__clickSubScreen = async function(name){
const tabs=[...document.querySelectorAll('.tab, .nav-link, [role="tab"], [data-tab], .sub-tab, .tab-item')];
const t=tabs.find(x=>x.textContent.trim()===name);
if(t){t.click(); return true;}
return false;
};
})();
"""
await page.evaluate(js_logic)
await asyncio.sleep(1)
screenshots = []
index = 0
# --- Capture all screens ---
while True:
result = await page.evaluate("window.__captureNext()")
if not result:
break
screen_name = result.get("screenName", f"screen_{index}")
sub_screens = result.get("subScreens", [])
screenshot_path = os.path.join(OUTPUT_DIR, f"{screen_name}.png")
print(f"📸 Capturing main screen: {screen_name}")
await wait_for_layout_stable()
await fix_layout_dynamically()
await asyncio.sleep(1.0)
await page.screenshot(path=screenshot_path, full_page=True)
screenshots.append(screenshot_path)
# --- Capture sub-tabs ---
first_active_skipped = False
for sub in sub_screens:
is_active = await page.evaluate(
"""(subText) => {
const tabs=[...document.querySelectorAll('.tab, .nav-link, [role="tab"], [data-tab], .sub-tab, .tab-item')];
const t=tabs.find(x=>x.textContent.trim()===subText);
if(!t) return false;
const cls=t.getAttribute('class')||'';
return cls.includes('active');
}""",
sub
)
if is_active and not first_active_skipped:
first_active_skipped = True
continue
print(f" ↳ Capturing sub-screen: {sub}")
await page.evaluate(f"window.__clickSubScreen('{sub}')")
await wait_for_layout_stable()
await fix_layout_dynamically()
await asyncio.sleep(1.0)
sub_name_clean = sub.replace(" ", "_").lower()
sub_path = os.path.join(OUTPUT_DIR, f"{screen_name}_{sub_name_clean}.png")
await page.screenshot(path=sub_path, full_page=True)
screenshots.append(sub_path)
index += 1
await browser.close()
# --- Generate PDF ---
if not screenshots:
raise RuntimeError("No screenshots captured — check if .screen elements exist!")
print(f"🧾 Combining {len(screenshots)} screenshots into PDF: {pdf_path}")
pdf = FPDF()
for img_path in screenshots:
image = Image.open(img_path)
w, h = image.size
pdf_w, pdf_h = 210, 297
aspect = h / w
pdf.add_page()
pdf.image(img_path, 0, 0, pdf_w, pdf_w * aspect)
pdf.output(pdf_path, "F")
print(f"✅ PDF generated successfully: {pdf_path}")
return pdf_path
generate_ui_report = capture_workflows |