tina4ruby 3.11.5 → 3.11.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- (function(){"use strict";var Je;const at="/__dev/api";async function z(e,t="GET",n){const i={method:t,headers:{}};return n&&(i.headers["Content-Type"]="application/json",i.body=JSON.stringify(n)),(await fetch(at+e,i)).json()}function a(e){const t=document.createElement("span");return t.textContent=e,t.innerHTML}const je={python:{color:"#3b82f6",name:"Python"},php:{color:"#8b5cf6",name:"PHP"},ruby:{color:"#ef4444",name:"Ruby"},nodejs:{color:"#22c55e",name:"Node.js"}};function rt(){const e=document.getElementById("app"),t=(e==null?void 0:e.dataset.framework)??"python",n=e==null?void 0:e.dataset.color,i=je[t]??je.python;return{framework:t,color:n??i.color,name:i.name}}function lt(e){const t=document.documentElement;t.style.setProperty("--primary",e.color),t.style.setProperty("--bg","#0f172a"),t.style.setProperty("--surface","#1e293b"),t.style.setProperty("--border","#334155"),t.style.setProperty("--text","#e2e8f0"),t.style.setProperty("--muted","#94a3b8"),t.style.setProperty("--success","#22c55e"),t.style.setProperty("--danger","#ef4444"),t.style.setProperty("--warn","#f59e0b"),t.style.setProperty("--info","#3b82f6")}const dt=`
1
+ (function(){"use strict";var Xe;const Ae={python:{color:"#3b82f6",name:"Python"},php:{color:"#8b5cf6",name:"PHP"},ruby:{color:"#ef4444",name:"Ruby"},nodejs:{color:"#22c55e",name:"Node.js"}};function ct(){const e=document.getElementById("app"),t=(e==null?void 0:e.dataset.framework)??"python",n=e==null?void 0:e.dataset.color,i=Ae[t]??Ae.python;return{framework:t,color:n??i.color,name:i.name}}function mt(e){const t=document.documentElement;t.style.setProperty("--primary",e.color),t.style.setProperty("--bg","#0f172a"),t.style.setProperty("--surface","#1e293b"),t.style.setProperty("--border","#334155"),t.style.setProperty("--text","#e2e8f0"),t.style.setProperty("--muted","#94a3b8"),t.style.setProperty("--success","#22c55e"),t.style.setProperty("--danger","#ef4444"),t.style.setProperty("--warn","#f59e0b"),t.style.setProperty("--info","#3b82f6")}const ut=`
2
2
  * { margin: 0; padding: 0; box-sizing: border-box; }
3
3
  body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); }
4
4
 
@@ -67,16 +67,16 @@ tr:hover { background: rgba(255,255,255,0.03); }
67
67
 
68
68
  .chat-container { display: flex; flex-direction: column; height: calc(100vh - 140px); }
69
69
  .chat-messages { flex: 1; overflow-y: auto; padding: 0.75rem; }
70
- .chat-msg { padding: 0.5rem 0.75rem; border-radius: 0.5rem; margin-bottom: 0.5rem; font-size: 0.85rem; line-height: 1.5; max-width: 85%; }
71
- .chat-user { background: var(--primary); color: white; margin-left: auto; }
72
- .chat-bot { background: var(--surface); border: 1px solid var(--border); }
70
+ .chat-msg { padding: 0.5rem 0.75rem; border-radius: 0.5rem; margin-bottom: 0.25rem; font-size: 0.85rem; line-height: 1.5; max-width: 85%; }
71
+ .chat-user { background: var(--primary); color: white; margin-left: auto; font-size: 0.8rem; padding: 0.35rem 0.65rem; max-width: 60%; border-radius: 0.5rem 0.5rem 0.15rem 0.5rem; }
72
+ .chat-bot { background: var(--surface); border: 1px solid var(--border); margin-bottom: 0.15rem; }
73
73
  .chat-input-row { display: flex; gap: 0.5rem; padding: 0.75rem; border-top: 1px solid var(--border); }
74
74
  .chat-input-row input { flex: 1; }
75
75
 
76
76
  .error-trace { background: var(--bg); border: 1px solid var(--border); border-radius: 0.375rem; padding: 0.5rem; font-family: monospace; font-size: 0.75rem; white-space: pre-wrap; max-height: 200px; overflow-y: auto; margin-top: 0.5rem; }
77
77
 
78
78
  .bubble-chart { width: 100%; height: 400px; background: var(--surface); border: 1px solid var(--border); border-radius: 0.5rem; overflow: hidden; }
79
- `;function ct(e){e.innerHTML=`
79
+ `,pt="/__dev/api";async function z(e,t="GET",n){const i={method:t,headers:{}};return n&&(i.headers["Content-Type"]="application/json",i.body=JSON.stringify(n)),(await fetch(pt+e,i)).json()}function l(e){const t=document.createElement("span");return t.textContent=e,t.innerHTML}function gt(e){e.innerHTML=`
80
80
  <div class="dev-panel-header">
81
81
  <h2>Routes <span id="routes-count" class="text-muted text-sm"></span></h2>
82
82
  <button class="btn btn-sm" onclick="window.__loadRoutes()">Refresh</button>
@@ -85,14 +85,14 @@ tr:hover { background: rgba(255,255,255,0.03); }
85
85
  <thead><tr><th>Method</th><th>Path</th><th>Auth</th><th>Handler</th></tr></thead>
86
86
  <tbody id="routes-body"></tbody>
87
87
  </table>
88
- `,He()}async function He(){const e=await z("/routes"),t=document.getElementById("routes-count");t&&(t.textContent=`(${e.count})`);const n=document.getElementById("routes-body");n&&(n.innerHTML=(e.routes||[]).map(i=>`
88
+ `,Re()}async function Re(){const e=await z("/routes"),t=document.getElementById("routes-count");t&&(t.textContent=`(${e.count})`);const n=document.getElementById("routes-body");n&&(n.innerHTML=(e.routes||[]).map(i=>`
89
89
  <tr>
90
- <td><span class="method method-${i.method.toLowerCase()}">${a(i.method)}</span></td>
91
- <td class="text-mono"><a href="${a(i.path)}" target="_blank" style="color:inherit;text-decoration:underline dotted">${a(i.path)}</a></td>
90
+ <td><span class="method method-${i.method.toLowerCase()}">${l(i.method)}</span></td>
91
+ <td class="text-mono"><a href="${l(i.path)}" target="_blank" style="color:inherit;text-decoration:underline dotted">${l(i.path)}</a></td>
92
92
  <td>${i.auth_required?'<span class="badge badge-warn">auth</span>':'<span class="badge badge-success">open</span>'}</td>
93
- <td class="text-sm text-muted">${a(i.handler||"")} <small>(${a(i.module||"")})</small></td>
93
+ <td class="text-sm text-muted">${l(i.handler||"")} <small>(${l(i.module||"")})</small></td>
94
94
  </tr>
95
- `).join(""))}window.__loadRoutes=He;let W=[],G=[],H=JSON.parse(localStorage.getItem("tina4_query_history")||"[]");function mt(e){e.innerHTML=`
95
+ `).join(""))}window.__loadRoutes=Re;let J=[],W=[],O=JSON.parse(localStorage.getItem("tina4_query_history")||"[]");function bt(e){e.innerHTML=`
96
96
  <div class="dev-panel-header">
97
97
  <h2>Database</h2>
98
98
  <button class="btn btn-sm" onclick="window.__loadTables()">Refresh</button>
@@ -159,11 +159,11 @@ tr:hover { background: rgba(255,255,255,0.03); }
159
159
  </div>
160
160
  </div>
161
161
  </div>
162
- `,ge(),he()}async function ge(){const t=(await z("/tables")).tables||[],n=document.getElementById("db-table-list");n&&(n.innerHTML=t.length?t.map(s=>`<div style="padding:0.3rem 0.5rem;cursor:pointer;border-radius:0.25rem;font-size:0.8rem;font-family:monospace" class="db-table-item" onclick="window.__selectTable('${a(s)}')" onmouseover="this.style.background='var(--border)'" onmouseout="this.style.background=''">${a(s)}</div>`).join(""):'<div class="text-sm text-muted">No tables</div>');const i=document.getElementById("db-seed-table");i&&(i.innerHTML='<option value="">Pick table...</option>'+t.map(s=>`<option value="${a(s)}">${a(s)}</option>`).join(""));const o=document.getElementById("paste-table");o&&(o.innerHTML='<option value="">Select table...</option>'+t.map(s=>`<option value="${a(s)}">${a(s)}</option>`).join(""))}function be(e){var n;(n=document.getElementById("db-limit"))!=null&&n.value;const t=document.getElementById("db-query");t&&(t.value=`SELECT * FROM ${e}`),document.querySelectorAll(".db-table-item").forEach(i=>{i.style.background=i.textContent===e?"var(--border)":""}),Pe()}function ut(){var n;const e=document.getElementById("db-query"),t=((n=document.getElementById("db-limit"))==null?void 0:n.value)||"20";e!=null&&e.value&&(e.value=e.value.replace(/LIMIT\s+\d+/i,`LIMIT ${t}`))}function pt(e){const t=e.trim();t&&(H=H.filter(n=>n!==t),H.unshift(t),H.length>50&&(H=H.slice(0,50)),localStorage.setItem("tina4_query_history",JSON.stringify(H)),he())}function he(){const e=document.getElementById("db-history");e&&(e.innerHTML='<option value="">Query history...</option>'+H.map((t,n)=>`<option value="${n}">${a(t.length>80?t.substring(0,80)+"...":t)}</option>`).join(""))}function gt(e){const t=parseInt(e);if(isNaN(t)||!H[t])return;const n=document.getElementById("db-query");n&&(n.value=H[t]),document.getElementById("db-history").selectedIndex=0}function bt(){H=[],localStorage.removeItem("tina4_query_history"),he()}async function Pe(){var o,s,c;const e=document.getElementById("db-query"),t=(o=e==null?void 0:e.value)==null?void 0:o.trim();if(!t)return;pt(t);const n=document.getElementById("db-result"),i=((s=document.getElementById("db-type"))==null?void 0:s.value)||"sql";n&&(n.innerHTML='<p class="text-muted">Running...</p>');try{const d=parseInt(((c=document.getElementById("db-limit"))==null?void 0:c.value)||"20"),p=await z("/query","POST",{query:t,type:i,limit:d});if(p.error){n&&(n.innerHTML=`<p style="color:var(--danger)">${a(p.error)}</p>`);return}p.rows&&p.rows.length>0?(G=Object.keys(p.rows[0]),W=p.rows,n&&(n.innerHTML=`<p class="text-sm text-muted" style="margin-bottom:0.5rem">${p.count??p.rows.length} rows</p>
163
- <div style="overflow-x:auto"><table><thead><tr>${G.map(u=>`<th>${a(u)}</th>`).join("")}</tr></thead>
164
- <tbody>${p.rows.map(u=>`<tr>${G.map(_=>`<td class="text-sm">${a(String(u[_]??""))}</td>`).join("")}</tr>`).join("")}</tbody></table></div>`)):p.affected!==void 0?(n&&(n.innerHTML=`<p class="text-muted">${p.affected} rows affected. ${p.success?"Success.":""}</p>`),W=[],G=[]):(n&&(n.innerHTML='<p class="text-muted">No results</p>'),W=[],G=[])}catch(d){n&&(n.innerHTML=`<p style="color:var(--danger)">${a(d.message)}</p>`)}}function ht(){if(!W.length)return;const e=G.join(","),t=W.map(n=>G.map(i=>{const o=String(n[i]??"");return o.includes(",")||o.includes('"')?`"${o.replace(/"/g,'""')}"`:o}).join(","));navigator.clipboard.writeText([e,...t].join(`
165
- `))}function ft(){W.length&&navigator.clipboard.writeText(JSON.stringify(W,null,2))}function yt(){const e=document.getElementById("db-paste-modal");e&&(e.style.display="flex")}function qe(){const e=document.getElementById("db-paste-modal");e&&(e.style.display="none")}async function vt(){var o,s,c,d,p;const e=(o=document.getElementById("paste-table"))==null?void 0:o.value,t=(c=(s=document.getElementById("paste-new-table"))==null?void 0:s.value)==null?void 0:c.trim(),n=t||e,i=(p=(d=document.getElementById("paste-data"))==null?void 0:d.value)==null?void 0:p.trim();if(!n||!i){alert("Select a table or enter a new table name, and paste data.");return}try{let u;try{u=JSON.parse(i),Array.isArray(u)||(u=[u])}catch{const k=i.split(`
166
- `).map(E=>E.trim()).filter(Boolean);if(k.length<2){alert("CSV needs at least a header row and one data row.");return}const h=k[0].split(",").map(E=>E.trim().replace(/[^a-zA-Z0-9_]/g,""));u=k.slice(1).map(E=>{const T=E.split(",").map(j=>j.trim()),v={};return h.forEach((j,ee)=>{v[j]=T[ee]??""}),v})}if(!u.length){alert("No data rows found.");return}if(t){const h=["id INTEGER PRIMARY KEY AUTOINCREMENT",...Object.keys(u[0]).filter(T=>T.toLowerCase()!=="id").map(T=>`"${T}" TEXT`)],E=await z("/query","POST",{query:`CREATE TABLE IF NOT EXISTS "${t}" (${h.join(", ")})`,type:"sql"});if(E.error){alert("Create table failed: "+E.error);return}}let _=0;for(const k of u){const h=t?Object.keys(k).filter(j=>j.toLowerCase()!=="id"):Object.keys(k),E=h.map(j=>`"${j}"`).join(","),T=h.map(j=>`'${String(k[j]).replace(/'/g,"''")}'`).join(","),v=await z("/query","POST",{query:`INSERT INTO "${n}" (${E}) VALUES (${T})`,type:"sql"});if(v.error){alert(`Row ${_+1} failed: ${v.error}`);break}_++}document.getElementById("paste-data").value="",document.getElementById("paste-new-table").value="",document.getElementById("paste-table").selectedIndex=0,qe(),ge(),_>0&&be(n)}catch(u){alert("Import error: "+u.message)}}async function xt(){var n,i;const e=(n=document.getElementById("db-seed-table"))==null?void 0:n.value,t=parseInt(((i=document.getElementById("db-seed-count"))==null?void 0:i.value)||"10");if(e)try{const o=await z("/seed","POST",{table:e,count:t});o.error?alert(o.error):be(e)}catch(o){alert("Seed error: "+o.message)}}window.__loadTables=ge,window.__selectTable=be,window.__updateLimit=ut,window.__runQuery=Pe,window.__copyCSV=ht,window.__copyJSON=ft,window.__showPaste=yt,window.__hidePaste=qe,window.__doPaste=vt,window.__seedTable=xt,window.__loadHistory=gt,window.__clearHistory=bt;function wt(e){e.innerHTML=`
162
+ `,he(),fe()}async function he(){const t=(await z("/tables")).tables||[],n=document.getElementById("db-table-list");n&&(n.innerHTML=t.length?t.map(s=>`<div style="padding:0.3rem 0.5rem;cursor:pointer;border-radius:0.25rem;font-size:0.8rem;font-family:monospace" class="db-table-item" onclick="window.__selectTable('${l(s)}')" onmouseover="this.style.background='var(--border)'" onmouseout="this.style.background=''">${l(s)}</div>`).join(""):'<div class="text-sm text-muted">No tables</div>');const i=document.getElementById("db-seed-table");i&&(i.innerHTML='<option value="">Pick table...</option>'+t.map(s=>`<option value="${l(s)}">${l(s)}</option>`).join(""));const o=document.getElementById("paste-table");o&&(o.innerHTML='<option value="">Select table...</option>'+t.map(s=>`<option value="${l(s)}">${l(s)}</option>`).join(""))}function ye(e){var n;(n=document.getElementById("db-limit"))!=null&&n.value;const t=document.getElementById("db-query");t&&(t.value=`SELECT * FROM ${e}`),document.querySelectorAll(".db-table-item").forEach(i=>{i.style.background=i.textContent===e?"var(--border)":""}),Pe()}function ht(){var n;const e=document.getElementById("db-query"),t=((n=document.getElementById("db-limit"))==null?void 0:n.value)||"20";e!=null&&e.value&&(e.value=e.value.replace(/LIMIT\s+\d+/i,`LIMIT ${t}`))}function yt(e){const t=e.trim();t&&(O=O.filter(n=>n!==t),O.unshift(t),O.length>50&&(O=O.slice(0,50)),localStorage.setItem("tina4_query_history",JSON.stringify(O)),fe())}function fe(){const e=document.getElementById("db-history");e&&(e.innerHTML='<option value="">Query history...</option>'+O.map((t,n)=>`<option value="${n}">${l(t.length>80?t.substring(0,80)+"...":t)}</option>`).join(""))}function ft(e){const t=parseInt(e);if(isNaN(t)||!O[t])return;const n=document.getElementById("db-query");n&&(n.value=O[t]),document.getElementById("db-history").selectedIndex=0}function vt(){O=[],localStorage.removeItem("tina4_query_history"),fe()}async function Pe(){var o,s,c;const e=document.getElementById("db-query"),t=(o=e==null?void 0:e.value)==null?void 0:o.trim();if(!t)return;yt(t);const n=document.getElementById("db-result"),i=((s=document.getElementById("db-type"))==null?void 0:s.value)||"sql";n&&(n.innerHTML='<p class="text-muted">Running...</p>');try{const r=parseInt(((c=document.getElementById("db-limit"))==null?void 0:c.value)||"20"),d=await z("/query","POST",{query:t,type:i,limit:r});if(d.error){n&&(n.innerHTML=`<p style="color:var(--danger)">${l(d.error)}</p>`);return}d.rows&&d.rows.length>0?(W=Object.keys(d.rows[0]),J=d.rows,n&&(n.innerHTML=`<p class="text-sm text-muted" style="margin-bottom:0.5rem">${d.count??d.rows.length} rows</p>
163
+ <div style="overflow-x:auto"><table><thead><tr>${W.map(a=>`<th>${l(a)}</th>`).join("")}</tr></thead>
164
+ <tbody>${d.rows.map(a=>`<tr>${W.map(b=>`<td class="text-sm">${l(String(a[b]??""))}</td>`).join("")}</tr>`).join("")}</tbody></table></div>`)):d.affected!==void 0?(n&&(n.innerHTML=`<p class="text-muted">${d.affected} rows affected. ${d.success?"Success.":""}</p>`),J=[],W=[]):(n&&(n.innerHTML='<p class="text-muted">No results</p>'),J=[],W=[])}catch(r){n&&(n.innerHTML=`<p style="color:var(--danger)">${l(r.message)}</p>`)}}function xt(){if(!J.length)return;const e=W.join(","),t=J.map(n=>W.map(i=>{const o=String(n[i]??"");return o.includes(",")||o.includes('"')?`"${o.replace(/"/g,'""')}"`:o}).join(","));navigator.clipboard.writeText([e,...t].join(`
165
+ `))}function wt(){J.length&&navigator.clipboard.writeText(JSON.stringify(J,null,2))}function _t(){const e=document.getElementById("db-paste-modal");e&&(e.style.display="flex")}function Ne(){const e=document.getElementById("db-paste-modal");e&&(e.style.display="none")}async function kt(){var o,s,c,r,d;const e=(o=document.getElementById("paste-table"))==null?void 0:o.value,t=(c=(s=document.getElementById("paste-new-table"))==null?void 0:s.value)==null?void 0:c.trim(),n=t||e,i=(d=(r=document.getElementById("paste-data"))==null?void 0:r.value)==null?void 0:d.trim();if(!n||!i){alert("Select a table or enter a new table name, and paste data.");return}try{let a;try{a=JSON.parse(i),Array.isArray(a)||(a=[a])}catch{const _=i.split(`
166
+ `).map(E=>E.trim()).filter(Boolean);if(_.length<2){alert("CSV needs at least a header row and one data row.");return}const g=_[0].split(",").map(E=>E.trim().replace(/[^a-zA-Z0-9_]/g,""));a=_.slice(1).map(E=>{const S=E.split(",").map(j=>j.trim()),w={};return g.forEach((j,te)=>{w[j]=S[te]??""}),w})}if(!a.length){alert("No data rows found.");return}if(t){const g=["id INTEGER PRIMARY KEY AUTOINCREMENT",...Object.keys(a[0]).filter(S=>S.toLowerCase()!=="id").map(S=>`"${S}" TEXT`)],E=await z("/query","POST",{query:`CREATE TABLE IF NOT EXISTS "${t}" (${g.join(", ")})`,type:"sql"});if(E.error){alert("Create table failed: "+E.error);return}}let b=0;for(const _ of a){const g=t?Object.keys(_).filter(j=>j.toLowerCase()!=="id"):Object.keys(_),E=g.map(j=>`"${j}"`).join(","),S=g.map(j=>`'${String(_[j]).replace(/'/g,"''")}'`).join(","),w=await z("/query","POST",{query:`INSERT INTO "${n}" (${E}) VALUES (${S})`,type:"sql"});if(w.error){alert(`Row ${b+1} failed: ${w.error}`);break}b++}document.getElementById("paste-data").value="",document.getElementById("paste-new-table").value="",document.getElementById("paste-table").selectedIndex=0,Ne(),he(),b>0&&ye(n)}catch(a){alert("Import error: "+a.message)}}async function $t(){var n,i;const e=(n=document.getElementById("db-seed-table"))==null?void 0:n.value,t=parseInt(((i=document.getElementById("db-seed-count"))==null?void 0:i.value)||"10");if(e)try{const o=await z("/seed","POST",{table:e,count:t});o.error?alert(o.error):ye(e)}catch(o){alert("Seed error: "+o.message)}}window.__loadTables=he,window.__selectTable=ye,window.__updateLimit=ht,window.__runQuery=Pe,window.__copyCSV=xt,window.__copyJSON=wt,window.__showPaste=_t,window.__hidePaste=Ne,window.__doPaste=kt,window.__seedTable=$t,window.__loadHistory=ft,window.__clearHistory=vt;function Et(e){e.innerHTML=`
167
167
  <div class="dev-panel-header">
168
168
  <h2>Errors <span id="errors-count" class="text-muted text-sm"></span></h2>
169
169
  <div class="flex gap-sm">
@@ -172,44 +172,44 @@ tr:hover { background: rgba(255,255,255,0.03); }
172
172
  </div>
173
173
  </div>
174
174
  <div id="errors-body"></div>
175
- `,ie()}async function ie(){const e=await z("/broken"),t=document.getElementById("errors-count"),n=document.getElementById("errors-body");if(!n)return;const i=e.errors||[];if(t&&(t.textContent=`(${i.length})`),!i.length){n.innerHTML='<div class="empty-state">No errors</div>';return}n.innerHTML=i.map((o,s)=>{const c=o.error_type?`${o.error_type}: ${o.message}`:o.error||o.message||"Unknown error",d=o.context||{},p=o.last_seen||o.first_seen||o.timestamp||"",u=p?new Date(p).toLocaleString():"";return`
175
+ `,se()}async function se(){const e=await z("/broken"),t=document.getElementById("errors-count"),n=document.getElementById("errors-body");if(!n)return;const i=e.errors||[];if(t&&(t.textContent=`(${i.length})`),!i.length){n.innerHTML='<div class="empty-state">No errors</div>';return}n.innerHTML=i.map((o,s)=>{const c=o.error_type?`${o.error_type}: ${o.message}`:o.error||o.message||"Unknown error",r=o.context||{},d=o.last_seen||o.first_seen||o.timestamp||"",a=d?new Date(d).toLocaleString():"";return`
176
176
  <div style="background:var(--surface);border:1px solid var(--border);border-radius:0.5rem;padding:0.75rem;margin-bottom:0.75rem">
177
177
  <div class="flex items-center" style="justify-content:space-between;flex-wrap:wrap;gap:0.5rem">
178
178
  <div style="flex:1;min-width:0">
179
179
  <span class="badge ${o.resolved?"badge-success":"badge-danger"}">${o.resolved?"RESOLVED":"UNRESOLVED"}</span>
180
180
  ${o.count>1?`<span class="badge badge-warn" style="margin-left:4px">x${o.count}</span>`:""}
181
- <strong style="margin-left:0.5rem;font-size:0.85rem">${a(c)}</strong>
181
+ <strong style="margin-left:0.5rem;font-size:0.85rem">${l(c)}</strong>
182
182
  </div>
183
183
  <div class="flex gap-sm" style="flex-shrink:0">
184
- ${o.resolved?"":`<button class="btn btn-sm" onclick="window.__resolveError('${a(o.id||String(s))}')">Resolve</button>`}
184
+ ${o.resolved?"":`<button class="btn btn-sm" onclick="window.__resolveError('${l(o.id||String(s))}')">Resolve</button>`}
185
185
  <button class="btn btn-sm btn-primary" onclick="window.__askAboutError(${s})">Ask Tina4</button>
186
186
  </div>
187
187
  </div>
188
- ${d.method?`<div class="text-sm text-mono" style="margin-top:0.5rem;color:var(--info)">${a(d.method)} ${a(d.path||"")}</div>`:""}
189
- ${o.traceback?`<pre style="margin-top:0.5rem;padding:0.5rem;background:var(--bg);border:1px solid var(--border);border-radius:4px;font-size:0.7rem;overflow-x:auto;white-space:pre-wrap;max-height:200px;overflow-y:auto">${a(o.traceback)}</pre>`:""}
190
- <div class="text-sm text-muted" style="margin-top:0.5rem">${a(u)}</div>
188
+ ${r.method?`<div class="text-sm text-mono" style="margin-top:0.5rem;color:var(--info)">${l(r.method)} ${l(r.path||"")}</div>`:""}
189
+ ${o.traceback?`<pre style="margin-top:0.5rem;padding:0.5rem;background:var(--bg);border:1px solid var(--border);border-radius:4px;font-size:0.7rem;overflow-x:auto;white-space:pre-wrap;max-height:200px;overflow-y:auto">${l(o.traceback)}</pre>`:""}
190
+ <div class="text-sm text-muted" style="margin-top:0.5rem">${l(a)}</div>
191
191
  </div>
192
- `}).join(""),window.__errorData=i}async function $t(e){await z("/broken/resolve","POST",{id:e}),ie()}async function _t(){await z("/broken/clear","POST"),ie()}function kt(e){const n=(window.__errorData||[])[e];if(!n)return;const i=n.error_type?`${n.error_type}: ${n.message}`:n.error||n.message||"Unknown error",o=n.context||{},s=o.method&&o.path?`
192
+ `}).join(""),window.__errorData=i}async function St(e){await z("/broken/resolve","POST",{id:e}),se()}async function Tt(){await z("/broken/clear","POST"),se()}function It(e){const n=(window.__errorData||[])[e];if(!n)return;const i=n.error_type?`${n.error_type}: ${n.message}`:n.error||n.message||"Unknown error",o=n.context||{},s=o.method&&o.path?`
193
193
  Route: ${o.method} ${o.path}`:"",c=`I have this error: ${i}${s}
194
194
 
195
- ${n.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillChat(c)},150)}window.__loadErrors=ie,window.__clearErrors=_t,window.__resolveError=$t,window.__askAboutError=kt;function Et(e){e.innerHTML=`
195
+ ${n.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillChat(c)},150)}window.__loadErrors=se,window.__clearErrors=Tt,window.__resolveError=St,window.__askAboutError=It;function Mt(e){e.innerHTML=`
196
196
  <div class="dev-panel-header">
197
197
  <h2>System</h2>
198
198
  </div>
199
199
  <div id="system-grid" class="metric-grid"></div>
200
200
  <div id="system-env" style="margin-top:1rem"></div>
201
- `,Oe()}function Tt(e){if(!e||e<0)return"?";const t=Math.floor(e/86400),n=Math.floor(e%86400/3600),i=Math.floor(e%3600/60),o=Math.floor(e%60),s=[];return t>0&&s.push(`${t}d`),n>0&&s.push(`${n}h`),i>0&&s.push(`${i}m`),s.length===0&&s.push(`${o}s`),s.join(" ")}function St(e){return e?e>=1024?`${(e/1024).toFixed(1)} GB`:`${e.toFixed(1)} MB`:"?"}async function Oe(){const e=await z("/system"),t=document.getElementById("system-grid"),n=document.getElementById("system-env");if(!t)return;const o=(e.python_version||e.php_version||e.ruby_version||e.node_version||e.runtime||"?").split("(")[0].trim(),s=[{label:"Framework",value:e.framework||"Tina4"},{label:"Runtime",value:o},{label:"Platform",value:e.platform||"?"},{label:"Architecture",value:e.architecture||"?"},{label:"PID",value:String(e.pid??"?")},{label:"Uptime",value:Tt(e.uptime_seconds)},{label:"Memory",value:St(e.memory_mb)},{label:"Database",value:e.database||"none"},{label:"DB Tables",value:String(e.db_tables??"?")},{label:"DB Connected",value:e.db_connected?"Yes":"No"},{label:"Debug",value:e.debug==="true"||e.debug===!0?"ON":"OFF"},{label:"Log Level",value:e.log_level||"?"},{label:"Modules",value:String(e.loaded_modules??"?")},{label:"Working Dir",value:e.cwd||"?"}],c=new Set(["Working Dir","Database"]);if(t.innerHTML=s.map(d=>`
202
- <div class="metric-card" style="${c.has(d.label)?"grid-column:1/-1":""}">
203
- <div class="label">${a(d.label)}</div>
204
- <div class="value" style="font-size:${c.has(d.label)?"0.75rem":"1.1rem"}">${a(d.value)}</div>
201
+ `,De()}function Ct(e){if(!e||e<0)return"?";const t=Math.floor(e/86400),n=Math.floor(e%86400/3600),i=Math.floor(e%3600/60),o=Math.floor(e%60),s=[];return t>0&&s.push(`${t}d`),n>0&&s.push(`${n}h`),i>0&&s.push(`${i}m`),s.length===0&&s.push(`${o}s`),s.join(" ")}function Lt(e){return e?e>=1024?`${(e/1024).toFixed(1)} GB`:`${e.toFixed(1)} MB`:"?"}async function De(){const e=await z("/system"),t=document.getElementById("system-grid"),n=document.getElementById("system-env");if(!t)return;const o=(e.python_version||e.php_version||e.ruby_version||e.node_version||e.runtime||"?").split("(")[0].trim(),s=[{label:"Framework",value:e.framework||"Tina4"},{label:"Runtime",value:o},{label:"Platform",value:e.platform||"?"},{label:"Architecture",value:e.architecture||"?"},{label:"PID",value:String(e.pid??"?")},{label:"Uptime",value:Ct(e.uptime_seconds)},{label:"Memory",value:Lt(e.memory_mb)},{label:"Database",value:e.database||"none"},{label:"DB Tables",value:String(e.db_tables??"?")},{label:"DB Connected",value:e.db_connected?"Yes":"No"},{label:"Debug",value:e.debug==="true"||e.debug===!0?"ON":"OFF"},{label:"Log Level",value:e.log_level||"?"},{label:"Modules",value:String(e.loaded_modules??"?")},{label:"Working Dir",value:e.cwd||"?"}],c=new Set(["Working Dir","Database"]);if(t.innerHTML=s.map(r=>`
202
+ <div class="metric-card" style="${c.has(r.label)?"grid-column:1/-1":""}">
203
+ <div class="label">${l(r.label)}</div>
204
+ <div class="value" style="font-size:${c.has(r.label)?"0.75rem":"1.1rem"}">${l(r.value)}</div>
205
205
  </div>
206
- `).join(""),n){const d=[];e.debug!==void 0&&d.push(["TINA4_DEBUG",String(e.debug)]),e.log_level&&d.push(["LOG_LEVEL",e.log_level]),e.database&&d.push(["DATABASE_URL",e.database]),d.length&&(n.innerHTML=`
206
+ `).join(""),n){const r=[];e.debug!==void 0&&r.push(["TINA4_DEBUG",String(e.debug)]),e.log_level&&r.push(["LOG_LEVEL",e.log_level]),e.database&&r.push(["DATABASE_URL",e.database]),r.length&&(n.innerHTML=`
207
207
  <h3 style="font-size:0.85rem;margin-bottom:0.5rem">Environment</h3>
208
208
  <table>
209
209
  <thead><tr><th>Variable</th><th>Value</th></tr></thead>
210
- <tbody>${d.map(([p,u])=>`<tr><td class="text-mono text-sm" style="padding:4px 8px">${a(p)}</td><td class="text-sm" style="padding:4px 8px">${a(u)}</td></tr>`).join("")}</tbody>
210
+ <tbody>${r.map(([d,a])=>`<tr><td class="text-mono text-sm" style="padding:4px 8px">${l(d)}</td><td class="text-sm" style="padding:4px 8px">${l(a)}</td></tr>`).join("")}</tbody>
211
211
  </table>
212
- `)}}window.__loadSystem=Oe;function It(e){e.innerHTML=`
212
+ `)}}window.__loadSystem=De;function Bt(e){e.innerHTML=`
213
213
  <div class="dev-panel-header">
214
214
  <h2>Code Metrics</h2>
215
215
  </div>
@@ -218,28 +218,28 @@ ${n.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
218
218
  <div id="metrics-chart" style="display:none;margin:1rem 0"></div>
219
219
  <div id="metrics-detail" style="margin-top:1rem"></div>
220
220
  <div id="metrics-complex" style="margin-top:1rem"></div>
221
- `,Mt()}async function Mt(){var s;const e=document.getElementById("metrics-chart"),t=document.getElementById("metrics-complex"),n=document.getElementById("metrics-scan-info");e&&(e.style.display="block",e.innerHTML='<p class="text-muted">Analyzing...</p>');const i=await z("/metrics/full");if(i.error||!i.file_metrics){e&&(e.innerHTML=`<p style="color:var(--danger)">${a(i.error||"No data")}</p>`);return}if(n){const c=i.scan_mode==="framework"?'<span style="color:#cba6f7;font-weight:600">(Framework)</span> Add code to src/ to see your project':"";n.innerHTML=`${i.files_analyzed} files analyzed | ${i.total_functions} functions ${c}`}const o=document.getElementById("metrics-quick");o&&(o.innerHTML=[N("Files Analyzed",i.files_analyzed),N("Total Functions",i.total_functions),N("Avg Complexity",i.avg_complexity),N("Avg Maintainability",i.avg_maintainability)].join("")),e&&i.file_metrics.length>0?Lt(i.file_metrics,e,i.dependency_graph||{},i.scan_mode||"project"):e&&(e.innerHTML='<p class="text-muted">No files to visualize</p>'),t&&((s=i.most_complex_functions)!=null&&s.length)&&(t.innerHTML=`
221
+ `,qt()}async function qt(){var s;const e=document.getElementById("metrics-chart"),t=document.getElementById("metrics-complex"),n=document.getElementById("metrics-scan-info");e&&(e.style.display="block",e.innerHTML='<p class="text-muted">Analyzing...</p>');const i=await z("/metrics/full");if(i.error||!i.file_metrics){e&&(e.innerHTML=`<p style="color:var(--danger)">${l(i.error||"No data")}</p>`);return}if(n){const c=i.scan_mode==="framework"?'<span style="color:#cba6f7;font-weight:600">(Framework)</span> Add code to src/ to see your project':"";n.innerHTML=`${i.files_analyzed} files analyzed | ${i.total_functions} functions ${c}`}const o=document.getElementById("metrics-quick");o&&(o.innerHTML=[N("Files Analyzed",i.files_analyzed),N("Total Functions",i.total_functions),N("Avg Complexity",i.avg_complexity),N("Avg Maintainability",i.avg_maintainability)].join("")),e&&i.file_metrics.length>0?zt(i.file_metrics,e,i.dependency_graph||{},i.scan_mode||"project"):e&&(e.innerHTML='<p class="text-muted">No files to visualize</p>'),t&&((s=i.most_complex_functions)!=null&&s.length)&&(t.innerHTML=`
222
222
  <h3 style="font-size:0.85rem;margin-bottom:0.5rem">Most Complex Functions</h3>
223
223
  <table>
224
224
  <thead><tr><th>Function</th><th>File</th><th>Line</th><th>CC</th><th>LOC</th></tr></thead>
225
225
  <tbody>${i.most_complex_functions.slice(0,15).map(c=>`
226
226
  <tr>
227
- <td class="text-mono">${a(c.name)}</td>
228
- <td class="text-sm text-muted" style="cursor:pointer;text-decoration:underline dotted" onclick="window.__drillDown('${a(c.file)}')">${a(c.file)}</td>
227
+ <td class="text-mono">${l(c.name)}</td>
228
+ <td class="text-sm text-muted" style="cursor:pointer;text-decoration:underline dotted" onclick="window.__drillDown('${l(c.file)}')">${l(c.file)}</td>
229
229
  <td>${c.line}</td>
230
230
  <td><span class="${c.complexity>10?"badge badge-danger":c.complexity>5?"badge badge-warn":"badge badge-success"}">${c.complexity}</span></td>
231
231
  <td>${c.loc}</td>
232
232
  </tr>`).join("")}
233
233
  </tbody>
234
234
  </table>
235
- `)}function Lt(e,t,n,i){var ot,it,st;const o=t.offsetWidth||900,s=Math.max(450,Math.min(650,o*.45)),c=Math.max(...e.map(f=>f.loc))||1,d=Math.max(...e.map(f=>f.dep_count||0))||1,p=14,u=Math.min(70,o/10);function _(f){const g=Math.min((f.avg_complexity||0)/10,1),y=f.has_tests?0:1,w=Math.min((f.dep_count||0)/5,1),m=g*.4+y*.4+w*.2,r=Math.max(0,Math.min(1,m)),b=Math.round(120*(1-r)),x=Math.round(70+r*30),$=Math.round(42+18*(1-r));return`hsl(${b},${x}%,${$}%)`}function k(f){return f.loc/c*.4+(f.avg_complexity||0)/10*.4+(f.dep_count||0)/d*.2}const h=[...e].sort((f,g)=>k(f)-k(g)),E=o/2,T=s/2,v=[];let j=0,ee=0;for(const f of h){const g=p+Math.sqrt(k(f))*(u-p),y=_(f);let w=!1;for(let m=0;m<800;m++){const r=E+ee*Math.cos(j),b=T+ee*Math.sin(j);let x=!1;for(const $ of v){const L=r-$.x,B=b-$.y;if(Math.sqrt(L*L+B*B)<g+$.r+2){x=!0;break}}if(!x&&r>g+2&&r<o-g-2&&b>g+25&&b<s-g-2){v.push({x:r,y:b,vx:0,vy:0,r:g,color:y,f}),w=!0;break}j+=.2,ee+=.04}w||v.push({x:E+(Math.random()-.5)*o*.3,y:T+(Math.random()-.5)*s*.3,vx:0,vy:0,r:g,color:y,f})}const Be=[];function Ye(f){const g=f.split("/").pop()||"",y=g.lastIndexOf(".");return(y>0?g.substring(0,y):g).toLowerCase()}const ze={};v.forEach((f,g)=>{ze[Ye(f.f.path)]=g});for(const[f,g]of Object.entries(n)){let y=null;if(v.forEach((w,m)=>{w.f.path===f&&(y=m)}),y!==null)for(const w of g){const m=w.replace(/^\.\//,"").replace(/^\.\.\//,"").split(/[./]/);let r;for(let b=m.length-1;b>=0;b--){const x=m[b].toLowerCase();if(x&&x!=="js"&&x!=="py"&&x!=="rb"&&x!=="ts"&&x!=="index"&&(r=ze[x],r!==void 0))break}r===void 0&&(r=ze[Ye(w)]),r!==void 0&&y!==r&&Be.push([y,r])}}const S=document.createElement("canvas");S.width=o,S.height=s,S.style.cssText="display:block;border:1px solid var(--border);border-radius:8px;cursor:pointer;background:#0f172a";const en=i==="framework"?'<span style="color:#cba6f7;font-weight:600">(Framework)</span> Add code to src/ to see your project':"";t.innerHTML=`<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.5rem"><h3 style="margin:0;font-size:0.85rem">Code Landscape ${en}</h3><span style="font-size:0.65rem;color:var(--muted)">Drag bubbles | Dbl-click to drill down</span></div><div style="position:relative" id="metrics-canvas-wrap"></div>`,document.getElementById("metrics-canvas-wrap").appendChild(S);const Ae=document.createElement("div");Ae.style.cssText="position:absolute;top:8px;left:8px;z-index:2;display:flex;gap:4px;flex-direction:column",Ae.innerHTML=`
235
+ `)}function zt(e,t,n,i){var at,lt,dt;const o=t.offsetWidth||900,s=Math.max(450,Math.min(650,o*.45)),c=Math.max(...e.map(f=>f.loc))||1,r=Math.max(...e.map(f=>f.dep_count||0))||1,d=14,a=Math.min(70,o/10);function b(f){const h=Math.min((f.avg_complexity||0)/10,1),v=f.has_tests?0:1,k=Math.min((f.dep_count||0)/5,1),p=h*.4+v*.4+k*.2,m=Math.max(0,Math.min(1,p)),y=Math.round(120*(1-m)),x=Math.round(70+m*30),$=Math.round(42+18*(1-m));return`hsl(${y},${x}%,${$}%)`}function _(f){return f.loc/c*.4+(f.avg_complexity||0)/10*.4+(f.dep_count||0)/r*.2}const g=[...e].sort((f,h)=>_(f)-_(h)),E=o/2,S=s/2,w=[];let j=0,te=0;for(const f of g){const h=d+Math.sqrt(_(f))*(a-d),v=b(f);let k=!1;for(let p=0;p<800;p++){const m=E+te*Math.cos(j),y=S+te*Math.sin(j);let x=!1;for(const $ of w){const L=m-$.x,q=y-$.y;if(Math.sqrt(L*L+q*q)<h+$.r+2){x=!0;break}}if(!x&&m>h+2&&m<o-h-2&&y>h+25&&y<s-h-2){w.push({x:m,y,vx:0,vy:0,r:h,color:v,f}),k=!0;break}j+=.2,te+=.04}k||w.push({x:E+(Math.random()-.5)*o*.3,y:S+(Math.random()-.5)*s*.3,vx:0,vy:0,r:h,color:v,f})}const je=[];function Ze(f){const h=f.split("/").pop()||"",v=h.lastIndexOf(".");return(v>0?h.substring(0,v):h).toLowerCase()}const He={};w.forEach((f,h)=>{He[Ze(f.f.path)]=h});for(const[f,h]of Object.entries(n)){let v=null;if(w.forEach((k,p)=>{k.f.path===f&&(v=p)}),v!==null)for(const k of h){const p=k.replace(/^\.\//,"").replace(/^\.\.\//,"").split(/[./]/);let m;for(let y=p.length-1;y>=0;y--){const x=p[y].toLowerCase();if(x&&x!=="js"&&x!=="py"&&x!=="rb"&&x!=="ts"&&x!=="index"&&(m=He[x],m!==void 0))break}m===void 0&&(m=He[Ze(k)]),m!==void 0&&v!==m&&je.push([v,m])}}const I=document.createElement("canvas");I.width=o,I.height=s,I.style.cssText="display:block;border:1px solid var(--border);border-radius:8px;cursor:pointer;background:#0f172a";const pn=i==="framework"?'<span style="color:#cba6f7;font-weight:600">(Framework)</span> Add code to src/ to see your project':"";t.innerHTML=`<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.5rem"><h3 style="margin:0;font-size:0.85rem">Code Landscape ${pn}</h3><span style="font-size:0.65rem;color:var(--muted)">Drag bubbles | Dbl-click to drill down</span></div><div style="position:relative" id="metrics-canvas-wrap"></div>`,document.getElementById("metrics-canvas-wrap").appendChild(I);const Oe=document.createElement("div");Oe.style.cssText="position:absolute;top:8px;left:8px;z-index:2;display:flex;gap:4px;flex-direction:column",Oe.innerHTML=`
236
236
  <button class="btn btn-sm" id="metrics-zoom-in" style="width:28px;height:28px;padding:0;font-size:14px;font-weight:700;line-height:1">+</button>
237
237
  <button class="btn btn-sm" id="metrics-zoom-out" style="width:28px;height:28px;padding:0;font-size:14px;font-weight:700;line-height:1">&minus;</button>
238
238
  <button class="btn btn-sm" id="metrics-zoom-fit" style="width:28px;height:28px;padding:0;font-size:10px;font-weight:700;line-height:1">Fit</button>
239
- `,document.getElementById("metrics-canvas-wrap").appendChild(Ae),(ot=document.getElementById("metrics-zoom-in"))==null||ot.addEventListener("click",()=>{M=Math.min(5,M*1.3)}),(it=document.getElementById("metrics-zoom-out"))==null||it.addEventListener("click",()=>{M=Math.max(.3,M*.7)}),(st=document.getElementById("metrics-zoom-fit"))==null||st.addEventListener("click",()=>{M=1,V=0,J=0});const l=S.getContext("2d");let F=-1,I=-1,Ke=0,Xe=0,V=0,J=0,M=1,te=!1,Qe=0,Ze=0,et=0,tt=0;function tn(){for(let m=0;m<v.length;m++){if(m===I)continue;const r=v[m],b=E-r.x,x=T-r.y,$=.3+r.r/u*.7,L=.008*$*$;r.vx+=b*L,r.vy+=x*L}for(const[m,r]of Be){const b=v[m],x=v[r],$=x.x-b.x,L=x.y-b.y,B=Math.sqrt($*$+L*L)||1,O=b.r+x.r+20,R=(B-O)*.002,ne=$/B*R,oe=L/B*R;m!==I&&(b.vx+=ne,b.vy+=oe),r!==I&&(x.vx-=ne,x.vy-=oe)}for(let m=0;m<v.length;m++)for(let r=m+1;r<v.length;r++){const b=v[m],x=v[r],$=x.x-b.x,L=x.y-b.y,B=Math.sqrt($*$+L*L)||1,O=b.r+x.r+20;if(B<O){const R=40*(O-B)/O,ne=$/B*R,oe=L/B*R;m!==I&&(b.vx-=ne,b.vy-=oe),r!==I&&(x.vx+=ne,x.vy+=oe)}}for(let m=0;m<v.length;m++){if(m===I)continue;const r=v[m];r.vx*=.65,r.vy*=.65;const b=2;r.vx=Math.max(-b,Math.min(b,r.vx)),r.vy=Math.max(-b,Math.min(b,r.vy)),r.x+=r.vx,r.y+=r.vy,r.x=Math.max(r.r+2,Math.min(o-r.r-2,r.x)),r.y=Math.max(r.r+25,Math.min(s-r.r-2,r.y))}}function nt(){var f;tn(),l.clearRect(0,0,o,s),l.save(),l.translate(V,J),l.scale(M,M),l.strokeStyle="rgba(255,255,255,0.03)",l.lineWidth=1/M;for(let g=0;g<o/M;g+=50)l.beginPath(),l.moveTo(g,0),l.lineTo(g,s/M),l.stroke();for(let g=0;g<s/M;g+=50)l.beginPath(),l.moveTo(0,g),l.lineTo(o/M,g),l.stroke();for(const[g,y]of Be){const w=v[g],m=v[y],r=m.x-w.x,b=m.y-w.y,x=Math.sqrt(r*r+b*b)||1,$=F===g||F===y;l.beginPath(),l.moveTo(w.x+r/x*w.r,w.y+b/x*w.r);const L=m.x-r/x*m.r,B=m.y-b/x*m.r;l.lineTo(L,B),l.strokeStyle=$?"rgba(139,180,250,0.9)":"rgba(255,255,255,0.15)",l.lineWidth=$?3:1,l.stroke();const O=$?12:6,R=Math.atan2(b,r);l.beginPath(),l.moveTo(L,B),l.lineTo(L-O*Math.cos(R-.4),B-O*Math.sin(R-.4)),l.lineTo(L-O*Math.cos(R+.4),B-O*Math.sin(R+.4)),l.closePath(),l.fillStyle=l.strokeStyle,l.fill()}for(let g=0;g<v.length;g++){const y=v[g],w=g===F,m=w?y.r+4:y.r;w&&(l.beginPath(),l.arc(y.x,y.y,m+8,0,Math.PI*2),l.fillStyle="rgba(255,255,255,0.08)",l.fill()),l.beginPath(),l.arc(y.x,y.y,m,0,Math.PI*2),l.fillStyle=y.color,l.globalAlpha=w?1:.85,l.fill(),l.globalAlpha=1,l.strokeStyle=w?"rgba(255,255,255,0.6)":"rgba(255,255,255,0.25)",l.lineWidth=w?2.5:1.5,l.stroke();const r=((f=y.f.path.split("/").pop())==null?void 0:f.replace(/\.\w+$/,""))||"?";if(m>16){const $=Math.max(8,Math.min(13,m*.38));l.fillStyle="#fff",l.font=`600 ${$}px monospace`,l.textAlign="center",l.fillText(r,y.x,y.y-2),l.fillStyle="rgba(255,255,255,0.65)",l.font=`${$-1}px monospace`,l.fillText(`${y.f.loc} LOC`,y.x,y.y+$)}const b=Math.max(9,m*.3),x=b*.7;if(m>14&&y.f.dep_count>0){const $=y.y-m+x+3;l.beginPath(),l.arc(y.x,$,x,0,Math.PI*2),l.fillStyle="#ea580c",l.fill(),l.fillStyle="#fff",l.font=`bold ${b}px sans-serif`,l.textAlign="center",l.fillText("D",y.x,$+b*.35)}if(m>14&&y.f.has_tests){const $=y.y+m-x-3;l.beginPath(),l.arc(y.x,$,x,0,Math.PI*2),l.fillStyle="#16a34a",l.fill(),l.fillStyle="#fff",l.font=`bold ${b}px sans-serif`,l.textAlign="center",l.fillText("T",y.x,$+b*.35)}}l.restore(),requestAnimationFrame(nt)}S.addEventListener("mousemove",f=>{const g=S.getBoundingClientRect(),y=(f.clientX-g.left-V)/M,w=(f.clientY-g.top-J)/M;if(te){V=et+(f.clientX-Qe),J=tt+(f.clientY-Ze);return}if(I>=0){pe=!0,v[I].x=y+Ke,v[I].y=w+Xe,v[I].vx=0,v[I].vy=0;return}F=-1;for(let m=v.length-1;m>=0;m--){const r=v[m],b=y-r.x,x=w-r.y;if(Math.sqrt(b*b+x*x)<r.r+4){F=m;break}}S.style.cursor=F>=0?"grab":"default"}),S.addEventListener("mousedown",f=>{const g=S.getBoundingClientRect(),y=(f.clientX-g.left-V)/M,w=(f.clientY-g.top-J)/M;if(f.button===2){te=!0,Qe=f.clientX,Ze=f.clientY,et=V,tt=J,S.style.cursor="move";return}F>=0&&(I=F,Ke=v[I].x-y,Xe=v[I].y-w,pe=!1,S.style.cursor="grabbing")});let pe=!1;S.addEventListener("mouseup",f=>{if(te){te=!1,S.style.cursor="default";return}if(I>=0){pe||fe(v[I].f.path),S.style.cursor="grab",I=-1,pe=!1;return}}),S.addEventListener("mouseleave",()=>{F=-1,I=-1,te=!1}),S.addEventListener("dblclick",f=>{const g=S.getBoundingClientRect(),y=(f.clientX-g.left-V)/M,w=(f.clientY-g.top-J)/M;for(let m=v.length-1;m>=0;m--){const r=v[m],b=y-r.x,x=w-r.y;if(Math.sqrt(b*b+x*x)<r.r+4){fe(r.f.path);break}}}),S.addEventListener("contextmenu",f=>f.preventDefault()),requestAnimationFrame(nt)}async function fe(e){const t=document.getElementById("metrics-detail");if(!t)return;t.innerHTML='<p class="text-muted">Loading file analysis...</p>';const n=await z("/metrics/file?path="+encodeURIComponent(e));if(n.error){t.innerHTML=`<p style="color:var(--danger)">${a(n.error)}</p>`;return}const i=n.functions||[],o=Math.max(1,...i.map(s=>s.complexity));t.innerHTML=`
239
+ `,document.getElementById("metrics-canvas-wrap").appendChild(Oe),(at=document.getElementById("metrics-zoom-in"))==null||at.addEventListener("click",()=>{C=Math.min(5,C*1.3)}),(lt=document.getElementById("metrics-zoom-out"))==null||lt.addEventListener("click",()=>{C=Math.max(.3,C*.7)}),(dt=document.getElementById("metrics-zoom-fit"))==null||dt.addEventListener("click",()=>{C=1,U=0,V=0});const u=I.getContext("2d");let G=-1,M=-1,et=0,tt=0,U=0,V=0,C=1,ne=!1,nt=0,ot=0,it=0,st=0;function gn(){for(let p=0;p<w.length;p++){if(p===M)continue;const m=w[p],y=E-m.x,x=S-m.y,$=.3+m.r/a*.7,L=.008*$*$;m.vx+=y*L,m.vy+=x*L}for(const[p,m]of je){const y=w[p],x=w[m],$=x.x-y.x,L=x.y-y.y,q=Math.sqrt($*$+L*L)||1,R=y.r+x.r+20,P=(q-R)*.002,oe=$/q*P,ie=L/q*P;p!==M&&(y.vx+=oe,y.vy+=ie),m!==M&&(x.vx-=oe,x.vy-=ie)}for(let p=0;p<w.length;p++)for(let m=p+1;m<w.length;m++){const y=w[p],x=w[m],$=x.x-y.x,L=x.y-y.y,q=Math.sqrt($*$+L*L)||1,R=y.r+x.r+20;if(q<R){const P=40*(R-q)/R,oe=$/q*P,ie=L/q*P;p!==M&&(y.vx-=oe,y.vy-=ie),m!==M&&(x.vx+=oe,x.vy+=ie)}}for(let p=0;p<w.length;p++){if(p===M)continue;const m=w[p];m.vx*=.65,m.vy*=.65;const y=2;m.vx=Math.max(-y,Math.min(y,m.vx)),m.vy=Math.max(-y,Math.min(y,m.vy)),m.x+=m.vx,m.y+=m.vy,m.x=Math.max(m.r+2,Math.min(o-m.r-2,m.x)),m.y=Math.max(m.r+25,Math.min(s-m.r-2,m.y))}}function rt(){var f;gn(),u.clearRect(0,0,o,s),u.save(),u.translate(U,V),u.scale(C,C),u.strokeStyle="rgba(255,255,255,0.03)",u.lineWidth=1/C;for(let h=0;h<o/C;h+=50)u.beginPath(),u.moveTo(h,0),u.lineTo(h,s/C),u.stroke();for(let h=0;h<s/C;h+=50)u.beginPath(),u.moveTo(0,h),u.lineTo(o/C,h),u.stroke();for(const[h,v]of je){const k=w[h],p=w[v],m=p.x-k.x,y=p.y-k.y,x=Math.sqrt(m*m+y*y)||1,$=G===h||G===v;u.beginPath(),u.moveTo(k.x+m/x*k.r,k.y+y/x*k.r);const L=p.x-m/x*p.r,q=p.y-y/x*p.r;u.lineTo(L,q),u.strokeStyle=$?"rgba(139,180,250,0.9)":"rgba(255,255,255,0.15)",u.lineWidth=$?3:1,u.stroke();const R=$?12:6,P=Math.atan2(y,m);u.beginPath(),u.moveTo(L,q),u.lineTo(L-R*Math.cos(P-.4),q-R*Math.sin(P-.4)),u.lineTo(L-R*Math.cos(P+.4),q-R*Math.sin(P+.4)),u.closePath(),u.fillStyle=u.strokeStyle,u.fill()}for(let h=0;h<w.length;h++){const v=w[h],k=h===G,p=k?v.r+4:v.r;k&&(u.beginPath(),u.arc(v.x,v.y,p+8,0,Math.PI*2),u.fillStyle="rgba(255,255,255,0.08)",u.fill()),u.beginPath(),u.arc(v.x,v.y,p,0,Math.PI*2),u.fillStyle=v.color,u.globalAlpha=k?1:.85,u.fill(),u.globalAlpha=1,u.strokeStyle=k?"rgba(255,255,255,0.6)":"rgba(255,255,255,0.25)",u.lineWidth=k?2.5:1.5,u.stroke();const m=((f=v.f.path.split("/").pop())==null?void 0:f.replace(/\.\w+$/,""))||"?";if(p>16){const $=Math.max(8,Math.min(13,p*.38));u.fillStyle="#fff",u.font=`600 ${$}px monospace`,u.textAlign="center",u.fillText(m,v.x,v.y-2),u.fillStyle="rgba(255,255,255,0.65)",u.font=`${$-1}px monospace`,u.fillText(`${v.f.loc} LOC`,v.x,v.y+$)}const y=Math.max(9,p*.3),x=y*.7;if(p>14&&v.f.dep_count>0){const $=v.y-p+x+3;u.beginPath(),u.arc(v.x,$,x,0,Math.PI*2),u.fillStyle="#ea580c",u.fill(),u.fillStyle="#fff",u.font=`bold ${y}px sans-serif`,u.textAlign="center",u.fillText("D",v.x,$+y*.35)}if(p>14&&v.f.has_tests){const $=v.y+p-x-3;u.beginPath(),u.arc(v.x,$,x,0,Math.PI*2),u.fillStyle="#16a34a",u.fill(),u.fillStyle="#fff",u.font=`bold ${y}px sans-serif`,u.textAlign="center",u.fillText("T",v.x,$+y*.35)}}u.restore(),requestAnimationFrame(rt)}I.addEventListener("mousemove",f=>{const h=I.getBoundingClientRect(),v=(f.clientX-h.left-U)/C,k=(f.clientY-h.top-V)/C;if(ne){U=it+(f.clientX-nt),V=st+(f.clientY-ot);return}if(M>=0){be=!0,w[M].x=v+et,w[M].y=k+tt,w[M].vx=0,w[M].vy=0;return}G=-1;for(let p=w.length-1;p>=0;p--){const m=w[p],y=v-m.x,x=k-m.y;if(Math.sqrt(y*y+x*x)<m.r+4){G=p;break}}I.style.cursor=G>=0?"grab":"default"}),I.addEventListener("mousedown",f=>{const h=I.getBoundingClientRect(),v=(f.clientX-h.left-U)/C,k=(f.clientY-h.top-V)/C;if(f.button===2){ne=!0,nt=f.clientX,ot=f.clientY,it=U,st=V,I.style.cursor="move";return}G>=0&&(M=G,et=w[M].x-v,tt=w[M].y-k,be=!1,I.style.cursor="grabbing")});let be=!1;I.addEventListener("mouseup",f=>{if(ne){ne=!1,I.style.cursor="default";return}if(M>=0){be||ve(w[M].f.path),I.style.cursor="grab",M=-1,be=!1;return}}),I.addEventListener("mouseleave",()=>{G=-1,M=-1,ne=!1}),I.addEventListener("dblclick",f=>{const h=I.getBoundingClientRect(),v=(f.clientX-h.left-U)/C,k=(f.clientY-h.top-V)/C;for(let p=w.length-1;p>=0;p--){const m=w[p],y=v-m.x,x=k-m.y;if(Math.sqrt(y*y+x*x)<m.r+4){ve(m.f.path);break}}}),I.addEventListener("contextmenu",f=>f.preventDefault()),requestAnimationFrame(rt)}async function ve(e){const t=document.getElementById("metrics-detail");if(!t)return;t.innerHTML='<p class="text-muted">Loading file analysis...</p>';const n=await z("/metrics/file?path="+encodeURIComponent(e));if(n.error){t.innerHTML=`<p style="color:var(--danger)">${l(n.error)}</p>`;return}const i=n.functions||[],o=Math.max(1,...i.map(s=>s.complexity));t.innerHTML=`
240
240
  <div style="background:var(--surface);border:1px solid var(--border);border-radius:0.5rem;padding:1rem">
241
241
  <div class="flex items-center" style="justify-content:space-between;margin-bottom:0.75rem">
242
- <h3 style="font-size:0.9rem">${a(n.path)}</h3>
242
+ <h3 style="font-size:0.9rem">${l(n.path)}</h3>
243
243
  <button class="btn btn-sm" onclick="document.getElementById('metrics-detail').innerHTML=''">Close</button>
244
244
  </div>
245
245
  <div class="metric-grid" style="margin-bottom:0.75rem">
@@ -251,17 +251,58 @@ ${n.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
251
251
  </div>
252
252
  ${i.length?`
253
253
  <h4 style="font-size:0.8rem;color:var(--info);margin-bottom:0.5rem">Cyclomatic Complexity by Function</h4>
254
- ${i.sort((s,c)=>c.complexity-s.complexity).map(s=>{const c=s.complexity/o*100,d=s.complexity>10?"#ef4444":s.complexity>5?"#f59e0b":"#22c55e";return`<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:3px;font-size:0.75rem">
255
- <div style="width:200px;flex-shrink:0;text-align:right;font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${a(s.name)}">${a(s.name)}</div>
256
- <div style="flex:1;height:14px;background:var(--bg);border-radius:2px;overflow:hidden"><div style="width:${c}%;height:100%;background:${d}"></div></div>
257
- <div style="width:180px;flex-shrink:0;font-family:monospace;text-align:right"><span style="color:${d}">CC:${s.complexity}</span> <span style="color:var(--muted)">${s.loc} LOC L${s.line}</span></div>
254
+ ${i.sort((s,c)=>c.complexity-s.complexity).map(s=>{const c=s.complexity/o*100,r=s.complexity>10?"#ef4444":s.complexity>5?"#f59e0b":"#22c55e";return`<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:3px;font-size:0.75rem">
255
+ <div style="width:200px;flex-shrink:0;text-align:right;font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${l(s.name)}">${l(s.name)}</div>
256
+ <div style="flex:1;height:14px;background:var(--bg);border-radius:2px;overflow:hidden"><div style="width:${c}%;height:100%;background:${r}"></div></div>
257
+ <div style="width:180px;flex-shrink:0;font-family:monospace;text-align:right"><span style="color:${r}">CC:${s.complexity}</span> <span style="color:var(--muted)">${s.loc} LOC L${s.line}</span></div>
258
258
  </div>`}).join("")}
259
259
  `:'<p class="text-muted">No functions</p>'}
260
260
  </div>
261
- `}function N(e,t){return`<div class="metric-card"><div class="label">${a(e)}</div><div class="value">${a(String(t??0))}</div></div>`}window.__drillDown=fe;const se={tina4:{model:"tina4-v1",url:"https://api.tina4.com/v1/chat/completions"},custom:{model:"",url:"http://localhost:11434"},anthropic:{model:"claude-sonnet-4-20250514",url:"https://api.anthropic.com"},openai:{model:"gpt-4o",url:"https://api.openai.com"}};function ae(e="tina4"){const t=se[e]||se.tina4;return{provider:e,model:t.model,url:t.url,apiKey:""}}function ye(e){const t={...ae(),...e||{}};return t.provider==="ollama"&&(t.provider="custom"),t}function Ct(){try{const e=JSON.parse(localStorage.getItem("tina4_chat_settings")||"{}");return{thinking:ye(e.thinking),vision:ye(e.vision),imageGen:ye(e.imageGen)}}catch{return{thinking:ae(),vision:ae(),imageGen:ae()}}}function Bt(e){localStorage.setItem("tina4_chat_settings",JSON.stringify(e)),C=e,U()}let C=Ct(),P="Idle";const re=[];function zt(e){var n,i,o,s,c,d,p,u,_,k;e.innerHTML=`
261
+ `}function N(e,t){return`<div class="metric-card"><div class="label">${l(e)}</div><div class="value">${l(String(t??0))}</div></div>`}window.__drillDown=ve;let xe=null;function jt(e){e.innerHTML=`
262
+ <div class="dev-panel-header">
263
+ <h2>GraphQL Explorer</h2>
264
+ <button class="btn btn-sm" onclick="window.__loadGqlSchema()">Refresh Schema</button>
265
+ </div>
266
+ <div style="display:flex;gap:1rem;height:calc(100vh - 140px)">
267
+ <div style="width:220px;flex-shrink:0;overflow-y:auto;border-right:1px solid var(--border);padding-right:0.75rem">
268
+ <div style="font-weight:600;font-size:0.75rem;color:var(--muted);text-transform:uppercase;margin-bottom:0.5rem">Schema</div>
269
+ <div id="gql-types"></div>
270
+ <div id="gql-queries" style="margin-top:1rem"></div>
271
+ <div id="gql-mutations" style="margin-top:1rem"></div>
272
+ </div>
273
+ <div style="flex:1;display:flex;flex-direction:column;min-width:0">
274
+ <div class="flex gap-sm items-center" style="margin-bottom:0.5rem">
275
+ <button class="btn btn-primary" onclick="window.__runGqlQuery()">Execute</button>
276
+ <button class="btn" onclick="window.__copyGqlResult()">Copy JSON</button>
277
+ <span class="text-sm text-muted">Ctrl+Enter</span>
278
+ </div>
279
+ <div class="text-sm text-muted" style="font-weight:600;margin-bottom:0.25rem">Query</div>
280
+ <textarea id="gql-query" class="input text-mono" style="width:100%;height:120px;resize:vertical" placeholder="{ users { id name email } }" onkeydown="if(event.ctrlKey&&event.key==='Enter')window.__runGqlQuery()"></textarea>
281
+ <div class="text-sm text-muted" style="font-weight:600;margin:0.5rem 0 0.25rem">Variables (JSON)</div>
282
+ <textarea id="gql-variables" class="input text-mono" style="width:100%;height:40px;resize:vertical" placeholder="{}"></textarea>
283
+ <div id="gql-error" style="display:none;color:var(--danger);font-size:0.8rem;margin-top:0.25rem"></div>
284
+ <div class="text-sm text-muted" style="font-weight:600;margin:0.5rem 0 0.25rem">Result</div>
285
+ <pre id="gql-result" style="flex:1;overflow:auto;background:var(--bg);border:1px solid var(--border);border-radius:0.375rem;padding:0.75rem;font-size:0.8rem;margin:0;white-space:pre-wrap;color:var(--text);font-family:monospace"></pre>
286
+ </div>
287
+ </div>
288
+ `,Ge()}async function Ge(){const e=document.getElementById("gql-types"),t=document.getElementById("gql-queries"),n=document.getElementById("gql-mutations");try{const i=await z("/graphql/schema");if(i.error){e&&(e.innerHTML=`<p class="text-sm" style="color:var(--danger)">${l(i.error)}</p>`);return}const o=i.schema||{},s=o.types||{},c=o.queries||{},r=o.mutations||{};if(e){const d=Object.keys(s);d.length?e.innerHTML=d.map(a=>{const b=s[a],_=Object.entries(b).map(([g,E])=>`<div style="padding-left:1rem;color:var(--muted);font-size:0.7rem">${l(g)}: <span style="color:var(--primary)">${l(String(E))}</span></div>`).join("");return`
289
+ <div style="margin-bottom:0.5rem">
290
+ <div style="font-weight:600;font-size:0.8rem;color:var(--text);cursor:pointer" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display==='none'?'block':'none'">${l(a)}</div>
291
+ <div style="display:none">${_}</div>
292
+ </div>`}).join(""):e.innerHTML='<p class="text-sm text-muted">No types registered</p>'}if(t){const d=Object.keys(c);d.length&&(t.innerHTML='<div style="font-weight:600;font-size:0.75rem;color:var(--muted);text-transform:uppercase;margin-bottom:0.25rem">Queries</div>'+d.map(a=>{const b=c[a],_=b.args?Object.entries(b.args).map(([g,E])=>`${g}: ${E}`).join(", "):"";return`<div style="font-size:0.8rem;padding:0.15rem 0;cursor:pointer;color:var(--text)" onclick="window.__insertGqlQuery('${l(a)}','query')" title="Click to insert">${l(a)}${_?`(${l(_)})`:""}: <span style="color:var(--primary)">${l(b.type||"")}</span></div>`}).join(""))}if(n){const d=Object.keys(r);d.length&&(n.innerHTML='<div style="font-weight:600;font-size:0.75rem;color:var(--muted);text-transform:uppercase;margin-bottom:0.25rem">Mutations</div>'+d.map(a=>{const b=r[a],_=b.args?Object.entries(b.args).map(([g,E])=>`${g}: ${E}`).join(", "):"";return`<div style="font-size:0.8rem;padding:0.15rem 0;cursor:pointer;color:var(--text)" onclick="window.__insertGqlQuery('${l(a)}','mutation')" title="Click to insert">${l(a)}${_?`(${l(_)})`:""}: <span style="color:var(--primary)">${l(b.type||"")}</span></div>`}).join(""))}}catch(i){e&&(e.innerHTML=`<p class="text-sm" style="color:var(--danger)">${l(i.message)}</p>`)}}function Ht(e,t){const n=document.getElementById("gql-query");n&&(t==="mutation"?n.value=`mutation {
293
+ ${e} {
294
+
295
+ }
296
+ }`:n.value=`{
297
+ ${e} {
298
+
299
+ }
300
+ }`,n.focus())}async function Ot(){var c,r,d;const e=document.getElementById("gql-query"),t=(c=e==null?void 0:e.value)==null?void 0:c.trim();if(!t)return;const n=document.getElementById("gql-error"),i=document.getElementById("gql-result");let o={};const s=(d=(r=document.getElementById("gql-variables"))==null?void 0:r.value)==null?void 0:d.trim();if(s&&s!=="{}")try{o=JSON.parse(s)}catch{n&&(n.style.display="block",n.textContent="Invalid JSON in variables");return}n&&(n.style.display="none"),i&&(i.textContent="Executing...");try{const a=await z("/query","POST",{query:t,type:"graphql",variables:o});if(xe=a,a.errors&&a.errors.length){const b=a.errors.map(_=>_.message||String(_)).join(`
301
+ `);n&&(n.style.display="block",n.textContent=b)}i&&(i.textContent=JSON.stringify(a.data??a,null,2))}catch(a){n&&(n.style.display="block",n.textContent=a.message),i&&(i.textContent="")}}function At(){xe&&navigator.clipboard.writeText(JSON.stringify(xe,null,2))}window.__loadGqlSchema=Ge,window.__runGqlQuery=Ot,window.__copyGqlResult=At,window.__insertGqlQuery=Ht;const re={tina4:{model:"",url:"http://41.71.84.173:11437"},custom:{model:"",url:"http://localhost:11434"},anthropic:{model:"claude-sonnet-4-20250514",url:"https://api.anthropic.com"},openai:{model:"gpt-4o",url:"https://api.openai.com"}},F={thinking:{model:"",url:"http://41.71.84.173:11437"},vision:{model:"",url:"http://41.71.84.173:11434"},imageGen:{model:"",url:"http://41.71.84.173:11436"}};function ae(e="tina4",t="thinking"){if(e==="tina4"&&F[t]){const i=F[t];return{provider:e,model:i.model,url:i.url,apiKey:""}}const n=re[e]||re.tina4;return{provider:e,model:n.model,url:n.url,apiKey:""}}function we(e,t="thinking"){const n={...ae("tina4",t),...e||{}};return n.provider==="ollama"&&(n.provider="custom"),n.model==="tina4-v1"&&(n.model=""),n.provider==="tina4"&&F[t]&&(n.url=F[t].url),n}function Rt(){try{const e=JSON.parse(localStorage.getItem("tina4_chat_settings")||"{}");return{thinking:we(e.thinking,"thinking"),vision:we(e.vision,"vision"),imageGen:we(e.imageGen,"imageGen")}}catch{return{thinking:ae("tina4","thinking"),vision:ae("tina4","vision"),imageGen:ae("tina4","imageGen")}}}function Pt(e){localStorage.setItem("tina4_chat_settings",JSON.stringify(e)),T=e,Q()}let T=Rt(),H="Idle";const le=[];function Nt(){const e=document.getElementById("chat-messages");if(!e)return;const t=[];e.querySelectorAll(".chat-msg").forEach(n=>{var s;const i=n.classList.contains("chat-user")?"user":"assistant",o=((s=n.querySelector(".chat-msg-content"))==null?void 0:s.innerHTML)||"";o.includes("Hi! I'm Tina4.")||t.push({role:i,content:o})});try{localStorage.setItem("tina4_chat_history",JSON.stringify(t))}catch{}}function Dt(){try{const e=localStorage.getItem("tina4_chat_history");if(!e)return;const t=JSON.parse(e);if(!t.length)return;t.reverse().forEach(n=>{const i=(n.content||"").trim();i&&B(i,n.role==="user"?"user":"bot")})}catch{}}function Gt(){localStorage.removeItem("tina4_chat_history");const e=document.getElementById("chat-messages");e&&(e.innerHTML=`<div class="chat-msg chat-bot">Hi! I'm Tina4. Ask me to build routes, templates, models — or ask questions about your project.</div>`),de=0}function Ft(e){var n,i,o,s,c,r,d,a,b,_;e.innerHTML=`
262
302
  <div class="dev-panel-header">
263
303
  <h2>Code With Me</h2>
264
304
  <div class="flex gap-sm">
305
+ <button class="btn btn-sm" onclick="window.__clearChat()" title="Clear chat history">Clear</button>
265
306
  <button class="btn btn-sm" id="chat-thoughts-btn" title="Supervisor thoughts">Thoughts <span id="thoughts-dot" style="display:none;color:var(--info)">&#9679;</span></button>
266
307
  <button class="btn btn-sm" id="chat-settings-btn" title="Settings">&#9881; Settings</button>
267
308
  </div>
@@ -309,78 +350,90 @@ ${n.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
309
350
  <button class="btn btn-sm" id="chat-modal-close" style="width:28px;height:28px;padding:0;font-size:16px;line-height:1">&times;</button>
310
351
  </div>
311
352
  <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:0.75rem;margin-bottom:0.75rem">
312
- ${["thinking","vision","imageGen"].map(h=>`
353
+ ${["thinking","vision","imageGen"].map(g=>`
313
354
  <fieldset style="border:1px solid var(--border);border-radius:0.375rem;padding:0.5rem 0.75rem;margin:0">
314
- <legend class="text-sm" style="font-weight:600;padding:0 4px">${h==="imageGen"?"Image Generation":h.charAt(0).toUpperCase()+h.slice(1)}</legend>
315
- <div style="margin-bottom:0.375rem"><label class="text-sm text-muted" style="display:block;margin-bottom:2px">Provider</label><select id="set-${h}-provider" class="input" style="width:100%"><option value="tina4">Tina4 Cloud</option><option value="custom">Custom / Local</option><option value="anthropic">Anthropic (Claude)</option><option value="openai">OpenAI</option></select></div>
316
- <div style="margin-bottom:0.375rem"><label class="text-sm text-muted" style="display:block;margin-bottom:2px">URL</label><input type="text" id="set-${h}-url" class="input" style="width:100%" /></div>
317
- <div id="set-${h}-key-row" style="margin-bottom:0.375rem"><label class="text-sm text-muted" style="display:block;margin-bottom:2px">API Key</label><input type="password" id="set-${h}-key" class="input" placeholder="sk-..." style="width:100%" /></div>
318
- <button class="btn btn-sm btn-primary" id="set-${h}-connect" style="width:100%;margin-bottom:0.375rem">Connect</button>
319
- <div id="set-${h}-result" class="text-sm" style="min-height:1.2em;margin-bottom:0.375rem"></div>
320
- <div style="margin-bottom:0.375rem"><label class="text-sm text-muted" style="display:block;margin-bottom:2px">Model</label><select id="set-${h}-model" class="input" style="width:100%" disabled><option value="">-- connect first --</option></select></div>
321
- <div id="set-${h}-result" class="text-sm" style="margin-top:4px;min-height:1.2em"></div>
355
+ <legend class="text-sm" style="font-weight:600;padding:0 4px">${g==="imageGen"?"Image Generation":g.charAt(0).toUpperCase()+g.slice(1)}</legend>
356
+ <div style="margin-bottom:0.375rem"><label class="text-sm text-muted" style="display:block;margin-bottom:2px">Provider</label><select id="set-${g}-provider" class="input" style="width:100%"><option value="tina4">Tina4 Cloud</option><option value="custom">Custom / Local</option><option value="anthropic">Anthropic (Claude)</option><option value="openai">OpenAI</option></select></div>
357
+ <div id="set-${g}-url-row" style="margin-bottom:0.375rem"><label class="text-sm text-muted" style="display:block;margin-bottom:2px">URL</label><input type="text" id="set-${g}-url" class="input" style="width:100%" /></div>
358
+ <div id="set-${g}-key-row" style="margin-bottom:0.375rem"><label class="text-sm text-muted" style="display:block;margin-bottom:2px">API Key</label><input type="password" id="set-${g}-key" class="input" placeholder="sk-..." style="width:100%" /></div>
359
+ <button class="btn btn-sm btn-primary" id="set-${g}-connect" style="width:100%;margin-bottom:0.375rem">Connect</button>
360
+ <div id="set-${g}-result" class="text-sm" style="min-height:1.2em;margin-bottom:0.375rem"></div>
361
+ <div style="margin-bottom:0.375rem"><label class="text-sm text-muted" style="display:block;margin-bottom:2px">Model</label><select id="set-${g}-model" class="input" style="width:100%" disabled><option value="">-- connect first --</option></select></div>
322
362
  </fieldset>`).join("")}
323
363
  </div>
324
364
  <button class="btn btn-primary" id="chat-modal-save" style="width:100%">Save Settings</button>
325
365
  </div>
326
366
  </div>
327
- `,(n=document.getElementById("chat-send-btn"))==null||n.addEventListener("click",K),(i=document.getElementById("chat-thoughts-btn"))==null||i.addEventListener("click",Te),(o=document.getElementById("chat-thoughts-close"))==null||o.addEventListener("click",Te),(s=document.getElementById("chat-settings-btn"))==null||s.addEventListener("click",At),(c=document.getElementById("chat-modal-close"))==null||c.addEventListener("click",ke),(d=document.getElementById("chat-modal-save"))==null||d.addEventListener("click",jt),(p=document.getElementById("chat-modal-overlay"))==null||p.addEventListener("click",h=>{h.target===h.currentTarget&&ke()}),(u=document.getElementById("chat-file-btn"))==null||u.addEventListener("click",()=>{var h;(h=document.getElementById("chat-file-input"))==null||h.click()}),(_=document.getElementById("chat-file-input"))==null||_.addEventListener("change",Vt),(k=document.getElementById("chat-mic-btn"))==null||k.addEventListener("click",Yt);const t=document.getElementById("chat-input");t==null||t.addEventListener("keydown",h=>{h.key==="Enter"&&!h.shiftKey&&(h.preventDefault(),K())}),U()}function ve(e,t){document.getElementById(`set-${e}-provider`).value=t.provider;const n=document.getElementById(`set-${e}-model`);t.model&&(n.innerHTML=`<option value="${t.model}">${t.model}</option>`,n.value=t.model,n.disabled=!1),document.getElementById(`set-${e}-url`).value=t.url,document.getElementById(`set-${e}-key`).value=t.apiKey,we(e,t.provider)}function xe(e){var t,n,i,o;return{provider:((t=document.getElementById(`set-${e}-provider`))==null?void 0:t.value)||"custom",model:((n=document.getElementById(`set-${e}-model`))==null?void 0:n.value)||"",url:((i=document.getElementById(`set-${e}-url`))==null?void 0:i.value)||"",apiKey:((o=document.getElementById(`set-${e}-key`))==null?void 0:o.value)||""}}function we(e,t){const n=document.getElementById(`set-${e}-key-row`);n&&(n.style.display="block")}function $e(e){const t=document.getElementById(`set-${e}-provider`);t==null||t.addEventListener("change",()=>{const n=se[t.value]||se.tina4,i=document.getElementById(`set-${e}-model`);i.innerHTML=`<option value="${n.model}">${n.model}</option>`,i.value=n.model,document.getElementById(`set-${e}-url`).value=n.url,we(e,t.value)}),we(e,(t==null?void 0:t.value)||"custom")}async function _e(e){var c,d,p;const t=((c=document.getElementById(`set-${e}-provider`))==null?void 0:c.value)||"custom",n=((d=document.getElementById(`set-${e}-url`))==null?void 0:d.value)||"",i=((p=document.getElementById(`set-${e}-key`))==null?void 0:p.value)||"",o=document.getElementById(`set-${e}-model`),s=document.getElementById(`set-${e}-result`);s&&(s.textContent="Connecting...",s.style.color="var(--muted)");try{let u=[];const _=n.replace(/\/(v1|api)\/.*$/,"").replace(/\/+$/,"");if(t==="tina4"){const h={"Content-Type":"application/json"};i&&(h.Authorization=`Bearer ${i}`);try{u=((await(await fetch(`${_}/v1/models`,{headers:h})).json()).data||[]).map(v=>v.id)}catch{}u.length||(u=["tina4-v1"])}else if(t==="custom"){try{u=((await(await fetch(`${_}/api/tags`)).json()).models||[]).map(T=>T.name||T.model)}catch{}if(!u.length)try{u=((await(await fetch(`${_}/v1/models`)).json()).data||[]).map(T=>T.id)}catch{}}else if(t==="anthropic")u=["claude-sonnet-4-20250514","claude-opus-4-20250514","claude-haiku-4-20250514","claude-3-5-sonnet-20241022"];else if(t==="openai"){const h=n.replace(/\/v1\/.*$/,"");u=((await(await fetch(`${h}/v1/models`,{headers:i?{Authorization:`Bearer ${i}`}:{}})).json()).data||[]).map(v=>v.id).filter(v=>v.startsWith("gpt"))}if(u.length===0){s&&(s.innerHTML='<span style="color:var(--warn)">No models found</span>');return}const k=o.value;o.innerHTML=u.map(h=>`<option value="${h}">${h}</option>`).join(""),u.includes(k)&&(o.value=k),o.disabled=!1,s&&(s.innerHTML=`<span style="color:var(--success)">&#10003; ${u.length} models available</span>`)}catch{s&&(s.innerHTML='<span style="color:var(--danger)">&#10007; Connection failed</span>')}}function At(){var t,n,i;const e=document.getElementById("chat-modal-overlay");e&&(e.style.display="flex",ve("thinking",C.thinking),ve("vision",C.vision),ve("imageGen",C.imageGen),$e("thinking"),$e("vision"),$e("imageGen"),(t=document.getElementById("set-thinking-connect"))==null||t.addEventListener("click",()=>_e("thinking")),(n=document.getElementById("set-vision-connect"))==null||n.addEventListener("click",()=>_e("vision")),(i=document.getElementById("set-imageGen-connect"))==null||i.addEventListener("click",()=>_e("imageGen")))}function ke(){const e=document.getElementById("chat-modal-overlay");e&&(e.style.display="none")}function jt(){Bt({thinking:xe("thinking"),vision:xe("vision"),imageGen:xe("imageGen")}),ke()}function U(){const e=document.getElementById("chat-summary");if(!e)return;const t=Y.length?Y.map(o=>`<div style="margin-bottom:4px;font-size:0.65rem;line-height:1.3">
328
- <span style="color:var(--muted)">${a(o.time)}</span>
329
- <span style="color:var(--info);font-size:0.6rem">${a(o.agent)}</span>
330
- <div>${a(o.text)}</div>
331
- </div>`).join(""):'<div class="text-muted" style="font-size:0.65rem">No activity yet</div>',n=P==="Idle"?"var(--muted)":P==="Thinking..."?"var(--info)":"var(--success)",i=o=>o.model?'<span style="color:var(--success)">&#9679;</span>':'<span style="color:var(--muted)">&#9675;</span>';e.innerHTML=`
367
+ `,(n=document.getElementById("chat-send-btn"))==null||n.addEventListener("click",Y),(i=document.getElementById("chat-thoughts-btn"))==null||i.addEventListener("click",Ie),(o=document.getElementById("chat-thoughts-close"))==null||o.addEventListener("click",Ie),(s=document.getElementById("chat-settings-btn"))==null||s.addEventListener("click",Jt),(c=document.getElementById("chat-modal-close"))==null||c.addEventListener("click",Te),(r=document.getElementById("chat-modal-save"))==null||r.addEventListener("click",Wt),(d=document.getElementById("chat-modal-overlay"))==null||d.addEventListener("click",g=>{g.target===g.currentTarget&&Te()}),(a=document.getElementById("chat-file-btn"))==null||a.addEventListener("click",()=>{var g;(g=document.getElementById("chat-file-input"))==null||g.click()}),(b=document.getElementById("chat-file-input"))==null||b.addEventListener("change",an),(_=document.getElementById("chat-mic-btn"))==null||_.addEventListener("click",dn);const t=document.getElementById("chat-input");t==null||t.addEventListener("keydown",g=>{g.key==="Enter"&&!g.shiftKey&&(g.preventDefault(),Y())}),Q(),Dt(),loadServerHistory()}function _e(e,t){document.getElementById(`set-${e}-provider`).value=t.provider;const n=document.getElementById(`set-${e}-model`);t.model&&(n.innerHTML=`<option value="${t.model}">${t.model}</option>`,n.value=t.model,n.disabled=!1),document.getElementById(`set-${e}-url`).value=t.url,document.getElementById(`set-${e}-key`).value=t.apiKey,$e(e,t.provider)}function ke(e){var t,n,i,o;return{provider:((t=document.getElementById(`set-${e}-provider`))==null?void 0:t.value)||"custom",model:((n=document.getElementById(`set-${e}-model`))==null?void 0:n.value)||"",url:((i=document.getElementById(`set-${e}-url`))==null?void 0:i.value)||"",apiKey:((o=document.getElementById(`set-${e}-key`))==null?void 0:o.value)||""}}function $e(e,t){const n=document.getElementById(`set-${e}-key-row`),i=document.getElementById(`set-${e}-url-row`);t==="tina4"?(n&&(n.style.display="none"),i&&(i.style.display="none")):(n&&(n.style.display="block"),i&&(i.style.display="block"))}function Ee(e){const t=document.getElementById(`set-${e}-provider`);t==null||t.addEventListener("change",()=>{let n;t.value==="tina4"&&F[e]?n=F[e]:n=re[t.value]||re.tina4;const i=document.getElementById(`set-${e}-model`);i.innerHTML=n.model?`<option value="${n.model}">${n.model}</option>`:'<option value="">-- connect first --</option>',i.value=n.model,document.getElementById(`set-${e}-url`).value=n.url,$e(e,t.value)}),$e(e,(t==null?void 0:t.value)||"custom")}async function Se(e){var c,r,d;const t=((c=document.getElementById(`set-${e}-provider`))==null?void 0:c.value)||"custom";let n=((r=document.getElementById(`set-${e}-url`))==null?void 0:r.value)||"";const i=((d=document.getElementById(`set-${e}-key`))==null?void 0:d.value)||"",o=document.getElementById(`set-${e}-model`),s=document.getElementById(`set-${e}-result`);t==="tina4"&&F[e]&&(n=F[e].url),s&&(s.textContent="Connecting...",s.style.color="var(--muted)");try{let a=[];const b=n.replace(/\/(v1|api)\/.*$/,"").replace(/\/+$/,"");if(t==="tina4"){try{a=((await(await fetch(`${b}/api/tags`)).json()).models||[]).map(S=>S.name||S.model)}catch{}if(!a.length)try{a=((await(await fetch(`${b}/v1/models`)).json()).data||[]).map(S=>S.id)}catch{}}else if(t==="custom"){try{a=((await(await fetch(`${b}/api/tags`)).json()).models||[]).map(S=>S.name||S.model)}catch{}if(!a.length)try{a=((await(await fetch(`${b}/v1/models`)).json()).data||[]).map(S=>S.id)}catch{}}else if(t==="anthropic")a=["claude-sonnet-4-20250514","claude-opus-4-20250514","claude-haiku-4-20250514","claude-3-5-sonnet-20241022"];else if(t==="openai"){const g=n.replace(/\/v1\/.*$/,"");a=((await(await fetch(`${g}/v1/models`,{headers:i?{Authorization:`Bearer ${i}`}:{}})).json()).data||[]).map(w=>w.id).filter(w=>w.startsWith("gpt"))}if(a.length===0){s&&(s.innerHTML='<span style="color:var(--warn)">No models found</span>');return}const _=o.value;o.innerHTML=a.map(g=>`<option value="${g}">${g}</option>`).join(""),a.includes(_)&&(o.value=_),o.disabled=!1,s&&(s.innerHTML=`<span style="color:var(--success)">&#10003; ${a.length} models available</span>`)}catch{s&&(s.innerHTML='<span style="color:var(--danger)">&#10007; Connection failed</span>')}}function Jt(){var t,n,i;const e=document.getElementById("chat-modal-overlay");e&&(e.style.display="flex",_e("thinking",T.thinking),_e("vision",T.vision),_e("imageGen",T.imageGen),Ee("thinking"),Ee("vision"),Ee("imageGen"),(t=document.getElementById("set-thinking-connect"))==null||t.addEventListener("click",()=>Se("thinking")),(n=document.getElementById("set-vision-connect"))==null||n.addEventListener("click",()=>Se("vision")),(i=document.getElementById("set-imageGen-connect"))==null||i.addEventListener("click",()=>Se("imageGen")))}function Te(){const e=document.getElementById("chat-modal-overlay");e&&(e.style.display="none")}function Wt(){Pt({thinking:ke("thinking"),vision:ke("vision"),imageGen:ke("imageGen")}),Te()}function Q(){const e=document.getElementById("chat-summary");if(!e)return;const t=K.length?K.map(o=>`<div style="margin-bottom:4px;font-size:0.65rem;line-height:1.3">
368
+ <span style="color:var(--muted)">${l(o.time)}</span>
369
+ <span style="color:var(--info);font-size:0.6rem">${l(o.agent)}</span>
370
+ <div>${l(o.text)}</div>
371
+ </div>`).join(""):'<div class="text-muted" style="font-size:0.65rem">No activity yet</div>',n=H==="Idle"?"var(--muted)":H==="Thinking..."?"var(--info)":"var(--success)",i=o=>o.model?'<span style="color:var(--success)">&#9679;</span>':'<span style="color:var(--muted)">&#9675;</span>';e.innerHTML=`
332
372
  <div style="margin-bottom:0.5rem;font-size:0.7rem">
333
- <span style="color:${n}">&#9679;</span> ${a(P)}
373
+ <span style="color:${n}">&#9679;</span> ${l(H)}
334
374
  </div>
335
375
  <div style="font-size:0.65rem;line-height:1.8">
336
- ${i(C.thinking)} T: ${a(C.thinking.model||"—")}<br>
337
- ${i(C.vision)} V: ${a(C.vision.model||"—")}<br>
338
- ${i(C.imageGen)} I: ${a(C.imageGen.model||"—")}
376
+ ${i(T.thinking)} T: ${l(T.thinking.model||"—")}<br>
377
+ ${i(T.vision)} V: ${l(T.vision.model||"—")}<br>
378
+ ${i(T.imageGen)} I: ${l(T.imageGen.model||"—")}
339
379
  </div>
340
- ${re.length?`
380
+ ${le.length?`
341
381
  <div style="margin-bottom:0.75rem">
342
382
  <div class="text-muted" style="font-size:0.65rem;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px">Files Changed</div>
343
- ${re.map(o=>`<div class="text-mono" style="font-size:0.65rem;color:var(--success);margin-bottom:2px">${a(o)}</div>`).join("")}
383
+ ${le.map(o=>`<div class="text-mono" style="font-size:0.65rem;color:var(--success);margin-bottom:2px">${l(o)}</div>`).join("")}
344
384
  </div>
345
385
  `:""}
346
386
  <div>
347
387
  <div class="text-muted" style="font-size:0.65rem;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px">Activity</div>
348
388
  ${t}
349
389
  </div>
350
- `}let Ee=0;function A(e,t){const n=document.getElementById("chat-messages");if(!n)return;const i=`msg-${++Ee}`,o=document.createElement("div");if(o.className=`chat-msg chat-${t}`,o.id=i,o.innerHTML=`
390
+ `}let de=0;function B(e,t){var s,c;const n=document.getElementById("chat-messages");if(!n)return;const i=`msg-${++de}`,o=document.createElement("div");if(o.className=`chat-msg chat-${t}`,o.id=i,o.innerHTML=`
351
391
  <div class="chat-msg-content">${e}</div>
352
392
  <div class="chat-msg-actions" style="display:flex;gap:4px;margin-top:4px;opacity:0.4">
353
393
  <button class="btn btn-sm" style="font-size:0.6rem;padding:1px 6px" onclick="window.__copyMsg('${i}')" title="Copy">Copy</button>
354
394
  <button class="btn btn-sm" style="font-size:0.6rem;padding:1px 6px" onclick="window.__replyMsg('${i}')" title="Reply">Reply</button>
355
395
  <button class="btn btn-sm btn-primary" style="font-size:0.6rem;padding:1px 6px;display:none" onclick="window.__submitAnswers('${i}')" title="Submit answers" data-submit-btn>Submit Answers</button>
356
396
  </div>
357
- `,o.addEventListener("mouseenter",()=>{const s=o.querySelector(".chat-msg-actions");s&&(s.style.opacity="1")}),o.addEventListener("mouseleave",()=>{const s=o.querySelector(".chat-msg-actions");s&&(s.style.opacity="0.4")}),o.querySelector(".chat-answer-input")){const s=o.querySelector("[data-submit-btn]");s&&(s.style.display="inline-block")}n.prepend(o)}function Ht(e){const t=document.getElementById(e);if(!t)return;const n=t.querySelectorAll(".chat-answer-input"),i=[];if(n.forEach(c=>{const d=c.dataset.q||"?",p=c.value.trim();p&&(i.push(`${d}. ${p}`),c.disabled=!0,c.style.opacity="0.6")}),!i.length)return;const o=document.getElementById("chat-input");o&&(o.value=i.join(`
358
- `),K());const s=t.querySelector("[data-submit-btn]");s&&(s.style.display="none")}function Pt(e,t){const n=e.parentElement;if(!n)return;const i=n.querySelector(".chat-answer-input");i&&(i.value=t,i.disabled=!0,i.style.opacity="0.5"),n.querySelectorAll("button").forEach(s=>s.remove());const o=document.createElement("span");o.style.cssText="font-size:0.65rem;padding:2px 8px;border-radius:3px;background:var(--info);color:white",o.textContent=t,n.appendChild(o)}window.__quickAnswer=Pt,window.__submitAnswers=Ht;function qt(e){const t=document.querySelector(`#${e} .chat-msg-content`);t&&navigator.clipboard.writeText(t.textContent||"").then(()=>{const n=document.querySelector(`#${e} .chat-msg-actions button`);if(n){const i=n.textContent;n.textContent="Copied!",setTimeout(()=>{n.textContent=i},1e3)}})}function Ot(e){const t=document.querySelector(`#${e} .chat-msg-content`);if(!t)return;const n=(t.textContent||"").substring(0,100),i=document.getElementById("chat-input");i&&(i.value=`> ${n}${n.length>=100?"...":""}
397
+ `,o.addEventListener("mouseenter",()=>{const r=o.querySelector(".chat-msg-actions");r&&(r.style.opacity="1")}),o.addEventListener("mouseleave",()=>{const r=o.querySelector(".chat-msg-actions");r&&(r.style.opacity="0.4")}),o.querySelector(".chat-answer-input")){const r=o.querySelector("[data-submit-btn]");r&&(r.style.display="inline-block")}if(t==="bot"){const d=(((s=o.querySelector(".chat-msg-content"))==null?void 0:s.textContent)||"").trim().endsWith("?"),a=o.querySelector(".chat-answer-input");if(d&&!a){const b=document.createElement("div");b.style.cssText="display:flex;gap:4px;margin-top:6px;flex-wrap:wrap",b.className="chat-quick-replies",b.innerHTML=`
398
+ <button class="btn btn-sm" style="font-size:0.65rem;padding:2px 8px" onclick="window.__quickReply('Yes')">Yes</button>
399
+ <button class="btn btn-sm" style="font-size:0.65rem;padding:2px 8px" onclick="window.__quickReply('No')">No</button>
400
+ <button class="btn btn-sm" style="font-size:0.65rem;padding:2px 8px" onclick="window.__quickReply('You decide')">You decide</button>
401
+ <button class="btn btn-sm" style="font-size:0.65rem;padding:2px 8px" onclick="window.__quickReply('Skip')">Skip</button>
402
+ <button class="btn btn-sm" style="font-size:0.65rem;padding:2px 8px" onclick="window.__quickReply('Just build it')">Just build it</button>
403
+ `,(c=o.querySelector(".chat-msg-content"))==null||c.appendChild(b)}}n.prepend(o),Nt()}function Qt(e){const t=document.getElementById(e);if(!t)return;const n=t.querySelectorAll(".chat-answer-input"),i=[];if(n.forEach(c=>{const r=c.dataset.q||"?",d=c.value.trim();d&&(i.push(`${r}. ${d}`),c.disabled=!0,c.style.opacity="0.6")}),!i.length)return;const o=document.getElementById("chat-input");o&&(o.value=i.join(`
404
+ `),Y());const s=t.querySelector("[data-submit-btn]");s&&(s.style.display="none")}function Ut(e,t){const n=e.parentElement;if(!n)return;const i=n.querySelector(".chat-answer-input");i&&(i.value=t,i.disabled=!0,i.style.opacity="0.5"),n.querySelectorAll("button").forEach(s=>s.remove());const o=document.createElement("span");o.style.cssText="font-size:0.65rem;padding:2px 8px;border-radius:3px;background:var(--info);color:white",o.textContent=t,n.appendChild(o)}window.__quickAnswer=Ut,window.__submitAnswers=Qt;function Vt(e){const t=document.querySelector(`#${e} .chat-msg-content`);t&&navigator.clipboard.writeText(t.textContent||"").then(()=>{const n=document.querySelector(`#${e} .chat-msg-actions button`);if(n){const i=n.textContent;n.textContent="Copied!",setTimeout(()=>{n.textContent=i},1e3)}})}function Yt(e){const t=document.querySelector(`#${e} .chat-msg-content`);if(!t)return;const n=(t.textContent||"").substring(0,100),i=document.getElementById("chat-input");i&&(i.value=`> ${n}${n.length>=100?"...":""}
359
405
 
360
- `,i.focus(),i.setSelectionRange(i.value.length,i.value.length))}function Rt(e){var i,o;const t=e.closest(".chat-checklist-item");if(!t||(i=t.nextElementSibling)!=null&&i.classList.contains("chat-comment-box"))return;const n=document.createElement("div");n.className="chat-comment-box",n.style.cssText="padding-left:1.8rem;margin:0.15rem 0;display:flex;gap:4px",n.innerHTML=`
406
+ `,i.focus(),i.setSelectionRange(i.value.length,i.value.length))}function Kt(e){var i,o;const t=e.closest(".chat-checklist-item");if(!t||(i=t.nextElementSibling)!=null&&i.classList.contains("chat-comment-box"))return;const n=document.createElement("div");n.className="chat-comment-box",n.style.cssText="padding-left:1.8rem;margin:0.15rem 0;display:flex;gap:4px",n.innerHTML=`
361
407
  <input type="text" class="input" placeholder="Your comment..." style="flex:1;font-size:0.7rem;padding:2px 6px;height:24px">
362
408
  <button class="btn btn-sm" style="font-size:0.6rem;padding:1px 6px;height:24px" onclick="window.__submitComment(this)">Add</button>
363
- `,t.after(n),(o=n.querySelector("input"))==null||o.focus()}function Nt(e){var s;const t=e.closest(".chat-comment-box");if(!t)return;const n=t.querySelector("input"),i=(s=n==null?void 0:n.value)==null?void 0:s.trim();if(!i)return;const o=document.createElement("div");o.style.cssText="padding-left:1.8rem;margin:0.1rem 0;font-size:0.7rem;color:var(--info);font-style:italic",o.textContent=`↳ ${i}`,t.replaceWith(o)}function Re(){const e=[],t=[],n=[];return document.querySelectorAll(".chat-checklist-item").forEach(i=>{var d,p;const o=i.querySelector("input[type=checkbox]"),s=((d=i.querySelector("label"))==null?void 0:d.textContent)||"";o!=null&&o.checked?e.push(s):t.push(s);const c=i.nextElementSibling;if(c&&!c.classList.contains("chat-checklist-item")&&!c.classList.contains("chat-comment-box")){const u=((p=c.textContent)==null?void 0:p.replace("↳ ",""))||"";u&&n.push(`${s}: ${u}`)}}),{accepted:e,rejected:t,comments:n}}let le=!1;function Te(){const e=document.getElementById("chat-thoughts-panel");e&&(le=!le,e.style.display=le?"block":"none",le&&Ne())}async function Ne(){const e=document.getElementById("thoughts-list");if(e)try{const i=(await(await fetch("/__dev/api/thoughts")).json()||[]).filter(s=>!s.dismissed),o=document.getElementById("thoughts-dot");if(o&&(o.style.display=i.length?"inline":"none"),!i.length){e.innerHTML='<div class="text-muted text-sm" style="text-align:center;padding:2rem 0">All clear. No observations.</div>';return}e.innerHTML=i.map(s=>`
409
+ `,t.after(n),(o=n.querySelector("input"))==null||o.focus()}function Xt(e){var s;const t=e.closest(".chat-comment-box");if(!t)return;const n=t.querySelector("input"),i=(s=n==null?void 0:n.value)==null?void 0:s.trim();if(!i)return;const o=document.createElement("div");o.style.cssText="padding-left:1.8rem;margin:0.1rem 0;font-size:0.7rem;color:var(--info);font-style:italic",o.textContent=`↳ ${i}`,t.replaceWith(o)}function Fe(){const e=[],t=[],n=[];return document.querySelectorAll(".chat-checklist-item").forEach(i=>{var r,d;const o=i.querySelector("input[type=checkbox]"),s=((r=i.querySelector("label"))==null?void 0:r.textContent)||"";o!=null&&o.checked?e.push(s):t.push(s);const c=i.nextElementSibling;if(c&&!c.classList.contains("chat-checklist-item")&&!c.classList.contains("chat-comment-box")){const a=((d=c.textContent)==null?void 0:d.replace("↳ ",""))||"";a&&n.push(`${s}: ${a}`)}}),{accepted:e,rejected:t,comments:n}}let ce=!1;function Ie(){const e=document.getElementById("chat-thoughts-panel");e&&(ce=!ce,e.style.display=ce?"block":"none",ce&&Je())}async function Je(){const e=document.getElementById("thoughts-list");if(e)try{const i=(await(await fetch("/__dev/api/thoughts")).json()||[]).filter(s=>!s.dismissed),o=document.getElementById("thoughts-dot");if(o&&(o.style.display=i.length?"inline":"none"),!i.length){e.innerHTML='<div class="text-muted text-sm" style="text-align:center;padding:2rem 0">All clear. No observations.</div>';return}e.innerHTML=i.map(s=>`
364
410
  <div style="background:var(--bg);border:1px solid var(--border);border-radius:0.375rem;padding:0.5rem;margin-bottom:0.5rem;font-size:0.75rem">
365
- <div style="line-height:1.4">${a(s.message)}</div>
411
+ <div style="line-height:1.4">${l(s.message)}</div>
366
412
  <div style="display:flex;gap:4px;margin-top:0.375rem">
367
- ${(s.actions||[]).map(c=>c.action==="dismiss"?`<button class="btn btn-sm" style="font-size:0.6rem" onclick="window.__dismissThought('${a(s.id)}')">Dismiss</button>`:`<button class="btn btn-sm btn-primary" style="font-size:0.6rem" onclick="window.__actOnThought('${a(s.id)}','${a(c.action)}')">${a(c.label)}</button>`).join("")}
413
+ ${(s.actions||[]).map(c=>c.action==="dismiss"?`<button class="btn btn-sm" style="font-size:0.6rem" onclick="window.__dismissThought('${l(s.id)}')">Dismiss</button>`:`<button class="btn btn-sm btn-primary" style="font-size:0.6rem" onclick="window.__actOnThought('${l(s.id)}','${l(c.action)}')">${l(c.label)}</button>`).join("")}
368
414
  </div>
369
415
  </div>
370
- `).join("")}catch{e.innerHTML='<div class="text-muted text-sm" style="text-align:center;padding:1rem">Agent not connected</div>'}}async function De(e){await fetch("/__dev/api/thoughts/dismiss",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:e})}).catch(()=>{}),Ne()}function Dt(e,t){De(e),Te()}setInterval(async()=>{try{const n=(await(await fetch("/__dev/api/thoughts")).json()||[]).filter(o=>!o.dismissed),i=document.getElementById("thoughts-dot");i&&(i.style.display=n.length?"inline":"none")}catch{}},6e4),window.__dismissThought=De,window.__actOnThought=Dt,window.__commentOnItem=Rt,window.__submitComment=Nt,window.__getChecklist=Re,window.__copyMsg=qt,window.__replyMsg=Ot;const Y=[];function Fe(e){const t=document.getElementById("chat-status-bar"),n=document.getElementById("chat-status-text");t&&(t.style.display="flex"),n&&(n.textContent=e)}function We(){const e=document.getElementById("chat-status-bar");e&&(e.style.display="none")}function de(e,t){const n=new Date().toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"});Y.unshift({time:n,text:e,agent:t}),Y.length>50&&(Y.length=50),U()}async function K(){var i;const e=document.getElementById("chat-input"),t=(i=e==null?void 0:e.value)==null?void 0:i.trim();if(!t)return;if(e.value="",A(a(t),"user"),D.length){const o=D.map(s=>s.name).join(", ");A(`<span class="text-sm text-muted">Attached: ${a(o)}</span>`,"user")}P="Thinking...",Fe("Analyzing request..."),de("Analyzing request...","supervisor");const n={message:t,settings:{thinking:C.thinking,vision:C.vision,imageGen:C.imageGen}};D.length&&(n.files=D.map(o=>({name:o.name,data:o.data})));try{const o=await fetch("/__dev/api/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!o.ok||!o.body){A(`<span style="color:var(--danger)">Error: ${o.statusText}</span>`,"bot"),P="Error",U();return}const s=o.body.getReader(),c=new TextDecoder;let d="";for(;;){const{done:p,value:u}=await s.read();if(p)break;d+=c.decode(u,{stream:!0});const _=d.split(`
371
- `);d=_.pop()||"";let k="";for(const h of _)if(h.startsWith("event: "))k=h.slice(7).trim();else if(h.startsWith("data: ")){const E=h.slice(6);try{const T=JSON.parse(E);Ge(k,T)}catch{}}}D.length=0,Se()}catch{A('<span style="color:var(--danger)">Connection failed</span>',"bot"),P="Error",U()}}function Ge(e,t){switch(e){case"status":P=t.text||"Working...",Fe(`${t.agent||"supervisor"}: ${t.text||"Working..."}`),de(t.text||"",t.agent||"supervisor");break;case"message":{const n=t.content||"",i=t.agent||"supervisor";let o=Kt(n);i!=="supervisor"&&(o=`<span class="badge" style="font-size:0.6rem;margin-right:4px">${a(i)}</span>`+o),t.files_changed&&t.files_changed.length>0&&(o+='<div style="margin-top:0.5rem;padding:0.5rem;background:var(--bg);border-radius:0.375rem;border:1px solid var(--border)">',o+='<div class="text-sm" style="color:var(--success);font-weight:600;margin-bottom:0.25rem">Files changed:</div>',t.files_changed.forEach(s=>{o+=`<div class="text-sm text-mono">${a(s)}</div>`,re.includes(s)||re.push(s)}),o+="</div>"),A(o,"bot");break}case"plan":if(t.approve){const n=`
372
- <div style="padding:0.5rem;background:var(--surface);border:1px solid var(--info);border-radius:0.375rem;margin-top:0.25rem">
373
- <div class="text-sm" style="color:var(--info);font-weight:600;margin-bottom:0.25rem">Plan ready: ${a(t.file||"")}</div>
374
- <div class="text-sm text-muted" style="margin-bottom:0.5rem">Uncheck items you don't want. Click + to add comments. Then choose an action.</div>
416
+ `).join("")}catch{e.innerHTML='<div class="text-muted text-sm" style="text-align:center;padding:1rem">Agent not connected</div>'}}async function We(e){await fetch("/__dev/api/thoughts/dismiss",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:e})}).catch(()=>{}),Je()}function Zt(e,t){We(e),Ie()}setInterval(async()=>{try{const n=(await(await fetch("/__dev/api/thoughts")).json()||[]).filter(o=>!o.dismissed),i=document.getElementById("thoughts-dot");i&&(i.style.display=n.length?"inline":"none")}catch{}},6e4),window.__dismissThought=We,window.__actOnThought=Zt,window.__commentOnItem=Kt,window.__submitComment=Xt,window.__getChecklist=Fe;function en(e){document.querySelectorAll(".chat-quick-replies").forEach(n=>n.remove());const t=document.getElementById("chat-input");t&&(t.value=e,Y())}window.__quickReply=en,window.__copyMsg=Vt,window.__replyMsg=Yt,window.__clearChat=Gt;const K=[];function me(e){const t=document.getElementById("chat-status-bar"),n=document.getElementById("chat-status-text");t&&(t.style.display="flex"),n&&(n.textContent=e)}function Qe(){const e=document.getElementById("chat-status-bar");e&&(e.style.display="none")}function ue(e,t){const n=new Date().toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"});K.unshift({time:n,text:e,agent:t}),K.length>50&&(K.length=50),Q()}async function Y(){var i;const e=document.getElementById("chat-input"),t=(i=e==null?void 0:e.value)==null?void 0:i.trim();if(!t)return;if(e.value="",B(l(t),"user"),D.length){const o=D.map(s=>s.name).join(", ");B(`<span class="text-sm text-muted">Attached: ${l(o)}</span>`,"user")}H="Thinking...",me("Analyzing request..."),ue("Analyzing request...","supervisor");const n={message:t,settings:{thinking:T.thinking,vision:T.vision,imageGen:T.imageGen}};D.length&&(n.files=D.map(o=>({name:o.name,data:o.data})));try{const o=await fetch("/__dev/api/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!o.ok||!o.body){const d=o.status===0?"Agent not running. Start: tina4 agent":`Error: ${o.status}`;B(`<span style="color:var(--danger)">${d}</span>`,"bot"),H="Error",Q();return}const s=o.body.getReader(),c=new TextDecoder;let r="";for(;;){const{done:d,value:a}=await s.read();if(d)break;r+=c.decode(a,{stream:!0});const b=r.split(`
417
+ `);r=b.pop()||"";let _="";for(const g of b)if(g.startsWith("event: "))_=g.slice(7).trim();else if(g.startsWith("data: ")){const E=g.slice(6);try{const S=JSON.parse(E);Me(_,S)}catch{}}}D.length=0,Ce()}catch{B('<span style="color:var(--danger)">Connection failed</span>',"bot"),H="Error",Q()}}function Me(e,t){switch(e){case"status":H=t.text||"Working...",me(`${t.agent||"supervisor"}: ${t.text||"Working..."}`),ue(t.text||"",t.agent||"supervisor");break;case"message":{const n=t.content||"",i=t.agent||"supervisor";let o=Ue(n);i!=="supervisor"&&(o=`<span class="badge" style="font-size:0.6rem;margin-right:4px">${l(i)}</span>`+o),t.files_changed&&t.files_changed.length>0&&(o+='<div style="margin-top:0.5rem;padding:0.5rem;background:var(--bg);border-radius:0.375rem;border:1px solid var(--border)">',o+='<div class="text-sm" style="color:var(--success);font-weight:600;margin-bottom:0.25rem">Files changed:</div>',t.files_changed.forEach(s=>{o+=`<div class="text-sm text-mono">${l(s)}</div>`,le.includes(s)||le.push(s)}),o+="</div>"),B(o,"bot");break}case"plan":{let n="";t.content&&(n+=Ue(t.content)),t.approve&&(n+=`
418
+ <div style="padding:0.5rem;background:var(--surface);border:1px solid var(--info);border-radius:0.375rem;margin-top:0.75rem">
419
+ <div class="text-sm text-muted" style="margin-bottom:0.5rem">Uncheck items you don't want. Click + to add comments.</div>
375
420
  <div class="flex gap-sm" style="flex-wrap:wrap">
376
- <button class="btn btn-sm" onclick="window.__submitFeedback()">Submit Feedback</button>
377
- <button class="btn btn-sm btn-primary" onclick="window.__approvePlan('${a(t.file||"")}')">Approve & Execute</button>
378
- <button class="btn btn-sm" onclick="window.__keepPlan('${a(t.file||"")}');this.parentElement.parentElement.remove()">Keep for Later</button>
379
- <button class="btn btn-sm" onclick="this.parentElement.parentElement.remove()">Dismiss</button>
421
+ <button class="btn btn-sm btn-primary" onclick="window.__approvePlan('${l(t.file||"")}')">Approve & Build</button>
422
+ <button class="btn btn-sm" onclick="window.__submitFeedback()">Give Feedback</button>
423
+ <button class="btn btn-sm" onclick="window.__keepPlan('${l(t.file||"")}')">Later</button>
424
+ <button class="btn btn-sm" onclick="this.closest('.chat-msg').remove()">Dismiss</button>
380
425
  </div>
381
426
  </div>
382
- `;A(n,"bot")}break;case"error":We(),A(`<span style="color:var(--danger)">${a(t.message||"Unknown error")}</span>`,"bot"),P="Error",U();break;case"done":P="Done",We(),de("Done","supervisor"),setTimeout(()=>{P="Idle",U()},3e3);break}}async function Ft(e){A(`<span style="color:var(--success)">Plan approved: ${a(e)}</span>`,"user"),P="Executing plan...",de("Plan approved — executing...","supervisor");const t={message:`Execute the plan in ${e}. Write all the files now.`,settings:{thinking:C.thinking,vision:C.vision,imageGen:C.imageGen}};try{const n=await fetch("/__dev/api/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!n.ok||!n.body)return;const i=n.body.getReader(),o=new TextDecoder;let s="";for(;;){const{done:c,value:d}=await i.read();if(c)break;s+=o.decode(d,{stream:!0});const p=s.split(`
383
- `);s=p.pop()||"";let u="";for(const _ of p)if(_.startsWith("event: "))u=_.slice(7).trim();else if(_.startsWith("data: "))try{Ge(u,JSON.parse(_.slice(6)))}catch{}}}catch{A('<span style="color:var(--danger)">Plan execution failed</span>',"bot")}}function Wt(e){A(`<span style="color:var(--muted)">Plan saved for later: ${a(e)}</span>`,"bot")}function Gt(){const{accepted:e,rejected:t,comments:n}=Re();let i=`Here's my feedback on the proposal:
427
+ `),t.agent&&t.agent!=="supervisor"&&(n=`<span class="badge" style="font-size:0.6rem;margin-right:4px">${l(t.agent)}</span>`+n),B(n,"bot");break}case"error":Qe(),B(`<span style="color:var(--danger)">${l(t.message||"Unknown error")}</span>`,"bot"),H="Error",Q();break;case"plan_failed":{const n=t.completed||0,i=t.total||0,o=t.failed_step||0,s=`
428
+ <div style="padding:0.5rem;background:var(--surface);border:1px solid var(--warn);border-radius:0.375rem;margin-top:0.25rem">
429
+ <div class="text-sm" style="margin-bottom:0.5rem">${n} of ${i} steps completed. Failed at step ${o}.</div>
430
+ <div class="flex gap-sm">
431
+ <button class="btn btn-sm btn-primary" onclick="window.__resumePlan('${l(t.file||"")}')">Resume</button>
432
+ <button class="btn btn-sm" onclick="this.closest('.chat-msg').remove()">Dismiss</button>
433
+ </div>
434
+ </div>
435
+ `;B(s,"bot");break}case"done":H="Done",Qe(),ue("Done","supervisor"),setTimeout(()=>{H="Idle",Q()},3e3);break}}async function tn(e){B(`<span style="color:var(--success)">Plan approved — let's build it!</span>`,"user"),H="Executing plan...",ue("Plan approved — building...","supervisor"),me("Building...");const t={plan_file:e,settings:{thinking:T.thinking,vision:T.vision,imageGen:T.imageGen}};try{const n=await fetch("/__dev/api/execute",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!n.ok||!n.body)return;const i=n.body.getReader(),o=new TextDecoder;let s="";for(;;){const{done:c,value:r}=await i.read();if(c)break;s+=o.decode(r,{stream:!0});const d=s.split(`
436
+ `);s=d.pop()||"";let a="";for(const b of d)if(b.startsWith("event: "))a=b.slice(7).trim();else if(b.startsWith("data: "))try{Me(a,JSON.parse(b.slice(6)))}catch{}}}catch{B('<span style="color:var(--danger)">Plan execution failed</span>',"bot")}}function nn(e){B(`<span style="color:var(--muted)">Plan saved for later: ${l(e)}</span>`,"bot")}function on(){const{accepted:e,rejected:t,comments:n}=Fe();let i=`Here's my feedback on the proposal:
384
437
 
385
438
  `;e.length&&(i+=`**Keep these:**
386
439
  `+e.map(s=>`- ${s}`).join(`
@@ -394,33 +447,34 @@ ${n.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
394
447
  `+n.map(s=>`- ${s}`).join(`
395
448
  `)+`
396
449
 
397
- `),!t.length&&!n.length&&(i+="Everything looks good. "),i+="Please revise the plan based on this feedback.";const o=document.getElementById("chat-input");o&&(o.value=i,K())}window.__submitFeedback=Gt,window.__approvePlan=Ft,window.__keepPlan=Wt;async function Ut(){try{const e=await z("/chat/undo","POST");A(`<span style="color:var(--warn)">${a(e.message||"Undo complete")}</span>`,"bot")}catch{A('<span style="color:var(--warn)">Nothing to undo</span>',"bot")}}const D=[];function Vt(){const e=document.getElementById("chat-file-input");e!=null&&e.files&&(document.getElementById("chat-attachments"),Array.from(e.files).forEach(t=>{const n=new FileReader;n.onload=()=>{D.push({name:t.name,data:n.result}),Se()},n.readAsDataURL(t)}),e.value="")}function Se(){const e=document.getElementById("chat-attachments");if(e){if(!D.length){e.style.display="none";return}e.style.display="flex",e.style.cssText+="gap:0.375rem;flex-wrap:wrap;margin-bottom:0.375rem;font-size:0.75rem",e.innerHTML=D.map((t,n)=>`<span style="background:var(--surface);border:1px solid var(--border);border-radius:4px;padding:2px 8px;display:inline-flex;align-items:center;gap:4px">
398
- ${a(t.name)} <span style="cursor:pointer;color:var(--danger)" onclick="window.__removeFile(${n})">&times;</span>
399
- </span>`).join("")}}function Jt(e){D.splice(e,1),Se()}let X=!1,q=null;function Yt(){const e=document.getElementById("chat-mic-btn"),t=window.SpeechRecognition||window.webkitSpeechRecognition;if(!t){A('<span style="color:var(--warn)">Speech recognition not supported in this browser</span>',"bot");return}if(X&&q){q.stop(),X=!1,e&&(e.textContent="Mic",e.style.background="");return}q=new t,q.continuous=!1,q.interimResults=!1,q.lang="en-US",q.onresult=n=>{const i=n.results[0][0].transcript,o=document.getElementById("chat-input");o&&(o.value=(o.value?o.value+" ":"")+i)},q.onend=()=>{X=!1,e&&(e.textContent="Mic",e.style.background="")},q.onerror=()=>{X=!1,e&&(e.textContent="Mic",e.style.background="")},q.start(),X=!0,e&&(e.textContent="Stop",e.style.background="var(--danger)")}window.__removeFile=Jt;function Kt(e){let t=e.replace(/\\n/g,`
400
- `);const n=[];t=t.replace(/```(\w*)\n([\s\S]*?)```/g,(c,d,p)=>{const u=n.length;return n.push(`<pre style="background:var(--bg);padding:0.75rem;border-radius:0.375rem;overflow-x:auto;margin:0.5rem 0;font-size:0.75rem;border:1px solid var(--border)"><code>${a(p)}</code></pre>`),`\0CODE${u}\0`});const i=t.split(`
401
- `),o=[];for(const c of i){const d=c.trim();if(d.startsWith("\0CODE")){o.push(d);continue}if(d.startsWith("### ")){o.push(`<div style="font-weight:700;font-size:0.8rem;margin:0.75rem 0 0.25rem;color:var(--info)">${a(d.slice(4))}</div>`);continue}if(d.startsWith("## ")){o.push(`<div style="font-weight:700;font-size:0.9rem;margin:0.75rem 0 0.25rem">${a(d.slice(3))}</div>`);continue}if(d.startsWith("# ")){o.push(`<div style="font-weight:700;font-size:1rem;margin:0.75rem 0 0.25rem">${a(d.slice(2))}</div>`);continue}if(d==="---"||d==="***"){o.push('<hr style="border:none;border-top:1px solid var(--border);margin:0.5rem 0">');continue}const p=d.match(/^(\d+)[.)]\s+(.+)/);if(p){if(p[2].trim().endsWith("?")){const _=`q-${Ee}-${p[1]}`;o.push(`<div style="margin:0.3rem 0;padding-left:0.5rem">
402
- <div style="margin-bottom:4px"><span style="color:var(--info);font-weight:600;margin-right:0.4rem">${p[1]}.</span>${Q(p[2])}</div>
450
+ `),!t.length&&!n.length&&(i+="Everything looks good. "),i+="Please revise the plan based on this feedback.";const o=document.getElementById("chat-input");o&&(o.value=i,Y())}async function sn(e){B('<span style="color:var(--info)">Resuming plan...</span>',"user"),H="Resuming...",me("Resuming...");const t={plan_file:e,resume:!0,settings:{thinking:T.thinking,vision:T.vision,imageGen:T.imageGen}};try{const n=await fetch("/__dev/api/execute",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!n.ok||!n.body)return;const i=n.body.getReader(),o=new TextDecoder;let s="";for(;;){const{done:c,value:r}=await i.read();if(c)break;s+=o.decode(r,{stream:!0});const d=s.split(`
451
+ `);s=d.pop()||"";let a="";for(const b of d)if(b.startsWith("event: "))a=b.slice(7).trim();else if(b.startsWith("data: "))try{Me(a,JSON.parse(b.slice(6)))}catch{}}}catch{B('<span style="color:var(--danger)">Resume failed</span>',"bot")}}window.__resumePlan=sn,window.__submitFeedback=on,window.__approvePlan=tn,window.__keepPlan=nn;async function rn(){try{const e=await z("/chat/undo","POST");B(`<span style="color:var(--warn)">${l(e.message||"Undo complete")}</span>`,"bot")}catch{B('<span style="color:var(--warn)">Nothing to undo</span>',"bot")}}const D=[];function an(){const e=document.getElementById("chat-file-input");e!=null&&e.files&&(document.getElementById("chat-attachments"),Array.from(e.files).forEach(t=>{const n=new FileReader;n.onload=()=>{D.push({name:t.name,data:n.result}),Ce()},n.readAsDataURL(t)}),e.value="")}function Ce(){const e=document.getElementById("chat-attachments");if(e){if(!D.length){e.style.display="none";return}e.style.display="flex",e.style.cssText+="gap:0.375rem;flex-wrap:wrap;margin-bottom:0.375rem;font-size:0.75rem",e.innerHTML=D.map((t,n)=>`<span style="background:var(--surface);border:1px solid var(--border);border-radius:4px;padding:2px 8px;display:inline-flex;align-items:center;gap:4px">
452
+ ${l(t.name)} <span style="cursor:pointer;color:var(--danger)" onclick="window.__removeFile(${n})">&times;</span>
453
+ </span>`).join("")}}function ln(e){D.splice(e,1),Ce()}let X=!1,A=null;function dn(){const e=document.getElementById("chat-mic-btn"),t=window.SpeechRecognition||window.webkitSpeechRecognition;if(!t){B('<span style="color:var(--warn)">Speech recognition not supported in this browser</span>',"bot");return}if(X&&A){A.stop(),X=!1,e&&(e.textContent="Mic",e.style.background="");return}A=new t,A.continuous=!1,A.interimResults=!1,A.lang="en-US",A.onresult=n=>{const i=n.results[0][0].transcript,o=document.getElementById("chat-input");o&&(o.value=(o.value?o.value+" ":"")+i)},A.onend=()=>{X=!1,e&&(e.textContent="Mic",e.style.background="")},A.onerror=()=>{X=!1,e&&(e.textContent="Mic",e.style.background="")},A.start(),X=!0,e&&(e.textContent="Stop",e.style.background="var(--danger)")}window.__removeFile=ln;function Ue(e){let t=e.replace(/\\n/g,`
454
+ `);const n=[];t=t.replace(/```(\w*)\n([\s\S]*?)```/g,(c,r,d)=>{const a=n.length;return n.push(`<pre style="background:var(--bg);padding:0.75rem;border-radius:0.375rem;overflow-x:auto;margin:0.5rem 0;font-size:0.75rem;border:1px solid var(--border)"><code>${d}</code></pre>`),`\0CODE${a}\0`});const i=t.split(`
455
+ `),o=[];for(const c of i){const r=c.trim();if(r.startsWith("\0CODE")){o.push(r);continue}if(r.startsWith("### ")){o.push(`<div style="font-weight:700;font-size:0.8rem;margin:0.75rem 0 0.25rem;color:var(--info)">${r.slice(4)}</div>`);continue}if(r.startsWith("## ")){o.push(`<div style="font-weight:700;font-size:0.9rem;margin:0.75rem 0 0.25rem">${r.slice(3)}</div>`);continue}if(r.startsWith("# ")){o.push(`<div style="font-weight:700;font-size:1rem;margin:0.75rem 0 0.25rem">${r.slice(2)}</div>`);continue}if(r==="---"||r==="***"){o.push('<hr style="border:none;border-top:1px solid var(--border);margin:0.5rem 0">');continue}const d=r.match(/^(\d+)[.)]\s+(.+)/);if(d){if(d[2].trim().endsWith("?")){const b=`q-${de}-${d[1]}`;o.push(`<div style="margin:0.3rem 0;padding-left:0.5rem">
456
+ <div style="margin-bottom:4px"><span style="color:var(--info);font-weight:600;margin-right:0.4rem">${d[1]}.</span>${Z(d[2])}</div>
403
457
  <div style="display:flex;gap:4px;align-items:center;flex-wrap:wrap">
404
- <input type="text" class="input chat-answer-input" id="${_}" data-q="${p[1]}" placeholder="Your answer..." style="font-size:0.75rem;padding:4px 8px;flex:1;max-width:350px">
458
+ <input type="text" class="input chat-answer-input" id="${b}" data-q="${d[1]}" placeholder="Your answer..." style="font-size:0.75rem;padding:4px 8px;flex:1;max-width:350px">
405
459
  <button class="btn btn-sm" style="font-size:0.6rem;padding:2px 6px" onclick="window.__quickAnswer(this,'Yes')">Yes</button>
406
460
  <button class="btn btn-sm" style="font-size:0.6rem;padding:2px 6px" onclick="window.__quickAnswer(this,'No')">No</button>
407
461
  <button class="btn btn-sm" style="font-size:0.6rem;padding:2px 6px" onclick="window.__quickAnswer(this,'Later')">Later</button>
408
462
  <button class="btn btn-sm" style="font-size:0.6rem;padding:2px 6px" onclick="window.__quickAnswer(this,'Skip')">Skip</button>
409
463
  </div>
410
- </div>`)}else o.push(`<div style="margin:0.15rem 0;padding-left:1.5rem"><span style="color:var(--info);font-weight:600;margin-right:0.4rem">${p[1]}.</span>${Q(p[2])}</div>`);continue}if(d.startsWith("- ")){const u=`chk-${Ee}-${o.length}`,_=d.slice(2);o.push(`<div style="margin:0.15rem 0;padding-left:0.5rem;display:flex;align-items:flex-start;gap:6px" class="chat-checklist-item">
411
- <input type="checkbox" id="${u}" checked style="margin-top:3px;cursor:pointer;accent-color:var(--success)">
412
- <label for="${u}" style="flex:1;cursor:pointer">${Q(_)}</label>
464
+ </div>`)}else o.push(`<div style="margin:0.15rem 0;padding-left:1.5rem"><span style="color:var(--info);font-weight:600;margin-right:0.4rem">${d[1]}.</span>${Z(d[2])}</div>`);continue}if(r.startsWith("- ")){const a=`chk-${de}-${o.length}`,b=r.slice(2);o.push(`<div style="margin:0.15rem 0;padding-left:0.5rem;display:flex;align-items:flex-start;gap:6px" class="chat-checklist-item">
465
+ <input type="checkbox" id="${a}" checked style="margin-top:3px;cursor:pointer;accent-color:var(--success)">
466
+ <label for="${a}" style="flex:1;cursor:pointer">${Z(b)}</label>
413
467
  <button class="btn btn-sm" style="font-size:0.55rem;padding:1px 4px;opacity:0.5;flex-shrink:0" onclick="window.__commentOnItem(this)" title="Add comment">+</button>
414
- </div>`);continue}if(d.startsWith("> ")){o.push(`<div style="border-left:3px solid var(--info);padding-left:0.75rem;margin:0.3rem 0;color:var(--muted);font-style:italic">${Q(d.slice(2))}</div>`);continue}if(d===""){o.push('<div style="height:0.4rem"></div>');continue}o.push(`<div style="margin:0.1rem 0">${Q(d)}</div>`)}let s=o.join("");return n.forEach((c,d)=>{s=s.replace(`\0CODE${d}\0`,c)}),s}function Q(e){return a(e).replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/\*(.+?)\*/g,"<em>$1</em>").replace(/`([^`]+)`/g,'<code style="background:var(--bg);padding:0.1rem 0.3rem;border-radius:0.2rem;font-size:0.8em;border:1px solid var(--border)">$1</code>')}function Xt(e){const t=document.getElementById("chat-input");t&&(t.value=e,t.focus(),t.scrollTop=t.scrollHeight)}window.__sendChat=K,window.__undoChat=Ut,window.__prefillChat=Xt;const Ue=document.createElement("style");Ue.textContent=dt,document.head.appendChild(Ue);const ce=rt();lt(ce);const Ie=[{id:"routes",label:"Routes",render:ct},{id:"database",label:"Database",render:mt},{id:"errors",label:"Errors",render:wt},{id:"metrics",label:"Metrics",render:It},{id:"system",label:"System",render:Et}],Ve={id:"chat",label:"Code With Me",render:zt};let me=localStorage.getItem("tina4_cwm_unlocked")==="true",ue=me?[Ve,...Ie]:[...Ie],Z=me?"chat":"routes";function Qt(){const e=document.getElementById("app");if(!e)return;e.innerHTML=`
468
+ </div>`);continue}if(r.startsWith("> ")){o.push(`<div style="border-left:3px solid var(--info);padding-left:0.75rem;margin:0.3rem 0;color:var(--muted);font-style:italic">${Z(r.slice(2))}</div>`);continue}if(r===""){o.push('<div style="height:0.4rem"></div>');continue}o.push(`<div style="margin:0.1rem 0">${Z(r)}</div>`)}let s=o.join("");return n.forEach((c,r)=>{s=s.replace(`\0CODE${r}\0`,c)}),s}function Z(e){return e.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/\*(.+?)\*/g,"<em>$1</em>").replace(/`([^`]+)`/g,'<code style="background:var(--bg);padding:0.1rem 0.3rem;border-radius:0.2rem;font-size:0.8em;border:1px solid var(--border)">$1</code>')}function cn(e){const t=document.getElementById("chat-input");t&&(t.value=e,t.focus(),t.scrollTop=t.scrollHeight)}window.__sendChat=Y,window.__undoChat=rn,window.__prefillChat=cn;const Ve=document.createElement("style");Ve.textContent=ut,document.head.appendChild(Ve);const Ye=ct();mt(Ye);const Le=[{id:"routes",label:"Routes",render:gt},{id:"database",label:"Database",render:bt},{id:"graphql",label:"GraphQL",render:jt},{id:"errors",label:"Errors",render:Et},{id:"metrics",label:"Metrics",render:Bt},{id:"system",label:"System",render:Mt}],Ke={id:"chat",label:"Code With Me",render:Ft};let pe=localStorage.getItem("tina4_cwm_unlocked")==="true",ge=pe?[Ke,...Le]:[...Le],ee=pe?"chat":"routes";function mn(){const e=document.getElementById("app");if(!e)return;e.innerHTML=`
415
469
  <div class="dev-admin">
416
470
  <div class="dev-header">
417
471
  <h1><span>Tina4</span> Dev Admin</h1>
418
472
  <div style="display:flex;align-items:center;gap:0.75rem">
419
- <span class="text-sm text-muted" id="version-label" style="cursor:default;user-select:none">${ce.name} &bull; loading&hellip;</span>
473
+ <span class="text-sm text-muted" id="version-label" style="cursor:default;user-select:none">${Ye.name} &bull; v3.10.70</span>
420
474
  <button class="btn btn-sm" onclick="window.__closeDevAdmin()" title="Close Dev Admin" style="font-size:14px;width:28px;height:28px;padding:0;line-height:1">&times;</button>
421
475
  </div>
422
476
  </div>
423
477
  <div class="dev-tabs" id="tab-bar"></div>
424
478
  <div class="dev-content" id="tab-content"></div>
425
479
  </div>
426
- `;const t=document.getElementById("tab-bar");t.innerHTML=ue.map(n=>`<button class="dev-tab ${n.id===Z?"active":""}" data-tab="${n.id}" onclick="window.__switchTab('${n.id}')">${n.label}</button>`).join(""),Me(Z)}function Me(e){Z=e,document.querySelectorAll(".dev-tab").forEach(o=>{o.classList.toggle("active",o.dataset.tab===e)});const t=document.getElementById("tab-content");if(!t)return;const n=document.createElement("div");n.className="dev-panel active",t.innerHTML="",t.appendChild(n);const i=ue.find(o=>o.id===e);i&&i.render(n)}function Zt(){if(window.parent!==window)try{const e=window.parent.document.getElementById("tina4-dev-panel");e&&e.remove()}catch{document.body.style.display="none"}}window.__closeDevAdmin=Zt,window.__switchTab=Me,Qt(),z("/system").then(e=>{const t=document.getElementById("version-label");t&&e.version&&(t.innerHTML=`${ce.name} &bull; v${a(e.version)}`)}).catch(()=>{const e=document.getElementById("version-label");e&&(e.innerHTML=`${ce.name}`)});let Le=0,Ce=null;(Je=document.getElementById("version-label"))==null||Je.addEventListener("click",()=>{if(!me&&(Le++,Ce&&clearTimeout(Ce),Ce=setTimeout(()=>{Le=0},2e3),Le>=5)){me=!0,localStorage.setItem("tina4_cwm_unlocked","true"),ue=[Ve,...Ie],Z="chat";const e=document.getElementById("tab-bar");e&&(e.innerHTML=ue.map(t=>`<button class="dev-tab ${t.id===Z?"active":""}" data-tab="${t.id}" onclick="window.__switchTab('${t.id}')">${t.label}</button>`).join("")),Me("chat")}})})();
480
+ `;const t=document.getElementById("tab-bar");t.innerHTML=ge.map(n=>`<button class="dev-tab ${n.id===ee?"active":""}" data-tab="${n.id}" onclick="window.__switchTab('${n.id}')">${n.label}</button>`).join(""),Be(ee)}function Be(e){ee=e,document.querySelectorAll(".dev-tab").forEach(o=>{o.classList.toggle("active",o.dataset.tab===e)});const t=document.getElementById("tab-content");if(!t)return;const n=document.createElement("div");n.className="dev-panel active",t.innerHTML="",t.appendChild(n);const i=ge.find(o=>o.id===e);i&&i.render(n)}function un(){if(window.parent!==window)try{const e=window.parent.document.getElementById("tina4-dev-panel");e&&e.remove()}catch{document.body.style.display="none"}}window.__closeDevAdmin=un,window.__switchTab=Be,mn();let qe=0,ze=null;(Xe=document.getElementById("version-label"))==null||Xe.addEventListener("click",()=>{if(!pe&&(qe++,ze&&clearTimeout(ze),ze=setTimeout(()=>{qe=0},2e3),qe>=5)){pe=!0,localStorage.setItem("tina4_cwm_unlocked","true"),ge=[Ke,...Le],ee="chat";const e=document.getElementById("tab-bar");e&&(e.innerHTML=ge.map(t=>`<button class="dev-tab ${t.id===ee?"active":""}" data-tab="${t.id}" onclick="window.__switchTab('${t.id}')">${t.label}</button>`).join("")),Be("chat")}})})();