Spaces:
Running on Zero
Running on Zero
Prepare for public launch: README and title, SafeSearch and family-DNS filter, friendly quota message, Triton tuning cache, 4 browsers, shorter suggestions
156048d verified Download shot_tool.py from docling-project/DeskForge: direct link, hf CLI and curl.
- Browser
- Download file 8.92 kB
-
https://huggingface.co/spaces/docling-project/DeskForge/resolve/main/shot_tool.py
- Command line
-
hf download hf://spaces/docling-project/DeskForge/shot_tool.py
-
curl -L -o shot_tool.py https://huggingface.co/spaces/docling-project/DeskForge/resolve/main/shot_tool.py
8.92 kB
| """The "Try a screenshot" view: ground one instruction on your own screenshot. | |
| Plain HTML + script inside one gr.HTML. The image is sent with the component's | |
| `upload()` helper, and grounding runs through a server function, so no stock | |
| Gradio widgets are involved. Example screenshots are served as static files. | |
| """ | |
| TEMPLATE = '<div class="dsh"></div>' | |
| SCRIPT = r""" | |
| const EX = props.examples || [], MODELS = props.models || []; | |
| const st = { path: null, preview: null, text: '', model: MODELS[0], busy: false, out: null, err: '' }; | |
| const root = element.querySelector('.dsh') || element; | |
| const esc = s => String(s ?? '').replace(/[&<>"]/g, c => ({'&': '&', '<': '<', '>': '>', '"': '"'}[c])); | |
| function render() { | |
| root.innerHTML = ` | |
| <div class="dsh-grid"> | |
| <div class="dsh-card"> | |
| <div class="dsh-cap">Your screenshot</div> | |
| <label class="dsh-drop ${st.preview ? 'has' : ''}"> | |
| ${st.preview ? `<img src="${esc(st.preview)}" alt="Screenshot">` : | |
| `<span class="dsh-drop-main">Drop, paste or click to choose a screenshot</span> | |
| <span class="dsh-drop-sub">PNG or JPG of a desktop, ideally a Linux desktop</span>`} | |
| <input type="file" accept="image/*" class="dsh-file" hidden> | |
| </label> | |
| <div class="dsh-cap">Instruction</div> | |
| <input class="dsh-text" type="text" placeholder="e.g. Open the File menu" value="${esc(st.text)}"> | |
| <div class="dsh-row"> | |
| <span class="dsh-seg">${MODELS.map(m => `<button type="button" data-model="${esc(m)}" class="${st.model === m ? 'on' : ''}">${esc(m)}</button>`).join('')}</span> | |
| <button type="button" class="dsh-go" data-act="go" ${st.busy ? 'disabled' : ''}>${st.busy ? 'Grounding…' : 'Ground it ↑'}</button> | |
| </div> | |
| ${st.err ? `<div class="dsh-err">${esc(st.err)}</div>` : ''} | |
| </div> | |
| <div class="dsh-card"> | |
| <div class="dsh-cap">Where the model would click</div> | |
| ${st.out ? `<img class="dsh-result" src="${st.out.image}" alt="Predicted click"> | |
| <div class="dsh-code">${esc(st.out.code)}</div> | |
| <div class="dsh-meta"><span>${esc(st.out.coords)}</span><span>${esc(st.out.time)}</span></div>` | |
| : `<div class="dsh-empty">${st.busy ? '<span class="dsh-spin"></span> Looking at the screen…' : 'The predicted click point shows up here.'}</div>`} | |
| </div> | |
| </div> | |
| <div class="dsh-cap dsh-excap">Or try an example</div> | |
| <div class="dsh-ex">${EX.map((e, i) => `<button type="button" class="dsh-exi" data-ex="${i}" title="${esc(e.text)}"> | |
| <img src="${esc(e.url)}" alt=""><span>${esc(e.text)}</span></button>`).join('')}</div>`; | |
| } | |
| async function useFile(file) { | |
| if (!file || !file.type.startsWith('image/')) return; | |
| st.preview = URL.createObjectURL(file); st.out = null; st.err = ''; st.path = null; render(); | |
| try { const r = await upload(file); st.path = r.path; } | |
| catch (e) { st.err = 'Upload failed, please try again.'; render(); } | |
| } | |
| async function go() { | |
| const text = (element.querySelector('.dsh-text') || {}).value || st.text; | |
| st.text = text; | |
| if (!st.path) { st.err = st.preview ? 'Still uploading, one moment…' : 'Choose a screenshot first.'; render(); return; } | |
| if (!text.trim()) { st.err = 'Write an instruction first.'; render(); return; } | |
| st.busy = true; st.err = ''; st.out = null; render(); | |
| try { st.out = await gpuCall('ground_screenshot', { src: st.path, text, model: st.model }); } | |
| catch (e) { | |
| const msg = e && e.message ? e.message : String(e), f = friendlyGpuError(msg); | |
| st.err = f ? f.title + '. ' + f.text : 'Grounding failed: ' + msg; | |
| } | |
| st.busy = false; render(); | |
| } | |
| element.addEventListener('change', e => { if (e.target.classList.contains('dsh-file')) useFile(e.target.files[0]); }); | |
| element.addEventListener('input', e => { if (e.target.classList.contains('dsh-text')) st.text = e.target.value; }); | |
| element.addEventListener('keydown', e => { if (e.target.classList.contains('dsh-text') && e.key === 'Enter') go(); }); | |
| element.addEventListener('dragover', e => e.preventDefault()); | |
| element.addEventListener('drop', e => { e.preventDefault(); useFile(e.dataTransfer.files[0]); }); | |
| element.addEventListener('paste', e => { const f = [...(e.clipboardData || {}).files || []][0]; if (f) useFile(f); }); | |
| element.addEventListener('click', e => { | |
| const t = e.target.closest('[data-model],[data-act],[data-ex]'); | |
| if (!t) return; | |
| if (t.dataset.model) { st.model = t.dataset.model; render(); } | |
| else if (t.dataset.act === 'go') go(); | |
| else if (t.dataset.ex) { | |
| const ex = EX[+t.dataset.ex]; | |
| st.path = 'example:' + ex.name; st.preview = ex.url; st.text = ex.text; st.out = null; st.err = ''; | |
| render(); go(); | |
| } | |
| }); | |
| render(); | |
| """ | |
| CSS = """ | |
| #df-view-shot { display: none; background: #F3EDE2; padding: 22px 26px !important; } | |
| #df-view-shot .dsh { font-family: 'Hanken Grotesk', system-ui, sans-serif; color: #2B2722; } | |
| #df-view-shot .dsh-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } | |
| @media (max-width: 1000px) { .dsh-grid { grid-template-columns: 1fr; } } | |
| #df-view-shot .dsh-card { background: #fff; border: 1px solid #E6E3DF; border-radius: 12px; padding: 14px; display: flex; flex-direction: column; gap: 9px; | |
| box-shadow: 0 1px 2px rgba(40,30,20,.04); } | |
| #df-view-shot .dsh-cap { font: 500 10px 'JetBrains Mono', monospace; letter-spacing: .06em; text-transform: uppercase; color: #9a9389; } | |
| #df-view-shot .dsh-drop { min-height: 230px; border: 1.5px dashed #DCD8D2; border-radius: 10px; display: flex; flex-direction: column; align-items: center; | |
| justify-content: center; gap: 4px; cursor: pointer; background: #FAF9F7; text-align: center; padding: 14px; overflow: hidden; } | |
| #df-view-shot .dsh-drop:hover { border-color: #D2603A; } | |
| #df-view-shot .dsh-drop.has { padding: 0; border-style: solid; background: #fff; } | |
| #df-view-shot .dsh-drop img { width: 100%; display: block; } | |
| #df-view-shot .dsh-drop-main { font-size: 14px; font-weight: 600; color: #3a332b; } | |
| #df-view-shot .dsh-drop-sub { font-size: 12.5px; color: #8a8378; } | |
| #df-view-shot .dsh-text { font: 14px 'Hanken Grotesk', system-ui, sans-serif; color: #2B2722; background: #fff; border: 1px solid #E6E3DF; border-radius: 9px; | |
| padding: 9px 11px; outline: none; } | |
| #df-view-shot .dsh-text:focus { border-color: #D2603A; box-shadow: 0 0 0 3px rgba(210,96,58,.10); } | |
| #df-view-shot .dsh-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; } | |
| #df-view-shot .dsh-seg { display: inline-flex; border: 1px solid #E6E3DF; border-radius: 9px; overflow: hidden; background: #fff; } | |
| #df-view-shot .dsh-seg button { font: 500 13px 'Hanken Grotesk', system-ui, sans-serif; color: #6f6a62; background: transparent; border: none; padding: 7px 12px; cursor: pointer; } | |
| #df-view-shot .dsh-seg button + button { border-left: 1px solid #E6E3DF; } | |
| #df-view-shot .dsh-seg button.on { background: #FBF3EC; color: #C0552F; font-weight: 600; } | |
| #df-view-shot .dsh-go { font: 600 14px 'Hanken Grotesk', system-ui, sans-serif; color: #fff; background: #D2603A; border: 1px solid #C0552F; border-radius: 9px; | |
| padding: 8px 16px; cursor: pointer; } | |
| #df-view-shot .dsh-go:disabled { opacity: .6; cursor: default; } | |
| #df-view-shot .dsh-err { font-size: 13px; color: #A8452A; } | |
| #df-view-shot .dsh-result { width: 100%; border-radius: 9px; border: 1px solid #E6E3DF; display: block; } | |
| #df-view-shot .dsh-code { font: 500 12.5px/1.6 'JetBrains Mono', monospace; color: #3a332b; background: #FAF9F7; border: 1px solid #EEECE8; border-radius: 8px; | |
| padding: 8px 10px; overflow-wrap: anywhere; } | |
| #df-view-shot .dsh-meta { display: flex; justify-content: space-between; font: 500 11.5px 'JetBrains Mono', monospace; color: #9a9389; } | |
| #df-view-shot .dsh-empty { min-height: 230px; display: flex; align-items: center; justify-content: center; gap: 8px; border-radius: 10px; background: #FAF9F7; | |
| border: 1px solid #EEECE8; font-size: 13.5px; color: #8a8378; } | |
| #df-view-shot .dsh-spin { width: 13px; height: 13px; border-radius: 50%; border: 2px solid #E6E3DF; border-top-color: #D2603A; animation: df-spin .7s linear infinite; } | |
| #df-view-shot .dsh-excap { margin: 18px 0 8px; } | |
| #df-view-shot .dsh-ex { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 10px; } | |
| #df-view-shot .dsh-exi { display: flex; flex-direction: column; gap: 6px; background: #fff; border: 1px solid #E6E3DF; border-radius: 10px; padding: 6px; cursor: pointer; | |
| text-align: left; font: 12.5px/1.35 'Hanken Grotesk', system-ui, sans-serif; color: #3a332b; } | |
| #df-view-shot .dsh-exi:hover { border-color: #D2603A; } | |
| #df-view-shot .dsh-exi img { width: 100%; height: 92px; object-fit: cover; object-position: top left; border-radius: 6px; border: 1px solid #EEECE8; } | |
| #df-view-shot .dsh-exi span { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } | |
| """ | |