Explain binary and oversized files in the editor preview
Browse files
app/src/editor-ui.mjs
CHANGED
|
@@ -53,7 +53,8 @@ export function createEditorUI(element) {
|
|
| 53 |
input.addEventListener('scroll', syncScroll, { passive: true });
|
| 54 |
const resize = new ResizeObserver(syncScroll); resize.observe(input);
|
| 55 |
return {
|
| 56 |
-
setFile(nextPath, text) {
|
|
|
|
| 57 |
if (path === nextPath && input.value === text) return;
|
| 58 |
const changed = path !== nextPath;
|
| 59 |
path = nextPath;
|
|
|
|
| 53 |
input.addEventListener('scroll', syncScroll, { passive: true });
|
| 54 |
const resize = new ResizeObserver(syncScroll); resize.observe(input);
|
| 55 |
return {
|
| 56 |
+
setFile(nextPath, text, placeholder = nextPath ? 'Empty file.' : 'Select a file to inspect or edit it.') {
|
| 57 |
+
input.placeholder = placeholder;
|
| 58 |
if (path === nextPath && input.value === text) return;
|
| 59 |
const changed = path !== nextPath;
|
| 60 |
path = nextPath;
|
app/src/local-folder.mjs
CHANGED
|
@@ -15,8 +15,9 @@ export function encodeBytes(bytes) {
|
|
| 15 |
export const decodeBytes = text => text == null ? null : Uint8Array.from(atob(text), c => c.charCodeAt(0));
|
| 16 |
export function displayText(bytes) {
|
| 17 |
if (bytes == null) return '';
|
| 18 |
-
|
| 19 |
-
|
|
|
|
| 20 |
return text;
|
| 21 |
}
|
| 22 |
const unsupported = () => { throw Error('ENOTSUP: Local folders do not support Unix permissions, timestamps, or links.'); };
|
|
@@ -80,7 +81,7 @@ export class LocalFolderFs {
|
|
| 80 |
if (!handle) return null;
|
| 81 |
if (handle.kind !== 'file') throw Error('A folder now occupies ' + path + '. No file was overwritten.');
|
| 82 |
const file = await handle.getFile();
|
| 83 |
-
if (file.size > LOCAL_FILE_LIMIT) throw Error(path + ' is larger than the 1 MiB per-file limit.');
|
| 84 |
return new Uint8Array(await file.arrayBuffer());
|
| 85 |
}
|
| 86 |
async diskEntry(path) {
|
|
|
|
| 15 |
export const decodeBytes = text => text == null ? null : Uint8Array.from(atob(text), c => c.charCodeAt(0));
|
| 16 |
export function displayText(bytes) {
|
| 17 |
if (bytes == null) return '';
|
| 18 |
+
let text;
|
| 19 |
+
try { text = new TextDecoder('utf-8', { fatal: true }).decode(bytes); } catch { /* Not UTF-8 text. */ }
|
| 20 |
+
if (text === undefined || text.includes('\0')) throw Object.assign(Error('This is a binary or non-UTF-8 file. The editor supports UTF-8 text.'), { code: 'BINARY_FILE' });
|
| 21 |
return text;
|
| 22 |
}
|
| 23 |
const unsupported = () => { throw Error('ENOTSUP: Local folders do not support Unix permissions, timestamps, or links.'); };
|
|
|
|
| 81 |
if (!handle) return null;
|
| 82 |
if (handle.kind !== 'file') throw Error('A folder now occupies ' + path + '. No file was overwritten.');
|
| 83 |
const file = await handle.getFile();
|
| 84 |
+
if (file.size > LOCAL_FILE_LIMIT) throw Object.assign(Error(path + ' is larger than the 1 MiB per-file limit.'), { code: 'FILE_TOO_LARGE' });
|
| 85 |
return new Uint8Array(await file.arrayBuffer());
|
| 86 |
}
|
| 87 |
async diskEntry(path) {
|
app/src/main.mjs
CHANGED
|
@@ -98,12 +98,18 @@ function renderFiles() {
|
|
| 98 |
}
|
| 99 |
async function loadPreview(file) {
|
| 100 |
const version = ++previewVersion;
|
| 101 |
-
previewReady = false; editorUI.setFile(file, ''); controls();
|
| 102 |
try {
|
| 103 |
const content = await rpc('preview', { path: file });
|
| 104 |
if (version !== previewVersion || file !== selected || dirty) return;
|
| 105 |
editorUI.setFile(file, content); previewReady = true;
|
| 106 |
-
} catch (error) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
finally { if (version === previewVersion) controls(); }
|
| 108 |
}
|
| 109 |
function selectFile(file) {
|
|
|
|
| 98 |
}
|
| 99 |
async function loadPreview(file) {
|
| 100 |
const version = ++previewVersion;
|
| 101 |
+
previewReady = false; editorUI.setFile(file, '', 'Loading file…'); controls();
|
| 102 |
try {
|
| 103 |
const content = await rpc('preview', { path: file });
|
| 104 |
if (version !== previewVersion || file !== selected || dirty) return;
|
| 105 |
editorUI.setFile(file, content); previewReady = true;
|
| 106 |
+
} catch (error) {
|
| 107 |
+
if (version !== previewVersion || file !== selected) return;
|
| 108 |
+
const message = error.code === 'BINARY_FILE' ? 'No text preview available.\nThis file is binary or isn’t UTF-8 text.'
|
| 109 |
+
: error.code === 'FILE_TOO_LARGE' ? 'File too large to preview (1 MiB limit).'
|
| 110 |
+
: 'Unable to preview this file.\n' + error.message;
|
| 111 |
+
editorUI.setFile(file, '', message);
|
| 112 |
+
}
|
| 113 |
finally { if (version === previewVersion) controls(); }
|
| 114 |
}
|
| 115 |
function selectFile(file) {
|
app/tests/local-folder.test.mjs
CHANGED
|
@@ -55,7 +55,7 @@ test('Local folder: indexing/listing/stat do not read file bytes; workspace snap
|
|
| 55 |
const listing = await workspace.bash.exec('ls -l /workspace'); assert.equal(listing.exitCode, 0, listing.stderr);
|
| 56 |
assert.equal(disk.reads, 0); assert.equal(local.skipped, 1);
|
| 57 |
assert.equal(await workspace.read('a.txt'), 'hello'); assert.equal(disk.reads, 1);
|
| 58 |
-
await assert.rejects(workspace.read('large.bin'),
|
| 59 |
});
|
| 60 |
test('Local folder: real Bash and agent tools stage edits, mkdir, copy, rename, delete; Apply writes only staged paths', async t => {
|
| 61 |
const disk = await fixture(t);
|
|
@@ -118,7 +118,9 @@ test('Local folder: unsupported operations, excludes, binary editor and traversa
|
|
| 118 |
for (const method of ['chmod', 'symlink', 'link', 'utimes']) assert.throws(() => local[method]('/README.md', 0o777), /ENOTSUP/);
|
| 119 |
await assert.rejects(local.rm('/src', { recursive: true }), /not supported/);
|
| 120 |
await assert.rejects(local.writeFile('/.git/config', 'oops'), /excluded/);
|
| 121 |
-
assert.throws(() => displayText(new Uint8Array([0, 1])),
|
|
|
|
|
|
|
| 122 |
await workspace.write('../outside.txt', 'virtual only');
|
| 123 |
assert.deepEqual(await local.patches(), []);
|
| 124 |
await assert.rejects(disk.read('outside.txt'), { code: 'ENOENT' });
|
|
|
|
| 55 |
const listing = await workspace.bash.exec('ls -l /workspace'); assert.equal(listing.exitCode, 0, listing.stderr);
|
| 56 |
assert.equal(disk.reads, 0); assert.equal(local.skipped, 1);
|
| 57 |
assert.equal(await workspace.read('a.txt'), 'hello'); assert.equal(disk.reads, 1);
|
| 58 |
+
await assert.rejects(workspace.read('large.bin'), { code: 'FILE_TOO_LARGE' });
|
| 59 |
});
|
| 60 |
test('Local folder: real Bash and agent tools stage edits, mkdir, copy, rename, delete; Apply writes only staged paths', async t => {
|
| 61 |
const disk = await fixture(t);
|
|
|
|
| 118 |
for (const method of ['chmod', 'symlink', 'link', 'utimes']) assert.throws(() => local[method]('/README.md', 0o777), /ENOTSUP/);
|
| 119 |
await assert.rejects(local.rm('/src', { recursive: true }), /not supported/);
|
| 120 |
await assert.rejects(local.writeFile('/.git/config', 'oops'), /excluded/);
|
| 121 |
+
assert.throws(() => displayText(new Uint8Array([0, 1])), { code: 'BINARY_FILE' });
|
| 122 |
+
assert.throws(() => displayText(new Uint8Array([0xff, 0xfe])), { code: 'BINARY_FILE' });
|
| 123 |
+
assert.equal(displayText(new TextEncoder().encode('Café 🐾')), 'Café 🐾');
|
| 124 |
await workspace.write('../outside.txt', 'virtual only');
|
| 125 |
assert.deepEqual(await local.patches(), []);
|
| 126 |
await assert.rejects(disk.read('outside.txt'), { code: 'ENOENT' });
|
assets/{agent.worker-BLr4cL8b.js → agent.worker-yyKS-x2c.js}
RENAMED
|
@@ -1766,7 +1766,7 @@ Folder,8
|
|
| 1766 |
`},cFe=e=>e.length>6e3?e.slice(0,6e3)+`
|
| 1767 |
[Output truncated at 6,000 characters; use head, grep, or a narrower query.]`:e,k9=(e,t={})=>({content:[{type:`text`,text:cFe(e)}],details:t});function A9(e=O9,t,n){t&&(e=Object.fromEntries(t.filter(([,e])=>e.type===`file`).map(([e,t])=>[e,{mode:t.mode,content:Uint8Array.from(atob(t.base64),e=>e.charCodeAt(0))}])));let r=new tFe({...n?{fs:n.mount()}:{files:e},cwd:`/workspace`,executionLimitProfile:`hardened`,fetch:oFe(),executionLimits:{maxExecutionTimeMs:1e4,maxCommandCount:3e3,maxFileSystemBytes:16*1024**2,maxOutputSize:262144,maxSourceBytes:131072}}),i=e=>r.fs.resolvePath(`/workspace`,e),a=(async()=>{for(let[e,n]of t??[])n.type===`directory`?(await r.fs.mkdir(e,{recursive:!0}),await r.fs.chmod(e,n.mode)):n.type===`symlink`&&await r.fs.symlink(n.target,e)})();async function o(e,t){let n=i(e);if(new TextEncoder().encode(t).length>1024**2)throw Error(`The editor accepts files up to 1 MiB.`);await r.fs.mkdir(n.slice(0,n.lastIndexOf(`/`))||`/`,{recursive:!0}),await r.fs.writeFile(n,t)}let s=[{name:`bash`,label:`Bash`,description:`Run a bash command in /workspace. Supports cat, ls, grep, sed, awk, jq, sort, pipes, redirects, shell scripts, and curl. HTTP GET/HEAD/POST is enabled for any HTTPS URL; there is no host allowlist. Use curl -fsS URL; use set -o pipefail before piping HTTP output to jq. Browser CORS applies; redirects require the final URL. HTTP limits: 8 seconds, 1 MiB. No Node.js, Python, npm, or native binaries. Files persist across commands; cd and variables reset each call.`,parameters:Sn({command:Yr({description:`The shell command to execute.`})}),execute:async(e,{command:t},n)=>{let i=await r.exec(t,{signal:n});return k9(`${i.stdout}${i.stderr?`
|
| 1768 |
STDERR:
|
| 1769 |
-
`+i.stderr:``}\nExit code: ${i.exitCode}`,{exitCode:i.exitCode})}},{name:`read`,label:`Read file`,description:`Read a UTF-8 text file. Use bash head/grep for large files.`,parameters:Sn({path:Yr()}),execute:async(e,t)=>{let n=await r.fs.realpath(i(t.path));return k9(await r.fs.readFile(n),{path:n})}},{name:`write`,label:`Write file`,description:`Create or replace a UTF-8 text file. Parent directories are created.`,parameters:Sn({path:Yr(),content:Yr()}),execute:async(e,t)=>(await o(t.path,t.content),k9(`Wrote `+i(t.path)))},{name:`edit`,label:`Edit file`,description:`Replace one exact occurrence of oldText with newText in a UTF-8 file. oldText must match exactly once.`,parameters:Sn({path:Yr(),oldText:Yr(),newText:Yr()}),execute:async(e,t)=>{let n=await r.fs.readFile(i(t.path));if(!t.oldText||n.split(t.oldText).length!==2)throw Error(`oldText must match exactly once. Read the file first.`);return await o(t.path,n.replace(t.oldText,()=>t.newText)),k9(`Edited `+i(t.path))}}];async function c(){if(n)return n.listing();let e={};for(let t of r.fs.getAllPaths().sort())t.startsWith(`/bin/`)||t.startsWith(`/usr/bin/`)||t.startsWith(`/dev/`)||t.startsWith(`/proc/`)||(await r.fs.lstat(t)).isFile&&(e[t]=await r.fs.readFile(t));return e}async function l(){if(n)return null;let e=[];for(let t of r.fs.getAllPaths().sort()){if([`/bin`,`/usr/bin`,`/dev`,`/proc`].some(e=>t===e||t.startsWith(e+`/`)))continue;let n=await r.fs.lstat(t);if(n.isSymbolicLink)e.push([t,{type:`symlink`,target:await r.fs.readlink(t)}]);else if(n.isDirectory)e.push([t,{type:`directory`,mode:n.mode}]);else if(n.isFile){let i=await r.fs.readFileBuffer(t),a=``;for(let e=0;e<i.length;e+=32768)a+=String.fromCharCode(...i.subarray(e,e+32768));e.push([t,{type:`file`,base64:btoa(a),mode:n.mode}])}}return e}return{bash:r,tools:s,ready:a,write:o,snapshot:c,serialize:l,localFolder:n,read:e=>r.fs.readFile(i(e))}}async function lFe(){let e=await new Promise((e,t)=>{let n=indexedDB.open(`minicpm5-pi-workspace`,1);n.onupgradeneeded=()=>n.result.createObjectStore(`state`),n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)});return{async load(t=`current`){return new Promise((n,r)=>{let i=e.transaction(`state`).objectStore(`state`).get(t);i.onsuccess=()=>n(i.result),i.onerror=()=>r(i.error)})},async save(t,n=`current`){return new Promise((r,i)=>{let a=e.transaction(`state`,`readwrite`);a.objectStore(`state`).put(t,n),a.oncomplete=r,a.onerror=()=>i(a.error),a.onabort=()=>i(a.error)})}}}const j9=1024**2,M9=16*j9,N9=new Set([`.git`,`node_modules`,`.venv`,`__pycache__`]),P9=(e,t)=>e?.length===t?.length&&(e==null||e.every((e,n)=>e===t[n]));function F9(e){if(e==null)return null;let t=``;for(let n=0;n<e.length;n+=32768)t+=String.fromCharCode(...e.subarray(n,n+32768));return btoa(t)}const I9=e=>e==null?null:Uint8Array.from(atob(e),e=>e.charCodeAt(0));function uFe(e){if(e==null)return``;let t=new TextDecoder(`utf-8`,{fatal:!0}).decode(e);if(t.includes(`\0`))throw Error(`This is a binary file. The editor supports UTF-8 text.`);return t}const L9=()=>{throw Error(`ENOTSUP: Local folders do not support Unix permissions, timestamps, or links.`)};var R9=class e{constructor(e){this.handle=e,this.memory=new N2({},{maxTotalBytes:M9}),this.original=new Map([[`/`,{kind:`directory`,handle:e}]]),this.baselines=new Map,this.dirty=new Set,this.operations=new Map,this.skipped=0,this.baselineBytes=0}static async open(t,{signal:n,patches:r=[]}={}){if(t?.kind!==`directory`)throw Error(`Select a folder.`);let i=new e(t),a=0;async function o(e,t,r){if(r>40)throw Error(`This folder is too deeply nested. Choose a smaller project folder.`);for await(let[s,c]of e.entries()){if(n?.throwIfAborted(),N9.has(s)){i.skipped++;continue}if(++a>5e3)throw Error(`This folder contains more than 5,000 entries. Choose a smaller project folder.`);let e=t+`/`+s;i.original.set(e,{kind:c.kind,handle:c}),c.kind===`directory`?(await i.memory.mkdir(e,{recursive:!0}),await o(c,e,r+1)):i.memory.writeFileLazy(e,()=>i.loadOriginal(e))}}await o(t,``,0);for(let e of r){let t=i.path(e.path);if(t===`/`)throw Error(`Invalid saved local-folder change.`);let n=I9(e.before);if(i.baselineBytes+=n?.length??0,i.baselineBytes>M9)throw Error(`Saved changes exceed the 16 MiB local-folder limit.`);i.baselines.set(t,n),i.dirty.add(t),e.kind===`directory`?await i.memory.mkdir(t,{recursive:!0}):e.after===null?await i.memory.rm(t,{force:!0}):(await i.memory.mkdir(i.parent(t),{recursive:!0}),await i.memory.writeFile(t,I9(e.after)))}return i}path(e){if(typeof e!=`string`||e.includes(`\0`))throw Error(`Invalid file path.`);let t=this.memory.resolvePath(`/`,e);if(t.split(`/`).some(e=>N9.has(e)))throw Error(`This folder is excluded: .git, node_modules, .venv, and __pycache__.`);return t}parent(e){return e.slice(0,e.lastIndexOf(`/`))||`/`}async diskBytes(e){let t=await this.diskEntry(e);if(!t)return null;if(t.kind!==`file`)throw Error(`A folder now occupies `+e+`. No file was overwritten.`);let n=await t.getFile();if(n.size>j9)throw Error(e+` is larger than the 1 MiB per-file limit.`);return new Uint8Array(await n.arrayBuffer())}async diskEntry(e){let t=this.handle,n=this.path(e).split(`/`).filter(Boolean);if(!n.length)return t;try{for(let e of n.slice(0,-1))t=await t.getDirectoryHandle(e);try{return await t.getFileHandle(n.at(-1))}catch(e){if(e.name!==`TypeMismatchError`)throw e;return await t.getDirectoryHandle(n.at(-1))}}catch(e){if(e.name===`NotFoundError`)return null;throw e}}async loadOriginal(e){let t=await this.diskBytes(e);if(!t)throw Error(`ENOENT: File was removed from disk: `+e+`. Refresh the folder.`);if(!this.baselines.has(e)){if(this.baselineBytes+t.length>M9)throw Error(`Local file reads reached the 16 MiB session limit. Apply or discard changes, then Refresh.`);this.baselines.set(e,t),this.baselineBytes+=t.length}return t}async baseline(e){this.baselines.has(e)||this.baselines.set(e,this.original.get(e)?.kind===`file`?await this.loadOriginal(e):null)}async atPath(e,t){let n=(this.operations.get(e)??Promise.resolve()).catch(()=>{}).then(t);this.operations.set(e,n);try{return await n}finally{this.operations.get(e)===n&&this.operations.delete(e)}}async readFile(e,t){let n=this.path(e);return this.atPath(n,()=>this.memory.readFile(n,t))}async readFileBuffer(e){let t=this.path(e);return this.atPath(t,()=>this.memory.readFileBuffer(t))}async readFileBytes(e){let t=this.path(e);return this.atPath(t,()=>this.memory.readFileBytes(t))}async exists(e){return this.memory.exists(this.path(e))}async stat(e){let t=this.path(e),n=this.original.get(t);if(n?.kind===`file`&&!this.dirty.has(t)){let e=await n.handle.getFile();return{isFile:!0,isDirectory:!1,isSymbolicLink:!1,mode:420,size:this.baselines.get(t)?.length??e.size,mtime:new Date(e.lastModified)}}return this.memory.stat(t)}async lstat(e){return this.stat(e)}async realpath(e){return this.memory.realpath(this.path(e))}resolvePath(e,t){return this.memory.resolvePath(e,t)}getAllPaths(){return this.memory.getAllPaths()}async readdir(e){return this.memory.readdir(this.path(e))}async readdirWithFileTypes(e){return this.memory.readdirWithFileTypes(this.path(e))}async mkdir(e,t){let n=this.path(e),r=new Set(this.getAllPaths());await this.memory.mkdir(n,t);for(let e of this.getAllPaths())r.has(e)||(this.dirty.add(e),this.baselines.set(e,null))}async writeFile(e,t,n){let r=this.path(e);return this.atPath(r,()=>this.writeAtPath(r,t,n))}async writeAtPath(e,t,n){if(e===`/`)throw Error(`EISDIR: Cannot replace the selected folder.`);let r=new N2({},{maxTotalBytes:j9});await r.writeFile(`/data`,t,n);let i=await r.readFileBuffer(`/data`);if(i.length>j9)throw Error(`Local files are limited to 1 MiB.`);if(!await this.memory.exists(this.parent(e)))throw Error(`ENOENT: Parent folder does not exist: `+this.parent(e));await this.baseline(e),await this.memory.writeFile(e,i),this.dirty.add(e)}async appendFile(e,t,n){let r=new N2({"/data":await this.exists(e)?await this.readFileBuffer(e):new Uint8Array},{maxTotalBytes:j9});await r.appendFile(`/data`,t,n),await this.writeFile(e,await r.readFileBuffer(`/data`))}async rm(e,t){let n=this.path(e);return this.atPath(n,()=>this.removeAtPath(n,t))}async removeAtPath(e,t){if(e===`/`)throw Error(`Cannot remove the selected local folder.`);if(!await this.exists(e))return this.memory.rm(e,t);if((await this.stat(e)).isDirectory){if(this.original.has(e))throw Error(`Removing or renaming existing local folders is not supported. Remove individual files instead.`);for(let t of this.getAllPaths().filter(t=>t.startsWith(e+`/`)))if(this.original.has(t))throw Error(`This folder contains existing local files.`);let n=this.getAllPaths().filter(t=>t===e||t.startsWith(e+`/`));await this.memory.rm(e,t);for(let e of n)this.dirty.delete(e),this.baselines.delete(e)}else await this.baseline(e),await this.memory.rm(e,t),this.dirty.add(e)}async cp(e,t,n){let r=this.path(e),i=this.path(t);if(await this.exists(i)&&(await this.stat(i)).isDirectory&&(i+=`/`+r.split(`/`).at(-1)),r===i||i.startsWith(r+`/`))throw Error(`Cannot copy a path into itself.`);if((await this.stat(r)).isDirectory){if(!n?.recursive)throw Error(`EISDIR: Use cp -r to copy a directory.`);await this.mkdir(i,{recursive:!0});for(let e of await this.readdir(r))await this.cp(r+`/`+e,i+`/`+e,n)}else await this.writeFile(i,await this.readFileBuffer(r))}async mv(e,t){if((await this.stat(e)).isDirectory)throw Error(`Renaming local folders is not supported.`);await this.cp(e,t),await this.rm(e)}chmod=L9;symlink=L9;link=L9;readlink=L9;utimes=L9;async patches(){let e=[];for(let t of[...this.dirty].sort()){let n=this.baselines.get(t)??null,r=await this.exists(t),i=r&&(await this.memory.stat(t)).isDirectory?`directory`:`file`,a=r&&i===`file`?await this.readFileBuffer(t):null;i===`file`&&P9(n,a)||e.push({path:t,kind:i,before:F9(n),after:F9(a)})}return e}async listing(){let e=Object.create(null);for(let t of this.getAllPaths()){let n=this.original.get(t);(n?.kind===`file`||!n&&(await this.memory.stat(t)).isFile)&&(e[`/workspace`+t]=null)}return e}async status(){return{name:this.handle.name,count:(await this.patches()).length,skipped:this.skipped}}async apply(e){let t=await this.patches(),n=async e=>{let t=await this.diskEntry(e.path);if(e.kind===`directory`){if(t)throw Error(`Conflict: `+e.path+` was created on disk.`)}else if(!P9(await this.diskBytes(e.path),I9(e.before)))throw Error(`Conflict: `+e.path+` changed on disk. Nothing will overwrite it. Discard staged edits and refresh to read the new version.`)};for(let r of t)e?.throwIfAborted(),await n(r);let r=0;try{for(let i of t){e?.throwIfAborted(),await n(i);let t=i.path.split(`/`).filter(Boolean),a=this.handle;for(let e of t.slice(0,-1))a=await a.getDirectoryHandle(e);let o=t.at(-1);if(i.kind===`directory`){let e=await a.getDirectoryHandle(o,{create:!0});this.original.set(i.path,{kind:`directory`,handle:e})}else if(i.after===null)await a.removeEntry(o),this.original.delete(i.path),this.baselineBytes-=this.baselines.get(i.path)?.length??0,this.baselines.delete(i.path);else{let t=await a.getFileHandle(o,{create:!0}),n;try{n=await t.createWritable(),await n.write(I9(i.after)),e?.throwIfAborted(),await n.close()}catch(e){if(await n?.abort().catch(()=>{}),i.before===null)try{(await t.getFile()).size===0&&await t.isSameEntry(await a.getFileHandle(o))&&await a.removeEntry(o)}catch{}throw e}this.original.set(i.path,{kind:`file`,handle:t}),this.baselineBytes+=I9(i.after).length-(this.baselines.get(i.path)?.length??0),this.baselines.set(i.path,I9(i.after))}this.dirty.delete(i.path),r++}}catch(e){throw Error(`${r} of ${t.length} changes applied. Remaining changes are still staged. ${e.message}`)}return r}mount(){return new J6({mounts:[{mountPoint:`/workspace`,filesystem:this}]})}};const z9=e=>postMessage(e),B9=MI(z9);let V9,H9,U9,W9=!1,G9,K9=0,q9,J9,Y9;const X9=`You are Pi, a coding assistant powered by MiniCPM5-2B, a language model from OpenBMB.
|
| 1770 |
The model, inference, tools, and workspace run entirely in the user's browser. Workspace files are stored in this browser, not on the user's operating-system filesystem.
|
| 1771 |
Use your tools to inspect and change actual files. Never claim a command ran or a file changed without a successful tool result.
|
| 1772 |
When the user explicitly asks you to use a tool, call that tool even if you already know the answer.
|
|
|
|
| 1766 |
`},cFe=e=>e.length>6e3?e.slice(0,6e3)+`
|
| 1767 |
[Output truncated at 6,000 characters; use head, grep, or a narrower query.]`:e,k9=(e,t={})=>({content:[{type:`text`,text:cFe(e)}],details:t});function A9(e=O9,t,n){t&&(e=Object.fromEntries(t.filter(([,e])=>e.type===`file`).map(([e,t])=>[e,{mode:t.mode,content:Uint8Array.from(atob(t.base64),e=>e.charCodeAt(0))}])));let r=new tFe({...n?{fs:n.mount()}:{files:e},cwd:`/workspace`,executionLimitProfile:`hardened`,fetch:oFe(),executionLimits:{maxExecutionTimeMs:1e4,maxCommandCount:3e3,maxFileSystemBytes:16*1024**2,maxOutputSize:262144,maxSourceBytes:131072}}),i=e=>r.fs.resolvePath(`/workspace`,e),a=(async()=>{for(let[e,n]of t??[])n.type===`directory`?(await r.fs.mkdir(e,{recursive:!0}),await r.fs.chmod(e,n.mode)):n.type===`symlink`&&await r.fs.symlink(n.target,e)})();async function o(e,t){let n=i(e);if(new TextEncoder().encode(t).length>1024**2)throw Error(`The editor accepts files up to 1 MiB.`);await r.fs.mkdir(n.slice(0,n.lastIndexOf(`/`))||`/`,{recursive:!0}),await r.fs.writeFile(n,t)}let s=[{name:`bash`,label:`Bash`,description:`Run a bash command in /workspace. Supports cat, ls, grep, sed, awk, jq, sort, pipes, redirects, shell scripts, and curl. HTTP GET/HEAD/POST is enabled for any HTTPS URL; there is no host allowlist. Use curl -fsS URL; use set -o pipefail before piping HTTP output to jq. Browser CORS applies; redirects require the final URL. HTTP limits: 8 seconds, 1 MiB. No Node.js, Python, npm, or native binaries. Files persist across commands; cd and variables reset each call.`,parameters:Sn({command:Yr({description:`The shell command to execute.`})}),execute:async(e,{command:t},n)=>{let i=await r.exec(t,{signal:n});return k9(`${i.stdout}${i.stderr?`
|
| 1768 |
STDERR:
|
| 1769 |
+
`+i.stderr:``}\nExit code: ${i.exitCode}`,{exitCode:i.exitCode})}},{name:`read`,label:`Read file`,description:`Read a UTF-8 text file. Use bash head/grep for large files.`,parameters:Sn({path:Yr()}),execute:async(e,t)=>{let n=await r.fs.realpath(i(t.path));return k9(await r.fs.readFile(n),{path:n})}},{name:`write`,label:`Write file`,description:`Create or replace a UTF-8 text file. Parent directories are created.`,parameters:Sn({path:Yr(),content:Yr()}),execute:async(e,t)=>(await o(t.path,t.content),k9(`Wrote `+i(t.path)))},{name:`edit`,label:`Edit file`,description:`Replace one exact occurrence of oldText with newText in a UTF-8 file. oldText must match exactly once.`,parameters:Sn({path:Yr(),oldText:Yr(),newText:Yr()}),execute:async(e,t)=>{let n=await r.fs.readFile(i(t.path));if(!t.oldText||n.split(t.oldText).length!==2)throw Error(`oldText must match exactly once. Read the file first.`);return await o(t.path,n.replace(t.oldText,()=>t.newText)),k9(`Edited `+i(t.path))}}];async function c(){if(n)return n.listing();let e={};for(let t of r.fs.getAllPaths().sort())t.startsWith(`/bin/`)||t.startsWith(`/usr/bin/`)||t.startsWith(`/dev/`)||t.startsWith(`/proc/`)||(await r.fs.lstat(t)).isFile&&(e[t]=await r.fs.readFile(t));return e}async function l(){if(n)return null;let e=[];for(let t of r.fs.getAllPaths().sort()){if([`/bin`,`/usr/bin`,`/dev`,`/proc`].some(e=>t===e||t.startsWith(e+`/`)))continue;let n=await r.fs.lstat(t);if(n.isSymbolicLink)e.push([t,{type:`symlink`,target:await r.fs.readlink(t)}]);else if(n.isDirectory)e.push([t,{type:`directory`,mode:n.mode}]);else if(n.isFile){let i=await r.fs.readFileBuffer(t),a=``;for(let e=0;e<i.length;e+=32768)a+=String.fromCharCode(...i.subarray(e,e+32768));e.push([t,{type:`file`,base64:btoa(a),mode:n.mode}])}}return e}return{bash:r,tools:s,ready:a,write:o,snapshot:c,serialize:l,localFolder:n,read:e=>r.fs.readFile(i(e))}}async function lFe(){let e=await new Promise((e,t)=>{let n=indexedDB.open(`minicpm5-pi-workspace`,1);n.onupgradeneeded=()=>n.result.createObjectStore(`state`),n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)});return{async load(t=`current`){return new Promise((n,r)=>{let i=e.transaction(`state`).objectStore(`state`).get(t);i.onsuccess=()=>n(i.result),i.onerror=()=>r(i.error)})},async save(t,n=`current`){return new Promise((r,i)=>{let a=e.transaction(`state`,`readwrite`);a.objectStore(`state`).put(t,n),a.oncomplete=r,a.onerror=()=>i(a.error),a.onabort=()=>i(a.error)})}}}const j9=1024**2,M9=16*j9,N9=new Set([`.git`,`node_modules`,`.venv`,`__pycache__`]),P9=(e,t)=>e?.length===t?.length&&(e==null||e.every((e,n)=>e===t[n]));function F9(e){if(e==null)return null;let t=``;for(let n=0;n<e.length;n+=32768)t+=String.fromCharCode(...e.subarray(n,n+32768));return btoa(t)}const I9=e=>e==null?null:Uint8Array.from(atob(e),e=>e.charCodeAt(0));function uFe(e){if(e==null)return``;let t;try{t=new TextDecoder(`utf-8`,{fatal:!0}).decode(e)}catch{}if(t===void 0||t.includes(`\0`))throw Object.assign(Error(`This is a binary or non-UTF-8 file. The editor supports UTF-8 text.`),{code:`BINARY_FILE`});return t}const L9=()=>{throw Error(`ENOTSUP: Local folders do not support Unix permissions, timestamps, or links.`)};var R9=class e{constructor(e){this.handle=e,this.memory=new N2({},{maxTotalBytes:M9}),this.original=new Map([[`/`,{kind:`directory`,handle:e}]]),this.baselines=new Map,this.dirty=new Set,this.operations=new Map,this.skipped=0,this.baselineBytes=0}static async open(t,{signal:n,patches:r=[]}={}){if(t?.kind!==`directory`)throw Error(`Select a folder.`);let i=new e(t),a=0;async function o(e,t,r){if(r>40)throw Error(`This folder is too deeply nested. Choose a smaller project folder.`);for await(let[s,c]of e.entries()){if(n?.throwIfAborted(),N9.has(s)){i.skipped++;continue}if(++a>5e3)throw Error(`This folder contains more than 5,000 entries. Choose a smaller project folder.`);let e=t+`/`+s;i.original.set(e,{kind:c.kind,handle:c}),c.kind===`directory`?(await i.memory.mkdir(e,{recursive:!0}),await o(c,e,r+1)):i.memory.writeFileLazy(e,()=>i.loadOriginal(e))}}await o(t,``,0);for(let e of r){let t=i.path(e.path);if(t===`/`)throw Error(`Invalid saved local-folder change.`);let n=I9(e.before);if(i.baselineBytes+=n?.length??0,i.baselineBytes>M9)throw Error(`Saved changes exceed the 16 MiB local-folder limit.`);i.baselines.set(t,n),i.dirty.add(t),e.kind===`directory`?await i.memory.mkdir(t,{recursive:!0}):e.after===null?await i.memory.rm(t,{force:!0}):(await i.memory.mkdir(i.parent(t),{recursive:!0}),await i.memory.writeFile(t,I9(e.after)))}return i}path(e){if(typeof e!=`string`||e.includes(`\0`))throw Error(`Invalid file path.`);let t=this.memory.resolvePath(`/`,e);if(t.split(`/`).some(e=>N9.has(e)))throw Error(`This folder is excluded: .git, node_modules, .venv, and __pycache__.`);return t}parent(e){return e.slice(0,e.lastIndexOf(`/`))||`/`}async diskBytes(e){let t=await this.diskEntry(e);if(!t)return null;if(t.kind!==`file`)throw Error(`A folder now occupies `+e+`. No file was overwritten.`);let n=await t.getFile();if(n.size>j9)throw Object.assign(Error(e+` is larger than the 1 MiB per-file limit.`),{code:`FILE_TOO_LARGE`});return new Uint8Array(await n.arrayBuffer())}async diskEntry(e){let t=this.handle,n=this.path(e).split(`/`).filter(Boolean);if(!n.length)return t;try{for(let e of n.slice(0,-1))t=await t.getDirectoryHandle(e);try{return await t.getFileHandle(n.at(-1))}catch(e){if(e.name!==`TypeMismatchError`)throw e;return await t.getDirectoryHandle(n.at(-1))}}catch(e){if(e.name===`NotFoundError`)return null;throw e}}async loadOriginal(e){let t=await this.diskBytes(e);if(!t)throw Error(`ENOENT: File was removed from disk: `+e+`. Refresh the folder.`);if(!this.baselines.has(e)){if(this.baselineBytes+t.length>M9)throw Error(`Local file reads reached the 16 MiB session limit. Apply or discard changes, then Refresh.`);this.baselines.set(e,t),this.baselineBytes+=t.length}return t}async baseline(e){this.baselines.has(e)||this.baselines.set(e,this.original.get(e)?.kind===`file`?await this.loadOriginal(e):null)}async atPath(e,t){let n=(this.operations.get(e)??Promise.resolve()).catch(()=>{}).then(t);this.operations.set(e,n);try{return await n}finally{this.operations.get(e)===n&&this.operations.delete(e)}}async readFile(e,t){let n=this.path(e);return this.atPath(n,()=>this.memory.readFile(n,t))}async readFileBuffer(e){let t=this.path(e);return this.atPath(t,()=>this.memory.readFileBuffer(t))}async readFileBytes(e){let t=this.path(e);return this.atPath(t,()=>this.memory.readFileBytes(t))}async exists(e){return this.memory.exists(this.path(e))}async stat(e){let t=this.path(e),n=this.original.get(t);if(n?.kind===`file`&&!this.dirty.has(t)){let e=await n.handle.getFile();return{isFile:!0,isDirectory:!1,isSymbolicLink:!1,mode:420,size:this.baselines.get(t)?.length??e.size,mtime:new Date(e.lastModified)}}return this.memory.stat(t)}async lstat(e){return this.stat(e)}async realpath(e){return this.memory.realpath(this.path(e))}resolvePath(e,t){return this.memory.resolvePath(e,t)}getAllPaths(){return this.memory.getAllPaths()}async readdir(e){return this.memory.readdir(this.path(e))}async readdirWithFileTypes(e){return this.memory.readdirWithFileTypes(this.path(e))}async mkdir(e,t){let n=this.path(e),r=new Set(this.getAllPaths());await this.memory.mkdir(n,t);for(let e of this.getAllPaths())r.has(e)||(this.dirty.add(e),this.baselines.set(e,null))}async writeFile(e,t,n){let r=this.path(e);return this.atPath(r,()=>this.writeAtPath(r,t,n))}async writeAtPath(e,t,n){if(e===`/`)throw Error(`EISDIR: Cannot replace the selected folder.`);let r=new N2({},{maxTotalBytes:j9});await r.writeFile(`/data`,t,n);let i=await r.readFileBuffer(`/data`);if(i.length>j9)throw Error(`Local files are limited to 1 MiB.`);if(!await this.memory.exists(this.parent(e)))throw Error(`ENOENT: Parent folder does not exist: `+this.parent(e));await this.baseline(e),await this.memory.writeFile(e,i),this.dirty.add(e)}async appendFile(e,t,n){let r=new N2({"/data":await this.exists(e)?await this.readFileBuffer(e):new Uint8Array},{maxTotalBytes:j9});await r.appendFile(`/data`,t,n),await this.writeFile(e,await r.readFileBuffer(`/data`))}async rm(e,t){let n=this.path(e);return this.atPath(n,()=>this.removeAtPath(n,t))}async removeAtPath(e,t){if(e===`/`)throw Error(`Cannot remove the selected local folder.`);if(!await this.exists(e))return this.memory.rm(e,t);if((await this.stat(e)).isDirectory){if(this.original.has(e))throw Error(`Removing or renaming existing local folders is not supported. Remove individual files instead.`);for(let t of this.getAllPaths().filter(t=>t.startsWith(e+`/`)))if(this.original.has(t))throw Error(`This folder contains existing local files.`);let n=this.getAllPaths().filter(t=>t===e||t.startsWith(e+`/`));await this.memory.rm(e,t);for(let e of n)this.dirty.delete(e),this.baselines.delete(e)}else await this.baseline(e),await this.memory.rm(e,t),this.dirty.add(e)}async cp(e,t,n){let r=this.path(e),i=this.path(t);if(await this.exists(i)&&(await this.stat(i)).isDirectory&&(i+=`/`+r.split(`/`).at(-1)),r===i||i.startsWith(r+`/`))throw Error(`Cannot copy a path into itself.`);if((await this.stat(r)).isDirectory){if(!n?.recursive)throw Error(`EISDIR: Use cp -r to copy a directory.`);await this.mkdir(i,{recursive:!0});for(let e of await this.readdir(r))await this.cp(r+`/`+e,i+`/`+e,n)}else await this.writeFile(i,await this.readFileBuffer(r))}async mv(e,t){if((await this.stat(e)).isDirectory)throw Error(`Renaming local folders is not supported.`);await this.cp(e,t),await this.rm(e)}chmod=L9;symlink=L9;link=L9;readlink=L9;utimes=L9;async patches(){let e=[];for(let t of[...this.dirty].sort()){let n=this.baselines.get(t)??null,r=await this.exists(t),i=r&&(await this.memory.stat(t)).isDirectory?`directory`:`file`,a=r&&i===`file`?await this.readFileBuffer(t):null;i===`file`&&P9(n,a)||e.push({path:t,kind:i,before:F9(n),after:F9(a)})}return e}async listing(){let e=Object.create(null);for(let t of this.getAllPaths()){let n=this.original.get(t);(n?.kind===`file`||!n&&(await this.memory.stat(t)).isFile)&&(e[`/workspace`+t]=null)}return e}async status(){return{name:this.handle.name,count:(await this.patches()).length,skipped:this.skipped}}async apply(e){let t=await this.patches(),n=async e=>{let t=await this.diskEntry(e.path);if(e.kind===`directory`){if(t)throw Error(`Conflict: `+e.path+` was created on disk.`)}else if(!P9(await this.diskBytes(e.path),I9(e.before)))throw Error(`Conflict: `+e.path+` changed on disk. Nothing will overwrite it. Discard staged edits and refresh to read the new version.`)};for(let r of t)e?.throwIfAborted(),await n(r);let r=0;try{for(let i of t){e?.throwIfAborted(),await n(i);let t=i.path.split(`/`).filter(Boolean),a=this.handle;for(let e of t.slice(0,-1))a=await a.getDirectoryHandle(e);let o=t.at(-1);if(i.kind===`directory`){let e=await a.getDirectoryHandle(o,{create:!0});this.original.set(i.path,{kind:`directory`,handle:e})}else if(i.after===null)await a.removeEntry(o),this.original.delete(i.path),this.baselineBytes-=this.baselines.get(i.path)?.length??0,this.baselines.delete(i.path);else{let t=await a.getFileHandle(o,{create:!0}),n;try{n=await t.createWritable(),await n.write(I9(i.after)),e?.throwIfAborted(),await n.close()}catch(e){if(await n?.abort().catch(()=>{}),i.before===null)try{(await t.getFile()).size===0&&await t.isSameEntry(await a.getFileHandle(o))&&await a.removeEntry(o)}catch{}throw e}this.original.set(i.path,{kind:`file`,handle:t}),this.baselineBytes+=I9(i.after).length-(this.baselines.get(i.path)?.length??0),this.baselines.set(i.path,I9(i.after))}this.dirty.delete(i.path),r++}}catch(e){throw Error(`${r} of ${t.length} changes applied. Remaining changes are still staged. ${e.message}`)}return r}mount(){return new J6({mounts:[{mountPoint:`/workspace`,filesystem:this}]})}};const z9=e=>postMessage(e),B9=MI(z9);let V9,H9,U9,W9=!1,G9,K9=0,q9,J9,Y9;const X9=`You are Pi, a coding assistant powered by MiniCPM5-2B, a language model from OpenBMB.
|
| 1770 |
The model, inference, tools, and workspace run entirely in the user's browser. Workspace files are stored in this browser, not on the user's operating-system filesystem.
|
| 1771 |
Use your tools to inspect and change actual files. Never claim a command ran or a file changed without a successful tool result.
|
| 1772 |
When the user explicitly asks you to use a tool, call that tool even if you already know the answer.
|
assets/{index-CV4PDQqc.js → index-O6siy3P4.js}
RENAMED
|
@@ -157,7 +157,7 @@ Node CLI extensions, npm, Python and native binaries are unavailable.`);else if(
|
|
| 157 |
`)),r.style.height=`auto`,r.style.height=Math.min(104,Math.max(32,r.scrollHeight))+`px`,i.scrollTop=r.scrollTop,i.scrollLeft=r.scrollLeft,v()}function b(e){r.value=e,y(),r.setSelectionRange(e.length,e.length)}function x(){m||(d.length=0,a.replaceChildren(l.cloneNode(!0)),v())}function S(e,t,n){if(!t)return;let r=document.createElement(`pre`);r.className=n,r.textContent=Jg(t),e.append(r)}o.onsubmit=async e=>{e.preventDefault();let n=r.value;if(m||h||!n.trim())return;m=!0,u.at(-1)!==n&&u.push(n),u.length>50&&u.shift(),f=u.length,p=``,b(``),r.focus(),d.length||a.replaceChildren();let i=document.createElement(`section`);i.className=`shell-entry`;let o=document.createElement(`div`);o.className=`shell-entry-heading`;let s=document.createElement(`span`);s.className=`shell-prompt`,s.textContent=`$`;let c=document.createElement(`pre`);c.className=`shell-code`,c.append(Yg(Jg(n)));let l=document.createElement(`span`);l.className=`shell-status`,l.textContent=`Running…`,o.append(s,c,l),i.append(o),a.append(i),d.push(i),_();try{let e=await t(n),r=g();S(i,e.stdout,`shell-stdout`),S(i,e.stderr,`shell-stderr`),l.classList.add(e.exitCode===0?`success`:`error`),l.textContent=`[exit ${e.exitCode}]`,r&&_()}catch(e){let t=g();S(i,e.message,`shell-stderr`),l.classList.add(`error`),l.textContent=`[error]`,r.value||b(n),t&&_()}finally{for(;d.length>1&&(d.length>40||a.textContent.length>48e3);)d.shift().remove();m=!1,v()}},r.addEventListener(`input`,()=>{f=u.length,p=r.value,y()}),r.addEventListener(`scroll`,()=>{i.scrollTop=r.scrollTop,i.scrollLeft=r.scrollLeft}),r.addEventListener(`keydown`,e=>{if(e.isComposing||e.keyCode===229)return;if(e.key===`Enter`&&!e.shiftKey){e.preventDefault(),o.requestSubmit();return}if(e.ctrlKey&&e.key.toLowerCase()===`l`){e.preventDefault(),x();return}if(e.ctrlKey||e.altKey||e.metaKey||e.shiftKey||r.selectionStart!==r.selectionEnd)return;let t=!r.value.slice(0,r.selectionStart).includes(`
|
| 158 |
`),n=!r.value.slice(r.selectionEnd).includes(`
|
| 159 |
`);e.key===`ArrowUp`&&t&&f>0?(e.preventDefault(),f===u.length&&(p=r.value),b(u[--f])):e.key===`ArrowDown`&&n&&f<u.length&&(e.preventDefault(),b(++f===u.length?p:u[f]))}),c.onclick=()=>{x(),r.focus()};let C=0;return new ResizeObserver(([e])=>{e.contentRect.width!==C&&(C=e.contentRect.width,y())}).observe(r.parentElement),y(),{setBusy(e){h=e,v()}}}function Zg(e){let t=e.querySelector(`.runtime-value`),n=e.querySelector(`.runtime-sparkline`),r=e.querySelector(`.runtime-bars`),i=e.querySelector(`[role="status"]`),a=e.querySelector(`.runtime-engine`),o=e.querySelector(`.runtime-content`);new ResizeObserver(([t])=>{e.style.width=`calc(${Math.ceil(t.contentRect.width)}px + 2 * var(--runtime-padding))`}).observe(o);let s=!1,c,l=[],u;function d(o,s,d){clearTimeout(u),o!==c&&(i.textContent=d??`${s} in this browser.`),c=o,a.textContent=c===`tool`?`JavaScript`:`WebGPU`,e.dataset.state=c,t.textContent=s,n.setAttribute(`hidden`,``),e.title=d??`${s} in this browser.`,c!==`generating`&&(l=[],r.replaceChildren())}function f(){d(`idle`,s?`Ready`:`Idle`,s?`Model ready. Inference and tools run in this browser.`:`Model loads when you send a message. Inference and tools run in this browser.`)}function p(e){if(!Number.isFinite(e)||e<0){d(`generating`,`Generating…`);return}d(`generating`,`${e<10?e.toFixed(1):Math.round(e)} tok/s`,`Live token generation speed, including thinking tokens. Prompt-processing time is excluded.`),l.push(e),l.length>12&&l.shift();let t=Math.max(40,...l),i=[...Array(12-l.length).fill(null),...l];r.replaceChildren(...i.map((e,n)=>{if(e===null||e===0){let t=document.createElementNS(`http://www.w3.org/2000/svg`,`circle`);return t.setAttribute(`cx`,String(n*4+1)),t.setAttribute(`cy`,`17`),t.setAttribute(`r`,`1`),t.setAttribute(`opacity`,e===null?`.35`:`.6`),t}let r=document.createElementNS(`http://www.w3.org/2000/svg`,`rect`),i=Math.max(2,Math.min(16,e/t*16));return r.setAttribute(`x`,String(n*4)),r.setAttribute(`y`,(18-i).toFixed(2)),r.setAttribute(`width`,`2`),r.setAttribute(`height`,i.toFixed(2)),r.setAttribute(`rx`,`1`),r})),n.removeAttribute(`hidden`),u=setTimeout(()=>{d(`generating`,`Generating…`),l=[],r.replaceChildren()},2e3)}return f(),{update(e){e.type===`loaded`?(s=!0,f()):e.type===`idle`||e.type===`initialized`?f():e.type===`fatal`?(s=!1,d(`error`,`Unavailable`)):e.type===`busy`?e.action===`load`?d(`loading`,`Loading…`,e.cachedOnly?`Loading local model…`:void 0):e.action===`prompt`?d(`prefill`,`Reading prompt…`):e.action===`shell`?d(`tool`,`Running bash…`):e.action===`read`?d(`tool`,`Reading file…`):e.action===`write`?d(`tool`,`Writing file…`):d(`working`,`Working…`):e.type===`load_progress`?d(`loading`,e.phase===`download`&&e.loaded<e.total?`Downloading ${Math.round(e.loaded/e.total*100)}%`:e.phase===`warmup`?`Warming up…`:`Preparing…`):e.type===`inference_activity`?e.phase===`prefill`?d(`prefill`,`Reading prompt…`):e.phase===`decode`?p(e.rate):(c===`generating`||c===`prefill`)&&d(`working`,`Working…`):e.type===`agent_event`&&e.event.type===`tool_execution_start`&&d(`tool`,{bash:`Running bash…`,read:`Reading file…`,write:`Writing file…`,edit:`Editing file…`}[e.event.toolName]??`Running tool…`)}}}var Qg={default:`#b0b0bd`,markdown:`#519aba`,csv:`#8dc149`,json:`#cbcb41`,javascript:`#cbcb41`,typescript:`#519aba`,react:`#519aba`,html:`#e37933`,css:`#519aba`,python:`#519aba`,shell:`#8dc149`,config:`#b0b0bd`,image:`#a074c4`,svg:`#a074c4`,git:`#e37933`,pdf:`#cc3e44`,zip:`#b0b0bd`,db:`#cbcb41`},$g={md:`markdown`,markdown:`markdown`,mdx:`markdown`,csv:`csv`,tsv:`csv`,json:`json`,jsonc:`json`,jsonl:`json`,js:`javascript`,mjs:`javascript`,cjs:`javascript`,ts:`typescript`,mts:`typescript`,cts:`typescript`,jsx:`react`,tsx:`react`,html:`html`,htm:`html`,xml:`html`,css:`css`,scss:`css`,sass:`css`,less:`css`,py:`python`,pyi:`python`,sh:`shell`,bash:`shell`,zsh:`shell`,fish:`shell`,yaml:`config`,yml:`config`,toml:`config`,ini:`config`,conf:`config`,env:`config`,png:`image`,jpg:`image`,jpeg:`image`,gif:`image`,webp:`image`,avif:`image`,ico:`image`,svg:`svg`,pdf:`pdf`,zip:`zip`,gz:`zip`,tar:`zip`,sql:`db`,sqlite:`db`,db:`db`};function e_(e){let t=e.split(`/`).at(-1).toLowerCase();return[`.gitignore`,`.gitattributes`,`.gitmodules`].includes(t)?`git`:t===`.env`||t.startsWith(`.env.`)||t===`.editorconfig`?`config`:$g[t.split(`.`).at(-1)]??`default`}function t_(e){let t=e_(e),n=document.createElement(`span`);return n.className=`file-icon`,n.dataset.kind=t,n.setAttribute(`aria-hidden`,`true`),n.style.setProperty(`--file-color`,Qg[t]),n.style.setProperty(`--file-icon`,`url("${new URL(`file-icons/${t}.svg`,document.baseURI).href}")`),n}var n_={html:`markup`,htm:`markup`,xml:`markup`,svg:`markup`,vue:`markup`,css:`css`,js:`javascript`,mjs:`javascript`,cjs:`javascript`,jsx:`jsx`,ts:`typescript`,mts:`typescript`,cts:`typescript`,tsx:`tsx`,json:`json`,ipynb:`json`,md:`markdown`,markdown:`markdown`,py:`python`,pyw:`python`,sh:`bash`,bash:`bash`,zsh:`bash`,yml:`yaml`,yaml:`yaml`,toml:`toml`,sql:`sql`,csv:`csv`,c:`c`,h:`c`,cpp:`cpp`,cc:`cpp`,cxx:`cpp`,hpp:`cpp`,cs:`csharp`,java:`java`,go:`go`,rs:`rust`,diff:`diff`,patch:`diff`};function r_(e=``){let t=e.split(`/`).at(-1).toLowerCase();return/^dockerfile(?:\.|$)/.test(t)?`docker`:/^\.(?:bashrc|bash_profile|zshrc|profile)$/.test(t)?`bash`:n_[t.split(`.`).at(-1)]??null}function i_(e){if(e.length>2e5)return!1;let t=1;for(let n=0;n<e.length;n++)if(e.charCodeAt(n)===10&&++t>5e3)return!1;return!0}function a_(e){let t=e.querySelector(`#editor`),n=e.querySelector(`#editor-highlight`),r=``,i=0,a,o,s,c=!1,l=!1,u;function d(){n.style.width=t.clientWidth+`px`,n.style.height=t.clientHeight+`px`,n.scrollTop=t.scrollTop,n.scrollLeft=t.scrollLeft}function f(){e.classList.remove(`is-highlighted`)}function p(){clearTimeout(o),s?.terminate(),s=void 0,c=!1}function m(){if(c||!u)return;let t=u;u=void 0;try{s||(s=new Worker(new URL(new URL(`editor-highlight.worker-oPpFVFqT.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),s.onmessage=({data:t})=>{clearTimeout(o),c=!1,t.id===i&&t.html!==null&&(n.innerHTML=t.html+`
|
| 160 |
-
`,d(),e.classList.add(`is-highlighted`)),m()},s.onerror=()=>{p(),u=void 0}),c=!0,s.postMessage(t),o=setTimeout(()=>{p(),m()},1500)}catch{p()}}function h(n=0){i++,clearTimeout(a),u=void 0,f();let o=r_(r),s=t.value;if(e.dataset.language=o??`plain`,l||!s||!o||!i_(s))return;let c=i;a=setTimeout(()=>{u={id:c,text:s,language:o},m()},n)}t.addEventListener(`input`,()=>h(80)),t.addEventListener(`compositionstart`,()=>{l=!0,h()}),t.addEventListener(`compositionend`,()=>{l=!1,h(80)}),t.addEventListener(`scroll`,d,{passive:!0});let g=new ResizeObserver(d);return g.observe(t),{setFile(e,n){if(r===e&&t.value===n)return;let
|
| 161 |
`))&&(e=e.trim()),(!n.newlineIsToken||!t.includes(`
|
| 162 |
`))&&(t=t.trim())):n.ignoreNewlineAtEof&&!n.newlineIsToken&&(e.endsWith(`
|
| 163 |
`)&&(e=e.slice(0,-1)),t.endsWith(`
|
|
@@ -171,4 +171,6 @@ Node CLI extensions, npm, Python and native binaries are unavailable.`);else if(
|
|
| 171 |
`).map(e=>e+`
|
| 172 |
`);return t?n.pop():n.push(n.pop().slice(0,-1)),n}var Q=e=>document.querySelector(e),F_=e=>{if(e===null)return``;let t=new TextDecoder(`utf-8`,{fatal:!0}).decode(Uint8Array.from(atob(e),e=>e.charCodeAt(0)));if(t.includes(`\0`))throw Error(`Binary file`);return t};function I_({rpc:e,notice:t,canSwitch:n,onSwitch:r}){let i,a,o,s=!1,c=!1,l=[],u=Q(`#local-review`),d=window.self!==window.top,f=typeof window.showDirectoryPicker==`function`;function p(){Q(`#open-folder`).hidden=!!o,Q(`#reconnect-folder`).hidden=!!o||!a,Q(`#local-folder-bar`).hidden=!o;for(let e of[`open-folder`,`reconnect-folder`,`review-changes`,`refresh-folder`,`disconnect-folder`])Q(`#`+e).disabled=s||c;Q(`#review-changes`).disabled||=!o?.count,Q(`#refresh-folder`).disabled||=!!o?.count,Q(`#review-apply`).disabled=s||!l.length,Q(`#review-discard`).disabled=s||!l.length,Q(`#review-close`).disabled=s,o&&(Q(`#local-folder-name`).textContent=o.name,Q(`#local-folder-hint`).textContent=o.count?`${o.count} staged change${o.count===1?``:`s`} · original files unchanged until Apply`:`Local folder · edits are staged until you apply them`,Q(`#review-changes`).textContent=o.count?`Review & apply (${o.count})`:`Review & apply`),Q(`#export`).textContent=o?`Export changes`:`Export`,Q(`#save`).textContent=o?`Stage edit`:`Save`}function m(){return d?(Q(`#open-tab`).href=new URL(`?local-folder`,location.href).href,Q(`#open-tab`).hidden=!1,t(`Open the workspace in a new tab, then choose Open local folder. Browsers cannot grant folder access inside this embedded Space.`),!1):f?!0:(t(`Direct local-folder access needs a supported browser, such as Chrome or Edge. In this browser, use Import and Export to work with copies of your files.`),!1)}async function h(o){if(!(!m()||s||c))try{n(),c=!0,p();let s;if(o){if(s=a.handle,await s.requestPermission({mode:`read`})!==`granted`)throw Error(`Folder access was not granted.`)}else{if(a?.count&&!window.confirm(`Opening another folder replaces the saved staged edits for the previous folder. Reconnect to review them first, or continue?`))return;s=await window.showDirectoryPicker({id:`pi-workspace`,mode:`read`})}let l=await e(`local_open`,{handle:s,restore:o});i=s,a={handle:s,count:l.localFolder.count},r(l),t(`Opened ${s.name}. Review & apply writes staged changes to disk. Files load on demand; Refresh picks up external edits.`)}catch(e){e.name!==`AbortError`&&t(e.message)}finally{c=!1,p()}}Q(`#open-folder`).onclick=()=>h(!1),Q(`#reconnect-folder`).onclick=()=>h(!0),Q(`#disconnect-folder`).onclick=async()=>{try{n(),a={handle:i,count:o.count},r(await e(`local_close`)),t(`Demo workspace restored. Reconnect to continue with your local folder and staged edits.`)}catch(e){t(e.message)}},Q(`#refresh-folder`).onclick=async()=>{try{n(),r(await e(`local_refresh`)),t(`Folder refreshed from disk. Started a new chat with the current files.`)}catch(e){t(e.message)}};function g(e){l=e,Q(`#review-files`).replaceChildren(),Q(`#review-error`).textContent=``,Q(`#review-description`).textContent=`Review changes to ${o.name}. Applying writes them to your local folder.`;for(let t of e){let n=document.createElement(`details`),r=document.createElement(`summary`),i=document.createElement(`pre`);if(r.textContent=`${t.kind===`directory`?`New folder`:t.after===null?`Delete`:t.before===null?`Create`:`Edit`} · ${t.path.slice(1)}`,n.append(r),n.open=e.length<5,t.kind!==`directory`){try{let e=F_(t.before),n=F_(t.after);if(e.length+n.length>16e4)i.textContent=`Large text change. Inspect the file in the editor before applying.`;else{let r=N_(t.path,t.path,e,n,`on disk when read`,`staged`,{context:3});for(let e of r.split(`
|
| 173 |
`)){let t=document.createElement(`span`);t.textContent=e+`
|
| 174 |
-
`,e.startsWith(`+`)&&(t.className=`diff-add`),e.startsWith(`-`)&&(t.className=`diff-remove`),i.append(t)}}}catch{i.textContent=`Binary file · ${t.before===null?0:atob(t.before).length} → ${t.after===null?0:atob(t.after).length} bytes`}n.append(i)}Q(`#review-files`).append(n)}p()}return Q(`#review-changes`).onclick=async()=>{try{n(),g(await e(`local_changes`)),u.showModal()}catch(e){t(e.message)}},Q(`#review-close`).onclick=()=>u.close(),u.addEventListener(`cancel`,e=>{s&&e.preventDefault()}),Q(`#review-apply`).onclick=async()=>{try{if(await i.requestPermission({mode:`readwrite`})!==`granted`)throw Error(`Write access was not granted. Changes are still staged.`);let n=await e(`local_apply`);u.close(),t(`Applied ${n.applied} change${n.applied===1?``:`s`} to ${i.name}.`)}catch(t){try{g(await e(`local_changes`))}catch{}Q(`#review-error`).textContent=t.message}},Q(`#review-discard`).onclick=async()=>{if(window.confirm(`Discard all staged changes and reload files from disk? Your local files will not be changed.`))try{r(await e(`local_discard`)),u.close(),t(`Staged edits discarded. Folder refreshed and chat reset.`)}catch(e){Q(`#review-error`).textContent=e.message}},window.addEventListener(`beforeunload`,e=>{o?.count&&(e.preventDefault(),e.returnValue=``)}),new URLSearchParams(location.search).has(`local-folder`)&&t(`Choose Open local folder to select the project you want Pi to work on.`),{setBusy(e){s=e,p()},setState(e){o=e,a&&e&&(a.count=e.count),p()},restore(e){a=e,p()},get active(){return!!o}}}var $=e=>document.querySelector(e);c_($(`main`),$(`#column-resizer`));var L_=new Worker(new URL(new URL(`agent.worker-BLr4cL8b.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),R_=new Map,z_=1,B_=!1,V_=!1,H_,U_=!1,W_={},G_=[],K_,q_,J_=0,Y_=!0,X_=new Promise(e=>{K_=e}),Z_={events:[],generations:[],runtimeSamples:[],device:null,contextUsage:Ng([])},Q_=Zg($(`#runtime-meter`)),$_=a_($(`.editor-input`)),ev=Ug($(`#pi-terminal`),{onInput:e=>$(`#send`).classList.toggle(`has-input`,!!e.trim()),load:()=>X_.then(hv),prompt:e=>X_.then(()=>gv(e)),newChat:()=>X_.then(_v),stop:()=>iv(`stop`),shell:e=>{if(U_)throw Error(`Save your file edits before running commands.`);return iv(`shell`,{command:e})}}),tv=Wg($(`#model-dialog`),{onClose:()=>ev.focus()}),nv=Xg($(`.workspace > .terminal`),{run:async e=>{if(await X_,U_)throw Error(`Save your file edits before running commands.`);if(B_)throw Error(`Wait for Pi to finish or stop the current task first.`);return iv(`shell`,{command:e})}}),rv=I_({rpc:iv,notice:av,canSwitch(){if(B_)throw Error(`Wait for Pi to finish or stop the current task first.`);if(U_)throw Error(`Save or stage the open file first.`)},onSwitch(e){q_=void 0,H_=void 0,U_=!1,J_++,Y_=!1,W_=e.files,G_=e.messages,rv.setState(e.localFolder),$(`#filename`).textContent=`Select a file`,$(`#filetype`).replaceChildren(),$_.setFile(``,``),ev.setMessages(G_,{reset:!0}),pv(Ng(G_)),$(`#shell-clear`).click(),cv();let t=Object.hasOwn(W_,`/workspace/README.md`)?`/workspace/README.md`:Object.keys(W_).sort()[0];t&&uv(t),sv()}});function iv(e,t){return new Promise((n,r)=>{let i=z_++;R_.set(i,{resolve:n,reject:r}),L_.postMessage({id:i,action:e,args:t})})}function av(e){$(`#notice`).textContent=e,$(`#notice`).hidden=!e}function ov(e){return window.self===window.top||!/denied|storage directory access/i.test(e)?e:($(`#open-tab`).href=location.href,$(`#open-tab`).hidden=!1,`Your browser blocked storage in this embedded app. Open the workspace in a new tab to load the model and save files.`)}function sv(){$(`#load`).disabled=B_||V_,$(`#load`).classList.toggle(`is-loaded`,V_),V_?$(`#load`).textContent=`Loaded`:B_||($(`#load`).textContent=`Load model`),$(`#load`).title=$(`#load-status`).textContent+` `+$(`#load-detail`).textContent,$(`#send`).disabled=B_,$(`#newline`).disabled=B_,ev.setState({busy:B_,loaded:V_,activity:$(`#activity`).textContent}),$(`#stop`).hidden=!B_;for(let e of[`#new-chat`,`#add-file`,`#upload`])$(e).disabled=B_;nv.setBusy(B_),rv.setBusy(B_),$(`#save`).disabled=B_||!H_||!U_,$(`#editor`).disabled=B_||!H_||!Y_}function cv(){ev.setFiles(W_),$(`#files`).replaceChildren();for(let e of Object.keys(W_).sort()){let t=document.createElement(`button`),n=document.createElement(`span`);n.className=`file-name`,n.textContent=e.replace(/^\/workspace\//,``),t.append(t_(e),n),t.title=e,t.classList.toggle(`selected`,e===H_),t.onclick=()=>uv(e),$(`#files`).append(t)}H_&&!Object.hasOwn(W_,H_)&&!U_&&(H_=void 0,J_++,Y_=!1,$(`#filename`).textContent=`Select a file`,$(`#filetype`).replaceChildren(),$_.setFile(``,``),sv()),H_&&!U_&&(W_[H_]===null?lv(H_):(Y_=!0,$_.setFile(H_,W_[H_]??``)))}async function lv(e){let t=++J_;Y_=!1,$_.setFile(e,``),sv();try{let n=await iv(`preview`,{path:e});if(t!==J_||e!==H_||U_)return;$_.setFile(e,n),Y_=!0}catch(e){t===J_&&av(e.message)}finally{t===J_&&sv()}}function uv(e){if(q_=void 0,U_){av(`Save the open file before selecting another file.`);return}H_=e,$(`#filename`).textContent=e,$(`#filetype`).replaceChildren(t_(e)),cv(),sv()}function dv(){if(U_){q_=void 0;return}if(!q_||!Object.hasOwn(W_,q_))return;let e=q_,t=e!==H_;uv(e),t&&($(`#editor`).scrollTop=0,$(`#editor`).scrollLeft=0);let n=[...$(`#files`).children].find(t=>t.title===e);if(n){let e=n.getBoundingClientRect(),t=$(`#files`).getBoundingClientRect();e.top<t.top?$(`#files`).scrollTop+=e.top-t.top:e.bottom>t.bottom&&($(`#files`).scrollTop+=e.bottom-t.bottom)}}function fv(){ev.setMessages(G_)}function pv(e){Z_.contextUsage=e,ev.setState({contextUsage:e})}L_.onmessage=({data:e})=>{if(Q_.update(e),e.type===`inference_activity`&&(Z_.runtimeSamples.push(e),Z_.runtimeSamples.length>160&&Z_.runtimeSamples.shift()),e.type===`reply`){let t=R_.get(e.id);R_.delete(e.id),e.error?t?.reject(Object.assign(Error(e.error),{name:e.errorName||`Error`,code:e.errorCode})):t?.resolve(e.value)}else if(e.type===`initialized`)rv.restore(e.savedLocal),rv.setState(e.localFolder),W_=e.files,G_=e.messages,cv(),fv(),pv(Ng(G_)),W_[`/workspace/README.md`]&&uv(`/workspace/README.md`),document.activeElement===document.body&&ev.focus(),K_();else if(e.type===`busy`)e.action===`load`&&($(`#load`).textContent=`Loading…`),B_=!0,$(`#activity`).textContent=e.action===`prompt`?`Pi is working…`:e.action===`load`?e.cachedOnly?`Loading local model…`:`Loading model…`:`Working…`,sv();else if(e.type===`idle`)B_=!1,$(`#progress`).hidden=!0,$(`#activity`).textContent=rv.active?`Local folder · review & apply to save changes to disk`:V_?`Ready · running on your device`:`Files and chat are saved on this device.`,sv();else if(e.type===`workspace`)W_=e.files,rv.setState(e.localFolder),cv(),dv();else if(e.type===`device`)Z_.device=e.device;else if(e.type===`load_progress`)$(`#progress`).hidden=!1,e.phase===`download`?($(`#progress`).hidden=!1,$(`#progress`).value=e.loaded/e.total*100,$(`#load-status`).textContent=e.loaded===e.total?`Download verified. Preparing model…`:`Downloading model weights from Hugging Face…`,$(`#load-detail`).textContent=`${(e.loaded/1e9).toFixed(2)} / ${(e.total/1e9).toFixed(2)} GB · ${Math.round(e.loaded/e.total*100)}%${e.cached?` · ${(e.cached/1e9).toFixed(2)} GB cached`:``}`,$(`#load`).textContent=`Loading ${Math.round(e.loaded/e.total*100)}%`):($(`#load-status`).textContent=e.phase===`compile`?`Preparing WebGPU session…`:`Warming up GPU shaders…`,$(`#load-detail`).textContent=``,$(`#progress`).removeAttribute(`value`),$(`#load`).textContent=e.phase===`compile`?`Preparing…`:`Almost ready…`),$(`#load`).title=$(`#load-status`).textContent+` `+$(`#load-detail`).textContent;else if(e.type===`loaded`)V_=!0,Z_.device=e.device,$(`#progress`).hidden=!0,$(`#load`).textContent=`Loaded`,$(`#load-status`).textContent=`Ready. Model inference and tools run entirely in this browser.`,$(`#load-detail`).textContent=e.cachedBytes?`Verified model restored from browser storage.`:`SHA-256 verified · model cached for next time.`,sv();else if(e.type===`agent_event`){let t=e.event;if(Z_.events.push(t.type),Z_.events.length>500&&Z_.events.shift(),t.message){let e=G_.findIndex(e=>e.timestamp===t.message.timestamp&&e.role===t.message.role&&e.toolCallId===t.message.toolCallId);e<0?G_.push(t.message):G_[e]=t.message,fv()}t.type===`tool_execution_start`&&($(`#activity`).textContent=`Running ${t.toolName}…`),t.type===`tool_execution_end`&&t.toolName===`read`&&!t.isError&&typeof t.result?.details?.path==`string`&&(q_=t.result.details.path,dv()),t.type===`turn_start`&&($(`#activity`).textContent=`Pi is thinking…`),ev.setState({activity:$(`#activity`).textContent})}else e.type===`context_usage`?pv({inputTokens:e.inputTokens,outputTokens:e.outputTokens}):e.type===`generation`?(Z_.generations.push(e),Z_.generations.length>100&&Z_.generations.shift()):e.type===`context_trim`?av(`Older turns were left out of the model context. Your full chat remains saved.`):e.type===`notice`?av(e.text):e.type===`persistence_error`?av(ov(e.error)):e.type===`fatal`&&(V_=!1,av(e.error),sv())},L_.onerror=e=>{Q_.update({type:`fatal`});let t=Error(e.message||`The worker stopped. Reload to restore your saved workspace.`);av(t.message);for(let e of R_.values())e.reject(t);R_.clear(),B_=!1,V_=!1,$(`#progress`).hidden=!0,sv()};var mv=e=>async t=>{t?.preventDefault();try{await X_,await e(t)}catch(e){av(e.message)}};async function hv({cachedOnly:e=!1}={}){if(B_)throw Error(`Wait for the current operation or stop it first.`);av(``);let t=new URLSearchParams(location.search);B_=!0,$(`#load`).textContent=`Loading…`,$(`#activity`).textContent=e?`Loading local model…`:`Loading model…`,$(`#load-status`).textContent=e?`Loading local model…`:`Checking your browser and saved model…`,$(`#load-detail`).textContent=``,$(`#progress`).hidden=!1,$(`#progress`).removeAttribute(`value`),Q_.update({type:`busy`,action:`load`,cachedOnly:e}),sv();try{return await iv(`load`,{runtimeURL:new URL(`./runtime/`,location.href).href,local:t.get(`source`)===`local`,origin:location.origin,limit128:t.has(`limit128`),cachedOnly:e})}catch(e){throw e.message=ov(e.message),$(`#load-status`).textContent=e.message,$(`#progress`).hidden=!0,e}}async function gv(e){if(e.trim()){if(U_)throw Error(`Save your file edits before asking Pi to work on them.`);try{if(!V_){let t;if(await iv(`cache_status`))try{ev.setQueuedPrompt(e),await hv({cachedOnly:!0})}catch(e){if(e.name===`AbortError`)throw e;e.code!==`MODEL_CACHE_MISS`&&(t=e.message)}for(;!V_;){if(ev.setQueuedPrompt(``),!await tv.request(e,{errorMessage:t}))throw new DOMException(`Message kept for later.`,`AbortError`);ev.setQueuedPrompt(e);try{await hv()}catch(e){if(e.name===`AbortError`)throw e;t=e.message}}}ev.setQueuedPrompt(``),av(``);let t=await iv(`prompt`,{text:e});return G_=t.messages,fv(),t}finally{ev.setQueuedPrompt(``)}}}$(`#load`).onclick=mv(hv),$(`#send`).onclick=()=>{ev.submit(),ev.focus()},$(`#newline`).onclick=()=>ev.newline(),$(`#stop`).onclick=mv(()=>iv(`stop`)),$(`.terminal-panel`).addEventListener(`click`,e=>{!e.defaultPrevented&&e.button===0&&e.target instanceof Element&&(e.target.closest(`#pi-terminal, button, a, input, textarea, select, label, summary, [role="button"], [contenteditable]`)||window.getSelection()?.isCollapsed!==!1&&ev.focus())});async function _v(){q_=void 0,G_=(await iv(`new_chat`)).messages,pv(Ng(G_)),ev.setMessages(G_,{reset:!0}),av(``),ev.focus()}$(`#new-chat`).onclick=mv(_v),$(`#editor`).oninput=()=>{U_=!0,sv()},$(`#save`).onclick=mv(async()=>{await iv(`write`,{path:H_,content:$(`#editor`).value}),U_=!1,cv(),sv(),av(``)}),$(`#add-file`).onclick=mv(async()=>{if(U_)throw Error(`Save the open file first.`);let e=window.prompt(`File path, relative to /workspace:`);if(!e?.trim())return;let t=e.startsWith(`/`)?e:`/workspace/`+e;if(Object.hasOwn(W_,t))throw Error(`That file already exists.`);await iv(`write`,{path:t,content:``}),uv(t)}),$(`#upload`).onchange=mv(async()=>{if(U_)throw Error(`Save the open file first.`);for(let e of $(`#upload`).files){if(e.size>1024**2)throw Error(`Import text files of 1 MiB or less.`);let t=await e.text();if(t.includes(`\0`))throw Error(`Import supports text files only.`);let n=`/workspace/`+e.name;if(Object.hasOwn(W_,n))throw Error(e.name+` already exists. Rename it before importing.`);await iv(`write`,{path:n,content:t}),uv(n)}$(`#upload`).value=``}),$(`#export`).onclick=mv(async()=>{let e=rv.active?{version:1,patches:await iv(`local_changes`)}:await iv(`snapshot`),t=URL.createObjectURL(new Blob([JSON.stringify(e,null,2)],{type:`application/json`})),n=document.createElement(`a`);n.href=t,n.download=rv.active?`pi-staged-changes.json`:`pi-workspace.json`,n.click(),setTimeout(()=>URL.revokeObjectURL(t),1e3)}),window.browserPi={ready:X_,load:hv,prompt:gv,rpc:iv,diagnostics:Z_,terminal:ev,runtimeMeter:Q_,get loaded(){return V_},get busy(){return B_}},sv();
|
|
|
|
|
|
|
|
|
| 157 |
`)),r.style.height=`auto`,r.style.height=Math.min(104,Math.max(32,r.scrollHeight))+`px`,i.scrollTop=r.scrollTop,i.scrollLeft=r.scrollLeft,v()}function b(e){r.value=e,y(),r.setSelectionRange(e.length,e.length)}function x(){m||(d.length=0,a.replaceChildren(l.cloneNode(!0)),v())}function S(e,t,n){if(!t)return;let r=document.createElement(`pre`);r.className=n,r.textContent=Jg(t),e.append(r)}o.onsubmit=async e=>{e.preventDefault();let n=r.value;if(m||h||!n.trim())return;m=!0,u.at(-1)!==n&&u.push(n),u.length>50&&u.shift(),f=u.length,p=``,b(``),r.focus(),d.length||a.replaceChildren();let i=document.createElement(`section`);i.className=`shell-entry`;let o=document.createElement(`div`);o.className=`shell-entry-heading`;let s=document.createElement(`span`);s.className=`shell-prompt`,s.textContent=`$`;let c=document.createElement(`pre`);c.className=`shell-code`,c.append(Yg(Jg(n)));let l=document.createElement(`span`);l.className=`shell-status`,l.textContent=`Running…`,o.append(s,c,l),i.append(o),a.append(i),d.push(i),_();try{let e=await t(n),r=g();S(i,e.stdout,`shell-stdout`),S(i,e.stderr,`shell-stderr`),l.classList.add(e.exitCode===0?`success`:`error`),l.textContent=`[exit ${e.exitCode}]`,r&&_()}catch(e){let t=g();S(i,e.message,`shell-stderr`),l.classList.add(`error`),l.textContent=`[error]`,r.value||b(n),t&&_()}finally{for(;d.length>1&&(d.length>40||a.textContent.length>48e3);)d.shift().remove();m=!1,v()}},r.addEventListener(`input`,()=>{f=u.length,p=r.value,y()}),r.addEventListener(`scroll`,()=>{i.scrollTop=r.scrollTop,i.scrollLeft=r.scrollLeft}),r.addEventListener(`keydown`,e=>{if(e.isComposing||e.keyCode===229)return;if(e.key===`Enter`&&!e.shiftKey){e.preventDefault(),o.requestSubmit();return}if(e.ctrlKey&&e.key.toLowerCase()===`l`){e.preventDefault(),x();return}if(e.ctrlKey||e.altKey||e.metaKey||e.shiftKey||r.selectionStart!==r.selectionEnd)return;let t=!r.value.slice(0,r.selectionStart).includes(`
|
| 158 |
`),n=!r.value.slice(r.selectionEnd).includes(`
|
| 159 |
`);e.key===`ArrowUp`&&t&&f>0?(e.preventDefault(),f===u.length&&(p=r.value),b(u[--f])):e.key===`ArrowDown`&&n&&f<u.length&&(e.preventDefault(),b(++f===u.length?p:u[f]))}),c.onclick=()=>{x(),r.focus()};let C=0;return new ResizeObserver(([e])=>{e.contentRect.width!==C&&(C=e.contentRect.width,y())}).observe(r.parentElement),y(),{setBusy(e){h=e,v()}}}function Zg(e){let t=e.querySelector(`.runtime-value`),n=e.querySelector(`.runtime-sparkline`),r=e.querySelector(`.runtime-bars`),i=e.querySelector(`[role="status"]`),a=e.querySelector(`.runtime-engine`),o=e.querySelector(`.runtime-content`);new ResizeObserver(([t])=>{e.style.width=`calc(${Math.ceil(t.contentRect.width)}px + 2 * var(--runtime-padding))`}).observe(o);let s=!1,c,l=[],u;function d(o,s,d){clearTimeout(u),o!==c&&(i.textContent=d??`${s} in this browser.`),c=o,a.textContent=c===`tool`?`JavaScript`:`WebGPU`,e.dataset.state=c,t.textContent=s,n.setAttribute(`hidden`,``),e.title=d??`${s} in this browser.`,c!==`generating`&&(l=[],r.replaceChildren())}function f(){d(`idle`,s?`Ready`:`Idle`,s?`Model ready. Inference and tools run in this browser.`:`Model loads when you send a message. Inference and tools run in this browser.`)}function p(e){if(!Number.isFinite(e)||e<0){d(`generating`,`Generating…`);return}d(`generating`,`${e<10?e.toFixed(1):Math.round(e)} tok/s`,`Live token generation speed, including thinking tokens. Prompt-processing time is excluded.`),l.push(e),l.length>12&&l.shift();let t=Math.max(40,...l),i=[...Array(12-l.length).fill(null),...l];r.replaceChildren(...i.map((e,n)=>{if(e===null||e===0){let t=document.createElementNS(`http://www.w3.org/2000/svg`,`circle`);return t.setAttribute(`cx`,String(n*4+1)),t.setAttribute(`cy`,`17`),t.setAttribute(`r`,`1`),t.setAttribute(`opacity`,e===null?`.35`:`.6`),t}let r=document.createElementNS(`http://www.w3.org/2000/svg`,`rect`),i=Math.max(2,Math.min(16,e/t*16));return r.setAttribute(`x`,String(n*4)),r.setAttribute(`y`,(18-i).toFixed(2)),r.setAttribute(`width`,`2`),r.setAttribute(`height`,i.toFixed(2)),r.setAttribute(`rx`,`1`),r})),n.removeAttribute(`hidden`),u=setTimeout(()=>{d(`generating`,`Generating…`),l=[],r.replaceChildren()},2e3)}return f(),{update(e){e.type===`loaded`?(s=!0,f()):e.type===`idle`||e.type===`initialized`?f():e.type===`fatal`?(s=!1,d(`error`,`Unavailable`)):e.type===`busy`?e.action===`load`?d(`loading`,`Loading…`,e.cachedOnly?`Loading local model…`:void 0):e.action===`prompt`?d(`prefill`,`Reading prompt…`):e.action===`shell`?d(`tool`,`Running bash…`):e.action===`read`?d(`tool`,`Reading file…`):e.action===`write`?d(`tool`,`Writing file…`):d(`working`,`Working…`):e.type===`load_progress`?d(`loading`,e.phase===`download`&&e.loaded<e.total?`Downloading ${Math.round(e.loaded/e.total*100)}%`:e.phase===`warmup`?`Warming up…`:`Preparing…`):e.type===`inference_activity`?e.phase===`prefill`?d(`prefill`,`Reading prompt…`):e.phase===`decode`?p(e.rate):(c===`generating`||c===`prefill`)&&d(`working`,`Working…`):e.type===`agent_event`&&e.event.type===`tool_execution_start`&&d(`tool`,{bash:`Running bash…`,read:`Reading file…`,write:`Writing file…`,edit:`Editing file…`}[e.event.toolName]??`Running tool…`)}}}var Qg={default:`#b0b0bd`,markdown:`#519aba`,csv:`#8dc149`,json:`#cbcb41`,javascript:`#cbcb41`,typescript:`#519aba`,react:`#519aba`,html:`#e37933`,css:`#519aba`,python:`#519aba`,shell:`#8dc149`,config:`#b0b0bd`,image:`#a074c4`,svg:`#a074c4`,git:`#e37933`,pdf:`#cc3e44`,zip:`#b0b0bd`,db:`#cbcb41`},$g={md:`markdown`,markdown:`markdown`,mdx:`markdown`,csv:`csv`,tsv:`csv`,json:`json`,jsonc:`json`,jsonl:`json`,js:`javascript`,mjs:`javascript`,cjs:`javascript`,ts:`typescript`,mts:`typescript`,cts:`typescript`,jsx:`react`,tsx:`react`,html:`html`,htm:`html`,xml:`html`,css:`css`,scss:`css`,sass:`css`,less:`css`,py:`python`,pyi:`python`,sh:`shell`,bash:`shell`,zsh:`shell`,fish:`shell`,yaml:`config`,yml:`config`,toml:`config`,ini:`config`,conf:`config`,env:`config`,png:`image`,jpg:`image`,jpeg:`image`,gif:`image`,webp:`image`,avif:`image`,ico:`image`,svg:`svg`,pdf:`pdf`,zip:`zip`,gz:`zip`,tar:`zip`,sql:`db`,sqlite:`db`,db:`db`};function e_(e){let t=e.split(`/`).at(-1).toLowerCase();return[`.gitignore`,`.gitattributes`,`.gitmodules`].includes(t)?`git`:t===`.env`||t.startsWith(`.env.`)||t===`.editorconfig`?`config`:$g[t.split(`.`).at(-1)]??`default`}function t_(e){let t=e_(e),n=document.createElement(`span`);return n.className=`file-icon`,n.dataset.kind=t,n.setAttribute(`aria-hidden`,`true`),n.style.setProperty(`--file-color`,Qg[t]),n.style.setProperty(`--file-icon`,`url("${new URL(`file-icons/${t}.svg`,document.baseURI).href}")`),n}var n_={html:`markup`,htm:`markup`,xml:`markup`,svg:`markup`,vue:`markup`,css:`css`,js:`javascript`,mjs:`javascript`,cjs:`javascript`,jsx:`jsx`,ts:`typescript`,mts:`typescript`,cts:`typescript`,tsx:`tsx`,json:`json`,ipynb:`json`,md:`markdown`,markdown:`markdown`,py:`python`,pyw:`python`,sh:`bash`,bash:`bash`,zsh:`bash`,yml:`yaml`,yaml:`yaml`,toml:`toml`,sql:`sql`,csv:`csv`,c:`c`,h:`c`,cpp:`cpp`,cc:`cpp`,cxx:`cpp`,hpp:`cpp`,cs:`csharp`,java:`java`,go:`go`,rs:`rust`,diff:`diff`,patch:`diff`};function r_(e=``){let t=e.split(`/`).at(-1).toLowerCase();return/^dockerfile(?:\.|$)/.test(t)?`docker`:/^\.(?:bashrc|bash_profile|zshrc|profile)$/.test(t)?`bash`:n_[t.split(`.`).at(-1)]??null}function i_(e){if(e.length>2e5)return!1;let t=1;for(let n=0;n<e.length;n++)if(e.charCodeAt(n)===10&&++t>5e3)return!1;return!0}function a_(e){let t=e.querySelector(`#editor`),n=e.querySelector(`#editor-highlight`),r=``,i=0,a,o,s,c=!1,l=!1,u;function d(){n.style.width=t.clientWidth+`px`,n.style.height=t.clientHeight+`px`,n.scrollTop=t.scrollTop,n.scrollLeft=t.scrollLeft}function f(){e.classList.remove(`is-highlighted`)}function p(){clearTimeout(o),s?.terminate(),s=void 0,c=!1}function m(){if(c||!u)return;let t=u;u=void 0;try{s||(s=new Worker(new URL(new URL(`editor-highlight.worker-oPpFVFqT.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),s.onmessage=({data:t})=>{clearTimeout(o),c=!1,t.id===i&&t.html!==null&&(n.innerHTML=t.html+`
|
| 160 |
+
`,d(),e.classList.add(`is-highlighted`)),m()},s.onerror=()=>{p(),u=void 0}),c=!0,s.postMessage(t),o=setTimeout(()=>{p(),m()},1500)}catch{p()}}function h(n=0){i++,clearTimeout(a),u=void 0,f();let o=r_(r),s=t.value;if(e.dataset.language=o??`plain`,l||!s||!o||!i_(s))return;let c=i;a=setTimeout(()=>{u={id:c,text:s,language:o},m()},n)}t.addEventListener(`input`,()=>h(80)),t.addEventListener(`compositionstart`,()=>{l=!0,h()}),t.addEventListener(`compositionend`,()=>{l=!1,h(80)}),t.addEventListener(`scroll`,d,{passive:!0});let g=new ResizeObserver(d);return g.observe(t),{setFile(e,n,i=e?`Empty file.`:`Select a file to inspect or edit it.`){if(t.placeholder=i,r===e&&t.value===n)return;let a=r!==e;r=e,t.value!==n&&(t.value=n),a&&(t.scrollTop=0,t.scrollLeft=0),h()},dispose(){clearTimeout(a),p(),g.disconnect()}}}var o_=`pi-column-ratio`,s_=1/1.83;function c_(e,t){let n=matchMedia(`(min-width: 851px)`),r=getComputedStyle(e),i=parseFloat(r.getPropertyValue(`--conversation-min`)),a=parseFloat(r.getPropertyValue(`--workspace-min`)),o=s_,s,c=0;try{let e=Number(localStorage.getItem(o_));Number.isFinite(e)&&e>0&&e<1&&(o=e)}catch{}function l(){if(!n.matches)return;let r=e.getBoundingClientRect().width;if(!r)return;let s=Math.max(i,Math.min(r-a,r*o));e.style.setProperty(`--conversation-width`,`${s}px`);let c=Math.round(s/r*100);t.setAttribute(`aria-valuemin`,String(Math.round(i/r*100))),t.setAttribute(`aria-valuemax`,String(Math.round((r-a)/r*100))),t.setAttribute(`aria-valuenow`,String(c)),t.setAttribute(`aria-valuetext`,`Chat ${c}%, workspace ${100-c}%`)}function u(){try{localStorage.setItem(o_,String(o))}catch{}}function d(t){let n=e.getBoundingClientRect().width;o=Math.max(i,Math.min(n-a,t))/n,l()}function f(){cancelAnimationFrame(c),c=0,s&&d(s.x-e.getBoundingClientRect().left-s.offset)}function p(e){if(!s)return;e?f():(cancelAnimationFrame(c),c=0,o=s.initialRatio);let n=s.pointer;s=void 0,t.hasPointerCapture(n)&&t.releasePointerCapture(n),document.documentElement.classList.remove(`is-resizing-columns`),l(),e&&u()}function m(){p(!1),o=s_,l(),u()}t.addEventListener(`pointerdown`,r=>{if(!n.matches||r.button!==0||!r.isPrimary||s)return;r.preventDefault(),t.focus({preventScroll:!0});let i=e.querySelector(`.conversation`).getBoundingClientRect().right;s={pointer:r.pointerId,initialRatio:o,x:r.clientX,offset:r.clientX-i},t.setPointerCapture(r.pointerId),document.documentElement.classList.add(`is-resizing-columns`)}),t.addEventListener(`pointermove`,e=>{s&&e.pointerId===s.pointer&&(s.x=e.clientX,c||=requestAnimationFrame(f))}),t.addEventListener(`pointerup`,e=>{s&&e.pointerId===s.pointer&&(s.x=e.clientX,p(!0))}),t.addEventListener(`pointercancel`,()=>p(!1)),t.addEventListener(`lostpointercapture`,()=>p(!1)),t.addEventListener(`dblclick`,m),t.addEventListener(`keydown`,t=>{if(!n.matches)return;if(t.key===`Escape`&&s){t.preventDefault(),p(!1);return}if(![`ArrowLeft`,`ArrowRight`,`Home`,`End`,`Enter`].includes(t.key))return;if(t.preventDefault(),t.key===`Enter`){m();return}p(!1);let r=e.querySelector(`.conversation`).getBoundingClientRect().width,i=t.shiftKey?48:16;d(t.key===`Home`?0:t.key===`End`?1/0:r+(t.key===`ArrowLeft`?-i:i)),u()}),window.addEventListener(`blur`,()=>p(!1)),n.addEventListener(`change`,()=>{p(!1),l()}),new ResizeObserver(l).observe(e),l()}var l_=class{diff(e,t,n={}){let r;typeof n==`function`?(r=n,n={}):`callback`in n&&(r=n.callback);let i=this.castInput(e,n),a=this.castInput(t,n),o=this.removeEmpty(this.tokenize(i,n)),s=this.removeEmpty(this.tokenize(a,n));return this.diffWithOptionsObj(o,s,n,r)}diffWithOptionsObj(e,t,n,r){let i=e=>{if(e=this.postProcess(e,n),r){setTimeout(function(){r(e)},0);return}return e},a=t.length,o=e.length,s=1,c=a+o;n.maxEditLength!=null&&(c=Math.min(c,n.maxEditLength));let l=n.timeout??1/0,u=Date.now()+l,d=[{oldPos:-1,lastComponent:void 0}],f=this.extractCommon(d[0],t,e,0,n);if(d[0].oldPos+1>=o&&f+1>=a)return i(this.buildValues(d[0].lastComponent,t,e));let p=-1/0,m=1/0,h=()=>{for(let r=Math.max(p,-s);r<=Math.min(m,s);r+=2){let s,c=d[r-1],l=d[r+1];c&&(d[r-1]=void 0);let u=!1;if(l){let e=l.oldPos-r;u=l&&0<=e&&e<a}let h=c&&c.oldPos+1<o;if(!u&&!h){d[r]=void 0;continue}if(s=!h||u&&c.oldPos<l.oldPos?this.addToPath(l,!0,!1,0,n):this.addToPath(c,!1,!0,1,n),f=this.extractCommon(s,t,e,r,n),s.oldPos+1>=o&&f+1>=a)return i(this.buildValues(s.lastComponent,t,e))||!0;d[r]=s,s.oldPos+1>=o&&(m=Math.min(m,r-1)),f+1>=a&&(p=Math.max(p,r+1))}s++};if(r)(function e(){setTimeout(function(){if(s>c||Date.now()>u)return r(void 0);h()||e()},0)})();else for(;s<=c&&Date.now()<=u;){let e=h();if(e)return e}}addToPath(e,t,n,r,i){let a=e.lastComponent;return a&&!i.oneChangePerToken&&a.added===t&&a.removed===n?{oldPos:e.oldPos+r,lastComponent:{count:a.count+1,added:t,removed:n,previousComponent:a.previousComponent}}:{oldPos:e.oldPos+r,lastComponent:{count:1,added:t,removed:n,previousComponent:a}}}extractCommon(e,t,n,r,i){let a=t.length,o=n.length,s=e.oldPos,c=s-r,l=0;for(;c+1<a&&s+1<o&&this.equals(n[s+1],t[c+1],i);)c++,s++,l++,i.oneChangePerToken&&(e.lastComponent={count:1,previousComponent:e.lastComponent,added:!1,removed:!1});return l&&!i.oneChangePerToken&&(e.lastComponent={count:l,previousComponent:e.lastComponent,added:!1,removed:!1}),e.oldPos=s,c}equals(e,t,n){return n.comparator?n.comparator(e,t):e===t||!!n.ignoreCase&&e.toLowerCase()===t.toLowerCase()}removeEmpty(e){let t=[];for(let n=0;n<e.length;n++)e[n]&&t.push(e[n]);return t}castInput(e,t){return e}tokenize(e,t){return Array.from(e)}join(e){return e.join(``)}postProcess(e,t){return e}get useLongestToken(){return!1}buildValues(e,t,n){let r=[],i;for(;e;)r.push(e),i=e.previousComponent,delete e.previousComponent,e=i;r.reverse();let a=r.length,o=0,s=0,c=0;for(;o<a;o++){let e=r[o];if(e.removed)e.value=this.join(n.slice(c,c+e.count)),c+=e.count;else{if(!e.added&&this.useLongestToken){let r=t.slice(s,s+e.count);r=r.map(function(e,t){let r=n[c+t];return r.length>e.length?r:e}),e.value=this.join(r)}else e.value=this.join(t.slice(s,s+e.count));s+=e.count,e.added||(c+=e.count)}}return r}};new class extends l_{};function u_(e,t){let n=0;for(;n<e.length&&n<t.length;n++)if(e[n]!=t[n])return e.slice(0,n);return e.slice(0,n)}function d_(e,t){let n;if(!e||!t||e[e.length-1]!=t[t.length-1])return``;for(n=0;n<e.length&&n<t.length;n++)if(e[e.length-(n+1)]!=t[t.length-(n+1)])return e.slice(-n);return e.slice(-n)}function f_(e,t,n){if(e.slice(0,t.length)!=t)throw Error(`string ${JSON.stringify(e)} doesn't start with prefix ${JSON.stringify(t)}; this is a bug`);return n+e.slice(t.length)}function p_(e,t,n){if(!t)return e+n;if(e.slice(-t.length)!=t)throw Error(`string ${JSON.stringify(e)} doesn't end with suffix ${JSON.stringify(t)}; this is a bug`);return e.slice(0,-t.length)+n}function m_(e,t){return f_(e,t,``)}function h_(e,t){return p_(e,t,``)}function g_(e,t){return t.slice(0,__(e,t))}function __(e,t){let n=0;e.length>t.length&&(n=e.length-t.length);let r=t.length;e.length<t.length&&(r=e.length);let i=Array(r),a=0;i[0]=0;for(let e=1;e<r;e++){for(t[e]==t[a]?i[e]=i[a]:i[e]=a;a>0&&t[e]!=t[a];)a=i[a];t[e]==t[a]&&a++}a=0;for(let r=n;r<e.length;r++){for(;a>0&&e[r]!=t[a];)a=i[a];e[r]==t[a]&&a++}return a}function v_(e,t){let n=[];for(let r of Array.from(t.segment(e))){let e=r.segment;n.length&&/\s/.test(n[n.length-1])&&/\s/.test(e)?n[n.length-1]+=e:n.push(e)}return n}function y_(e,t){if(t)return x_(e,t)[1];let n;for(n=e.length-1;n>=0&&e[n].match(/\s/);n--);return e.substring(n+1)}function b_(e,t){if(t)return x_(e,t)[0];let n=e.match(/^\s*/);return n?n[0]:``}function x_(e,t){if(!t)return[b_(e),y_(e)];if(t.resolvedOptions().granularity!=`word`)throw Error(`The segmenter passed must have a granularity of "word"`);let n=v_(e,t),r=n[0],i=n[n.length-1];return[/\s/.test(r)?r:``,/\s/.test(i)?i:``]}var S_=`a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}`,C_=RegExp(`[${S_}]+|\\s+|[^${S_}]`,`ug`);new class extends l_{equals(e,t,n){return n.ignoreCase&&(e=e.toLowerCase(),t=t.toLowerCase()),e.trim()===t.trim()}tokenize(e,t={}){let n;if(t.intlSegmenter){let r=t.intlSegmenter;if(r.resolvedOptions().granularity!=`word`)throw Error(`The segmenter passed must have a granularity of "word"`);n=v_(e,r)}else n=e.match(C_)||[];let r=[],i=null;return n.forEach(e=>{/\s/.test(e)?i==null?r.push(e):r.push(r.pop()+e):i!=null&&/\s/.test(i)?r[r.length-1]==i?r.push(r.pop()+e):r.push(i+e):r.push(e),i=e}),r}join(e){return e.map((e,t)=>t==0?e:e.replace(/^\s+/,``)).join(``)}postProcess(e,t){if(!e||t.oneChangePerToken)return e;let n=null,r=null,i=null;return e.forEach(e=>{e.added?r=e:e.removed?i=e:((r||i)&&w_(n,i,r,e,t.intlSegmenter),n=e,r=null,i=null)}),(r||i)&&w_(n,i,r,null,t.intlSegmenter),e}};function w_(e,t,n,r,i){if(t&&n){let[a,o]=x_(t.value,i),[s,c]=x_(n.value,i);if(e){let r=u_(a,s);e.value=p_(e.value,s,r),t.value=m_(t.value,r),n.value=m_(n.value,r)}if(r){let e=d_(o,c);r.value=f_(r.value,c,e),t.value=h_(t.value,e),n.value=h_(n.value,e)}}else if(n){if(e){let e=b_(n.value,i);n.value=n.value.substring(e.length)}if(r){let e=b_(r.value,i);r.value=r.value.substring(e.length)}}else if(e&&r){let n=b_(r.value,i),[a,o]=x_(t.value,i),s=u_(n,a);t.value=m_(t.value,s);let c=d_(m_(n,s),o);t.value=h_(t.value,c),r.value=f_(r.value,n,c),e.value=p_(e.value,n,n.slice(0,n.length-c.length))}else if(r){let e=b_(r.value,i),n=g_(y_(t.value,i),e);t.value=h_(t.value,n)}else if(e){let n=g_(y_(e.value,i),b_(t.value,i));t.value=m_(t.value,n)}}new class extends l_{tokenize(e){let t=RegExp(`(\\r?\\n)|[${S_}]+|[^\\S\\n\\r]+|[^${S_}]`,`ug`);return e.match(t)||[]}};var T_=new class extends l_{constructor(){super(...arguments),this.tokenize=D_}equals(e,t,n){return n.ignoreWhitespace?((!n.newlineIsToken||!e.includes(`
|
| 161 |
`))&&(e=e.trim()),(!n.newlineIsToken||!t.includes(`
|
| 162 |
`))&&(t=t.trim())):n.ignoreNewlineAtEof&&!n.newlineIsToken&&(e.endsWith(`
|
| 163 |
`)&&(e=e.slice(0,-1)),t.endsWith(`
|
|
|
|
| 171 |
`).map(e=>e+`
|
| 172 |
`);return t?n.pop():n.push(n.pop().slice(0,-1)),n}var Q=e=>document.querySelector(e),F_=e=>{if(e===null)return``;let t=new TextDecoder(`utf-8`,{fatal:!0}).decode(Uint8Array.from(atob(e),e=>e.charCodeAt(0)));if(t.includes(`\0`))throw Error(`Binary file`);return t};function I_({rpc:e,notice:t,canSwitch:n,onSwitch:r}){let i,a,o,s=!1,c=!1,l=[],u=Q(`#local-review`),d=window.self!==window.top,f=typeof window.showDirectoryPicker==`function`;function p(){Q(`#open-folder`).hidden=!!o,Q(`#reconnect-folder`).hidden=!!o||!a,Q(`#local-folder-bar`).hidden=!o;for(let e of[`open-folder`,`reconnect-folder`,`review-changes`,`refresh-folder`,`disconnect-folder`])Q(`#`+e).disabled=s||c;Q(`#review-changes`).disabled||=!o?.count,Q(`#refresh-folder`).disabled||=!!o?.count,Q(`#review-apply`).disabled=s||!l.length,Q(`#review-discard`).disabled=s||!l.length,Q(`#review-close`).disabled=s,o&&(Q(`#local-folder-name`).textContent=o.name,Q(`#local-folder-hint`).textContent=o.count?`${o.count} staged change${o.count===1?``:`s`} · original files unchanged until Apply`:`Local folder · edits are staged until you apply them`,Q(`#review-changes`).textContent=o.count?`Review & apply (${o.count})`:`Review & apply`),Q(`#export`).textContent=o?`Export changes`:`Export`,Q(`#save`).textContent=o?`Stage edit`:`Save`}function m(){return d?(Q(`#open-tab`).href=new URL(`?local-folder`,location.href).href,Q(`#open-tab`).hidden=!1,t(`Open the workspace in a new tab, then choose Open local folder. Browsers cannot grant folder access inside this embedded Space.`),!1):f?!0:(t(`Direct local-folder access needs a supported browser, such as Chrome or Edge. In this browser, use Import and Export to work with copies of your files.`),!1)}async function h(o){if(!(!m()||s||c))try{n(),c=!0,p();let s;if(o){if(s=a.handle,await s.requestPermission({mode:`read`})!==`granted`)throw Error(`Folder access was not granted.`)}else{if(a?.count&&!window.confirm(`Opening another folder replaces the saved staged edits for the previous folder. Reconnect to review them first, or continue?`))return;s=await window.showDirectoryPicker({id:`pi-workspace`,mode:`read`})}let l=await e(`local_open`,{handle:s,restore:o});i=s,a={handle:s,count:l.localFolder.count},r(l),t(`Opened ${s.name}. Review & apply writes staged changes to disk. Files load on demand; Refresh picks up external edits.`)}catch(e){e.name!==`AbortError`&&t(e.message)}finally{c=!1,p()}}Q(`#open-folder`).onclick=()=>h(!1),Q(`#reconnect-folder`).onclick=()=>h(!0),Q(`#disconnect-folder`).onclick=async()=>{try{n(),a={handle:i,count:o.count},r(await e(`local_close`)),t(`Demo workspace restored. Reconnect to continue with your local folder and staged edits.`)}catch(e){t(e.message)}},Q(`#refresh-folder`).onclick=async()=>{try{n(),r(await e(`local_refresh`)),t(`Folder refreshed from disk. Started a new chat with the current files.`)}catch(e){t(e.message)}};function g(e){l=e,Q(`#review-files`).replaceChildren(),Q(`#review-error`).textContent=``,Q(`#review-description`).textContent=`Review changes to ${o.name}. Applying writes them to your local folder.`;for(let t of e){let n=document.createElement(`details`),r=document.createElement(`summary`),i=document.createElement(`pre`);if(r.textContent=`${t.kind===`directory`?`New folder`:t.after===null?`Delete`:t.before===null?`Create`:`Edit`} · ${t.path.slice(1)}`,n.append(r),n.open=e.length<5,t.kind!==`directory`){try{let e=F_(t.before),n=F_(t.after);if(e.length+n.length>16e4)i.textContent=`Large text change. Inspect the file in the editor before applying.`;else{let r=N_(t.path,t.path,e,n,`on disk when read`,`staged`,{context:3});for(let e of r.split(`
|
| 173 |
`)){let t=document.createElement(`span`);t.textContent=e+`
|
| 174 |
+
`,e.startsWith(`+`)&&(t.className=`diff-add`),e.startsWith(`-`)&&(t.className=`diff-remove`),i.append(t)}}}catch{i.textContent=`Binary file · ${t.before===null?0:atob(t.before).length} → ${t.after===null?0:atob(t.after).length} bytes`}n.append(i)}Q(`#review-files`).append(n)}p()}return Q(`#review-changes`).onclick=async()=>{try{n(),g(await e(`local_changes`)),u.showModal()}catch(e){t(e.message)}},Q(`#review-close`).onclick=()=>u.close(),u.addEventListener(`cancel`,e=>{s&&e.preventDefault()}),Q(`#review-apply`).onclick=async()=>{try{if(await i.requestPermission({mode:`readwrite`})!==`granted`)throw Error(`Write access was not granted. Changes are still staged.`);let n=await e(`local_apply`);u.close(),t(`Applied ${n.applied} change${n.applied===1?``:`s`} to ${i.name}.`)}catch(t){try{g(await e(`local_changes`))}catch{}Q(`#review-error`).textContent=t.message}},Q(`#review-discard`).onclick=async()=>{if(window.confirm(`Discard all staged changes and reload files from disk? Your local files will not be changed.`))try{r(await e(`local_discard`)),u.close(),t(`Staged edits discarded. Folder refreshed and chat reset.`)}catch(e){Q(`#review-error`).textContent=e.message}},window.addEventListener(`beforeunload`,e=>{o?.count&&(e.preventDefault(),e.returnValue=``)}),new URLSearchParams(location.search).has(`local-folder`)&&t(`Choose Open local folder to select the project you want Pi to work on.`),{setBusy(e){s=e,p()},setState(e){o=e,a&&e&&(a.count=e.count),p()},restore(e){a=e,p()},get active(){return!!o}}}var $=e=>document.querySelector(e);c_($(`main`),$(`#column-resizer`));var L_=new Worker(new URL(new URL(`agent.worker-yyKS-x2c.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),R_=new Map,z_=1,B_=!1,V_=!1,H_,U_=!1,W_={},G_=[],K_,q_,J_=0,Y_=!0,X_=new Promise(e=>{K_=e}),Z_={events:[],generations:[],runtimeSamples:[],device:null,contextUsage:Ng([])},Q_=Zg($(`#runtime-meter`)),$_=a_($(`.editor-input`)),ev=Ug($(`#pi-terminal`),{onInput:e=>$(`#send`).classList.toggle(`has-input`,!!e.trim()),load:()=>X_.then(hv),prompt:e=>X_.then(()=>gv(e)),newChat:()=>X_.then(_v),stop:()=>iv(`stop`),shell:e=>{if(U_)throw Error(`Save your file edits before running commands.`);return iv(`shell`,{command:e})}}),tv=Wg($(`#model-dialog`),{onClose:()=>ev.focus()}),nv=Xg($(`.workspace > .terminal`),{run:async e=>{if(await X_,U_)throw Error(`Save your file edits before running commands.`);if(B_)throw Error(`Wait for Pi to finish or stop the current task first.`);return iv(`shell`,{command:e})}}),rv=I_({rpc:iv,notice:av,canSwitch(){if(B_)throw Error(`Wait for Pi to finish or stop the current task first.`);if(U_)throw Error(`Save or stage the open file first.`)},onSwitch(e){q_=void 0,H_=void 0,U_=!1,J_++,Y_=!1,W_=e.files,G_=e.messages,rv.setState(e.localFolder),$(`#filename`).textContent=`Select a file`,$(`#filetype`).replaceChildren(),$_.setFile(``,``),ev.setMessages(G_,{reset:!0}),pv(Ng(G_)),$(`#shell-clear`).click(),cv();let t=Object.hasOwn(W_,`/workspace/README.md`)?`/workspace/README.md`:Object.keys(W_).sort()[0];t&&uv(t),sv()}});function iv(e,t){return new Promise((n,r)=>{let i=z_++;R_.set(i,{resolve:n,reject:r}),L_.postMessage({id:i,action:e,args:t})})}function av(e){$(`#notice`).textContent=e,$(`#notice`).hidden=!e}function ov(e){return window.self===window.top||!/denied|storage directory access/i.test(e)?e:($(`#open-tab`).href=location.href,$(`#open-tab`).hidden=!1,`Your browser blocked storage in this embedded app. Open the workspace in a new tab to load the model and save files.`)}function sv(){$(`#load`).disabled=B_||V_,$(`#load`).classList.toggle(`is-loaded`,V_),V_?$(`#load`).textContent=`Loaded`:B_||($(`#load`).textContent=`Load model`),$(`#load`).title=$(`#load-status`).textContent+` `+$(`#load-detail`).textContent,$(`#send`).disabled=B_,$(`#newline`).disabled=B_,ev.setState({busy:B_,loaded:V_,activity:$(`#activity`).textContent}),$(`#stop`).hidden=!B_;for(let e of[`#new-chat`,`#add-file`,`#upload`])$(e).disabled=B_;nv.setBusy(B_),rv.setBusy(B_),$(`#save`).disabled=B_||!H_||!U_,$(`#editor`).disabled=B_||!H_||!Y_}function cv(){ev.setFiles(W_),$(`#files`).replaceChildren();for(let e of Object.keys(W_).sort()){let t=document.createElement(`button`),n=document.createElement(`span`);n.className=`file-name`,n.textContent=e.replace(/^\/workspace\//,``),t.append(t_(e),n),t.title=e,t.classList.toggle(`selected`,e===H_),t.onclick=()=>uv(e),$(`#files`).append(t)}H_&&!Object.hasOwn(W_,H_)&&!U_&&(H_=void 0,J_++,Y_=!1,$(`#filename`).textContent=`Select a file`,$(`#filetype`).replaceChildren(),$_.setFile(``,``),sv()),H_&&!U_&&(W_[H_]===null?lv(H_):(Y_=!0,$_.setFile(H_,W_[H_]??``)))}async function lv(e){let t=++J_;Y_=!1,$_.setFile(e,``,`Loading file…`),sv();try{let n=await iv(`preview`,{path:e});if(t!==J_||e!==H_||U_)return;$_.setFile(e,n),Y_=!0}catch(n){if(t!==J_||e!==H_)return;let r=n.code===`BINARY_FILE`?`No text preview available.
|
| 175 |
+
This file is binary or isn’t UTF-8 text.`:n.code===`FILE_TOO_LARGE`?`File too large to preview (1 MiB limit).`:`Unable to preview this file.
|
| 176 |
+
`+n.message;$_.setFile(e,``,r)}finally{t===J_&&sv()}}function uv(e){if(q_=void 0,U_){av(`Save the open file before selecting another file.`);return}H_=e,$(`#filename`).textContent=e,$(`#filetype`).replaceChildren(t_(e)),cv(),sv()}function dv(){if(U_){q_=void 0;return}if(!q_||!Object.hasOwn(W_,q_))return;let e=q_,t=e!==H_;uv(e),t&&($(`#editor`).scrollTop=0,$(`#editor`).scrollLeft=0);let n=[...$(`#files`).children].find(t=>t.title===e);if(n){let e=n.getBoundingClientRect(),t=$(`#files`).getBoundingClientRect();e.top<t.top?$(`#files`).scrollTop+=e.top-t.top:e.bottom>t.bottom&&($(`#files`).scrollTop+=e.bottom-t.bottom)}}function fv(){ev.setMessages(G_)}function pv(e){Z_.contextUsage=e,ev.setState({contextUsage:e})}L_.onmessage=({data:e})=>{if(Q_.update(e),e.type===`inference_activity`&&(Z_.runtimeSamples.push(e),Z_.runtimeSamples.length>160&&Z_.runtimeSamples.shift()),e.type===`reply`){let t=R_.get(e.id);R_.delete(e.id),e.error?t?.reject(Object.assign(Error(e.error),{name:e.errorName||`Error`,code:e.errorCode})):t?.resolve(e.value)}else if(e.type===`initialized`)rv.restore(e.savedLocal),rv.setState(e.localFolder),W_=e.files,G_=e.messages,cv(),fv(),pv(Ng(G_)),W_[`/workspace/README.md`]&&uv(`/workspace/README.md`),document.activeElement===document.body&&ev.focus(),K_();else if(e.type===`busy`)e.action===`load`&&($(`#load`).textContent=`Loading…`),B_=!0,$(`#activity`).textContent=e.action===`prompt`?`Pi is working…`:e.action===`load`?e.cachedOnly?`Loading local model…`:`Loading model…`:`Working…`,sv();else if(e.type===`idle`)B_=!1,$(`#progress`).hidden=!0,$(`#activity`).textContent=rv.active?`Local folder · review & apply to save changes to disk`:V_?`Ready · running on your device`:`Files and chat are saved on this device.`,sv();else if(e.type===`workspace`)W_=e.files,rv.setState(e.localFolder),cv(),dv();else if(e.type===`device`)Z_.device=e.device;else if(e.type===`load_progress`)$(`#progress`).hidden=!1,e.phase===`download`?($(`#progress`).hidden=!1,$(`#progress`).value=e.loaded/e.total*100,$(`#load-status`).textContent=e.loaded===e.total?`Download verified. Preparing model…`:`Downloading model weights from Hugging Face…`,$(`#load-detail`).textContent=`${(e.loaded/1e9).toFixed(2)} / ${(e.total/1e9).toFixed(2)} GB · ${Math.round(e.loaded/e.total*100)}%${e.cached?` · ${(e.cached/1e9).toFixed(2)} GB cached`:``}`,$(`#load`).textContent=`Loading ${Math.round(e.loaded/e.total*100)}%`):($(`#load-status`).textContent=e.phase===`compile`?`Preparing WebGPU session…`:`Warming up GPU shaders…`,$(`#load-detail`).textContent=``,$(`#progress`).removeAttribute(`value`),$(`#load`).textContent=e.phase===`compile`?`Preparing…`:`Almost ready…`),$(`#load`).title=$(`#load-status`).textContent+` `+$(`#load-detail`).textContent;else if(e.type===`loaded`)V_=!0,Z_.device=e.device,$(`#progress`).hidden=!0,$(`#load`).textContent=`Loaded`,$(`#load-status`).textContent=`Ready. Model inference and tools run entirely in this browser.`,$(`#load-detail`).textContent=e.cachedBytes?`Verified model restored from browser storage.`:`SHA-256 verified · model cached for next time.`,sv();else if(e.type===`agent_event`){let t=e.event;if(Z_.events.push(t.type),Z_.events.length>500&&Z_.events.shift(),t.message){let e=G_.findIndex(e=>e.timestamp===t.message.timestamp&&e.role===t.message.role&&e.toolCallId===t.message.toolCallId);e<0?G_.push(t.message):G_[e]=t.message,fv()}t.type===`tool_execution_start`&&($(`#activity`).textContent=`Running ${t.toolName}…`),t.type===`tool_execution_end`&&t.toolName===`read`&&!t.isError&&typeof t.result?.details?.path==`string`&&(q_=t.result.details.path,dv()),t.type===`turn_start`&&($(`#activity`).textContent=`Pi is thinking…`),ev.setState({activity:$(`#activity`).textContent})}else e.type===`context_usage`?pv({inputTokens:e.inputTokens,outputTokens:e.outputTokens}):e.type===`generation`?(Z_.generations.push(e),Z_.generations.length>100&&Z_.generations.shift()):e.type===`context_trim`?av(`Older turns were left out of the model context. Your full chat remains saved.`):e.type===`notice`?av(e.text):e.type===`persistence_error`?av(ov(e.error)):e.type===`fatal`&&(V_=!1,av(e.error),sv())},L_.onerror=e=>{Q_.update({type:`fatal`});let t=Error(e.message||`The worker stopped. Reload to restore your saved workspace.`);av(t.message);for(let e of R_.values())e.reject(t);R_.clear(),B_=!1,V_=!1,$(`#progress`).hidden=!0,sv()};var mv=e=>async t=>{t?.preventDefault();try{await X_,await e(t)}catch(e){av(e.message)}};async function hv({cachedOnly:e=!1}={}){if(B_)throw Error(`Wait for the current operation or stop it first.`);av(``);let t=new URLSearchParams(location.search);B_=!0,$(`#load`).textContent=`Loading…`,$(`#activity`).textContent=e?`Loading local model…`:`Loading model…`,$(`#load-status`).textContent=e?`Loading local model…`:`Checking your browser and saved model…`,$(`#load-detail`).textContent=``,$(`#progress`).hidden=!1,$(`#progress`).removeAttribute(`value`),Q_.update({type:`busy`,action:`load`,cachedOnly:e}),sv();try{return await iv(`load`,{runtimeURL:new URL(`./runtime/`,location.href).href,local:t.get(`source`)===`local`,origin:location.origin,limit128:t.has(`limit128`),cachedOnly:e})}catch(e){throw e.message=ov(e.message),$(`#load-status`).textContent=e.message,$(`#progress`).hidden=!0,e}}async function gv(e){if(e.trim()){if(U_)throw Error(`Save your file edits before asking Pi to work on them.`);try{if(!V_){let t;if(await iv(`cache_status`))try{ev.setQueuedPrompt(e),await hv({cachedOnly:!0})}catch(e){if(e.name===`AbortError`)throw e;e.code!==`MODEL_CACHE_MISS`&&(t=e.message)}for(;!V_;){if(ev.setQueuedPrompt(``),!await tv.request(e,{errorMessage:t}))throw new DOMException(`Message kept for later.`,`AbortError`);ev.setQueuedPrompt(e);try{await hv()}catch(e){if(e.name===`AbortError`)throw e;t=e.message}}}ev.setQueuedPrompt(``),av(``);let t=await iv(`prompt`,{text:e});return G_=t.messages,fv(),t}finally{ev.setQueuedPrompt(``)}}}$(`#load`).onclick=mv(hv),$(`#send`).onclick=()=>{ev.submit(),ev.focus()},$(`#newline`).onclick=()=>ev.newline(),$(`#stop`).onclick=mv(()=>iv(`stop`)),$(`.terminal-panel`).addEventListener(`click`,e=>{!e.defaultPrevented&&e.button===0&&e.target instanceof Element&&(e.target.closest(`#pi-terminal, button, a, input, textarea, select, label, summary, [role="button"], [contenteditable]`)||window.getSelection()?.isCollapsed!==!1&&ev.focus())});async function _v(){q_=void 0,G_=(await iv(`new_chat`)).messages,pv(Ng(G_)),ev.setMessages(G_,{reset:!0}),av(``),ev.focus()}$(`#new-chat`).onclick=mv(_v),$(`#editor`).oninput=()=>{U_=!0,sv()},$(`#save`).onclick=mv(async()=>{await iv(`write`,{path:H_,content:$(`#editor`).value}),U_=!1,cv(),sv(),av(``)}),$(`#add-file`).onclick=mv(async()=>{if(U_)throw Error(`Save the open file first.`);let e=window.prompt(`File path, relative to /workspace:`);if(!e?.trim())return;let t=e.startsWith(`/`)?e:`/workspace/`+e;if(Object.hasOwn(W_,t))throw Error(`That file already exists.`);await iv(`write`,{path:t,content:``}),uv(t)}),$(`#upload`).onchange=mv(async()=>{if(U_)throw Error(`Save the open file first.`);for(let e of $(`#upload`).files){if(e.size>1024**2)throw Error(`Import text files of 1 MiB or less.`);let t=await e.text();if(t.includes(`\0`))throw Error(`Import supports text files only.`);let n=`/workspace/`+e.name;if(Object.hasOwn(W_,n))throw Error(e.name+` already exists. Rename it before importing.`);await iv(`write`,{path:n,content:t}),uv(n)}$(`#upload`).value=``}),$(`#export`).onclick=mv(async()=>{let e=rv.active?{version:1,patches:await iv(`local_changes`)}:await iv(`snapshot`),t=URL.createObjectURL(new Blob([JSON.stringify(e,null,2)],{type:`application/json`})),n=document.createElement(`a`);n.href=t,n.download=rv.active?`pi-staged-changes.json`:`pi-workspace.json`,n.click(),setTimeout(()=>URL.revokeObjectURL(t),1e3)}),window.browserPi={ready:X_,load:hv,prompt:gv,rpc:iv,diagnostics:Z_,terminal:ev,runtimeMeter:Q_,get loaded(){return V_},get busy(){return B_}},sv();
|
index.html
CHANGED
|
@@ -21,7 +21,7 @@
|
|
| 21 |
<meta name="twitter:image" content="https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/social-thumbnail-v3.png">
|
| 22 |
<meta name="twitter:image:alt" content="A coding agent running entirely in your browser, above the Pi terminal and its pixel cat mascot.">
|
| 23 |
<title>Pi · Coding agent in your browser</title>
|
| 24 |
-
<script type="module" crossorigin src="./assets/index-
|
| 25 |
<link rel="stylesheet" crossorigin href="./assets/index-Uy7sEXBC.css">
|
| 26 |
</head>
|
| 27 |
<body>
|
|
|
|
| 21 |
<meta name="twitter:image" content="https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/social-thumbnail-v3.png">
|
| 22 |
<meta name="twitter:image:alt" content="A coding agent running entirely in your browser, above the Pi terminal and its pixel cat mascot.">
|
| 23 |
<title>Pi · Coding agent in your browser</title>
|
| 24 |
+
<script type="module" crossorigin src="./assets/index-O6siy3P4.js"></script>
|
| 25 |
<link rel="stylesheet" crossorigin href="./assets/index-Uy7sEXBC.css">
|
| 26 |
</head>
|
| 27 |
<body>
|
validation/local-folder-live.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"url": "https://mike0021-minicpm5-2b-webgpu-pi-http.static.hf.space/",
|
| 3 |
-
"date": "2026-09-16T14:
|
| 4 |
"checks": [
|
| 5 |
"Real directory handle crosses worker boundary; picker called with user activation; existing editor/tree reused; excluded directories omitted",
|
| 6 |
"Editor changes are staged; readable diff reviewed; Apply writes through the browser API",
|
|
|
|
| 1 |
{
|
| 2 |
"url": "https://mike0021-minicpm5-2b-webgpu-pi-http.static.hf.space/",
|
| 3 |
+
"date": "2026-09-16T14:30:31.156Z",
|
| 4 |
"checks": [
|
| 5 |
"Real directory handle crosses worker boundary; picker called with user activation; existing editor/tree reused; excluded directories omitted",
|
| 6 |
"Editor changes are staged; readable diff reviewed; Apply writes through the browser API",
|