custom
code
sovereign-compute
File size: 16,820 Bytes
92d9567
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="color-scheme" content="dark">
<title>Macro VM</title>
<script src="https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js"></script>
<style>

:root{--bg:#0f0f10;--surface:#1a1a1c;--surface2:#242426;--border:rgba(255,255,255,.06);--text:#e5e5e7;--muted:#7a7a7e;--dim:#4a4a4e;--blue:#5b7fff;--green:#34c759;--red:#ff453a;--yellow:#f0b232}

*{box-sizing:border-box;margin:0;padding:0}

html,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,system-ui,sans-serif;font-size:14px;-webkit-font-smoothing:antialiased;overflow:hidden}

.mono{font-family:'SF Mono','Menlo','Monaco',ui-monospace,monospace}

.onboard{position:fixed;inset:0;z-index:100;background:var(--bg);display:grid;place-items:center}

.onboard.hidden{display:none}

.card{width:380px;padding:28px;background:var(--surface);border:1px solid var(--border);border-radius:16px}

.card h1{font-size:18px;font-weight:600;margin-bottom:2px}

.card .sub{font-size:12px;color:var(--muted);margin-bottom:20px}

.field{margin-bottom:12px}

.field label{display:block;font-size:11px;color:var(--muted);margin-bottom:4px;font-weight:500}

.field input,.field select{width:100%;padding:8px 10px;background:var(--surface2);border:1px solid var(--border);border-radius:7px;color:var(--text);font-size:13px;outline:none}

.field input:focus,.field select:focus{border-color:var(--blue)}

.field input::placeholder{color:var(--dim)}

.field .hint{font-size:10px;color:var(--dim);margin-top:2px}

.bp{width:100%;padding:9px;border-radius:7px;border:none;background:var(--blue);color:#fff;font-size:13px;font-weight:500;cursor:pointer;margin-top:6px}

.bp:hover{opacity:.9}.bp:disabled{opacity:.4;cursor:default}

.em{color:var(--red);font-size:11px;margin-top:6px;display:none}

.app{display:flex;flex-direction:column;height:100vh}

.topbar{height:42px;display:flex;align-items:center;justify-content:space-between;padding:0 14px;border-bottom:1px solid var(--border);background:rgba(26,26,28,.9);backdrop-filter:blur(20px);flex-shrink:0}

.topbar .b{font-size:13px;font-weight:600}

.topbar .s{display:flex;align-items:center;gap:5px;font-size:10px;color:var(--muted)}

.dot{width:6px;height:6px;border-radius:50%;background:var(--dim)}.dot.on{background:var(--green)}.dot.err{background:var(--red)}.dot.busy{background:var(--yellow)}

.topbar .a{display:flex;gap:5px}

.topbar .p{padding:3px 7px;border-radius:5px;background:var(--surface2);font-size:10px;color:var(--muted);cursor:pointer;border:none}.topbar .p:hover{color:var(--text)}

.chat{flex:1;overflow:auto;padding:14px;display:flex;flex-direction:column;gap:10px}

.msg{max-width:700px;width:100%;margin:0 auto}

.msg .r{font-size:10px;color:var(--dim);margin-bottom:3px;text-transform:uppercase;letter-spacing:.03em}

.msg.user .r{text-align:right}

.msg.user .c{background:var(--blue);color:#fff;padding:9px 13px;border-radius:14px 14px 4px 14px;font-size:13px;line-height:1.5;margin-left:auto;max-width:80%;display:inline-block}

.msg.agent .c{background:var(--surface);padding:9px 13px;border-radius:14px 14px 14px 4px;font-size:13px;line-height:1.5}

.msg.agent .c pre{margin-top:6px;padding:8px;background:rgba(0,0,0,.3);border-radius:7px;font-family:'SF Mono',monospace;font-size:11px;overflow-x:auto;white-space:pre-wrap}

.msg.agent .c code{font-family:'SF Mono',monospace;font-size:11px;background:rgba(0,0,0,.3);padding:1px 4px;border-radius:3px}

.tc{margin-top:6px;padding:7px 9px;background:rgba(91,127,255,.06);border:1px solid rgba(91,127,255,.12);border-radius:7px;font-size:11px}

.tc .th{color:var(--blue);font-weight:500;margin-bottom:3px;display:flex;align-items:center;gap:5px}

.tc .th .sp{width:11px;height:11px;border:2px solid var(--blue);border-top-color:transparent;border-radius:50%;animation:sp .6s linear infinite}

@keyframes sp{to{transform:rotate(360deg)}}

.tc .td{font-family:'SF Mono',monospace;font-size:10px;color:var(--muted);white-space:pre-wrap;max-height:100px;overflow:auto}

.tr{margin-top:5px;padding:7px 9px;background:rgba(52,199,89,.06);border:1px solid rgba(52,199,89,.12);border-radius:7px;font-size:10px;font-family:'SF Mono',monospace;white-space:pre-wrap;max-height:160px;overflow:auto;color:var(--green)}

.tr.er{background:rgba(255,69,58,.06);border-color:rgba(255,69,58,.12);color:var(--red)}

.typing{display:flex;gap:3px;padding:6px 0}

.typing span{width:5px;height:5px;border-radius:50%;background:var(--dim);animation:ty .8s infinite}

.typing span:nth-child(2){animation-delay:.15s}.typing span:nth-child(3){animation-delay:.3s}

@keyframes ty{0%,100%{opacity:.3}50%{opacity:1}}

.wel{text-align:center;padding:50px 20px;color:var(--muted)}

.wel h2{font-size:16px;color:var(--text);margin-bottom:6px;font-weight:500}

.wel p{font-size:12px;line-height:1.5}

.wel .q{display:flex;gap:6px;justify-content:center;margin-top:14px;flex-wrap:wrap}

.wel .qb{padding:7px 12px;border-radius:7px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:11px;cursor:pointer}.wel .qb:hover{border-color:var(--blue);color:var(--blue)}

.ib{padding:10px 14px;border-top:1px solid var(--border);display:flex;gap:7px;align-items:flex-end;max-width:728px;width:100%;margin:0 auto}

.ib textarea{flex:1;padding:9px 12px;background:var(--surface);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:13px;font-family:inherit;outline:none;resize:none;min-height:40px;max-height:140px;line-height:1.4}

.ib textarea:focus{border-color:var(--blue)}.ib textarea::placeholder{color:var(--dim)}

.sb{width:40px;height:40px;border-radius:9px;border:none;background:var(--blue);color:#fff;cursor:pointer;display:grid;place-items:center;flex-shrink:0}.sb:hover{opacity:.85}.sb:disabled{opacity:.3;cursor:default}

.tp{border-top:1px solid var(--border);background:var(--surface);max-height:0;overflow:hidden;transition:max-height .2s}.tp.open{max-height:220px}

.tp .h{padding:5px 14px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);font-size:10px;color:var(--muted)}

.tp .h button{background:none;border:none;color:var(--muted);cursor:pointer;font-size:10px}.tp .h button:hover{color:var(--text)}

.tp .b{padding:6px 14px;height:180px;overflow:auto;font-family:'SF Mono',monospace;font-size:10px;line-height:1.5;color:var(--muted);white-space:pre-wrap}

.tl{margin-bottom:1px}.tl.ok{color:var(--green)}.tl.err{color:var(--red)}.tl.info{color:var(--blue)}.tl.tool{color:var(--yellow)}

</style>
</head>
<body>
<div class="onboard" id="onboard">
<div class="card">
<h1>Macro VM</h1>
<p class="sub">Sign in with Ollama Cloud.</p>
<div class="field"><label>API Key</label><input id="apiKey" type="password" placeholder="ollama key from ollama.com/settings/keys"><div class="hint">Get yours at ollama.com/settings/keys</div></div>
<div class="field"><label>Model</label><select id="modelPick">
<option value="gpt-oss:120b">GPT-OSS 120B</option>
<option value="gpt-oss:20b">GPT-OSS 20B</option>
<option value="deepseek-v4-flash">DeepSeek V4 Flash</option>
<option value="deepseek-v4-pro">DeepSeek V4 Pro</option>
<option value="gemma4:31b">Gemma 4 31B</option>
<option value="kimi-k3">Kimi K3</option>
<option value="kimi-k2.6">Kimi K2.6</option>
<option value="minimax-m3">MiniMax M3</option>
<option value="mistral-large-3">Mistral Large 3</option>
<option value="nemotron-3-ultra">Nemotron 3 Ultra</option>
<option value="qwen3.5:397b">Qwen 3.5 397B</option>
</select></div>
<button class="bp" id="btnConnect" onclick="connect()">Sign in</button>
<p class="em" id="connectErr"></p>
</div>
</div>

<div class="app" id="mainApp" style="display:none">
<header class="topbar">
<div class="b">Macro VM</div>
<div class="s"><span class="dot on" id="connDot"></span><span id="modelLabel"></span></div>
<div class="a"><button class="p" onclick="toggleTerm()">Terminal</button><button class="p" onclick="document.getElementById('onboard').classList.remove('hidden')">Sign out</button></div>
</header>
<div class="chat" id="chat">
<div class="wel">
<h2>What can I help with?</h2>
<p>Python, search, and terminal tools run automatically.</p>
<div class="q">
<button class="qb" onclick="quickAsk('Calculate the first 20 Fibonacci numbers')">Python math</button>
<button class="qb" onclick="quickAsk('Search for what is new in WebGPU 2025')">Search web</button>
<button class="qb" onclick="quickAsk('Write and run a Python function to find primes up to 100')">Run code</button>
</div>
</div>
</div>
<div class="ib">
<textarea id="askInput" rows="1" placeholder="Ask anything..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send()}"></textarea>
<button class="sb" id="sendBtn" onclick="send()"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13"/><path d="M22 2L15 22L11 13L2 9L22 2Z"/></svg></button>
</div>
<div class="tp" id="termPanel">
<div class="h"><span>Output</span><button onclick="toggleTerm()">Close</button></div>
<div class="b" id="termBody"></div>
</div>
</div>

<script>

let apiKey='',model='',chatHistory=[],sending=false,pyodide=null,pyReady=false;

const SP=`You are a helpful AI assistant with access to tools. You can run Python code, search the web, and execute terminal commands.



To use a tool, emit a fenced code block with the tool name as language tag:

\`\`\`python

your code here

\`\`\`

\`\`\`search

search query here

\`\`\`

\`\`\`terminal

command here

\`\`\`



Rules:

- For Python: write valid Python 3. Print results with print().

- For search: emit ONLY the search query.

- For terminal: emit ONLY the shell command.

- After seeing tool results, continue your answer with the findings.

- Use markdown for formatting.`;



async function connect(){

  const btn=document.getElementById('btnConnect'),err=document.getElementById('connectErr');

  btn.disabled=true;btn.textContent='Signing in...';err.style.display='none';

  try{

    apiKey=document.getElementById('apiKey').value.trim();

    model=document.getElementById('modelPick').value;

    if(!apiKey){throw new Error('Enter your API key')}

    const r=await fetch('https://ollama.com/api/chat',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+apiKey},body:JSON.stringify({model,messages:[{role:'user',content:'hi'}],stream:false,max_tokens:1})});

    if(!r.ok){const e=await r.text();throw new Error('Bad key ('+r.status+'): '+e.slice(0,150))}

    document.getElementById('onboard').classList.add('hidden');

    document.getElementById('mainApp').style.display='';

    document.getElementById('modelLabel').textContent=model;

    t('Signed in — '+model,'ok');t('Loading Python...','info');initPyodide();

  }catch(e){err.textContent=e.message;err.style.display=''}

  btn.disabled=false;btn.textContent='Sign in';

}



async function initPyodide(){try{pyodide=await loadPyodide();pyReady=true;t('Python ready','ok');document.getElementById('connDot').className='dot on'}catch(e){t('Python error: '+e.message,'err')}}

async function runPython(code){if(!pyReady)return'Error: Python not loaded';try{let o='';pyodide.setStdout({batched:x=>{o+=x}});pyodide.setStderr({batched:x=>{o+='ERROR: '+x}});await pyodide.runPythonAsync(code);return o||'(no output)'}catch(e){return'Error: '+e.message}}



async function execTool(n,input){

  t('→ tool: '+n+'('+input.slice(0,80)+')','tool');

  if(n==='python')return await runPython(input);

  if(n==='search')return await runSearch(input);

  if(n==='terminal')return await runTerminal(input);

  return'Unknown tool: '+n;

}

async function runSearch(q){

  try{const r=await fetch('https://api.duckduckgo.com/?q='+encodeURIComponent(q)+'&format=json&no_html=1');const d=await r.json();let R=[];

  if(d.AbstractText)R.push({t:d.Heading,s:d.AbstractText,u:d.AbstractURL});

  if(d.RelatedTopics)d.RelatedTopics.slice(0,5).forEach(x=>{if(x.Text)R.push({t:x.Text.slice(0,60),s:x.Text,u:x.FirstURL||''})});

  if(!R.length)return'No results for: '+q;return R.map((r,i)=>`${i+1}. ${r.t}\n   ${r.s}\n   ${r.u}`).join('\n\n')}catch(e){return'Search error: '+e.message}

}

async function runTerminal(cmd){

  const c=cmd.trim().toLowerCase();

  if(c==='pwd'||c==='cd')return'/workspace';if(c.startsWith('ls'))return'README.md  src/  docs/  package.json  .git/';

  if(c==='whoami')return'macro-vm';if(c==='date')return new Date().toISOString();if(c.startsWith('echo '))return cmd.slice(5);

  if(c==='help')return'Available: ls, pwd, cat, echo, whoami, date, help';

  return'Command: "'+cmd+'"\n(client-side)';

}



function extractToolCalls(text){const C=[];const rx=/```(python|search|terminal)\n([\s\S]*?)```/g;let m;while((m=rx.exec(text))!==null)C.push({n:m[1],c:m[2].trim()});return C}



async function callModel(){

  const msgs=[{role:'system',content:SP},...chatHistory];

  const r=await fetch('https://ollama.com/api/chat',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+apiKey},body:JSON.stringify({model,messages:msgs,stream:false})});

  if(!r.ok){const e=await r.text();throw new Error('Ollama: '+e.slice(0,200))}

  const d=await r.json();return d.message?.content||'';

}



async function send(){

  if(sending)return;const inp=document.getElementById('askInput');const txt=inp.value.trim();if(!txt)return;

  inp.value='';inp.style.height='auto';sending=true;document.getElementById('sendBtn').disabled=true;

  const w=document.querySelector('.wel');if(w)w.remove();

  addMsg('user',txt);chatHistory.push({role:'user',content:txt});

  const cd=document.getElementById('connDot');

  try{for(let i=0;i<8;i++){

    cd.className='dot busy';const te=addTyping();let rp;try{rp=await callModel()}catch(e){te.remove();throw e}te.remove();

    const tc=extractToolCalls(rp);addMsgWithTools(rp,tc);

    if(!tc.length){chatHistory.push({role:'assistant',content:rp});cd.className='dot on';break}

    chatHistory.push({role:'assistant',content:rp});let rm='Tool results:\n\n';

    for(const t of tc){const res=await execTool(t.n,t.c);t('← '+t.n+': '+res.slice(0,80)+(res.length>80?'...':''),'ok');rm+=`\`\`\`${t.n}\n${res}\n\`\`\`\n\n`;updateLastToolRes(t.n,res)}

    chatHistory.push({role:'user',content:rm});

  }}catch(e){addMsg('agent','Error: '+e.message);t('Error: '+e.message,'err');cd.className='dot err'}

  sending=false;document.getElementById('sendBtn').disabled=false;

}



function addMsg(role,text){const ch=document.getElementById('chat'),d=document.createElement('div');d.className='msg '+role;d.innerHTML=`<div class="r">${role==='user'?'You':'Macro VM'}</div><div class="c">${rMd(text)}</div>`;ch.appendChild(d);ch.scrollTop=ch.scrollHeight}

function addMsgWithTools(text,tc){const ch=document.getElementById('chat'),d=document.createElement('div');d.className='msg agent';let dt=text.replace(/```(python|search|terminal)\n[\s\S]*?```/g,'').trim();let h=dt?rMd(dt):'';for(const t of tc)h+=`<div class="tc" data-tool="${t.n}"><div class="th"><span class="sp"></span> ${t.n}</div><div class="td">${esc(t.c)}</div></div>`;d.innerHTML=`<div class="r">Macro VM</div><div class="c">${h}</div>`;ch.appendChild(d);ch.scrollTop=ch.scrollHeight}

function updateLastToolRes(n,r){const c=document.querySelectorAll('.tc[data-tool="'+n+'"]');const l=c[c.length-1];if(!l)return;l.querySelector('.th').innerHTML='✓ '+n;const d=document.createElement('div');d.className='tr'+(r.startsWith('Error')?' er':'');d.textContent=r.slice(0,500)+(r.length>500?'\n...':'');l.appendChild(d)}

function rMd(t){let h=t.replace(/```(\w*)\n([\s\S]*?)```/g,'<pre><code>$2</code></pre>');h=h.replace(/`([^`]+)`/g,'<code>$1</code>');h=h.replace(/\n/g,'<br>');return h}

function esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}

function addTyping(){const ch=document.getElementById('chat'),d=document.createElement('div');d.className='msg agent';d.innerHTML='<div class="r">Macro VM</div><div class="typing"><span></span><span></span><span></span></div>';ch.appendChild(d);ch.scrollTop=ch.scrollHeight;return d}

function quickAsk(t){document.getElementById('askInput').value=t;send()}

function t(text,cls){const b=document.getElementById('termBody'),l=document.createElement('div');l.className='tl'+(cls?' '+cls:'');l.textContent=text;b.appendChild(l);b.scrollTop=b.scrollHeight}

function toggleTerm(){document.getElementById('termPanel').classList.toggle('open')}

document.getElementById('askInput')?.addEventListener('input',function(){this.style.height='auto';this.style.height=Math.min(this.scrollHeight,140)+'px'});

</script>
</body>
</html>