symbiot 0.58.2 → 0.58.3
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.
- package/CHANGELOG.md +17 -0
- package/README.md +2 -0
- package/agents.mjs +67 -18
- package/core.mjs +29 -3
- package/guard.mjs +4 -3
- package/headless.mjs +105 -9
- package/home.mjs +8 -6
- package/index.mjs +60 -7
- package/lanes.mjs +9 -5
- package/marketing.mjs +86 -11
- package/mind.mjs +96 -20
- package/package.json +4 -3
- package/post.mjs +4 -2
- package/server.mjs +13 -3
- package/tray.mjs +154 -0
- package/ui.mjs +157 -32
- package/watch.mjs +57 -10
package/ui.mjs
CHANGED
|
@@ -122,6 +122,10 @@ body.lq-liquid .tabs{display:none}
|
|
|
122
122
|
body.lq-liquid main{display:none}
|
|
123
123
|
body.lq-liquid.lq-pooled main{display:block;position:fixed;top:64px;left:max(12px,4vw);right:max(12px,4vw);bottom:88px;z-index:5;border-radius:24px;background:var(--ink);border:1px solid rgba(220,228,240,.25);box-shadow:0 30px 80px -30px rgba(0,0,0,.8)}
|
|
124
124
|
#lqsinkrow{display:none}
|
|
125
|
+
#lqwhy{display:none}
|
|
126
|
+
body.lq-liquid.lq-pooled #lqwhy.on{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0 0 12px;color:#8A919B;font-size:13px;line-height:1.5}
|
|
127
|
+
#lqwhy .lqmb{min-height:34px;padding:5px 13px;font-size:13px}
|
|
128
|
+
body.lq-light #lqwhy{color:#5A6470}
|
|
125
129
|
body.lq-liquid.lq-pooled #lqsinkrow{display:flex;justify-content:space-between;align-items:center;gap:12px;position:sticky;top:0;z-index:2;margin:-8px -6px 10px;padding:6px 6px 10px;border-bottom:1px solid rgba(220,228,240,.12);background:rgba(6,8,11,.9);backdrop-filter:blur(14px)}
|
|
126
130
|
body.lq-liquid.lq-light.lq-pooled #lqsinkrow{background:rgba(246,248,251,.94);border-bottom-color:rgba(20,30,45,.1)}
|
|
127
131
|
/* where you are: the section (or the project) you opened, named large at the top of its panel */
|
|
@@ -374,6 +378,9 @@ details.steps summary::-webkit-details-marker{display:none}
|
|
|
374
378
|
details.steps summary::before{content:'';width:5px;height:5px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(-45deg);transition:transform .2s ease}
|
|
375
379
|
details.steps[open] summary::before{transform:rotate(45deg)}
|
|
376
380
|
details.steps ul{margin:6px 0 0;padding:0 0 0 12px;list-style:none;border-left:1px solid var(--line);display:flex;flex-direction:column;gap:3px}
|
|
381
|
+
.lqmsg .lqalts{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:8px;color:#8A919B;font-size:13px}
|
|
382
|
+
.lqmsg .lqalts .lqmb{min-height:36px;padding:6px 14px;font-size:13px;text-shadow:none}
|
|
383
|
+
body.lq-light .lqmsg .lqalts{color:#5A6470}
|
|
377
384
|
.lqmsg details.steps{color:#8A919B}
|
|
378
385
|
body.lq-light .lqmsg details.steps{color:#5A6470}
|
|
379
386
|
.thinking{background:linear-gradient(90deg,#8C96A6 0%,#FFFFFF 40%,#8C96A6 60%,#8C96A6 100%);background-size:220% 100%;-webkit-background-clip:text;background-clip:text;color:transparent !important;animation:lqshim 2.2s linear infinite}
|
|
@@ -482,7 +489,17 @@ body.lq-liquid .rcard.pcard,body.lq-liquid.lq-light .rcard.pcard{border-left:3px
|
|
|
482
489
|
.tag.cust{background:transparent;border-color:var(--amber);color:var(--amber)}
|
|
483
490
|
/* Marketing: the product an item markets, a quiet word before it; what needs you, ruled amber; the Add row */
|
|
484
491
|
.mkp{display:inline-block;margin-right:8px;padding-right:8px;border-right:1px solid var(--line);font:600 10.5px/1.4 var(--sans);letter-spacing:.09em;text-transform:uppercase;color:var(--green);vertical-align:1px}
|
|
485
|
-
.mkst{font-size:11px;padding:1px 7px;border-radius:999px;border:1px solid var(--line);color:var(--faint)}.mkst.approved{color:var(--green);border-color:var(--green)}
|
|
492
|
+
.mkst{font-size:11px;padding:1px 7px;border-radius:999px;border:1px solid var(--line);color:var(--faint)}.mkst.approved{color:var(--green);border-color:var(--green)}.mkst.posted{color:var(--green);border-color:var(--green);background:var(--green-dim)}.mkst.superseded{text-decoration:line-through}
|
|
493
|
+
/* the pick tray: a card a capture, its blur boxes on it (solid: blurred; dashed: shown), each a click to switch; Use puts it on a post */
|
|
494
|
+
.mktray{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,380px),1fr));gap:14px;align-items:start}
|
|
495
|
+
.mkcap{display:flex;flex-direction:column;gap:8px;min-width:0;padding:12px;border:1px solid var(--line);border-radius:12px;background:var(--ink2)}
|
|
496
|
+
body.lq-liquid .mkcap{border-radius:18px;border-color:rgba(255,255,255,.08);background:rgba(255,255,255,.035)}body.lq-liquid.lq-light .mkcap{border-color:rgba(21,26,33,.08);background:rgba(255,255,255,.75)}
|
|
497
|
+
.mkcap.busy .mkshot{opacity:.6}.mkshot{position:relative;line-height:0;border-radius:8px;overflow:hidden;background:var(--ink3)}.mkshot img,.mkshot video{display:block;width:100%;height:auto}
|
|
498
|
+
.mkbox{all:unset;box-sizing:border-box;position:absolute;border:1.5px dashed var(--amber);border-radius:3px;cursor:pointer;background:rgba(242,165,65,.08)}.mkbox.on{border:1.5px solid var(--green);background:rgba(10,12,14,.28)}
|
|
499
|
+
.mkbox:hover{box-shadow:0 0 0 2px rgba(255,255,255,.55)}.mkbox:focus-visible{outline:2px solid var(--bone);outline-offset:1px}
|
|
500
|
+
.mkcapt{font-size:13px;line-height:1.45;color:var(--text);overflow-wrap:anywhere}.mkcapm{font-size:11.5px;color:var(--faint)}
|
|
501
|
+
.mkuse{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.mkuse button{padding:4px 10px;font-size:12px}#mktray .mkuse select{width:auto;max-width:100%;min-height:0;padding:4px 30px 4px 10px;border-radius:999px;font-size:12px;background-position:right 11px center}
|
|
502
|
+
.mkchips{display:flex;flex-wrap:wrap;gap:5px}.mkchip{all:unset;box-sizing:border-box;cursor:pointer;padding:2px 9px;border-radius:999px;border:1px dashed var(--amber);color:var(--text);font:500 11.5px/1.5 var(--sans)}.mkchip.on{border:1px solid var(--green);color:var(--green)}.mkchip:hover,.mkchip:focus-visible{outline:2px solid var(--bone);outline-offset:1px}.mkshot .mkbox.hl{box-shadow:0 0 0 2px var(--bone)}
|
|
486
503
|
.mkpv{margin:6px 0 14px}.mkpv .mkpvact{margin-top:8px}.mkpv .pchars{font-size:11px;color:var(--faint)}
|
|
487
504
|
.lipost{max-width:552px;background:#fff;color:#191919;border:1px solid #e0dfdc;border-radius:10px;padding:12px 16px 4px;font:14px/1.43 -apple-system,system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
|
|
488
505
|
.liauth{display:flex;gap:8px;align-items:flex-start;margin-bottom:10px}
|
|
@@ -1066,7 +1083,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
1066
1083
|
<button class="tab" data-tab="settings">Settings</button>
|
|
1067
1084
|
</div>
|
|
1068
1085
|
<main>
|
|
1069
|
-
<div id="lqsinkrow"><div class="lqwhere"><span id="lqcrumb" class="lqcrumb">Home</span><h2 id="lqtitle" aria-live="polite"></h2><button type="button" class="ghost hidden" id="lqpark"></button></div><div class="lqacts"><span id="lqtools"><button class="ghost hidden" type="button" id="pushtasks" title="Write .symbiot/TASKS.md into each repo for your coding agent">Send to repos</button></span><button class="iconbtn wide" type="button" id="lqsink" title="close it: it sinks back into the liquid (Esc, or right-click)"><svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><path d="M2 2l8 8M10 2l-8 8" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>Close</button></div></div>
|
|
1086
|
+
<div id="lqsinkrow"><div class="lqwhere"><span id="lqcrumb" class="lqcrumb">Home</span><h2 id="lqtitle" aria-live="polite"></h2><button type="button" class="ghost hidden" id="lqpark"></button></div><div class="lqacts"><span id="lqtools"><button class="ghost hidden" type="button" id="pushtasks" title="Write .symbiot/TASKS.md into each repo for your coding agent">Send to repos</button></span><button class="iconbtn wide" type="button" id="lqsink" title="close it: it sinks back into the liquid (Esc, or right-click)"><svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><path d="M2 2l8 8M10 2l-8 8" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>Close</button></div></div><div id="lqwhy" aria-live="polite"></div>
|
|
1070
1087
|
<section id="panel-map">
|
|
1071
1088
|
<div class="profile muted" id="profile">Mapping your work…</div>
|
|
1072
1089
|
<div class="maprow">
|
|
@@ -1137,6 +1154,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
1137
1154
|
<div id="mkmsg"></div>
|
|
1138
1155
|
<div id="mkneeds"></div>
|
|
1139
1156
|
<div id="mktasks"></div>
|
|
1157
|
+
<div id="mktray"></div>
|
|
1140
1158
|
<div id="boardposts"></div>
|
|
1141
1159
|
<div id="mkreplies"></div>
|
|
1142
1160
|
<div id="mktest"></div>
|
|
@@ -1264,6 +1282,23 @@ var SELWAIT={};function selWait(key,f){SELWAIT[key]=f;}
|
|
|
1264
1282
|
if(document.addEventListener)document.addEventListener('selectionchange',function(){var ks=Object.keys(SELWAIT);if(!ks.length)return;var s=window.getSelection&&window.getSelection();if(s&&!s.isCollapsed)return;var w=SELWAIT;SELWAIT={};ks.forEach(function(k){try{w[k]();}catch(e){}});});
|
|
1265
1283
|
function whenFree(el,key,f){if(selIn(el)){selWait(key,f);return false;}f();return true;}
|
|
1266
1284
|
function sameHtml(el,h){if(el._h===h)return true;el.innerHTML=h;el._h=h;return false;}
|
|
1285
|
+
// As sameHtml, but the card you're typing in (a child of el with data-key, holding
|
|
1286
|
+
// the focus) stays the very same node: the rest is rebuilt around it, and it takes
|
|
1287
|
+
// its new twin's place and data-i without leaving the page, so its text, focus and
|
|
1288
|
+
// cursor survive. Home redrew every card on any change (a count, an agent's step)
|
|
1289
|
+
// and wiped a half-typed answer (2026-10-09). With no twin (the card's gone) it's
|
|
1290
|
+
// rebuilt as usual: its draft is kept anyway (bdSet). Its content may be a redraw
|
|
1291
|
+
// behind, so the next call draws it afresh.
|
|
1292
|
+
function keepTyping(el,h){if(el._h===h)return true;
|
|
1293
|
+
var f=document.activeElement,k=f&&f!==document.body&&el.contains&&el.contains(f)&&f.closest?f.closest('[data-key]'):null;
|
|
1294
|
+
if(!k||k.parentNode!==el){el.innerHTML=h;el._h=h;return false;}
|
|
1295
|
+
var t=document.createElement('div');t.innerHTML=h;var key=k.getAttribute('data-key'),twin=null;
|
|
1296
|
+
for(var c=t.firstChild;c;c=c.nextSibling)if(c.getAttribute&&c.getAttribute('data-key')===key){twin=c;break;}
|
|
1297
|
+
if(!twin){el.innerHTML=h;el._h=h;return false;}
|
|
1298
|
+
if(twin.getAttribute('data-i')!==null)k.setAttribute('data-i',twin.getAttribute('data-i'));
|
|
1299
|
+
while(el.firstChild&&el.firstChild!==k)el.removeChild(el.firstChild);while(k.nextSibling)el.removeChild(k.nextSibling);
|
|
1300
|
+
while(t.firstChild&&t.firstChild!==twin)el.insertBefore(t.firstChild,k);t.removeChild(twin);while(t.firstChild)el.appendChild(t.firstChild);
|
|
1301
|
+
el._h='';return false;}
|
|
1267
1302
|
var current='map',WDJUMP=0;var mapLoaded=false;var driftLoaded=false;
|
|
1268
1303
|
var COLORS={person:'#3DDC97',repo:'#F4F1EA',lang:'#F2A541',tool:'#6bb3ff',agent:'#c58af9',ai:'#5fe3b0',folder:'#b7a98c'};
|
|
1269
1304
|
function tabs(){return document.querySelectorAll('.tab');}
|
|
@@ -1644,8 +1679,10 @@ if(ss.length>shown)h+="<div class='row'><button class='ghost qmore' title='the a
|
|
|
1644
1679
|
return h+"</div>";}
|
|
1645
1680
|
var IDEASOPEN={};
|
|
1646
1681
|
function qKeyOf(qe){var box=qe.closest('.aq');var a=box&&agentById(box.getAttribute('data-id'));var q=a&&a.ask.questions[+qe.getAttribute('data-i')];return q?a.path+'|'+q.q:'';}
|
|
1647
|
-
|
|
1648
|
-
|
|
1682
|
+
// the typed answer is kept in localStorage too (bdSet, the same key as its blob on Home): a reload or a restart keeps it
|
|
1683
|
+
if(document.addEventListener)document.addEventListener('input',function(ev){var t=ev.target;if(!t||!t.classList||!t.classList.contains('qother')||!t.closest)return;var qe=t.closest('.aq .q'),k=qe&&qKeyOf(qe);if(k)bdSet(k,t.value,null,null,'desk');});
|
|
1684
|
+
function saveDrafts(el){el.querySelectorAll('.aq .q').forEach(function(qe){var k=qKeyOf(qe);if(!k)return;var pick=qe.querySelector('input[type=radio]:checked'),t=qe.querySelector('.qother').value;QDRAFT[k]={o:pick?pick.value:'',t:t};if(t||bdAll()[k])bdSet(k,t,null,null,'desk');});}
|
|
1685
|
+
function restoreDrafts(el){el.querySelectorAll('.aq .q').forEach(function(qe){var k=qKeyOf(qe),d=QDRAFT[k];if(!d&&k){var b=bdAll()[k];if(b&&b.text)d={o:'',t:b.text};}if(!d)return;qe.querySelectorAll('input[type=radio]').forEach(function(r){r.checked=!r.disabled&&r.value===d.o;});qe.querySelector('.qother').value=d.t||'';});}
|
|
1649
1686
|
function collectAnswers(box,a){var out=[];box.querySelectorAll('.q').forEach(function(qe){var q=a.ask.questions[+qe.getAttribute('data-i')];if(!q)return;
|
|
1650
1687
|
var other=(qe.querySelector('.qother').value||'').trim();var pick=qe.querySelector('input[type=radio]:checked');var ans=other||(pick?q.options[+pick.value]:'');if(ans)out.push({q:q.q,a:ans});});return out;}
|
|
1651
1688
|
function wireAsks(el){el.querySelectorAll('.aq').forEach(function(box){var a=agentById(box.getAttribute('data-id'));if(!a||!a.ask)return;
|
|
@@ -1654,6 +1691,7 @@ if(!ans.length){msg.innerHTML="<div class='note err'>Pick an option or type an a
|
|
|
1654
1691
|
box.querySelectorAll('button').forEach(function(b){b.disabled=true;});
|
|
1655
1692
|
api('/api/agents/answer',{path:a.path,answers:ans,rerun:rerun}).then(function(r){
|
|
1656
1693
|
if(!r||r.error){box.querySelectorAll('button').forEach(function(b){b.disabled=false;});msg.innerHTML="<div class='note err'>"+esc((r&&r.error)||'failed')+"</div>";return;}
|
|
1694
|
+
ans.forEach(function(x){var k=a.path+'|'+x.q;delete QDRAFT[k];bdSet(k,'');}); // sent: its drafts go
|
|
1657
1695
|
msg.innerHTML="<div class='note ok'>✓ Saved "+r.saved+" answer"+(r.saved>1?"s":"")+" for <b>"+esc(a.name)+"</b> in .symbiot/ANSWERS.md"+(r.rerun?" · your agent is picking them up now.":".")+(r.yours&&r.yours.length?"<div class='err'><i class=ic-person></i> Still yours to do: "+esc(r.yours.join(' '))+"</div>":"")+(r.note?"<div class='muted'>"+esc(r.note)+"</div>":"")+"</div>";loadAgents();});}
|
|
1658
1696
|
var s1=box.querySelector('.qsend'),s2=box.querySelector('.qsave');
|
|
1659
1697
|
if(s1)s1.addEventListener('click',function(){send(true);});if(s2)s2.addEventListener('click',function(){send(false);});
|
|
@@ -1674,7 +1712,7 @@ return "<div class='aq'><h4><i class=ic-person></i> "+(ok?"Waits for your OK":"N
|
|
|
1674
1712
|
function wireNeeds(el){el.querySelectorAll('.ngo,.nskip').forEach(function(btn){btn.addEventListener('click',function(){var a=agentById(btn.getAttribute('data-id'));if(!a)return;btn.disabled=true;var msg=document.getElementById('agentsmsg');
|
|
1675
1713
|
api('/api/home/answer',{id:'needs:'+a.id,pick:btn.classList.contains('ngo')?0:1}).then(function(r){msg.innerHTML=r&&!r.error?"<div class='note ok'>"+esc(a.name)+": "+esc(r.said||'Done.')+"</div>":"<div class='note err'>"+esc((r&&r.error)||'failed')+"</div>";loadAgents();}).catch(function(e){btn.disabled=false;msg.innerHTML="<div class='note err'>"+esc(String((e&&e.message)||e))+"</div>";});});});}
|
|
1676
1714
|
function wireWaits(el){el.querySelectorAll('.wstart').forEach(function(btn){btn.addEventListener('click',function(){var a=agentById(btn.getAttribute('data-id'));if(!a)return;btn.disabled=true;
|
|
1677
|
-
api('/api/open',{path:a.path,force:true}).then(function(x){var msg=document.getElementById('agentsmsg');msg.innerHTML=x&&x.opened?"<div class='note ok'>✓ Started your agent in <b>"+esc(a.name)+"</b>.</div>":"<div class='note err'>"+esc(a.name)+": "+(x&&x.busy?"an agent is already running there.":"it didn't start. Check the command in Settings.")+"</div>";loadAgents();}).catch(function(e){btn.disabled=false;document.getElementById('agentsmsg').innerHTML="<div class='note err'>"+esc(String((e&&e.message)||e))+"</div>";});});});}
|
|
1715
|
+
api('/api/open',{path:a.path,force:true}).then(function(x){var msg=document.getElementById('agentsmsg');msg.innerHTML=x&&x.opened?"<div class='note ok'>✓ Started your agent in <b>"+esc(a.name)+"</b>.</div>":"<div class='note err'>"+esc(a.name)+": "+(x&&x.busy?"an agent is already running there.":x&&x.note?esc(x.note):"it didn't start. Check the command in Settings.")+"</div>";loadAgents();}).catch(function(e){btn.disabled=false;document.getElementById('agentsmsg').innerHTML="<div class='note err'>"+esc(String((e&&e.message)||e))+"</div>";});});});}
|
|
1678
1716
|
// What a run left for Symbiot's memory (.symbiot/REMEMBER.json, handback.mjs): nothing goes in until Remember
|
|
1679
1717
|
function factsHtml(a){var fs=a.remember;if(!fs||!fs.length)return '';
|
|
1680
1718
|
return "<div class='aq rfacts' data-id='"+esc(a.id)+"'><h4>🧠 For Symbiot to remember</h4><div class='qc'>This run found "+fs.length+" lasting fact"+(fs.length>1?"s":"")+". Untick any you don't want, then Remember: every chat can use them from then on.</div>"+
|
|
@@ -1690,11 +1728,11 @@ msg.innerHTML=skip?"<div class='note ok'>Skipped what <b>"+esc(a.name)+"</b> fou
|
|
|
1690
1728
|
var k=box.querySelector('.rkeep'),s=box.querySelector('.rskip');if(k)k.addEventListener('click',function(){send(false);});if(s)s.addEventListener('click',function(){send(true);});});}
|
|
1691
1729
|
function answering(){var f=document.activeElement;return !!(f&&f.closest&&f.closest('.aq'));}
|
|
1692
1730
|
// Handovers (lanes.mjs): what one lane's agent handed to another, and where it stands.
|
|
1693
|
-
var LANEWORD={started:'working on it',held:'queued: that lane is busy',done:'done, reported back',error:"couldn't hand over"};
|
|
1731
|
+
var LANEWORD={started:'working on it',held:'queued: that lane is busy',blocked:"didn't start",done:'done, reported back',error:"couldn't hand over"};
|
|
1694
1732
|
function loadLanes(){api('/api/lanes').then(function(d){var el=document.getElementById('laneslist');if(!el||!d)return;var hs=d.handoffs||[];
|
|
1695
1733
|
var row=function(h){
|
|
1696
|
-
var back=h.result||h.error,more=(h.full?"<div>"+esc(h.full)+"</div>":"")+(back?"<div class='tres'>"+esc(back)+"</div>":"");
|
|
1697
|
-
return "<div class='task'><span class='t'><b>"+esc(h.from)+"</b> → <b>"+esc(h.to)+"</b>: "+esc(String(h.text||'').split('**').join(''))+(h.n>1?" <span class='muted'>×"+h.n+"</span>":"")+(more?"<details class='tfull'><summary>"+(h.full?"in full":"what came back")+"</summary>"+more+"</details>":"")+"</span><span class='rp'"+(h.status==='error'?" style='color:var(--amber)'":"")+">"+esc(LANEWORD[h.status]||h.status)+"</span></div>";};
|
|
1734
|
+
var back=h.result||h.error||(h.note?"It didn't start: "+h.note:''),more=(h.full?"<div>"+esc(h.full)+"</div>":"")+(back?"<div class='tres'>"+esc(back)+"</div>":"");
|
|
1735
|
+
return "<div class='task'><span class='t'><b>"+esc(h.from)+"</b> → <b>"+esc(h.to)+"</b>: "+esc(String(h.text||'').split('**').join(''))+(h.n>1?" <span class='muted'>×"+h.n+"</span>":"")+(more?"<details class='tfull'><summary>"+(h.full?"in full":"what came back")+"</summary>"+more+"</details>":"")+"</span><span class='rp'"+(h.status==='error'||h.status==='blocked'?" style='color:var(--amber)'":"")+">"+esc(LANEWORD[h.status]||h.status)+"</span></div>";};
|
|
1698
1736
|
var seen={},uniq=[];hs.forEach(function(h){var k=h.from+'|'+h.to+'|'+h.status+'|'+h.text;if(seen[k]){seen[k].n++;return;}var c={};for(var x in h)c[x]=h[x];c.n=1;seen[k]=c;uniq.push(c);});
|
|
1699
1737
|
var live=uniq.filter(function(h){return h.status!=='done';}),fin=uniq.filter(function(h){return h.status==='done';});
|
|
1700
1738
|
whenFree(el,'lanes',function(){sameHtml(el,(live.length?"<div class='tgroup agg'>Handovers under way <span class='tcount'>"+live.length+"</span></div>"+live.slice(0,12).map(row).join(''):'')+(fin.length?"<details class='agdone'><summary class='tgroup agg'>Finished handovers <span class='tcount'>"+fin.length+"</span></summary>"+fin.slice(0,20).map(row).join('')+"</details>":''));});});}
|
|
@@ -1792,7 +1830,7 @@ var n=(r.written||[]).length;var opens=[];
|
|
|
1792
1830
|
// one line, its paths on hover: the review cards below it stay in view
|
|
1793
1831
|
var h="<div class='drift pushed'><div class='dh'><span class='dot-c'></span><span class='dn'>Sent to "+n+" repo"+(n===1?"":"s")+"</span><span class='dd'>"+(r.written||[]).map(function(w){return "<span title='"+esc(w.file).replace(/'/g,''')+"'>✓ <b>"+esc(w.name)+"</b> ("+w.count+" task"+(w.count===1?"":"s")+(w.held?", <i class=ic-pause></i> held till its running agent finishes":"")+")</span>";}).join(" · ")+"</span></div>";
|
|
1794
1832
|
if(r.unresolved&&r.unresolved.length){var names=r.unresolved.map(function(u){return u.name;}).join(", ");var hasNoRepo=r.unresolved.some(function(u){return u.name==='(no repo)';});h+="<div class='dd' style='margin-top:6px'><i class=ic-warn></i> not sent: "+esc(names)+". "+(hasNoRepo?"Tell Symbiot in the bar at the bottom which project it's for, so it has somewhere to go.":"That repo isn't in the map — add its folder in Settings.")+"</div>";}
|
|
1795
|
-
if(r.handoff&&r.written&&r.written.length){opens=r.written.map(function(w){return api('/api/open',{path:w.path}).then(function(x){if(x&&x.blocked){var d=document.createElement('div');d.className='dd';d.innerHTML="<i class=ic-pause></i> <b>"+esc(w.name)+"</b>: "+esc(x.note)+" <button class='ghost' style='padding:2px 8px;font-size:12px'>Start it anyway</button>";d.querySelector('button').addEventListener('click',function(){this.disabled=true;api('/api/open',{path:w.path,force:true}).then(function(y){d.innerHTML=y&&y.opened?"✓ Started an agent in <b>"+esc(w.name)+"</b>.":"<i class=ic-warn></i> <b>"+esc(w.name)+"</b>: "+(y&&y.busy?"an agent is already running there.":"it didn't start. Check the command in Settings.");});});o.appendChild(d);}if(x&&x.busy){var d=document.createElement('div');d.className='dd';d.innerHTML="<i class=ic-warn></i> <b>"+esc(w.name)+"</b> already has an agent running, so another wasn't started. "+(x.auto?"Its new tasks are held, and an agent starts on them when it finishes.":"Its new tasks are held until it finishes. After that, an agent starts on them the next time this tab checks the repo, or send again.");o.appendChild(d);}}).catch(function(){});});h+="<div class='dd'><i class=ic-bot></i> Handed "+n+" repo"+(n===1?"":"s")+" to your agent — opening the <b>Agents</b> tab to watch it work…</div>";setTimeout(function(){setTab('agents');},500);}
|
|
1833
|
+
if(r.handoff&&r.written&&r.written.length){opens=r.written.map(function(w){return api('/api/open',{path:w.path}).then(function(x){if(x&&x.blocked){var d=document.createElement('div');d.className='dd';d.innerHTML="<i class=ic-pause></i> <b>"+esc(w.name)+"</b>: "+esc(x.note)+" <button class='ghost' style='padding:2px 8px;font-size:12px'>Start it anyway</button>";d.querySelector('button').addEventListener('click',function(){this.disabled=true;api('/api/open',{path:w.path,force:true}).then(function(y){d.innerHTML=y&&y.opened?"✓ Started an agent in <b>"+esc(w.name)+"</b>.":"<i class=ic-warn></i> <b>"+esc(w.name)+"</b>: "+(y&&y.busy?"an agent is already running there.":y&&y.note?esc(y.note):"it didn't start. Check the command in Settings.");});});o.appendChild(d);}if(x&&x.busy){var d=document.createElement('div');d.className='dd';d.innerHTML="<i class=ic-warn></i> <b>"+esc(w.name)+"</b> already has an agent running, so another wasn't started. "+(x.auto?"Its new tasks are held, and an agent starts on them when it finishes.":"Its new tasks are held until it finishes. After that, an agent starts on them the next time this tab checks the repo, or send again.");o.appendChild(d);}}).catch(function(){});});h+="<div class='dd'><i class=ic-bot></i> Handed "+n+" repo"+(n===1?"":"s")+" to your agent — opening the <b>Agents</b> tab to watch it work…</div>";setTimeout(function(){setTab('agents');},500);}
|
|
1796
1834
|
else{h+="<div class='dd' style='margin-top:8px'>Set an <b>agent command</b> in Settings to auto-run it on send (and watch it on the Workdesk). For now, tell your agent: <b>“Read .symbiot/TASKS.md and implement the unchecked items.”</b></div>";}
|
|
1797
1835
|
h+="</div>";
|
|
1798
1836
|
o.innerHTML=h;
|
|
@@ -2164,7 +2202,7 @@ postsMsg('ok',"✓ Added "+r.added+" of your LinkedIn posts: "+r.total+" in
|
|
|
2164
2202
|
// can be drafted, or are (home.mjs marketing).
|
|
2165
2203
|
var MKT=null;
|
|
2166
2204
|
function mkMsg(cls,html){var o=$('mkmsg');if(o)o.innerHTML=html?"<div class='note "+cls+"'>"+html+"</div>":'';}
|
|
2167
|
-
function loadMarketing(){loadPostsUI();api('/api/marketing').then(function(d){if(d&&!d.error){MKT=d;renderMarketing();renderMkLane();if(POSTS)renderPosts();}});}
|
|
2205
|
+
function loadMarketing(){loadPostsUI();loadMkTray();api('/api/marketing').then(function(d){if(d&&!d.error){MKT=d;renderMarketing();renderMkLane();if(POSTS)renderPosts();}});}
|
|
2168
2206
|
// The Marketing lane (home.mjs marketingState): what needs you there first (its agent's
|
|
2169
2207
|
// questions, its work waiting for your OK, the week's drafts), then its tasks, each tagged
|
|
2170
2208
|
// with the product it markets, an Add for a new one, what its agent drafted, and other
|
|
@@ -2189,8 +2227,11 @@ var h="<div class='lipost' aria-label='"+escQ(plat)+" preview'><div class='liaut
|
|
|
2189
2227
|
+(p.media||[]).map(function(m){return m.kind==='video'?"<video class='limedia' src='"+escQ(mkMediaSrc(m.rel))+"' controls muted playsinline preload='metadata'></video>":"<img class='limedia' src='"+escQ(mkMediaSrc(m.rel))+"' alt='"+escQ(m.name)+"'>";}).join('')
|
|
2190
2228
|
+"<div class='libar'><span>Like</span><span>Comment</span><span>Repost</span><span>Send</span></div></div>";
|
|
2191
2229
|
if((p.missing||[]).length)h+="<div class='note err'>It names "+p.missing.map(function(m){return "<code>"+esc(m)+"</code>";}).join(', ')+", which isn't next to it.</div>";
|
|
2192
|
-
var
|
|
2193
|
-
|
|
2230
|
+
var fin=p.status==='posted'||p.status==='superseded',on=p.postedOn==='linkedin'?'LinkedIn':p.postedOn;
|
|
2231
|
+
var done=p.status==='posted'?"Posted"+(on?" on "+esc(on):"")+(p.posted?", "+esc(new Date(p.posted).toLocaleString(undefined,{day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'})):"")+(p.url?" · <a href='"+escQ(p.url)+"' target='_blank' rel='noopener'>see it</a>":"")+". It isn't approved or posted again"+(p.edited?", though its file changed since":"")+"."
|
|
2232
|
+
:p.status==='superseded'?"Superseded"+((p.by||[]).length?" by "+p.by.map(function(b){return "<code>"+esc(b)+"</code>";}).join(', '):"")+": it won't be posted."
|
|
2233
|
+
:p.status==='approved'?"Approved "+agoTxt(p.statusAt)+": its agent "+(p.when?"schedules":"posts")+" it through Symbiot's browser, with this text.":p.status==='skipped'?"Skipped "+agoTxt(p.statusAt)+": it won't be posted.":"";
|
|
2234
|
+
h+="<div class='row mkpvact'>"+(fin||p.status==='approved'?"":"<button class='act mkok'>Approve"+(p.when?", schedule it":", post it")+"</button>")+(fin||p.status==='skipped'?"":"<button class='ghost mkskip'>Skip</button>")
|
|
2194
2235
|
+"<span class='muted' style='font-size:12px;flex:1'>"+(done||(p.when?"Its agent schedules it for "+esc(p.when)+" once you approve.":"Its agent posts it once you approve."))+"</span><span class='pchars'>"+p.chars+" / 3000</span></div>";
|
|
2195
2236
|
h+="<details class='linotes'><summary>Its notes</summary>"+(p.notes?"<div class='linotest'>"+esc(p.notes)+"</div>":"<div class='muted'>None.</div>")+"<button type='button' class='ghost mkfile' title='open the draft in your editor'>Edit the file</button></details>";
|
|
2196
2237
|
return h;}
|
|
@@ -2225,6 +2266,37 @@ el.querySelectorAll('.mkprev').forEach(function(b){b.addEventListener('click',fu
|
|
|
2225
2266
|
if(MKPV){var pv=null;el.querySelectorAll('.mkpv').forEach(function(x){if(x.getAttribute('data-rel')===MKPV)pv=x;});if(pv)mkPreview(pv,MKPV);else MKPV='';}
|
|
2226
2267
|
var g=el.querySelector('.mkgo');if(g)g.addEventListener('click',function(){g.disabled=true;api('/api/marketing/go',{}).then(function(r){if(!r||r.error){g.disabled=false;mkMsg('err',esc((r&&r.error)||'It didn't start.'));return;}mkMsg('ok',r.queued?"Its agent takes them once the run there now finishes.":"Marketing's agent started. It's on the Workdesk, and what it drafts waits here for your OK.");loadMarketing();}).catch(function(e){g.disabled=false;mkMsg('err',esc(String((e&&e.message)||e)));});});
|
|
2227
2268
|
if(MKLIT){MKLIT=false;var lit=nd.querySelector('.mkneeds');if(lit&&lit.scrollIntoView)lit.scrollIntoView({block:'start'});}}
|
|
2269
|
+
// The pick tray (tray.mjs): every capture its agent took of a product, blurred where it
|
|
2270
|
+
// shows something private. Its blur boxes sit on it, solid where it's blurred and dashed
|
|
2271
|
+
// where it shows; a click switches one (a clip's box at the same place in each moment, at
|
|
2272
|
+
// once) and the capture is blurred again. Use puts it on a post (its media: line) in one
|
|
2273
|
+
// click: the posts it was taken for each have a button, any other is in the list.
|
|
2274
|
+
var MKTRAY=null;
|
|
2275
|
+
function loadMkTray(){api('/api/marketing/tray').then(function(d){if(d&&!d.error){MKTRAY=d.trays||[];renderMkTray();}});}
|
|
2276
|
+
function mkTraySrc(rel,at){return '/api/marketing/tray/media?rel='+encodeURIComponent(rel)+'&v='+Math.round(at||0)+'&t='+encodeURIComponent(T);}
|
|
2277
|
+
// a capture's boxes, one a place: a clip has the same box in each moment it shows there
|
|
2278
|
+
function mkBoxes(c){var g={},out=[];(c.regions||[]).forEach(function(r){var k=[r.x,r.y,r.w,r.h].join();if(!g[k]){g[k]={x:r.x,y:r.y,w:r.w,h:r.h,kind:r.kind,ids:[],on:0};out.push(g[k]);}g[k].ids.push(r.id);if(r.on)g[k].on++;});out.forEach(function(b){b.all=b.on===b.ids.length;});return out;}
|
|
2279
|
+
var MKKIND={still:'Picture',clip:'Clip',reel:'Reel'};
|
|
2280
|
+
function renderMkTray(){var el=$('mktray');if(!el)return;var ts=MKTRAY||[];
|
|
2281
|
+
var h=ts.map(function(t,ti){var open=(t.drafts||[]).filter(function(d){return d.status!=='posted'&&d.status!=='superseded';});
|
|
2282
|
+
return "<div class='pshead'><span>Pick tray · "+esc(t.product)+"</span><small>"+t.items.length+" capture"+(t.items.length===1?"":"s")+", blurred where "+(t.items.length===1?"it shows":"they show")+" something private. Click a box to blur it or show it; Use puts one on a post.</small></div><div class='mktray'>"
|
|
2283
|
+
+t.items.map(function(c,ci){var bx=mkBoxes(c),n=bx.filter(function(b){return b.all;}).length;
|
|
2284
|
+
var shot=c.show?(c.showKind==='video'?"<video src='"+escQ(mkTraySrc(c.show,c.at))+"' muted loop playsinline controls preload='metadata'></video>":"<img src='"+escQ(mkTraySrc(c.show,c.at))+"' alt='"+escQ(c.caption||c.name)+"' loading='lazy'>"):"<div class='muted' style='padding:20px;line-height:1.4'>Not rendered yet.</div>";
|
|
2285
|
+
shot+=bx.map(function(b,bi){var p=function(v){return Math.round(v*10000)/100+'%';};return "<button type='button' class='mkbox"+(b.all?" on":"")+"' data-b='"+bi+"' aria-pressed='"+b.all+"' style='left:"+p(b.x)+";top:"+p(b.y)+";width:"+p(b.w)+";height:"+p(b.h)+"' title='"+escQ(b.kind+(b.all?": blurred. Click to show it":": shown. Click to blur it"))+"' aria-label='"+escQ(b.kind+(b.all?", blurred":", shown"))+"'></button>";}).join('');
|
|
2286
|
+
var fr=(c.for||[]).map(function(f){return t.folder+'/'+f;}),mine=open.filter(function(d){return fr.indexOf(d.rel)>=0;}),rest=open.filter(function(d){return fr.indexOf(d.rel)<0;});
|
|
2287
|
+
var use=c.use?mine.map(function(d){var di=t.drafts.indexOf(d);return (d.media||[]).indexOf(c.use)>=0?"<span class='mkst approved' title='its picture or video now'>on "+esc(d.name)+"</span>":"<button type='button' class='ghost mkuseb' data-d='"+di+"'>Use for "+esc(d.name)+"</button>";}).join('')
|
|
2288
|
+
+(rest.length?"<select class='mkusel' aria-label='Use it for another post'><option value=''>"+(mine.length?"Another post":"Use for a post")+"…</option>"+rest.map(function(d){return "<option value='"+t.drafts.indexOf(d)+"'"+((d.media||[]).indexOf(c.use)>=0?" disabled":"")+">"+esc(d.name)+((d.media||[]).indexOf(c.use)>=0?" (on it)":"")+"</option>";}).join('')+"</select>":""):"";
|
|
2289
|
+
return "<div class='mkcap' data-t='"+ti+"' data-c='"+ci+"'><div class='mkshot'>"+shot+"</div><div class='mkcapt'>"+esc(c.caption||c.name)+"</div><div class='mkcapm'>"+esc(MKKIND[c.kind]||c.kind)+(c.screen?" · "+esc(c.screen):"")+(bx.length?" · "+n+" of "+bx.length+" box"+(bx.length===1?"":"es")+" blurred":c.kind==='reel'?" · its clips' blur":" · nothing to blur")+"</div>"+(bx.length?"<div class='mkchips'>"+bx.map(function(b,bi){return "<button type='button' class='mkchip"+(b.all?" on":"")+"' data-b='"+bi+"' aria-pressed='"+b.all+"' title='"+escQ(b.all?"blurred: click to show it":"shown: click to blur it")+"'>"+esc(b.kind)+(b.all?" · blurred":" · shown")+"</button>";}).join('')+"</div>":"")+(use?"<div class='mkuse'>"+use+"</div>":"")+"</div>";}).join('')+"</div>";}).join('');
|
|
2290
|
+
if(sameHtml(el,h))return;
|
|
2291
|
+
var at=function(x){var c=x.closest('.mkcap');return {t:ts[+c.getAttribute('data-t')],c:ts[+c.getAttribute('data-t')].items[+c.getAttribute('data-c')],el:c};};
|
|
2292
|
+
el.querySelectorAll('.mkchip').forEach(function(c){var box=function(){return c.closest('.mkcap').querySelector(".mkbox[data-b='"+c.getAttribute('data-b')+"']");};c.addEventListener('mouseenter',function(){var x=box();if(x)x.classList.add('hl');});c.addEventListener('mouseleave',function(){var x=box();if(x)x.classList.remove('hl');});});
|
|
2293
|
+
el.querySelectorAll('.mkbox,.mkchip').forEach(function(b){b.addEventListener('click',function(){var a=at(b),g=mkBoxes(a.c)[+b.getAttribute('data-b')];if(!g||a.el.classList.contains('busy'))return;a.el.classList.add('busy');
|
|
2294
|
+
api('/api/marketing/tray/blur',{tray:a.t.rel,name:a.c.name,id:g.ids,on:!g.all}).then(function(r){a.el.classList.remove('busy');if(!r||r.error){mkMsg('err',esc((r&&r.error)||'failed'));return;}
|
|
2295
|
+
mkMsg(r.rendered?'ok':'err',r.rendered?(g.all?"Shown: that box isn't blurred in it now.":"Blurred: that box is blurred in it now."):"Saved, but it isn't "+(g.all?"shown":"blurred")+" in the picture yet: "+esc(r.note||'it couldn't be blurred'));loadMkTray();}).catch(function(e){a.el.classList.remove('busy');mkMsg('err',esc(String((e&&e.message)||e)));});});});
|
|
2296
|
+
var useIt=function(x,d){var a=at(x),dr=a.t.drafts[d];if(!dr)return;x.disabled=true;
|
|
2297
|
+
api('/api/marketing/tray/use',{rel:dr.rel,file:a.c.use}).then(function(r){x.disabled=false;if(!r||r.error){mkMsg('err',esc((r&&r.error)||'failed'));return;}mkMsg('ok',"“"+esc(dr.name)+"” now shows "+esc(a.c.caption||a.c.name)+"."+(r.reopened?" It was approved with another, so it asks for your OK again.":""));loadMarketing();}).catch(function(e){x.disabled=false;mkMsg('err',esc(String((e&&e.message)||e)));});};
|
|
2298
|
+
el.querySelectorAll('.mkuseb').forEach(function(b){b.addEventListener('click',function(){useIt(b,+b.getAttribute('data-d'));});});
|
|
2299
|
+
el.querySelectorAll('.mkusel').forEach(function(s){s.addEventListener('change',function(){if(s.value!=='')useIt(s,+s.value);});});}
|
|
2228
2300
|
var MKDAY=function(s){var d=new Date(s+'T00:00:00');return isNaN(d)?s:d.toLocaleDateString(undefined,{day:'numeric',month:'short'});};
|
|
2229
2301
|
function renderMarketing(){var d=MKT,r=$('mkreplies'),t=$('mktest');if(!d||!r||!t)return;var rs=d.replies||[];
|
|
2230
2302
|
r.innerHTML="<div class='pshead'><span>Replies</span><small>"+(!d.linkedin?"Link LinkedIn, and comments on and mentions of your posts show here, marked when they may be a customer.":rs.length?"On LinkedIn in the last 30 days. Draft a reply leaves one in the comment box for you to post.":"None on LinkedIn in the last 30 days. Comments on and mentions of your posts show here, marked when they may be a customer.")+"</small></div>"
|
|
@@ -2696,7 +2768,7 @@ var tc=document.getElementById('themecolor');if(tc&&tc.setAttribute)tc.setAttrib
|
|
|
2696
2768
|
var bd=document.body;if(bd&&bd.classList){bd.classList.toggle('lq-light',th.light);['ferro','glass','pearl'].forEach(function(l){bd.classList.toggle('lq-look-'+l,l===look);});bd.classList.toggle('lq-contrast',th.contrast);bd.classList.toggle('lq-solid',th.solid||th.contrast);bd.classList.toggle('lq-forced',th.forced);}
|
|
2697
2769
|
LQ.theme=th;return th;}
|
|
2698
2770
|
function lqSize(){var el=$('liquid');var w=(el&&el.clientWidth)||window.innerWidth||1280,h=(el&&el.clientHeight)||window.innerHeight||800;return {w:w,h:h,s:Math.max(0.55,Math.min(1.15,Math.min(w,h)/860))};}
|
|
2699
|
-
function lqLoad(commit){if(LQ.scene==='work'){lqLoadWork();return;}var fresh=LQ.fresh;LQ.fresh=false;Promise.all([api('/api/adapt?from='+encodeURIComponent(LQ.last)+(commit?'&commit=1':'')+(LQ.touch?'&touch=1':'')),api('/api/home'+(fresh?'?fresh=1':''))]).then(function(r){LQ.adapt=r[0]||{};LQ.home=r[1]||{};lqRedraw();}).catch(function(){});}
|
|
2771
|
+
function lqLoad(commit){if(LQ.scene==='work'){lqLoadWork();return;}var fresh=LQ.fresh;LQ.fresh=false;Promise.all([api('/api/adapt?from='+encodeURIComponent(LQ.last)+(commit?'&commit=1':'')+(LQ.touch?'&touch=1':'')),api('/api/home'+(fresh?'?fresh=1':''))]).then(function(r){LQ.adapt=r[0]||{};LQ.home=r[1]||{};lqRedraw();if(r[1]&&r[1].you)bdOrphans(r[1].you);}).catch(function(){});}
|
|
2700
2772
|
// A droplet's own place: a steady nudge in angle and distance from its id, so the
|
|
2701
2773
|
// layout reads as grown, not ruled, and stays put from one visit to the next.
|
|
2702
2774
|
// Home, in zones: what needs you in a band across the top (lqNeeds), then the app's
|
|
@@ -2716,8 +2788,16 @@ var LQ_BLOBW=250,LQ_NEEDY=128;
|
|
|
2716
2788
|
function lqNeeds(h,S){var rank={ask:0,approve:1,setup:2},rk=function(y){return y.urgent?(y.id==='setup:ai'?-3:y.id==='setup:agent'?-2:-1):rank[y.kind]||0;},ys=(h.you||[]).slice().sort(function(a,b){return rk(a)-rk(b)||(a.id<b.id?-1:a.id>b.id?1:0);});
|
|
2717
2789
|
var nb=lqNextBox(h,S);if(!ys.length)return {items:[],bottom:nb,x:0,page:0,pages:1,count:0};
|
|
2718
2790
|
var R=Math.round(24*Math.max(0.85,Math.min(1.1,S.s))),unit=3*R+22+LQ_BLOBW,gap=28,cols=Math.max(1,Math.min(3,Math.floor((S.w-24+gap)/(unit+gap)))),pages=Math.max(1,Math.ceil(ys.length/cols));
|
|
2719
|
-
LQ.needPage=(LQ.needPage||0)%pages;
|
|
2791
|
+
LQ.needPage=(LQ.needPage||0)%pages;
|
|
2792
|
+
// the card you're typing in stays in view: a new one ahead of it doesn't page it away
|
|
2793
|
+
var fa=document.activeElement,tb=fa&&fa.closest?fa.closest('#lqdrops .lqblob[data-key]'):null;if(tb){var tk=tb.getAttribute('data-key');for(var ti=0;ti<ys.length;ti++)if(bdKey(ys[ti])===tk){LQ.needPage=Math.floor(ti/cols);break;}}
|
|
2794
|
+
var shown=ys.slice(LQ.needPage*cols,LQ.needPage*cols+cols),x0=Math.max(12,(S.w-(shown.length*unit+(shown.length-1)*gap))/2);
|
|
2720
2795
|
var tall=(shown.some(function(y){return y.kind==='ask'&&(y.options||[]).length;})?172:116)+(shown.some(function(y){return y.fix==='handover';})?16:0)+(shown.some(function(y){return y.draft;})?150:0);
|
|
2796
|
+
// the band is as tall as its tallest card really is: two long answers, the own-words
|
|
2797
|
+
// box open and "+4 more questions" made Post 1's card 60px taller than the guess, over
|
|
2798
|
+
// the mailbox agent's name in the band below (2026-10-09). lqBlobFit lays out again
|
|
2799
|
+
// when a card grows or shrinks.
|
|
2800
|
+
LQ.needEst=tall;var mh=lqBlobH();if(mh)tall=Math.max(tall,mh-8);LQ.needTall=tall;
|
|
2721
2801
|
var items=shown.map(function(y,i){return {id:y.id,kind:'you',blob:true,item:y,shape:y.shape,title:y.title,sub:y.sub,r:R,tx:x0+i*(unit+gap)+R,ty:LQ_NEEDY+R,ax:0,ay:0};});
|
|
2722
2802
|
return {items:items,bottom:items.length?LQ_NEEDY+tall+20:0,x:x0,page:LQ.needPage,pages:pages,count:ys.length};}
|
|
2723
2803
|
// The agents at work, a band of their own under the needs-you band (or the header):
|
|
@@ -2748,6 +2828,9 @@ el.querySelectorAll('.nxgo').forEach(function(b){b.addEventListener('click',func
|
|
|
2748
2828
|
api('/api/home/next',{id:s.id}).then(function(r){if(!r||r.error){b.disabled=false;lqSaid((r&&r.error)||'It didn’t start. Try again.');return;}lqSaid(r.said||'Started.');if(li&&li.classList)li.classList.add('gone');LQ.fresh=true;setTimeout(function(){lqLoad(false);},650);}).catch(function(e){b.disabled=false;lqSaid(String((e&&e.message)||e));});});});}
|
|
2749
2829
|
if(el.classList)el.classList.add('on');if(el.style){el.style.width=w+'px';el.style.transform='translate('+Math.round((S.w-w)/2)+'px,'+(LQ_NEEDY-30)+'px)';}
|
|
2750
2830
|
return LQ_NEEDY-30+(el.offsetHeight||(48+Math.ceil(ns.length/(two?2:1))*58))+24;}
|
|
2831
|
+
// a blob's top sits 4px above LQ_NEEDY and the band's bottom LQ_NEEDY+tall+20: mh-8 leaves 12px between
|
|
2832
|
+
function lqBlobH(){var m=0,el=$('lqdrops');if(el&&el.querySelectorAll)el.querySelectorAll('.lqblob').forEach(function(b){if(b.offsetHeight>m)m=b.offsetHeight;});return m;}
|
|
2833
|
+
function lqBlobFit(){if(LQ.needTall==null||LQ.scene==='work')return;var mh=lqBlobH(),want=Math.max(LQ.needEst||0,mh?mh-8:0);if(Math.abs(want-LQ.needTall)>2)lqBuild();}
|
|
2751
2834
|
function lqNeedPage(){LQ.needPage=(LQ.needPage||0)+1;lqAct();lqBuild();}
|
|
2752
2835
|
function lqArchive(){lqPool('tasks');LQ.arch=true;if(typeof loadArchived==='function')loadArchived();}
|
|
2753
2836
|
function lqOrg(id,k){var h=k*977;id=String(id);for(var i=0;i<id.length;i++)h=(h*31+id.charCodeAt(i))|0;return ((h>>>0)%1000)/1000;}
|
|
@@ -2873,12 +2956,13 @@ while(out.indexOf(' ')>=0)out=out.split(' ').join(' ');[' :',' ,',' ;',' .'].f
|
|
|
2873
2956
|
// handlers read the droplet by its place (data-i) when clicked, not from when drawn.
|
|
2874
2957
|
function lqLabels(){var el=$('lqdrops');if(!el)return;
|
|
2875
2958
|
var dropAt=function(b){return LQ.drops[+b.getAttribute('data-i')];};
|
|
2876
|
-
var same=
|
|
2959
|
+
var same=keepTyping(el,LQ.drops.map(function(d,i){if(d.blob)return "<div class='lqd lq-you lqblob"+(d.item.urgent?' urgent':'')+"' data-i='"+i+"' data-key='"+escQ(bdKey(d.item))+"' role='group' aria-label='"+escQ(String(d.item.name||d.item.title||'')+': '+String(d.item.q||d.item.sub||''))+"'>"+blobBody(d.item,true)+"</div>";
|
|
2877
2960
|
return "<div class='lqd lq-"+d.kind+(d.band?' lq-band':'')+(d.parked?' lq-parked':'')+(d.ask&&d.kind==='shape'?' lq-lit':'')+(LQ.openTag===d.id?' open':'')+"' data-i='"+i+"'><button type='button' class='lt' title='"+escQ(String(d.title||'').split('**').join('')+(d.sub?' · '+d.sub:''))+"' aria-expanded='"+(LQ.openTag===d.id)+"'><span>"+esc(lqShort(d.title,42))+"</span>"+((d.kind==='proj'||d.kind==='wait'||d.kind==='run')&&d.sub?"<span class='lm'>"+esc(lqShort(d.sub,d.kind==='proj'?40:56))+"</span>":"")+"</button>"+(d.sub?"<small>"+esc(d.sub)+"</small>":"")+"<button type='button' class='lgo'>Open ›</button></div>";}).join(''));
|
|
2878
2961
|
if(!same){LQ.btns=[];LQ.lts=[];el.querySelectorAll('.lqd').forEach(function(b){LQ.btns.push(b);var d=dropAt(b),lt=b.querySelector('.lt'),go=b.querySelector('.lgo');LQ.lts.push(lt);
|
|
2962
|
+
if(b._wired)return;b._wired=true; // the blob kept through the redraw (keepTyping) is wired already
|
|
2879
2963
|
if(lt)lt.addEventListener('click',function(ev){lqOpen(dropAt(b),ev);});
|
|
2880
2964
|
if(go)go.addEventListener('click',function(ev){LQ.openTag=null;lqOpen(dropAt(b),ev);});
|
|
2881
|
-
if(d&&d.blob)wireBlob(b,d.item,{sent:function(){var x=dropAt(b);if(x)x.gone=true;},done:function(){LQ.fresh=true;lqLoad(false);},say:lqSaid});});}
|
|
2965
|
+
if(d&&d.blob)wireBlob(b,d.item,{sent:function(){var x=dropAt(b);if(x)x.gone=true;},done:function(){LQ.fresh=true;lqLoad(false);},say:lqSaid,grew:lqBlobFit});});setTimeout(lqBlobFit,0);}
|
|
2882
2966
|
var nt=$('lqneedt'),nd=LQ.scene!=='work'&&LQ.need;if(nt){var on=!!(nd&&nd.items.length);if(nt.classList)nt.classList.toggle('on',on);
|
|
2883
2967
|
if(on){if(!sameHtml(nt,"<span>Needs you"+(nd.count>1?" · "+nd.count:"")+"</span><small>only you can do these</small>"+(nd.pages>1?"<button type='button' class='lqmb lqneedpg' style='pointer-events:auto;min-height:32px;padding:4px 12px;font-size:12.5px'>"+(nd.page+1)+" of "+nd.pages+" ›</button>":""))){var pg=nt.querySelector('.lqneedpg');if(pg)pg.addEventListener('click',lqNeedPage);}
|
|
2884
2968
|
if(nt.style)nt.style.transform='translate('+Math.round(nd.x)+'px,'+(LQ_NEEDY-46)+'px)';}}
|
|
@@ -2916,6 +3000,20 @@ function lqGo(shape){if(shape==='tasks'||shape==='agents'){lqWorkBy('task');retu
|
|
|
2916
3000
|
// A drafted reply as it will look: a LinkedIn message, a WhatsApp bubble or an email, the reply only.
|
|
2917
3001
|
function draftHtml(d){var where=d.kind==='social'?'LinkedIn':d.kind==='chat'?'WhatsApp':(d.platform||'email');return "<div class='dmsg dm-"+escQ(d.kind||'mail')+"' aria-label='the reply, as it will be sent'><div class='dmto'>"+(d.to?'To '+esc(d.to)+' · ':'')+esc(d.platform||where)+"</div><div class='dmb'>"+esc(d.text||'')+"</div></div>";}
|
|
2918
3002
|
function blobOpt(o){return String(o||'').replace(/[ ]*[(]recommended[)][ ]*$/i,'');}
|
|
3003
|
+
// What you type in a blob's own-words box, kept per question (its folder and the
|
|
3004
|
+
// question, else its id) in localStorage as you type: a redraw, a reload or a restart brings it
|
|
3005
|
+
// back, with the cursor where it was, and it goes only once the answer is sent. A
|
|
3006
|
+
// week-old one is dropped. One whose question went (answered elsewhere) is said in
|
|
3007
|
+
// Home's talk with what you wrote (bdOrphans), never dropped silently.
|
|
3008
|
+
var BDKEY='symbiot-blobdrafts',BDKEEP=7*86400000;
|
|
3009
|
+
function bdKey(y){return y.path&&y.q?String(y.path)+'|'+String(y.q):String(y.id||'')+'|'+String(y.q||y.sub||'');} // the same question on Home, the Workdesk and a project
|
|
3010
|
+
function bdAll(){try{var a=JSON.parse(localStorage.getItem(BDKEY)||'{}');return a&&typeof a==='object'?a:{};}catch(e){return {};}}
|
|
3011
|
+
function bdGet(y){var d=bdAll()[bdKey(y)];return d&&d.text?d:null;}
|
|
3012
|
+
function bdSet(y,text,s,e,src){var a=bdAll(),k=typeof y==='string'?y:bdKey(y),now=Date.now();if(text)a[k]={text:String(text),at:now,s:s,e:e,name:y.name||y.repo||'',q:y.q||'',src:src||'blob'};else delete a[k];
|
|
3013
|
+
for(var x in a)if(now-(a[x].at||0)>BDKEEP)delete a[x];try{localStorage.setItem(BDKEY,JSON.stringify(a));}catch(er){}}
|
|
3014
|
+
function bdOrphans(you){var a=bdAll(),keys={},ch=false;(you||[]).forEach(function(y){if(y.kind==='ask')keys[bdKey(y)]=1;});
|
|
3015
|
+
for(var k in a){var d=a[k];if(keys[k]||d.told||!d.text||d.src==='desk')continue;d.told=1;ch=true;lqSaid('Your answer to '+(d.name||'an agent')+' wasn’t sent: '+(d.q?'“'+lqShort(d.q,70)+'” ':'its question ')+'was answered or closed elsewhere. What you wrote: “'+d.text+'”');}
|
|
3016
|
+
if(ch)try{localStorage.setItem(BDKEY,JSON.stringify(a));}catch(e){}}
|
|
2919
3017
|
function blobBody(y,home){var h='',nm=y.name||y.repo||(y.kind==='setup'?y.title:'')||'';
|
|
2920
3018
|
h+=home?"<button type='button' class='lt' title='open "+escQ(nm)+", with this lit up'><span>"+esc(lqShort(nm,30))+"</span><i>open ›</i></button>":"<div class='nhead'><b>"+esc(nm)+(y.kind==='ask'?" asks":"")+"</b>"+(y.more?"<span class='muted'>+"+y.more+" more on its block in Agents</span>":"")+"</div>";
|
|
2921
3019
|
if(y.kind!=='ask')return h+"<div class='bq'>"+esc(y.kind==='approve'?String(y.sub||'').replace(/ · only you decide$/,''):String(y.sub||'').charAt(0).toUpperCase()+String(y.sub||'').slice(1)+'.')+"</div><div class='bo'><button type='button' class='nopt rec ngo'><span class='bt'>"+(y.kind==='approve'?'Review and approve':y.id==='setup:inbox'?(y.signin?'Sign in again':'Show me your inbox'):y.id==='setup:ai'?'Connect an AI':y.pick?'Use '+y.pick.name:y.urgent?'Reconnect':'Open Settings')+"</span></button></div>";
|
|
@@ -2933,10 +3031,13 @@ var send=function(ans,j){ans=String(ans||'').trim();if(!ans||(!y.path&&!y.fix))r
|
|
|
2933
3031
|
(y.fix?api('/api/home/answer',j!=null?{id:y.id,pick:j}:{id:y.id,text:ans}):api('/api/agents/answer',{path:y.path,answers:[{q:y.q,a:ans}],rerun:true})).then(function(r){
|
|
2934
3032
|
if(!r||r.error){lock(false);say(nm+': '+((r&&r.error)||'the answer didn’t save. Try again on its block on the Workdesk.'),true);return;}
|
|
2935
3033
|
say(y.fix?nm+': '+(r.said||'Done.'):'Sent to '+(r.lane||nm)+'. '+(r.yours&&r.yours.length?'Still yours to do: '+r.yours.join(' '):r.rerun?'Its agent is picking it up now.':(r.note||'Saved in its ANSWERS.md.')));
|
|
2936
|
-
if(o.sent)o.sent();if(box.classList)box.classList.add('gone');setTimeout(function(){if(o.done)o.done();},650);}).catch(function(e){lock(false);say(nm+': '+String((e&&e.message)||e),true);});};
|
|
3034
|
+
bdSet(y,'');if(o.sent)o.sent();if(box.classList)box.classList.add('gone');setTimeout(function(){if(o.done)o.done();},650);}).catch(function(e){lock(false);say(nm+': '+String((e&&e.message)||e),true);});};
|
|
2937
3035
|
box.querySelectorAll('.nopt[data-j]').forEach(function(b){b.addEventListener('click',function(){var j=+b.getAttribute('data-j');send((y.options||[])[j],j);});});
|
|
2938
|
-
var fr=box.querySelector('.bfree'),fx=box.querySelector('.bfx');
|
|
2939
|
-
if(
|
|
3036
|
+
var fr=box.querySelector('.bfree'),fx=box.querySelector('.bfx'),fi=fx&&fx.querySelector('input');
|
|
3037
|
+
if(fi){var dr=bdGet(y);if(dr&&!fi.value){fi.value=dr.text;if(fx.classList)fx.classList.remove('hidden');if(fr)fr.setAttribute('aria-expanded','true');try{if(dr.s!=null)fi.setSelectionRange(dr.s,dr.e!=null?dr.e:dr.s);}catch(e){}}
|
|
3038
|
+
var keep=function(){bdSet(y,fi.value,fi.selectionStart,fi.selectionEnd);};['input','keyup','mouseup','select'].forEach(function(t){fi.addEventListener(t,keep);});}
|
|
3039
|
+
if(fr&&fx)fr.addEventListener('click',function(){var shut=fx.classList.toggle('hidden');fr.setAttribute('aria-expanded',shut?'false':'true');var i=fx.querySelector('input');if(!shut&&i&&i.focus)i.focus();if(o.grew)o.grew();});
|
|
3040
|
+
if(o.grew&&fi&&fx&&!fx.classList.contains('hidden'))setTimeout(o.grew,0); // a draft brought back opened it
|
|
2940
3041
|
if(fx)fx.addEventListener('submit',function(ev){if(ev&&ev.preventDefault)ev.preventDefault();var i=fx.querySelector('input');send(i&&i.value);});
|
|
2941
3042
|
var go=box.querySelector('.ngo');if(go)go.addEventListener('click',function(){if(!y.pick){lqFocus(y);return;}
|
|
2942
3043
|
// Pick your agent: the one Symbiot found, in one click; Settings has the others
|
|
@@ -2960,10 +3061,10 @@ function renderNeeds(){var el=$('needsbox');if(!el)return;var repo=TFILTER.repo;
|
|
|
2960
3061
|
var po=$('pushout');if(po&&po.getAttribute('data-needs')!==null&&po.getAttribute('data-needs')!==String(repo||'')){po.innerHTML='';po.removeAttribute('data-needs');}if(!repo||TARCH){el.innerHTML='';return;}
|
|
2961
3062
|
var ys=((LQ.home&&LQ.home.you)||[]).filter(function(y){return y.repo===repo&&y.kind==='ask';});
|
|
2962
3063
|
var paint=function(held){if(TFILTER.repo!==repo)return;if(!ys.length&&!held.length){el.innerHTML='';lqLight();return;}
|
|
2963
|
-
el
|
|
2964
|
-
ys.map(function(y,i){return "<div class='task nitem lqblob' data-k='"+i+"'>"+blobBody(y,false)+"</div>";}).join('')+
|
|
2965
|
-
held.map(function(x){return "<div class='task nitem'><div class='nhead'><b>"+esc(x.from)+" → "+esc(x.to)+"</b><span class='muted'>handed over, "+esc(LANEWORD.held)+"</span></div><div class='bq'>"+esc(x.text)+"</div></div>";}).join('');
|
|
2966
|
-
el.querySelectorAll('.nitem[data-k]').forEach(function(box){var y=ys[+box.getAttribute('data-k')];if(!y)return;
|
|
3064
|
+
keepTyping(el,"<div class='tgroup' style='color:var(--amber)'>Needs you here <span class='tcount'>"+(ys.length+held.length)+"</span></div>"+
|
|
3065
|
+
ys.map(function(y,i){return "<div class='task nitem lqblob' data-k='"+i+"' data-key='"+escQ(bdKey(y))+"'>"+blobBody(y,false)+"</div>";}).join('')+
|
|
3066
|
+
held.map(function(x){return "<div class='task nitem'><div class='nhead'><b>"+esc(x.from)+" → "+esc(x.to)+"</b><span class='muted'>handed over, "+esc(LANEWORD.held)+"</span></div><div class='bq'>"+esc(x.text)+"</div></div>";}).join(''));
|
|
3067
|
+
el.querySelectorAll('.nitem[data-k]').forEach(function(box){var y=ys[+box.getAttribute('data-k')];if(!y||box._wired)return;box._wired=true;
|
|
2967
3068
|
if(y.kind==='ask')wireBlob(box,y,{say:function(t,bad){var o=$('pushout');o.innerHTML="<div class='note "+(bad?'err':'ok')+"'>"+esc(t)+"</div>";o.setAttribute('data-needs',String(repo||''));},done:function(){LQ.home.you=(LQ.home.you||[]).filter(function(x){return x!==y;});LQ.fresh=true;renderNeeds();}});
|
|
2968
3069
|
else{var rv=box.querySelector('.ngo');if(rv){rv.innerHTML="<span class='bt'>Review it below</span>";rv.addEventListener('click',function(){var rc=null;document.querySelectorAll('#reviewlist .rcard').forEach(function(c){if(c.getAttribute('data-repo')===repo)rc=c;});if(!rc)return;rc.classList.add('lit');if(rc.scrollIntoView)rc.scrollIntoView({block:'center',behavior:'smooth'});var ap=rc.querySelector('.approve');if(ap&&ap.focus)ap.focus({preventScroll:true});});}}});
|
|
2969
3070
|
lqLight();};
|
|
@@ -2988,8 +3089,8 @@ function lqRemember(){if(LQ.back)return;LQ.hist=LQ.hist||[];var s=lqState(),top=
|
|
|
2988
3089
|
function lqBack(){var h=LQ.hist||[],s=h.pop(),cur=lqState();if(!s){if(cur.pool)s={scene:cur.scene,pool:null};else if(cur.scene==='work')s={scene:'home',pool:null};else return;}
|
|
2989
3090
|
LQ.back=true;try{if(s.scene==='work'&&(cur.scene!=='work'||cur.by!==s.by)){LQ.workBy=s.by||'task';lqWork();}else if(s.scene==='home'&&cur.scene==='work')lqHome();
|
|
2990
3091
|
if(s.pool){if(s.arch)lqArchive();else lqPool(s.pool);}else if(LQ.mode==='pool')lqSink();}finally{LQ.back=false;}}
|
|
2991
|
-
function lqPool(shape){if(shape==='map'&&LM.items&&LM.items.length)setTimeout(function(){lmFit();lmStart();},60);lqRemember();LQ.poolShape=shape;LQ.arch=false;tabPicked=true;setTab(shape);var b=document.body;if(b&&b.classList)b.classList.add('lq-pooled');LQ.mode='pool';var m=document.querySelector('main');if(m){m.scrollTop=0;if(m.focus)m.focus();}}
|
|
2992
|
-
function lqSink(){lqRemember();LQ.focus=null;var b=document.body;if(b&&b.classList)b.classList.remove('lq-pooled');LQ.mode='aware';lqAct();lqLoad(false);}
|
|
3092
|
+
function lqPool(shape){lqWhy(null);if(shape==='map'&&LM.items&&LM.items.length)setTimeout(function(){lmFit();lmStart();},60);lqRemember();LQ.poolShape=shape;LQ.arch=false;tabPicked=true;setTab(shape);var b=document.body;if(b&&b.classList)b.classList.add('lq-pooled');LQ.mode='pool';var m=document.querySelector('main');if(m){m.scrollTop=0;if(m.focus)m.focus();}}
|
|
3093
|
+
function lqSink(){lqWhy(null);lqRemember();LQ.focus=null;var b=document.body;if(b&&b.classList)b.classList.remove('lq-pooled');LQ.mode='aware';lqAct();lqLoad(false);}
|
|
2993
3094
|
function lqAct(){if(AWAY)return;LQ.lastAct=Date.now();if(LQ.mode==='rest'){LQ.mode='aware';lqLoad(true);}}
|
|
2994
3095
|
function lqTalkMode(on){LQ.talking=on;var b=document.body;if(b&&b.classList)b.classList.toggle('lq-talking',on);}
|
|
2995
3096
|
// Minimize: the chat shrinks back to its input and Home comes back as it was; what was
|
|
@@ -3002,7 +3103,8 @@ var h=!n?'':LQ.talkMin?"<button type='button' class='lqtb-open' aria-expanded='f
|
|
|
3002
3103
|
if(!sameHtml(el,h)){var bt=el.querySelector('button');if(bt)bt.addEventListener('click',function(){if(LQ.talkMin)lqChatOpen();else lqChatMin();});}}
|
|
3003
3104
|
function lqChatMin(){if(!LQ.talk.length||LQ.talkMin)return;LQ.talkMin=true;var i=$('lqask');if(i&&i.blur&&document.activeElement===i)i.blur();lqTalkMode(false);lqTalkBar();lqAct();}
|
|
3004
3105
|
function lqChatOpen(){LQ.talkMin=false;lqTalkMode(true);lqTalkShow(false);lqAct();var i=$('lqask');if(i&&i.focus)i.focus();}
|
|
3005
|
-
function lqTalkShow(wait){var el=$('lqtalk');if(!el)return;var n=LQ.talk.length;el.innerHTML=LQ.talk.map(function(m,i){var age=n-1-i,op=age<2?1:age===2?0.7:age===3?0.45:0.25;return "<div class='lqmsg"+(m.me?' me':'')+"' style='opacity:"+op+"'>"+(m.imgs&&m.imgs.length?"<span class='lqmimgs'>"+m.imgs.map(function(d){return "<img src='"+d+"' alt='your screenshot'>";}).join('')+"</span>":"")+esc(m.text)+stepsHtml(m.steps)+"</div>";}).join('')+(wait?"<div class='lqmsg thinking'>Thinking: recalling what it knows, reading what's here…</div>":'');if(el.scrollHeight)el.scrollTop=el.scrollHeight;
|
|
3106
|
+
function lqTalkShow(wait){var el=$('lqtalk');if(!el)return;var n=LQ.talk.length;el.innerHTML=LQ.talk.map(function(m,i){var age=n-1-i,op=age<2?1:age===2?0.7:age===3?0.45:0.25;return "<div class='lqmsg"+(m.me?' me':'')+"' style='opacity:"+op+"'>"+(m.imgs&&m.imgs.length?"<span class='lqmimgs'>"+m.imgs.map(function(d){return "<img src='"+d+"' alt='your screenshot'>";}).join('')+"</span>":"")+esc(m.text)+lqAltsHtml(m.go)+stepsHtml(m.steps)+"</div>";}).join('')+(wait?"<div class='lqmsg thinking'>Thinking: recalling what it knows, reading what's here…</div>":'');if(el.scrollHeight)el.scrollTop=el.scrollHeight;
|
|
3107
|
+
el.querySelectorAll('[data-go]').forEach(function(b){b.addEventListener('click',function(){lqNav(b.getAttribute('data-go'));});});
|
|
3006
3108
|
// scrolled up to read back: the whole history shows, clear; back at the bottom it rolls on again
|
|
3007
3109
|
if(!el.wired&&el.addEventListener){el.wired=true;el.addEventListener('scroll',function(){var up=el.scrollHeight-el.scrollTop-el.clientHeight>24;if(el.classList)el.classList.toggle('back',up);});}lqTalkBar();}
|
|
3008
3110
|
// Screenshots in the talk: paste one (Ctrl+V) or drop it on the bar; it shows as a
|
|
@@ -3042,7 +3144,8 @@ try{r.start();}catch(e){VOICE.on=false;voiceMic(false);}}
|
|
|
3042
3144
|
// Speaking, with no voice to choose: the system's own when it has a natural one
|
|
3043
3145
|
// (Edge on Windows, Macs with Apple's better voices, phones), in your region's accent if it has that too;
|
|
3044
3146
|
// otherwise Symbiot's own (voice.mjs: Piper on this computer), fetched once in the
|
|
3045
|
-
// background the first time, with the system's voice meanwhile. Plain words only
|
|
3147
|
+
// background the first time, with the system's voice meanwhile. Plain words only: the
|
|
3148
|
+
// reply's spoken version (mind.mjs spokenLine), and no arrows read out as "right arrow".
|
|
3046
3149
|
var SPEAK={audio:null,n:0,own:null,asked:false};
|
|
3047
3150
|
function voiceLang(){return navigator.language||'en-US';}
|
|
3048
3151
|
function voiceTag(v){return String((v&&v.lang)||'').toLowerCase().split('_').join('-');}
|
|
@@ -3054,7 +3157,7 @@ if(!good.length&&/android|iphone|ipad/i.test(navigator.userAgent||''))good=vs; /
|
|
|
3054
3157
|
good.sort(function(a,b){return (voiceTag(b)===tag)-(voiceTag(a)===tag);});
|
|
3055
3158
|
return {best:good[0]||null,any:good[0]||vs[0]||null};}
|
|
3056
3159
|
function voiceStop(){SPEAK.n++;try{if(window.speechSynthesis)window.speechSynthesis.cancel();}catch(e){}if(SPEAK.audio){try{SPEAK.audio.pause();}catch(e){}SPEAK.audio=null;}}
|
|
3057
|
-
function voiceClean(t){var clean=String(t||'').replace(/[*_#>|]+/g,' ').replace(new RegExp(String.fromCharCode(96),'g'),'').replace(/https?:[^ ]+/g,'a link').replace(/ +/g,' ').trim();
|
|
3160
|
+
function voiceClean(t){var clean=String(t||'').replace(/\\s*[\\u2190-\\u21ff\\u2794-\\u27bf\\u27f5-\\u27ff]\\s*/g,', ').replace(/(^|[.?!:]), /g,'$1 ').replace(/[*_#>|]+/g,' ').replace(new RegExp(String.fromCharCode(96),'g'),'').replace(/https?:[^ ]+/g,'a link').replace(/ +/g,' ').trim();
|
|
3058
3161
|
if(clean.length>700){var cut=clean.slice(0,700),at=Math.max(cut.lastIndexOf('. '),cut.lastIndexOf('? '),cut.lastIndexOf('! '));clean=(at>200?cut.slice(0,at+1):cut)+' The rest is on screen.';}
|
|
3059
3162
|
return clean;}
|
|
3060
3163
|
function voiceSystem(text,v){if(!window.speechSynthesis||!text)return;try{var u=new SpeechSynthesisUtterance(text);if(v)u.voice=v;u.lang=voiceLang();u.rate=1.02;window.speechSynthesis.cancel();window.speechSynthesis.speak(u);}catch(e){}}
|
|
@@ -3074,14 +3177,36 @@ voiceSystem(clean,nat&&nat.any);};
|
|
|
3074
3177
|
if(SPEAK.own&&SPEAK.own.state==='ready')go(SPEAK.own);else api('/api/voice?lang='+encodeURIComponent(lang)).then(function(st){SPEAK.own=st;go(st);}).catch(function(){go(null);});}
|
|
3075
3178
|
// Chrome lists its voices only after a first ask
|
|
3076
3179
|
try{if(window.speechSynthesis)window.speechSynthesis.getVoices();}catch(e){}
|
|
3180
|
+
// Going somewhere by asking: "take me to reports" (or "navigate me to tasks", "open my
|
|
3181
|
+
// week") opens it here, with no wait for the model; anything vaguer goes to the model,
|
|
3182
|
+
// whose reply can carry a page to open (mind.mjs goOf: { to, why, alts }), and the
|
|
3183
|
+
// pages it might have meant instead, a tap each under its reply.
|
|
3184
|
+
var LQ_NAV_ALIAS={dashboard:'board',cards:'board',tasks:'tasks',agents:'tasks',workdesk:'tasks',home:'home','home page':'home',archive:'archive'};
|
|
3185
|
+
function lqNavHit(q){var low=String(q||'').toLowerCase().replace(/[.!?]+$/,'').replace(/^(hey |ok |okay )?(symbiot,? )?/,'').replace(/^(please |can you |could you |would you )+/,'').replace(/ please$/,'').trim();
|
|
3186
|
+
var v=low.match(/^(?:open(?: up)?|show(?: me)?|go(?: back)? to|take me(?: back)?(?: to)?|navigate(?: me)? to|bring up|switch to|jump to|go(?: back)?)(?: the| my)? (.+)$/),t=(v?v[1]:low).replace(/ (page|tab|screen)$/,'').trim();
|
|
3187
|
+
if(!v&&t!=='dashboard'&&!LQNAMES[t]&&!Object.keys(LQNAMES).some(function(k){return LQNAMES[k].toLowerCase()===t;}))return '';
|
|
3188
|
+
if(v&&t==='home')return 'home';if(LQ_NAV_ALIAS[t]&&(v||t!=='home'))return LQ_NAV_ALIAS[t];
|
|
3189
|
+
var hit='';Object.keys(LQNAMES).forEach(function(k){if(!hit&&(LQNAMES[k].toLowerCase()===t||k===t))hit=k;});return hit;}
|
|
3190
|
+
// g: the model's go, when it picked the page: its reason and the other pages, shown on the
|
|
3191
|
+
// page it opened too (the chat sinks under an open page).
|
|
3192
|
+
function lqNav(to,g){to=String(to||'');lqTalkMode(true);
|
|
3193
|
+
if(to==='home'){if(LQ.mode==='pool')lqSink();if(LQ.scene==='work')lqHome();return;}
|
|
3194
|
+
if(to==='archive'){lqUse('tasks',null,'talk');lqArchive();}
|
|
3195
|
+
else if(to.indexOf('lane:')===0)lqOpen({kind:'proj',repo:to.slice(5)});
|
|
3196
|
+
else if(LQNAMES[to]){lqUse(to,null,'talk');lqGo(to);}
|
|
3197
|
+
else return;
|
|
3198
|
+
lqWhy(g&&g.to===to?g:null);}
|
|
3199
|
+
function lqAltsHtml(g){return g&&g.alts&&g.alts.length?"<span class='lqalts'>Or: "+g.alts.map(function(a){return "<button type='button' class='lqmb' data-go='"+escQ(a.to)+"'>"+esc(a.label||a.to)+"</button>";}).join('')+"</span>":'';}
|
|
3200
|
+
function lqWhy(g){var el=$('lqwhy');if(!el)return;var on=!!(g&&(g.why||(g.alts&&g.alts.length)));if(el.classList)el.classList.toggle('on',on);
|
|
3201
|
+
el.innerHTML=on?(g.why?"<span>"+esc(g.why)+"</span>":'')+(g.alts&&g.alts.length?"<span>Or:</span>"+g.alts.map(function(a){return "<button type='button' class='lqmb' data-go='"+escQ(a.to)+"'>"+esc(a.label||a.to)+"</button>";}).join(''):''):'';
|
|
3202
|
+
if(on)el.querySelectorAll('[data-go]').forEach(function(b){b.addEventListener('click',function(){lqNav(b.getAttribute('data-go'));});});}
|
|
3077
3203
|
function lqSay(){var spoke=VOICE.spoke;VOICE.spoke=false;var i=$('lqask');var q=((i&&i.value)||'').trim(),shots=LQ.atts.slice();if(!q&&!shots.length)return;if(!q)q='Take a look at this screenshot.';i.value='';lqGrowAsk();LQ.atts=[];lqAttsShow();lqAct();
|
|
3078
|
-
var low=q.toLowerCase().replace(/^(please |can you |could you )/,''),hit=
|
|
3079
|
-
|
|
3080
|
-
if(hit){lqTalkMode(true);lqUse(hit,null,'talk');lqGo(hit);return;}
|
|
3204
|
+
var low=q.toLowerCase().replace(/^(please |can you |could you )/,''),hit=lqNavHit(q);
|
|
3205
|
+
if(hit){lqNav(hit);return;}
|
|
3081
3206
|
if(/^(go|start|go for it|start them|start it)[.! ]*$/.test(low)){lqTalkMode(true);lqGoWork();return;}
|
|
3082
3207
|
if(/^(home|back)[.! ]*$/.test(low)&&LQ.scene==='work'){lqHome();return;}
|
|
3083
3208
|
LQ.talkMin=false;lqTalkMode(true);LQ.talk.push({me:true,text:q,imgs:shots.map(function(a){return a.data;})});LQ.talk=LQ.talk.slice(-8);lqTalkShow(true);
|
|
3084
|
-
api('/api/home/ask',{question:q,images:shots}).then(function(r){LQ.talk.push({me:false,steps:r.steps||[],text:r.error==='not-connected'?'Connect an AI in Settings to talk to me. Say "open settings".':(r.answer||r.error||'(no answer)')});if(spoke)voiceSpeak(LQ.talk[LQ.talk.length-1].text);LQ.talk=LQ.talk.slice(-8);lqTalkShow(false);LQ.ripple=[0.5,0.47,LQ.t];lqLoad(false);}).catch(function(e){LQ.talk.push({me:false,text:String((e&&e.message)||e)});lqTalkShow(false);});}
|
|
3209
|
+
api('/api/home/ask',{question:q,images:shots}).then(function(r){LQ.talk.push({me:false,steps:r.steps||[],go:r.go||null,text:r.error==='not-connected'?'Connect an AI in Settings to talk to me. Say "open settings".':(r.answer||r.error||'(no answer)')});if(spoke)voiceSpeak(r.spoken||LQ.talk[LQ.talk.length-1].text);LQ.talk=LQ.talk.slice(-8);lqTalkShow(false);LQ.ripple=[0.5,0.47,LQ.t];if(r.go&&r.go.to){lqNav(r.go.to,r.go);return;}lqLoad(false);}).catch(function(e){LQ.talk.push({me:false,text:String((e&&e.message)||e)});lqTalkShow(false);});}
|
|
3085
3210
|
// The physics: each droplet a critically damped spring to its place (no wobble,
|
|
3086
3211
|
// no overshoot), pushed off its neighbours and the core where they'd overlap.
|
|
3087
3212
|
function lqStep(){var S0=lqSize();if(S0.w!==LQ.bw||S0.h!==LQ.bh){if(LQ.scene==='work'&&LQ.workData)lqBuildWork();else if(LQ.adapt)lqBuild();}var S=S0,cx=S.w/2,cy=(LQ.scene!=='work'&&LQ.cy)||S.h*0.47,k=0.022,c=2*Math.sqrt(k),th=LQ.theme,still=th.still,rest=LQ.mode==='rest',pool=LQ.mode==='pool',talk=LQ.talking&&!pool;
|