Spaces:
Running on Zero
Running on Zero
Commit ·
97d3fbc
1
Parent(s): e539b1e
Serve the app in the huggingface.co embed, not a link to it
Browse filesWith 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 +0 -0
- app.py +26 -61
- web/assets/index-BuqJnO4-.js +2 -2
- web/index.html +14 -16
__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
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 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:
|
| 19 |
-
|
| 20 |
-
|
| 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
|
| 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
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
But COOP is also what makes a Space "only run in standalone mode,
|
| 191 |
-
in the default Space iframed view" (HF's
|
| 192 |
-
it, the
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
|
|
|
|
|
|
| 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
|
| 28 |
-
|
| 29 |
-
SharedArrayBuffer
|
| 30 |
-
|
| 31 |
-
|
| 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:
|
| 43 |
-
"font:
|
| 44 |
-
"color:#f2f2ee;border-bottom:2px solid #ff4b00;display:flex;gap:10px;
|
|
|
|
| 45 |
bar.innerHTML =
|
| 46 |
-
'<span style="color:#ff4b00">!</span><span>embedded
|
| 47 |
-
"
|
| 48 |
-
'<a href="' + direct + '" target="_blank" rel="noopener" ' +
|
| 49 |
-
'style="color:#ff4b00;font-weight:700">open
|
| 50 |
document.body.appendChild(bar);
|
| 51 |
-
document.body.style.paddingTop = "
|
| 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>
|