tina4ruby 3.11.9 → 3.11.10

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df3e8a44d54b3dc392460aeef376b7085198b2804b0e2457e5b90acb686369ed
4
- data.tar.gz: 7005aeee87cedaa5f186102bb41ce3fcd70d3848371355e3c0cbbf85ae76b0a7
3
+ metadata.gz: b34971f90bd1f889e9f8449670704a31f49d09e5e2707e9ba3238d532b4de190
4
+ data.tar.gz: b9a55d76f87b11e1dc38ea54f145ef0f5bc2b63e73783a9009d86987e3680e91
5
5
  SHA512:
6
- metadata.gz: a369e8dde59dad51bade987d67477eb270a879bf0edd6d4ad4e0bce752c5a471cd89806b8a692b3eec9608eb765ceab38fa10fd52b41422bf628414302f66dbb
7
- data.tar.gz: f239c1cae63624e1e399d7e9e6a804880273c4c4ef702ae9536d3adf0702e18edcdf26d8494dce5072197b8233c8a96e2ea0638605118986a4aac49d2936c4c8
6
+ metadata.gz: ae188f9e3379dd807fdd21d20703ad13741cff59f7da1825b251c4cc292bfa80c330cf1b52e983841c969a9368244e265e8d50f08ede36a00e09776f49fbf4b0
7
+ data.tar.gz: 44562a4fd4ac7c065b93cb6bbbdc3ea9b1581f772c64a326daf0a4997ff80249a947afc6bc4c151b0e8e0fbfbae9015b02535c9ad5901dffb7ae60efcb47e166
@@ -357,8 +357,41 @@ module Tina4
357
357
  json_response({ cleared: true })
358
358
  when ["GET", "/__dev/api/system"]
359
359
  json_response(system_payload)
360
+ when ["GET", "/__dev/api/queue/topics"]
361
+ queue_dir = File.join(Dir.pwd, "data", "queue")
362
+ topics = Dir.exist?(queue_dir) ? Dir.children(queue_dir).select { |d| File.directory?(File.join(queue_dir, d)) }.sort : []
363
+ topics = ["default"] if topics.empty?
364
+ json_response({ topics: topics })
365
+ when ["GET", "/__dev/api/queue/dead-letters"]
366
+ topic = query_param(env, "topic") || "default"
367
+ jobs = []
368
+ begin
369
+ queue = Tina4::Queue.new(backend: :file, topic: topic) if defined?(Tina4::Queue)
370
+ jobs = queue.respond_to?(:dead_letters) ? queue.dead_letters.map { |j| j.merge(status: "dead_letter") } : []
371
+ rescue StandardError => e
372
+ jobs = []
373
+ end
374
+ json_response({ jobs: jobs, count: jobs.size, topic: topic })
360
375
  when ["GET", "/__dev/api/queue"]
361
- json_response({ jobs: [], stats: { pending: 0, completed: 0, failed: 0, reserved: 0 } })
376
+ topic = query_param(env, "topic") || "default"
377
+ stats = { pending: 0, completed: 0, failed: 0, reserved: 0 }
378
+ jobs = []
379
+ begin
380
+ if defined?(Tina4::Queue)
381
+ queue = Tina4::Queue.new(backend: :file, topic: topic)
382
+ stats = {
383
+ pending: queue.respond_to?(:size) ? queue.size("pending") : 0,
384
+ completed: queue.respond_to?(:size) ? queue.size("completed") : 0,
385
+ failed: queue.respond_to?(:size) ? queue.size("failed") : 0,
386
+ reserved: queue.respond_to?(:size) ? queue.size("reserved") : 0,
387
+ }
388
+ jobs.concat(queue.failed.map { |j| j.merge(status: "failed") }) if queue.respond_to?(:failed)
389
+ jobs.concat(queue.dead_letters.map { |j| j.merge(status: "dead_letter") }) if queue.respond_to?(:dead_letters)
390
+ end
391
+ rescue StandardError => e
392
+ # fall through to empty stats
393
+ end
394
+ json_response({ jobs: jobs, stats: stats })
362
395
  when ["GET", "/__dev/api/mailbox"]
363
396
  messages = mailbox.inbox
364
397
  json_response({ messages: messages, count: messages.size, unread: mailbox.unread_count })
@@ -1,4 +1,4 @@
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=`
1
+ (function(){"use strict";var at;const vt="/__dev/api";async function T(e,t="GET",n){const i={method:t,headers:{}};return n&&(i.headers["Content-Type"]="application/json",i.body=JSON.stringify(n)),(await fetch(vt+e,i)).json()}function r(e){const t=document.createElement("span");return t.textContent=e,t.innerHTML}const Ue={python:{color:"#3b82f6",name:"Python"},php:{color:"#8b5cf6",name:"PHP"},ruby:{color:"#ef4444",name:"Ruby"},nodejs:{color:"#22c55e",name:"Node.js"}};function xt(){const e=document.getElementById("app"),t=(e==null?void 0:e.dataset.framework)??"python",n=e==null?void 0:e.dataset.color,i=Ue[t]??Ue.python;return{framework:t,color:n??i.color,name:i.name}}function wt(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 _t=`
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
 
@@ -76,7 +76,7 @@ tr:hover { background: rgba(255,255,255,0.03); }
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
- `,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=`
79
+ `;function kt(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
- `,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=>`
88
+ `,Ve()}async function Ve(){const e=await T("/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()}">${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>
90
+ <td><span class="method method-${i.method.toLowerCase()}">${r(i.method)}</span></td>
91
+ <td class="text-mono"><a href="${r(i.path)}" target="_blank" style="color:inherit;text-decoration:underline dotted">${r(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">${l(i.handler||"")} <small>(${l(i.module||"")})</small></td>
93
+ <td class="text-sm text-muted">${r(i.handler||"")} <small>(${r(i.module||"")})</small></td>
94
94
  </tr>
95
- `).join(""))}window.__loadRoutes=Re;let J=[],W=[],O=JSON.parse(localStorage.getItem("tina4_query_history")||"[]");function bt(e){e.innerHTML=`
95
+ `).join(""))}window.__loadRoutes=Ve;let Q=[],U=[],O=JSON.parse(localStorage.getItem("tina4_query_history")||"[]");function $t(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
- `,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=`
162
+ `,ke(),Ee()}async function ke(){const t=(await T("/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('${r(s)}')" onmouseover="this.style.background='var(--border)'" onmouseout="this.style.background=''">${r(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="${r(s)}">${r(s)}</option>`).join(""));const o=document.getElementById("paste-table");o&&(o.innerHTML='<option value="">Select table...</option>'+t.map(s=>`<option value="${r(s)}">${r(s)}</option>`).join(""))}function $e(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)":""}),Ye()}function Et(){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 St(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)),Ee())}function Ee(){const e=document.getElementById("db-history");e&&(e.innerHTML='<option value="">Query history...</option>'+O.map((t,n)=>`<option value="${n}">${r(t.length>80?t.substring(0,80)+"...":t)}</option>`).join(""))}function Tt(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 It(){O=[],localStorage.removeItem("tina4_query_history"),Ee()}async function Ye(){var o,s,l;const e=document.getElementById("db-query"),t=(o=e==null?void 0:e.value)==null?void 0:o.trim();if(!t)return;St(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 a=parseInt(((l=document.getElementById("db-limit"))==null?void 0:l.value)||"20"),c=await T("/query","POST",{query:t,type:i,limit:a});if(c.error){n&&(n.innerHTML=`<p style="color:var(--danger)">${r(c.error)}</p>`);return}c.rows&&c.rows.length>0?(U=Object.keys(c.rows[0]),Q=c.rows,n&&(n.innerHTML=`<p class="text-sm text-muted" style="margin-bottom:0.5rem">${c.count??c.rows.length} rows</p>
163
+ <div style="overflow-x:auto"><table><thead><tr>${U.map(d=>`<th>${r(d)}</th>`).join("")}</tr></thead>
164
+ <tbody>${c.rows.map(d=>`<tr>${U.map(b=>`<td class="text-sm">${r(String(d[b]??""))}</td>`).join("")}</tr>`).join("")}</tbody></table></div>`)):c.affected!==void 0?(n&&(n.innerHTML=`<p class="text-muted">${c.affected} rows affected. ${c.success?"Success.":""}</p>`),Q=[],U=[]):(n&&(n.innerHTML='<p class="text-muted">No results</p>'),Q=[],U=[])}catch(a){n&&(n.innerHTML=`<p style="color:var(--danger)">${r(a.message)}</p>`)}}function qt(){if(!Q.length)return;const e=U.join(","),t=Q.map(n=>U.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 Mt(){Q.length&&navigator.clipboard.writeText(JSON.stringify(Q,null,2))}function Lt(){const e=document.getElementById("db-paste-modal");e&&(e.style.display="flex")}function Ke(){const e=document.getElementById("db-paste-modal");e&&(e.style.display="none")}async function Ct(){var o,s,l,a,c;const e=(o=document.getElementById("paste-table"))==null?void 0:o.value,t=(l=(s=document.getElementById("paste-new-table"))==null?void 0:s.value)==null?void 0:l.trim(),n=t||e,i=(c=(a=document.getElementById("paste-data"))==null?void 0:a.value)==null?void 0:c.trim();if(!n||!i){alert("Select a table or enter a new table name, and paste data.");return}try{let d;try{d=JSON.parse(i),Array.isArray(d)||(d=[d])}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,""));d=_.slice(1).map(E=>{const S=E.split(",").map(H=>H.trim()),w={};return g.forEach((H,ie)=>{w[H]=S[ie]??""}),w})}if(!d.length){alert("No data rows found.");return}if(t){const g=["id INTEGER PRIMARY KEY AUTOINCREMENT",...Object.keys(d[0]).filter(S=>S.toLowerCase()!=="id").map(S=>`"${S}" TEXT`)],E=await T("/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 d){const g=t?Object.keys(_).filter(H=>H.toLowerCase()!=="id"):Object.keys(_),E=g.map(H=>`"${H}"`).join(","),S=g.map(H=>`'${String(_[H]).replace(/'/g,"''")}'`).join(","),w=await T("/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,Ke(),ke(),b>0&&$e(n)}catch(d){alert("Import error: "+d.message)}}async function Bt(){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 T("/seed","POST",{table:e,count:t});o.error?alert(o.error):$e(e)}catch(o){alert("Seed error: "+o.message)}}window.__loadTables=ke,window.__selectTable=$e,window.__updateLimit=Et,window.__runQuery=Ye,window.__copyCSV=qt,window.__copyJSON=Mt,window.__showPaste=Lt,window.__hidePaste=Ke,window.__doPaste=Ct,window.__seedTable=Bt,window.__loadHistory=Tt,window.__clearHistory=It;function zt(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
- `,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`
175
+ `,le()}async function le(){const e=await T("/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 l=o.error_type?`${o.error_type}: ${o.message}`:o.error||o.message||"Unknown error",a=o.context||{},c=o.last_seen||o.first_seen||o.timestamp||"",d=c?new Date(c).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">${l(c)}</strong>
181
+ <strong style="margin-left:0.5rem;font-size:0.85rem">${r(l)}</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('${l(o.id||String(s))}')">Resolve</button>`}
184
+ ${o.resolved?"":`<button class="btn btn-sm" onclick="window.__resolveError('${r(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
- ${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>
188
+ ${a.method?`<div class="text-sm text-mono" style="margin-top:0.5rem;color:var(--info)">${r(a.method)} ${r(a.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">${r(o.traceback)}</pre>`:""}
190
+ <div class="text-sm text-muted" style="margin-top:0.5rem">${r(d)}</div>
191
191
  </div>
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
- Route: ${o.method} ${o.path}`:"",c=`I have this error: ${i}${s}
192
+ `}).join(""),window.__errorData=i}async function Ht(e){await T("/broken/resolve","POST",{id:e}),le()}async function Rt(){await T("/broken/clear","POST"),le()}function At(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
+ Route: ${o.method} ${o.path}`:"",l=`I have this error: ${i}${s}
194
194
 
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=`
195
+ ${n.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillChat(l)},150)}window.__loadErrors=le,window.__clearErrors=Rt,window.__resolveError=Ht,window.__askAboutError=At;function Ot(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
- `,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>
201
+ `,Xe()}function Pt(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 jt(e){return e?e>=1024?`${(e/1024).toFixed(1)} GB`:`${e.toFixed(1)} MB`:"?"}async function Xe(){const e=await T("/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:Pt(e.uptime_seconds)},{label:"Memory",value:jt(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||"?"}],l=new Set(["Working Dir","Database"]);if(t.innerHTML=s.map(a=>`
202
+ <div class="metric-card" style="${l.has(a.label)?"grid-column:1/-1":""}">
203
+ <div class="label">${r(a.label)}</div>
204
+ <div class="value" style="font-size:${l.has(a.label)?"0.75rem":"1.1rem"}">${r(a.value)}</div>
205
205
  </div>
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=`
206
+ `).join(""),n){const a=[];e.debug!==void 0&&a.push(["TINA4_DEBUG",String(e.debug)]),e.log_level&&a.push(["LOG_LEVEL",e.log_level]),e.database&&a.push(["DATABASE_URL",e.database]),a.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>${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>
210
+ <tbody>${a.map(([c,d])=>`<tr><td class="text-mono text-sm" style="padding:4px 8px">${r(c)}</td><td class="text-sm" style="padding:4px 8px">${r(d)}</td></tr>`).join("")}</tbody>
211
211
  </table>
212
- `)}}window.__loadSystem=De;function Bt(e){e.innerHTML=`
212
+ `)}}window.__loadSystem=Xe;function Nt(e){e.innerHTML=`
213
213
  <div class="dev-panel-header">
214
214
  <h2>Code Metrics</h2>
215
215
  </div>
@@ -218,47 +218,47 @@ ${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
- `,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=`
221
+ `,Ft()}async function Ft(){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 T("/metrics/full");if(i.error||!i.file_metrics){e&&(e.innerHTML=`<p style="color:var(--danger)">${r(i.error||"No data")}</p>`);return}if(n){const l=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 ${l}`}const o=document.getElementById("metrics-quick");o&&(o.innerHTML=[F("Files Analyzed",i.files_analyzed),F("Total Functions",i.total_functions),F("Avg Complexity",i.avg_complexity),F("Avg Maintainability",i.avg_maintainability)].join("")),e&&i.file_metrics.length>0?Dt(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
- <tbody>${i.most_complex_functions.slice(0,15).map(c=>`
225
+ <tbody>${i.most_complex_functions.slice(0,15).map(l=>`
226
226
  <tr>
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
- <td>${c.line}</td>
230
- <td><span class="${c.complexity>10?"badge badge-danger":c.complexity>5?"badge badge-warn":"badge badge-success"}">${c.complexity}</span></td>
231
- <td>${c.loc}</td>
227
+ <td class="text-mono">${r(l.name)}</td>
228
+ <td class="text-sm text-muted" style="cursor:pointer;text-decoration:underline dotted" onclick="window.__drillDown('${r(l.file)}')">${r(l.file)}</td>
229
+ <td>${l.line}</td>
230
+ <td><span class="${l.complexity>10?"badge badge-danger":l.complexity>5?"badge badge-warn":"badge badge-success"}">${l.complexity}</span></td>
231
+ <td>${l.loc}</td>
232
232
  </tr>`).join("")}
233
233
  </tbody>
234
234
  </table>
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=`
235
+ `)}function Dt(e,t,n,i){var yt,ft,ht;const o=t.offsetWidth||900,s=Math.max(450,Math.min(650,o*.45)),l=Math.max(...e.map(h=>h.loc))||1,a=Math.max(...e.map(h=>h.dep_count||0))||1,c=14,d=Math.min(70,o/10);function b(h){const y=Math.min((h.avg_complexity||0)/10,1),v=h.has_tests?0:1,k=Math.min((h.dep_count||0)/5,1),p=y*.4+v*.4+k*.2,m=Math.max(0,Math.min(1,p)),f=Math.round(120*(1-m)),x=Math.round(70+m*30),$=Math.round(42+18*(1-m));return`hsl(${f},${x}%,${$}%)`}function _(h){return h.loc/l*.4+(h.avg_complexity||0)/10*.4+(h.dep_count||0)/a*.2}const g=[...e].sort((h,y)=>_(h)-_(y)),E=o/2,S=s/2,w=[];let H=0,ie=0;for(const h of g){const y=c+Math.sqrt(_(h))*(d-c),v=b(h);let k=!1;for(let p=0;p<800;p++){const m=E+ie*Math.cos(H),f=S+ie*Math.sin(H);let x=!1;for(const $ of w){const C=m-$.x,z=f-$.y;if(Math.sqrt(C*C+z*z)<y+$.r+2){x=!0;break}}if(!x&&m>y+2&&m<o-y-2&&f>y+25&&f<s-y-2){w.push({x:m,y:f,vx:0,vy:0,r:y,color:v,f:h}),k=!0;break}H+=.2,ie+=.04}k||w.push({x:E+(Math.random()-.5)*o*.3,y:S+(Math.random()-.5)*s*.3,vx:0,vy:0,r:y,color:v,f:h})}const Je=[];function lt(h){const y=h.replace(/\\/g,"/").split("/").pop()||"",v=y.lastIndexOf(".");return(v>0?y.substring(0,v):y).toLowerCase()}const We={};w.forEach((h,y)=>{We[lt(h.f.path)]=y});for(const[h,y]of Object.entries(n)){let v=null;if(w.forEach((k,p)=>{k.f.path===h&&(v=p)}),v!==null)for(const k of y){const p=k.replace(/^\.\//,"").replace(/^\.\.\//,"").split(/[./]/);let m;for(let f=p.length-1;f>=0;f--){const x=p[f].toLowerCase();if(x&&x!=="js"&&x!=="py"&&x!=="rb"&&x!=="ts"&&x!=="index"&&(m=We[x],m!==void 0))break}m===void 0&&(m=We[lt(k)]),m!==void 0&&v!==m&&Je.push([v,m])}}const q=document.createElement("canvas");q.width=o,q.height=s,q.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(q);const Qe=document.createElement("div");Qe.style.cssText="position:absolute;top:8px;left:8px;z-index:2;display:flex;gap:4px;flex-direction:column",Qe.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(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=`
239
+ `,document.getElementById("metrics-canvas-wrap").appendChild(Qe),(yt=document.getElementById("metrics-zoom-in"))==null||yt.addEventListener("click",()=>{L=Math.min(5,L*1.3)}),(ft=document.getElementById("metrics-zoom-out"))==null||ft.addEventListener("click",()=>{L=Math.max(.3,L*.7)}),(ht=document.getElementById("metrics-zoom-fit"))==null||ht.addEventListener("click",()=>{L=1,K=0,X=0});const u=q.getContext("2d");let G=-1,M=-1,dt=0,ct=0,K=0,X=0,L=1,se=!1,mt=0,ut=0,pt=0,gt=0;function Sn(){for(let p=0;p<w.length;p++){if(p===M)continue;const m=w[p],f=E-m.x,x=S-m.y,$=.3+m.r/d*.7,C=.008*$*$;m.vx+=f*C,m.vy+=x*C}for(const[p,m]of Je){const f=w[p],x=w[m],$=x.x-f.x,C=x.y-f.y,z=Math.sqrt($*$+C*C)||1,j=f.r+x.r+20,N=(z-j)*.002,re=$/z*N,ae=C/z*N;p!==M&&(f.vx+=re,f.vy+=ae),m!==M&&(x.vx-=re,x.vy-=ae)}for(let p=0;p<w.length;p++)for(let m=p+1;m<w.length;m++){const f=w[p],x=w[m],$=x.x-f.x,C=x.y-f.y,z=Math.sqrt($*$+C*C)||1,j=f.r+x.r+20;if(z<j){const N=40*(j-z)/j,re=$/z*N,ae=C/z*N;p!==M&&(f.vx-=re,f.vy-=ae),m!==M&&(x.vx+=re,x.vy+=ae)}}for(let p=0;p<w.length;p++){if(p===M)continue;const m=w[p];m.vx*=.65,m.vy*=.65;const f=2;m.vx=Math.max(-f,Math.min(f,m.vx)),m.vy=Math.max(-f,Math.min(f,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 bt(){var h;Sn(),u.clearRect(0,0,o,s),u.save(),u.translate(K,X),u.scale(L,L),u.strokeStyle="rgba(255,255,255,0.03)",u.lineWidth=1/L;for(let y=0;y<o/L;y+=50)u.beginPath(),u.moveTo(y,0),u.lineTo(y,s/L),u.stroke();for(let y=0;y<s/L;y+=50)u.beginPath(),u.moveTo(0,y),u.lineTo(o/L,y),u.stroke();for(const[y,v]of Je){const k=w[y],p=w[v],m=p.x-k.x,f=p.y-k.y,x=Math.sqrt(m*m+f*f)||1,$=G===y||G===v;u.beginPath(),u.moveTo(k.x+m/x*k.r,k.y+f/x*k.r);const C=p.x-m/x*p.r,z=p.y-f/x*p.r;u.lineTo(C,z),u.strokeStyle=$?"rgba(139,180,250,0.9)":"rgba(255,255,255,0.15)",u.lineWidth=$?3:1,u.stroke();const j=$?12:6,N=Math.atan2(f,m);u.beginPath(),u.moveTo(C,z),u.lineTo(C-j*Math.cos(N-.4),z-j*Math.sin(N-.4)),u.lineTo(C-j*Math.cos(N+.4),z-j*Math.sin(N+.4)),u.closePath(),u.fillStyle=u.strokeStyle,u.fill()}for(let y=0;y<w.length;y++){const v=w[y],k=y===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=((h=v.f.path.split("/").pop())==null?void 0:h.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 f=Math.max(9,p*.3),x=f*.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 ${f}px sans-serif`,u.textAlign="center",u.fillText("D",v.x,$+f*.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 ${f}px sans-serif`,u.textAlign="center",u.fillText("T",v.x,$+f*.35)}}u.restore(),requestAnimationFrame(bt)}q.addEventListener("mousemove",h=>{const y=q.getBoundingClientRect(),v=(h.clientX-y.left-K)/L,k=(h.clientY-y.top-X)/L;if(se){K=pt+(h.clientX-mt),X=gt+(h.clientY-ut);return}if(M>=0){_e=!0,w[M].x=v+dt,w[M].y=k+ct,w[M].vx=0,w[M].vy=0;return}G=-1;for(let p=w.length-1;p>=0;p--){const m=w[p],f=v-m.x,x=k-m.y;if(Math.sqrt(f*f+x*x)<m.r+4){G=p;break}}q.style.cursor=G>=0?"grab":"default"}),q.addEventListener("mousedown",h=>{const y=q.getBoundingClientRect(),v=(h.clientX-y.left-K)/L,k=(h.clientY-y.top-X)/L;if(h.button===2){se=!0,mt=h.clientX,ut=h.clientY,pt=K,gt=X,q.style.cursor="move";return}G>=0&&(M=G,dt=w[M].x-v,ct=w[M].y-k,_e=!1,q.style.cursor="grabbing")});let _e=!1;q.addEventListener("mouseup",h=>{if(se){se=!1,q.style.cursor="default";return}if(M>=0){_e||Se(w[M].f.path),q.style.cursor="grab",M=-1,_e=!1;return}}),q.addEventListener("mouseleave",()=>{G=-1,M=-1,se=!1}),q.addEventListener("dblclick",h=>{const y=q.getBoundingClientRect(),v=(h.clientX-y.left-K)/L,k=(h.clientY-y.top-X)/L;for(let p=w.length-1;p>=0;p--){const m=w[p],f=v-m.x,x=k-m.y;if(Math.sqrt(f*f+x*x)<m.r+4){Se(m.f.path);break}}}),q.addEventListener("contextmenu",h=>h.preventDefault()),requestAnimationFrame(bt)}async function Se(e){const t=document.getElementById("metrics-detail");if(!t)return;t.innerHTML='<p class="text-muted">Loading file analysis...</p>';const n=await T("/metrics/file?path="+encodeURIComponent(e));if(n.error){t.innerHTML=`<p style="color:var(--danger)">${r(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">${l(n.path)}</h3>
242
+ <h3 style="font-size:0.9rem">${r(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">
246
- ${N("LOC",n.loc)}
247
- ${N("Total Lines",n.total_lines)}
248
- ${N("Classes",n.classes)}
249
- ${N("Functions",i.length)}
250
- ${N("Imports",n.imports?n.imports.length:0)}
246
+ ${F("LOC",n.loc)}
247
+ ${F("Total Lines",n.total_lines)}
248
+ ${F("Classes",n.classes)}
249
+ ${F("Functions",i.length)}
250
+ ${F("Imports",n.imports?n.imports.length:0)}
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,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>
254
+ ${i.sort((s,l)=>l.complexity-s.complexity).map(s=>{const l=s.complexity/o*100,a=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="${r(s.name)}">${r(s.name)}</div>
256
+ <div style="flex:1;height:14px;background:var(--bg);border-radius:2px;overflow:hidden"><div style="width:${l}%;height:100%;background:${a}"></div></div>
257
+ <div style="width:180px;flex-shrink:0;font-family:monospace;text-align:right"><span style="color:${a}">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">${l(e)}</div><div class="value">${l(String(t??0))}</div></div>`}window.__drillDown=ve;let xe=null;function jt(e){e.innerHTML=`
261
+ `}function F(e,t){return`<div class="metric-card"><div class="label">${r(e)}</div><div class="value">${r(String(t??0))}</div></div>`}window.__drillDown=Se;let Te=null;function Gt(e){e.innerHTML=`
262
262
  <div class="dev-panel-header">
263
263
  <h2>GraphQL Explorer</h2>
264
264
  <button class="btn btn-sm" onclick="window.__loadGqlSchema()">Refresh Schema</button>
@@ -285,11 +285,11 @@ ${n.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
285
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
286
  </div>
287
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`
288
+ `,Ze()}async function Ze(){const e=document.getElementById("gql-types"),t=document.getElementById("gql-queries"),n=document.getElementById("gql-mutations");try{const i=await T("/graphql/schema");if(i.error){e&&(e.innerHTML=`<p class="text-sm" style="color:var(--danger)">${r(i.error)}</p>`);return}const o=i.schema||{},s=o.types||{},l=o.queries||{},a=o.mutations||{};if(e){const c=Object.keys(s);c.length?e.innerHTML=c.map(d=>{const b=s[d],_=Object.entries(b).map(([g,E])=>`<div style="padding-left:1rem;color:var(--muted);font-size:0.7rem">${r(g)}: <span style="color:var(--primary)">${r(String(E))}</span></div>`).join("");return`
289
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>
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'">${r(d)}</div>
291
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 {
292
+ </div>`}).join(""):e.innerHTML='<p class="text-sm text-muted">No types registered</p>'}if(t){const c=Object.keys(l);c.length&&(t.innerHTML='<div style="font-weight:600;font-size:0.75rem;color:var(--muted);text-transform:uppercase;margin-bottom:0.25rem">Queries</div>'+c.map(d=>{const b=l[d],_=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('${r(d)}','query')" title="Click to insert">${r(d)}${_?`(${r(_)})`:""}: <span style="color:var(--primary)">${r(b.type||"")}</span></div>`}).join(""))}if(n){const c=Object.keys(a);c.length&&(n.innerHTML='<div style="font-weight:600;font-size:0.75rem;color:var(--muted);text-transform:uppercase;margin-bottom:0.25rem">Mutations</div>'+c.map(d=>{const b=a[d],_=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('${r(d)}','mutation')" title="Click to insert">${r(d)}${_?`(${r(_)})`:""}: <span style="color:var(--primary)">${r(b.type||"")}</span></div>`}).join(""))}}catch(i){e&&(e.innerHTML=`<p class="text-sm" style="color:var(--danger)">${r(i.message)}</p>`)}}function Jt(e,t){const n=document.getElementById("gql-query");n&&(t==="mutation"?n.value=`mutation {
293
293
  ${e} {
294
294
 
295
295
  }
@@ -297,8 +297,93 @@ ${n.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
297
297
  ${e} {
298
298
 
299
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=`
300
+ }`,n.focus())}async function Wt(){var l,a,c;const e=document.getElementById("gql-query"),t=(l=e==null?void 0:e.value)==null?void 0:l.trim();if(!t)return;const n=document.getElementById("gql-error"),i=document.getElementById("gql-result");let o={};const s=(c=(a=document.getElementById("gql-variables"))==null?void 0:a.value)==null?void 0:c.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 d=await T("/query","POST",{query:t,type:"graphql",variables:o});if(Te=d,d.errors&&d.errors.length){const b=d.errors.map(_=>_.message||String(_)).join(`
301
+ `);n&&(n.style.display="block",n.textContent=b)}i&&(i.textContent=JSON.stringify(d.data??d,null,2))}catch(d){n&&(n.style.display="block",n.textContent=d.message),i&&(i.textContent="")}}function Qt(){Te&&navigator.clipboard.writeText(JSON.stringify(Te,null,2))}window.__loadGqlSchema=Ze,window.__runGqlQuery=Wt,window.__copyGqlResult=Qt,window.__insertGqlQuery=Jt;let Ie=!1,qe=null,de="jobs",V="",R="default",ce=["default"],me=null;function Ut(e){me=e,T("/queue/topics").then(t=>{t.topics&&t.topics.length>0&&(ce=t.topics,ce.includes(R)||(R=ce[0])),Me()}).catch(()=>Me())}function Me(){if(!me)return;const e=ce.map(t=>`<option value="${r(t)}" ${t===R?"selected":""}>${r(t)}</option>`).join("");me.innerHTML=`
302
+ <div class="dev-panel-header">
303
+ <h2>Queue Monitor</h2>
304
+ <div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap">
305
+ <select id="queue-topic-select" onchange="window.__queueTopic(this.value)" style="background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:0.25rem;padding:0.25rem 0.5rem;font-size:0.8rem">
306
+ ${e}
307
+ </select>
308
+ <button class="btn btn-sm ${de==="jobs"?"btn-primary":""}" onclick="window.__queueView('jobs')">Jobs</button>
309
+ <button class="btn btn-sm ${de==="dead-letters"?"btn-primary":""}" onclick="window.__queueView('dead-letters')">Dead Letters</button>
310
+ <span style="color:var(--muted)">|</span>
311
+ <label style="font-size:0.75rem;color:var(--muted);cursor:pointer;display:flex;align-items:center;gap:0.25rem">
312
+ <input type="checkbox" ${Ie?"checked":""} onchange="window.__queueAutoRefresh(this.checked)"> Auto
313
+ </label>
314
+ <button class="btn btn-sm" onclick="window.__queueRefresh()">Refresh</button>
315
+ </div>
316
+ </div>
317
+ <div id="queue-stats" style="display:flex;gap:0.75rem;margin-bottom:1rem;flex-wrap:wrap"></div>
318
+ <div id="queue-actions" style="display:flex;gap:0.5rem;margin-bottom:1rem;flex-wrap:wrap"></div>
319
+ <div id="queue-list"></div>
320
+ `,J()}async function J(){if(de==="dead-letters"){Vt();return}try{const e=`?topic=${encodeURIComponent(R)}${V?`&status=${V}`:""}`,t=await T(`/queue${e}`),n=document.getElementById("queue-stats");if(n&&t.stats){const s=t.stats,l=(s.pending||0)+(s.reserved||0)+(s.completed||0)+(s.failed||0);n.innerHTML=`
321
+ <span class="badge" style="background:var(--surface);border:1px solid var(--border);color:var(--text);cursor:pointer" onclick="window.__queueFilter('')">All: ${l}</span>
322
+ <span class="badge" style="background:var(--warn);color:#000;cursor:pointer" onclick="window.__queueFilter('pending')">Pending: ${s.pending||0}</span>
323
+ <span class="badge" style="background:var(--info);cursor:pointer" onclick="window.__queueFilter('reserved')">Reserved: ${s.reserved||0}</span>
324
+ <span class="badge" style="background:var(--success);cursor:pointer" onclick="window.__queueFilter('completed')">Completed: ${s.completed||0}</span>
325
+ <span class="badge" style="background:var(--danger);cursor:pointer" onclick="window.__queueFilter('failed')">Failed: ${s.failed||0}</span>
326
+ ${V?`<span class="badge" style="background:var(--muted);cursor:pointer" onclick="window.__queueFilter('')">&times; Clear</span>`:""}
327
+ `}const i=document.getElementById("queue-actions");i&&(i.innerHTML=`
328
+ <button class="btn btn-sm" style="background:var(--warn);color:#000" onclick="window.__queueRetryAll()">Retry Failed</button>
329
+ <button class="btn btn-sm" style="background:var(--success)" onclick="window.__queuePurge('completed')">Purge Completed</button>
330
+ <button class="btn btn-sm" style="background:var(--danger)" onclick="window.__queuePurge('failed')">Purge Failed</button>
331
+ `);const o=document.getElementById("queue-list");if(!o)return;if(!t.jobs||t.jobs.length===0){o.innerHTML=`<div class="text-muted text-center" style="padding:2rem">No jobs in <strong>${r(R)}</strong>${V?` with status <strong>${r(V)}</strong>`:""}</div>`;return}o.innerHTML=`
332
+ <table class="table" style="font-size:0.8rem">
333
+ <thead>
334
+ <tr>
335
+ <th style="width:80px">ID</th>
336
+ <th style="width:80px;text-align:center">Status</th>
337
+ <th>Payload</th>
338
+ <th style="width:200px">Error</th>
339
+ <th style="width:60px;text-align:center">Actions</th>
340
+ </tr>
341
+ </thead>
342
+ <tbody>
343
+ ${t.jobs.map((s,l)=>(window.__queueJobs=window.__queueJobs||[],window.__queueJobs[l]=s,`
344
+ <tr>
345
+ <td style="font-family:var(--mono);font-size:0.65rem">${r(String(s.id||"").slice(0,8))}</td>
346
+ <td style="text-align:center">${Yt(s.status)}</td>
347
+ <td><code style="font-size:0.7rem;word-break:break-all;max-width:300px;display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer" onclick="window.__queueExpandPayload(this,${l})">${r(JSON.stringify(s.data||{}).slice(0,80))}</code></td>
348
+ <td style="color:var(--danger);font-size:0.7rem;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${r(s.error||"")}">${r(s.error||"-")}</td>
349
+ <td style="text-align:center">
350
+ ${s.status==="failed"||s.status==="dead_letter"?`<button class="btn btn-sm" style="font-size:0.65rem;padding:2px 6px" onclick="window.__queueReplay('${r(String(s.id))}')">Retry</button>`:""}
351
+ </td>
352
+ </tr>`)).join("")}
353
+ </tbody>
354
+ </table>
355
+ `}catch(e){const t=document.getElementById("queue-list");t&&(t.innerHTML=`<div style="color:var(--danger);padding:1rem">${r(e.message||String(e))}</div>`)}}async function Vt(){try{const e=await T(`/queue/dead-letters?topic=${encodeURIComponent(R)}`),t=document.getElementById("queue-stats");t&&(t.innerHTML=`<span class="badge" style="background:var(--danger)">Dead Letters: ${e.count||0}</span>`);const n=document.getElementById("queue-actions");n&&(n.innerHTML=e.count>0?'<button class="btn btn-sm" style="background:var(--warn);color:#000" onclick="window.__queueRetryAll()">Retry All</button>':"");const i=document.getElementById("queue-list");if(!i)return;if(!e.jobs||e.jobs.length===0){i.innerHTML=`<div class="text-muted text-center" style="padding:2rem">No dead letter jobs in <strong>${r(R)}</strong></div>`;return}i.innerHTML=`
356
+ <table class="table" style="font-size:0.8rem">
357
+ <thead>
358
+ <tr>
359
+ <th style="width:80px">ID</th>
360
+ <th>Payload</th>
361
+ <th style="width:200px">Error</th>
362
+ <th style="width:50px;text-align:center">Tries</th>
363
+ <th style="width:60px;text-align:center">Actions</th>
364
+ </tr>
365
+ </thead>
366
+ <tbody>
367
+ ${e.jobs.map((o,s)=>(window.__queueJobs=window.__queueJobs||[],window.__queueJobs[1e3+s]=o,`
368
+ <tr>
369
+ <td style="font-family:var(--mono);font-size:0.65rem">${r(String(o.id||"").slice(0,8))}</td>
370
+ <td><code style="font-size:0.7rem;word-break:break-all;max-width:250px;display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer" onclick="window.__queueExpandPayload(this,${1e3+s})">${r(JSON.stringify(o.data||{}).slice(0,60))}</code></td>
371
+ <td style="color:var(--danger);font-size:0.7rem;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${r(o.error||"")}">${r(o.error||"")}</td>
372
+ <td style="text-align:center">${o.retries||o.attempts||0}</td>
373
+ <td style="text-align:center">
374
+ <button class="btn btn-sm" style="font-size:0.65rem;padding:2px 6px" onclick="window.__queueReplay('${r(String(o.id))}')">Replay</button>
375
+ </td>
376
+ </tr>`)).join("")}
377
+ </tbody>
378
+ </table>
379
+ `}catch(e){const t=document.getElementById("queue-list");t&&(t.innerHTML=`<div style="color:var(--danger);padding:1rem">${r(e.message||String(e))}</div>`)}}function Yt(e){return`<span class="badge" style="background:${{pending:"var(--warn)",reserved:"var(--info)",completed:"var(--success)",failed:"var(--danger)",dead_letter:"#8b0000"}[e]||"var(--muted)"};font-size:0.65rem">${r(e)}</span>`}window.__queueExpandPayload=(e,t)=>{var a;const n=e.closest("tr");if(!n)return;const i=n.nextElementSibling;if(i&&i.classList.contains("queue-payload-row")){i.remove();return}const o=(a=window.__queueJobs)==null?void 0:a[t],s=JSON.stringify((o==null?void 0:o.data)||{},null,2),l=document.createElement("tr");l.className="queue-payload-row",l.innerHTML=`<td colspan="5" style="padding:0.75rem 1rem;background:rgba(0,0,0,0.3)">
380
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem">
381
+ <span style="font-size:0.7rem;color:var(--muted)">Job ${r((o==null?void 0:o.id)||"")} — ${r((o==null?void 0:o.status)||"")}</span>
382
+ <button class="btn btn-sm" style="font-size:0.65rem;padding:2px 8px" onclick="navigator.clipboard.writeText(this.closest('td').querySelector('pre').textContent).then(()=>{this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1000)})">Copy</button>
383
+ </div>
384
+ <pre style="margin:0;font-size:0.75rem;white-space:pre-wrap;color:var(--text);max-height:300px;overflow:auto;background:rgba(0,0,0,0.2);padding:0.5rem;border-radius:0.25rem">${r(s)}</pre>
385
+ ${o!=null&&o.error?`<div style="margin-top:0.5rem;font-size:0.7rem;color:var(--danger)">Error: ${r(o.error)}</div>`:""}
386
+ </td>`,n.after(l)},window.__queueTopic=e=>{R=e,J()},window.__queueView=e=>{de=e,V="",me&&Me()},window.__queueFilter=e=>{V=e,J()},window.__queueRefresh=()=>J(),window.__queueAutoRefresh=e=>{Ie=e,qe&&clearInterval(qe),Ie&&(qe=setInterval(J,3e3))},window.__queueRetryAll=async()=>{await T("/queue/retry","POST",{topic:R}),J()},window.__queuePurge=async e=>{confirm(`Purge all ${e} jobs in ${R}?`)&&(await T("/queue/purge","POST",{status:e,topic:R}),J())},window.__queueReplay=async e=>{await T("/queue/replay","POST",{id:e,topic:R}),J()};const ue={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"}},W={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 pe(e="tina4",t="thinking"){if(e==="tina4"&&W[t]){const i=W[t];return{provider:e,model:i.model,url:i.url,apiKey:""}}const n=ue[e]||ue.tina4;return{provider:e,model:n.model,url:n.url,apiKey:""}}function Le(e,t="thinking"){const n={...pe("tina4",t),...e||{}};return n.provider==="ollama"&&(n.provider="custom"),n.model==="tina4-v1"&&(n.model=""),n.provider==="tina4"&&W[t]&&(n.url=W[t].url),n}function Kt(){try{const e=JSON.parse(localStorage.getItem("tina4_chat_settings")||"{}");return{thinking:Le(e.thinking,"thinking"),vision:Le(e.vision,"vision"),imageGen:Le(e.imageGen,"imageGen")}}catch{return{thinking:pe("tina4","thinking"),vision:pe("tina4","vision"),imageGen:pe("tina4","imageGen")}}}function Xt(e){localStorage.setItem("tina4_chat_settings",JSON.stringify(e)),I=e,Y()}let I=Kt(),A="Idle";const ge=[];function Zt(){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 en(){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 tn(){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>`),be=0}function nn(e){var n,i,o,s,l,a,c,d,b,_;e.innerHTML=`
302
387
  <div class="dev-panel-header">
303
388
  <h2>Code With Me</h2>
304
389
  <div class="flex gap-sm">
@@ -364,76 +449,76 @@ ${n.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
364
449
  <button class="btn btn-primary" id="chat-modal-save" style="width:100%">Save Settings</button>
365
450
  </div>
366
451
  </div>
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=`
452
+ `,(n=document.getElementById("chat-send-btn"))==null||n.addEventListener("click",Z),(i=document.getElementById("chat-thoughts-btn"))==null||i.addEventListener("click",Oe),(o=document.getElementById("chat-thoughts-close"))==null||o.addEventListener("click",Oe),(s=document.getElementById("chat-settings-btn"))==null||s.addEventListener("click",on),(l=document.getElementById("chat-modal-close"))==null||l.addEventListener("click",Ae),(a=document.getElementById("chat-modal-save"))==null||a.addEventListener("click",sn),(c=document.getElementById("chat-modal-overlay"))==null||c.addEventListener("click",g=>{g.target===g.currentTarget&&Ae()}),(d=document.getElementById("chat-file-btn"))==null||d.addEventListener("click",()=>{var g;(g=document.getElementById("chat-file-input"))==null||g.click()}),(b=document.getElementById("chat-file-input"))==null||b.addEventListener("change",vn),(_=document.getElementById("chat-mic-btn"))==null||_.addEventListener("click",wn);const t=document.getElementById("chat-input");t==null||t.addEventListener("keydown",g=>{g.key==="Enter"&&!g.shiftKey&&(g.preventDefault(),Z())}),Y(),en(),loadServerHistory()}function Ce(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,ze(e,t.provider)}function Be(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 ze(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 He(e){const t=document.getElementById(`set-${e}-provider`);t==null||t.addEventListener("change",()=>{let n;t.value==="tina4"&&W[e]?n=W[e]:n=ue[t.value]||ue.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,ze(e,t.value)}),ze(e,(t==null?void 0:t.value)||"custom")}async function Re(e){var l,a,c;const t=((l=document.getElementById(`set-${e}-provider`))==null?void 0:l.value)||"custom";let n=((a=document.getElementById(`set-${e}-url`))==null?void 0:a.value)||"";const i=((c=document.getElementById(`set-${e}-key`))==null?void 0:c.value)||"",o=document.getElementById(`set-${e}-model`),s=document.getElementById(`set-${e}-result`);t==="tina4"&&W[e]&&(n=W[e].url),s&&(s.textContent="Connecting...",s.style.color="var(--muted)");try{let d=[];const b=n.replace(/\/(v1|api)\/.*$/,"").replace(/\/+$/,"");if(t==="tina4"){try{d=((await(await fetch(`${b}/api/tags`)).json()).models||[]).map(S=>S.name||S.model)}catch{}if(!d.length)try{d=((await(await fetch(`${b}/v1/models`)).json()).data||[]).map(S=>S.id)}catch{}}else if(t==="custom"){try{d=((await(await fetch(`${b}/api/tags`)).json()).models||[]).map(S=>S.name||S.model)}catch{}if(!d.length)try{d=((await(await fetch(`${b}/v1/models`)).json()).data||[]).map(S=>S.id)}catch{}}else if(t==="anthropic")d=["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\/.*$/,"");d=((await(await fetch(`${g}/v1/models`,{headers:i?{Authorization:`Bearer ${i}`}:{}})).json()).data||[]).map(w=>w.id).filter(w=>w.startsWith("gpt"))}if(d.length===0){s&&(s.innerHTML='<span style="color:var(--warn)">No models found</span>');return}const _=o.value;o.innerHTML=d.map(g=>`<option value="${g}">${g}</option>`).join(""),d.includes(_)&&(o.value=_),o.disabled=!1,s&&(s.innerHTML=`<span style="color:var(--success)">&#10003; ${d.length} models available</span>`)}catch{s&&(s.innerHTML='<span style="color:var(--danger)">&#10007; Connection failed</span>')}}function on(){var t,n,i;const e=document.getElementById("chat-modal-overlay");e&&(e.style.display="flex",Ce("thinking",I.thinking),Ce("vision",I.vision),Ce("imageGen",I.imageGen),He("thinking"),He("vision"),He("imageGen"),(t=document.getElementById("set-thinking-connect"))==null||t.addEventListener("click",()=>Re("thinking")),(n=document.getElementById("set-vision-connect"))==null||n.addEventListener("click",()=>Re("vision")),(i=document.getElementById("set-imageGen-connect"))==null||i.addEventListener("click",()=>Re("imageGen")))}function Ae(){const e=document.getElementById("chat-modal-overlay");e&&(e.style.display="none")}function sn(){Xt({thinking:Be("thinking"),vision:Be("vision"),imageGen:Be("imageGen")}),Ae()}function Y(){const e=document.getElementById("chat-summary");if(!e)return;const t=ee.length?ee.map(o=>`<div style="margin-bottom:4px;font-size:0.65rem;line-height:1.3">
453
+ <span style="color:var(--muted)">${r(o.time)}</span>
454
+ <span style="color:var(--info);font-size:0.6rem">${r(o.agent)}</span>
455
+ <div>${r(o.text)}</div>
456
+ </div>`).join(""):'<div class="text-muted" style="font-size:0.65rem">No activity yet</div>',n=A==="Idle"?"var(--muted)":A==="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=`
372
457
  <div style="margin-bottom:0.5rem;font-size:0.7rem">
373
- <span style="color:${n}">&#9679;</span> ${l(H)}
458
+ <span style="color:${n}">&#9679;</span> ${r(A)}
374
459
  </div>
375
460
  <div style="font-size:0.65rem;line-height:1.8">
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||"—")}
461
+ ${i(I.thinking)} T: ${r(I.thinking.model||"—")}<br>
462
+ ${i(I.vision)} V: ${r(I.vision.model||"—")}<br>
463
+ ${i(I.imageGen)} I: ${r(I.imageGen.model||"—")}
379
464
  </div>
380
- ${le.length?`
465
+ ${ge.length?`
381
466
  <div style="margin-bottom:0.75rem">
382
467
  <div class="text-muted" style="font-size:0.65rem;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px">Files Changed</div>
383
- ${le.map(o=>`<div class="text-mono" style="font-size:0.65rem;color:var(--success);margin-bottom:2px">${l(o)}</div>`).join("")}
468
+ ${ge.map(o=>`<div class="text-mono" style="font-size:0.65rem;color:var(--success);margin-bottom:2px">${r(o)}</div>`).join("")}
384
469
  </div>
385
470
  `:""}
386
471
  <div>
387
472
  <div class="text-muted" style="font-size:0.65rem;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px">Activity</div>
388
473
  ${t}
389
474
  </div>
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=`
475
+ `}let be=0;function B(e,t){var s,l;const n=document.getElementById("chat-messages");if(!n)return;const i=`msg-${++be}`,o=document.createElement("div");if(o.className=`chat-msg chat-${t}`,o.id=i,o.innerHTML=`
391
476
  <div class="chat-msg-content">${e}</div>
392
477
  <div class="chat-msg-actions" style="display:flex;gap:4px;margin-top:4px;opacity:0.4">
393
478
  <button class="btn btn-sm" style="font-size:0.6rem;padding:1px 6px" onclick="window.__copyMsg('${i}')" title="Copy">Copy</button>
394
479
  <button class="btn btn-sm" style="font-size:0.6rem;padding:1px 6px" onclick="window.__replyMsg('${i}')" title="Reply">Reply</button>
395
480
  <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>
396
481
  </div>
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=`
482
+ `,o.addEventListener("mouseenter",()=>{const a=o.querySelector(".chat-msg-actions");a&&(a.style.opacity="1")}),o.addEventListener("mouseleave",()=>{const a=o.querySelector(".chat-msg-actions");a&&(a.style.opacity="0.4")}),o.querySelector(".chat-answer-input")){const a=o.querySelector("[data-submit-btn]");a&&(a.style.display="inline-block")}if(t==="bot"){const c=(((s=o.querySelector(".chat-msg-content"))==null?void 0:s.textContent)||"").trim().endsWith("?"),d=o.querySelector(".chat-answer-input");if(c&&!d){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
483
  <button class="btn btn-sm" style="font-size:0.65rem;padding:2px 8px" onclick="window.__quickReply('Yes')">Yes</button>
399
484
  <button class="btn btn-sm" style="font-size:0.65rem;padding:2px 8px" onclick="window.__quickReply('No')">No</button>
400
485
  <button class="btn btn-sm" style="font-size:0.65rem;padding:2px 8px" onclick="window.__quickReply('You decide')">You decide</button>
401
486
  <button class="btn btn-sm" style="font-size:0.65rem;padding:2px 8px" onclick="window.__quickReply('Skip')">Skip</button>
402
487
  <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?"...":""}
488
+ `,(l=o.querySelector(".chat-msg-content"))==null||l.appendChild(b)}}n.prepend(o),Zt()}function rn(e){const t=document.getElementById(e);if(!t)return;const n=t.querySelectorAll(".chat-answer-input"),i=[];if(n.forEach(l=>{const a=l.dataset.q||"?",c=l.value.trim();c&&(i.push(`${a}. ${c}`),l.disabled=!0,l.style.opacity="0.6")}),!i.length)return;const o=document.getElementById("chat-input");o&&(o.value=i.join(`
489
+ `),Z());const s=t.querySelector("[data-submit-btn]");s&&(s.style.display="none")}function an(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=an,window.__submitAnswers=rn;function ln(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 dn(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?"...":""}
405
490
 
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=`
491
+ `,i.focus(),i.setSelectionRange(i.value.length,i.value.length))}function cn(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=`
407
492
  <input type="text" class="input" placeholder="Your comment..." style="flex:1;font-size:0.7rem;padding:2px 6px;height:24px">
408
493
  <button class="btn btn-sm" style="font-size:0.6rem;padding:1px 6px;height:24px" onclick="window.__submitComment(this)">Add</button>
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=>`
494
+ `,t.after(n),(o=n.querySelector("input"))==null||o.focus()}function mn(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 et(){const e=[],t=[],n=[];return document.querySelectorAll(".chat-checklist-item").forEach(i=>{var a,c;const o=i.querySelector("input[type=checkbox]"),s=((a=i.querySelector("label"))==null?void 0:a.textContent)||"";o!=null&&o.checked?e.push(s):t.push(s);const l=i.nextElementSibling;if(l&&!l.classList.contains("chat-checklist-item")&&!l.classList.contains("chat-comment-box")){const d=((c=l.textContent)==null?void 0:c.replace("↳ ",""))||"";d&&n.push(`${s}: ${d}`)}}),{accepted:e,rejected:t,comments:n}}let ye=!1;function Oe(){const e=document.getElementById("chat-thoughts-panel");e&&(ye=!ye,e.style.display=ye?"block":"none",ye&&tt())}async function tt(){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=>`
410
495
  <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">
411
- <div style="line-height:1.4">${l(s.message)}</div>
496
+ <div style="line-height:1.4">${r(s.message)}</div>
412
497
  <div style="display:flex;gap:4px;margin-top:0.375rem">
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("")}
498
+ ${(s.actions||[]).map(l=>l.action==="dismiss"?`<button class="btn btn-sm" style="font-size:0.6rem" onclick="window.__dismissThought('${r(s.id)}')">Dismiss</button>`:`<button class="btn btn-sm btn-primary" style="font-size:0.6rem" onclick="window.__actOnThought('${r(s.id)}','${r(l.action)}')">${r(l.label)}</button>`).join("")}
414
499
  </div>
415
500
  </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+=`
501
+ `).join("")}catch{e.innerHTML='<div class="text-muted text-sm" style="text-align:center;padding:1rem">Agent not connected</div>'}}async function nt(e){await fetch("/__dev/api/thoughts/dismiss",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:e})}).catch(()=>{}),tt()}function un(e,t){nt(e),Oe()}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=nt,window.__actOnThought=un,window.__commentOnItem=cn,window.__submitComment=mn,window.__getChecklist=et;function pn(e){document.querySelectorAll(".chat-quick-replies").forEach(n=>n.remove());const t=document.getElementById("chat-input");t&&(t.value=e,Z())}window.__quickReply=pn,window.__copyMsg=ln,window.__replyMsg=dn,window.__clearChat=tn;const ee=[];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 ot(){const e=document.getElementById("chat-status-bar");e&&(e.style.display="none")}function he(e,t){const n=new Date().toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"});ee.unshift({time:n,text:e,agent:t}),ee.length>50&&(ee.length=50),Y()}async function Z(){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(r(t),"user"),D.length){const o=D.map(s=>s.name).join(", ");B(`<span class="text-sm text-muted">Attached: ${r(o)}</span>`,"user")}A="Thinking...",fe("Analyzing request..."),he("Analyzing request...","supervisor");const n={message:t,settings:{thinking:I.thinking,vision:I.vision,imageGen:I.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 c=o.status===0?"Agent not running. Start: tina4 agent":`Error: ${o.status}`;B(`<span style="color:var(--danger)">${c}</span>`,"bot"),A="Error",Y();return}const s=o.body.getReader(),l=new TextDecoder;let a="";for(;;){const{done:c,value:d}=await s.read();if(c)break;a+=l.decode(d,{stream:!0});const b=a.split(`
502
+ `);a=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);Pe(_,S)}catch{}}}D.length=0,je()}catch{B('<span style="color:var(--danger)">Connection failed</span>',"bot"),A="Error",Y()}}function Pe(e,t){switch(e){case"status":A=t.text||"Working...",fe(`${t.agent||"supervisor"}: ${t.text||"Working..."}`),he(t.text||"",t.agent||"supervisor");break;case"message":{const n=t.content||"",i=t.agent||"supervisor";let o=it(n);i!=="supervisor"&&(o=`<span class="badge" style="font-size:0.6rem;margin-right:4px">${r(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">${r(s)}</div>`,ge.includes(s)||ge.push(s)}),o+="</div>"),B(o,"bot");break}case"plan":{let n="";t.content&&(n+=it(t.content)),t.approve&&(n+=`
418
503
  <div style="padding:0.5rem;background:var(--surface);border:1px solid var(--info);border-radius:0.375rem;margin-top:0.75rem">
419
504
  <div class="text-sm text-muted" style="margin-bottom:0.5rem">Uncheck items you don't want. Click + to add comments.</div>
420
505
  <div class="flex gap-sm" style="flex-wrap:wrap">
421
- <button class="btn btn-sm btn-primary" onclick="window.__approvePlan('${l(t.file||"")}')">Approve & Build</button>
506
+ <button class="btn btn-sm btn-primary" onclick="window.__approvePlan('${r(t.file||"")}')">Approve & Build</button>
422
507
  <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>
508
+ <button class="btn btn-sm" onclick="window.__keepPlan('${r(t.file||"")}')">Later</button>
424
509
  <button class="btn btn-sm" onclick="this.closest('.chat-msg').remove()">Dismiss</button>
425
510
  </div>
426
511
  </div>
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=`
512
+ `),t.agent&&t.agent!=="supervisor"&&(n=`<span class="badge" style="font-size:0.6rem;margin-right:4px">${r(t.agent)}</span>`+n),B(n,"bot");break}case"error":ot(),B(`<span style="color:var(--danger)">${r(t.message||"Unknown error")}</span>`,"bot"),A="Error",Y();break;case"plan_failed":{const n=t.completed||0,i=t.total||0,o=t.failed_step||0,s=`
428
513
  <div style="padding:0.5rem;background:var(--surface);border:1px solid var(--warn);border-radius:0.375rem;margin-top:0.25rem">
429
514
  <div class="text-sm" style="margin-bottom:0.5rem">${n} of ${i} steps completed. Failed at step ${o}.</div>
430
515
  <div class="flex gap-sm">
431
- <button class="btn btn-sm btn-primary" onclick="window.__resumePlan('${l(t.file||"")}')">Resume</button>
516
+ <button class="btn btn-sm btn-primary" onclick="window.__resumePlan('${r(t.file||"")}')">Resume</button>
432
517
  <button class="btn btn-sm" onclick="this.closest('.chat-msg').remove()">Dismiss</button>
433
518
  </div>
434
519
  </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:
520
+ `;B(s,"bot");break}case"done":A="Done",ot(),he("Done","supervisor"),setTimeout(()=>{A="Idle",Y()},3e3);break}}async function gn(e){B(`<span style="color:var(--success)">Plan approved — let's build it!</span>`,"user"),A="Executing plan...",he("Plan approved — building...","supervisor"),fe("Building...");const t={plan_file:e,settings:{thinking:I.thinking,vision:I.vision,imageGen:I.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:l,value:a}=await i.read();if(l)break;s+=o.decode(a,{stream:!0});const c=s.split(`
521
+ `);s=c.pop()||"";let d="";for(const b of c)if(b.startsWith("event: "))d=b.slice(7).trim();else if(b.startsWith("data: "))try{Pe(d,JSON.parse(b.slice(6)))}catch{}}}catch{B('<span style="color:var(--danger)">Plan execution failed</span>',"bot")}}function bn(e){B(`<span style="color:var(--muted)">Plan saved for later: ${r(e)}</span>`,"bot")}function yn(){const{accepted:e,rejected:t,comments:n}=et();let i=`Here's my feedback on the proposal:
437
522
 
438
523
  `;e.length&&(i+=`**Keep these:**
439
524
  `+e.map(s=>`- ${s}`).join(`
@@ -447,34 +532,34 @@ ${n.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
447
532
  `+n.map(s=>`- ${s}`).join(`
448
533
  `)+`
449
534
 
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>
535
+ `),!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,Z())}async function fn(e){B('<span style="color:var(--info)">Resuming plan...</span>',"user"),A="Resuming...",fe("Resuming...");const t={plan_file:e,resume:!0,settings:{thinking:I.thinking,vision:I.vision,imageGen:I.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:l,value:a}=await i.read();if(l)break;s+=o.decode(a,{stream:!0});const c=s.split(`
536
+ `);s=c.pop()||"";let d="";for(const b of c)if(b.startsWith("event: "))d=b.slice(7).trim();else if(b.startsWith("data: "))try{Pe(d,JSON.parse(b.slice(6)))}catch{}}}catch{B('<span style="color:var(--danger)">Resume failed</span>',"bot")}}window.__resumePlan=fn,window.__submitFeedback=yn,window.__approvePlan=gn,window.__keepPlan=bn;async function hn(){try{const e=await T("/chat/undo","POST");B(`<span style="color:var(--warn)">${r(e.message||"Undo complete")}</span>`,"bot")}catch{B('<span style="color:var(--warn)">Nothing to undo</span>',"bot")}}const D=[];function vn(){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}),je()},n.readAsDataURL(t)}),e.value="")}function je(){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">
537
+ ${r(t.name)} <span style="cursor:pointer;color:var(--danger)" onclick="window.__removeFile(${n})">&times;</span>
538
+ </span>`).join("")}}function xn(e){D.splice(e,1),je()}let te=!1,P=null;function wn(){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(te&&P){P.stop(),te=!1,e&&(e.textContent="Mic",e.style.background="");return}P=new t,P.continuous=!1,P.interimResults=!1,P.lang="en-US",P.onresult=n=>{const i=n.results[0][0].transcript,o=document.getElementById("chat-input");o&&(o.value=(o.value?o.value+" ":"")+i)},P.onend=()=>{te=!1,e&&(e.textContent="Mic",e.style.background="")},P.onerror=()=>{te=!1,e&&(e.textContent="Mic",e.style.background="")},P.start(),te=!0,e&&(e.textContent="Stop",e.style.background="var(--danger)")}window.__removeFile=xn;function it(e){let t=e.replace(/\\n/g,`
539
+ `);const n=[];t=t.replace(/```(\w*)\n([\s\S]*?)```/g,(l,a,c)=>{const d=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>${r(c)}</code></pre>`),`\0CODE${d}\0`});const i=t.split(`
540
+ `),o=[];for(const l of i){const a=l.trim();if(a.startsWith("\0CODE")){o.push(a);continue}if(a.startsWith("### ")){o.push(`<div style="font-weight:700;font-size:0.8rem;margin:0.75rem 0 0.25rem;color:var(--info)">${r(a.slice(4))}</div>`);continue}if(a.startsWith("## ")){o.push(`<div style="font-weight:700;font-size:0.9rem;margin:0.75rem 0 0.25rem">${r(a.slice(3))}</div>`);continue}if(a.startsWith("# ")){o.push(`<div style="font-weight:700;font-size:1rem;margin:0.75rem 0 0.25rem">${r(a.slice(2))}</div>`);continue}if(a==="---"||a==="***"){o.push('<hr style="border:none;border-top:1px solid var(--border);margin:0.5rem 0">');continue}const c=a.match(/^(\d+)[.)]\s+(.+)/);if(c){if(c[2].trim().endsWith("?")){const b=`q-${be}-${c[1]}`;o.push(`<div style="margin:0.3rem 0;padding-left:0.5rem">
541
+ <div style="margin-bottom:4px"><span style="color:var(--info);font-weight:600;margin-right:0.4rem">${c[1]}.</span>${ne(c[2])}</div>
457
542
  <div style="display:flex;gap:4px;align-items:center;flex-wrap:wrap">
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">
543
+ <input type="text" class="input chat-answer-input" id="${b}" data-q="${c[1]}" placeholder="Your answer..." style="font-size:0.75rem;padding:4px 8px;flex:1;max-width:350px">
459
544
  <button class="btn btn-sm" style="font-size:0.6rem;padding:2px 6px" onclick="window.__quickAnswer(this,'Yes')">Yes</button>
460
545
  <button class="btn btn-sm" style="font-size:0.6rem;padding:2px 6px" onclick="window.__quickAnswer(this,'No')">No</button>
461
546
  <button class="btn btn-sm" style="font-size:0.6rem;padding:2px 6px" onclick="window.__quickAnswer(this,'Later')">Later</button>
462
547
  <button class="btn btn-sm" style="font-size:0.6rem;padding:2px 6px" onclick="window.__quickAnswer(this,'Skip')">Skip</button>
463
548
  </div>
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>
549
+ </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">${c[1]}.</span>${ne(c[2])}</div>`);continue}if(a.startsWith("- ")){const d=`chk-${be}-${o.length}`,b=a.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">
550
+ <input type="checkbox" id="${d}" checked style="margin-top:3px;cursor:pointer;accent-color:var(--success)">
551
+ <label for="${d}" style="flex:1;cursor:pointer">${ne(b)}</label>
467
552
  <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>
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=`
553
+ </div>`);continue}if(a.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">${ne(a.slice(2))}</div>`);continue}if(a===""){o.push('<div style="height:0.4rem"></div>');continue}o.push(`<div style="margin:0.1rem 0">${ne(a)}</div>`)}let s=o.join("");return n.forEach((l,a)=>{s=s.replace(`\0CODE${a}\0`,l)}),s}function ne(e){return r(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 _n(e){const t=document.getElementById("chat-input");t&&(t.value=e,t.focus(),t.scrollTop=t.scrollHeight)}window.__sendChat=Z,window.__undoChat=hn,window.__prefillChat=_n;const st=document.createElement("style");st.textContent=_t,document.head.appendChild(st);const ve=xt();wt(ve);const Ne=[{id:"routes",label:"Routes",render:kt},{id:"database",label:"Database",render:$t},{id:"graphql",label:"GraphQL",render:Gt},{id:"queue",label:"Queue",render:Ut},{id:"errors",label:"Errors",render:zt},{id:"metrics",label:"Metrics",render:Nt},{id:"system",label:"System",render:Ot}],rt={id:"chat",label:"Code With Me",render:nn};let xe=localStorage.getItem("tina4_cwm_unlocked")==="true",we=xe?[rt,...Ne]:[...Ne],oe=xe?"chat":"routes";function kn(){const e=document.getElementById("app");if(!e)return;e.innerHTML=`
469
554
  <div class="dev-admin">
470
555
  <div class="dev-header">
471
556
  <h1><span>Tina4</span> Dev Admin</h1>
472
557
  <div style="display:flex;align-items:center;gap:0.75rem">
473
- <span class="text-sm text-muted" id="version-label" style="cursor:default;user-select:none">${Ye.name} &bull; v3.10.70</span>
558
+ <span class="text-sm text-muted" id="version-label" style="cursor:default;user-select:none">${ve.name} &bull; loading&hellip;</span>
474
559
  <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>
475
560
  </div>
476
561
  </div>
477
562
  <div class="dev-tabs" id="tab-bar"></div>
478
563
  <div class="dev-content" id="tab-content"></div>
479
564
  </div>
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")}})})();
565
+ `;const t=document.getElementById("tab-bar");t.innerHTML=we.map(n=>`<button class="dev-tab ${n.id===oe?"active":""}" data-tab="${n.id}" onclick="window.__switchTab('${n.id}')">${n.label}</button>`).join(""),Fe(oe)}function Fe(e){oe=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=we.find(o=>o.id===e);i&&i.render(n)}function $n(){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=$n,window.__switchTab=Fe,kn(),T("/system").then(e=>{const t=document.getElementById("version-label"),n=e.version||(typeof e.framework=="object"?e.framework.version:null)||(typeof e.framework=="string"?e.framework:null);t&&n&&(t.innerHTML=`${ve.name} &bull; v${r(n)}`)}).catch(()=>{const e=document.getElementById("version-label");e&&(e.innerHTML=`${ve.name}`)});let De=0,Ge=null;(at=document.getElementById("version-label"))==null||at.addEventListener("click",()=>{if(!xe&&(De++,Ge&&clearTimeout(Ge),Ge=setTimeout(()=>{De=0},2e3),De>=5)){xe=!0,localStorage.setItem("tina4_cwm_unlocked","true"),we=[rt,...Ne],oe="chat";const e=document.getElementById("tab-bar");e&&(e.innerHTML=we.map(t=>`<button class="dev-tab ${t.id===oe?"active":""}" data-tab="${t.id}" onclick="window.__switchTab('${t.id}')">${t.label}</button>`).join("")),Fe("chat")}})})();
data/lib/tina4/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.11.9"
4
+ VERSION = "3.11.10"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.11.9
4
+ version: 3.11.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team