From 2bb4287ac9b1c2fd1b22596abfa07595969941d4 Mon Sep 17 00:00:00 2001 From: nv-kasikritc Date: Sun, 15 Mar 2026 21:42:58 +0000 Subject: [PATCH 1/7] feat(ui): inference --no-verify, OpenShell rebrand, NVIDIA green vars, fixed save bar, upgrade URL short name --- brev/launch.sh | 4 +- .../__tests__/cluster-inference.test.js | 3 +- brev/welcome-ui/server.js | 4 +- .../extension/inference-page.ts | 4 +- .../extension/model-registry.ts | 5 +- .../extension/styles.css | 243 ++++++++++-------- 6 files changed, 148 insertions(+), 115 deletions(-) diff --git a/brev/launch.sh b/brev/launch.sh index 9296266..d1ea0b9 100755 --- a/brev/launch.sh +++ b/brev/launch.sh @@ -568,12 +568,12 @@ set_inference_route() { log "Configuring inference route..." - if "$CLI_BIN" inference set --provider nvidia-endpoints --model minimaxai/minimax-m2.5 >/dev/null 2>&1; then + if "$CLI_BIN" inference set --provider nvidia-endpoints --model minimaxai/minimax-m2.5 --no-verify >/dev/null 2>&1; then log "Configured inference via '$CLI_BIN inference set'." return fi - if "$CLI_BIN" cluster inference set --provider nvidia-endpoints --model minimaxai/minimax-m2.5 >/dev/null 2>&1; then + if "$CLI_BIN" cluster inference set --provider nvidia-endpoints --model minimaxai/minimax-m2.5 --no-verify >/dev/null 2>&1; then log "Configured inference via legacy '$CLI_BIN cluster inference set'." return fi diff --git a/brev/welcome-ui/__tests__/cluster-inference.test.js b/brev/welcome-ui/__tests__/cluster-inference.test.js index 7db882d..cca1c44 100644 --- a/brev/welcome-ui/__tests__/cluster-inference.test.js +++ b/brev/welcome-ui/__tests__/cluster-inference.test.js @@ -151,7 +151,7 @@ describe("POST /api/cluster-inference", () => { expect(res.status).toBe(400); }); - it("TC-CI10: calls nemoclaw cluster inference set with --provider and --model", async () => { + it("TC-CI10: calls nemoclaw cluster inference set with --provider, --model, and --no-verify", async () => { execFile.mockImplementation((cmd, args, opts, cb) => { if (typeof opts === "function") { cb = opts; opts = {}; } cb(null, "", ""); @@ -170,5 +170,6 @@ describe("POST /api/cluster-inference", () => { expect(args).toContain("test-prov"); expect(args).toContain("--model"); expect(args).toContain("test-model"); + expect(args).toContain("--no-verify"); }); }); diff --git a/brev/welcome-ui/server.js b/brev/welcome-ui/server.js index 6ce5bed..6108a77 100644 --- a/brev/welcome-ui/server.js +++ b/brev/welcome-ui/server.js @@ -1205,8 +1205,8 @@ async function handleClusterInferenceSet(req, res) { try { const result = await execFirstSuccess( [ - cliArgs("inference", "set", "--provider", providerName, "--model", modelId), - cliArgs("cluster", "inference", "set", "--provider", providerName, "--model", modelId), + cliArgs("inference", "set", "--provider", providerName, "--model", modelId, "--no-verify"), + cliArgs("cluster", "inference", "set", "--provider", providerName, "--model", modelId, "--no-verify"), ], 30000 ); diff --git a/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/inference-page.ts b/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/inference-page.ts index 680a270..04421a5 100644 --- a/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/inference-page.ts +++ b/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/inference-page.ts @@ -297,10 +297,10 @@ function buildGatewayStrip(): HTMLElement { tooltip.innerHTML = `
Your Code sends requests to inference.local
-
NemoClaw Proxy intercepts, injects credentials
+
OpenShell Proxy intercepts, injects credentials
Provider API receives authenticated request
- `; + `; tooltip.style.display = "none"; let tooltipOpen = false; diff --git a/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/model-registry.ts b/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/model-registry.ts index 5689d15..a503c3c 100644 --- a/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/model-registry.ts +++ b/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/model-registry.ts @@ -383,10 +383,13 @@ const UPGRADE_INTEGRATIONS_BASE = "https://build.nvidia.com/openshell/integratio /** * URL for upgrading the same model via an NVIDIA Cloud Partner. * Use when active route is NVIDIA free tier; pass current model id (e.g. qwen/qwen3.5-397b-a17b). + * Query param uses short name only (e.g. minimax-m2.5) so integrations page resolves correctly. */ export function getUpgradeIntegrationsUrl(modelId: string): string { if (!modelId || !modelId.trim()) return UPGRADE_INTEGRATIONS_BASE; - return `${UPGRADE_INTEGRATIONS_BASE}?model=${encodeURIComponent(modelId.trim())}`; + const trimmed = modelId.trim(); + const shortName = trimmed.includes("/") ? trimmed.split("/").pop()! : trimmed; + return `${UPGRADE_INTEGRATIONS_BASE}?model=${encodeURIComponent(shortName)}`; } /** diff --git a/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/styles.css b/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/styles.css index db6d073..92f656e 100644 --- a/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/styles.css +++ b/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/styles.css @@ -1,7 +1,27 @@ /* =========================================== - NeMoClaw DevX — NVIDIA Green: #76B900 + NeMoClaw DevX — NVIDIA brand colors (official) + https://www.nvidia.com/en-us/about-nvidia/legal-info/logo-brand-usage/ + - NVIDIA Green: #76B900 (Pantone 376 C). Min contrast 1:10 on background. + - On light backgrounds we use a darker same-hue green for accents to meet + contrast; solid buttons use the accent variable with white text. =========================================== */ +:root { + --nvidia-green: #76B900; + --nvidia-green-hover: #6aa300; + --nvidia-green-active: #5a8500; + --nvidia-accent: var(--nvidia-green); + --nvidia-accent-hover: var(--nvidia-green-hover); + --nvidia-accent-active: var(--nvidia-green-active); +} + +/* Light mode: use darker NVIDIA green for text/border accents (same hue, meets contrast) */ +:root[data-theme="light"] { + --nvidia-accent: #5a8c00; + --nvidia-accent-hover: #4a7600; + --nvidia-accent-active: #3d6200; +} + /* Hide the OpenClaw version-update banner */ .update-banner { display: none !important; @@ -18,9 +38,9 @@ padding: 6px 14px; height: 32px; box-sizing: border-box; - border: 1px solid #76B900; + border: 1px solid var(--nvidia-accent); border-radius: var(--radius-full, 9999px); - background: #76B900; + background: var(--nvidia-accent); color: #fff; font-size: 12px; font-weight: 600; @@ -36,8 +56,8 @@ } .nemoclaw-deploy-btn:hover { - background: #6aa300; - border-color: #6aa300; + background: var(--nvidia-accent-hover); + border-color: var(--nvidia-accent-hover); box-shadow: 0 4px 12px rgba(118, 185, 0, 0.35), 0 0 20px rgba(118, 185, 0, 0.15); @@ -45,14 +65,14 @@ } .nemoclaw-deploy-btn:active { - background: #5a8500; - border-color: #5a8500; + background: var(--nvidia-accent-active); + border-color: var(--nvidia-accent-active); transform: translateY(0); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); } .nemoclaw-deploy-btn:focus-visible { - outline: 2px solid #76B900; + outline: 2px solid var(--nvidia-accent); outline-offset: 2px; } @@ -181,7 +201,7 @@ } .nemoclaw-modal__partner-cta a { - color: #76B900; + color: var(--nvidia-accent); text-decoration: none; } @@ -212,7 +232,7 @@ } .nemoclaw-target:hover { - border-color: #76B900; + border-color: var(--nvidia-accent); background: rgba(118, 185, 0, 0.06); box-shadow: 0 0 0 1px rgba(118, 185, 0, 0.15); } @@ -224,7 +244,7 @@ background: rgba(118, 185, 0, 0.12); display: grid; place-items: center; - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-target__icon svg { @@ -259,7 +279,7 @@ } .nemoclaw-target:hover .nemoclaw-target__arrow { - color: #76B900; + color: var(--nvidia-accent); transform: translateX(2px); } @@ -296,7 +316,7 @@ .nemoclaw-status--success { border: 1px solid rgba(118, 185, 0, 0.25); background: rgba(118, 185, 0, 0.08); - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-status--error { @@ -308,7 +328,7 @@ .nemoclaw-status--loading { border: 1px solid rgba(118, 185, 0, 0.25); background: rgba(118, 185, 0, 0.08); - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-status svg { @@ -331,20 +351,20 @@ =========================================== */ .nemoclaw-nav-group .nav-label__text { - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-nav-group .nav-label:hover .nav-label__text { - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-nav-group .nav-label__chevron { - color: #76B900; + color: var(--nvidia-accent); opacity: 0.7; } .nemoclaw-nav-group .nav-item { - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-nav-group .nav-item:hover { @@ -352,7 +372,7 @@ } .nemoclaw-nav-group .nav-item .nav-item__icon { - color: #76B900; + color: var(--nvidia-accent); opacity: 0.85; } @@ -362,12 +382,12 @@ } .nemoclaw-nav-group .nav-item.active { - color: #76B900; + color: var(--nvidia-accent); background: rgba(118, 185, 0, 0.12); } .nemoclaw-nav-group .nav-item.active .nav-item__icon { - color: #76B900; + color: var(--nvidia-accent); opacity: 1; } @@ -426,7 +446,7 @@ main.content { display: grid; place-items: center; margin-bottom: 20px; - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-empty-state__icon svg { @@ -478,14 +498,14 @@ main.content { .nemoclaw-model-powered { font-size: 9px; font-weight: 500; - color: #76B900; + color: var(--nvidia-accent); text-decoration: none; white-space: nowrap; transition: color 150ms ease; } .nemoclaw-model-powered:hover { - color: #76B900; + color: var(--nvidia-accent); text-decoration: underline; text-underline-offset: 2px; } @@ -500,7 +520,7 @@ main.content { } .nemoclaw-model-upgrade-link { - color: #76B900; + color: var(--nvidia-accent); text-decoration: none; } @@ -536,7 +556,7 @@ main.content { } .nemoclaw-model-selector--open .nemoclaw-model-trigger { - border-color: #76B900; + border-color: var(--nvidia-accent); } .nemoclaw-model-trigger__label { @@ -546,7 +566,7 @@ main.content { } .nemoclaw-model-trigger__value { - color: #76B900; + color: var(--nvidia-accent); font-size: 13px; font-weight: 600; } @@ -563,7 +583,7 @@ main.content { .nemoclaw-model-selector--open .nemoclaw-model-trigger__chevron { transform: rotate(180deg); - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-model-trigger__chevron svg { @@ -628,7 +648,7 @@ main.content { } .nemoclaw-model-option--selected { - color: #76B900; + color: var(--nvidia-accent); background: rgba(118, 185, 0, 0.10); } @@ -679,7 +699,7 @@ body[data-nemoclaw-ready] .nemoclaw-connect-overlay { width: 32px; height: 32px; border: 3px solid rgba(118, 185, 0, 0.2); - border-top-color: #76B900; + border-top-color: var(--nvidia-accent); border-radius: 50%; animation: nemoclaw-spin 0.8s linear infinite; } @@ -870,7 +890,7 @@ body.nemoclaw-switching openclaw-app { animation: nemoclaw-slide-down 200ms cubic-bezier(0.16, 1, 0.3, 1); border: 1px solid rgba(118, 185, 0, 0.25); background: rgba(118, 185, 0, 0.08); - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-switching-banner svg { @@ -886,7 +906,7 @@ body.nemoclaw-switching openclaw-app { .nemoclaw-switching-banner--propagating { border-color: rgba(118, 185, 0, 0.2); background: rgba(118, 185, 0, 0.06); - color: #76B900; + color: var(--nvidia-accent); flex-wrap: wrap; } @@ -911,13 +931,13 @@ body.nemoclaw-switching openclaw-app { height: 100%; width: 0%; border-radius: 2px; - background: #76B900; + background: var(--nvidia-accent); } .nemoclaw-switching-banner--success { border-color: rgba(118, 185, 0, 0.3); background: rgba(118, 185, 0, 0.1); - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-switching-banner--error { @@ -971,7 +991,7 @@ body.nemoclaw-switching openclaw-app { display: grid; place-items: center; margin-bottom: 16px; - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-key-intro__icon svg { @@ -994,7 +1014,7 @@ body.nemoclaw-switching openclaw-app { .nemoclaw-key-intro__link { font-size: 13px; font-weight: 600; - color: #76B900; + color: var(--nvidia-accent); text-decoration: none; transition: color 150ms ease; } @@ -1012,7 +1032,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-key-intro__partner-cta a { - color: #76B900; + color: var(--nvidia-accent); text-decoration: none; } @@ -1077,7 +1097,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-key-field__input:focus { - border-color: #76B900; + border-color: var(--nvidia-accent); box-shadow: 0 0 0 2px rgba(118, 185, 0, 0.2); } @@ -1128,7 +1148,7 @@ body.nemoclaw-switching openclaw-app { .nemoclaw-key-dot--ok, .nemoclaw-nav-dot--ok { - background: #76B900; + background: var(--nvidia-accent); box-shadow: 0 0 4px rgba(118, 185, 0, 0.5); } @@ -1152,9 +1172,9 @@ body.nemoclaw-switching openclaw-app { align-items: center; gap: 6px; padding: 10px 24px; - border: 1px solid #76B900; + border: 1px solid var(--nvidia-accent); border-radius: var(--radius-md, 8px); - background: #76B900; + background: var(--nvidia-accent); color: #fff; font-size: 14px; font-weight: 600; @@ -1164,20 +1184,20 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-key-save:hover { - background: #6aa300; - border-color: #6aa300; + background: var(--nvidia-accent-hover); + border-color: var(--nvidia-accent-hover); box-shadow: 0 4px 12px rgba(118, 185, 0, 0.35); transform: translateY(-1px); } .nemoclaw-key-save:active { - background: #5a8500; - border-color: #5a8500; + background: var(--nvidia-accent-active); + border-color: var(--nvidia-accent-active); transform: translateY(0); } .nemoclaw-key-save:focus-visible { - outline: 2px solid #76B900; + outline: 2px solid var(--nvidia-accent); outline-offset: 2px; } @@ -1193,7 +1213,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-key-feedback--success { - color: #76B900; + color: var(--nvidia-accent); animation: nemoclaw-fade-in 200ms ease; } @@ -1282,7 +1302,7 @@ body.nemoclaw-switching openclaw-app { display: flex; width: 18px; height: 18px; - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-policy-loading__spinner svg { @@ -1317,7 +1337,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-retry-btn:hover { - border-color: #76B900; + border-color: var(--nvidia-accent); background: rgba(118, 185, 0, 0.06); } @@ -1364,8 +1384,8 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-tabbar__tab--active { - color: #76B900; - border-bottom-color: #76B900; + color: var(--nvidia-accent); + border-bottom-color: var(--nvidia-accent); } .nemoclaw-policy-tabbar__count { @@ -1379,7 +1399,7 @@ body.nemoclaw-switching openclaw-app { font-weight: 700; border-radius: 9999px; background: rgba(118, 185, 0, 0.12); - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-policy-tab-panel { @@ -1504,7 +1524,7 @@ body.nemoclaw-switching openclaw-app { padding: 1px 5px; border-radius: 3px; background: rgba(118, 185, 0, 0.08); - color: #76B900; + color: var(--nvidia-accent); } /* Badges */ @@ -1539,7 +1559,7 @@ body.nemoclaw-switching openclaw-app { .nemoclaw-policy-badge--editable { border: 1px solid rgba(118, 185, 0, 0.3); background: rgba(118, 185, 0, 0.08); - color: #76B900; + color: var(--nvidia-accent); } /* Recommendations (from recent blocks — one-click approve) */ @@ -1604,7 +1624,7 @@ body.nemoclaw-switching openclaw-app { font-size: 12px; font-weight: 600; color: #fff; - background: #76B900; + background: var(--nvidia-accent); border: none; border-radius: var(--radius-sm, 6px); cursor: pointer; @@ -1612,7 +1632,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-recommendations__approve-all:hover:not(:disabled) { - background: #6aa300; + background: var(--nvidia-accent-hover); } .nemoclaw-policy-recommendations__approve-all:disabled { @@ -1693,7 +1713,7 @@ body.nemoclaw-switching openclaw-app { font-size: 13px; font-weight: 600; color: #fff; - background: #76B900; + background: var(--nvidia-accent); border: none; border-radius: var(--radius-sm, 6px); cursor: pointer; @@ -1701,7 +1721,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-recommendation-card__approve:hover:not(:disabled) { - background: #6aa300; + background: var(--nvidia-accent-hover); } .nemoclaw-policy-recommendation-card__approve:disabled { @@ -1768,7 +1788,7 @@ body.nemoclaw-switching openclaw-app { padding: 1px 5px; border-radius: 4px; background: rgba(118, 185, 0, 0.08); - color: #76B900; + color: var(--nvidia-accent); } /* Immutable cards */ @@ -1904,7 +1924,7 @@ body.nemoclaw-switching openclaw-app { .nemoclaw-policy-path--rw { background: rgba(118, 185, 0, 0.06); border-color: rgba(118, 185, 0, 0.2); - color: #76B900; + color: var(--nvidia-accent); } /* Network policy cards */ @@ -1972,7 +1992,7 @@ body.nemoclaw-switching openclaw-app { .nemoclaw-policy-netcard--expanded .nemoclaw-policy-netcard__chevron { transform: rotate(90deg); - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-policy-netcard__name { @@ -2271,7 +2291,7 @@ body.nemoclaw-switching openclaw-app { .nemoclaw-policy-input:focus, .nemoclaw-policy-select:focus { - border-color: #76B900; + border-color: var(--nvidia-accent); box-shadow: 0 0 0 2px rgba(118, 185, 0, 0.15); } @@ -2311,8 +2331,8 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-add-btn:hover { - border-color: #76B900; - color: #76B900; + border-color: var(--nvidia-accent); + color: var(--nvidia-accent); background: rgba(118, 185, 0, 0.04); } @@ -2343,8 +2363,8 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-add-small-btn:hover { - border-color: #76B900; - color: #76B900; + border-color: var(--nvidia-accent); + color: var(--nvidia-accent); } .nemoclaw-policy-add-small-btn svg { @@ -2360,23 +2380,26 @@ body.nemoclaw-switching openclaw-app { /* Save bar */ .nemoclaw-policy-savebar { - position: sticky; + position: fixed; bottom: 0; + left: 0; + right: 0; + z-index: 1000; display: flex; align-items: center; justify-content: space-between; gap: 16px; - padding: 14px 18px; - margin-top: 24px; + padding: 16px 24px; border: 1px solid var(--border, #27272a); - border-radius: var(--radius-lg, 12px); + border-top-width: 2px; + border-radius: 0; background: var(--card, #181b22); - box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.25); + box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.35); } :root[data-theme="light"] .nemoclaw-policy-savebar { background: #fff; - box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.08); + box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.12); } .nemoclaw-policy-savebar__info { @@ -2395,7 +2418,7 @@ body.nemoclaw-switching openclaw-app { padding: 1px 4px; border-radius: 3px; background: rgba(118, 185, 0, 0.08); - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-policy-savebar__info-icon { @@ -2429,9 +2452,9 @@ body.nemoclaw-switching openclaw-app { align-items: center; gap: 6px; padding: 9px 22px; - border: 1px solid #76B900; + border: 1px solid var(--nvidia-accent); border-radius: var(--radius-md, 8px); - background: #76B900; + background: var(--nvidia-accent); color: #fff; font-size: 13px; font-weight: 600; @@ -2442,15 +2465,15 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-save-btn:hover { - background: #6aa300; - border-color: #6aa300; + background: var(--nvidia-accent-hover); + border-color: var(--nvidia-accent-hover); box-shadow: 0 4px 12px rgba(118, 185, 0, 0.35); transform: translateY(-1px); } .nemoclaw-policy-save-btn:active { - background: #5a8500; - border-color: #5a8500; + background: var(--nvidia-accent-active); + border-color: var(--nvidia-accent-active); transform: translateY(0); } @@ -2462,7 +2485,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-save-btn:focus-visible { - outline: 2px solid #76B900; + outline: 2px solid var(--nvidia-accent); outline-offset: 2px; } @@ -2479,11 +2502,11 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-savebar__feedback--saving { - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-policy-savebar__feedback--success { - color: #76B900; + color: var(--nvidia-accent); animation: nemoclaw-fade-in 200ms ease; } @@ -2606,7 +2629,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-enf-pill--enforce { - color: #76B900; + color: var(--nvidia-accent); background: rgba(118, 185, 0, 0.1); border: 1px solid rgba(118, 185, 0, 0.25); } @@ -2743,7 +2766,7 @@ body.nemoclaw-switching openclaw-app { .nemoclaw-policy-confirm-btn--create { background: rgba(118, 185, 0, 0.12); - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-policy-confirm-btn--create:hover { @@ -2844,7 +2867,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-ep-advanced-toggle:hover { - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-policy-ep-advanced-toggle__chevron { @@ -2869,7 +2892,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-ep-advanced-toggle--open { - color: #76B900; + color: var(--nvidia-accent); } /* =========================================== @@ -2920,7 +2943,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-policy-search:focus { - border-color: #76B900; + border-color: var(--nvidia-accent); width: 240px; } @@ -2946,7 +2969,7 @@ body.nemoclaw-switching openclaw-app { padding: 0 6px; border-radius: var(--radius-full, 9999px); background: rgba(118, 185, 0, 0.12); - color: #76B900; + color: var(--nvidia-accent); font-size: 11px; font-weight: 700; } @@ -3085,9 +3108,15 @@ body.nemoclaw-switching openclaw-app { to { opacity: 1; transform: translateY(0); } } +/* Reserve space so content is not hidden behind the fixed bar */ +.nemoclaw-inference-page:has(.nemoclaw-policy-savebar--visible), +.nemoclaw-policy-page:has(.nemoclaw-policy-savebar--visible) { + padding-bottom: 80px; +} + .nemoclaw-policy-savebar__summary { - font-size: 13px; - font-weight: 500; + font-size: 14px; + font-weight: 600; color: var(--text, #e4e4e7); display: block; } @@ -3171,7 +3200,7 @@ body.nemoclaw-switching openclaw-app { border-radius: 6px; background: rgba(118, 185, 0, 0.08); border: 1px solid rgba(118, 185, 0, 0.2); - color: #76B900; + color: var(--nvidia-accent); white-space: nowrap; } @@ -3210,7 +3239,7 @@ body.nemoclaw-switching openclaw-app { .nc-gateway-strip__help:hover, .nc-gateway-strip__help--active { background: var(--bg-hover, #262a35); - color: #76B900; + color: var(--nvidia-accent); } .nc-gateway-strip__help svg { @@ -3258,7 +3287,7 @@ body.nemoclaw-switching openclaw-app { padding: 1px 5px; border-radius: 3px; background: rgba(118, 185, 0, 0.08); - color: #76B900; + color: var(--nvidia-accent); } .nc-gateway-tooltip__arrow { @@ -3331,7 +3360,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-inference-apikeys__link:hover { - color: #76B900; + color: var(--nvidia-accent); } .nemoclaw-inference-apikeys__form.nemoclaw-key-form { @@ -3395,7 +3424,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-inference-apikeys__add-form .nemoclaw-policy-confirm-btn--create { - background: #76B900; + background: var(--nvidia-accent); color: #0c0c0d; } @@ -3509,7 +3538,7 @@ body.nemoclaw-switching openclaw-app { } .nc-quick-picker__add-btn:hover { - color: #76B900; + color: var(--nvidia-accent); } .nc-quick-picker__add-form { @@ -3548,7 +3577,7 @@ body.nemoclaw-switching openclaw-app { } .nc-upgrade-banner__link { - color: #76B900; + color: var(--nvidia-accent); font-weight: 500; text-decoration: none; } @@ -3632,7 +3661,7 @@ body.nemoclaw-switching openclaw-app { .nc-partner-tile__add { font-size: 11px; - color: #76B900; + color: var(--nvidia-accent); font-weight: 600; } @@ -3830,7 +3859,7 @@ body.nemoclaw-switching openclaw-app { .nc-providers-section--expanded .nc-providers-section__chevron { transform: rotate(90deg); - color: #76B900; + color: var(--nvidia-accent); } .nc-providers-section__title { @@ -3868,7 +3897,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-inference-type-pill--openai { border-color: #10a37f; color: #10a37f; } .nemoclaw-inference-type-pill--anthropic { border-color: #d97706; color: #d97706; } -.nemoclaw-inference-type-pill--nvidia { border-color: #76b900; color: #76b900; } +.nemoclaw-inference-type-pill--nvidia { border-color: var(--nvidia-accent); color: var(--nvidia-accent); } .nemoclaw-inference-type-pill--generic { border-color: #6b7280; color: #6b7280; } /* --- Status Dots --- */ @@ -4157,7 +4186,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-inference-proto-row__method { font-weight: 600; - color: var(--accent, #76b900); + color: var(--accent, var(--nvidia-accent)); min-width: 40px; } .nemoclaw-inference-proto-row__path { @@ -4257,15 +4286,15 @@ body.nemoclaw-switching openclaw-app { font-weight: 600; padding: 4px 12px; border-radius: 4px; - border: 1px solid var(--accent, #76b900); + border: 1px solid var(--accent, var(--nvidia-accent)); background: transparent; - color: var(--accent, #76b900); + color: var(--accent, var(--nvidia-accent)); cursor: pointer; white-space: nowrap; transition: background 0.15s ease, color 0.15s ease; } .nemoclaw-inference-activate-btn:hover { - background: var(--accent, #76b900); + background: var(--accent, var(--nvidia-accent)); color: #fff; } .nemoclaw-inference-activate-btn--current { @@ -4341,7 +4370,7 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-inference-empty-tile:hover { - border-color: #76B900; + border-color: var(--nvidia-accent); background: rgba(118, 185, 0, 0.04); box-shadow: 0 0 0 1px rgba(118, 185, 0, 0.15); } @@ -4361,7 +4390,7 @@ body.nemoclaw-switching openclaw-app { padding: 1px 6px; border-radius: 3px; background: rgba(118, 185, 0, 0.08); - color: #76B900; + color: var(--nvidia-accent); width: fit-content; } @@ -4446,6 +4475,6 @@ body.nemoclaw-switching openclaw-app { } .nemoclaw-model-dropdown__route-link:hover { - color: #76B900; + color: var(--nvidia-accent); background: rgba(118, 185, 0, 0.06); } From b6dafc879b8438ee6fd9f8d6df14b1a67758bf7f Mon Sep 17 00:00:00 2001 From: nv-kasikritc Date: Sun, 15 Mar 2026 22:22:39 +0000 Subject: [PATCH 2/7] Hide recommendation in policy tab page when there is no recommendation --- .../nemoclaw-ui-extension/extension/policy-page.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/policy-page.ts b/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/policy-page.ts index 2fc65ec..013a4e4 100644 --- a/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/policy-page.ts +++ b/sandboxes/openclaw-nvidia/nemoclaw-ui-extension/extension/policy-page.ts @@ -428,6 +428,7 @@ function buildTabLayout(): HTMLElement { function buildRecommendationsSection(): HTMLElement { const section = document.createElement("div"); section.className = "nemoclaw-policy-recommendations"; + section.style.display = "none"; // Shown only when we have recommendations (after async load) section.innerHTML = `
${ICON_SHIELD} @@ -474,10 +475,10 @@ function buildRecommendationsSection(): HTMLElement { setCount(unique.length); if (unique.length === 0) { - list.classList.add("nemoclaw-policy-recommendations__list--empty"); - list.textContent = "No recent blocks. Denied connections will appear here."; + section.style.display = "none"; return; } + section.style.display = ""; approveAllBtn.onclick = async () => { approveAllBtn.disabled = true; From b3bd5dc47f4e649ca4128fd547f7399942bcdc3a Mon Sep 17 00:00:00 2001 From: nv-kasikritc Date: Mon, 16 Mar 2026 01:44:08 +0000 Subject: [PATCH 3/7] feat(welcome-ui): add inference provider picker (NVIDIA + partners) in install flow --- brev/launch.sh | 14 +- brev/welcome-ui/SERVER_ARCHITECTURE.md | 19 ++ .../__tests__/cluster-inference.test.js | 4 +- .../__tests__/template-render.test.js | 40 +++- brev/welcome-ui/app.js | 79 ++++++- brev/welcome-ui/index.html | 7 +- brev/welcome-ui/inference-providers.yaml | 183 +++++++++++++++ brev/welcome-ui/server.js | 208 +++++++++++++++++- brev/welcome-ui/styles.css | 155 +++++++++++++ sandboxes/openclaw-nvidia/Dockerfile | 4 +- sandboxes/openclaw-nvidia/README.md | 19 +- .../openclaw-nvidia/inference-options.js | 126 +++++++++++ .../openclaw-nvidia/ncp-logos/baseten.webp | Bin 0 -> 516 bytes .../openclaw-nvidia/ncp-logos/bitdeer.webp | Bin 0 -> 4728 bytes .../openclaw-nvidia/ncp-logos/coreweave.webp | Bin 0 -> 2900 bytes .../ncp-logos/deepinfra-color.webp | Bin 0 -> 12800 bytes .../ncp-logos/digital-ocean.webp | Bin 0 -> 2794 bytes .../openclaw-nvidia/ncp-logos/fireworks.webp | Bin 0 -> 3808 bytes .../openclaw-nvidia/ncp-logos/gmi-cloud.webp | Bin 0 -> 3478 bytes .../ncp-logos/lightning-ai-color.webp | Bin 0 -> 6742 bytes .../openclaw-nvidia/ncp-logos/nvidia.png | Bin 0 -> 65034 bytes .../openclaw-nvidia/ncp-logos/together.webp | Bin 0 -> 4988 bytes .../openclaw-nvidia/ncp-logos/vultr.webp | Bin 0 -> 2584 bytes .../extension/inference-page.ts | 18 +- .../extension/model-registry.ts | 72 ++++-- .../extension/styles.css | 42 +++- .../openclaw-nvidia/openclaw-nvidia-start.sh | 2 +- sandboxes/openclaw-nvidia/policy-proxy.js | 119 ++++++++++ 28 files changed, 1069 insertions(+), 42 deletions(-) create mode 100644 brev/welcome-ui/inference-providers.yaml create mode 100644 sandboxes/openclaw-nvidia/inference-options.js create mode 100644 sandboxes/openclaw-nvidia/ncp-logos/baseten.webp create mode 100644 sandboxes/openclaw-nvidia/ncp-logos/bitdeer.webp create mode 100644 sandboxes/openclaw-nvidia/ncp-logos/coreweave.webp create mode 100644 sandboxes/openclaw-nvidia/ncp-logos/deepinfra-color.webp create mode 100644 sandboxes/openclaw-nvidia/ncp-logos/digital-ocean.webp create mode 100644 sandboxes/openclaw-nvidia/ncp-logos/fireworks.webp create mode 100644 sandboxes/openclaw-nvidia/ncp-logos/gmi-cloud.webp create mode 100644 sandboxes/openclaw-nvidia/ncp-logos/lightning-ai-color.webp create mode 100644 sandboxes/openclaw-nvidia/ncp-logos/nvidia.png create mode 100644 sandboxes/openclaw-nvidia/ncp-logos/together.webp create mode 100644 sandboxes/openclaw-nvidia/ncp-logos/vultr.webp diff --git a/brev/launch.sh b/brev/launch.sh index d1ea0b9..6040400 100755 --- a/brev/launch.sh +++ b/brev/launch.sh @@ -566,19 +566,23 @@ } set_inference_route() { + # Set the default inference route to one model (nvidia-endpoints + kimi-k2.5). + # Canonical CLI per brev/welcome-ui/SERVER_ARCHITECTURE.md: cluster inference set + # (CLI_BIN is either "openshell" or "nemoclaw"; both accept the same subcommands.) + # Try canonical first, then "inference set" as fallback for other CLI versions. log "Configuring inference route..." - if "$CLI_BIN" inference set --provider nvidia-endpoints --model minimaxai/minimax-m2.5 --no-verify >/dev/null 2>&1; then - log "Configured inference via '$CLI_BIN inference set'." + if "$CLI_BIN" cluster inference set --provider nvidia-endpoints --model moonshotai/kimi-k2.5 --no-verify >/dev/null 2>&1; then + log "Configured inference via '$CLI_BIN cluster inference set'." return fi - if "$CLI_BIN" cluster inference set --provider nvidia-endpoints --model minimaxai/minimax-m2.5 --no-verify >/dev/null 2>&1; then - log "Configured inference via legacy '$CLI_BIN cluster inference set'." + if "$CLI_BIN" inference set --provider nvidia-endpoints --model moonshotai/kimi-k2.5 --no-verify >/dev/null 2>&1; then + log "Configured inference via '$CLI_BIN inference set'." return fi - log "Unable to configure inference route with either current or legacy CLI commands." + log "Unable to configure inference route with either 'cluster inference set' or 'inference set'." exit 1 } diff --git a/brev/welcome-ui/SERVER_ARCHITECTURE.md b/brev/welcome-ui/SERVER_ARCHITECTURE.md index fc6a762..a9543ab 100644 --- a/brev/welcome-ui/SERVER_ARCHITECTURE.md +++ b/brev/welcome-ui/SERVER_ARCHITECTURE.md @@ -126,6 +126,8 @@ The server operates in **two distinct modes** depending on sandbox readiness: | `LOG_FILE` | `/tmp/nemoclaw-sandbox-create.log` | Sandbox creation log (written by subprocess) | | `PROVIDER_CONFIG_CACHE` | `/tmp/nemoclaw-provider-config-cache.json` | Provider config values cache | | `OTHER_AGENTS_YAML` | `ROOT/other-agents.yaml` | YAML modal definition file | +| `INFERENCE_PROVIDERS_YAML` | `ROOT/inference-providers.yaml` | Inference provider picker and per-partner instructions | +| `NCP_LOGOS_DIR` | `SANDBOX_DIR/ncp-logos` | Partner and NVIDIA logos served at `/ncp-logos/*` | | `NEMOCLAW_IMAGE` | `ghcr.io/nvidia/openshell-community/sandboxes/openclaw-nvidia:local` | Optional image override | | `SANDBOX_PORT` | `18789` | Port the sandbox listens on (localhost) | @@ -845,6 +847,21 @@ steps: # Array of instruction sections 4. **Fallback:** If YAML fails to parse or PyYAML is not installed, the placeholder is replaced with an HTML comment: `` +### Inference Provider Picker (`inference-providers.yaml`) + +The server also renders `inference-providers.yaml` into HTML and injects it into `index.html`, replacing the `{{INFERENCE_PROVIDER_PICKER}}` placeholder. This provides: + +- **Picker screen:** "Choose your inference provider" with NVIDIA (free) in its own row and paid partners in a 5×2 grid. Each tile has `data-provider-id` for JS. +- **Partner instruction blocks:** For each partner, a `div#provider-instructions-{id}` with title, intro, and steps (same schema as other-agents steps). Shown when the user clicks a partner. + +**YAML schema:** Top-level `nvidia: { displayName, logoFile }` and `partners: [ { id, name, logoFile, instructions: { title, intro?, steps[] } } ]`. Logo filenames refer to files under `NCP_LOGOS_DIR`, served at `GET /ncp-logos/`. + +**Fallback:** If the file is missing or invalid, the placeholder is replaced with `` and the Install OpenClaw modal shows only the NVIDIA API key view (no picker). + +### NCP Logos Route (`GET /ncp-logos/*`) + +In welcome-ui mode (before sandbox is ready), `GET /ncp-logos/` serves static files from `SANDBOX_DIR/ncp-logos/`. Path traversal is rejected; only files under that directory are served. Used for NVIDIA and partner logos in the provider picker. MIME type for `.webp` is `image/webp`. + --- ## 9. Policy Management Pipeline @@ -1146,6 +1163,8 @@ All CLI commands are executed via `subprocess.run()` or `subprocess.Popen()`. Ev |------|------|----------| | `ROOT/index.html` | First request to `/` | Yes | | `ROOT/other-agents.yaml` | First request to `/` | No (graceful fallback) | +| `ROOT/inference-providers.yaml` | First request to `/` | No (graceful fallback) | +| `SANDBOX_DIR/ncp-logos/*` | `GET /ncp-logos/` | No (optional logos) | | `ROOT/styles.css` | Static file serving | Yes (for UI) | | `ROOT/app.js` | Static file serving | Yes (for UI) | | `SANDBOX_DIR/policy.yaml` | Sandbox creation | No (graceful fallback) | diff --git a/brev/welcome-ui/__tests__/cluster-inference.test.js b/brev/welcome-ui/__tests__/cluster-inference.test.js index cca1c44..8ae5f77 100644 --- a/brev/welcome-ui/__tests__/cluster-inference.test.js +++ b/brev/welcome-ui/__tests__/cluster-inference.test.js @@ -151,7 +151,7 @@ describe("POST /api/cluster-inference", () => { expect(res.status).toBe(400); }); - it("TC-CI10: calls nemoclaw cluster inference set with --provider, --model, and --no-verify", async () => { + it("TC-CI10: calls CLI (openshell or nemoclaw) with cluster inference set or inference set, --provider, --model, --no-verify", async () => { execFile.mockImplementation((cmd, args, opts, cb) => { if (typeof opts === "function") { cb = opts; opts = {}; } cb(null, "", ""); @@ -162,7 +162,7 @@ describe("POST /api/cluster-inference", () => { .send({ providerName: "test-prov", modelId: "test-model" }); const setCall = execFile.mock.calls.find( - (c) => c[0] === "nemoclaw" && c[1]?.includes("inference") && c[1]?.includes("set") + (c) => (c[0] === "openshell" || c[0] === "nemoclaw") && c[1]?.includes("inference") && c[1]?.includes("set") ); expect(setCall).toBeDefined(); const args = setCall[1]; diff --git a/brev/welcome-ui/__tests__/template-render.test.js b/brev/welcome-ui/__tests__/template-render.test.js index db534d5..aa3da49 100644 --- a/brev/welcome-ui/__tests__/template-render.test.js +++ b/brev/welcome-ui/__tests__/template-render.test.js @@ -3,7 +3,13 @@ import { describe, it, expect, beforeEach } from 'vitest'; import serverModule from '../server.js'; -const { renderOtherAgentsModal, getRenderedIndex, escapeHtml, _resetForTesting } = serverModule; +const { + renderOtherAgentsModal, + renderInferenceProviderPickerAndInstructions, + getRenderedIndex, + escapeHtml, + _resetForTesting, +} = serverModule; // === TC-T01 through TC-T14: YAML-to-HTML template rendering === @@ -88,6 +94,26 @@ describe("renderOtherAgentsModal", () => { }); }); +describe("renderInferenceProviderPickerAndInstructions", () => { + it("TC-T15: picker contains NVIDIA row and heading when YAML present", () => { + const html = renderInferenceProviderPickerAndInstructions(); + if (!html) return; + expect(html).toContain("Choose your inference provider"); + expect(html).toContain('data-provider-id="nvidia"'); + expect(html).toContain("Free endpoint provider"); + }); + + it("TC-T16: picker contains partner grid and provider-instructions blocks", () => { + const html = renderInferenceProviderPickerAndInstructions(); + if (!html) return; + expect(html).toContain("provider-picker__grid"); + expect(html).toContain("install-partner-instructions"); + expect(html).toContain("partner-instructions-content"); + expect(html).toContain("provider-instructions-"); + expect(html).toContain("Back to providers"); + }); +}); + describe("getRenderedIndex", () => { beforeEach(() => { _resetForTesting(); @@ -112,4 +138,16 @@ describe("getRenderedIndex", () => { const hasComment = html.includes(""); expect(hasModal || hasComment).toBe(true); }); + + it("TC-T17: {{INFERENCE_PROVIDER_PICKER}} is replaced in index.html", () => { + const html = getRenderedIndex(); + expect(html).not.toContain("{{INFERENCE_PROVIDER_PICKER}}"); + }); + + it("TC-T18: inference picker replaced with content or fallback comment", () => { + const html = getRenderedIndex(); + const hasPicker = html.includes("install-provider-picker"); + const hasComment = html.includes(""); + expect(hasPicker || hasComment).toBe(true); + }); }); diff --git a/brev/welcome-ui/app.js b/brev/welcome-ui/app.js index 4ba3de2..0be0e92 100644 --- a/brev/welcome-ui/app.js +++ b/brev/welcome-ui/app.js @@ -14,6 +14,9 @@ // Install modal elements const installMain = $("#install-main"); + const installNvidiaKey = $("#install-nvidia-key"); + const installProviderPicker = $("#install-provider-picker"); + const installPartnerInstructions = $("#install-partner-instructions"); const stepError = $("#install-step-error"); const apiKeyInput = $("#api-key-input"); const toggleKeyVis = $("#toggle-key-vis"); @@ -233,6 +236,24 @@ stepError.hidden = true; } + // -- Install modal screens (picker / NVIDIA / partner) ----------------- + + function showInstallScreen(screen, providerId) { + if (installProviderPicker) installProviderPicker.hidden = screen !== "picker"; + if (installNvidiaKey) installNvidiaKey.hidden = screen !== "nvidia"; + if (installPartnerInstructions) { + installPartnerInstructions.hidden = screen !== "partner"; + if (screen === "partner" && providerId) { + const content = $("#partner-instructions-content"); + if (content) { + content.querySelectorAll("[id^=\"provider-instructions-\"]").forEach((el) => { + el.hidden = el.id !== "provider-instructions-" + providerId; + }); + } + } + } + } + function showError(msg) { stopPolling(); installMain.hidden = true; @@ -387,6 +408,7 @@ updateButtonState(); showOverlay(overlayInstall); + showInstallScreen("nvidia"); if (!keyInjected) { startPolling(); } @@ -398,6 +420,7 @@ updateButtonState(); showOverlay(overlayInstall); + showInstallScreen("nvidia"); startPolling(); } } catch { @@ -425,13 +448,19 @@ showOverlay(overlayInstall); if (installFailed) { stepError.hidden = false; - installMain.hidden = true; + if (installProviderPicker) installProviderPicker.hidden = true; + if (installNvidiaKey) installNvidiaKey.hidden = true; + if (installPartnerInstructions) installPartnerInstructions.hidden = true; } else { - showMainView(); + if (installProviderPicker) { + showInstallScreen("picker"); + } else { + showInstallScreen("nvidia"); + apiKeyInput.focus(); + if (!installTriggered) triggerInstall(); + } } - apiKeyInput.focus(); updateButtonState(); - if (!installTriggered && !installFailed) triggerInstall(); }); cardOther.addEventListener("click", () => { @@ -442,6 +471,48 @@ closeInstall.addEventListener("click", () => hideOverlay(overlayInstall)); closeInstr.addEventListener("click", () => hideOverlay(overlayInstr)); + const backFromNvidia = $("#back-from-nvidia"); + if (backFromNvidia) { + backFromNvidia.addEventListener("click", () => showInstallScreen("picker")); + } + document.addEventListener("click", (e) => { + const backPartner = e.target.closest("#back-from-partner"); + if (backPartner) showInstallScreen("picker"); + }); + + const installBody = $("#install-body"); + if (installBody) { + installBody.addEventListener("click", (e) => { + const tile = e.target.closest("[data-provider-id]"); + if (!tile) return; + const id = tile.getAttribute("data-provider-id"); + if (!id) return; + e.preventDefault(); + if (id === "nvidia") { + showInstallScreen("nvidia"); + apiKeyInput.focus(); + if (!installTriggered && !installFailed) triggerInstall(); + } else { + showInstallScreen("partner", id); + } + }); + installBody.addEventListener("keydown", (e) => { + if (e.key !== "Enter" && e.key !== " ") return; + const tile = e.target.closest("[data-provider-id]"); + if (!tile) return; + const id = tile.getAttribute("data-provider-id"); + if (!id) return; + e.preventDefault(); + if (id === "nvidia") { + showInstallScreen("nvidia"); + apiKeyInput.focus(); + if (!installTriggered && !installFailed) triggerInstall(); + } else { + showInstallScreen("partner", id); + } + }); + } + closeOnBackdrop(overlayInstall); closeOnBackdrop(overlayInstr); diff --git a/brev/welcome-ui/index.html b/brev/welcome-ui/index.html index 4d95a34..6aafd6f 100644 --- a/brev/welcome-ui/index.html +++ b/brev/welcome-ui/index.html @@ -82,7 +82,11 @@