AlexWortega commited on
Commit
97d3fbc
·
1 Parent(s): e539b1e

Serve the app in the huggingface.co embed, not a link to it

Browse files

With the model on this Space's GPU, the embed needs SharedArrayBuffer for
nothing on the main path — the run only died there because the v86 project
runtime is activated before every run and its failure aborted the whole run.
That call is now non-fatal, so a missing runtime costs the terminal, not the
agent.

So the iframe gets the real app, minus the isolating headers (which are what
blank an embedded Space). A bar at the top of the embedded view names what is
unavailable there — the terminal and the in-browser WebGPU model — and links to
the standalone tab, where the headers and both features come back.

__pycache__/app.cpython-314.pyc CHANGED
Binary files a/__pycache__/app.cpython-314.pyc and b/__pycache__/app.cpython-314.pyc differ
 
app.py CHANGED
@@ -1,9 +1,9 @@
1
  """My Pi Agent — the shipped web app, with its model served here on ZeroGPU.
2
 
3
- `web/` is the production build of https://yourlocalagent.ai. The only difference
4
- from the live site is one build-time variable: `VITE_LLM_SERVER` points at this
5
- Space, so the agent's model runs on this Space's GPU instead of in the visitor's
6
- browser on WebGPU. Same UI, same agent loop, same tools, same preview.
7
 
8
  Routes:
9
  / the app
@@ -15,9 +15,9 @@ The model is `AlexWortega/qwen35-4b-soyuz-vibeapps-merged` (Q4_K_M GGUF), the
15
  Soyuz checkpoint fine-tuned for self-contained web apps — the same weights the
16
  site otherwise downloads into the browser.
17
 
18
- Cross-origin isolation: the build ships gzuidhof/coi-serviceworker because
19
- GitHub Pages cannot set response headers. Here we can, so the three headers the
20
- worker would install are sent directly and it finds nothing left to do.
21
  """
22
 
23
  import ctypes
@@ -67,7 +67,7 @@ import spaces
67
  import uvicorn
68
  from fastapi import FastAPI, Request
69
  from fastapi.middleware.cors import CORSMiddleware
70
- from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse, StreamingResponse
71
  from fastapi.staticfiles import StaticFiles
72
  from huggingface_hub import hf_hub_download
73
  from llama_cpp import Llama
@@ -146,53 +146,22 @@ def generate(messages: list[dict], temperature: float, max_tokens: int, top_p: f
146
 
147
  app = FastAPI()
148
 
149
-
150
- STANDALONE_URL = "https://alexwortega-my-pi-agent.hf.space/"
151
-
152
- EMBED_NOTICE = """<!doctype html>
153
- <html lang="en"><head><meta charset="utf-8">
154
- <meta name="viewport" content="width=device-width,initial-scale=1">
155
- <title>Pi Agent</title>
156
- <style>
157
- :root {{ color-scheme: light dark; }}
158
- body {{ margin:0; min-height:100vh; display:grid; place-items:center;
159
- background:#f2f2ee; color:#1a1a16;
160
- font:13px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace; }}
161
- @media (prefers-color-scheme: dark) {{ body {{ background:#12120f; color:#f2f2ee; }} }}
162
- main {{ max-width:34rem; padding:2rem; }}
163
- h1 {{ font-size:15px; margin:0 0 1rem; letter-spacing:.02em; }}
164
- p {{ margin:0 0 1rem; opacity:.85; }}
165
- a.cta {{ display:inline-block; padding:10px 16px; background:#ff4b00; color:#fff;
166
- text-decoration:none; font-weight:700; }}
167
- code {{ background:rgba(128,128,128,.18); padding:1px 4px; }}
168
- </style></head>
169
- <body><main>
170
- <h1>pi agent — open in its own tab</h1>
171
- <p>This app needs <code>SharedArrayBuffer</code>: its in-browser Linux runtime and
172
- the WebGPU model both refuse to start without it. That requires cross-origin
173
- isolation, and a page embedded inside huggingface.co cannot be isolated — only
174
- the Space's own origin can.</p>
175
- <p><a class="cta" href="{url}" target="_blank" rel="noopener">open pi agent →</a></p>
176
- <p>There you can run the model in your browser on WebGPU, or pick
177
- <em>Soyuz 4B — this Space's GPU</em> to run the same weights on this Space's ZeroGPU.</p>
178
- </main></body></html>
179
- """.format(url=STANDALONE_URL)
180
-
181
-
182
  class CrossOriginIsolation(BaseHTTPMiddleware):
183
- """Cross-origin isolation, minus the one case where it breaks the page.
184
-
185
- The app needs SharedArrayBuffer (the v86 browser runtime, wllama's threaded
186
- WASM fallback), which needs COOP+COEP. On GitHub Pages coi-serviceworker.js
187
- installs them client-side because Pages cannot set headers; here we set them
188
- directly.
189
-
190
- But COOP is also what makes a Space "only run in standalone mode, no longer
191
- in the default Space iframed view" (HF's own docs on `custom_headers`) — with
192
- it, the huggingface.co iframe renders nothing at all. Isolation is not
193
- optional for this app, so instead of dropping it, iframe *navigations* get a
194
- short page pointing at the Space's own origin, and only that response goes
195
- out without the isolating headers.
 
 
196
  """
197
 
198
  async def dispatch(self, request: Request, call_next):
@@ -200,15 +169,11 @@ class CrossOriginIsolation(BaseHTTPMiddleware):
200
  request.headers.get("sec-fetch-dest") == "iframe"
201
  and request.url.path in ("/", "/index.html")
202
  )
203
- if embedded:
204
- return HTMLResponse(
205
- EMBED_NOTICE,
206
- headers={"Cross-Origin-Resource-Policy": "cross-origin", "Cache-Control": "no-store"},
207
- )
208
  response = await call_next(request)
209
- response.headers["Cross-Origin-Opener-Policy"] = "same-origin"
210
- response.headers["Cross-Origin-Embedder-Policy"] = "require-corp"
211
  response.headers["Cross-Origin-Resource-Policy"] = "cross-origin"
 
 
 
212
  return response
213
 
214
 
 
1
  """My Pi Agent — the shipped web app, with its model served here on ZeroGPU.
2
 
3
+ `web/` is the production build of https://yourlocalagent.ai, pointed at this
4
+ Space: by default the agent's model runs here on ZeroGPU instead of in the
5
+ visitor's browser on WebGPU (the in-browser model stays in the picker). Same UI,
6
+ same agent loop, same tools, same preview.
7
 
8
  Routes:
9
  / the app
 
15
  Soyuz checkpoint fine-tuned for self-contained web apps — the same weights the
16
  site otherwise downloads into the browser.
17
 
18
+ Cross-origin isolation: sent as headers here (GitHub Pages cannot, which is why
19
+ the site build ships coi-serviceworker; this build drops it). See
20
+ CrossOriginIsolation below for the one response that deliberately goes without.
21
  """
22
 
23
  import ctypes
 
67
  import uvicorn
68
  from fastapi import FastAPI, Request
69
  from fastapi.middleware.cors import CORSMiddleware
70
+ from fastapi.responses import JSONResponse, RedirectResponse, StreamingResponse
71
  from fastapi.staticfiles import StaticFiles
72
  from huggingface_hub import hf_hub_download
73
  from llama_cpp import Llama
 
146
 
147
  app = FastAPI()
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  class CrossOriginIsolation(BaseHTTPMiddleware):
150
+ """Cross-origin isolation everywhere except the huggingface.co iframe.
151
+
152
+ SharedArrayBuffer needs COOP+COEP. On GitHub Pages coi-serviceworker.js
153
+ installs them client-side because Pages cannot set headers; here we send them
154
+ directly and standalone that is what makes the in-browser WebGPU model and
155
+ the v86 project runtime work.
156
+
157
+ But COOP is also what makes a Space "only run in standalone mode, but no
158
+ longer in the default Space iframed view" (HF's docs on `custom_headers`):
159
+ with it, the embed renders nothing at all. Since the default model is served
160
+ by this Space's GPU, the embed does not need SharedArrayBuffer for anything
161
+ on the main path, so the iframe navigation gets the same app *without* the
162
+ isolating headers. What it loses there is the v86 terminal and the WebGPU
163
+ model — the app says so itself in a bar at the top when it finds it is not
164
+ isolated.
165
  """
166
 
167
  async def dispatch(self, request: Request, call_next):
 
169
  request.headers.get("sec-fetch-dest") == "iframe"
170
  and request.url.path in ("/", "/index.html")
171
  )
 
 
 
 
 
172
  response = await call_next(request)
 
 
173
  response.headers["Cross-Origin-Resource-Policy"] = "cross-origin"
174
+ if not embedded:
175
+ response.headers["Cross-Origin-Opener-Policy"] = "same-origin"
176
+ response.headers["Cross-Origin-Embedder-Policy"] = "require-corp"
177
  return response
178
 
179
 
web/assets/index-BuqJnO4-.js CHANGED
@@ -801,9 +801,9 @@ button, input, textarea, select { font: inherit; }
801
  `):""}let EF=0;const va=()=>`ui-${++EF}`;function AF(e,t){const[n,a]=te.useState({}),[r,u]=te.useState(!1),[l,m]=te.useState(0),[f,h]=te.useState(null),[x,b]=te.useState([]),[v,k]=te.useState(!1),T=te.useRef(null),S=te.useRef(null),W=te.useRef(null),N=te.useRef(t),L=te.useRef(null),I=te.useRef(null),K=te.useRef(null),_=te.useRef(new Map),G=te.useCallback((q,X)=>{a(R=>{const M=X(R[q]??Sf);return bF(q,M),{...R,[q]:M}})},[]);te.useEffect(()=>{let q=!1;return Promise.all([wF(t),Y3(t)]).then(([X,R])=>{q||(X&&a(M=>({...M,[t]:X})),b(R))}),()=>{q=!0}},[t]);const H=te.useCallback((q,X)=>{G(q,R=>({...R,messages:[...R.messages,X]}))},[G]),ae=te.useCallback((q,X)=>{G(N.current,R=>({...R,messages:R.messages.map(M=>M.id===q?X(M):M)}))},[G]),We=te.useCallback(()=>{if(L.current)return L.current;const q=va();return L.current=q,H(N.current,{id:q,kind:"assistant",text:"",thinking:"",streaming:!0}),q},[H]),Me=100,Ce=te.useRef(null),ce=te.useRef(null),ue=te.useRef(0),be=te.useCallback(()=>{ce.current&&(clearTimeout(ce.current),ce.current=null);const q=Ce.current;if(Ce.current=null,!q)return;ue.current=Date.now();const X=We(),{text:R,thinking:M}=Zw(q.content);ae(X,ee=>ee.kind==="assistant"?{...ee,text:R,thinking:M,streaming:!0}:ee);const B=Jw(R);B&&h(B.html)},[We,ae]),Be=te.useCallback(q=>{var R,M,B;const X=N.current;switch(q.type){case"message_update":{const ee=q.message;if(ee.role!=="assistant")break;Ce.current=ee;const ne=Date.now()-ue.current;ne>=Me?be():ce.current||(ce.current=setTimeout(be,Me-ne));break}case"message_end":{be();const ee=q.message;if(ee.role==="assistant"){const ne=We(),{text:we,thinking:Te}=Zw(ee.content),Ke=ee.stopReason==="error"||ee.stopReason==="aborted"?ee.errorMessage:void 0;ae(ne,ye=>ye.kind==="assistant"?{...ye,text:we,thinking:Te,streaming:!1,error:Ke}:ye);const _t=Jw(we);_t&&(I.current=_t.html,K.current=ne),L.current=null}break}case"tool_execution_start":{const ee=q;console.debug(`[pi] 🔧 ${ee.toolName}(`,ee.args,")"),ee.toolName==="bash"&&_.current.set(ee.toolCallId,((R=ee.args)==null?void 0:R.command)??""),H(X,{id:va(),kind:"tool",toolCallId:ee.toolCallId,name:ee.toolName,args:ee.args,status:"running",resultText:""}),L.current=null;break}case"tool_execution_end":{const ee=q,ne=(B=(M=ee.result)==null?void 0:M.details)==null?void 0:B.diff,we=SF(ee.result);if(console.debug(`[pi] ${ee.isError?"❌":"✅"} ${ee.toolName} →`,ee.isError?we:ne||we||"(ok)"),G(X,Te=>({...Te,messages:Te.messages.map(Ke=>Ke.kind==="tool"&&Ke.toolCallId===ee.toolCallId?{...Ke,status:ee.isError?"error":"ok",resultText:we,diff:ne}:Ke)})),m(Te=>Te+1),ee.toolName==="bash"&&we){const Te=_.current.get(ee.toolCallId)??"";_.current.delete(ee.toolCallId),G(X,Ke=>({...Ke,terminal:[...Ke.terminal,`$ ${Te}`,we,""]}))}break}}},[We,ae,H,G,be]),j=te.useCallback(async q=>{var _t;const X=q.trim();if(!X)return;if(r){const ye=T.current;if(!ye||N.current!==t)return;H(t,{id:va(),kind:"user",text:X}),ye.steer(`The user sent a message while you were working:
802
  <user_query>
803
  ${X.slice(0,25e3)}
804
- </user_query>`).catch(()=>{});return}const R=t;xh(R)&&/^(approve|approved|implement|go ahead|да|одобряю|утверждаю|делай)\b/i.test(X)&&(B3(R,!1),k(!1)),H(R,{id:va(),kind:"user",text:X}),u(!0),L.current=null,I.current=null,K.current=null,h(null);const M=gs(R);try{const ye=It();!await Ip(ye).catch(()=>null)&&await gF(ye,M)&&m(en=>en+1),await Ta.activate(R);const bn=await yF(R,ye,M,X);b(en=>[bn,...en].slice(0,20))}catch(ye){const St=ye instanceof Error?ye.message:String(ye);H(R,{id:va(),kind:"assistant",text:"",thinking:"",streaming:!1,error:`Browser runtime failed: ${St}`}),u(!1);return}const B=await e();if("error"in B){H(R,{id:va(),kind:"assistant",text:"",thinking:"",streaming:!1,error:B.error}),u(!1);return}const ee=B.model,{tools:ne,systemPromptAdditions:we}=await fF(),Te=await Ip(It()).catch(()=>null);let Ke;try{Ke=await uF({projectId:R,model:ee,extraTools:ne.length?ne:void 0,systemPromptAdditions:we,repo:Te?`${Te.owner}/${Te.repo}@${Te.branch}`:void 0,containerExec:ye=>Ta.exec(ye)})}catch(ye){const St=ye instanceof Error?ye.message:String(ye);H(R,{id:va(),kind:"assistant",text:"",thinking:"",streaming:!1,error:St}),u(!1);return}(W.current!==R||T.current!==Ke)&&((_t=S.current)==null||_t.call(S),S.current=Ke.subscribe(Be),T.current=Ke,W.current=R),N.current=R;try{if(Yw(R,ee.contextWindow,ee.id))try{await Ke.compact()}catch(bn){console.warn("[pi] pre-run compaction failed",bn)}await aF(Ke,R,X),k(xh(R));const ye=`${M}/index.html`,St=await It().exists(ye),Qt=G3(ee.id);if((!St||Qt)&&I.current&&!Te)try{await It().writeText(ye,I.current),m(en=>en+1);const bn=K.current;if(bn){const en="✅ Saved to `index.html` — see the live preview →";ae(bn,oi=>{if(oi.kind!=="assistant")return oi;const br=RF(oi.text);return{...oi,text:br?`${br}
805
 
806
- ${en}`:en}})}}catch{}if(Yw(R,ee.contextWindow,ee.id)){const bn=va();H(R,{id:bn,kind:"assistant",thinking:"",streaming:!0,text:"🗜️ Context is nearly full — compacting older turns…"});try{await Ke.compact(),ae(bn,en=>en.kind==="assistant"?{...en,streaming:!1,text:"🗜️ Context compacted — older turns were summarized to fit the model's window."}:en)}catch(en){console.warn("[pi] compaction failed",en),ae(bn,oi=>oi.kind==="assistant"?{...oi,streaming:!1,text:"⚠️ Context compaction failed — the next message may hit the context limit."}:oi)}}}catch(ye){console.error("[pi] agent run failed",ye);const St=ye instanceof Error?ye.message:String(ye);H(R,{id:va(),kind:"assistant",text:"",thinking:"",streaming:!1,error:St})}finally{u(!1),L.current=null,h(null)}},[e,r,t,Be,ae,H]),V=te.useCallback(()=>{var q;(q=T.current)==null||q.abort().catch(()=>{})},[]),re=te.useCallback(async()=>{const q=gs(t);try{await It().remove(q)}catch{}m(X=>X+1)},[t]),me=te.useCallback(()=>{var q;W.current===t&&((q=S.current)==null||q.call(S),S.current=null,T.current=null,W.current=null),Xw(t),L.current=null,a(X=>({...X,[t]:Sf})),h(null)},[t]),oe=te.useCallback(async q=>{var X;W.current===q&&((X=S.current)==null||X.call(S),S.current=null,T.current=null,W.current=null),Xw(q),a(R=>{const{[q]:M,...B}=R;return B});try{await It().remove(gs(q))}catch{}await TF(q).catch(()=>{}),m(R=>R+1)},[]),A=n[t]??Sf,F=te.useCallback(async()=>{const q=x[0];!q||r||(await kF(q,It(),gs(t)),m(X=>X+1),b(X=>X.slice(1)),await Ta.activate(t))},[x,t,r]);return{messages:A.messages,running:r,fsVersion:l,liveHtml:N.current===t?f:null,terminalLines:A.terminal,send:j,stop:V,reset:me,discardProject:oe,clearWorkspace:re,checkpoints:x,undo:F,planPending:v}}const ey=e=>e>0?`${(e/1024/1024).toFixed(0)} mb`:"";function Ef(e){return e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function WF({model:e,eng:t}){var v;const{tps:n,contextUsed:a,contextWindow:r,generating:u,liveTokens:l,phase:m}=Jg(),f=t.modelId===e.id,h=f&&t.phase==="loading",x=f&&t.phase==="error",b=[];return h?b.push(`dl ${Math.round(t.progress*100)}%${t.total?` ${ey(t.loaded)}/${ey(t.total)}`:""}`):u?(b.push(`${m??"working"}…`),b.push(`${Ef(l)} tok${n?` · ${n} tok/s`:""}`)):(n&&b.push(`${n} tok/s`),r&&b.push(`ctx ${a?Ef(a):"0"}/${Ef(r)}`)),y.jsxs("span",{className:"hidden md:flex items-center gap-2 whitespace-nowrap text-[12px]",children:[y.jsx("span",{className:`w-2.5 h-2.5 shrink-0 ${u||h?"animate-pulse":""}`,style:{background:"var(--color-pi)"},title:x?`error: ${t.error}`:t.phase}),y.jsxs("span",{className:u?"text-[var(--color-pi-2)]":"text-[var(--color-ink-dim)]",children:[e.label.toLowerCase(),b.length>0?` · ${b.join(" · ")}`:""]}),x&&y.jsxs("span",{className:"text-[var(--color-pi-2)] max-w-[26rem] truncate",title:t.error,children:["err: ",(v=t.error)==null?void 0:v.slice(0,80)]})]})}function UF({projects:e,activeId:t,onSelect:n,onCreate:a,onDelete:r,onRename:u}){const[l,m]=te.useState(!1),[f,h]=te.useState(null),x=te.useRef(null),b=e.find(v=>v.id===t);return te.useEffect(()=>{if(!l)return;const v=k=>{x.current&&!x.current.contains(k.target)&&m(!1)};return window.addEventListener("mousedown",v),()=>window.removeEventListener("mousedown",v)},[l]),y.jsxs("div",{ref:x,className:"relative flex items-center",children:[y.jsxs("button",{className:"btn-bracket text-[12.5px]",onClick:()=>m(v=>!v),title:"Switch project",children:["project: ",y.jsx("span",{className:"text-[var(--color-ink)] font-semibold",children:(b==null?void 0:b.name)??"—"})," ▾"]}),l&&y.jsxs("div",{className:"absolute left-0 top-full mt-px w-72 z-40 bg-[var(--color-panel)] border border-[var(--color-ink)] text-[12px]",children:[e.map(v=>y.jsxs("div",{className:`group flex items-center gap-2 px-3 py-1.5 cursor-pointer ${v.id===t?"bg-[var(--color-panel-2)]":"hover:bg-[var(--color-panel-2)]"}`,onClick:()=>{n(v.id),m(!1)},children:[f===v.id?y.jsx("input",{autoFocus:!0,defaultValue:v.name,className:"field py-0.5 text-[12px]",onClick:k=>k.stopPropagation(),onBlur:k=>{u(v.id,k.target.value.trim()||v.name),h(null)},onKeyDown:k=>{k.key==="Enter"&&k.target.blur(),k.key==="Escape"&&h(null)}}):y.jsxs("span",{className:"flex-1 min-w-0 truncate",onDoubleClick:k=>{k.stopPropagation(),h(v.id)},children:[v.id===t?"▸ ":" ",v.name,y.jsxs("span",{className:"text-[var(--color-ink-faint)]",children:[" · ",v.messages.length," msg"]})]}),y.jsx("button",{className:"opacity-0 group-hover:opacity-100 text-[var(--color-ink-faint)] hover:text-[var(--color-pi-2)]",onClick:k=>{k.stopPropagation(),r(v.id)},title:"Delete project",children:"×"})]},v.id)),y.jsx("button",{className:"w-full text-left px-3 py-1.5 border-t border-[var(--color-edge)] text-[var(--color-ink-dim)] hover:text-[var(--color-ink)] hover:bg-[var(--color-panel-2)]",onClick:()=>{a(),m(!1)},children:"+ new project"})]})]})}function CF({model:e,eng:t,projects:n,activeId:a,onSelectProject:r,onCreateProject:u,onDeleteProject:l,onRenameProject:m,onOpenPicker:f,onOpenSkills:h,onOpenPlugins:x,onReload:b}){const k=t.modelId===e.id&&t.phase==="loading";return y.jsxs("header",{className:"h-11 shrink-0 flex items-center gap-6 px-4 border-b-2 border-[var(--color-ink)] bg-[var(--color-void)] text-[12.5px]",children:[y.jsx("span",{className:"font-bold tracking-tight",children:"pi agent"}),y.jsx(UF,{projects:n,activeId:a,onSelect:r,onCreate:u,onDelete:l,onRename:m}),y.jsxs("div",{className:"ml-auto flex items-center gap-4 min-w-0",children:[y.jsx(WF,{model:e,eng:t}),y.jsx("button",{className:"btn-bracket",onClick:f,title:"Choose model & params",children:"[model]"}),y.jsx("button",{className:"btn-bracket",onClick:h,title:"Manage skills",children:"[skills]"}),y.jsx("button",{className:"btn-bracket",onClick:x,title:"Manage plugins",children:"[plugins]"}),y.jsxs("button",{className:"btn-bracket hidden lg:inline",onClick:b,disabled:k,title:"Reload / download model now",children:["[",k?"loading…":"reload","]"]})]})]})}const _F=["Website","Dashboard","App","Game"],ty=[{id:"saas-landing",name:"SaaS landing",tagline:"Hero, features, pricing",emoji:"🚀",category:"Website",prompt:"Build a modern SaaS landing page in index.html: a sticky top nav with logo and CTA, a hero with a bold headline, subtitle and two buttons, a 3-card feature grid, a 3-tier pricing table, one testimonial, and a footer. Dark theme, generous spacing, fully responsive, no external images (use CSS shapes/emoji)."},{id:"portfolio",name:"Portfolio",tagline:"Personal showcase site",emoji:"🎨",category:"Website",prompt:"Build a personal portfolio in index.html: a hero with name, role and a short intro, an about section, a projects grid with cards (title, description, tag), a skills list, and contact links. Minimal, elegant, responsive, dark theme."},{id:"blog",name:"Blog",tagline:"Article list + reader",emoji:"📰",category:"Website",prompt:"Build a minimal blog in index.html: a header, a list of article cards (title, date, excerpt), and a reading view that opens when you click an article (show full mock text, back button). Keep 4-5 sample posts in memory. Clean typography, responsive, dark theme."},{id:"analytics",name:"Analytics dashboard",tagline:"KPIs + charts",emoji:"📊",category:"Dashboard",prompt:"Build an analytics dashboard in index.html: a left sidebar nav, a row of 4 KPI cards with a trend indicator, a line chart and a bar chart using Chart.js from the approved CDN, and a recent-activity table with mock data. Dark theme, responsive."},{id:"expenses",name:"Expense tracker",tagline:"Budget + category chart",emoji:"💰",category:"Dashboard",prompt:"Build an expense tracker in index.html: a form to add an income or expense with amount and category, a running balance, a transaction list with delete, and a category breakdown pie chart using Chart.js from the approved CDN. Keep all state in memory. Dark theme, responsive."},{id:"kanban",name:"Kanban board",tagline:"Drag-and-drop tasks",emoji:"🗂️",category:"App",prompt:"Build a Kanban board in index.html: three columns (Todo, Doing, Done), add a card to any column, drag cards between columns, and delete cards. Keep state in memory (no localStorage). Clean, responsive, dark theme."},{id:"chat",name:"Chat UI",tagline:"Telegram-style messenger",emoji:"💬",category:"App",prompt:"Build a chat app UI in index.html: a contact list on the left, a message thread on the right with incoming/outgoing bubbles, and an input bar. Load one sample conversation; sending a message appends it and auto-replies with an echo after a short delay. Telegram-style dark-blue theme, responsive."},{id:"notes",name:"Markdown notes",tagline:"Editor + live preview",emoji:"📝",category:"App",prompt:"Build a markdown notes app in index.html: a note list on the left, a split editor with a live rendered preview on the right (write a small markdown-to-HTML parser for headings, bold, italic, lists and code), plus new/delete and search. Keep notes in memory. Dark theme, responsive."},{id:"pomodoro",name:"Pomodoro timer",tagline:"Focus cycles + ring",emoji:"🍅",category:"App",prompt:"Build a Pomodoro timer in index.html: 25-minute work and 5-minute break cycles, start/pause/reset, a circular SVG progress ring that fills as time passes, and a completed-sessions counter. Minimal, centered, dark theme."},{id:"weather",name:"Weather card",tagline:"Current + 5-day forecast",emoji:"🌦️",category:"App",prompt:"Build a weather dashboard UI in index.html: a search bar, a current-conditions card (temp, condition, humidity, wind) and a 5-day forecast row, using CSS-drawn or emoji weather icons and mock data that changes per searched city. Clean, responsive, dark theme."},{id:"breakout",name:"Breakout game",tagline:"Canvas arcade classic",emoji:"🎮",category:"Game",prompt:"Build a playable Breakout game in index.html on a canvas: a paddle controlled by mouse and arrow keys, ball physics with wall/paddle bounce, a grid of colored bricks, score and lives, and win/lose states with restart. Plain JavaScript, no libraries."},{id:"snake",name:"Snake game",tagline:"Grid classic with score",emoji:"🐍",category:"Game",prompt:"Build a playable Snake game in index.html on a canvas: arrow-key movement, growing snake, random food, wall/self collision game-over with restart, and a live score. Keep it minimal and smooth. Plain JavaScript, no libraries."}];function MF(){const{phase:e}=Jg(),[t,n]=te.useState(0);return te.useEffect(()=>{const a=Date.now(),r=setInterval(()=>n(Math.round((Date.now()-a)/1e3)),500);return()=>clearInterval(r)},[]),y.jsxs("div",{className:"text-[13px] text-[var(--color-ink-faint)] blink",children:[e??"working",t>=2?` ${t}s`:""]})}function OF({messages:e,eng:t}){const{phase:n,liveTokens:a,tps:r}=Jg(),[u,l]=te.useState(0);te.useEffect(()=>{const x=Date.now(),b=setInterval(()=>l(Math.floor((Date.now()-x)/1e3)),250);return()=>clearInterval(b)},[]);const m=[...e].reverse().find(x=>x.kind==="tool"&&x.status==="running"),f=(m==null?void 0:m.kind)==="tool"?J3(m.args):"",h=t.phase==="loading"?t.total?`loading model ${Math.round(t.progress*100)}%`:"starting model":(m==null?void 0:m.kind)==="tool"?`${m.name}${f?` ${f}`:""}`:n||(a>0?"generating":"preparing context");return y.jsxs("div",{role:"status","aria-live":"polite",className:"ml-[3.25rem] border border-[var(--color-edge-2)] bg-[var(--color-panel)] px-3 py-2.5",children:[y.jsxs("div",{className:"flex items-center gap-2 text-[12.5px]",children:[y.jsxs("span",{className:"relative flex h-2.5 w-2.5 shrink-0",children:[y.jsx("span",{className:"absolute inline-flex h-full w-full animate-ping bg-[var(--color-pi)] opacity-40"}),y.jsx("span",{className:"relative inline-flex h-2.5 w-2.5 bg-[var(--color-pi)]"})]}),y.jsx("span",{className:"font-semibold text-[var(--color-ink)]",children:"agent is working"}),y.jsxs("span",{className:"text-[var(--color-ink-dim)] truncate",children:["· ",h]}),y.jsxs("span",{className:"ml-auto tabular-nums text-[var(--color-ink-faint)]",children:[u,"s"]})]}),y.jsx("div",{className:"mt-2 h-px overflow-hidden bg-[var(--color-edge)]",children:y.jsx("div",{className:"h-full w-1/3 animate-[activity-slide_1.2s_ease-in-out_infinite] bg-[var(--color-pi)]"})}),y.jsx("div",{className:"mt-1.5 text-[10.5px] text-[var(--color-ink-faint)]",children:a>0?`${a} tokens${r?` · ${r} tok/s`:""}`:"waiting for the next model event"})]})}const IF=[{label:"neon pomodoro",prompt:"Build a neon synthwave pomodoro timer in index.html with start/pause/reset and a glowing circular progress ring."},{label:"cursor constellation",prompt:"Make a particle constellation on a full-page canvas that links dots near the cursor. Plain JavaScript."},{label:"tip calculator",prompt:"Build a clean tip calculator in index.html: bill amount, tip percentage, split between N people, live per-person and total."},{label:"color palette",prompt:"Build a color palette generator in index.html: generate 5 harmonious colors, click a swatch to copy its hex, lock colors, regenerate."},{label:"typing test",prompt:"Build a typing speed test in index.html: a sample paragraph, live WPM and accuracy as you type, and a restart button. Plain JavaScript."}];function LF({messages:e,running:t,eng:n,onSend:a,onStop:r,onUndo:u,canUndo:l,planPending:m}){const[f,h]=te.useState(""),x=te.useRef(null),b=te.useRef(null);te.useEffect(()=>{if(e.length===0)return;const T=x.current;T&&(T.scrollTop=T.scrollHeight)},[e,t]);const v=()=>{f.trim()&&(a(f),h(""),b.current&&(b.current.style.height="auto"))},k=T=>{T.style.height="auto",T.style.height=Math.min(T.scrollHeight,200)+"px"};return y.jsxs("section",{className:"flex-1 min-w-0 flex flex-col",children:[y.jsx("div",{ref:x,className:"flex-1 min-h-0 overflow-y-auto px-6 py-6",children:e.length===0?y.jsx(jF,{onPick:a}):y.jsxs("div",{className:"space-y-4 max-w-3xl",children:[e.map(T=>y.jsx(NF,{m:T},T.id)),t&&y.jsx(OF,{messages:e,eng:n})]})}),e.length>0&&y.jsxs("div",{className:"px-6 pb-4 pt-2",children:[y.jsxs("div",{className:"max-w-3xl flex items-end gap-3 border-b border-[var(--color-ink)] focus-within:border-b-2 pb-1",children:[y.jsx("textarea",{ref:b,value:f,rows:1,placeholder:t?"steer the agent — it reads this mid-run…":"describe what to build",className:"flex-1 bg-transparent resize-none outline-none text-[13.5px] py-1 max-h-[200px] placeholder:text-[var(--color-ink-faint)]",onChange:T=>{h(T.target.value),k(T.target)},onKeyDown:T=>{T.key==="Enter"&&!T.shiftKey&&(T.preventDefault(),v())}}),y.jsx("button",{className:"btn-bracket accent text-[13.5px]",onClick:v,disabled:!f.trim(),title:t?"Steer the agent":"Send",children:"[send]"}),t&&y.jsx("button",{className:"btn-bracket text-[13.5px]",onClick:r,title:"Stop the run",children:"[stop]"}),!t&&y.jsx("button",{className:"btn-bracket text-[13.5px]",onClick:u,disabled:!l,title:"Restore the project to before the last request",children:"[undo]"}),!t&&m&&y.jsx("button",{className:"btn-bracket accent text-[13.5px]",onClick:()=>a("Approve the plan and implement it."),title:"Approve the proposed plan",children:"[approve plan]"})]}),y.jsx("div",{className:"text-[10.5px] text-[var(--color-ink-faint)] mt-1.5",children:"real pi agent loop · local webgpu or cloud with your key · ⏎ send · ⇧⏎ newline"})]})]})}const NF=te.memo(function({m:t}){return t.kind==="user"?y.jsx(X3,{prefix:"you:",children:y.jsx("div",{className:"whitespace-pre-wrap text-[13.5px] font-medium",children:t.text})}):t.kind==="tool"?y.jsx(zF,{m:t}):y.jsx(DF,{m:t})});function X3({prefix:e,children:t}){return y.jsxs("div",{className:"flex gap-3",children:[y.jsx("span",{className:"w-10 shrink-0 font-bold text-[13.5px] select-none",children:e}),y.jsx("div",{className:"min-w-0 flex-1",children:t})]})}function DF({m:e}){const[t,n]=te.useState(null),a=t??e.streaming;return y.jsxs(X3,{prefix:"pi:",children:[e.thinking&&y.jsxs("button",{onClick:()=>n(!a),className:"mb-1.5 text-[11px] text-[var(--color-ink-faint)] hover:text-[var(--color-ink-dim)] inline-flex items-center gap-1",children:[y.jsx("span",{className:`transition ${a?"rotate-90":""}`,children:"▸"}),a?"hide":"show"," reasoning",e.streaming&&e.thinking&&!e.text?" · thinking…":""]}),e.thinking&&a&&y.jsx("div",{className:"mb-2 text-[12px] text-[var(--color-ink-faint)] border-l border-[var(--color-edge-2)] pl-3 whitespace-pre-wrap",children:e.thinking}),e.text&&y.jsx("div",{className:e.streaming?"blink":void 0,children:y.jsx("div",{className:"msg-md text-[13.5px] text-[var(--color-ink)] break-words inline-block max-w-full align-top",dangerouslySetInnerHTML:{__html:pF(e.text)}})}),e.streaming&&!e.text&&!e.thinking&&y.jsx(MF,{}),e.error&&y.jsxs("div",{className:"mt-1 text-[13px] text-[var(--color-pi-2)] whitespace-pre-wrap",children:["err: ",e.error]})]})}function J3(e){const t=e??{};return typeof t.path=="string"?t.path.split("/").pop()??t.path:typeof t.command=="string"?t.command:""}function $F(e){try{const t=JSON.stringify(e??{},null,2);return t.length>2e3?t.slice(0,2e3)+`
807
  … (truncated)`:t}catch{return String(e)}}function zF({m:e}){const[t,n]=te.useState(null),a=t??e.status==="error",r=e.status==="running"?y.jsxs("span",{className:"inline-flex items-center gap-1.5 text-[var(--color-pi-2)]",children:[y.jsx("span",{className:"inline-block h-2 w-2 animate-pulse bg-[var(--color-pi)]"}),"running"]}):e.status==="ok"?y.jsx("span",{className:"text-[var(--color-ink-dim)]",children:"ok"}):y.jsx("span",{className:"text-[var(--color-pi-2)] font-bold",children:"err"}),u=J3(e.args),l=e.status==="error"?e.resultText:e.diff||e.resultText;return y.jsxs("div",{className:"pl-[3.25rem]",children:[y.jsxs("button",{onClick:()=>n(!a),className:"w-full flex items-baseline text-left text-[13px] font-mono hover:bg-[var(--color-panel-2)] transition-colors",title:"Show tool arguments & result",children:[y.jsxs("span",{className:"shrink-0 text-[var(--color-ink)]",children:[e.name,u&&y.jsxs("span",{className:"text-[var(--color-ink-dim)]",children:[" ",u]})]}),y.jsx("span",{className:"leader"}),y.jsx("span",{className:"shrink-0",children:r})]}),a&&y.jsxs("div",{className:"mt-1 mb-2 border border-[var(--color-edge)] bg-[var(--color-panel)] px-3 py-2 space-y-2",children:[y.jsxs("div",{children:[y.jsx("div",{className:"text-[9.5px] uppercase tracking-wider text-[var(--color-ink-faint)] mb-1",children:"arguments"}),y.jsx("pre",{className:"text-[11px] leading-relaxed font-mono text-[var(--color-ink-dim)] overflow-x-auto max-h-48 overflow-y-auto whitespace-pre-wrap",children:$F(e.args)})]}),(l||e.status==="running")&&y.jsxs("div",{children:[y.jsx("div",{className:"text-[9.5px] uppercase tracking-wider text-[var(--color-ink-faint)] mb-1",children:e.status==="error"?"error":e.diff?"diff":"result"}),y.jsx("pre",{className:`text-[11px] leading-relaxed font-mono overflow-x-auto max-h-72 overflow-y-auto whitespace-pre-wrap ${e.status==="error"?"text-[var(--color-pi-2)]":"text-[var(--color-ink-dim)]"}`,children:l||"…"})]})]})]})}function jF({onPick:e}){const[t,n]=te.useState(""),[a,r]=te.useState("All"),u=te.useRef(null),[l,m]=te.useState(()=>{try{return sessionStorage.getItem(er)??""}catch{return""}}),f=b=>{m(b);try{const v=b.trim();v?sessionStorage.setItem(er,v):sessionStorage.removeItem(er)}catch{}},h=()=>{const b=t.trim();b&&e(b)},x=a==="All"?ty:ty.filter(b=>b.category===a);return y.jsxs("div",{className:"max-w-2xl pt-10 pb-8",children:[y.jsxs("div",{className:"flex items-center gap-2.5",children:[y.jsx("span",{className:"w-3 h-3 bg-[var(--color-pi)]"}),y.jsx("h1",{className:"text-[20px] font-bold tracking-tight",children:"what do you want to build?"})]}),y.jsx("p",{className:"mt-2 text-[12.5px] text-[var(--color-ink-dim)]",children:"describe an app or pick a template — the real pi agent writes the files and previews them here."}),y.jsxs("div",{className:"mt-6 flex items-end gap-3 border-b-2 border-[var(--color-ink)] pb-1",children:[y.jsx("textarea",{ref:u,value:t,rows:1,placeholder:"describe what to build","data-testid":"hero-input",className:"flex-1 bg-transparent resize-none outline-none text-[14px] py-1 max-h-[200px] placeholder:text-[var(--color-ink-faint)]",onChange:b=>{n(b.target.value);const v=b.target;v.style.height="auto",v.style.height=Math.min(v.scrollHeight,200)+"px"},onKeyDown:b=>{b.key==="Enter"&&!b.shiftKey&&(b.preventDefault(),h())}}),y.jsx("button",{className:"btn-bracket accent text-[14px]",onClick:h,disabled:!t.trim(),"data-testid":"hero-send",children:"[send]"})]}),y.jsx("div",{className:"mt-3 flex flex-wrap gap-x-5 gap-y-1 text-[12px]",children:IF.map(b=>y.jsxs("button",{onClick:()=>e(b.prompt),className:"text-[var(--color-ink-faint)] hover:text-[var(--color-pi-2)] transition-colors",children:["· ",b.label]},b.label))}),!l.trim()&&y.jsxs("div",{className:"mt-5 flex items-center gap-3 flex-wrap",children:[y.jsx("input",{className:"field text-[12px] max-w-[300px]",type:"password",placeholder:"openrouter key — sk-or-v1-… (claude / gpt / qwen)","data-testid":"welcome-key",onChange:b=>f(b.target.value)}),y.jsx("span",{className:"text-[10.5px] text-[var(--color-ink-faint)]",children:"or pick local soyuz (no key)"})]}),y.jsxs("div",{className:"mt-10",children:[y.jsxs("div",{className:"flex items-baseline gap-4 mb-3 flex-wrap text-[12px]",children:[y.jsx("h2",{className:"font-bold text-[var(--color-ink)]",children:"templates"}),["All",..._F].map(b=>y.jsxs("button",{onClick:()=>r(b),className:`transition-colors ${a===b?"text-[var(--color-pi-2)]":"text-[var(--color-ink-faint)] hover:text-[var(--color-ink)]"}`,children:["[",b.toLowerCase(),"]"]},b))]}),y.jsx("div",{className:"grid sm:grid-cols-2 md:grid-cols-3 gap-2",children:x.map(b=>y.jsxs("button",{onClick:()=>e(b.prompt),"data-testid":`template-${b.id}`,className:"text-left border border-[var(--color-edge)] hover:border-[var(--color-ink)] bg-[var(--color-panel)] px-3 py-2.5 transition-colors group",children:[y.jsxs("div",{className:"flex items-baseline gap-2",children:[y.jsx("span",{className:"text-[14px] leading-none",children:b.emoji}),y.jsx("span",{className:"text-[12.5px] font-semibold group-hover:text-[var(--color-pi-2)] transition-colors",children:b.name.toLowerCase()})]}),y.jsx("div",{className:"text-[11px] text-[var(--color-ink-faint)] mt-1",children:b.tagline.toLowerCase()})]},b.id))})]})]})}function ny(e){return/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(e)}function iy(e,t){const n=e.split(/[?#]/)[0],a=n.startsWith("/")?n:`${t}/${n}`,r=[];for(const u of a.split("/"))if(!(u===""||u===".")){if(u===".."){r.pop();continue}r.push(u)}return"/"+r.join("/")}function ay(e,t){return e.replace(new RegExp(`</${t}`,"gi"),`<\\/${t}`)}async function BF(e,t,n){var l;let a=e;const r=[...a.matchAll(/<link\b[^>]*>/gi)];for(const m of r){const f=m[0];if(!/rel=["']?stylesheet["']?/i.test(f))continue;const h=(l=f.match(/href=["']([^"']+)["']/i))==null?void 0:l[1];if(!h||ny(h))continue;const x=await n(iy(h,t));x!==null&&(a=a.replace(f,`<style>
808
  ${ay(x,"style")}
809
  </style>`))}const u=[...a.matchAll(/<script\b[^>]*\bsrc=["']([^"']+)["'][^>]*>\s*<\/script>/gi)];for(const m of u){const[f,h]=m;if(ny(h))continue;const x=await n(iy(h,t));if(x===null)continue;const b=/type=["']?module["']?/i.test(f);a=a.replace(f,`<script${b?' type="module"':""}>
 
801
  `):""}let EF=0;const va=()=>`ui-${++EF}`;function AF(e,t){const[n,a]=te.useState({}),[r,u]=te.useState(!1),[l,m]=te.useState(0),[f,h]=te.useState(null),[x,b]=te.useState([]),[v,k]=te.useState(!1),T=te.useRef(null),S=te.useRef(null),W=te.useRef(null),N=te.useRef(t),L=te.useRef(null),I=te.useRef(null),K=te.useRef(null),_=te.useRef(new Map),G=te.useCallback((q,X)=>{a(R=>{const M=X(R[q]??Sf);return bF(q,M),{...R,[q]:M}})},[]);te.useEffect(()=>{let q=!1;return Promise.all([wF(t),Y3(t)]).then(([X,R])=>{q||(X&&a(M=>({...M,[t]:X})),b(R))}),()=>{q=!0}},[t]);const H=te.useCallback((q,X)=>{G(q,R=>({...R,messages:[...R.messages,X]}))},[G]),ae=te.useCallback((q,X)=>{G(N.current,R=>({...R,messages:R.messages.map(M=>M.id===q?X(M):M)}))},[G]),We=te.useCallback(()=>{if(L.current)return L.current;const q=va();return L.current=q,H(N.current,{id:q,kind:"assistant",text:"",thinking:"",streaming:!0}),q},[H]),Me=100,Ce=te.useRef(null),ce=te.useRef(null),ue=te.useRef(0),be=te.useCallback(()=>{ce.current&&(clearTimeout(ce.current),ce.current=null);const q=Ce.current;if(Ce.current=null,!q)return;ue.current=Date.now();const X=We(),{text:R,thinking:M}=Zw(q.content);ae(X,ee=>ee.kind==="assistant"?{...ee,text:R,thinking:M,streaming:!0}:ee);const B=Jw(R);B&&h(B.html)},[We,ae]),Be=te.useCallback(q=>{var R,M,B;const X=N.current;switch(q.type){case"message_update":{const ee=q.message;if(ee.role!=="assistant")break;Ce.current=ee;const ne=Date.now()-ue.current;ne>=Me?be():ce.current||(ce.current=setTimeout(be,Me-ne));break}case"message_end":{be();const ee=q.message;if(ee.role==="assistant"){const ne=We(),{text:we,thinking:Te}=Zw(ee.content),Ke=ee.stopReason==="error"||ee.stopReason==="aborted"?ee.errorMessage:void 0;ae(ne,ye=>ye.kind==="assistant"?{...ye,text:we,thinking:Te,streaming:!1,error:Ke}:ye);const _t=Jw(we);_t&&(I.current=_t.html,K.current=ne),L.current=null}break}case"tool_execution_start":{const ee=q;console.debug(`[pi] 🔧 ${ee.toolName}(`,ee.args,")"),ee.toolName==="bash"&&_.current.set(ee.toolCallId,((R=ee.args)==null?void 0:R.command)??""),H(X,{id:va(),kind:"tool",toolCallId:ee.toolCallId,name:ee.toolName,args:ee.args,status:"running",resultText:""}),L.current=null;break}case"tool_execution_end":{const ee=q,ne=(B=(M=ee.result)==null?void 0:M.details)==null?void 0:B.diff,we=SF(ee.result);if(console.debug(`[pi] ${ee.isError?"❌":"✅"} ${ee.toolName} →`,ee.isError?we:ne||we||"(ok)"),G(X,Te=>({...Te,messages:Te.messages.map(Ke=>Ke.kind==="tool"&&Ke.toolCallId===ee.toolCallId?{...Ke,status:ee.isError?"error":"ok",resultText:we,diff:ne}:Ke)})),m(Te=>Te+1),ee.toolName==="bash"&&we){const Te=_.current.get(ee.toolCallId)??"";_.current.delete(ee.toolCallId),G(X,Ke=>({...Ke,terminal:[...Ke.terminal,`$ ${Te}`,we,""]}))}break}}},[We,ae,H,G,be]),j=te.useCallback(async q=>{var _t;const X=q.trim();if(!X)return;if(r){const ye=T.current;if(!ye||N.current!==t)return;H(t,{id:va(),kind:"user",text:X}),ye.steer(`The user sent a message while you were working:
802
  <user_query>
803
  ${X.slice(0,25e3)}
804
+ </user_query>`).catch(()=>{});return}const R=t;xh(R)&&/^(approve|approved|implement|go ahead|да|одобряю|утверждаю|делай)\b/i.test(X)&&(B3(R,!1),k(!1)),H(R,{id:va(),kind:"user",text:X}),u(!0),L.current=null,I.current=null,K.current=null,h(null);const M=gs(R);try{const ye=It();!await Ip(ye).catch(()=>null)&&await gF(ye,M)&&m(en=>en+1),await Ta.activate(R).catch(()=>{});const bn=await yF(R,ye,M,X);b(en=>[bn,...en].slice(0,20))}catch(ye){const St=ye instanceof Error?ye.message:String(ye);H(R,{id:va(),kind:"assistant",text:"",thinking:"",streaming:!1,error:`Browser runtime failed: ${St}`}),u(!1);return}const B=await e();if("error"in B){H(R,{id:va(),kind:"assistant",text:"",thinking:"",streaming:!1,error:B.error}),u(!1);return}const ee=B.model,{tools:ne,systemPromptAdditions:we}=await fF(),Te=await Ip(It()).catch(()=>null);let Ke;try{Ke=await uF({projectId:R,model:ee,extraTools:ne.length?ne:void 0,systemPromptAdditions:we,repo:Te?`${Te.owner}/${Te.repo}@${Te.branch}`:void 0,containerExec:ye=>Ta.exec(ye)})}catch(ye){const St=ye instanceof Error?ye.message:String(ye);H(R,{id:va(),kind:"assistant",text:"",thinking:"",streaming:!1,error:St}),u(!1);return}(W.current!==R||T.current!==Ke)&&((_t=S.current)==null||_t.call(S),S.current=Ke.subscribe(Be),T.current=Ke,W.current=R),N.current=R;try{if(Yw(R,ee.contextWindow,ee.id))try{await Ke.compact()}catch(bn){console.warn("[pi] pre-run compaction failed",bn)}await aF(Ke,R,X),k(xh(R));const ye=`${M}/index.html`,St=await It().exists(ye),Qt=G3(ee.id);if((!St||Qt)&&I.current&&!Te)try{await It().writeText(ye,I.current),m(en=>en+1);const bn=K.current;if(bn){const en="✅ Saved to `index.html` — see the live preview →";ae(bn,oi=>{if(oi.kind!=="assistant")return oi;const br=RF(oi.text);return{...oi,text:br?`${br}
805
 
806
+ ${en}`:en}})}}catch{}if(Yw(R,ee.contextWindow,ee.id)){const bn=va();H(R,{id:bn,kind:"assistant",thinking:"",streaming:!0,text:"🗜️ Context is nearly full — compacting older turns…"});try{await Ke.compact(),ae(bn,en=>en.kind==="assistant"?{...en,streaming:!1,text:"🗜️ Context compacted — older turns were summarized to fit the model's window."}:en)}catch(en){console.warn("[pi] compaction failed",en),ae(bn,oi=>oi.kind==="assistant"?{...oi,streaming:!1,text:"⚠️ Context compaction failed — the next message may hit the context limit."}:oi)}}}catch(ye){console.error("[pi] agent run failed",ye);const St=ye instanceof Error?ye.message:String(ye);H(R,{id:va(),kind:"assistant",text:"",thinking:"",streaming:!1,error:St})}finally{u(!1),L.current=null,h(null)}},[e,r,t,Be,ae,H]),V=te.useCallback(()=>{var q;(q=T.current)==null||q.abort().catch(()=>{})},[]),re=te.useCallback(async()=>{const q=gs(t);try{await It().remove(q)}catch{}m(X=>X+1)},[t]),me=te.useCallback(()=>{var q;W.current===t&&((q=S.current)==null||q.call(S),S.current=null,T.current=null,W.current=null),Xw(t),L.current=null,a(X=>({...X,[t]:Sf})),h(null)},[t]),oe=te.useCallback(async q=>{var X;W.current===q&&((X=S.current)==null||X.call(S),S.current=null,T.current=null,W.current=null),Xw(q),a(R=>{const{[q]:M,...B}=R;return B});try{await It().remove(gs(q))}catch{}await TF(q).catch(()=>{}),m(R=>R+1)},[]),A=n[t]??Sf,F=te.useCallback(async()=>{const q=x[0];!q||r||(await kF(q,It(),gs(t)),m(X=>X+1),b(X=>X.slice(1)),await Ta.activate(t).catch(()=>{}))},[x,t,r]);return{messages:A.messages,running:r,fsVersion:l,liveHtml:N.current===t?f:null,terminalLines:A.terminal,send:j,stop:V,reset:me,discardProject:oe,clearWorkspace:re,checkpoints:x,undo:F,planPending:v}}const ey=e=>e>0?`${(e/1024/1024).toFixed(0)} mb`:"";function Ef(e){return e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function WF({model:e,eng:t}){var v;const{tps:n,contextUsed:a,contextWindow:r,generating:u,liveTokens:l,phase:m}=Jg(),f=t.modelId===e.id,h=f&&t.phase==="loading",x=f&&t.phase==="error",b=[];return h?b.push(`dl ${Math.round(t.progress*100)}%${t.total?` ${ey(t.loaded)}/${ey(t.total)}`:""}`):u?(b.push(`${m??"working"}…`),b.push(`${Ef(l)} tok${n?` · ${n} tok/s`:""}`)):(n&&b.push(`${n} tok/s`),r&&b.push(`ctx ${a?Ef(a):"0"}/${Ef(r)}`)),y.jsxs("span",{className:"hidden md:flex items-center gap-2 whitespace-nowrap text-[12px]",children:[y.jsx("span",{className:`w-2.5 h-2.5 shrink-0 ${u||h?"animate-pulse":""}`,style:{background:"var(--color-pi)"},title:x?`error: ${t.error}`:t.phase}),y.jsxs("span",{className:u?"text-[var(--color-pi-2)]":"text-[var(--color-ink-dim)]",children:[e.label.toLowerCase(),b.length>0?` · ${b.join(" · ")}`:""]}),x&&y.jsxs("span",{className:"text-[var(--color-pi-2)] max-w-[26rem] truncate",title:t.error,children:["err: ",(v=t.error)==null?void 0:v.slice(0,80)]})]})}function UF({projects:e,activeId:t,onSelect:n,onCreate:a,onDelete:r,onRename:u}){const[l,m]=te.useState(!1),[f,h]=te.useState(null),x=te.useRef(null),b=e.find(v=>v.id===t);return te.useEffect(()=>{if(!l)return;const v=k=>{x.current&&!x.current.contains(k.target)&&m(!1)};return window.addEventListener("mousedown",v),()=>window.removeEventListener("mousedown",v)},[l]),y.jsxs("div",{ref:x,className:"relative flex items-center",children:[y.jsxs("button",{className:"btn-bracket text-[12.5px]",onClick:()=>m(v=>!v),title:"Switch project",children:["project: ",y.jsx("span",{className:"text-[var(--color-ink)] font-semibold",children:(b==null?void 0:b.name)??"—"})," ▾"]}),l&&y.jsxs("div",{className:"absolute left-0 top-full mt-px w-72 z-40 bg-[var(--color-panel)] border border-[var(--color-ink)] text-[12px]",children:[e.map(v=>y.jsxs("div",{className:`group flex items-center gap-2 px-3 py-1.5 cursor-pointer ${v.id===t?"bg-[var(--color-panel-2)]":"hover:bg-[var(--color-panel-2)]"}`,onClick:()=>{n(v.id),m(!1)},children:[f===v.id?y.jsx("input",{autoFocus:!0,defaultValue:v.name,className:"field py-0.5 text-[12px]",onClick:k=>k.stopPropagation(),onBlur:k=>{u(v.id,k.target.value.trim()||v.name),h(null)},onKeyDown:k=>{k.key==="Enter"&&k.target.blur(),k.key==="Escape"&&h(null)}}):y.jsxs("span",{className:"flex-1 min-w-0 truncate",onDoubleClick:k=>{k.stopPropagation(),h(v.id)},children:[v.id===t?"▸ ":" ",v.name,y.jsxs("span",{className:"text-[var(--color-ink-faint)]",children:[" · ",v.messages.length," msg"]})]}),y.jsx("button",{className:"opacity-0 group-hover:opacity-100 text-[var(--color-ink-faint)] hover:text-[var(--color-pi-2)]",onClick:k=>{k.stopPropagation(),r(v.id)},title:"Delete project",children:"×"})]},v.id)),y.jsx("button",{className:"w-full text-left px-3 py-1.5 border-t border-[var(--color-edge)] text-[var(--color-ink-dim)] hover:text-[var(--color-ink)] hover:bg-[var(--color-panel-2)]",onClick:()=>{a(),m(!1)},children:"+ new project"})]})]})}function CF({model:e,eng:t,projects:n,activeId:a,onSelectProject:r,onCreateProject:u,onDeleteProject:l,onRenameProject:m,onOpenPicker:f,onOpenSkills:h,onOpenPlugins:x,onReload:b}){const k=t.modelId===e.id&&t.phase==="loading";return y.jsxs("header",{className:"h-11 shrink-0 flex items-center gap-6 px-4 border-b-2 border-[var(--color-ink)] bg-[var(--color-void)] text-[12.5px]",children:[y.jsx("span",{className:"font-bold tracking-tight",children:"pi agent"}),y.jsx(UF,{projects:n,activeId:a,onSelect:r,onCreate:u,onDelete:l,onRename:m}),y.jsxs("div",{className:"ml-auto flex items-center gap-4 min-w-0",children:[y.jsx(WF,{model:e,eng:t}),y.jsx("button",{className:"btn-bracket",onClick:f,title:"Choose model & params",children:"[model]"}),y.jsx("button",{className:"btn-bracket",onClick:h,title:"Manage skills",children:"[skills]"}),y.jsx("button",{className:"btn-bracket",onClick:x,title:"Manage plugins",children:"[plugins]"}),y.jsxs("button",{className:"btn-bracket hidden lg:inline",onClick:b,disabled:k,title:"Reload / download model now",children:["[",k?"loading…":"reload","]"]})]})]})}const _F=["Website","Dashboard","App","Game"],ty=[{id:"saas-landing",name:"SaaS landing",tagline:"Hero, features, pricing",emoji:"🚀",category:"Website",prompt:"Build a modern SaaS landing page in index.html: a sticky top nav with logo and CTA, a hero with a bold headline, subtitle and two buttons, a 3-card feature grid, a 3-tier pricing table, one testimonial, and a footer. Dark theme, generous spacing, fully responsive, no external images (use CSS shapes/emoji)."},{id:"portfolio",name:"Portfolio",tagline:"Personal showcase site",emoji:"🎨",category:"Website",prompt:"Build a personal portfolio in index.html: a hero with name, role and a short intro, an about section, a projects grid with cards (title, description, tag), a skills list, and contact links. Minimal, elegant, responsive, dark theme."},{id:"blog",name:"Blog",tagline:"Article list + reader",emoji:"📰",category:"Website",prompt:"Build a minimal blog in index.html: a header, a list of article cards (title, date, excerpt), and a reading view that opens when you click an article (show full mock text, back button). Keep 4-5 sample posts in memory. Clean typography, responsive, dark theme."},{id:"analytics",name:"Analytics dashboard",tagline:"KPIs + charts",emoji:"📊",category:"Dashboard",prompt:"Build an analytics dashboard in index.html: a left sidebar nav, a row of 4 KPI cards with a trend indicator, a line chart and a bar chart using Chart.js from the approved CDN, and a recent-activity table with mock data. Dark theme, responsive."},{id:"expenses",name:"Expense tracker",tagline:"Budget + category chart",emoji:"💰",category:"Dashboard",prompt:"Build an expense tracker in index.html: a form to add an income or expense with amount and category, a running balance, a transaction list with delete, and a category breakdown pie chart using Chart.js from the approved CDN. Keep all state in memory. Dark theme, responsive."},{id:"kanban",name:"Kanban board",tagline:"Drag-and-drop tasks",emoji:"🗂️",category:"App",prompt:"Build a Kanban board in index.html: three columns (Todo, Doing, Done), add a card to any column, drag cards between columns, and delete cards. Keep state in memory (no localStorage). Clean, responsive, dark theme."},{id:"chat",name:"Chat UI",tagline:"Telegram-style messenger",emoji:"💬",category:"App",prompt:"Build a chat app UI in index.html: a contact list on the left, a message thread on the right with incoming/outgoing bubbles, and an input bar. Load one sample conversation; sending a message appends it and auto-replies with an echo after a short delay. Telegram-style dark-blue theme, responsive."},{id:"notes",name:"Markdown notes",tagline:"Editor + live preview",emoji:"📝",category:"App",prompt:"Build a markdown notes app in index.html: a note list on the left, a split editor with a live rendered preview on the right (write a small markdown-to-HTML parser for headings, bold, italic, lists and code), plus new/delete and search. Keep notes in memory. Dark theme, responsive."},{id:"pomodoro",name:"Pomodoro timer",tagline:"Focus cycles + ring",emoji:"🍅",category:"App",prompt:"Build a Pomodoro timer in index.html: 25-minute work and 5-minute break cycles, start/pause/reset, a circular SVG progress ring that fills as time passes, and a completed-sessions counter. Minimal, centered, dark theme."},{id:"weather",name:"Weather card",tagline:"Current + 5-day forecast",emoji:"🌦️",category:"App",prompt:"Build a weather dashboard UI in index.html: a search bar, a current-conditions card (temp, condition, humidity, wind) and a 5-day forecast row, using CSS-drawn or emoji weather icons and mock data that changes per searched city. Clean, responsive, dark theme."},{id:"breakout",name:"Breakout game",tagline:"Canvas arcade classic",emoji:"🎮",category:"Game",prompt:"Build a playable Breakout game in index.html on a canvas: a paddle controlled by mouse and arrow keys, ball physics with wall/paddle bounce, a grid of colored bricks, score and lives, and win/lose states with restart. Plain JavaScript, no libraries."},{id:"snake",name:"Snake game",tagline:"Grid classic with score",emoji:"🐍",category:"Game",prompt:"Build a playable Snake game in index.html on a canvas: arrow-key movement, growing snake, random food, wall/self collision game-over with restart, and a live score. Keep it minimal and smooth. Plain JavaScript, no libraries."}];function MF(){const{phase:e}=Jg(),[t,n]=te.useState(0);return te.useEffect(()=>{const a=Date.now(),r=setInterval(()=>n(Math.round((Date.now()-a)/1e3)),500);return()=>clearInterval(r)},[]),y.jsxs("div",{className:"text-[13px] text-[var(--color-ink-faint)] blink",children:[e??"working",t>=2?` ${t}s`:""]})}function OF({messages:e,eng:t}){const{phase:n,liveTokens:a,tps:r}=Jg(),[u,l]=te.useState(0);te.useEffect(()=>{const x=Date.now(),b=setInterval(()=>l(Math.floor((Date.now()-x)/1e3)),250);return()=>clearInterval(b)},[]);const m=[...e].reverse().find(x=>x.kind==="tool"&&x.status==="running"),f=(m==null?void 0:m.kind)==="tool"?J3(m.args):"",h=t.phase==="loading"?t.total?`loading model ${Math.round(t.progress*100)}%`:"starting model":(m==null?void 0:m.kind)==="tool"?`${m.name}${f?` ${f}`:""}`:n||(a>0?"generating":"preparing context");return y.jsxs("div",{role:"status","aria-live":"polite",className:"ml-[3.25rem] border border-[var(--color-edge-2)] bg-[var(--color-panel)] px-3 py-2.5",children:[y.jsxs("div",{className:"flex items-center gap-2 text-[12.5px]",children:[y.jsxs("span",{className:"relative flex h-2.5 w-2.5 shrink-0",children:[y.jsx("span",{className:"absolute inline-flex h-full w-full animate-ping bg-[var(--color-pi)] opacity-40"}),y.jsx("span",{className:"relative inline-flex h-2.5 w-2.5 bg-[var(--color-pi)]"})]}),y.jsx("span",{className:"font-semibold text-[var(--color-ink)]",children:"agent is working"}),y.jsxs("span",{className:"text-[var(--color-ink-dim)] truncate",children:["· ",h]}),y.jsxs("span",{className:"ml-auto tabular-nums text-[var(--color-ink-faint)]",children:[u,"s"]})]}),y.jsx("div",{className:"mt-2 h-px overflow-hidden bg-[var(--color-edge)]",children:y.jsx("div",{className:"h-full w-1/3 animate-[activity-slide_1.2s_ease-in-out_infinite] bg-[var(--color-pi)]"})}),y.jsx("div",{className:"mt-1.5 text-[10.5px] text-[var(--color-ink-faint)]",children:a>0?`${a} tokens${r?` · ${r} tok/s`:""}`:"waiting for the next model event"})]})}const IF=[{label:"neon pomodoro",prompt:"Build a neon synthwave pomodoro timer in index.html with start/pause/reset and a glowing circular progress ring."},{label:"cursor constellation",prompt:"Make a particle constellation on a full-page canvas that links dots near the cursor. Plain JavaScript."},{label:"tip calculator",prompt:"Build a clean tip calculator in index.html: bill amount, tip percentage, split between N people, live per-person and total."},{label:"color palette",prompt:"Build a color palette generator in index.html: generate 5 harmonious colors, click a swatch to copy its hex, lock colors, regenerate."},{label:"typing test",prompt:"Build a typing speed test in index.html: a sample paragraph, live WPM and accuracy as you type, and a restart button. Plain JavaScript."}];function LF({messages:e,running:t,eng:n,onSend:a,onStop:r,onUndo:u,canUndo:l,planPending:m}){const[f,h]=te.useState(""),x=te.useRef(null),b=te.useRef(null);te.useEffect(()=>{if(e.length===0)return;const T=x.current;T&&(T.scrollTop=T.scrollHeight)},[e,t]);const v=()=>{f.trim()&&(a(f),h(""),b.current&&(b.current.style.height="auto"))},k=T=>{T.style.height="auto",T.style.height=Math.min(T.scrollHeight,200)+"px"};return y.jsxs("section",{className:"flex-1 min-w-0 flex flex-col",children:[y.jsx("div",{ref:x,className:"flex-1 min-h-0 overflow-y-auto px-6 py-6",children:e.length===0?y.jsx(jF,{onPick:a}):y.jsxs("div",{className:"space-y-4 max-w-3xl",children:[e.map(T=>y.jsx(NF,{m:T},T.id)),t&&y.jsx(OF,{messages:e,eng:n})]})}),e.length>0&&y.jsxs("div",{className:"px-6 pb-4 pt-2",children:[y.jsxs("div",{className:"max-w-3xl flex items-end gap-3 border-b border-[var(--color-ink)] focus-within:border-b-2 pb-1",children:[y.jsx("textarea",{ref:b,value:f,rows:1,placeholder:t?"steer the agent — it reads this mid-run…":"describe what to build",className:"flex-1 bg-transparent resize-none outline-none text-[13.5px] py-1 max-h-[200px] placeholder:text-[var(--color-ink-faint)]",onChange:T=>{h(T.target.value),k(T.target)},onKeyDown:T=>{T.key==="Enter"&&!T.shiftKey&&(T.preventDefault(),v())}}),y.jsx("button",{className:"btn-bracket accent text-[13.5px]",onClick:v,disabled:!f.trim(),title:t?"Steer the agent":"Send",children:"[send]"}),t&&y.jsx("button",{className:"btn-bracket text-[13.5px]",onClick:r,title:"Stop the run",children:"[stop]"}),!t&&y.jsx("button",{className:"btn-bracket text-[13.5px]",onClick:u,disabled:!l,title:"Restore the project to before the last request",children:"[undo]"}),!t&&m&&y.jsx("button",{className:"btn-bracket accent text-[13.5px]",onClick:()=>a("Approve the plan and implement it."),title:"Approve the proposed plan",children:"[approve plan]"})]}),y.jsx("div",{className:"text-[10.5px] text-[var(--color-ink-faint)] mt-1.5",children:"real pi agent loop · local webgpu or cloud with your key · ⏎ send · ⇧⏎ newline"})]})]})}const NF=te.memo(function({m:t}){return t.kind==="user"?y.jsx(X3,{prefix:"you:",children:y.jsx("div",{className:"whitespace-pre-wrap text-[13.5px] font-medium",children:t.text})}):t.kind==="tool"?y.jsx(zF,{m:t}):y.jsx(DF,{m:t})});function X3({prefix:e,children:t}){return y.jsxs("div",{className:"flex gap-3",children:[y.jsx("span",{className:"w-10 shrink-0 font-bold text-[13.5px] select-none",children:e}),y.jsx("div",{className:"min-w-0 flex-1",children:t})]})}function DF({m:e}){const[t,n]=te.useState(null),a=t??e.streaming;return y.jsxs(X3,{prefix:"pi:",children:[e.thinking&&y.jsxs("button",{onClick:()=>n(!a),className:"mb-1.5 text-[11px] text-[var(--color-ink-faint)] hover:text-[var(--color-ink-dim)] inline-flex items-center gap-1",children:[y.jsx("span",{className:`transition ${a?"rotate-90":""}`,children:"▸"}),a?"hide":"show"," reasoning",e.streaming&&e.thinking&&!e.text?" · thinking…":""]}),e.thinking&&a&&y.jsx("div",{className:"mb-2 text-[12px] text-[var(--color-ink-faint)] border-l border-[var(--color-edge-2)] pl-3 whitespace-pre-wrap",children:e.thinking}),e.text&&y.jsx("div",{className:e.streaming?"blink":void 0,children:y.jsx("div",{className:"msg-md text-[13.5px] text-[var(--color-ink)] break-words inline-block max-w-full align-top",dangerouslySetInnerHTML:{__html:pF(e.text)}})}),e.streaming&&!e.text&&!e.thinking&&y.jsx(MF,{}),e.error&&y.jsxs("div",{className:"mt-1 text-[13px] text-[var(--color-pi-2)] whitespace-pre-wrap",children:["err: ",e.error]})]})}function J3(e){const t=e??{};return typeof t.path=="string"?t.path.split("/").pop()??t.path:typeof t.command=="string"?t.command:""}function $F(e){try{const t=JSON.stringify(e??{},null,2);return t.length>2e3?t.slice(0,2e3)+`
807
  … (truncated)`:t}catch{return String(e)}}function zF({m:e}){const[t,n]=te.useState(null),a=t??e.status==="error",r=e.status==="running"?y.jsxs("span",{className:"inline-flex items-center gap-1.5 text-[var(--color-pi-2)]",children:[y.jsx("span",{className:"inline-block h-2 w-2 animate-pulse bg-[var(--color-pi)]"}),"running"]}):e.status==="ok"?y.jsx("span",{className:"text-[var(--color-ink-dim)]",children:"ok"}):y.jsx("span",{className:"text-[var(--color-pi-2)] font-bold",children:"err"}),u=J3(e.args),l=e.status==="error"?e.resultText:e.diff||e.resultText;return y.jsxs("div",{className:"pl-[3.25rem]",children:[y.jsxs("button",{onClick:()=>n(!a),className:"w-full flex items-baseline text-left text-[13px] font-mono hover:bg-[var(--color-panel-2)] transition-colors",title:"Show tool arguments & result",children:[y.jsxs("span",{className:"shrink-0 text-[var(--color-ink)]",children:[e.name,u&&y.jsxs("span",{className:"text-[var(--color-ink-dim)]",children:[" ",u]})]}),y.jsx("span",{className:"leader"}),y.jsx("span",{className:"shrink-0",children:r})]}),a&&y.jsxs("div",{className:"mt-1 mb-2 border border-[var(--color-edge)] bg-[var(--color-panel)] px-3 py-2 space-y-2",children:[y.jsxs("div",{children:[y.jsx("div",{className:"text-[9.5px] uppercase tracking-wider text-[var(--color-ink-faint)] mb-1",children:"arguments"}),y.jsx("pre",{className:"text-[11px] leading-relaxed font-mono text-[var(--color-ink-dim)] overflow-x-auto max-h-48 overflow-y-auto whitespace-pre-wrap",children:$F(e.args)})]}),(l||e.status==="running")&&y.jsxs("div",{children:[y.jsx("div",{className:"text-[9.5px] uppercase tracking-wider text-[var(--color-ink-faint)] mb-1",children:e.status==="error"?"error":e.diff?"diff":"result"}),y.jsx("pre",{className:`text-[11px] leading-relaxed font-mono overflow-x-auto max-h-72 overflow-y-auto whitespace-pre-wrap ${e.status==="error"?"text-[var(--color-pi-2)]":"text-[var(--color-ink-dim)]"}`,children:l||"…"})]})]})]})}function jF({onPick:e}){const[t,n]=te.useState(""),[a,r]=te.useState("All"),u=te.useRef(null),[l,m]=te.useState(()=>{try{return sessionStorage.getItem(er)??""}catch{return""}}),f=b=>{m(b);try{const v=b.trim();v?sessionStorage.setItem(er,v):sessionStorage.removeItem(er)}catch{}},h=()=>{const b=t.trim();b&&e(b)},x=a==="All"?ty:ty.filter(b=>b.category===a);return y.jsxs("div",{className:"max-w-2xl pt-10 pb-8",children:[y.jsxs("div",{className:"flex items-center gap-2.5",children:[y.jsx("span",{className:"w-3 h-3 bg-[var(--color-pi)]"}),y.jsx("h1",{className:"text-[20px] font-bold tracking-tight",children:"what do you want to build?"})]}),y.jsx("p",{className:"mt-2 text-[12.5px] text-[var(--color-ink-dim)]",children:"describe an app or pick a template — the real pi agent writes the files and previews them here."}),y.jsxs("div",{className:"mt-6 flex items-end gap-3 border-b-2 border-[var(--color-ink)] pb-1",children:[y.jsx("textarea",{ref:u,value:t,rows:1,placeholder:"describe what to build","data-testid":"hero-input",className:"flex-1 bg-transparent resize-none outline-none text-[14px] py-1 max-h-[200px] placeholder:text-[var(--color-ink-faint)]",onChange:b=>{n(b.target.value);const v=b.target;v.style.height="auto",v.style.height=Math.min(v.scrollHeight,200)+"px"},onKeyDown:b=>{b.key==="Enter"&&!b.shiftKey&&(b.preventDefault(),h())}}),y.jsx("button",{className:"btn-bracket accent text-[14px]",onClick:h,disabled:!t.trim(),"data-testid":"hero-send",children:"[send]"})]}),y.jsx("div",{className:"mt-3 flex flex-wrap gap-x-5 gap-y-1 text-[12px]",children:IF.map(b=>y.jsxs("button",{onClick:()=>e(b.prompt),className:"text-[var(--color-ink-faint)] hover:text-[var(--color-pi-2)] transition-colors",children:["· ",b.label]},b.label))}),!l.trim()&&y.jsxs("div",{className:"mt-5 flex items-center gap-3 flex-wrap",children:[y.jsx("input",{className:"field text-[12px] max-w-[300px]",type:"password",placeholder:"openrouter key — sk-or-v1-… (claude / gpt / qwen)","data-testid":"welcome-key",onChange:b=>f(b.target.value)}),y.jsx("span",{className:"text-[10.5px] text-[var(--color-ink-faint)]",children:"or pick local soyuz (no key)"})]}),y.jsxs("div",{className:"mt-10",children:[y.jsxs("div",{className:"flex items-baseline gap-4 mb-3 flex-wrap text-[12px]",children:[y.jsx("h2",{className:"font-bold text-[var(--color-ink)]",children:"templates"}),["All",..._F].map(b=>y.jsxs("button",{onClick:()=>r(b),className:`transition-colors ${a===b?"text-[var(--color-pi-2)]":"text-[var(--color-ink-faint)] hover:text-[var(--color-ink)]"}`,children:["[",b.toLowerCase(),"]"]},b))]}),y.jsx("div",{className:"grid sm:grid-cols-2 md:grid-cols-3 gap-2",children:x.map(b=>y.jsxs("button",{onClick:()=>e(b.prompt),"data-testid":`template-${b.id}`,className:"text-left border border-[var(--color-edge)] hover:border-[var(--color-ink)] bg-[var(--color-panel)] px-3 py-2.5 transition-colors group",children:[y.jsxs("div",{className:"flex items-baseline gap-2",children:[y.jsx("span",{className:"text-[14px] leading-none",children:b.emoji}),y.jsx("span",{className:"text-[12.5px] font-semibold group-hover:text-[var(--color-pi-2)] transition-colors",children:b.name.toLowerCase()})]}),y.jsx("div",{className:"text-[11px] text-[var(--color-ink-faint)] mt-1",children:b.tagline.toLowerCase()})]},b.id))})]})]})}function ny(e){return/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(e)}function iy(e,t){const n=e.split(/[?#]/)[0],a=n.startsWith("/")?n:`${t}/${n}`,r=[];for(const u of a.split("/"))if(!(u===""||u===".")){if(u===".."){r.pop();continue}r.push(u)}return"/"+r.join("/")}function ay(e,t){return e.replace(new RegExp(`</${t}`,"gi"),`<\\/${t}`)}async function BF(e,t,n){var l;let a=e;const r=[...a.matchAll(/<link\b[^>]*>/gi)];for(const m of r){const f=m[0];if(!/rel=["']?stylesheet["']?/i.test(f))continue;const h=(l=f.match(/href=["']([^"']+)["']/i))==null?void 0:l[1];if(!h||ny(h))continue;const x=await n(iy(h,t));x!==null&&(a=a.replace(f,`<style>
808
  ${ay(x,"style")}
809
  </style>`))}const u=[...a.matchAll(/<script\b[^>]*\bsrc=["']([^"']+)["'][^>]*>\s*<\/script>/gi)];for(const m of u){const[f,h]=m;if(ny(h))continue;const x=await n(iy(h,t));if(x===null)continue;const b=/type=["']?module["']?/i.test(f);a=a.replace(f,`<script${b?' type="module"':""}>
web/index.html CHANGED
@@ -24,31 +24,29 @@
24
  Space-only addition (not part of the site build).
25
 
26
  On huggingface.co this app is a nested iframe, and a nested frame cannot be
27
- cross-origin isolated unless its parent is huggingface.co is not, and its
28
- iframe does not delegate `cross-origin-isolated`. No isolation means no
29
- SharedArrayBuffer, and the in-browser Linux runtime (v86) that runs the
30
- project refuses to start with "SharedArrayBuffer transfer requires
31
- self.crossOriginIsolated". The headers this Space sends do give isolation
32
- on its own origin, so send people there instead of letting them hit that
33
- error mid-build.
34
  -->
35
  <script>
36
  (function () {
37
  if (window.crossOriginIsolated) return;
38
  var direct = "https://alexwortega-my-pi-agent.hf.space/";
39
- if (location.origin + "/" === direct && window.top === window.self) return;
40
  var bar = document.createElement("div");
41
  bar.style.cssText =
42
- "position:fixed;left:0;right:0;top:0;z-index:2147483647;padding:8px 12px;" +
43
- "font:12.5px ui-monospace,SFMono-Regular,Menlo,monospace;background:#1a1a16;" +
44
- "color:#f2f2ee;border-bottom:2px solid #ff4b00;display:flex;gap:10px;align-items:center";
 
45
  bar.innerHTML =
46
- '<span style="color:#ff4b00">!</span><span>embedded here without cross-origin isolation ' +
47
- "the in-browser runtime and the local model need it.</span>" +
48
- '<a href="' + direct + '" target="_blank" rel="noopener" ' +
49
- 'style="color:#ff4b00;font-weight:700">open in its own tab →</a>';
50
  document.body.appendChild(bar);
51
- document.body.style.paddingTop = "34px";
52
  })();
53
  </script>
54
  </body>
 
24
  Space-only addition (not part of the site build).
25
 
26
  On huggingface.co this app is a nested iframe, and a nested frame cannot be
27
+ cross-origin isolated unless its parent is. The agent itself is fine there
28
+ its model runs on this Space's GPU but two things do need
29
+ SharedArrayBuffer and stay unavailable in the embed: the in-browser Linux
30
+ runtime behind the terminal, and the WebGPU (in-browser) model. Say so,
31
+ once, instead of letting either fail silently later.
 
 
32
  -->
33
  <script>
34
  (function () {
35
  if (window.crossOriginIsolated) return;
36
  var direct = "https://alexwortega-my-pi-agent.hf.space/";
 
37
  var bar = document.createElement("div");
38
  bar.style.cssText =
39
+ "position:fixed;left:0;right:0;top:0;z-index:2147483647;padding:7px 12px;" +
40
+ "font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;background:#1a1a16;" +
41
+ "color:#f2f2ee;border-bottom:2px solid #ff4b00;display:flex;gap:10px;" +
42
+ "align-items:center;flex-wrap:wrap";
43
  bar.innerHTML =
44
+ '<span style="color:#ff4b00">!</span><span>embedded view the agent runs on this ' +
45
+ "Space's GPU, but the terminal and the in-browser WebGPU model need a standalone tab." +
46
+ '</span><a href="' + direct + '" target="_blank" rel="noopener" ' +
47
+ 'style="color:#ff4b00;font-weight:700;margin-left:auto">open standalone →</a>';
48
  document.body.appendChild(bar);
49
+ document.body.style.paddingTop = "32px";
50
  })();
51
  </script>
52
  </body>