Spaces:
Running on Zero
Running on Zero
File size: 8,920 Bytes
bf47338 ec54602 156048d bf47338 | 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 | """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; }
"""
|