tina4ruby 3.13.89 → 3.13.90
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 +4 -4
- data/lib/tina4/cli.rb +39 -16
- data/lib/tina4/public/js/tina4-dev-admin.min.js +2 -2
- data/lib/tina4/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bcacb51cc9aec287d69bb3d18d0d5736d4e72fd9f25e4b28145af7a8c3e69245
|
|
4
|
+
data.tar.gz: 5cbe29c5b764594b014c386ab1df06e93d67cc8ad79ee321a2d5c03c5383cc73
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b01320730d284fb8f8babdefdcd6c26fc3a4ae29e5ee3a2f700320276a1d211d5d3e88fc0c8de4e1722af814a60333a4056041428fd387ec70446b50f066a208
|
|
7
|
+
data.tar.gz: 32077d83e0b6c457656915818683193f8a4ef9b3dfd505e3035d97f7b835b979d29a536f6993aa85d987138e9fe1aa229d1d002fec769879fb0c4ca1dbaef2fa
|
data/lib/tina4/cli.rb
CHANGED
|
@@ -233,6 +233,21 @@ module Tina4
|
|
|
233
233
|
to_snake_case(name)
|
|
234
234
|
end
|
|
235
235
|
|
|
236
|
+
# Called without --fields, the generators fall back to a single `name`
|
|
237
|
+
# string column. That default MUST be materialised here, in one place, and
|
|
238
|
+
# then flow into the model, the migration, the form, the view and the spec
|
|
239
|
+
# alike. It used to live only inside the model template, so `generate model
|
|
240
|
+
# X` / `generate crud X` wrote a model declaring `name` while the migration
|
|
241
|
+
# - built from the parsed field list, which was empty - created only id +
|
|
242
|
+
# created_at. The first write then failed with "no such column: name".
|
|
243
|
+
DEFAULT_FIELDS = [["name", "string"]].freeze
|
|
244
|
+
|
|
245
|
+
# Parsed --fields, or the default single `name` column when none given.
|
|
246
|
+
def fields_or_default(fields_str)
|
|
247
|
+
parsed = parse_fields(fields_str)
|
|
248
|
+
parsed.any? ? parsed : DEFAULT_FIELDS.map(&:dup)
|
|
249
|
+
end
|
|
250
|
+
|
|
236
251
|
# Parse "name:string,price:float" -> [["name","string"], ["price","float"]]
|
|
237
252
|
def parse_fields(fields_str)
|
|
238
253
|
return [] if fields_str.nil? || fields_str.strip.empty?
|
|
@@ -1231,19 +1246,15 @@ module Tina4
|
|
|
1231
1246
|
# ── Generator: model ─────────────────────────────────────────────────
|
|
1232
1247
|
|
|
1233
1248
|
def generate_model(name, flags, emit_test: true)
|
|
1234
|
-
fields =
|
|
1249
|
+
fields = fields_or_default(flags["fields"])
|
|
1235
1250
|
table = to_table_name(name)
|
|
1236
1251
|
snake = to_snake_case(name)
|
|
1237
1252
|
|
|
1238
1253
|
# Build field lines
|
|
1239
1254
|
field_lines = [" integer_field :id, primary_key: true, auto_increment: true"]
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
field_lines << " #{info[:orm]} :#{fname}"
|
|
1244
|
-
end
|
|
1245
|
-
else
|
|
1246
|
-
field_lines << " string_field :name"
|
|
1255
|
+
fields.each do |fname, ftype|
|
|
1256
|
+
info = FIELD_TYPE_MAP[ftype] || FIELD_TYPE_MAP["string"]
|
|
1257
|
+
field_lines << " #{info[:orm]} :#{fname}"
|
|
1247
1258
|
end
|
|
1248
1259
|
field_lines << " string_field :created_at"
|
|
1249
1260
|
|
|
@@ -1355,6 +1366,11 @@ module Tina4
|
|
|
1355
1366
|
Tina4::Router.post "/api/#{route_path}" do |request, response|
|
|
1356
1367
|
#{ext_create.chomp}
|
|
1357
1368
|
item = #{model}.create(request.body)
|
|
1369
|
+
# create/save signal failure by RETURN VALUE, they do not raise -
|
|
1370
|
+
# unchecked, a failed write surfaces as an unrelated NoMethodError
|
|
1371
|
+
# on false and hides the real cause.
|
|
1372
|
+
next response.json({ error: "Could not create #{singular}" }, 400) if item == false
|
|
1373
|
+
|
|
1358
1374
|
response.json(item.to_h, 201)
|
|
1359
1375
|
end#{no_auth}
|
|
1360
1376
|
|
|
@@ -1368,7 +1384,8 @@ module Tina4
|
|
|
1368
1384
|
setter = "#{'#'}{key}="
|
|
1369
1385
|
item.send(setter, value) if item.respond_to?(setter)
|
|
1370
1386
|
end
|
|
1371
|
-
item.save
|
|
1387
|
+
next response.json({ error: "Could not update #{singular}" }, 400) if item.save == false
|
|
1388
|
+
|
|
1372
1389
|
response.json(item.to_h)
|
|
1373
1390
|
end#{no_auth}
|
|
1374
1391
|
|
|
@@ -1521,7 +1538,11 @@ module Tina4
|
|
|
1521
1538
|
end
|
|
1522
1539
|
|
|
1523
1540
|
# Build SQL columns from fields
|
|
1524
|
-
|
|
1541
|
+
# An EMPTY array is truthy in Ruby, so a plain `fields_override || parse`
|
|
1542
|
+
# short-circuits to [] and the fallback never fires - that is exactly how
|
|
1543
|
+
# a model declaring `name` ended up with a column-less migration. Test for
|
|
1544
|
+
# content, not truthiness, so the semantics match Python/Node.
|
|
1545
|
+
fields = fields_override&.any? ? fields_override : parse_fields(flags["fields"])
|
|
1525
1546
|
is_create = name.start_with?("create_") || !fields_override.nil?
|
|
1526
1547
|
|
|
1527
1548
|
filename = "#{timestamp}_#{name}.sql"
|
|
@@ -1771,7 +1792,7 @@ module Tina4
|
|
|
1771
1792
|
# ── Generator: form ──────────────────────────────────────────────────
|
|
1772
1793
|
|
|
1773
1794
|
def generate_form(name, flags = {})
|
|
1774
|
-
fields =
|
|
1795
|
+
fields = fields_or_default(flags["fields"])
|
|
1775
1796
|
table = to_table_name(name)
|
|
1776
1797
|
route_name = "#{table}s"
|
|
1777
1798
|
|
|
@@ -1794,8 +1815,7 @@ module Tina4
|
|
|
1794
1815
|
|
|
1795
1816
|
# Build form fields
|
|
1796
1817
|
field_html = ""
|
|
1797
|
-
|
|
1798
|
-
form_fields.each do |fname, ftype|
|
|
1818
|
+
fields.each do |fname, ftype|
|
|
1799
1819
|
itype = input_types[ftype] || "text"
|
|
1800
1820
|
label = fname.tr("_", " ").split.map(&:capitalize).join(" ")
|
|
1801
1821
|
step = %w[float numeric decimal].include?(ftype) ? ' step="0.01"' : ""
|
|
@@ -1850,11 +1870,11 @@ module Tina4
|
|
|
1850
1870
|
# ── Generator: view ──────────────────────────────────────────────────
|
|
1851
1871
|
|
|
1852
1872
|
def generate_view(name, flags = {})
|
|
1853
|
-
fields =
|
|
1873
|
+
fields = fields_or_default(flags["fields"])
|
|
1854
1874
|
table = to_table_name(name)
|
|
1855
1875
|
route_name = "#{table}s"
|
|
1856
1876
|
|
|
1857
|
-
cols = fields.
|
|
1877
|
+
cols = fields.map { |f, _| f }
|
|
1858
1878
|
|
|
1859
1879
|
dir = "src/templates/pages"
|
|
1860
1880
|
FileUtils.mkdir_p(dir)
|
|
@@ -2480,7 +2500,10 @@ module Tina4
|
|
|
2480
2500
|
|
|
2481
2501
|
# model -> real SQLite roundtrip (create / read back / missing -> nil).
|
|
2482
2502
|
def emit_model_test(model, table, fields)
|
|
2483
|
-
|
|
2503
|
+
# Reuse the single DEFAULT_FIELDS constant rather than re-stating the
|
|
2504
|
+
# literal, so the co-emitted spec can never describe a shape the model
|
|
2505
|
+
# does not actually have.
|
|
2506
|
+
fields = fields.empty? ? DEFAULT_FIELDS.map(&:dup) : fields
|
|
2484
2507
|
payload = fields.map { |fname, ftype| %("#{fname}" => #{sample_literal(ftype)}) }.join(", ")
|
|
2485
2508
|
# Assert a STRING field round-trips (type-safe); else just the id round-trips
|
|
2486
2509
|
# (avoids datetime/bool/float equality pitfalls on the read-back).
|
|
@@ -985,7 +985,7 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
|
|
|
985
985
|
<span class="thread-pip"></span>
|
|
986
986
|
<span class="thread-title">${g(t.title)}</span>
|
|
987
987
|
${o}
|
|
988
|
-
</div>`}).join("")}let cd=!1;async function Qa(i=0){for(let e=0;;e++)try{Se=await NZ(),cd=!1,EQ();return}catch(t){if(e>=i){cd=!0,console.error("refreshThreadList failed",t);return}await new Promise(n=>setTimeout(n,250*2**e))}}function HZ(i){const e=document.getElementById("editor-ai-messages");if(!e)return;const t=rt.get(i)||[];if(!t.length){e.innerHTML='<div class="ai-msg ai-bot" style="opacity:0.6">Start the conversation…</div>';return}e.innerHTML="";for(const n of t){const r=document.createElement("div");r.className=`ai-msg ai-${n.role==="user"?"user":"bot"}`,n.role==="user"?r.textContent=n.content:r.innerHTML=fs(n.content),e.appendChild(r)}e.scrollTop=e.scrollHeight}async function AQ(i,e=!1){if(!(!e&&rt.has(i)))try{const t=await FZ(i);rt.set(i,t)}catch(t){console.error(`loadThreadMessages(${i}) failed`,t),rt.set(i,[])}}async function dd(i){if(i!==ve){ve=i;try{localStorage.setItem(WQ,i)}catch{}EQ(),await AQ(i),HZ(i)}}async function KZ(){try{const i=await cs();Se.push(i),rt.set(i.id,[]),await dd(i.id);const e=document.getElementById("editor-ai-input");e==null||e.focus()}catch(i){AZ(`<span style="color:var(--danger)">Couldn't create thread: ${g(String((i==null?void 0:i.message)||i))}</span>`,"bot")}}async function JZ(i){const e=document.querySelector(`.thread-row[data-thread-id="${CSS.escape(i)}"]`),t=e==null?void 0:e.querySelector(".thread-title");if(!e||!t)return;const n=t.textContent||"",r=document.createElement("input");r.className="thread-title-edit",r.value=n,t.replaceWith(r),r.focus(),r.select();const s=a=>{const l=document.createElement("span");l.className="thread-title",l.textContent=a,r.replaceWith(l)},o=async()=>{const a=r.value.trim();if(!a||a===n){s(n);return}try{const l=await ga(i,{title:a}),O=Se.find(c=>c.id===i);O&&(O.title=l.title),s(l.title)}catch(l){console.error("rename failed",l),s(n)}};r.addEventListener("blur",()=>{o()}),r.addEventListener("keydown",a=>{a.key==="Enter"?(a.preventDefault(),r.blur()):a.key==="Escape"&&(a.preventDefault(),s(n))})}async function ez(){if(await Qa(4),!Se.length){ve=null,er();return}try{const i=localStorage.getItem(WQ);i&&Se.some(e=>e.id===i)&&(ve=i)}catch{}ya()}window.__editorThreadNew=()=>{KZ()},window.__editorThreadSwitch=i=>{dd(i)},window.__editorThreadRename=i=>{JZ(i)};const tz={done:"DONE",awaiting_customer:"AWAITING YOU",wont_do:"WONT DO",blocked:"BLOCKED",feedback:"NEW FEEDBACK",idle:"IDLE",running:"RUNNING"};function hd(i){const e=tz[i]||i.toUpperCase();return`<span class="status-pill" data-status="${g(i)}">${g(e)}</span>`}function fd(i){if(!i)return"";let e;if(/^\d+Z$/.test(i)?e=new Date(parseInt(i,10)*1e3):e=new Date(i),isNaN(e.getTime()))return i;const t=n=>String(n).padStart(2,"0");return`${t(e.getDate())}/${t(e.getMonth()+1)}/${e.getFullYear()} ${t(e.getHours())}:${t(e.getMinutes())}`}let Jn=null;function ya(){const i=
|
|
988
|
+
</div>`}).join("")}let cd=!1;async function Qa(i=0){for(let e=0;;e++)try{Se=await NZ(),cd=!1,EQ();return}catch(t){if(e>=i){cd=!0,console.error("refreshThreadList failed",t);return}await new Promise(n=>setTimeout(n,250*2**e))}}function HZ(i){const e=document.getElementById("editor-ai-messages");if(!e)return;const t=rt.get(i)||[];if(!t.length){e.innerHTML='<div class="ai-msg ai-bot" style="opacity:0.6">Start the conversation…</div>';return}e.innerHTML="";for(const n of t){const r=document.createElement("div");r.className=`ai-msg ai-${n.role==="user"?"user":"bot"}`,n.role==="user"?r.textContent=n.content:r.innerHTML=fs(n.content),e.appendChild(r)}e.scrollTop=e.scrollHeight}async function AQ(i,e=!1){if(!(!e&&rt.has(i)))try{const t=await FZ(i);rt.set(i,t)}catch(t){console.error(`loadThreadMessages(${i}) failed`,t),rt.set(i,[])}}async function dd(i){if(i!==ve){ve=i;try{localStorage.setItem(WQ,i)}catch{}EQ(),await AQ(i),HZ(i)}}async function KZ(){try{const i=await cs();Se.push(i),rt.set(i.id,[]),await dd(i.id);const e=document.getElementById("editor-ai-input");e==null||e.focus()}catch(i){AZ(`<span style="color:var(--danger)">Couldn't create thread: ${g(String((i==null?void 0:i.message)||i))}</span>`,"bot")}}async function JZ(i){const e=document.querySelector(`.thread-row[data-thread-id="${CSS.escape(i)}"]`),t=e==null?void 0:e.querySelector(".thread-title");if(!e||!t)return;const n=t.textContent||"",r=document.createElement("input");r.className="thread-title-edit",r.value=n,t.replaceWith(r),r.focus(),r.select();const s=a=>{const l=document.createElement("span");l.className="thread-title",l.textContent=a,r.replaceWith(l)},o=async()=>{const a=r.value.trim();if(!a||a===n){s(n);return}try{const l=await ga(i,{title:a}),O=Se.find(c=>c.id===i);O&&(O.title=l.title),s(l.title)}catch(l){console.error("rename failed",l),s(n)}};r.addEventListener("blur",()=>{o()}),r.addEventListener("keydown",a=>{a.key==="Enter"?(a.preventDefault(),r.blur()):a.key==="Escape"&&(a.preventDefault(),s(n))})}async function ez(){if(await Qa(4),!Se.length){ve=null,er();return}try{const i=localStorage.getItem(WQ);i&&Se.some(e=>e.id===i)&&(ve=i)}catch{}ya()}window.__editorThreadNew=()=>{KZ()},window.__editorThreadSwitch=i=>{dd(i)},window.__editorThreadRename=i=>{JZ(i)};const tz={done:"DONE",awaiting_customer:"AWAITING YOU",wont_do:"WONT DO",blocked:"BLOCKED",feedback:"NEW FEEDBACK",idle:"IDLE",running:"RUNNING"};function hd(i){const e=tz[i]||i.toUpperCase();return`<span class="status-pill" data-status="${g(i)}">${g(e)}</span>`}function fd(i){if(!i)return"";let e;if(/^\d+Z$/.test(i)?e=new Date(parseInt(i,10)*1e3):e=new Date(i),isNaN(e.getTime()))return i;const t=n=>String(n).padStart(2,"0");return`${t(e.getDate())}/${t(e.getMonth()+1)}/${e.getFullYear()} ${t(e.getHours())}:${t(e.getMinutes())}`}let Jn=null;function ya(){const i=s=>document.getElementById(s),e=i("threads-pane-head-list");e&&(e.hidden=!1);const t=i("threads-pane-head-detail");t&&(t.hidden=!0);const n=i("threads-list-view");n&&(n.hidden=!1);const r=i("threads-detail-view");r&&(r.hidden=!0),Qa().then(er)}async function ba(i){await dd(i);const e=Se.find(c=>c.id===i);if(!e)return;const t=c=>document.getElementById(c),n=t("threads-pane-head-list");n&&(n.hidden=!0);const r=t("threads-pane-head-detail");r&&(r.hidden=!1);const s=t("threads-list-view");s&&(s.hidden=!0);const o=t("threads-detail-view");o&&(o.hidden=!1);const a=t("threads-detail-title");a&&(a.textContent=e.title||"Thread");const l=t("threads-detail-meta"),O=e.sender?`<span>📨 from ${g(e.sender)}</span>`:"";l&&(l.innerHTML=`${hd(e.status_hint||"idle")} <span>${g(fd(e.last_message_at))}</span> ${O}`),pd(i),setTimeout(()=>{var c;return(c=t("threads-reply-input"))==null?void 0:c.focus()},30)}async function iz(){if(!ve)return;const i=ve;try{await ga(i,{archived:!0,closure_reason:"done"});const e=Se.find(t=>t.id===i);e&&(e.archived=!0,e.closure_reason="done"),ve=null,ya()}catch(e){console.error("archive failed",e)}}async function nz(i){try{await ga(i,{archived:!0});const e=Se.find(t=>t.id===i);e&&(e.archived=!0),ve===i?(ve=null,ya()):er()}catch(e){console.error("archive-from-list failed",e)}}function er(){const i=document.getElementById("threads-rows");if(!i)return;if(!Se.length){i.innerHTML=cd?`<div class="threads-empty">
|
|
989
989
|
<div style="margin-bottom:0.6rem">Can't reach the agent — threads unavailable.</div>
|
|
990
990
|
<button type="button" class="action-pill"
|
|
991
991
|
onclick="window.__threadsRetry()">Retry</button>
|
|
@@ -1012,7 +1012,7 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
|
|
|
1012
1012
|
<span>severity: ${g(e.severity||"-")}</span>
|
|
1013
1013
|
</div>
|
|
1014
1014
|
<div style="margin-top:0.3rem">${g(e.summary||"")}</div>
|
|
1015
|
-
</div>`}catch{return fs(i)}}async function az(){try{const i=await cs();Se.push(i),rt.set(i.id,[]),await ba(i.id)}catch(i){console.error("threadsNew failed",i)}}async function lz(){if(!ve)return;const i=Se.find(t=>t.id===ve);if(!i)return;const e=window.prompt("Rename thread:",i.title);if(!(e==null||e.trim()===""||e===i.title))try{const t=await ga(ve,{title:e.trim()});i.title=t.title;const n=document.getElementById("threads-detail-title");n&&(n.textContent=t.title),er()}catch(t){console.error("rename failed",t)}}function Oz(i){return i?i.status_hint==="done"||i.status_hint==="wont_do"||i.closure_reason==="done"||i.closure_reason==="wont_do":!1}async function md(){const i=document.getElementById("threads-reply-input");if(!i)return;let e=i.value.trim();if(!e)return;i.value="";const t=e.match(/^new topic:\s*(.*)$/is);if(t){const o=t[1].trim();try{const a=await cs(o.slice(0,80)||void 0);if(Se.push(a),rt.set(a.id,[]),await ba(a.id),!o)return;e=o}catch(a){console.error("New Topic spawn failed",a);return}}if(!t&&ve&&Oz(Se.find(o=>o.id===ve)))try{const o=await cs(e.slice(0,80)||void 0);Se.push(o),rt.set(o.id,[]),await ba(o.id)}catch(o){console.error("auto-new-thread on done reply failed",o);return}if(!ve)try{const o=await cs(e.slice(0,80));Se.push(o),rt.set(o.id,[]),ve=o.id}catch(o){console.error("auto-create failed",o);return}const n=ve,r=rt.get(n)||[];r.push({id:`local-${Date.now()}`,role:"user",content:e,timestamp:new Date().toISOString(),thread_id:n}),rt.set(n,r),pd(n),$a.add(n),er();const s=document.getElementById("threads-chat");Jn==null||Jn.abort(),Jn=new AbortController;try{await pz(e,n,Jn.signal,s)}catch(o){if((o==null?void 0:o.name)!=="AbortError"){const a=document.createElement("div");a.className="ai-msg ai-bot",a.style.color="var(--danger,#f38ba8)",a.textContent=`Connection failed: ${(o==null?void 0:o.message)||o}`,s==null||s.appendChild(a)}}finally{$a.delete(n),Jn=null;try{await AQ(n,!0)}catch{}await Qa(),pd(n);const o=Se.find(
|
|
1015
|
+
</div>`}catch{return fs(i)}}async function az(){try{const i=await cs();Se.push(i),rt.set(i.id,[]),await ba(i.id)}catch(i){console.error("threadsNew failed",i)}}async function lz(){if(!ve)return;const i=Se.find(t=>t.id===ve);if(!i)return;const e=window.prompt("Rename thread:",i.title);if(!(e==null||e.trim()===""||e===i.title))try{const t=await ga(ve,{title:e.trim()});i.title=t.title;const n=document.getElementById("threads-detail-title");n&&(n.textContent=t.title),er()}catch(t){console.error("rename failed",t)}}function Oz(i){return i?i.status_hint==="done"||i.status_hint==="wont_do"||i.closure_reason==="done"||i.closure_reason==="wont_do":!1}async function md(){const i=document.getElementById("threads-reply-input");if(!i)return;let e=i.value.trim();if(!e)return;i.value="";const t=e.match(/^new topic:\s*(.*)$/is);if(t){const o=t[1].trim();try{const a=await cs(o.slice(0,80)||void 0);if(Se.push(a),rt.set(a.id,[]),await ba(a.id),!o)return;e=o}catch(a){console.error("New Topic spawn failed",a);return}}if(!t&&ve&&Oz(Se.find(o=>o.id===ve)))try{const o=await cs(e.slice(0,80)||void 0);Se.push(o),rt.set(o.id,[]),await ba(o.id)}catch(o){console.error("auto-new-thread on done reply failed",o);return}if(!ve)try{const o=await cs(e.slice(0,80));Se.push(o),rt.set(o.id,[]),ve=o.id}catch(o){console.error("auto-create failed",o);return}const n=ve,r=rt.get(n)||[];r.push({id:`local-${Date.now()}`,role:"user",content:e,timestamp:new Date().toISOString(),thread_id:n}),rt.set(n,r),pd(n),$a.add(n),er();const s=document.getElementById("threads-chat");Jn==null||Jn.abort(),Jn=new AbortController;try{await pz(e,n,Jn.signal,s)}catch(o){if((o==null?void 0:o.name)!=="AbortError"){const a=document.createElement("div");a.className="ai-msg ai-bot",a.style.color="var(--danger,#f38ba8)",a.textContent=`Connection failed: ${(o==null?void 0:o.message)||o}`,s==null||s.appendChild(a)}}finally{$a.delete(n),Jn=null;try{await AQ(n,!0)}catch{}await Qa(),pd(n);const o=Se.find(l=>l.id===n),a=document.getElementById("threads-detail-meta");if(o&&a){const l=o.sender?`<span>📨 from ${g(o.sender)}</span>`:"";a.innerHTML=`${hd(o.status_hint||"idle")} <span>${g(fd(o.last_message_at))}</span> ${l}`}}}queueMicrotask(()=>{const i=document.getElementById("threads-reply-form");i&&i.addEventListener("submit",t=>{t.preventDefault(),md()});const e=document.getElementById("threads-reply-input");e&&e.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),t.stopPropagation(),md())})}),window.__threadsShowList=()=>ya(),window.__threadsShowDetail=i=>{ba(i)},window.__threadsNew=()=>{az()},window.__threadsRetry=()=>{Qa(2).then(er)},window.__threadsRenameActive=()=>{lz()};let un=!1;async function jQ(){const i=document.getElementById("plans-panel"),e=document.getElementById("plans-toggle-btn");i&&(un=!un,i.hidden=!un,e==null||e.classList.toggle("active",un),un&&await cz())}async function cz(){const i=document.getElementById("plans-rows");if(i){i.innerHTML='<div class="threads-empty">Loading plans…</div>';try{const e=await Mi("plan_list",{}),t=e.ok&&Array.isArray(e.result)?e.result:[];if(!t.length){i.innerHTML='<div class="threads-empty">No plans yet — they appear here when the planner agent creates one.</div>';return}const n=[...t].sort((r,s)=>String(s.name||s.file||"").localeCompare(String(r.name||r.file||"")));i.innerHTML=n.map(r=>{var h,f;const s=String(r.name||r.file||""),o=String(r.path||`plan/${s}`),a=String(r.title||s).slice(0,60),l=r.steps_done??((h=r.progress)==null?void 0:h.done)??0,O=r.steps_total??((f=r.progress)==null?void 0:f.total)??0,c=O>0?`${l}/${O} steps`:"",d=r.is_current||r.current?" · ★ current":"";return`<div class="plan-row" onclick="window.__plansOpen('${g(o)}')" title="Open ${g(o)} in editor">
|
|
1016
1016
|
<div class="plan-name">${g(a)}</div>
|
|
1017
1017
|
<div class="plan-meta">${g(s)}${c?" · "+g(c):""}${d}</div>
|
|
1018
1018
|
</div>`}).join("")}catch{i.innerHTML='<div class="threads-empty" style="color:var(--danger,#f38ba8)">Failed to load plans</div>'}}}async function dz(i){var t;try{await ui(i)}catch(n){console.error("plansOpen failed",n)}un=!1;const e=document.getElementById("plans-panel");e&&(e.hidden=!0),(t=document.getElementById("plans-toggle-btn"))==null||t.classList.remove("active")}window.__plansToggle=()=>{jQ()},window.__plansOpen=i=>{dz(i)};let ds=!1;async function hz(){const i=document.getElementById("grounding-panel"),e=document.getElementById("grounding-toggle-btn");i&&(un&&jQ(),ds=!ds,i.hidden=!ds,e==null||e.classList.toggle("active",ds),ds&&await MQ())}async function MQ(){const i=document.getElementById("grounding-body");if(!i)return;i.innerHTML='<div class="threads-empty">Loading…</div>';let e={};try{const r=await fetch("/__dev/api/grounding/status");r.ok&&(e=await r.json())}catch{}const t=g(e.url||"https://mcp.tina4.com"),n=e.configured?`<span style="color:var(--success,#a6e3a1)">● Configured</span> <span style="opacity:0.6">(…${g(e.last4||"")})</span>`:'<span style="color:var(--warn,#f9e2af)">○ Not set</span> — using local corpus fallback';i.innerHTML=`
|
data/lib/tina4/version.rb
CHANGED