@javierfrancoleal/dsh-supervisor 1.0.25 → 1.0.27

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/dashboard.html CHANGED
@@ -102,6 +102,19 @@ td,th{border-bottom:1px solid var(--line);padding:9px 6px;text-align:left}
102
102
  .row .btn{flex:1;min-width:110px}
103
103
  .switchuser{margin-top:26px;text-align:center}
104
104
  .langs{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin:8px 0 14px}
105
+ .langov{position:fixed;inset:0;background:linear-gradient(160deg,#4D6BFE,#7c5cff 60%,#a78bfa);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:90;animation:rise .3s ease}
106
+ .langov .top{position:absolute;top:22px;right:26px}
107
+ .langov .close{background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.3);color:#fff;border-radius:999px;width:44px;height:44px;font-size:18px;cursor:pointer}
108
+ .orow{width:min(520px,92vw);display:flex;justify-content:space-between;align-items:center;padding:15px 22px;border-radius:14px;font-size:19px;color:#fff;cursor:pointer;transition:background .15s,transform .1s}
109
+ .orow:hover{background:rgba(255,255,255,.12)}
110
+ .orow:active{transform:scale(.98)}
111
+ .orow .sub{font-size:13px;opacity:.7}
112
+ .orow.center{background:rgba(255,255,255,.30);font-size:23px;font-weight:800;border:1px solid rgba(255,255,255,.45)}
113
+ .orow.center .sub{opacity:.9}
114
+ .langreel{width:min(340px,86vw);height:58px;margin:10px auto;background:rgba(255,255,255,.10);border:1px solid rgba(255,255,255,.30);border-radius:10px;backdrop-filter:blur(8px);overflow:hidden;cursor:pointer;position:relative;box-shadow:0 10px 30px rgba(15,23,42,.15)}
115
+ body.dark .langreel{background:rgba(20,31,49,.55)}
116
+ .ticks{display:flex;flex-direction:column;will-change:transform}
117
+ .tick{height:58px;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:600;opacity:.85;color:#fff}
105
118
  .pill{background:var(--card);border:1px solid var(--line);border-radius:999px;padding:8px 13px;font-size:13px;cursor:pointer}
106
119
  .pill.active{background:var(--blue);color:#fff;border-color:var(--blue)}
107
120
  .person{background:var(--card2);border:1px solid var(--line);border-radius:16px;padding:14px;margin:10px 0}
@@ -161,6 +174,10 @@ body.wall pre{max-height:40vh}
161
174
  <button class="btn small gray" id="logoutBtn" style="display:none">Log out</button>
162
175
  </header>
163
176
  <main>
177
+ <div id="langOverlay" class="langov hidden" style="display:none">
178
+ <div class="top"><button class="close" id="closeOverlay">✕</button></div>
179
+ <div id="langRows"></div>
180
+ </div>
164
181
  <div id="loginView" class="card" style="max-width:440px;margin:26px auto">
165
182
  <div class="loginbrand">
166
183
  <div class="mark" style="background:none;box-shadow:none"><svg width="64" height="64" viewBox="0 0 64 64" aria-hidden="true"><defs><linearGradient id="osgd" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#4D6BFE"/><stop offset="1" stop-color="#7c5cff"/></linearGradient></defs><path pathLength="100" d="M10 32 C10 22 16 14 32 14 C48 14 54 22 54 32 C54 42 48 50 32 50 C20 50 12.5 45.2 10.5 38.5 C14 42.5 20 41 25.4 34.8 A6.6 6.6 0 0 1 38.6 34.8 C38.6 31.4 36.6 28.2 32 22.5 C27.4 28.2 25.4 31.4 25.4 34.8" fill="none" stroke="url(#osgd)" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
@@ -168,7 +185,7 @@ body.wall pre{max-height:40vh}
168
185
  <p id="tagline">The AI works. The human decides.</p>
169
186
  </div>
170
187
  <h2 id="loginTitle" style="font-size:14px;color:var(--muted);text-align:center">Log in</h2>
171
- <div id="langs" class="langs"></div>
188
+ <div class="langreel" id="langReel"><div class="ticks" id="langTicks"></div></div>
172
189
  <input id="loginUser" placeholder="User" autocomplete="username">
173
190
  <div class="passwrap">
174
191
  <input id="loginPass" type="password" placeholder="Password" autocomplete="current-password">
@@ -350,7 +367,7 @@ body.wall pre{max-height:40vh}
350
367
  <div id="toast" class="toast"></div>
351
368
  <div id="msg" class="note" style="text-align:center"></div>
352
369
  </main>
353
- <footer class="foot"><div class="langs" id="footLangs"></div><div class="copyr">Suite J.Franco · open source (MIT) · autor J.Franco</div></footer>
370
+ <footer class="foot"><div class="langs" id="footLangs"></div><div class="copyr">Le Veilleux · open source (MIT) · autor J.Franco</div><div class="copyr" id="verBadge" style="opacity:.75;margin-top:2px"></div></footer>
354
371
  <script>
355
372
  "use strict";
356
373
  const LANG={es:"ES",en:"EN",fr:"FR",de:"DE",it:"IT",pt:"PT",zh:"中文",ja:"日本語",ko:"한국어",ar:"العربية"};
@@ -436,6 +453,7 @@ async function loadRegister(){
436
453
  renderRegSales(r);
437
454
  }
438
455
  if(me&&me.role==="employee"){$("regCount").textContent=(r.sales||[]).length+" "+t("salesToday");}
456
+ try{api("/api/health").then(hp=>{if(hp&&hp.version)$("verBadge").textContent="supervisor v"+hp.version+" · app 0.1.0 · dsh "+((hp.workers!==undefined)?"":"")+"runtime";});}catch(e){}
439
457
  }
440
458
  function renderRegSales(r){
441
459
  const q=($("regSearch").value||"").toLowerCase();
@@ -495,6 +513,46 @@ function applyLang(){
495
513
  panelLinks();
496
514
  reRenderDynamic();
497
515
  }
516
+ const LANGNAMES={es:"Español",en:"English",fr:"Français",de:"Deutsch",it:"Italiano",pt:"Português",zh:"中文",ja:"日本語",ko:"한국어",ar:"العربية"};
517
+ const LANGORDER=["it","zh","de","es","en","fr","ja","pt","ko","ar"];
518
+ function buildLangMenu(){
519
+ const rows=$("langRows");
520
+ for(const l of LANGORDER){
521
+ const r=document.createElement("div");
522
+ r.className="orow"+(l==="en"?" center":"");
523
+ r.innerHTML='<span>'+LANGNAMES[l]+'</span><span class="sub">'+l.toUpperCase()+'</span>';
524
+ r.onclick=()=>{cur=l;applyLang();$("langOverlay").style.display="none";markReel();};
525
+ rows.appendChild(r);
526
+ }
527
+ }
528
+ function markReel(){
529
+ document.querySelectorAll("#langTicks .tick").forEach(t=>t.classList.toggle("active",t.dataset.l===cur));
530
+ }
531
+ function buildReel(){
532
+ for(let pass=0;pass<2;pass++){
533
+ for(const l of Object.keys(LANG)){
534
+ const d=document.createElement("div");
535
+ d.className="tick";d.dataset.l=l;d.textContent=LANGNAMES[l];
536
+ $("langTicks").appendChild(d);
537
+ }
538
+ }
539
+ const N=Object.keys(LANG).length,H=58,CYCLE=20000;
540
+ const t0=performance.now();
541
+ (function frame(now){
542
+ const t=(now-t0)%CYCLE;
543
+ $("langTicks").style.transform="translateY(-"+((t/CYCLE)*N*H)+"px)";
544
+ requestAnimationFrame(frame);
545
+ })(t0);
546
+ }
547
+ buildLangMenu();
548
+ buildReel();
549
+ markReel();
550
+ $("langReel").onclick=()=>{$("langOverlay").style.display="flex";const en=document.querySelector("#langRows .center");if(en)en.scrollIntoView({block:"center"});};
551
+ $("closeOverlay").onclick=()=>{$("langOverlay").style.display="none";};
552
+ // The Apple flow: after the splash, the language menu opens once (until chosen).
553
+ setTimeout(()=>{
554
+ try{if(!localStorage.getItem("dsh-lang")){$("langOverlay").style.display="flex";}}catch(e){}
555
+ },1600);
498
556
  function pill(l){for(const host of ["langs","footLangs"]){const b=document.createElement("button");b.className="pill";b.dataset.l=l;b.textContent=LANG[l];b.onclick=()=>{cur=l;applyLang();};$(host).appendChild(b);}}
499
557
  for(const l of Object.keys(LANG))pill(l);
500
558
  try{const q=new URLSearchParams(location.search);const ql=q.get("lang");if(ql&&S[ql])cur=ql;}catch(e){}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@javierfrancoleal/dsh-supervisor",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "The Veilleux central control plane of the J.Franco agentic workflow suite: user accounts, the live conversation flow (listen → per-person summary → confirm → orders), telemetry, and the universal sector dashboard with 10 languages. Created by J.Franco.",
5
5
  "author": "J.Franco",
6
6
  "license": "MIT",
package/server.js CHANGED
@@ -44,6 +44,7 @@ const LIVE_FILE = path.join(DATA_DIR, "live.json");
44
44
  const SECTOR_FILE = path.join(DATA_DIR, "sector.json");
45
45
  const REG_FILE = path.join(DATA_DIR, "register.json");
46
46
  const PRICE_PER_MILLION = Number(process.env.DSH_PRICE_PER_MILLION || 0.28);
47
+ const VERSION = "1.0.25";
47
48
  // Homeostasis: the daily token budget (metabolic limit). The brain rests
48
49
  // when the budget is spent — no runaway costs.
49
50
  const DAILY_BUDGET_USD = Number(process.env.DSH_DAILY_BUDGET_USD || 0); // 0 = unlimited: consumption is the business
@@ -384,7 +385,7 @@ async function api(req, res, pathname) {
384
385
  }
385
386
  if (pathname === "/api/health" && req.method === "GET") {
386
387
  const m = metrics();
387
- return send(res, 200, { ok: true, uptime: Math.round(process.uptime()), workers: m.workers, reports: m.reports, budgetUsd: DAILY_BUDGET_USD, spentTodayUsd: todayCostUsd(), budgetPct: DAILY_BUDGET_USD > 0 ? Math.round((todayCostUsd() / DAILY_BUDGET_USD) * 100) : 0 }, true);
388
+ return send(res, 200, { ok: true, version: VERSION, uptime: Math.round(process.uptime()), workers: m.workers, reports: m.reports, budgetUsd: DAILY_BUDGET_USD, spentTodayUsd: todayCostUsd(), budgetPct: DAILY_BUDGET_USD > 0 ? Math.round((todayCostUsd() / DAILY_BUDGET_USD) * 100) : 0 }, true);
388
389
  }
389
390
  if (pathname === "/api/sector" && req.method === "GET") {
390
391
  try { return send(res, 200, JSON.parse(fs.readFileSync(SECTOR_FILE, "utf8"))); }