Download app/src/workspace.mjs from Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP: direct link, hf CLI and curl.
- Browser
- Download file 7.46 kB
-
https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/src/workspace.mjs
- Command line
-
hf download hf://spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/app/src/workspace.mjs
-
curl -L -o workspace.mjs https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/src/workspace.mjs
7.46 kB
| import { Bash } from 'just-bash/browser'; | |
| import { Type } from '@earendil-works/pi-ai'; | |
| import { createBrowserFetch } from './browser-http.mjs'; | |
| // Frozen previous starter data, used only to recognize an unmodified demo. | |
| export const legacyProductsCsv = 'product,unit_cost,stock\nNotebook,2.50,24\nPencil,0.50,120\nFolder,1.20,18\nEraser,0.35,40\nStapler,2.75,9\nRuler,0.80,30\n'; | |
| export const legacyWorkspaceReadme = '# Browser workspace\n\nPi can read, write, edit, and run shell commands here.\nEverything stays in this browser. Explore our sales, product costs, and stock levels.\n'; | |
| export const legacyProductsJson = JSON.stringify([ | |
| { product: 'Notebook', unit_cost: 2.50, stock: 24 }, | |
| { product: 'Pencil', unit_cost: 0.50, stock: 120 }, | |
| { product: 'Folder', unit_cost: 1.20, stock: 18 }, | |
| { product: 'Eraser', unit_cost: 0.35, stock: 40 }, | |
| { product: 'Stapler', unit_cost: 2.75, stock: 9 }, | |
| { product: 'Ruler', unit_cost: 0.80, stock: 30 }, | |
| ], null, 2) + '\n'; | |
| export const legacyCostProductsJson = JSON.stringify(JSON.parse(legacyProductsJson).map(({ product, unit_cost }) => ({ product, unit_cost })), null, 2) + '\n'; | |
| export const legacySalesCsv = 'product,units,price\nNotebook,12,5\nPencil,30,2\nFolder,8,3\n'; | |
| export const legacyProfitReadme = '# Browser workspace\n\nSales and product costs for a small shop.\nPrices and unit costs are in USD.\nProfit means revenue minus the cost of units sold,\nbefore tax and other operating expenses.\n\nPi can read, edit, and run shell commands here.\nEverything stays in this browser.\n'; | |
| export const initialFiles = { | |
| '/workspace/README.md': '# Browser workspace\n\nYour files are saved in this browser.\n', | |
| '/workspace/sales.csv': 'product,units\nNotebook,12\nPencil,30\nFolder,8\n', | |
| '/workspace/products.json': JSON.stringify([ | |
| { product: 'Notebook', price: 5 }, | |
| { product: 'Pencil', price: 2 }, | |
| { product: 'Folder', price: 3 }, | |
| { product: 'Eraser', price: 1.5 }, | |
| { product: 'Stapler', price: 4.99 }, | |
| { product: 'Ruler', price: 2.5 }, | |
| ], null, 2) + '\n', | |
| }; | |
| const limitText = text => text.length > 6000 ? text.slice(0, 6000) + '\n[Output truncated at 6,000 characters; use head, grep, or a narrower query.]' : text; | |
| const result = (text, details = {}) => ({ content: [{ type: 'text', text: limitText(text) }], details }); | |
| export function createWorkspace(files = initialFiles, entries, localFolder) { | |
| if (entries) files = Object.fromEntries(entries.filter(([, entry]) => entry.type === 'file').map(([path, entry]) => [path, | |
| { mode: entry.mode, content: Uint8Array.from(atob(entry.base64), c => c.charCodeAt(0)) }])); | |
| const bash = new Bash({ ...(localFolder ? { fs: localFolder.mount() } : { files }), cwd: '/workspace', executionLimitProfile: 'hardened', fetch: createBrowserFetch(), | |
| executionLimits: { maxExecutionTimeMs: 10_000, maxCommandCount: 3000, | |
| maxFileSystemBytes: 16 * 1024 ** 2, maxOutputSize: 256 * 1024, maxSourceBytes: 128 * 1024 }, | |
| }); | |
| const path = value => bash.fs.resolvePath('/workspace', value); | |
| const ready = (async () => { | |
| for (const [file, entry] of entries ?? []) { | |
| if (entry.type === 'directory') { await bash.fs.mkdir(file, { recursive: true }); await bash.fs.chmod(file, entry.mode); } | |
| else if (entry.type === 'symlink') await bash.fs.symlink(entry.target, file); | |
| } | |
| })(); | |
| async function write(file, content) { | |
| const resolved = path(file); | |
| if (new TextEncoder().encode(content).length > 1024 ** 2) throw Error('The editor accepts files up to 1 MiB.'); | |
| await bash.fs.mkdir(resolved.slice(0, resolved.lastIndexOf('/')) || '/', { recursive: true }); | |
| await bash.fs.writeFile(resolved, content); | |
| } | |
| const tools = [{ | |
| 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: Type.Object({ command: Type.String({ description: 'The shell command to execute.' }) }), | |
| execute: async (_id, { command }, signal) => { | |
| const r = await bash.exec(command, { signal }); | |
| return result(`${r.stdout}${r.stderr ? '\nSTDERR:\n' + r.stderr : ''}\nExit code: ${r.exitCode}`, { exitCode: r.exitCode }); | |
| }, | |
| }, { | |
| name: 'read', label: 'Read file', description: 'Read a UTF-8 text file. Use bash head/grep for large files.', | |
| parameters: Type.Object({ path: Type.String() }), | |
| execute: async (_id, args) => { | |
| const resolved = await bash.fs.realpath(path(args.path)); | |
| return result(await bash.fs.readFile(resolved), { path: resolved }); | |
| }, | |
| }, { | |
| name: 'write', label: 'Write file', description: 'Create or replace a UTF-8 text file. Parent directories are created.', | |
| parameters: Type.Object({ path: Type.String(), content: Type.String() }), | |
| execute: async (_id, args) => { await write(args.path, args.content); return result('Wrote ' + path(args.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: Type.Object({ path: Type.String(), oldText: Type.String(), newText: Type.String() }), | |
| execute: async (_id, args) => { | |
| const content = await bash.fs.readFile(path(args.path)); | |
| if (!args.oldText || content.split(args.oldText).length !== 2) throw Error('oldText must match exactly once. Read the file first.'); | |
| await write(args.path, content.replace(args.oldText, () => args.newText)); | |
| return result('Edited ' + path(args.path)); | |
| }, | |
| }]; | |
| async function snapshot() { | |
| if (localFolder) return localFolder.listing(); | |
| const files = {}; | |
| for (const file of bash.fs.getAllPaths().sort()) { | |
| // Persist real files across the virtual FS, excluding built-in executable stubs. | |
| if (file.startsWith('/bin/') || file.startsWith('/usr/bin/') || file.startsWith('/dev/') || file.startsWith('/proc/')) continue; | |
| const stat = await bash.fs.lstat(file); | |
| if (stat.isFile) files[file] = await bash.fs.readFile(file); | |
| } | |
| return files; | |
| } | |
| async function serialize() { | |
| if (localFolder) return null; // Local file bytes are never included in demo snapshots. | |
| const entries = []; | |
| for (const file of bash.fs.getAllPaths().sort()) { | |
| if (['/bin', '/usr/bin', '/dev', '/proc'].some(root => file === root || file.startsWith(root + '/'))) continue; | |
| const stat = await bash.fs.lstat(file); | |
| if (stat.isSymbolicLink) entries.push([file, { type: 'symlink', target: await bash.fs.readlink(file) }]); | |
| else if (stat.isDirectory) entries.push([file, { type: 'directory', mode: stat.mode }]); | |
| else if (stat.isFile) { | |
| const bytes = await bash.fs.readFileBuffer(file); let binary = ''; | |
| for (let i = 0; i < bytes.length; i += 32768) binary += String.fromCharCode(...bytes.subarray(i, i + 32768)); | |
| entries.push([file, { type: 'file', base64: btoa(binary), mode: stat.mode }]); | |
| } | |
| } | |
| return entries; | |
| } | |
| return { bash, tools, ready, write, snapshot, serialize, localFolder, read: file => bash.fs.readFile(path(file)) }; | |
| } | |