Spaces:
Running
Enable browser curl requests to any HTTPS host
Browse filesThe browser shell currently rejects network commands. This enables `curl` from Shell and Pi's bash tool for any HTTPS host, using browser `fetch` without a backend or proxy. For example, Pi can fetch a raw GitHub Gist or download JSON and inspect it with `jq`.
GET, HEAD, and POST are supported, with an 8-second deadline, a 1 MiB response cap, and cancellation through Stop. Browser CORS still applies; cookies are omitted and redirects require the final URL. Model/tool instructions, the shell footer, setup copy, and HTTP help explain this behavior.
This follows the repository's prebuilt-static-app layout: it replaces the main and agent-worker bundles, removes their superseded versions, adds the help page, and updates the README. The JavaScript bundles are byte-identical to the tested duplicate. The HTML/help retain this Space's identity, and stale townbox metadata/direct-app links now point to victor.
Validation:
- 22 unit tests and the production build passed.
- 17 real-browser shell checks passed on the [test Space](https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP), including previously unlisted hosts, raw Gists, GET/HEAD/POST, CORS/redirect errors, timeout, cancellation, and persistence.
- Actual MiniCPM5-2B fetched a public Gist and correctly described it in 7.4 seconds.
- A final smoke test applied these exact PR files over the current victor snapshot: raw Gist fetch, non-HTTPS rejection, help page, and canonical metadata passed with no page errors.
Reviewable [source and tests](https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/tree/1b0b7301591a12ab1aa1b6b9fe1c6e1042c4546a/app): the transport is `src/browser-http.mjs`, wired through `src/workspace.mjs` and `src/agent.worker.mjs`. The [validation report](https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/blob/1b0b7301591a12ab1aa1b6b9fe1c6e1042c4546a/validation/REPORT.md) records both current results and earlier model limitations (duplicate POSTs and excessive retries). These checks establish the integration, not general agent reliability.
- README.md +16 -3
- assets/{agent.worker-CS8jL-AS.js → agent.worker-DONV7yi_.js} +0 -0
- assets/{index-DnXFGQ8z.js → index-BzGjruQ9.js} +1 -1
- http-help.html +17 -0
- index.html +6 -6
|
@@ -8,7 +8,7 @@ app_file: index.html
|
|
| 8 |
pinned: false
|
| 9 |
license: apache-2.0
|
| 10 |
short_description: A coding agent running entirely in your browser
|
| 11 |
-
thumbnail: https://huggingface.co/spaces/
|
| 12 |
models:
|
| 13 |
- openbmb/MiniCPM5-2B
|
| 14 |
- Mike0021/MiniCPM5-2B-ONNX
|
|
@@ -28,18 +28,31 @@ Run **Pi's real agent core**, **just-bash**, and **MiniCPM5-2B** entirely in you
|
|
| 28 |
|
| 29 |
Send your first message. If the complete verified model is already cached in this browser, Pi restores it and sends your message automatically, without a confirmation dialog. Otherwise, a **Load model & send** dialog explains the download. Confirming closes the dialog and shows progress in the top bar, then sends your message automatically. Cancel or Stop keeps your unsent draft; loading errors offer a retry. If the cache is incomplete or has changed, downloading missing files requires confirmation. You can also click **Load model** ahead of time. The first download is about **1.84 GB**; files are SHA-256 verified and cached in OPFS. The loader uses bounded byte ranges, retries, cancellation, and separate download / GPU setup / warm-up phases. Chat and virtual files are saved in IndexedDB on this site. Cached weights can be reused after reloading. The entire workspace uses a dark theme.
|
| 30 |
|
| 31 |
-
If your browser blocks storage inside Hugging Face's embedded view, use the app's **Open workspace in a new tab** button or [open the app directly](https://
|
| 32 |
|
| 33 |
Requires WebGPU with FP16 (`shader-f16`) in a browser worker, OPFS, and sufficient storage and device memory. There is an 8,192-token prompt-plus-output budget, up to 2,048 generated tokens per model call, and a 12-turn limit per request. Desktop and narrow-screen layouts are included. Physical Android/iOS devices have not been tested.
|
| 34 |
|
| 35 |
Try: **What's our total sales revenue?**
|
| 36 |
|
| 37 |
-
Pi has `bash`, `read`, `write`, and `edit` tools. just-bash supports shell scripts, pipes, redirects, `awk`, `jq`, `sed`, `grep`, and other text commands. Node.js, Python, npm, native binaries,
|
| 38 |
|
| 39 |
The top bar shows the model, its load status, and a live token-speed sparkline labeled **WebGPU** for inference or **JavaScript** for tool execution. Speed is measured from generated tokens, including thinking; prompt processing and tool execution have their own statuses. The chart clears when generation stops. Successful file reads open the corresponding workspace file while preserving unsaved edits. Common file types have Seti icons. The shell includes live Bash syntax coloring, multiline commands, session history, colored errors/exit states, and a resizable output area. Panels use background shading and rounded corners sized to their nested spacing throughout the dark workspace.
|
| 40 |
|
| 41 |
The terminal uses Pi's dark palette and native message/editor padding. Recent checks cover seven viewport sizes, Unicode alignment, long tool output, a simulated 60-turn transcript, twelve keyboard interactions, five actual-model workflows, and a ten-turn conversation with twelve tool results. Mobile sizes were simulated on desktop Chromium.
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
## Our conversion
|
| 44 |
|
| 45 |
The included `q4f16` model was converted directly from the official [OpenBMB MiniCPM5-2B BF16 checkpoint](https://huggingface.co/openbmb/MiniCPM5-2B/tree/abe115e887989b14f05e64a3b260648329324c3f), not from community-converted weights.
|
|
|
|
| 8 |
pinned: false
|
| 9 |
license: apache-2.0
|
| 10 |
short_description: A coding agent running entirely in your browser
|
| 11 |
+
thumbnail: https://huggingface.co/spaces/victor/MiniCPM5-2B-WebGPU-Pi/resolve/main/social-thumbnail-v3.png
|
| 12 |
models:
|
| 13 |
- openbmb/MiniCPM5-2B
|
| 14 |
- Mike0021/MiniCPM5-2B-ONNX
|
|
|
|
| 28 |
|
| 29 |
Send your first message. If the complete verified model is already cached in this browser, Pi restores it and sends your message automatically, without a confirmation dialog. Otherwise, a **Load model & send** dialog explains the download. Confirming closes the dialog and shows progress in the top bar, then sends your message automatically. Cancel or Stop keeps your unsent draft; loading errors offer a retry. If the cache is incomplete or has changed, downloading missing files requires confirmation. You can also click **Load model** ahead of time. The first download is about **1.84 GB**; files are SHA-256 verified and cached in OPFS. The loader uses bounded byte ranges, retries, cancellation, and separate download / GPU setup / warm-up phases. Chat and virtual files are saved in IndexedDB on this site. Cached weights can be reused after reloading. The entire workspace uses a dark theme.
|
| 30 |
|
| 31 |
+
If your browser blocks storage inside Hugging Face's embedded view, use the app's **Open workspace in a new tab** button or [open the app directly](https://victor-minicpm5-2b-webgpu-pi.static.hf.space/). This keeps inference and files in your browser and does not require changing browser privacy settings.
|
| 32 |
|
| 33 |
Requires WebGPU with FP16 (`shader-f16`) in a browser worker, OPFS, and sufficient storage and device memory. There is an 8,192-token prompt-plus-output budget, up to 2,048 generated tokens per model call, and a 12-turn limit per request. Desktop and narrow-screen layouts are included. Physical Android/iOS devices have not been tested.
|
| 34 |
|
| 35 |
Try: **What's our total sales revenue?**
|
| 36 |
|
| 37 |
+
Pi has `bash`, `read`, `write`, and `edit` tools. just-bash supports shell scripts, pipes, redirects, `awk`, `jq`, `sed`, `grep`, and other text commands. The shell also supports `curl` requests to any HTTPS URL. Node.js, Python, npm, native binaries, and SQLite remain unavailable. The left column uses xterm.js with Pi's actual terminal renderer, editor, and message components. MiniCPM and the tools run in the browser worker. This reuses Pi's TUI; the full Node CLI and its extensions are not included. Files are virtual and stored on your device. HTTP commands can send explicitly requested URLs, headers, and bodies to external services. Export work you want to keep.
|
| 38 |
|
| 39 |
The top bar shows the model, its load status, and a live token-speed sparkline labeled **WebGPU** for inference or **JavaScript** for tool execution. Speed is measured from generated tokens, including thinking; prompt processing and tool execution have their own statuses. The chart clears when generation stops. Successful file reads open the corresponding workspace file while preserving unsaved edits. Common file types have Seti icons. The shell includes live Bash syntax coloring, multiline commands, session history, colored errors/exit states, and a resizable output area. Panels use background shading and rounded corners sized to their nested spacing throughout the dark workspace.
|
| 40 |
|
| 41 |
The terminal uses Pi's dark palette and native message/editor padding. Recent checks cover seven viewport sizes, Unicode alignment, long tool output, a simulated 60-turn transcript, twelve keyboard interactions, five actual-model workflows, and a ten-turn conversation with twelve tool results. Mobile sizes were simulated on desktop Chromium.
|
| 42 |
|
| 43 |
+
## HTTP requests
|
| 44 |
+
|
| 45 |
+
Use `curl` in Shell or ask Pi to fetch a URL. Requests go directly from the browser to the destination, with no proxy or backend. Any HTTPS host is allowed; the destination must permit browser access through CORS. Inference remains local.
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
curl -fsS https://jsonplaceholder.typicode.com/todos/1 -o todo.json
|
| 49 |
+
jq '{id, title, completed}' todo.json
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
GET, HEAD, and POST are supported. Requests have an 8-second deadline and a 1 MiB response limit; Stop cancels an active request. Browser cookies are omitted, and credentials embedded in URLs are rejected. Redirects require the final HTTPS URL directly. Use `set -o pipefail` before pipelines so download failures are preserved. See [HTTP help](http-help.html) for examples and details.
|
| 53 |
+
|
| 54 |
+
Verification on the [HTTP test Space](https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP): 22 unit tests and 17 live shell checks passed, including raw GitHub Gists and a previously unlisted JSON API. A real MiniCPM5-2B trial fetched a public Gist and answered correctly in 7.4 seconds. The [report](https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/blob/1b0b7301591a12ab1aa1b6b9fe1c6e1042c4546a/validation/REPORT.md) also records model limitations, including duplicate POSTs and excessive retries in earlier trials.
|
| 55 |
+
|
| 56 |
## Our conversion
|
| 57 |
|
| 58 |
The included `q4f16` model was converted directly from the official [OpenBMB MiniCPM5-2B BF16 checkpoint](https://huggingface.co/openbmb/MiniCPM5-2B/tree/abe115e887989b14f05e64a3b260648329324c3f), not from community-converted weights.
|
|
The diff for this file is too large to render.
See raw diff
|
|
|
|
@@ -157,4 +157,4 @@ 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=qg(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(Jg(qg(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 Xg(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 Zg={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`},Qg={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 $g(e){let t=e.split(`/`).at(-1).toLowerCase();return[`.gitignore`,`.gitattributes`,`.gitmodules`].includes(t)?`git`:t===`.env`||t.startsWith(`.env.`)||t===`.editorconfig`?`config`:Qg[t.split(`.`).at(-1)]??`default`}function e_(e){let t=$g(e),n=document.createElement(`span`);return n.className=`file-icon`,n.dataset.kind=t,n.setAttribute(`aria-hidden`,`true`),n.style.setProperty(`--file-color`,Zg[t]),n.style.setProperty(`--file-icon`,`url("${new URL(`file-icons/${t}.svg`,document.baseURI).href}")`),n}var t_={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 n_(e=``){let t=e.split(`/`).at(-1).toLowerCase();return/^dockerfile(?:\.|$)/.test(t)?`docker`:/^\.(?:bashrc|bash_profile|zshrc|profile)$/.test(t)?`bash`:t_[t.split(`.`).at(-1)]??null}function r_(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 i_(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=n_(r),s=t.value;if(e.dataset.language=o??`plain`,l||!s||!o||!r_(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 i=r!==e;r=e,t.value!==n&&(t.value=n),i&&(t.scrollTop=0,t.scrollLeft=0),h()},dispose(){clearTimeout(a),p(),g.disconnect()}}}var a_=`pi-column-ratio`,o_=1/1.83;function s_(e,t){let n=matchMedia(`(min-width: 851px)`),r=getComputedStyle(e),i=parseFloat(r.getPropertyValue(`--conversation-min`)),a=parseFloat(r.getPropertyValue(`--workspace-min`)),o=o_,s,c=0;try{let e=Number(localStorage.getItem(a_));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(a_,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=o_,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 $=e=>document.querySelector(e);s_($(`main`),$(`#column-resizer`));var c_=new Worker(new URL(new URL(`agent.worker-CS8jL-AS.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),l_=new Map,u_=1,d_=!1,f_=!1,p_,m_=!1,h_={},g_=[],__,v_,y_=new Promise(e=>{__=e}),b_={events:[],generations:[],runtimeSamples:[],device:null,contextUsage:Mg([])},x_=Xg($(`#runtime-meter`)),S_=i_($(`.editor-input`)),C_=Hg($(`#pi-terminal`),{onInput:e=>$(`#send`).classList.toggle(`has-input`,!!e.trim()),load:()=>y_.then(I_),prompt:e=>y_.then(()=>L_(e)),newChat:()=>y_.then(R_),stop:()=>E_(`stop`),shell:e=>{if(m_)throw Error(`Save your file edits before running commands.`);return E_(`shell`,{command:e})}}),w_=Ug($(`#model-dialog`),{onClose:()=>C_.focus()}),T_=Yg($(`.workspace > .terminal`),{run:async e=>{if(await y_,m_)throw Error(`Save your file edits before running commands.`);if(d_)throw Error(`Wait for Pi to finish or stop the current task first.`);return E_(`shell`,{command:e})}});function E_(e,t){return new Promise((n,r)=>{let i=u_++;l_.set(i,{resolve:n,reject:r}),c_.postMessage({id:i,action:e,args:t})})}function D_(e){$(`#notice`).textContent=e,$(`#notice`).hidden=!e}function O_(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 k_(){$(`#load`).disabled=d_||f_,$(`#load`).classList.toggle(`is-loaded`,f_),f_?$(`#load`).textContent=`Loaded`:d_||($(`#load`).textContent=`Load model`),$(`#load`).title=$(`#load-status`).textContent+` `+$(`#load-detail`).textContent,$(`#send`).disabled=d_,$(`#newline`).disabled=d_,C_.setState({busy:d_,loaded:f_,activity:$(`#activity`).textContent}),$(`#stop`).hidden=!d_;for(let e of[`#new-chat`,`#add-file`,`#upload`])$(e).disabled=d_;T_.setBusy(d_),$(`#save`).disabled=d_||!p_||!m_,$(`#editor`).disabled=d_||!p_}function A_(){C_.setFiles(h_),$(`#files`).replaceChildren();for(let e of Object.keys(h_).sort()){let t=document.createElement(`button`),n=document.createElement(`span`);n.className=`file-name`,n.textContent=e.replace(/^\/workspace\//,``),t.append(e_(e),n),t.title=e,t.classList.toggle(`selected`,e===p_),t.onclick=()=>j_(e),$(`#files`).append(t)}p_&&!m_&&S_.setFile(p_,h_[p_]??``)}function j_(e){if(v_=void 0,m_){D_(`Save the open file before selecting another file.`);return}p_=e,$(`#filename`).textContent=e,$(`#filetype`).replaceChildren(e_(e)),A_(),k_()}function M_(){if(m_){v_=void 0;return}if(!v_||!Object.hasOwn(h_,v_))return;let e=v_,t=e!==p_;j_(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 N_(){C_.setMessages(g_)}function P_(e){b_.contextUsage=e,C_.setState({contextUsage:e})}c_.onmessage=({data:e})=>{if(x_.update(e),e.type===`inference_activity`&&(b_.runtimeSamples.push(e),b_.runtimeSamples.length>160&&b_.runtimeSamples.shift()),e.type===`reply`){let t=l_.get(e.id);l_.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`)h_=e.files,g_=e.messages,A_(),N_(),P_(Mg(g_)),h_[`/workspace/README.md`]&&j_(`/workspace/README.md`),document.activeElement===document.body&&C_.focus(),__();else if(e.type===`busy`)e.action===`load`&&($(`#load`).textContent=`Loading…`),d_=!0,$(`#activity`).textContent=e.action===`prompt`?`Pi is working…`:e.action===`load`?e.cachedOnly?`Loading local model…`:`Loading model…`:`Working…`,k_();else if(e.type===`idle`)d_=!1,$(`#progress`).hidden=!0,$(`#activity`).textContent=f_?`Ready · running on your device`:`Files and chat are saved on this device.`,k_();else if(e.type===`workspace`)h_=e.files,A_(),M_();else if(e.type===`device`)b_.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`)f_=!0,b_.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.`,k_();else if(e.type===`agent_event`){let t=e.event;if(b_.events.push(t.type),b_.events.length>500&&b_.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,N_()}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`&&(v_=t.result.details.path,M_()),t.type===`turn_start`&&($(`#activity`).textContent=`Pi is thinking…`),C_.setState({activity:$(`#activity`).textContent})}else e.type===`context_usage`?P_({inputTokens:e.inputTokens,outputTokens:e.outputTokens}):e.type===`generation`?(b_.generations.push(e),b_.generations.length>100&&b_.generations.shift()):e.type===`context_trim`?D_(`Older turns were left out of the model context. Your full chat remains saved.`):e.type===`notice`?D_(e.text):e.type===`persistence_error`?D_(O_(e.error)):e.type===`fatal`&&(f_=!1,D_(e.error),k_())},c_.onerror=e=>{x_.update({type:`fatal`});let t=Error(e.message||`The worker stopped. Reload to restore your saved workspace.`);D_(t.message);for(let e of l_.values())e.reject(t);l_.clear(),d_=!1,f_=!1,$(`#progress`).hidden=!0,k_()};var F_=e=>async t=>{t?.preventDefault();try{await y_,await e(t)}catch(e){D_(e.message)}};async function I_({cachedOnly:e=!1}={}){if(d_)throw Error(`Wait for the current operation or stop it first.`);D_(``);let t=new URLSearchParams(location.search);d_=!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`),x_.update({type:`busy`,action:`load`,cachedOnly:e}),k_();try{return await E_(`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=O_(e.message),$(`#load-status`).textContent=e.message,$(`#progress`).hidden=!0,e}}async function L_(e){if(e.trim()){if(m_)throw Error(`Save your file edits before asking Pi to work on them.`);try{if(!f_){let t;if(await E_(`cache_status`))try{C_.setQueuedPrompt(e),await I_({cachedOnly:!0})}catch(e){if(e.name===`AbortError`)throw e;e.code!==`MODEL_CACHE_MISS`&&(t=e.message)}for(;!f_;){if(C_.setQueuedPrompt(``),!await w_.request(e,{errorMessage:t}))throw new DOMException(`Message kept for later.`,`AbortError`);C_.setQueuedPrompt(e);try{await I_()}catch(e){if(e.name===`AbortError`)throw e;t=e.message}}}C_.setQueuedPrompt(``),D_(``);let t=await E_(`prompt`,{text:e});return g_=t.messages,N_(),t}finally{C_.setQueuedPrompt(``)}}}$(`#load`).onclick=F_(I_),$(`#send`).onclick=()=>{C_.submit(),C_.focus()},$(`#newline`).onclick=()=>C_.newline(),$(`#stop`).onclick=F_(()=>E_(`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&&C_.focus())});async function R_(){v_=void 0,g_=(await E_(`new_chat`)).messages,P_(Mg(g_)),C_.setMessages(g_,{reset:!0}),D_(``),C_.focus()}$(`#new-chat`).onclick=F_(R_),$(`#editor`).oninput=()=>{m_=!0,k_()},$(`#save`).onclick=F_(async()=>{await E_(`write`,{path:p_,content:$(`#editor`).value}),m_=!1,A_(),k_(),D_(``)}),$(`#add-file`).onclick=F_(async()=>{if(m_)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(h_,t))throw Error(`That file already exists.`);await E_(`write`,{path:t,content:``}),j_(t)}),$(`#upload`).onchange=F_(async()=>{if(m_)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(h_,n))throw Error(e.name+` already exists. Rename it before importing.`);await E_(`write`,{path:n,content:t}),j_(n)}$(`#upload`).value=``}),$(`#export`).onclick=F_(async()=>{let e=await E_(`snapshot`),t=URL.createObjectURL(new Blob([JSON.stringify(e,null,2)],{type:`application/json`})),n=document.createElement(`a`);n.href=t,n.download=`pi-workspace.json`,n.click(),setTimeout(()=>URL.revokeObjectURL(t),1e3)}),window.browserPi={ready:y_,load:I_,prompt:L_,rpc:E_,diagnostics:b_,terminal:C_,runtimeMeter:x_,get loaded(){return f_},get busy(){return d_}},k_();
|
|
|
|
| 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=qg(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(Jg(qg(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 Xg(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 Zg={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`},Qg={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 $g(e){let t=e.split(`/`).at(-1).toLowerCase();return[`.gitignore`,`.gitattributes`,`.gitmodules`].includes(t)?`git`:t===`.env`||t.startsWith(`.env.`)||t===`.editorconfig`?`config`:Qg[t.split(`.`).at(-1)]??`default`}function e_(e){let t=$g(e),n=document.createElement(`span`);return n.className=`file-icon`,n.dataset.kind=t,n.setAttribute(`aria-hidden`,`true`),n.style.setProperty(`--file-color`,Zg[t]),n.style.setProperty(`--file-icon`,`url("${new URL(`file-icons/${t}.svg`,document.baseURI).href}")`),n}var t_={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 n_(e=``){let t=e.split(`/`).at(-1).toLowerCase();return/^dockerfile(?:\.|$)/.test(t)?`docker`:/^\.(?:bashrc|bash_profile|zshrc|profile)$/.test(t)?`bash`:t_[t.split(`.`).at(-1)]??null}function r_(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 i_(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=n_(r),s=t.value;if(e.dataset.language=o??`plain`,l||!s||!o||!r_(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 i=r!==e;r=e,t.value!==n&&(t.value=n),i&&(t.scrollTop=0,t.scrollLeft=0),h()},dispose(){clearTimeout(a),p(),g.disconnect()}}}var a_=`pi-column-ratio`,o_=1/1.83;function s_(e,t){let n=matchMedia(`(min-width: 851px)`),r=getComputedStyle(e),i=parseFloat(r.getPropertyValue(`--conversation-min`)),a=parseFloat(r.getPropertyValue(`--workspace-min`)),o=o_,s,c=0;try{let e=Number(localStorage.getItem(a_));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(a_,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=o_,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 $=e=>document.querySelector(e);s_($(`main`),$(`#column-resizer`));var c_=new Worker(new URL(new URL(`agent.worker-DONV7yi_.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),l_=new Map,u_=1,d_=!1,f_=!1,p_,m_=!1,h_={},g_=[],__,v_,y_=new Promise(e=>{__=e}),b_={events:[],generations:[],runtimeSamples:[],device:null,contextUsage:Mg([])},x_=Xg($(`#runtime-meter`)),S_=i_($(`.editor-input`)),C_=Hg($(`#pi-terminal`),{onInput:e=>$(`#send`).classList.toggle(`has-input`,!!e.trim()),load:()=>y_.then(I_),prompt:e=>y_.then(()=>L_(e)),newChat:()=>y_.then(R_),stop:()=>E_(`stop`),shell:e=>{if(m_)throw Error(`Save your file edits before running commands.`);return E_(`shell`,{command:e})}}),w_=Ug($(`#model-dialog`),{onClose:()=>C_.focus()}),T_=Yg($(`.workspace > .terminal`),{run:async e=>{if(await y_,m_)throw Error(`Save your file edits before running commands.`);if(d_)throw Error(`Wait for Pi to finish or stop the current task first.`);return E_(`shell`,{command:e})}});function E_(e,t){return new Promise((n,r)=>{let i=u_++;l_.set(i,{resolve:n,reject:r}),c_.postMessage({id:i,action:e,args:t})})}function D_(e){$(`#notice`).textContent=e,$(`#notice`).hidden=!e}function O_(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 k_(){$(`#load`).disabled=d_||f_,$(`#load`).classList.toggle(`is-loaded`,f_),f_?$(`#load`).textContent=`Loaded`:d_||($(`#load`).textContent=`Load model`),$(`#load`).title=$(`#load-status`).textContent+` `+$(`#load-detail`).textContent,$(`#send`).disabled=d_,$(`#newline`).disabled=d_,C_.setState({busy:d_,loaded:f_,activity:$(`#activity`).textContent}),$(`#stop`).hidden=!d_;for(let e of[`#new-chat`,`#add-file`,`#upload`])$(e).disabled=d_;T_.setBusy(d_),$(`#save`).disabled=d_||!p_||!m_,$(`#editor`).disabled=d_||!p_}function A_(){C_.setFiles(h_),$(`#files`).replaceChildren();for(let e of Object.keys(h_).sort()){let t=document.createElement(`button`),n=document.createElement(`span`);n.className=`file-name`,n.textContent=e.replace(/^\/workspace\//,``),t.append(e_(e),n),t.title=e,t.classList.toggle(`selected`,e===p_),t.onclick=()=>j_(e),$(`#files`).append(t)}p_&&!m_&&S_.setFile(p_,h_[p_]??``)}function j_(e){if(v_=void 0,m_){D_(`Save the open file before selecting another file.`);return}p_=e,$(`#filename`).textContent=e,$(`#filetype`).replaceChildren(e_(e)),A_(),k_()}function M_(){if(m_){v_=void 0;return}if(!v_||!Object.hasOwn(h_,v_))return;let e=v_,t=e!==p_;j_(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 N_(){C_.setMessages(g_)}function P_(e){b_.contextUsage=e,C_.setState({contextUsage:e})}c_.onmessage=({data:e})=>{if(x_.update(e),e.type===`inference_activity`&&(b_.runtimeSamples.push(e),b_.runtimeSamples.length>160&&b_.runtimeSamples.shift()),e.type===`reply`){let t=l_.get(e.id);l_.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`)h_=e.files,g_=e.messages,A_(),N_(),P_(Mg(g_)),h_[`/workspace/README.md`]&&j_(`/workspace/README.md`),document.activeElement===document.body&&C_.focus(),__();else if(e.type===`busy`)e.action===`load`&&($(`#load`).textContent=`Loading…`),d_=!0,$(`#activity`).textContent=e.action===`prompt`?`Pi is working…`:e.action===`load`?e.cachedOnly?`Loading local model…`:`Loading model…`:`Working…`,k_();else if(e.type===`idle`)d_=!1,$(`#progress`).hidden=!0,$(`#activity`).textContent=f_?`Ready · running on your device`:`Files and chat are saved on this device.`,k_();else if(e.type===`workspace`)h_=e.files,A_(),M_();else if(e.type===`device`)b_.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`)f_=!0,b_.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.`,k_();else if(e.type===`agent_event`){let t=e.event;if(b_.events.push(t.type),b_.events.length>500&&b_.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,N_()}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`&&(v_=t.result.details.path,M_()),t.type===`turn_start`&&($(`#activity`).textContent=`Pi is thinking…`),C_.setState({activity:$(`#activity`).textContent})}else e.type===`context_usage`?P_({inputTokens:e.inputTokens,outputTokens:e.outputTokens}):e.type===`generation`?(b_.generations.push(e),b_.generations.length>100&&b_.generations.shift()):e.type===`context_trim`?D_(`Older turns were left out of the model context. Your full chat remains saved.`):e.type===`notice`?D_(e.text):e.type===`persistence_error`?D_(O_(e.error)):e.type===`fatal`&&(f_=!1,D_(e.error),k_())},c_.onerror=e=>{x_.update({type:`fatal`});let t=Error(e.message||`The worker stopped. Reload to restore your saved workspace.`);D_(t.message);for(let e of l_.values())e.reject(t);l_.clear(),d_=!1,f_=!1,$(`#progress`).hidden=!0,k_()};var F_=e=>async t=>{t?.preventDefault();try{await y_,await e(t)}catch(e){D_(e.message)}};async function I_({cachedOnly:e=!1}={}){if(d_)throw Error(`Wait for the current operation or stop it first.`);D_(``);let t=new URLSearchParams(location.search);d_=!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`),x_.update({type:`busy`,action:`load`,cachedOnly:e}),k_();try{return await E_(`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=O_(e.message),$(`#load-status`).textContent=e.message,$(`#progress`).hidden=!0,e}}async function L_(e){if(e.trim()){if(m_)throw Error(`Save your file edits before asking Pi to work on them.`);try{if(!f_){let t;if(await E_(`cache_status`))try{C_.setQueuedPrompt(e),await I_({cachedOnly:!0})}catch(e){if(e.name===`AbortError`)throw e;e.code!==`MODEL_CACHE_MISS`&&(t=e.message)}for(;!f_;){if(C_.setQueuedPrompt(``),!await w_.request(e,{errorMessage:t}))throw new DOMException(`Message kept for later.`,`AbortError`);C_.setQueuedPrompt(e);try{await I_()}catch(e){if(e.name===`AbortError`)throw e;t=e.message}}}C_.setQueuedPrompt(``),D_(``);let t=await E_(`prompt`,{text:e});return g_=t.messages,N_(),t}finally{C_.setQueuedPrompt(``)}}}$(`#load`).onclick=F_(I_),$(`#send`).onclick=()=>{C_.submit(),C_.focus()},$(`#newline`).onclick=()=>C_.newline(),$(`#stop`).onclick=F_(()=>E_(`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&&C_.focus())});async function R_(){v_=void 0,g_=(await E_(`new_chat`)).messages,P_(Mg(g_)),C_.setMessages(g_,{reset:!0}),D_(``),C_.focus()}$(`#new-chat`).onclick=F_(R_),$(`#editor`).oninput=()=>{m_=!0,k_()},$(`#save`).onclick=F_(async()=>{await E_(`write`,{path:p_,content:$(`#editor`).value}),m_=!1,A_(),k_(),D_(``)}),$(`#add-file`).onclick=F_(async()=>{if(m_)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(h_,t))throw Error(`That file already exists.`);await E_(`write`,{path:t,content:``}),j_(t)}),$(`#upload`).onchange=F_(async()=>{if(m_)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(h_,n))throw Error(e.name+` already exists. Rename it before importing.`);await E_(`write`,{path:n,content:t}),j_(n)}$(`#upload`).value=``}),$(`#export`).onclick=F_(async()=>{let e=await E_(`snapshot`),t=URL.createObjectURL(new Blob([JSON.stringify(e,null,2)],{type:`application/json`})),n=document.createElement(`a`);n.href=t,n.download=`pi-workspace.json`,n.click(),setTimeout(()=>URL.revokeObjectURL(t),1e3)}),window.browserPi={ready:y_,load:I_,prompt:L_,rpc:E_,diagnostics:b_,terminal:C_,runtimeMeter:x_,get loaded(){return f_},get busy(){return d_}},k_();
|
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
| 3 |
+
<title>HTTP · Pi browser workspace</title>
|
| 4 |
+
<style>html{color-scheme:dark;background:#18181e;color:#e4e4eb;font:17px/1.6 system-ui}body{max-width:760px;margin:40px auto;padding:0 20px}a{color:#a4e2d4}pre{background:#262630;padding:16px;overflow:auto;border-radius:12px}code{font-size:14px}</style>
|
| 5 |
+
<h1>HTTP from the browser shell</h1>
|
| 6 |
+
<p>The shell supports real <code>curl</code> requests. The model and shell run on your device. Requests go directly from your browser to the destination; there is no proxy. Request URLs, headers, and bodies are sent to that service.</p>
|
| 7 |
+
<p>Any HTTPS URL is allowed, including GitHub Gists; there is no host allowlist. The destination must still permit browser access with CORS. GET, HEAD, and POST are enabled. Cookies are omitted. Requests have an 8-second deadline and a 1 MiB response limit.</p>
|
| 8 |
+
<h2>Try in Shell</h2>
|
| 9 |
+
<pre>curl -fsS https://jsonplaceholder.typicode.com/todos/1 -o todo.json
|
| 10 |
+
jq '{id, title, completed}' todo.json</pre>
|
| 11 |
+
<pre>set -o pipefail
|
| 12 |
+
curl -fsS https://api.github.com/repos/huggingface/transformers.js | jq '{full_name, stargazers_count}'</pre>
|
| 13 |
+
<h2>Ask Pi</h2>
|
| 14 |
+
<p>“Fetch https://jsonplaceholder.typicode.com/todos/1, save it as todo.json, and tell me its title and whether it is completed.”</p>
|
| 15 |
+
<p>The destination must allow browser access with CORS. Redirects are reported as errors: use the final URL directly, with the exact capitalization. A plain curl pipeline can hide an error unless you enable <code>set -o pipefail</code>. Python, Node.js, npm, and native binaries remain unavailable.</p>
|
| 16 |
+
<p><a href="./">Back to the workspace</a> · <a href="https://huggingface.co/spaces/victor/MiniCPM5-2B-WebGPU-Pi">Hugging Face Space</a></p>
|
| 17 |
+
</html>
|
|
@@ -10,18 +10,18 @@
|
|
| 10 |
<meta property="og:type" content="website">
|
| 11 |
<meta property="og:title" content="Pi, entirely in your browser.">
|
| 12 |
<meta property="og:description" content="MiniCPM5-2B, WebGPU inference, and a local shell. A coding agent running entirely in your browser.">
|
| 13 |
-
<meta property="og:url" content="https://
|
| 14 |
-
<meta property="og:image" content="https://huggingface.co/spaces/
|
| 15 |
<meta property="og:image:width" content="3136">
|
| 16 |
<meta property="og:image:height" content="1984">
|
| 17 |
<meta property="og:image:alt" content="A coding agent running entirely in your browser, above the Pi terminal and its pixel cat mascot.">
|
| 18 |
<meta name="twitter:card" content="summary_large_image">
|
| 19 |
<meta name="twitter:title" content="Pi, entirely in your browser.">
|
| 20 |
<meta name="twitter:description" content="MiniCPM5-2B, WebGPU inference, and a local shell. A coding agent running entirely in your browser.">
|
| 21 |
-
<meta name="twitter:image" content="https://huggingface.co/spaces/
|
| 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-CLisF1Ml.css">
|
| 26 |
</head>
|
| 27 |
<body>
|
|
@@ -67,7 +67,7 @@
|
|
| 67 |
</form>
|
| 68 |
<div id="shell-hint"><span>Enter to run · Shift+Enter for newline</span><span>↑↓ history</span></div>
|
| 69 |
</details>
|
| 70 |
-
<p class="workspace-note">
|
| 71 |
</aside>
|
| 72 |
</main>
|
| 73 |
<dialog id="model-dialog" aria-labelledby="model-title" aria-describedby="model-description">
|
|
@@ -78,7 +78,7 @@
|
|
| 78 |
<button id="model-cancel" type="button" aria-label="Close model setup" title="Close model setup"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="m6 6 12 12M18 6 6 18"/></svg></button>
|
| 79 |
</div>
|
| 80 |
<div class="setup-body">
|
| 81 |
-
<p id="model-description">
|
| 82 |
<dl class="setup-runtime">
|
| 83 |
<div><dt>Model</dt><dd class="setup-model"><a class="model-org" href="https://huggingface.co/openbmb" target="_blank" rel="noopener noreferrer" aria-label="OpenBMB on Hugging Face (opens in a new tab)" title="OpenBMB on Hugging Face"><img src="./openbmb-avatar.webp" width="18" height="18" alt=""></a><a class="model-source" href="https://huggingface.co/openbmb/MiniCPM5-2B" target="_blank" rel="noopener noreferrer" title="View MiniCPM5-2B on Hugging Face">MiniCPM5-2B</a></dd></div>
|
| 84 |
<div><dt>Inference</dt><dd>On your GPU</dd></div>
|
|
|
|
| 10 |
<meta property="og:type" content="website">
|
| 11 |
<meta property="og:title" content="Pi, entirely in your browser.">
|
| 12 |
<meta property="og:description" content="MiniCPM5-2B, WebGPU inference, and a local shell. A coding agent running entirely in your browser.">
|
| 13 |
+
<meta property="og:url" content="https://victor-minicpm5-2b-webgpu-pi.static.hf.space/">
|
| 14 |
+
<meta property="og:image" content="https://huggingface.co/spaces/victor/MiniCPM5-2B-WebGPU-Pi/resolve/main/social-thumbnail-v3.png">
|
| 15 |
<meta property="og:image:width" content="3136">
|
| 16 |
<meta property="og:image:height" content="1984">
|
| 17 |
<meta property="og:image:alt" content="A coding agent running entirely in your browser, above the Pi terminal and its pixel cat mascot.">
|
| 18 |
<meta name="twitter:card" content="summary_large_image">
|
| 19 |
<meta name="twitter:title" content="Pi, entirely in your browser.">
|
| 20 |
<meta name="twitter:description" content="MiniCPM5-2B, WebGPU inference, and a local shell. A coding agent running entirely in your browser.">
|
| 21 |
+
<meta name="twitter:image" content="https://huggingface.co/spaces/victor/MiniCPM5-2B-WebGPU-Pi/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-BzGjruQ9.js"></script>
|
| 25 |
<link rel="stylesheet" crossorigin href="./assets/index-CLisF1Ml.css">
|
| 26 |
</head>
|
| 27 |
<body>
|
|
|
|
| 67 |
</form>
|
| 68 |
<div id="shell-hint"><span>Enter to run · Shift+Enter for newline</span><span>↑↓ history</span></div>
|
| 69 |
</details>
|
| 70 |
+
<p class="workspace-note">HTTP: curl can request any HTTPS URL. Browser CORS applies. <a href="./http-help.html" target="_blank" rel="noopener">HTTP help ↗</a></p>
|
| 71 |
</aside>
|
| 72 |
</main>
|
| 73 |
<dialog id="model-dialog" aria-labelledby="model-title" aria-describedby="model-description">
|
|
|
|
| 78 |
<button id="model-cancel" type="button" aria-label="Close model setup" title="Close model setup"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="m6 6 12 12M18 6 6 18"/></svg></button>
|
| 79 |
</div>
|
| 80 |
<div class="setup-body">
|
| 81 |
+
<p id="model-description">The model and shell run in your browser. HTTP commands can send requests and data to external services.</p>
|
| 82 |
<dl class="setup-runtime">
|
| 83 |
<div><dt>Model</dt><dd class="setup-model"><a class="model-org" href="https://huggingface.co/openbmb" target="_blank" rel="noopener noreferrer" aria-label="OpenBMB on Hugging Face (opens in a new tab)" title="OpenBMB on Hugging Face"><img src="./openbmb-avatar.webp" width="18" height="18" alt=""></a><a class="model-source" href="https://huggingface.co/openbmb/MiniCPM5-2B" target="_blank" rel="noopener noreferrer" title="View MiniCPM5-2B on Hugging Face">MiniCPM5-2B</a></dd></div>
|
| 84 |
<div><dt>Inference</dt><dd>On your GPU</dd></div>
|