How can I help you today?

Type a message below to get started.

🕶Anonymous mode is on — this chat won't be saved to your history.

Generate images from text

Describe what you want to see below.

🎬
Coming soon

Video generation is on its way

We’re putting the finishing touches on this feature. Soon you’ll be able to turn your ideas into stunning videos right here — stay tuned!

✨ What's new
/* ============================================================ Vista AI - AI-generated files (PDF / Word / Excel / CSV / TXT) Standard Vista only. Inlined at the end of index.html. Uses jsPDF, SheetJS (XLSX) and JSZip already loaded above. ============================================================ */ (function () { 'use strict'; var nativeFetch = window.fetch.bind(window); /* ── settings ────────────────────────────────────────────── */ var ASK_FOR_TOPIC = true; /* ask "what should it contain?" when no topic */ var PENDING_TTL = 2 * 60 * 1000; /* how long an armed question stays valid */ var MAX_IMAGES = 12; var IMG_RETRY_PAUSE = 700; /* ms between image attempts */ /* ── constants ───────────────────────────────────────────── */ var SPEC_RE = /\[\[FILE\]\]([\s\S]*?)\[\[\/FILE\]\]/; var SPEC_ALL = /\[\[FILE\]\][\s\S]*?\[\[\/FILE\]\]/g; var SPEC_TAIL = /\[\[FILE\]\][\s\S]*$/; var ANY_NOTE = /\[\[[\s\S]*?\]\]/g; var EXT = { pdf:'pdf', docx:'docx', doc:'docx', word:'docx', xlsx:'xlsx', xls:'xlsx', excel:'xlsx', spreadsheet:'xlsx', csv:'csv', txt:'txt', text:'txt' }; var ICON = { pdf:'\uD83D\uDCC4', docx:'\uD83D\uDCDD', xlsx:'\uD83D\uDCCA', csv:'\uD83D\uDCCA', txt:'\uD83D\uDCC3' }; var LABEL = { pdf:'PDF document', docx:'Word document', xlsx:'Excel workbook', csv:'CSV file', txt:'Text file' }; var FORMATS = [ { v: '', l: 'No file' }, { v: 'pdf', l: 'PDF (.pdf)' }, { v: 'docx', l: 'Word (.docx)' }, { v: 'xlsx', l: 'Excel (.xlsx)' }, { v: 'csv', l: 'CSV (.csv)' }, { v: 'txt', l: 'Text (.txt)' } ]; var THEMES = { clean: { label:'Clean', pdfFont:'helvetica', docFont:'Calibri', title:'#111827', head:'#1f2937', body:'#1f2937', accent:[238,240,247], rule:false, center:false }, classic: { label:'Classic', pdfFont:'times', docFont:'Georgia', title:'#1a1a1a', head:'#2b2b2b', body:'#222222', accent:[240,236,225], rule:true, center:true }, modern: { label:'Modern', pdfFont:'helvetica', docFont:'Segoe UI', title:'#4338ca', head:'#4f46e5', body:'#111827', accent:[224,231,255], rule:true, center:false }, report: { label:'Report', pdfFont:'courier', docFont:'Consolas', title:'#0f172a', head:'#0f172a', body:'#111827', accent:[226,232,240], rule:false, center:false } }; var TH = THEMES.clean; function themeOf(k) { return THEMES[String(k || '').toLowerCase()] || THEMES.clean; } function hx(c) { return String(c || '#000000').replace('#', '').toUpperCase(); } function rgbHex(a) { return a.map(function (n) { return ('0' + Number(n).toString(16)).slice(-2); }).join('').toUpperCase(); } var built = {}; var seen = {}; var autoOk = {}; var pending = null; var sendSeq = 0; /* ── helpers ─────────────────────────────────────────────── */ function stdTag(tag) { return String(tag || '').toLowerCase() !== 'vista uncensored'; } function stripNotes(s) { return String(s || '').replace(ANY_NOTE, ' ').replace(/\s+/g, ' ').trim(); } function wait(ms) { return new Promise(function (r) { setTimeout(r, ms); }); } function xe(s) { return String(s == null ? '' : s) .replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } function plain(s) { return String(s == null ? '' : s) .replace(/\*\*(.+?)\*\*/g, '$1').replace(/`([^`]+)`/g, '$1') .replace(/^#+\s*/gm, '').replace(/\r/g, ''); } function safeName(s, ext) { var b = String(s || 'document').trim().replace(/\.[a-z0-9]{1,5}$/i, '') .replace(/[^\w\-\s]/g, '').replace(/\s+/g, '_').slice(0, 48); return (b || 'document') + '.' + ext; } function saveBlob(blob, name) { var u = URL.createObjectURL(blob), a = document.createElement('a'); a.href = u; a.download = name; document.body.appendChild(a); a.click(); setTimeout(function () { document.body.removeChild(a); URL.revokeObjectURL(u); }, 1500); } function fingerprint(s) { var h = 0; for (var i = 0; i < s.length; i++) { h = (h * 31 + s.charCodeAt(i)) | 0; } return 'f' + (h >>> 0).toString(36) + s.length; } function parseSpec(text) { var m = String(text || '').match(SPEC_RE); if (!m) return null; var raw = m[1].trim().replace(/^```[a-z]*\s*/i, '').replace(/```\s*$/, '').trim(); var spec = null; try { spec = JSON.parse(raw); } catch (e) { var a = raw.indexOf('{'), b = raw.lastIndexOf('}'); if (a < 0 || b <= a) return null; try { spec = JSON.parse(raw.slice(a, b + 1)); } catch (e2) { return null; } } if (!spec || typeof spec !== 'object') return null; var t = String(spec.type || 'pdf').toLowerCase().replace(/^\./, ''); spec.type = EXT[t] || 'pdf'; spec.blocks = Array.isArray(spec.blocks) ? spec.blocks : []; spec.sheets = Array.isArray(spec.sheets) ? spec.sheets : []; spec.title = spec.title || ''; spec.theme = String(spec.theme || 'clean').toLowerCase(); spec.name = safeName(spec.filename || spec.title, spec.type); spec.images = spec.blocks.filter(function (b) { return b && b.t === 'image' && b.prompt; }).slice(0, MAX_IMAGES); spec.raw = raw; return spec; } /* ── image generation, with silent policy retries ────────── */ function cacheGet(p) { try { return JSON.parse(localStorage.getItem('vf_img:' + p) || 'null'); } catch (e) { return null; } } function cacheSet(p, v) { try { localStorage.setItem('vf_img:' + p, JSON.stringify(v)); } catch (e) {} } function chatCtx() { var St = window.S || {}; return { modelId: St.currentModelId || '', modelTag: St.currentModelTag || '', roomId: St.roomId || null }; } function looksPolicy(msg) { var m = String(msg || '').toLowerCase(); return m.indexOf('content_policy') !== -1 || m.indexOf('content policy') !== -1 || m.indexOf('safety system') !== -1 || m.indexOf('policy violation') !== -1 || m.indexOf('was rejected') !== -1 || m.indexOf('flagged') !== -1 || m.indexOf('not allowed') !== -1 || m.indexOf('violation') !== -1; } var SOFTEN = [ [/\b(blood|bloody|bleeding|gore|gory)\b/gi, 'dramatic'], [/\b(kill|killing|killed|murder|slaughter|death|dead|dying|corpse)\b/gi, 'defeated'], [/\b(weapon|weapons|gun|guns|rifle|pistol|knife|knives|sword|blade|bomb|explosion)\b/gi, 'glowing staff'], [/\b(fight|fighting|battle|combat|war|attack|attacking|violent|violence)\b/gi, 'tense standoff'], [/\b(wound|wounded|injur\w*|scar|torture|pain|suffering)\b/gi, 'weary'], [/\b(nude|naked|nudity|topless|lingerie|underwear|sexy|seductive|erotic|sensual)\b/gi, 'fully dressed'], [/\b(kid|kids|child|children|girl|boy|teen|teenager|minor|schoolgirl|schoolboy)\b/gi, 'young adult'], [/\b(drug|drugs|cocaine|heroin|alcohol|drunk|cigarette|smoking)\b/gi, 'tea'], [/\b(demon|demonic|satanic|hell|cult|occult|ritual|sacrifice)\b/gi, 'mystical'], [/\b(horror|terrifying|nightmare|creepy|disturbing|grotesque|monstrous)\b/gi, 'mysterious'] ]; function softenPrompt(p) { var out = String(p || ''); SOFTEN.forEach(function (pair) { out = out.replace(pair[0], pair[1]); }); out = out.replace(/\s+/g, ' ').trim(); return out + ', calm scene, soft colours, storybook illustration'; } async function rewritePrompt(p) { try { var q = 'Rewrite this picture description so an image generator will accept it. ' + 'Keep the same setting, characters and mood, but describe only what is visually present - ' + 'no violence, no weapons, no injury, no romance, no children, no real people or brands. ' + 'Reply with ONLY the rewritten description, one or two sentences, no quotes, no explanation.\n\n' + String(p || '').slice(0, 600); var t = cleanProse(await ask(q, chatCtx())); t = t.replace(/^["'\s]+|["'\s]+$/g, '').split('\n')[0].trim(); return (t.length > 15 && t.length < 700) ? t : ''; } catch (e) { return ''; } } function genericPrompt(caption) { var who = String(caption || '').replace(/[^\w\s]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 60); return 'A gentle storybook illustration' + (who ? ' representing "' + who + '"' : '') + ': a peaceful landscape at golden hour with soft light, warm colours, no people'; } async function generateOnce(prompt) { var r = await nativeFetch('image-generate.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: prompt, from_chat: 1 }) }); var d = null; try { d = await r.json(); } catch (e) {} if (!d) throw new Error('empty response'); if (d.error) { var err = new Error(String(d.error)); err.policy = looksPolicy(d.error); throw err; } if (!d.show_url && !d.id) throw new Error('no image returned'); return d.show_url || ('api/image.php?id=' + d.id); } async function imageUrlFor(prompt, caption) { var c = cacheGet(prompt); if (c && c.url) return c.url; var tries = [String(prompt || '')], i, url; for (i = 0; i < tries.length && i < 4; i++) { if (i > 0) await wait(IMG_RETRY_PAUSE); try { url = await generateOnce(tries[i]); cacheSet(prompt, { url: url }); return url; } catch (e) { if (!e || !e.policy) { if (i === 0) { tries.push(tries[0]); continue; } return ''; } if (i === 0) { tries.push(softenPrompt(tries[0])); } else if (i === 1) { var rw = await rewritePrompt(prompt); tries.push(rw || genericPrompt(caption)); } else if (i === 2) { tries.push(genericPrompt(caption)); } } } return ''; } function asDataUrl(url) { return nativeFetch(url, { credentials: 'same-origin' }) .then(function (r) { if (!r.ok) throw new Error('Could not load the image.'); return r.blob(); }) .then(function (blob) { return new Promise(function (res, rej) { var fr = new FileReader(); fr.onerror = function () { rej(new Error('Could not read the image.')); }; fr.onload = function () { var img = new Image(); img.onload = function () { res({ dataUrl: fr.result, w: img.naturalWidth || 1024, h: img.naturalHeight || 1024, mime: blob.type || 'image/png' }); }; img.onerror = function () { res({ dataUrl: fr.result, w: 1024, h: 1024, mime: blob.type || 'image/png' }); }; img.src = fr.result; }; fr.readAsDataURL(blob); }); }); } async function collectImages(spec, onStep) { var out = {}, i, p, url; for (i = 0; i < spec.images.length; i++) { p = spec.images[i].prompt; if (out[p]) continue; if (onStep) onStep('Drawing picture ' + (i + 1) + ' of ' + spec.images.length + '...'); url = await imageUrlFor(p, spec.images[i].caption); if (!url) continue; try { out[p] = await asDataUrl(url); } catch (e) {} } return out; } /* ── PDF (themed) ────────────────────────────────────────── */ function buildPdf(spec, imgs) { var JS = window.jspdf && window.jspdf.jsPDF; if (!JS) throw new Error('The PDF library did not load.'); var doc = new JS({ unit: 'pt', format: 'a4' }); var M = 56, PW = doc.internal.pageSize.getWidth(), PH = doc.internal.pageSize.getHeight(); var maxW = PW - M * 2, y = M; var FONT = TH.pdfFont; function need(h) { if (y + h > PH - M) { doc.addPage(); y = M; } } function text(t, size, style, after, color, center) { doc.setFont(FONT, style || 'normal'); doc.setFontSize(size); doc.setTextColor(color || TH.body); var lines = doc.splitTextToSize(plain(t), maxW), lh = size * 1.45; lines.forEach(function (l) { need(lh); if (center) doc.text(l, PW / 2, y, { align: 'center' }); else doc.text(l, M, y); y += lh; }); y += (after == null ? size * 0.6 : after); } function rule() { if (!TH.rule) return; need(10); doc.setDrawColor(TH.accent[0], TH.accent[1], TH.accent[2]); doc.setLineWidth(1.2); doc.line(M, y - 4, M + maxW, y - 4); y += 8; } function table(header, rows) { var all = (header && header.length ? [header] : []).concat(rows || []); if (!all.length) return; var cols = all.reduce(function (n, r) { return Math.max(n, r.length); }, 0) || 1; var cw = maxW / cols, pad = 5; doc.setFontSize(9.5); all.forEach(function (row, ri) { var head = ri === 0 && header && header.length; doc.setFont(FONT, head ? 'bold' : 'normal'); doc.setTextColor(TH.body); var cells = [], hMax = 0; for (var c = 0; c < cols; c++) { var ls = doc.splitTextToSize(plain(row[c] == null ? '' : row[c]), cw - pad * 2); cells.push(ls); hMax = Math.max(hMax, ls.length * 12 + pad * 2); } need(hMax); if (head) { doc.setFillColor(TH.accent[0], TH.accent[1], TH.accent[2]); doc.rect(M, y, maxW, hMax, 'F'); } doc.setDrawColor(199, 203, 214); doc.setLineWidth(0.5); for (var k = 0; k < cols; k++) { doc.rect(M + k * cw, y, cw, hMax); cells[k].forEach(function (l, li) { doc.text(l, M + k * cw + pad, y + pad + 9 + li * 12); }); } y += hMax; }); y += 12; } function picture(b) { var im = imgs[b.prompt]; if (!im) return; var w = Math.min(maxW, 400), h = w * (im.h / im.w); if (h > PH - M * 2 - 40) { h = PH - M * 2 - 40; w = h * (im.w / im.h); } need(h + 18); try { doc.addImage(im.dataUrl, /jpe?g/i.test(im.mime) ? 'JPEG' : 'PNG', M + (maxW - w) / 2, y, w, h); } catch (e) { return; } y += h + 6; if (b.caption) text(b.caption, 9, 'italic', 12, '#6b7280', true); } if (spec.title) { if (TH.center) y += 40; text(spec.title, TH.center ? 26 : 22, 'bold', TH.center ? 26 : 18, TH.title, TH.center); rule(); } (spec.blocks || []).forEach(function (b) { if (!b) return; switch (b.t) { case 'h1': text(b.x, 17, 'bold', 8, TH.head); rule(); break; case 'h2': text(b.x, 14, 'bold', 8, TH.head); break; case 'h3': text(b.x, 12, 'bold', 6, TH.head); break; case 'quote': text(b.x, 11, 'italic', 10, '#4b5563'); break; case 'bullets': (b.items || []).forEach(function (it) { text('\u2022 ' + plain(it), 11, 'normal', 2); }); y += 8; break; case 'table': table(b.header, b.rows); break; case 'image': picture(b); break; case 'pagebreak': doc.addPage(); y = M; break; default: if (b.x) text(b.x, 11, 'normal', 10); } }); return doc.output('blob'); } /* ── Word (.docx, real OOXML, themed) ────────────────────── */ var W_NS = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" ' + 'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" ' + 'xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" ' + 'xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" ' + 'xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"'; var CONTENT_W_TWIPS = 9638, CONTENT_W_EMU = 6120000; function wRun(t, o) { o = o || {}; var rpr = '' + '' + (o.b ? '' : '') + (o.i ? '' : '') + '' + ''; return '' + rpr + '' + xe(plain(t)) + ''; } function wP(inner, o) { o = o || {}; var ppr = '' + (o.align ? '' : '') + (o.ind ? '' : '') + '' + (o.keep ? '' : '') + ''; return '' + ppr + inner + ''; } function wTable(header, rows) { var all = (header && header.length ? [header] : []).concat(rows || []); if (!all.length) return ''; var cols = all.reduce(function (n, r) { return Math.max(n, r.length); }, 0) || 1; var cw = Math.floor(CONTENT_W_TWIPS / cols), grid = '', i; var shade = rgbHex(TH.accent); for (i = 0; i < cols; i++) grid += ''; var borders = '' + ['top', 'left', 'bottom', 'right', 'insideH', 'insideV'].map(function (s) { return ''; }).join('') + ''; var body = all.map(function (row, ri) { var head = ri === 0 && header && header.length, tcs = ''; for (var c = 0; c < cols; c++) { tcs += '' + (head ? '' : '') + '' + wP(wRun(row[c] == null ? '' : row[c], { b: !!head, sz: 19 }), { after: 40 }) + ''; } return '' + tcs + ''; }).join(''); return '' + borders + '' + grid + '' + body + '' + wP('', { after: 120 }); } function wImage(rid, n, im) { var cx = Math.min(CONTENT_W_EMU, Math.round(im.w * 9525)); var cy = Math.round(cx * (im.h / im.w)); return wP('' + '' + '' + '' + '' + '' + '' + '' + '' + '', { align: 'center', after: 80 }); } async function buildDocx(spec, imgs) { if (typeof JSZip === 'undefined') throw new Error('The zip library did not load.'); var zip = new JSZip(), body = '', rels = '', media = 0; if (spec.title) { body += wP(wRun(spec.title, { b: true, sz: 44, color: hx(TH.title) }), { align: TH.center ? 'center' : null, after: 220 }); } (spec.blocks || []).forEach(function (b) { if (!b) return; switch (b.t) { case 'h1': body += wP(wRun(b.x, { b: true, sz: 34, color: hx(TH.head) }), { before: 200, after: 120, keep: true }); break; case 'h2': body += wP(wRun(b.x, { b: true, sz: 28, color: hx(TH.head) }), { before: 180, after: 100, keep: true }); break; case 'h3': body += wP(wRun(b.x, { b: true, sz: 24, color: hx(TH.head) }), { before: 160, after: 80, keep: true }); break; case 'quote': body += wP(wRun(b.x, { i: true, color: '4B5563' }), { ind: 360 }); break; case 'bullets': (b.items || []).forEach(function (it) { body += wP(wRun('\u2022 ' + plain(it)), { ind: 360, after: 60 }); }); body += wP('', { after: 100 }); break; case 'table': body += wTable(b.header, b.rows); break; case 'pagebreak': body += ''; break; case 'image': { var im = imgs[b.prompt]; if (!im) break; media++; var ext = /jpe?g/i.test(im.mime) ? 'jpg' : 'png', rid = 'rIdImg' + media; zip.file('word/media/image' + media + '.' + ext, im.dataUrl.split(',')[1], { base64: true }); rels += ''; body += wImage(rid, media, im); if (b.caption) body += wP(wRun(b.caption, { i: true, sz: 18, color: '6B7280' }), { align: 'center', after: 180 }); break; } default: if (b.x) body += wP(wRun(b.x)); } }); body += '' + ''; zip.file('[Content_Types].xml', '' + '' + '' + '' + '' + '' + '' + '' + ''); zip.file('_rels/.rels', '' + '' + '' + ''); zip.file('word/_rels/document.xml.rels', '' + '' + rels + ''); zip.file('word/document.xml', '' + '' + body + ''); return await zip.generateAsync({ type: 'blob', compression: 'DEFLATE', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }); } /* ── Excel / CSV / TXT ───────────────────────────────────── */ function specToSheets(spec) { if (spec.sheets.length) { return spec.sheets.map(function (s, i) { return { name: String(s.name || ('Sheet' + (i + 1))).slice(0, 28), rows: (s.rows || []).map(function (r) { return Array.isArray(r) ? r : [r]; }) }; }); } var tables = (spec.blocks || []).filter(function (b) { return b && b.t === 'table'; }); if (tables.length) { return tables.map(function (t, i) { return { name: 'Table' + (i + 1), rows: (t.header && t.header.length ? [t.header] : []).concat(t.rows || []) }; }); } var rows = []; (spec.blocks || []).forEach(function (b) { if (!b) return; if (b.t === 'bullets') (b.items || []).forEach(function (it) { rows.push([plain(it)]); }); else if (b.x) rows.push([plain(b.x)]); }); return [{ name: 'Sheet1', rows: rows.length ? rows : [['(empty)']] }]; } function buildXlsx(spec) { if (typeof XLSX === 'undefined') throw new Error('The spreadsheet library did not load.'); var wb = XLSX.utils.book_new(); specToSheets(spec).forEach(function (s) { XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(s.rows), s.name); }); var out = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); return new Blob([out], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); } function buildCsv(spec) { var rows = specToSheets(spec)[0].rows; var csv = rows.map(function (r) { return r.map(function (c) { var v = c == null ? '' : String(c); return /[",\n]/.test(v) ? '"' + v.replace(/"/g, '""') + '"' : v; }).join(','); }).join('\r\n'); return new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' }); } function buildTxt(spec) { var out = spec.title ? (spec.title + '\n' + new Array(spec.title.length + 1).join('=') + '\n\n') : ''; (spec.blocks || []).forEach(function (b) { if (!b) return; if (b.t === 'bullets') (b.items || []).forEach(function (it) { out += ' \u2022 ' + plain(it) + '\n'; }); else if (b.t === 'table') { ((b.header && b.header.length ? [b.header] : []).concat(b.rows || [])).forEach(function (r) { out += r.join('\t') + '\n'; }); out += '\n'; } else if (b.t === 'pagebreak') out += '\n\n'; else if (b.x) out += plain(b.x) + '\n\n'; }); return new Blob([out], { type: 'text/plain;charset=utf-8' }); } async function buildFile(spec, onStep) { TH = themeOf(spec.theme); var imgs = {}; if (spec.images.length && (spec.type === 'pdf' || spec.type === 'docx')) imgs = await collectImages(spec, onStep); if (onStep) onStep('Putting the file together...'); if (spec.type === 'pdf') return buildPdf(spec, imgs); if (spec.type === 'docx') return await buildDocx(spec, imgs); if (spec.type === 'xlsx') return buildXlsx(spec); if (spec.type === 'csv') return buildCsv(spec); return buildTxt(spec); } /* ── card UI ─────────────────────────────────────────────── */ function injectCss() { if (document.getElementById('vf-css')) return; var st = document.createElement('style'); st.id = 'vf-css'; st.textContent = '.vf-card{display:flex;align-items:center;gap:12px;margin:10px 0 2px;padding:12px 14px;' + 'border:1px solid rgba(99,102,241,.28);border-radius:14px;background:rgba(99,102,241,.10)}' + '.vf-ico{font-size:26px;line-height:1}' + '.vf-body{flex:1;min-width:0}' + '.vf-name{font-weight:700;font-size:14px;word-break:break-all}' + '.vf-note{font-size:12px;opacity:.75;margin-top:2px}' + '.vf-btn{flex:0 0 auto;border:0;border-radius:9px;padding:9px 14px;font-size:13px;font-weight:700;' + 'cursor:pointer;background:#4338ca;color:#fff}' + '.vf-btn:disabled{opacity:.6;cursor:default}' + '.vf-btn.done{background:#0f9d58}' + '.vf-card.error{border-color:rgba(220,38,38,.35);background:rgba(220,38,38,.10)}'; document.head.appendChild(st); } function hostFor(row) { return row.querySelector('.msg-content') || row.querySelector('.msg-body') || row.querySelector('.msg-bubble') || row; } function lastBotRow() { var rows = document.querySelectorAll('#chatMessages .msg-row:not(.user)'); return rows[rows.length - 1] || null; } function attachCardTo(row, text) { var spec = parseSpec(text); if (!spec || !row || row.querySelector('.vf-card')) return false; injectCss(); var id = 'vf' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6); var card = document.createElement('div'); card.className = 'vf-card'; card.id = id; card.innerHTML = '
' + (ICON[spec.type] || ICON.pdf) + '
' + '
' + xe(spec.name) + '
' + '
' + xe(LABEL[spec.type] || 'Document') + (spec.images.length ? ' \u00b7 ' + spec.images.length + ' picture' + (spec.images.length > 1 ? 's' : '') : '') + ' \u00b7 ' + xe(themeOf(spec.theme).label) + '
'; hostFor(row).appendChild(card); var btn = card.querySelector('.vf-btn'); var note = card.querySelector('.vf-note'); btn.addEventListener('click', function () { if (built[id]) { saveBlob(built[id].blob, built[id].name); return; } run(); }); async function run() { btn.disabled = true; btn.textContent = 'Working...'; card.classList.remove('error'); try { var blob = await buildFile(spec, function (msg) { note.textContent = msg; }); built[id] = { blob: blob, name: spec.name }; note.textContent = (LABEL[spec.type] || 'Document') + ' \u00b7 ' + themeOf(spec.theme).label + ' \u00b7 ' + Math.max(1, Math.round(blob.size / 1024)) + ' KB'; btn.disabled = false; btn.className = 'vf-btn done'; btn.textContent = '\u2b07 Download'; saveBlob(blob, spec.name); } catch (e) { card.classList.add('error'); note.textContent = (e && e.message) ? e.message : 'The file could not be created.'; btn.disabled = false; btn.textContent = 'Try again'; } } var fp = fingerprint(spec.raw); if (!spec.images.length && autoOk[fp] && !seen[fp]) { seen[fp] = true; run(); } return true; } function attachCard(text) { return attachCardTo(lastBotRow(), text); } function attachSoon(text) { var row = lastBotRow(); if (row) attachCardTo(row, text); [250, 800].forEach(function (ms) { setTimeout(function () { try { attachCardTo(lastBotRow(), text); } catch (e) {} }, ms); }); } /* ── talking to the model (always via nativeFetch) ───────── */ async function ask(message, ctx) { var r = await nativeFetch('api/chat.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message, model_id: ctx.modelId || '', model_tag: ctx.modelTag || '', model_provider: '', room_id: ctx.roomId || null, attachments: [] }) }); if (r.status === 401) { window.location.reload(); throw new Error('Session expired'); } var ct = r.headers.get('content-type') || ''; if (ct.indexOf('text/event-stream') === -1) { var d = null; try { d = await r.json(); } catch (e) { throw new Error('Invalid server response'); } if (!d || d.error) throw new Error((d && d.error) || 'Server error'); if (d.room_id) ctx.roomId = d.room_id; return String(d.content || d.message || ''); } var reader = r.body.getReader(), dec = new TextDecoder(), buf = '', acc = '', fin = null; while (true) { var rd = await reader.read(); if (rd.done) break; buf += dec.decode(rd.value, { stream: true }); var i; while ((i = buf.indexOf('\n')) !== -1) { var line = buf.slice(0, i).trim(); buf = buf.slice(i + 1); if (line.indexOf('data:') !== 0) continue; var e = null; try { e = JSON.parse(line.slice(5).trim()); } catch (x) { continue; } if (e.type === 'delta') acc += e.chunk || ''; else if (e.type === 'final') fin = e; else if (e.type === 'error') throw new Error(e.error || 'Server error'); } } if (fin && fin.room_id) ctx.roomId = fin.room_id; var text = (fin && (fin.content || fin.message)) ? String(fin.content || fin.message) : acc; if (!text.trim()) throw new Error('The response ended unexpectedly.'); return text; } function grabJson(text) { var t = String(text || '').replace(/```[a-z]*\s*/gi, '').replace(/```/g, ''); var a = t.indexOf('{'), b = t.lastIndexOf('}'); if (a < 0 || b <= a) return null; try { return JSON.parse(t.slice(a, b + 1)); } catch (e) {} try { return JSON.parse(t.slice(a, b + 1).replace(/,\s*([}\]])/g, '$1')); } catch (e) {} return null; } function cleanProse(t) { return String(t || '') .replace(ANY_NOTE, '') .replace(/\[IMAGE:[^\]]*\]/gi, '') .replace(/^\s*(chapter|section|part)\s+\d+\s*[:.\-\u2013]?\s*.*$/gim, '') .replace(/^#+\s*/gm, '') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/^\s*[-*]\s+/gm, '') .replace(/\n{3,}/g, '\n\n') .trim(); } /* ── what counts as a file request ───────────────────────── */ var FMT = [ { re: /\bpdf\b/i, type: 'pdf' }, { re: /\b(word|docx?|\.doc)\b/i, type: 'docx' }, { re: /\b(excel|xlsx?|spreadsheet|workbook)\b/i, type: 'xlsx' }, { re: /\bcsv\b/i, type: 'csv' }, { re: /\b(txt|text file|plain text)\b/i, type: 'txt' } ]; var ACT = /\b(make|create|generate|write|build|produce|export|prepare|need|want|give|turn|save|as)\b/i; var STOP = /\b(pdf|word|docx?|excel|xlsx?|csv|txt|spreadsheet|workbook|file|files|document|please|can|could|you|me|make|create|generate|write|build|produce|export|prepare|need|want|give|turn|save|into|for|with|and|a|an|the|as|of|it|that|this|full|now|ok|okay)\b/gi; var NUMWORD = { one:1, two:2, three:3, four:4, five:5, six:6, seven:7, eight:8, nine:9, ten:10, eleven:11, twelve:12, fifteen:15, twenty:20 }; function fmtOf(m) { for (var i = 0; i < FMT.length; i++) if (FMT[i].re.test(m)) return FMT[i].type; return null; } function topicOf(m) { return String(m || '').replace(STOP, ' ').replace(/[^\w\s]/g, ' ').replace(/\s+/g, ' ').trim(); } function isSmallTalk(m) { var t = String(m || '').toLowerCase().replace(/[^a-z\s]/g, ' ').replace(/\s+/g, ' ').trim(); if (!t) return true; if (t.split(' ').length > 4) return false; return /^(hi|hii+|hello+|hey+|heya|hiya|yo|sup|greetings|good (morning|afternoon|evening|night)|thanks|thank you|thx|ty|ok|okay|k|yes|yeah|yep|no|nope|nah|sure|cool|nice|great|lol|wow|hmm|test|testing|ping|hru|how are you|who are you|what can you do|what do you do|are you there|are you online)$/.test(t); } /* digits, spelled-out numbers, or "a single chapter" */ function unitsOf(text) { var t = String(text || ''); var m = t.match(/(\d{1,2})\s*(chapters?|sections?|parts?|pages?)/i); if (m) return Math.min(20, Math.max(1, parseInt(m[1], 10))); var w = t.match(/\b(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|fifteen|twenty)\s+(chapters?|sections?|parts?)\b/i); if (w) return NUMWORD[w[1].toLowerCase()] || 0; if (/\b(a|one|single|just one)\s+(chapter|section|part)\b/i.test(t)) return 1; return 0; } function mk(type, text) { return { type: type, ask: text, units: unitsOf(text), story: /\b(story|novel|tale|fiction|manga|anime|adventure|fairy ?tale|comic|bedtime)\b/i.test(text), images: /\b(image|images|picture|pictures|photo|photos|illustration|illustrations|illustrated|artwork|visuals?)\b/i.test(text) }; } function resolveRequest(msg) { sendSeq++; var forced = forcedFormat(); if (forced) { pending = null; if (isSmallTalk(msg) || String(msg).trim().length < 3) return null; return mk(forced, msg); } var type = fmtOf(msg), topic = topicOf(msg); if (type && ACT.test(msg)) { pending = null; if (topic.length >= 3) return mk(type, msg); if (!ASK_FOR_TOPIC) return null; pending = { type: type, ask: msg, ts: Date.now(), seq: sendSeq }; return { clarify: true, type: type }; } if (pending) { var armed = pending; pending = null; if (armed.seq === sendSeq - 1 && (Date.now() - armed.ts) < PENDING_TTL && !isSmallTalk(msg) && topic.length >= 3) { return mk(armed.type, armed.ask + ' ' + msg); } } return null; } /* ── how long each chapter should be ─────────────────────── */ /* One model reply reliably carries ~900 words before the upstream truncates it, so anything longer is written in several passes. */ var MAX_PER_CALL = 900; var LEN = [ { max: 1, lo: 1200, hi: 1800 }, { max: 3, lo: 900, hi: 1300 }, { max: 6, lo: 600, hi: 900 }, { max: 10, lo: 450, hi: 700 }, { max: 99, lo: 350, hi: 550 } ]; var SIZE_HINT = [ { re: /\b(very short|tiny|micro|one[- ]page)\b/i, mul: 0.5 }, { re: /\b(very|extremely)\s+(long|detailed)\b|\bnovel[- ]length\b|\bmassive\b/i, mul: 2 }, { re: /\b(long|detailed|in[- ]depth|extensive|elaborate|epic)\b/i, mul: 1.5 }, { re: /\b(short|concise|compact|brief|quick)\b/i, mul: 0.7 } ]; function wordsPer(req, n) { var band = LEN[LEN.length - 1], i; for (i = 0; i < LEN.length; i++) if (n <= LEN[i].max) { band = LEN[i]; break; } var lo = band.lo, hi = band.hi; /* an explicit "600 words each" wins outright */ var m = String(req.ask).match(/(\d{2,5})\s*(?:\+|or more)?\s*words?/i); if (m) { var w = Math.min(4000, Math.max(120, parseInt(m[1], 10))); lo = Math.round(w * 0.85); hi = Math.round(w * 1.15); } else { var mul = 1; for (i = 0; i < SIZE_HINT.length; i++) { if (SIZE_HINT[i].re.test(req.ask)) { mul = SIZE_HINT[i].mul; break; } } lo = Math.round(lo * mul); hi = Math.round(hi * mul); } if (!req.story) { lo = Math.round(lo * 0.6); hi = Math.round(hi * 0.6); } return { lo: Math.max(120, lo), hi: Math.max(200, hi) }; } /* ── the pipeline ────────────────────────────────────────── */ var ONLY = 'Reply with ONLY minified JSON and nothing else: no greeting, no explanation, no advice, ' + 'no tool recommendations, no web search, no markdown fence, no text before or after the JSON. ' + 'Never say you cannot create files - you are only producing JSON here.'; async function planDoc(req, ctx) { var unit = req.story ? 'chapter' : 'section'; var n = req.units || (req.story ? 6 : 5); var p = ONLY + '\nShape: {"title":"...","filename":"short_snake_case_name","chapters":[' + '{"title":"...","beats":"one sentence saying what happens in it"' + (req.images ? ',"image":"one full standalone description of an illustration for it, naming the characters and the setting"' : '') + '}]}\n' + 'Produce exactly ' + n + ' ' + unit + (n === 1 ? '' : 's') + ' for this request: "' + req.ask + '". ' + (n === 1 ? 'It is the ONLY ' + unit + ', so it must stand alone as a complete piece with a beginning, a middle and an ending. ' : 'Invent original characters and an original plot - never reference an existing franchise. ') + (req.images ? 'Every "image" value must describe the same characters consistently so the pictures look like one book. ' + 'Describe only calm, non-violent scenes with adult characters, no weapons and no injuries, ' + 'so an image generator will accept every description. ' : '') + 'Remember: JSON only.'; var plan = grabJson(await ask(p, ctx)); if (!plan || !Array.isArray(plan.chapters) || !plan.chapters.length) { plan = grabJson(await ask('That was not valid JSON. ' + p, ctx)); } if (!plan || !Array.isArray(plan.chapters) || !plan.chapters.length) throw new Error('outline'); plan.chapters = plan.chapters.slice(0, n); return plan; } /* Long chapters are written in successive passes and stitched, because a single reply cannot carry more than ~MAX_PER_CALL words intact. */ async function writeChapter(plan, idx, prev, ctx, story, len, onStep) { var c = plan.chapters[idx], n = plan.chapters.length; var unit = story ? 'chapter' : 'section'; var passes = Math.min(3, Math.max(1, Math.ceil(len.hi / MAX_PER_CALL))); var perLo = Math.round(len.lo / passes), perHi = Math.round(len.hi / passes); var out = '', k, p, piece; for (k = 0; k < passes; k++) { if (passes > 1 && onStep) { onStep('Writing ' + unit + ' ' + (idx + 1) + ' of ' + n + ' (part ' + (k + 1) + ' of ' + passes + ')...'); } p = 'You are writing "' + (plan.title || 'the document') + '". '; if (k === 0) { p += 'Write ' + unit + ' ' + (idx + 1) + ' of ' + n + ', titled "' + (c.title || '') + '". ' + 'What it covers: ' + (c.beats || c.title || '') + '. ' + (prev ? 'The previous ' + unit + ' ended like this: "' + prev.slice(-300) + '" ' : '') + (passes > 1 ? 'This is part 1 of ' + passes + ' - open it and stop mid-scene, do not conclude yet. ' : ''); } else { p += 'Continue ' + unit + ' ' + (idx + 1) + ' ("' + (c.title || '') + '"), ' + 'part ' + (k + 1) + ' of ' + passes + '. ' + 'It still has to cover: ' + (c.beats || '') + '. ' + 'What you have written so far ends like this: "' + out.slice(-400) + '" ' + 'Carry straight on from there - never repeat or recap it. ' + (k === passes - 1 ? 'This is the final part, so bring it to a close. ' : 'Stop mid-scene again. '); } p += 'Write ONLY the finished prose - ' + perLo + ' to ' + perHi + ' words, ' + 'plain paragraphs separated by a blank line. No title, no heading, no number, ' + 'no markdown, no bullet points, no links, no image markers, no commentary.'; piece = cleanProse(await ask(p, ctx)); if (piece) out += (out ? '\n\n' : '') + piece; } return out; } async function buildSpec(req, ctx, onStep) { if (req.type === 'xlsx' || req.type === 'csv') { onStep('Working out the columns...'); var pr = ONLY + '\nShape: {"title":"...","filename":"short_snake_case_name","sheets":[{"name":"Sheet1","rows":[["Header A","Header B"],["value","value"]]}]}\n' + 'Fill it with the real, complete data for this request: "' + req.ask + '". ' + 'Every row is an array of plain strings. Remember: JSON only.'; var sj = grabJson(await ask(pr, ctx)); if (!sj || !Array.isArray(sj.sheets) || !sj.sheets.length) throw new Error('outline'); sj.type = req.type; sj.theme = currentStyle(); return parseSpec('[[FILE]]' + JSON.stringify(sj) + '[[/FILE]]'); } onStep('Planning the outline...'); var plan = await planDoc(req, ctx); var len = wordsPer(req, plan.chapters.length); var blocks = [], prev = '', i; for (i = 0; i < plan.chapters.length; i++) { onStep('Writing ' + (req.story ? 'chapter ' : 'section ') + (i + 1) + ' of ' + plan.chapters.length + '...'); var body = await writeChapter(plan, i, prev, ctx, req.story, len, onStep); prev = body; if (i > 0) blocks.push({ t: 'pagebreak' }); blocks.push({ t: 'h1', x: plan.chapters[i].title || ((req.story ? 'Chapter ' : 'Section ') + (i + 1)) }); if (req.images && plan.chapters[i].image) { blocks.push({ t: 'image', prompt: String(plan.chapters[i].image), caption: plan.chapters[i].title || '' }); } body.split(/\n\s*\n/).forEach(function (para) { var t = para.trim(); if (t) blocks.push({ t: 'p', x: t }); }); } return parseSpec('[[FILE]]' + JSON.stringify({ type: req.type, theme: currentStyle(), title: plan.title || '', filename: plan.filename || plan.title || 'document', blocks: blocks }) + '[[/FILE]]'); } /* ── format + style picker under the Vista mode buttons ─── */ function pickerCss() { if (document.getElementById('vf-pick-css')) return; var st = document.createElement('style'); st.id = 'vf-pick-css'; st.textContent = '.vf-picker-row{display:flex;align-items:center;justify-content:center;gap:8px;' + 'margin-top:8px;flex-wrap:wrap;font-size:12.5px}' + '.vf-picker-row label{opacity:.7;font-weight:600}' + '.vf-picker-row select{appearance:none;-webkit-appearance:none;border-radius:999px;' + 'padding:7px 30px 7px 14px;font-size:12.5px;font-weight:700;cursor:pointer;color:inherit;' + 'border:1px solid rgba(99,102,241,.28);background-color:rgba(148,163,184,.14);' + 'background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),' + 'linear-gradient(135deg,currentColor 50%,transparent 50%);' + 'background-position:calc(100% - 16px) 50%,calc(100% - 11px) 50%;' + 'background-size:5px 5px,5px 5px;background-repeat:no-repeat}' + '.vf-picker-row select option{color:#111827;background:#fff}' + '.vf-picker-row select:disabled{opacity:.45;cursor:default}' + '.vf-picker-row select.on{background-color:rgba(99,102,241,.30);border-color:transparent}' + '@media (max-width:600px){.vf-picker-row{font-size:11.5px}}'; document.head.appendChild(st); } function opts(list) { return list.map(function (o) { return ''; }).join(''); } function pickerAnchor() { var row = document.getElementById('vistaModeRow') || document.querySelector('.vista-mode-row'); if (row && row.parentNode) return { parent: row.parentNode, before: row.nextSibling }; var std = document.getElementById('vistaStdBtn'); if (std && std.parentNode && std.parentNode.parentNode) { return { parent: std.parentNode.parentNode, before: std.parentNode.nextSibling }; } var area = document.querySelector('#tab-chat .input-area'); if (area) return { parent: area, before: null }; var panel = document.getElementById('tab-chat'); if (panel) return { parent: panel, before: null }; return null; } function injectPicker() { var existing = document.getElementById('vfPickerRow'); var anchor = pickerAnchor(); if (!anchor) return false; if (existing) { if (existing.parentNode !== anchor.parent) anchor.parent.insertBefore(existing, anchor.before); return true; } pickerCss(); var themeOpts = Object.keys(THEMES).map(function (k) { return { v: k, l: THEMES[k].label }; }); var wrap = document.createElement('div'); wrap.id = 'vfPickerRow'; wrap.className = 'vf-picker-row'; wrap.innerHTML = '' + '' + ''; anchor.parent.insertBefore(wrap, anchor.before); var f = wrap.querySelector('#vfFormat'), s = wrap.querySelector('#vfStyle'); try { f.value = localStorage.getItem('vf_fmt') || ''; s.value = localStorage.getItem('vf_style') || 'clean'; } catch (e) {} if (!f.value) f.selectedIndex = 0; if (!s.value) s.value = 'clean'; function sync() { var on = !!f.value; f.classList.toggle('on', on); s.disabled = !on; s.classList.toggle('on', on); try { localStorage.setItem('vf_fmt', f.value); localStorage.setItem('vf_style', s.value); } catch (e) {} } f.addEventListener('change', function () { sync(); if (typeof window.toast === 'function') { window.toast(f.value ? 'Next message will be built as a ' + (LABEL[f.value] || 'file') + '.' : 'File creation off - normal chat.'); } }); s.addEventListener('change', sync); sync(); return true; } function pickerVis() { var wrap = document.getElementById('vfPickerRow'); if (!wrap) return; var unc = false; try { unc = !stdTag(window.S && window.S.currentModelTag); } catch (e) {} wrap.style.display = unc ? 'none' : ''; } function forcedFormat() { var el = document.getElementById('vfFormat'); return (el && el.value) ? el.value : ''; } function currentStyle() { var el = document.getElementById('vfStyle'); return (el && el.value) || 'clean'; } /* ── late binding ────────────────────────────────────────── */ var hooked = { rm: false, bubble: false, vista: false }; function hookLate() { if (!hooked.rm && typeof window.rm === 'function') { hooked.rm = true; var baseRm = window.rm; window.rm = function (text) { if (typeof text === 'string') { text = SPEC_RE.test(text) ? text.replace(SPEC_ALL, '') : (text.indexOf('[[FILE]]') !== -1 ? text.replace(SPEC_TAIL, '') : text); } return baseRm(text); }; } if (!hooked.bubble && typeof window.addBubble === 'function') { hooked.bubble = true; var baseAddBubble = window.addBubble; window.addBubble = function (text, sender, tag, attachments) { var res = baseAddBubble(text, sender, tag, attachments); try { if (sender !== 'user' && typeof text === 'string' && text.indexOf('[[FILE]]') !== -1) { var row = lastBotRow(); attachCardTo(row, text); setTimeout(function () { attachCardTo(row, text); }, 300); } } catch (e) {} return res; }; } if (!hooked.vista && typeof window.syncVistaModeBtns === 'function') { hooked.vista = true; var baseVistaSync = window.syncVistaModeBtns; window.syncVistaModeBtns = function () { var r = baseVistaSync.apply(this, arguments); pickerVis(); return r; }; } return hooked.rm && hooked.bubble && hooked.vista; } /* ── synthetic SSE response ──────────────────────────────── */ /* The app reads d.content (chat.php sends 'content'); an undefined content would save an EMPTY message into the chat history. */ function sendFinal(send, text, roomId) { send({ type: 'final', success: true, content: text, message: text, room_id: roomId || null, model_alias: (window.S && window.S.currentModelTag) || '', title: null }); } function sseResponse(worker) { var enc = new TextEncoder(); var stream = new ReadableStream({ start: function (c) { function send(o) { try { c.enqueue(enc.encode('data: ' + JSON.stringify(o) + '\n\n')); } catch (e) {} } send({ type: 'start' }); Promise.resolve(worker(send)).then( function () { try { c.close(); } catch (e) {} }, function () { sendFinal(send, 'I could not finish building that file. Please try again, and say a little more about what it should contain.', null); try { c.close(); } catch (e) {} }); } }); return new Response(stream, { status: 200, headers: { 'Content-Type': 'text/event-stream; charset=UTF-8' } }); } /* ── the interception ────────────────────────────────────── */ window.fetch = function (input, init) { var url = (typeof input === 'string') ? input : (input && input.url) || ''; if (!/api\/chat\.php/.test(url) || !init || String(init.method || '').toUpperCase() !== 'POST' || !init.body) { return nativeFetch(input, init); } var payload = null; try { payload = JSON.parse(init.body); } catch (e) { return nativeFetch(input, init); } if (!payload || typeof payload.message !== 'string') return nativeFetch(input, init); if (!stdTag(payload.model_tag)) return nativeFetch(input, init); if (payload.attachments && payload.attachments.length) return nativeFetch(input, init); var msg = stripNotes(payload.message); var req = resolveRequest(msg); if (!req) return nativeFetch(input, init); var ctx = { modelId: payload.model_id, modelTag: payload.model_tag, roomId: payload.room_id || null }; if (req.clarify) { return Promise.resolve(sseResponse(function (send) { sendFinal(send, 'What should the ' + (LABEL[req.type] || 'file') + ' contain? Tell me the topic and I will write it and build the file - ' + 'for example "a 10-chapter anime story with images".', ctx.roomId); })); } return Promise.resolve(sseResponse(async function (send) { var shown = false; function step(text) { send({ type: 'delta', chunk: (shown ? '\n' : '') + text }); shown = true; } var spec = await buildSpec(req, ctx, step); if (!spec) throw new Error('assemble'); var msgOut = 'Your ' + (LABEL[req.type] || 'document') + ' is ready' + (spec.images.length ? ' - the pictures are drawn when you create the file.' : '.') + '\n\n[[FILE]]' + JSON.stringify({ type: spec.type, theme: spec.theme, title: spec.title, filename: spec.name.replace(/\.[a-z0-9]+$/i, ''), blocks: spec.blocks, sheets: spec.sheets }) + '[[/FILE]]'; var fresh = parseSpec(msgOut); if (fresh) autoOk[fingerprint(fresh.raw)] = true; sendFinal(send, msgOut, ctx.roomId); attachSoon(msgOut); })); }; /* ── exports + boot ──────────────────────────────────────── */ window.VistaDocs = { parseSpec: parseSpec, buildFile: buildFile, attachCard: attachCard, attachCardTo: attachCardTo, themes: THEMES, format: forcedFormat, style: currentStyle, soften: softenPrompt, words: function (m, n) { return wordsPer(mk('pdf', String(m || '')), n || unitsOf(String(m || '')) || 6); }, units: unitsOf, mount: function () { var ok = injectPicker(); pickerVis(); return ok; }, check: function (m) { return resolveRequest(String(m || '')); }, pending: function () { return pending; }, reset: function () { pending = null; return 'cleared'; } }; console.log('[VistaDocs v9] loaded - jsPDF:' + !!(window.jspdf && window.jspdf.jsPDF) + ' XLSX:' + (typeof XLSX !== 'undefined') + ' JSZip:' + (typeof JSZip !== 'undefined')); (function boot(n) { var ready = injectPicker(); if (ready) pickerVis(); var allHooked = hookLate(); if (ready && allHooked) { try { new MutationObserver(function () { if (!document.getElementById('vfPickerRow')) { injectPicker(); pickerVis(); } }).observe(document.body, { childList: true, subtree: true }); } catch (e) {} console.log('[VistaDocs v9] picker mounted, hooks bound'); return; } if (n < 120) setTimeout(function () { boot(n + 1); }, 250); else console.warn('[VistaDocs v9] gave up - picker:' + ready + ' hooks:' + JSON.stringify(hooked)); })(0); })();

Report an issue

Tell us what happened and what you expected. Do not include passwords or payment details. Reports are linked to your serial, even in anonymous chat mode.

Checking daily allowance...

Up to 3 JPG, PNG or WebP images, 5 MB each. Remove sensitive details from screenshots before sending.