Upload health-server.js with huggingface_hub

#3
by ruang101 - opened
Files changed (1) hide show
  1. health-server.js +330 -150
health-server.js CHANGED
@@ -446,115 +446,48 @@ function valueOrUnset(value, fallback = "Not set") {
446
  }
447
 
448
  function renderTile({
449
- title,
450
- value,
451
- detail = "",
452
- tone = "neutral",
453
- meta = "",
454
  }) {
455
- return `<article class="tile ${tone}">
456
- <div class="tile-head">
457
- <span class="tile-title">${escapeHtml(title)}</span>
458
- <span class="tile-dot"></span>
 
 
459
  </div>
460
- <div class="tile-value">${value}</div>
461
- ${detail ? `<div class="tile-detail">${detail}</div>` : ""}
462
- ${meta ? `<div class="tile-meta">${meta}</div>` : ""}
463
  </article>`;
464
  }
465
 
466
  function renderDashboard(data) {
467
  const syncStatus = String(data.backup?.status || "unknown");
468
- const syncTone = ["success", "restored", "synced", "configured"].includes(
469
- syncStatus,
470
- )
471
- ? "ok"
472
- : syncStatus === "disabled"
473
- ? "warn"
474
- : "neutral";
475
  const telegramTone = data.telegram.configured
476
- ? data.telegram.webhookListening || !data.telegram.webhook
477
- ? "ok"
478
- : "warn"
479
- : "warn";
480
  const keepaliveConfigured = data.keepalive?.configured === true;
481
- const keepaliveStatus = String(
482
- data.keepalive?.status ||
483
- (process.env.CLOUDFLARE_WORKERS_TOKEN ? "pending" : "not configured"),
484
- );
485
- const keepAliveTone = keepaliveConfigured
486
- ? "ok"
487
- : process.env.CLOUDFLARE_WORKERS_TOKEN
488
- ? "warn"
489
- : "neutral";
490
  const telegramDetail = data.telegram.configured
491
- ? `${data.telegram.webhook ? "Webhook" : "Polling"}${data.telegram.proxy ? " via CF proxy" : ""}`
492
  : "Not configured";
493
- const backupDetail = data.backup?.message
494
- ? escapeHtml(data.backup.message)
495
- : "No status yet";
496
  const keepAliveDetail = keepaliveConfigured
497
  ? `Pinging <code>${escapeHtml(data.keepalive.targetUrl || "/health")}</code>`
498
  : keepaliveStatus === "error" && data.keepalive?.message
499
  ? escapeHtml(data.keepalive.message)
500
- : process.env.CLOUDFLARE_WORKERS_TOKEN
501
- ? "Worker pending or failed"
502
- : "Not configured";
503
- const serviceOk = data.gateway && data.dashboard;
504
 
505
  const tiles = [
506
- renderTile({
507
- title: "Gateway",
508
- value: toneBadge(
509
- data.gateway ? "Online" : "Offline",
510
- data.gateway ? "ok" : "off",
511
- ),
512
- detail: data.gateway
513
- ? `API on port ${data.ports.gateway}`
514
- : `Unreachable`,
515
- tone: data.gateway ? "ok" : "off",
516
- meta: data.authConfigured ? "Protected" : "Unprotected",
517
- }),
518
- renderTile({
519
- title: "Model",
520
- value: `<code>${valueOrUnset(data.model)}</code>`,
521
- detail: `Provider: ${valueOrUnset(data.provider || "auto")}`,
522
- tone: data.model ? "ok" : "warn",
523
- }),
524
- renderTile({
525
- title: "Runtime",
526
- value: escapeHtml(data.uptime),
527
- detail: `Port ${data.ports.public}`,
528
- tone: "neutral",
529
- }),
530
- renderTile({
531
- title: "Telegram",
532
- value: toneBadge(
533
- data.telegram.configured ? "Configured" : "Disabled",
534
- telegramTone,
535
- ),
536
- detail: telegramDetail,
537
- tone: telegramTone,
538
- }),
539
- renderTile({
540
- title: "Backup",
541
- value: toneBadge(syncStatus.toUpperCase(), syncTone),
542
- detail: backupDetail,
543
- tone: syncTone,
544
- meta: data.backup?.timestamp
545
- ? `<span class="local-time" data-iso="${data.backup.timestamp}"></span>`
546
- : "",
547
- }),
548
- renderTile({
549
- title: "Keep Awake",
550
- value: toneBadge(
551
- keepaliveConfigured ? "CF Cron" : keepaliveStatus.toUpperCase(),
552
- keepAliveTone,
553
- ),
554
- detail: keepAliveDetail,
555
- tone: keepAliveTone,
556
- }),
557
- ].join("");
558
 
559
  return `<!doctype html>
560
  <html lang="en">
@@ -562,72 +495,320 @@ function renderDashboard(data) {
562
  <meta charset="utf-8" />
563
  <meta name="viewport" content="width=device-width, initial-scale=1" />
564
  <title>HuggingMes</title>
 
 
 
565
  <style>
566
- :root { color-scheme: dark; --bg:#08080f; --panel:#12111b; --panel2:#151421; --line:#26243a; --text:#f6f4ff; --muted:#7f7a9e; --soft:#b8b3d7; --good:#22c55e; --warn:#f5c542; --bad:#fb7185; --accent:#6557df; --accent2:#7c6cf2; }
567
- * { box-sizing:border-box; }
568
- body { margin:0; min-height:100vh; font-family:Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background:var(--bg); color:var(--text); font-size:13px; }
569
- main { width:min(720px, calc(100% - 32px)); margin:0 auto; padding:36px 0 44px; }
570
- header { text-align:center; margin-bottom:22px; }
571
- h1 { margin:0; font-size:1.65rem; line-height:1; letter-spacing:0; }
572
- .subtitle { margin-top:12px; color:var(--muted); font-size:.72rem; text-transform:uppercase; letter-spacing:.14em; font-weight:800; }
573
- .hero-buttons { display:flex; gap:10px; margin:24px 0 20px; }
574
- .hero-action { display:flex; flex:1; min-height:46px; align-items:center; justify-content:center; border-radius:8px; background:#ffffff; color:#000000; text-decoration:none; font-weight:850; font-size:.98rem; transition:background 0.15s ease; }
575
- .hero-action:hover { background:#e5e5e5; }
576
- .hero-action.secondary { background:var(--panel); color:var(--text); border:1px solid var(--line); }
577
- .hero-action.secondary:hover { background:var(--panel2); }
578
- .overview { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px; margin-bottom:10px; }
579
- .tile { border:1px solid var(--line); background:var(--panel); border-radius:11px; padding:18px; min-height:124px; display:flex; flex-direction:column; gap:10px; position:relative; }
580
- .tile.ok { border-color:rgba(34,197,94,.22); }
581
- .tile.warn { border-color:rgba(245,197,66,.24); }
582
- .tile.off { border-color:rgba(251,113,133,.28); }
583
- .tile-head { display:flex; align-items:center; justify-content:space-between; gap:12px; }
584
- .tile-title { color:var(--muted); font-size:.67rem; letter-spacing:.18em; text-transform:uppercase; font-weight:850; }
585
- .tile-dot { width:7px; height:7px; border-radius:50%; background:var(--line); }
586
- .tile.ok .tile-dot { background:var(--good); }
587
- .tile.warn .tile-dot { background:var(--warn); }
588
- .tile.off .tile-dot { background:var(--bad); }
589
- .tile-value { font-size:1.12rem; font-weight:850; overflow-wrap:anywhere; }
590
- .tile-detail { color:var(--soft); line-height:1.45; font-size:.83rem; }
591
- .tile-meta { color:var(--muted); line-height:1.4; font-size:.75rem; margin-top:auto; overflow-wrap:anywhere; }
592
-
593
- code { background:#232234; border:1px solid #34324c; border-radius:6px; padding:2px 6px; color:var(--text); font-size:.9em; }
594
- pre { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; background:#0d0d0d; border:1px solid var(--line); border-radius:7px; padding:10px; color:var(--soft); font-size:.82rem; line-height:1.45; }
595
- .row { display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
596
- .badge { display:inline-flex; align-items:center; width:max-content; border:1px solid var(--line); border-radius:999px; padding:5px 10px; font-size:.72rem; font-weight:850; line-height:1; text-transform:uppercase; }
597
- .badge.ok { color:var(--good); border-color:rgba(34,197,94,.34); background:rgba(34,197,94,.11); }
598
- .badge.warn { color:var(--warn); border-color:rgba(245,197,66,.34); background:rgba(245,197,66,.11); }
599
- .badge.off { color:var(--bad); border-color:rgba(251,113,133,.34); background:rgba(251,113,133,.11); }
600
- .badge.neutral { color:var(--soft); }
601
- .muted { color:var(--muted); }
602
- .button { display:inline-flex; align-items:center; justify-content:center; min-height:40px; padding:0 16px; border-radius:8px; color:#fff; background:var(--accent); text-decoration:none; font-weight:850; font-size:.9rem; }
603
- .button.secondary { color:var(--text); background:#242424; border:1px solid var(--line); }
604
- footer { color:var(--muted); text-align:center; font-size:.74rem; margin-top:18px; }
605
- footer .live { color:var(--good); }
606
- @media (max-width: 700px) { .overview { grid-template-columns:1fr; } main { width:min(100% - 22px, 720px); padding-top:28px; } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
  </style>
608
  </head>
609
  <body>
610
  <main>
611
- <header>
 
 
 
 
612
  <h1>HuggingMes</h1>
613
- <div class="subtitle">Self-hosted - Hermes Agent</div>
 
 
 
 
 
614
  </header>
615
- <div class="hero-buttons">
616
- <a class="hero-action" data-space-link="app" href="${APP_BASE}/">Open Hermes Agent β†’</a>
617
- <a class="hero-action secondary" data-space-link="terminal" href="/terminal/">πŸ’» Open Terminal β†’</a>
618
- <a class="hero-action secondary" data-space-link="env-builder" href="/env-builder">βš™οΈ ENV Builder β†’</a>
 
619
  </div>
620
- <section class="overview">
621
- ${tiles}
622
- </section>
623
- <footer>Built by <a href="https://github.com/somratpro" target="_blank" rel="noopener noreferrer" style="color: var(--accent); text-decoration: none;">@somratpro</a></footer>
 
 
 
624
  </main>
625
  <script>
626
  document.querySelectorAll('.local-time').forEach(el => {
627
- const date = new Date(el.getAttribute('data-iso'));
628
- if (!isNaN(date)) {
629
- el.textContent = 'At ' + date.toLocaleTimeString();
630
- }
631
  });
632
  const inEmbeddedApp = (() => { try { return window.top !== window.self; } catch { return true; } })();
633
  const isDirectHfSpaceHost = /\.hf\.space$/i.test(window.location.hostname);
@@ -635,12 +816,11 @@ function renderDashboard(data) {
635
  const SPACE_IS_PRIVATE = ${JSON.stringify(SPACE_IS_PRIVATE)};
636
  if (SPACE_IS_PRIVATE && isDirectHfSpaceHost && !inEmbeddedApp && HF_SPACE_URL) {
637
  const notice = document.createElement('div');
638
- notice.style.cssText = 'position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#08080f;color:#f6f4ff;font-family:sans-serif;flex-direction:column;gap:16px;z-index:9999';
639
- notice.innerHTML = '<span style="font-size:1.1rem">πŸ”’ Private Space &mdash; Redirecting&hellip;</span><a href="' + HF_SPACE_URL + '" style="color:#a5b4fc;font-size:.85rem">Click here if not redirected</a>';
640
  document.body.appendChild(notice);
641
  setTimeout(() => { window.location.replace(HF_SPACE_URL); }, 300);
642
  }
643
- // Force new-tab navigation when running inside the HF App iframe or on a raw .hf.space link
644
  const openInNewTab = inEmbeddedApp || isDirectHfSpaceHost;
645
  document.querySelectorAll('a[data-space-link]').forEach((a) => {
646
  if (openInNewTab) {
 
446
  }
447
 
448
  function renderTile({
449
+ title, value, detail = "", tone = "neutral", meta = "",
 
 
 
 
450
  }) {
451
+ const dotColors = { ok: "var(--good)", warn: "var(--warn)", off: "var(--bad)", neutral: "var(--line)" };
452
+ const glowColors = { ok: "rgba(34,197,94,.18)", warn: "rgba(245,197,66,.18)", off: "rgba(251,113,133,.18)", neutral: "transparent" };
453
+ return `<article class="tile tile--${tone}">
454
+ <div class="tile__header">
455
+ <span class="tile__label">${escapeHtml(title)}</span>
456
+ <span class="tile__dot" style="background:${dotColors[tone]};box-shadow:0 0 6px ${glowColors[tone]}"></span>
457
  </div>
458
+ <div class="tile__value">${value}</div>
459
+ ${detail ? `<div class="tile__detail">${detail}</div>` : ""}
460
+ ${meta ? `<div class="tile__meta">${meta}</div>` : ""}
461
  </article>`;
462
  }
463
 
464
  function renderDashboard(data) {
465
  const syncStatus = String(data.backup?.status || "unknown");
466
+ const syncTone = ["success", "restored", "synced", "configured"].includes(syncStatus) ? "ok"
467
+ : syncStatus === "disabled" ? "warn" : "neutral";
 
 
 
 
 
468
  const telegramTone = data.telegram.configured
469
+ ? (data.telegram.webhookListening || !data.telegram.webhook ? "ok" : "warn") : "warn";
 
 
 
470
  const keepaliveConfigured = data.keepalive?.configured === true;
471
+ const keepaliveStatus = String(data.keepalive?.status || (process.env.CLOUDFLARE_WORKERS_TOKEN ? "pending" : "not configured"));
472
+ const keepAliveTone = keepaliveConfigured ? "ok" : process.env.CLOUDFLARE_WORKERS_TOKEN ? "warn" : "neutral";
 
 
 
 
 
 
 
473
  const telegramDetail = data.telegram.configured
474
+ ? `${data.telegram.webhook ? "Webhook" : "Polling"}${data.telegram.proxy ? " Β· via CF proxy" : ""}`
475
  : "Not configured";
476
+ const backupDetail = data.backup?.message ? escapeHtml(data.backup.message) : "No status yet";
 
 
477
  const keepAliveDetail = keepaliveConfigured
478
  ? `Pinging <code>${escapeHtml(data.keepalive.targetUrl || "/health")}</code>`
479
  : keepaliveStatus === "error" && data.keepalive?.message
480
  ? escapeHtml(data.keepalive.message)
481
+ : process.env.CLOUDFLARE_WORKERS_TOKEN ? "Worker pending or failed" : "Not configured";
 
 
 
482
 
483
  const tiles = [
484
+ { title: "Gateway", value: toneBadge(data.gateway ? "Online" : "Offline", data.gateway ? "ok" : "off"), detail: data.gateway ? `API on port ${data.ports.gateway}` : "Unreachable", tone: data.gateway ? "ok" : "off", meta: data.authConfigured ? "Protected" : "Unprotected" },
485
+ { title: "Model", value: `<code>${valueOrUnset(data.model)}</code>`, detail: `Provider: ${valueOrUnset(data.provider || "auto")}`, tone: data.model ? "ok" : "warn" },
486
+ { title: "Runtime", value: escapeHtml(data.uptime), detail: `Port ${data.ports.public}`, tone: "neutral" },
487
+ { title: "Telegram", value: toneBadge(data.telegram.configured ? "Active" : "Disabled", telegramTone), detail: telegramDetail, tone: telegramTone },
488
+ { title: "Backup", value: toneBadge(syncStatus.toUpperCase(), syncTone), detail: backupDetail, tone: syncTone, meta: data.backup?.timestamp ? `<span class="local-time" data-iso="${data.backup.timestamp}"></span>` : "" },
489
+ { title: "Keep Awake", value: toneBadge(keepaliveConfigured ? "CF Cron" : keepaliveStatus.toUpperCase(), keepAliveTone), detail: keepAliveDetail, tone: keepAliveTone },
490
+ ].map(renderTile).join("");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
 
492
  return `<!doctype html>
493
  <html lang="en">
 
495
  <meta charset="utf-8" />
496
  <meta name="viewport" content="width=device-width, initial-scale=1" />
497
  <title>HuggingMes</title>
498
+ <link rel="preconnect" href="https://fonts.googleapis.com">
499
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
500
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
501
  <style>
502
+ :root {
503
+ --bg: #06060c;
504
+ --surface: rgba(255,255,255,0.03);
505
+ --surface2: rgba(255,255,255,0.05);
506
+ --border: rgba(255,255,255,0.08);
507
+ --border2: rgba(255,255,255,0.14);
508
+ --text: #f0f2ff;
509
+ --muted: #6b7280;
510
+ --soft: #9ca3af;
511
+ --good: #34d399;
512
+ --warn: #fbbf24;
513
+ --bad: #f87171;
514
+ --accent: #818cf8;
515
+ --accent-glow: rgba(129,140,248,.25);
516
+ --r: 16px;
517
+ --r2: 10px;
518
+ }
519
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
520
+ html { scroll-behavior: smooth; }
521
+ body {
522
+ font-family: 'Inter', ui-sans-serif, system-ui, sans-serif;
523
+ background: var(--bg);
524
+ color: var(--text);
525
+ min-height: 100vh;
526
+ -webkit-font-smoothing: antialiased;
527
+ }
528
+ body::before {
529
+ content: '';
530
+ position: fixed;
531
+ inset: 0;
532
+ background:
533
+ radial-gradient(ellipse 80% 60% at 50% -20%, rgba(99,102,241,.12) 0%, transparent 60%),
534
+ radial-gradient(ellipse 50% 40% at 80% 110%, rgba(129,140,248,.06) 0%, transparent 50%);
535
+ pointer-events: none;
536
+ z-index: 0;
537
+ }
538
+ main {
539
+ position: relative;
540
+ z-index: 1;
541
+ max-width: 860px;
542
+ margin: 0 auto;
543
+ padding: 64px 24px 80px;
544
+ }
545
+ /* ── Hero ── */
546
+ .hero { text-align: center; margin-bottom: 48px; }
547
+ .hero-badge {
548
+ display: inline-flex;
549
+ align-items: center;
550
+ gap: 7px;
551
+ background: rgba(129,140,248,.1);
552
+ border: 1px solid rgba(129,140,248,.25);
553
+ border-radius: 999px;
554
+ padding: 6px 14px;
555
+ font-size: .72rem;
556
+ font-weight: 600;
557
+ color: var(--accent);
558
+ letter-spacing: .04em;
559
+ text-transform: uppercase;
560
+ margin-bottom: 20px;
561
+ }
562
+ .pulse-dot {
563
+ width: 7px; height: 7px; border-radius: 50%;
564
+ background: var(--good);
565
+ box-shadow: 0 0 0 0 rgba(52,211,153,.4);
566
+ animation: pulse 2s infinite;
567
+ }
568
+ @keyframes pulse {
569
+ 0% { box-shadow: 0 0 0 0 rgba(52,211,153,.4); }
570
+ 70% { box-shadow: 0 0 0 7px rgba(52,211,153,0); }
571
+ 100% { box-shadow: 0 0 0 0 rgba(52,211,153,0); }
572
+ }
573
+ h1 {
574
+ font-size: clamp(2.2rem, 6vw, 3.2rem);
575
+ font-weight: 800;
576
+ letter-spacing: -.04em;
577
+ line-height: 1.05;
578
+ background: linear-gradient(135deg, #f0f2ff 30%, #818cf8 70%, #c084fc 100%);
579
+ -webkit-background-clip: text;
580
+ -webkit-text-fill-color: transparent;
581
+ background-clip: text;
582
+ margin-bottom: 12px;
583
+ }
584
+ .subtitle {
585
+ font-size: 1rem;
586
+ color: var(--muted);
587
+ font-weight: 400;
588
+ line-height: 1.5;
589
+ max-width: 480px;
590
+ margin: 0 auto 28px;
591
+ }
592
+ /* ── CTA buttons ── */
593
+ .cta-row {
594
+ display: flex;
595
+ gap: 10px;
596
+ justify-content: center;
597
+ flex-wrap: wrap;
598
+ margin-bottom: 56px;
599
+ }
600
+ .btn {
601
+ display: inline-flex;
602
+ align-items: center;
603
+ gap: 8px;
604
+ padding: 11px 22px;
605
+ border-radius: var(--r2);
606
+ font-weight: 600;
607
+ font-size: .88rem;
608
+ text-decoration: none;
609
+ transition: all .2s cubic-bezier(.4,0,.2,1);
610
+ cursor: pointer;
611
+ border: none;
612
+ }
613
+ .btn-primary {
614
+ background: linear-gradient(135deg, #6366f1, #818cf8);
615
+ color: #fff;
616
+ box-shadow: 0 4px 20px rgba(99,102,241,.35);
617
+ }
618
+ .btn-primary:hover {
619
+ transform: translateY(-2px);
620
+ box-shadow: 0 8px 30px rgba(99,102,241,.45);
621
+ }
622
+ .btn-secondary {
623
+ background: var(--surface2);
624
+ color: var(--text);
625
+ border: 1px solid var(--border2);
626
+ backdrop-filter: blur(8px);
627
+ }
628
+ .btn-secondary:hover {
629
+ background: rgba(255,255,255,.08);
630
+ border-color: rgba(255,255,255,.2);
631
+ transform: translateY(-1px);
632
+ }
633
+ /* ── Divider ── */
634
+ .divider {
635
+ display: flex;
636
+ align-items: center;
637
+ gap: 12px;
638
+ margin-bottom: 20px;
639
+ }
640
+ .divider-line {
641
+ flex: 1;
642
+ height: 1px;
643
+ background: linear-gradient(90deg, transparent, var(--border), transparent);
644
+ }
645
+ .divider-label {
646
+ font-size: .7rem;
647
+ font-weight: 600;
648
+ letter-spacing: .12em;
649
+ text-transform: uppercase;
650
+ color: var(--muted);
651
+ }
652
+ /* ── Stats row ── */
653
+ .stats-row {
654
+ display: grid;
655
+ grid-template-columns: repeat(3, 1fr);
656
+ gap: 12px;
657
+ margin-bottom: 32px;
658
+ }
659
+ .stat {
660
+ background: var(--surface);
661
+ border: 1px solid var(--border);
662
+ border-radius: var(--r);
663
+ padding: 18px 16px;
664
+ text-align: center;
665
+ backdrop-filter: blur(10px);
666
+ }
667
+ .stat__val {
668
+ font-family: 'JetBrains Mono', monospace;
669
+ font-size: 1.5rem;
670
+ font-weight: 600;
671
+ color: var(--text);
672
+ margin-bottom: 4px;
673
+ }
674
+ .stat__lab {
675
+ font-size: .68rem;
676
+ color: var(--muted);
677
+ text-transform: uppercase;
678
+ letter-spacing: .1em;
679
+ font-weight: 600;
680
+ }
681
+ /* ── Tiles grid ── */
682
+ .tiles {
683
+ display: grid;
684
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
685
+ gap: 12px;
686
+ margin-bottom: 32px;
687
+ }
688
+ .tile {
689
+ background: var(--surface);
690
+ border: 1px solid var(--border);
691
+ border-radius: var(--r);
692
+ padding: 20px;
693
+ display: flex;
694
+ flex-direction: column;
695
+ gap: 10px;
696
+ position: relative;
697
+ overflow: hidden;
698
+ backdrop-filter: blur(10px);
699
+ transition: border-color .2s, box-shadow .2s, transform .2s;
700
+ }
701
+ .tile::before {
702
+ content: '';
703
+ position: absolute;
704
+ top: 0; left: 0; right: 0;
705
+ height: 1px;
706
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,.1), transparent);
707
+ }
708
+ .tile--ok { border-color: rgba(52,211,153,.2); }
709
+ .tile--ok:hover { box-shadow: 0 0 20px rgba(52,211,153,.08); }
710
+ .tile--warn { border-color: rgba(251,191,36,.2); }
711
+ .tile--warn:hover { box-shadow: 0 0 20px rgba(251,191,36,.08); }
712
+ .tile--off { border-color: rgba(248,113,113,.2); }
713
+ .tile--off:hover { box-shadow: 0 0 20px rgba(248,113,113,.08); }
714
+ .tile:hover { transform: translateY(-2px); border-color: var(--border2); }
715
+ .tile__header {
716
+ display: flex;
717
+ align-items: center;
718
+ justify-content: space-between;
719
+ }
720
+ .tile__label {
721
+ font-size: .66rem;
722
+ font-weight: 700;
723
+ letter-spacing: .14em;
724
+ text-transform: uppercase;
725
+ color: var(--muted);
726
+ }
727
+ .tile__dot {
728
+ width: 8px; height: 8px;
729
+ border-radius: 50%;
730
+ flex-shrink: 0;
731
+ transition: box-shadow .3s;
732
+ }
733
+ .tile__value { font-size: 1.05rem; font-weight: 700; line-height: 1.2; }
734
+ .tile__detail { color: var(--soft); font-size: .8rem; line-height: 1.45; }
735
+ .tile__meta { color: var(--muted); font-size: .72rem; margin-top: auto; padding-top: 4px; }
736
+ code {
737
+ background: rgba(255,255,255,.06);
738
+ border: 1px solid var(--border);
739
+ border-radius: 6px;
740
+ padding: 2px 7px;
741
+ font-family: 'JetBrains Mono', monospace;
742
+ font-size: .88em;
743
+ color: var(--text);
744
+ }
745
+ /* ── Badges ── */
746
+ .badge {
747
+ display: inline-flex;
748
+ align-items: center;
749
+ border-radius: 999px;
750
+ padding: 3px 10px;
751
+ font-size: .7rem;
752
+ font-weight: 700;
753
+ letter-spacing: .04em;
754
+ text-transform: uppercase;
755
+ }
756
+ .badge.ok { color: var(--good); background: rgba(52,211,153,.12); border: 1px solid rgba(52,211,153,.25); }
757
+ .badge.warn { color: var(--warn); background: rgba(251,191,36,.12); border: 1px solid rgba(251,191,36,.25); }
758
+ .badge.off { color: var(--bad); background: rgba(248,113,113,.12); border: 1px solid rgba(248,113,113,.25); }
759
+ .badge.neutral { color: var(--soft); background: rgba(255,255,255,.05); border: 1px solid var(--border); }
760
+ .muted { color: var(--muted); font-style: italic; }
761
+ /* ── Footer ── */
762
+ footer {
763
+ text-align: center;
764
+ padding-top: 32px;
765
+ border-top: 1px solid var(--border);
766
+ color: var(--muted);
767
+ font-size: .78rem;
768
+ }
769
+ footer a { color: var(--accent); text-decoration: none; }
770
+ footer a:hover { text-decoration: underline; }
771
+ /* ── Responsive ── */
772
+ @media (max-width: 600px) {
773
+ main { padding: 40px 16px 60px; }
774
+ .stats-row { grid-template-columns: 1fr; }
775
+ h1 { font-size: 2rem; }
776
+ }
777
  </style>
778
  </head>
779
  <body>
780
  <main>
781
+ <header class="hero">
782
+ <div class="hero-badge">
783
+ <span class="pulse-dot"></span>
784
+ Self-hosted Β· 24/7
785
+ </div>
786
  <h1>HuggingMes</h1>
787
+ <p class="subtitle">Self-hosted Hermes Agent gateway on Hugging Face Spaces. Chat, tools, memory β€” always on.</p>
788
+ <div class="cta-row">
789
+ <a class="btn btn-primary" data-space-link="app" href="${APP_BASE}/">Open Hermes Agent β†’</a>
790
+ <a class="btn btn-secondary" data-space-link="terminal" href="/terminal/">πŸ’» Terminal</a>
791
+ <a class="btn btn-secondary" data-space-link="env-builder" href="/env-builder">βš™οΈ ENV Builder</a>
792
+ </div>
793
  </header>
794
+
795
+ <div class="divider">
796
+ <div class="divider-line"></div>
797
+ <span class="divider-label">System Status</span>
798
+ <div class="divider-line"></div>
799
  </div>
800
+
801
+ <div class="tiles">${tiles}</div>
802
+
803
+ <footer>
804
+ Built by <a href="https://github.com/somratpro" target="_blank">@somratpro</a> Β·
805
+ <a href="https://github.com/NousResearch/hermes-agent" target="_blank">Hermes Agent</a>
806
+ </footer>
807
  </main>
808
  <script>
809
  document.querySelectorAll('.local-time').forEach(el => {
810
+ const d = new Date(el.getAttribute('data-iso'));
811
+ if (!isNaN(d)) el.textContent = 'At ' + d.toLocaleTimeString();
 
 
812
  });
813
  const inEmbeddedApp = (() => { try { return window.top !== window.self; } catch { return true; } })();
814
  const isDirectHfSpaceHost = /\.hf\.space$/i.test(window.location.hostname);
 
816
  const SPACE_IS_PRIVATE = ${JSON.stringify(SPACE_IS_PRIVATE)};
817
  if (SPACE_IS_PRIVATE && isDirectHfSpaceHost && !inEmbeddedApp && HF_SPACE_URL) {
818
  const notice = document.createElement('div');
819
+ notice.style.cssText = 'position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#06060c;color:#f0f2ff;font-family:sans-serif;flex-direction:column;gap:16px;z-index:9999';
820
+ notice.innerHTML = '<span style="font-size:1.1rem">πŸ”’ Private Space &mdash; Redirecting&hellip;</span><a href="' + HF_SPACE_URL + '" style="color:#818cf8;font-size:.85rem">Click here if not redirected</a>';
821
  document.body.appendChild(notice);
822
  setTimeout(() => { window.location.replace(HF_SPACE_URL); }, 300);
823
  }
 
824
  const openInNewTab = inEmbeddedApp || isDirectHfSpaceHost;
825
  document.querySelectorAll('a[data-space-link]').forEach((a) => {
826
  if (openInNewTab) {