diff --git a/core/http/react-ui/index.html b/core/http/react-ui/index.html index 727105148..ee1a144cf 100644 --- a/core/http/react-ui/index.html +++ b/core/http/react-ui/index.html @@ -7,7 +7,7 @@ - +
diff --git a/core/http/react-ui/src/App.css b/core/http/react-ui/src/App.css index debb7bca7..116574ecb 100644 --- a/core/http/react-ui/src/App.css +++ b/core/http/react-ui/src/App.css @@ -143,15 +143,15 @@ width: var(--sidebar-width); height: 100vh; height: 100dvh; - background: var(--color-bg-primary); + background: var(--color-bg-secondary); border-right: 1px solid var(--color-border-subtle); display: flex; flex-direction: column; z-index: 50; overflow-y: auto; box-shadow: var(--shadow-sidebar); - transition: width var(--duration-normal) var(--ease-default), - transform var(--duration-normal) var(--ease-default); + transition: width var(--duration-normal) var(--ease-spring), + transform var(--duration-normal) var(--ease-spring); will-change: transform; } @@ -250,30 +250,32 @@ } .nav-item { + position: relative; display: flex; align-items: center; gap: var(--spacing-sm); - padding: 8px var(--spacing-md) 8px var(--spacing-sm); + padding: 8px var(--spacing-md) 8px calc(var(--spacing-sm) + 2px); color: var(--color-text-secondary); text-decoration: none; font-size: var(--text-sm); font-weight: var(--font-weight-medium); - transition: color var(--duration-fast), background var(--duration-fast), border-color var(--duration-fast); - border-left: 3px solid transparent; + transition: color var(--duration-normal) var(--ease-spring), + background var(--duration-normal) var(--ease-spring), + box-shadow var(--duration-normal) var(--ease-spring); white-space: nowrap; overflow: hidden; } .nav-item:hover:not(.active) { color: var(--color-text-primary); - background: var(--color-surface-hover); + background: var(--color-surface-elevated); } .nav-item.active { color: var(--color-primary); background: var(--color-primary-light); - border-left-color: var(--color-primary); - font-weight: var(--font-weight-semibold); + box-shadow: inset 2px 0 0 var(--color-primary); + font-weight: var(--font-weight-medium); } .nav-icon { @@ -492,6 +494,13 @@ padding: var(--spacing-xs) var(--spacing-md); } +.operation-text { + font-family: var(--font-mono); +} +.operation-progress { + font-variant-numeric: tabular-nums; +} + .operation-item { display: flex; align-items: center; @@ -540,7 +549,7 @@ .operation-bar-container { flex: 0 1 160px; min-width: 80px; - height: 4px; + height: 3px; background: var(--color-surface-sunken); border-radius: var(--radius-full); overflow: hidden; @@ -550,7 +559,8 @@ height: 100%; background: var(--color-primary); border-radius: var(--radius-full); - transition: width 300ms var(--ease-default); + transition: width var(--duration-slow) var(--ease-spring); + animation: opsBarBreathe 1.4s ease-in-out infinite; } /* Inline install indicator — used in table rows (Models, Backends) */ @@ -602,43 +612,45 @@ align-items: center; gap: var(--spacing-sm); padding: var(--spacing-sm) var(--spacing-md); + background: var(--color-bg-secondary); + border: 1px solid var(--color-border-subtle); border-radius: var(--radius-lg); - box-shadow: var(--shadow-lg); - font-size: 0.875rem; - animation: slideIn 200ms ease-out; + box-shadow: var(--shadow-sm); + color: var(--color-text-primary); + font-family: var(--font-mono); + font-size: 0.75rem; + letter-spacing: -0.005em; + animation: toastSlideIn var(--duration-normal) var(--ease-spring); min-width: 280px; } .toast-enter { opacity: 0; - transform: translateX(20px); + transform: translateX(12px); } .toast-exit { opacity: 0; - transform: translateX(20px); - transition: opacity 150ms ease, transform 150ms ease; + transform: translateX(12px); + transition: opacity var(--duration-fast) var(--ease-spring), + transform var(--duration-fast) var(--ease-spring); } .toast-success { - background: var(--color-success-light); - border: 1px solid var(--color-success-border); - color: var(--color-success); + box-shadow: inset 3px 0 0 var(--color-success), var(--shadow-sm); + border-color: var(--color-border-subtle); } .toast-error { - background: var(--color-error-light); - border: 1px solid var(--color-error-border); - color: var(--color-error); + box-shadow: inset 3px 0 0 var(--color-error), var(--shadow-sm); + border-color: var(--color-border-subtle); } .toast-warning { - background: var(--color-warning-light); - border: 1px solid var(--color-warning-border); - color: var(--color-warning); + box-shadow: inset 3px 0 0 var(--color-warning), var(--shadow-sm); + border-color: var(--color-border-subtle); } .toast-info { - background: var(--color-info-light); - border: 1px solid var(--color-info-border); - color: var(--color-info); + box-shadow: inset 3px 0 0 var(--color-info), var(--shadow-sm); + border-color: var(--color-border-subtle); } .toast-close { @@ -845,20 +857,30 @@ /* Cards */ .card { - background: var(--color-surface-raised); + background: var(--color-bg-secondary); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-lg); padding: var(--spacing-lg); box-shadow: var(--shadow-subtle), var(--shadow-inset-top); - transition: border-color var(--duration-fast), box-shadow var(--duration-fast), transform var(--duration-fast); + transition: border-color var(--duration-normal) var(--ease-spring), + box-shadow var(--duration-normal) var(--ease-spring), + transform var(--duration-normal) var(--ease-spring); } .card:hover { - border-color: var(--color-border-default); - box-shadow: var(--shadow-md), var(--shadow-inset-top); + border-color: var(--color-border-strong); + box-shadow: var(--shadow-sm), var(--shadow-inset-top); transform: translateY(-1px); } +/* Accent-rail variant — editorial left bar for highlighted cards */ +.card--accent { + box-shadow: inset 2px 0 0 var(--color-primary), var(--shadow-subtle), var(--shadow-inset-top); +} +.card--accent:hover { + box-shadow: inset 2px 0 0 var(--color-primary), var(--shadow-sm), var(--shadow-inset-top); +} + .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); @@ -1008,7 +1030,7 @@ border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); padding: var(--spacing-sm) var(--spacing-md); - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); font-size: var(--text-xs); line-height: var(--leading-snug); color: var(--color-text-secondary); @@ -1283,50 +1305,59 @@ justify-content: center; gap: var(--spacing-xs); padding: 0.5rem var(--spacing-md); - min-height: 36px; + min-height: 34px; border-radius: var(--radius-md); font-size: var(--text-sm); font-family: inherit; font-weight: var(--font-weight-medium); - letter-spacing: 0.005em; + letter-spacing: -0.005em; cursor: pointer; border: 1px solid transparent; - transition: background var(--duration-fast) var(--ease-default), color var(--duration-fast) var(--ease-default), border-color var(--duration-fast) var(--ease-default), box-shadow var(--duration-fast) var(--ease-default), transform var(--duration-fast) var(--ease-default); + transition: background var(--duration-normal) var(--ease-spring), + color var(--duration-normal) var(--ease-spring), + border-color var(--duration-normal) var(--ease-spring), + box-shadow var(--duration-normal) var(--ease-spring), + filter var(--duration-normal) var(--ease-spring), + transform var(--duration-normal) var(--ease-spring); text-decoration: none; white-space: nowrap; } .btn:focus-visible { outline: none; - box-shadow: 0 0 0 3px var(--color-border-focus); + box-shadow: 0 0 0 3px var(--color-focus-ring); } /* Global focus ring — any interactive that isn't a .btn */ :where(a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])):focus-visible { - outline: 2px solid var(--color-border-focus); - outline-offset: 2px; + outline: none; + box-shadow: 0 0 0 3px var(--color-focus-ring); border-radius: var(--radius-sm); } .btn-primary { background: var(--color-primary); color: var(--color-primary-text); - box-shadow: var(--shadow-subtle); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25), var(--shadow-inset-hi); } .btn-primary:hover:not(:disabled) { - background: var(--color-primary-hover); - box-shadow: var(--shadow-sm); + filter: brightness(1.06); + transform: translateY(-1px); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3), var(--shadow-inset-hi); +} +.btn-primary:focus-visible:not(:disabled) { + box-shadow: 0 0 0 3px var(--color-focus-ring), var(--shadow-inset-hi); } .btn-secondary { - background: var(--color-surface-raised); + background: var(--color-surface-elevated); color: var(--color-text-primary); border-color: var(--color-border-default); } .btn-secondary:hover:not(:disabled) { border-color: var(--color-border-strong); - background: var(--color-primary-light); - color: var(--color-primary); + background: var(--color-surface-hover); + transform: translateY(-1px); } .btn-ghost { @@ -1335,7 +1366,7 @@ border-color: transparent; } .btn-ghost:hover:not(:disabled) { - background: var(--color-surface-hover); + background: var(--color-surface-elevated); color: var(--color-text-primary); } @@ -1348,21 +1379,27 @@ background: var(--color-error); color: var(--color-text-inverse); border-color: var(--color-error); + filter: brightness(1.04); + transform: translateY(-1px); } .btn-sm { padding: 0.35rem var(--spacing-sm); - min-height: 30px; + min-height: 28px; font-size: var(--text-xs); + letter-spacing: 0; } .btn:active:not(:disabled) { - transform: translateY(1px); + filter: brightness(0.95); + transform: translateY(0); } .btn:disabled { - opacity: 0.5; + opacity: 0.45; cursor: not-allowed; + filter: none; + transform: none; } /* Toggle switch */ @@ -1415,68 +1452,84 @@ opacity: 0.5; } -/* Inputs */ +/* Inputs — sunken well, quiet border, sage focus ring */ .input { - background: var(--color-surface-raised); + background: var(--color-surface-sunken); color: var(--color-text-primary); border: 1px solid var(--color-border-default); border-radius: var(--radius-md); padding: var(--spacing-sm) var(--spacing-md); font-size: var(--text-sm); font-family: inherit; + letter-spacing: -0.005em; outline: none; width: 100%; - transition: border-color var(--duration-fast), box-shadow var(--duration-fast), background var(--duration-fast); + transition: border-color var(--duration-normal) var(--ease-spring), + box-shadow var(--duration-normal) var(--ease-spring), + background var(--duration-normal) var(--ease-spring); } .input::placeholder { color: var(--color-text-muted); + opacity: 0.8; } .input:hover:not(:disabled):not(:focus) { border-color: var(--color-border-strong); } .input:focus { border-color: var(--color-primary); - box-shadow: 0 0 0 3px var(--color-primary-light); + box-shadow: 0 0 0 3px var(--color-focus-ring); + background: var(--color-surface-sunken); } .input:disabled { background: var(--color-surface-sunken); color: var(--color-text-muted); cursor: not-allowed; + opacity: 0.7; } select.input { cursor: pointer; padding-right: var(--spacing-xl); } +.input-mono { + font-family: var(--font-mono); + font-size: var(--text-sm); + letter-spacing: -0.01em; +} + .textarea { - background: var(--color-surface-raised); + background: var(--color-surface-sunken); color: var(--color-text-primary); border: 1px solid var(--color-border-default); border-radius: var(--radius-md); padding: var(--spacing-sm) var(--spacing-md); font-size: var(--text-sm); font-family: inherit; + letter-spacing: -0.005em; outline: none; width: 100%; resize: vertical; min-height: 80px; line-height: var(--leading-normal); - transition: border-color var(--duration-fast), box-shadow var(--duration-fast); + transition: border-color var(--duration-normal) var(--ease-spring), + box-shadow var(--duration-normal) var(--ease-spring); } .textarea::placeholder { color: var(--color-text-muted); + opacity: 0.8; } .textarea:hover:not(:disabled):not(:focus) { border-color: var(--color-border-strong); } .textarea:focus { border-color: var(--color-primary); - box-shadow: 0 0 0 3px var(--color-primary-light); + box-shadow: 0 0 0 3px var(--color-focus-ring); } .textarea:disabled { background: var(--color-surface-sunken); color: var(--color-text-muted); cursor: not-allowed; + opacity: 0.7; } /* CodeMirror editor wrapper */ @@ -1502,15 +1555,20 @@ select.input { font-weight: 500; } -/* Badges */ +/* Badges — sharp editorial rectangles, mono caps */ .badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; - border-radius: var(--radius-full); - font-size: 0.6875rem; + border-radius: var(--radius-sm); + font-family: var(--font-mono); + font-size: 0.625rem; font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + line-height: 1.4; + font-variant-numeric: tabular-nums; } .badge-success { @@ -1553,7 +1611,7 @@ select.input { } .cell-mono { - font-family: 'JetBrains Mono', ui-monospace, monospace; + font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-primary); } @@ -1679,7 +1737,7 @@ select.input { .stat-card__value { font-size: var(--text-2xl); font-weight: 600; - font-family: 'JetBrains Mono', ui-monospace, monospace; + font-family: var(--font-mono); line-height: 1; color: var(--color-text-primary); word-break: break-word; @@ -2123,7 +2181,7 @@ select.input { } .model-item-name { - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); font-size: 0.8rem; color: var(--color-text-primary); overflow: hidden; @@ -2366,28 +2424,66 @@ select.input { } /* Empty state */ +/* Empty state — editorial: eyebrow rule + large mono headline + lede. + Existing pages pass `icon`, `title`, `text` children into .empty-state as + nested elements; the rules below re-style each without JSX changes. */ .empty-state { - text-align: center; - padding: var(--spacing-3xl, 4rem) var(--spacing-xl); - animation: fadeIn var(--duration-normal) var(--ease-default); + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--spacing-md); + text-align: left; + padding: var(--spacing-3xl) var(--spacing-xl); + max-width: 640px; + margin: 0 auto; + animation: fadeIn var(--duration-slow) var(--ease-spring); } +/* Center the column within its parent without shrinking type. */ +.empty-state > * { max-width: 100%; } + .empty-state-icon { - font-size: 3rem; - color: var(--color-text-muted); - margin-bottom: var(--spacing-md); + font-size: 1.5rem; + color: var(--color-primary); + opacity: 0.7; + margin: 0; } .empty-state-title { - font-size: 1.25rem; - font-weight: 600; - margin-bottom: var(--spacing-sm); + font-family: var(--font-mono); + font-size: clamp(1.5rem, 3vw, var(--text-3xl)); + font-weight: var(--font-weight-regular); + letter-spacing: -0.03em; + color: var(--color-text-primary); + margin: 0; + line-height: 1.15; } .empty-state-text { color: var(--color-text-secondary); - font-size: 0.875rem; - margin-bottom: var(--spacing-lg); + font-size: var(--text-base); + line-height: var(--leading-normal); + max-width: 52ch; + margin: 0; +} + +/* Opt-in editorial sub-parts — pages can adopt these class names over time */ +.empty-state__eyebrow { + display: inline-flex; + align-items: center; + gap: var(--spacing-sm); + font-family: var(--font-mono); + font-size: 0.625rem; + letter-spacing: 0.24em; + text-transform: uppercase; + color: var(--color-text-muted); +} +.empty-state__eyebrow::before { + content: ""; + display: block; + width: 24px; + height: 1px; + background: var(--color-border-strong); } /* Animations */ @@ -2396,18 +2492,43 @@ select.input { } @keyframes slideIn { - from { opacity: 0; transform: translateX(20px); } - to { opacity: 1; transform: translateX(0); } + from { opacity: 0; transform: translateX(12px); } + to { opacity: 1; transform: translateX(0); } +} + +@keyframes toastSlideIn { + from { opacity: 0; transform: translateX(12px); } + to { opacity: 1; transform: translateX(0); } } @keyframes fadeIn { from { opacity: 0; } - to { opacity: 1; } + to { opacity: 1; } +} + +@keyframes popIn { + from { opacity: 0; transform: scale(0.96) translateY(4px); } + to { opacity: 1; transform: scale(1) translateY(0); } } @keyframes pulse { 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } + 50% { opacity: 0.5; } +} + +@keyframes attentionPulse { + 0%, 100% { opacity: 0.65; } + 50% { opacity: 1; } +} + +@keyframes opsBarBreathe { + 0%, 100% { opacity: 0.85; } + 50% { opacity: 1; } +} + +@keyframes breathingRing { + 0%, 100% { box-shadow: inset 0 0 0 1.5px var(--color-primary-light); } + 50% { box-shadow: inset 0 0 0 2px var(--color-primary); } } @keyframes messageSlideIn { @@ -2635,29 +2756,34 @@ select.input { } .chat-message-model { + font-family: var(--font-mono); font-size: 0.625rem; - font-weight: 600; + font-weight: 500; color: var(--color-text-muted); text-transform: uppercase; - letter-spacing: 0.03em; + letter-spacing: 0.18em; padding-left: 2px; } .chat-message-content { - background: var(--color-bg-secondary); - border: 1px solid var(--color-border-subtle); - border-radius: 4px 16px 16px 16px; - padding: var(--spacing-md) var(--spacing-lg); - font-size: 0.875rem; - line-height: 1.6; + background: transparent; + border: none; + border-left: 2px solid var(--color-border-strong); + border-radius: 0; + padding: 2px var(--spacing-md); + font-size: var(--text-base); + line-height: var(--leading-normal); word-break: break-word; + color: var(--color-text-primary); } .chat-message-user .chat-message-content { - background: var(--color-primary); - color: var(--color-primary-text); - border-color: var(--color-primary); + background: var(--color-primary-light); + color: var(--color-text-primary); + border: 1px solid var(--color-primary-border); border-radius: 16px 4px 16px 16px; + padding: var(--spacing-md) var(--spacing-lg); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02); } .chat-message-content pre { @@ -2671,21 +2797,21 @@ select.input { } .chat-message-user .chat-message-content pre { - background: color-mix(in oklab, var(--color-primary-text) 12%, transparent); - border-color: color-mix(in oklab, var(--color-primary-text) 18%, transparent); - color: var(--color-primary-text); + background: var(--color-surface-sunken); + border-color: var(--color-border-subtle); + color: var(--color-text-primary); } .chat-message-user .chat-message-content code { - color: var(--color-primary-text); + color: var(--color-text-primary); } .chat-message-user .chat-message-content a { - color: var(--color-primary-text); + color: var(--color-primary); text-decoration: underline; text-underline-offset: 2px; } .chat-message-content code { - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); font-size: 0.8125rem; } @@ -2878,7 +3004,7 @@ select.input { font-size: 0.6875rem; color: var(--color-text-muted); padding-top: var(--spacing-xs); - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); display: flex; align-items: center; gap: 4px; @@ -3196,7 +3322,7 @@ select.input { overflow-y: auto; } .chat-activity-item-code code { - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); font-size: 0.65rem; } .chat-activity-params { @@ -3456,7 +3582,7 @@ select.input { } .chat-model-info-row > span:last-child { color: var(--color-text-primary); - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); font-size: 0.75rem; max-width: 60%; overflow: hidden; @@ -4387,17 +4513,17 @@ select.input { justify-content: center; background: var(--color-modal-backdrop); backdrop-filter: blur(4px); - animation: fadeIn 150ms ease; + animation: fadeIn var(--duration-normal) var(--ease-spring); } .confirm-dialog { background: var(--color-bg-secondary); - border: 1px solid var(--color-border-subtle); + border: 1px solid var(--color-border-strong); border-radius: var(--radius-lg); max-width: 420px; width: 90%; padding: var(--spacing-lg); - box-shadow: var(--shadow-lg); - animation: slideUp 150ms ease; + box-shadow: var(--shadow-md); + animation: popIn var(--duration-slow) var(--ease-spring); will-change: transform, opacity; } @keyframes slideUp { @@ -4430,13 +4556,15 @@ select.input { justify-content: flex-end; gap: var(--spacing-sm); } -.btn-danger { +.confirm-dialog-actions .btn-danger { background: var(--color-error); - color: white; - border: none; + color: var(--color-text-inverse); + border: 1px solid var(--color-error); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), var(--shadow-inset-hi); } -.btn-danger:hover { - background: var(--color-error-hover, #dc2626); +.confirm-dialog-actions .btn-danger:hover:not(:disabled) { + filter: brightness(1.06); + transform: translateY(-1px); } /* Home page */ @@ -4487,7 +4615,7 @@ select.input { } .home-resource-pct { margin-left: auto; - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); font-weight: var(--font-weight-medium); font-size: var(--text-xs); } @@ -4721,7 +4849,7 @@ select.input { border: 1px solid var(--color-border-divider); border-radius: var(--radius-full); font-size: var(--text-xs); - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); } .home-loaded-item button { background: none; diff --git a/core/http/react-ui/src/components/ClientMCPDropdown.jsx b/core/http/react-ui/src/components/ClientMCPDropdown.jsx index 493656997..7be97ba9d 100644 --- a/core/http/react-ui/src/components/ClientMCPDropdown.jsx +++ b/core/http/react-ui/src/components/ClientMCPDropdown.jsx @@ -80,7 +80,7 @@ export default function ClientMCPDropdown({ placeholder="Server URL (e.g. https://mcp.example.com/sse)" value={url} onChange={e => setUrl(e.target.value)} - style={{ width: '100%', marginBottom: '4px' }} + style={{ width: '100%', marginBottom: 'var(--spacing-xs)' }} /> setName(e.target.value)} - style={{ width: '100%', marginBottom: '4px' }} + style={{ width: '100%', marginBottom: 'var(--spacing-xs)' }} /> setAuthToken(e.target.value)} - style={{ width: '100%', marginBottom: '4px' }} + style={{ width: '100%', marginBottom: 'var(--spacing-xs)' }} /> -
+
diff --git a/core/http/react-ui/src/components/ConfigFieldRenderer.jsx b/core/http/react-ui/src/components/ConfigFieldRenderer.jsx index 29bb3b190..f2c80885d 100644 --- a/core/http/react-ui/src/components/ConfigFieldRenderer.jsx +++ b/core/http/react-ui/src/components/ConfigFieldRenderer.jsx @@ -135,7 +135,7 @@ function JsonEditor({ value, onChange }) { className="input" value={text} onChange={e => handleChange(e.target.value)} - style={{ width: '100%', minHeight: 80, fontFamily: 'monospace', fontSize: '0.8125rem', resize: 'vertical' }} + style={{ width: '100%', minHeight: 80, fontFamily: 'var(--font-mono)', fontSize: '0.8125rem', resize: 'vertical' }} /> {parseError &&
{parseError}
}
diff --git a/core/http/react-ui/src/components/FieldBrowser.jsx b/core/http/react-ui/src/components/FieldBrowser.jsx index 882482f54..57c6cf1d2 100644 --- a/core/http/react-ui/src/components/FieldBrowser.jsx +++ b/core/http/react-ui/src/components/FieldBrowser.jsx @@ -158,7 +158,7 @@ export default function FieldBrowser({ fields, activeFieldPaths, onAddField }) { {field.description} )} -
+
{field.path}
diff --git a/core/http/react-ui/src/components/ResourceMonitor.jsx b/core/http/react-ui/src/components/ResourceMonitor.jsx index b30d97de2..c4555af1d 100644 --- a/core/http/react-ui/src/components/ResourceMonitor.jsx +++ b/core/http/react-ui/src/components/ResourceMonitor.jsx @@ -51,7 +51,7 @@ export default function ResourceMonitor() {
- + {pct.toFixed(0)}%
@@ -76,7 +76,7 @@ export default function ResourceMonitor() {
- + {(ram.usage_percent || 0).toFixed(0)}%
@@ -91,7 +91,7 @@ export default function ResourceMonitor() { {isGpu && aggregate.gpu_count > 1 && (
Total VRAM - + {formatBytes(aggregate.used_memory)} / {formatBytes(aggregate.total_memory)} ({aggregate.usage_percent?.toFixed(1)}%)
@@ -101,7 +101,7 @@ export default function ResourceMonitor() { {resources.storage_size != null && (
Models storage - + {formatBytes(resources.storage_size)}
diff --git a/core/http/react-ui/src/components/SearchableSelect.jsx b/core/http/react-ui/src/components/SearchableSelect.jsx index fe391db5f..2ad9a0295 100644 --- a/core/http/react-ui/src/components/SearchableSelect.jsx +++ b/core/http/react-ui/src/components/SearchableSelect.jsx @@ -116,7 +116,7 @@ export default function SearchableSelect({ aria-expanded={open} onClick={() => { if (!disabled) { setOpen(!open); setQuery(''); setFocusIndex(-1) } }} style={{ - width: '100%', padding: '4px 8px', fontSize: '0.8125rem', + width: '100%', padding: 'var(--spacing-xs) var(--spacing-sm)', fontSize: '0.8125rem', cursor: disabled ? 'not-allowed' : 'pointer', display: 'flex', alignItems: 'center', gap: '6px', background: 'var(--color-bg-primary)', border: '1px solid var(--color-border)', @@ -145,7 +145,7 @@ export default function SearchableSelect({ value={query} onChange={(e) => { setQuery(e.target.value); setFocusIndex(-1) }} onKeyDown={handleKeyDown} - style={{ width: '100%', padding: '4px 8px', fontSize: '0.8125rem' }} + style={{ width: '100%', padding: 'var(--spacing-xs) var(--spacing-sm)', fontSize: '0.8125rem' }} />
diff --git a/core/http/react-ui/src/components/TemplateSelector.jsx b/core/http/react-ui/src/components/TemplateSelector.jsx index d0ae043b0..799508dfa 100644 --- a/core/http/react-ui/src/components/TemplateSelector.jsx +++ b/core/http/react-ui/src/components/TemplateSelector.jsx @@ -24,7 +24,7 @@ export default function TemplateSelector({ onSelect }) {

{t.description}

-
+
{Object.keys(t.fields).filter(k => k !== 'name').map(k => ( setUrl(e.target.value)} - style={{ width: '100%', marginBottom: '4px' }} + style={{ width: '100%', marginBottom: 'var(--spacing-xs)' }} /> setName(e.target.value)} - style={{ width: '100%', marginBottom: '4px' }} + style={{ width: '100%', marginBottom: 'var(--spacing-xs)' }} /> setAuthToken(e.target.value)} - style={{ width: '100%', marginBottom: '4px' }} + style={{ width: '100%', marginBottom: 'var(--spacing-xs)' }} /> -
+
diff --git a/core/http/react-ui/src/index.css b/core/http/react-ui/src/index.css index 94d590f29..ef1e92521 100644 --- a/core/http/react-ui/src/index.css +++ b/core/http/react-ui/src/index.css @@ -12,14 +12,17 @@ html { } body { - font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, sans-serif; + font-family: var(--font-sans); font-size: var(--text-base); font-weight: var(--font-weight-regular); line-height: var(--leading-normal); + font-feature-settings: "ss01", "ss03", "cv11"; + letter-spacing: -0.005em; min-height: 100%; background-color: var(--color-bg-primary); color: var(--color-text-primary); - transition: background-color 200ms ease, color 200ms ease; + transition: background-color var(--duration-normal) var(--ease-default), + color var(--duration-normal) var(--ease-default); } #root { @@ -27,36 +30,73 @@ body { min-height: 100dvh; } -/* Scrollbar */ -::-webkit-scrollbar { width: 6px; height: 6px; } -::-webkit-scrollbar-track { background: var(--color-bg-primary); } -::-webkit-scrollbar-thumb { background: var(--color-bg-secondary); border-radius: 3px; } -::-webkit-scrollbar-thumb:hover { background: var(--color-primary); } -* { scrollbar-width: thin; scrollbar-color: var(--color-bg-secondary) var(--color-bg-primary); } +/* Global selection + focus */ +::selection { + background: var(--color-primary-light); + color: var(--color-text-primary); +} -/* Typography */ +/* Scrollbar — slightly wider, warmer thumb */ +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { + background: var(--color-border-default); + border-radius: var(--radius-sm); + border: 2px solid var(--color-bg-primary); +} +::-webkit-scrollbar-thumb:hover { background: var(--color-border-strong); } +* { scrollbar-width: thin; scrollbar-color: var(--color-border-default) transparent; } + +/* Typography — editorial hierarchy */ h1, h2, h3, h4, h5, h6 { - font-family: 'Space Grotesk', sans-serif; + font-family: var(--font-sans); color: var(--color-text-primary); line-height: var(--leading-tight); - letter-spacing: -0.01em; } -h1 { font-size: var(--text-2xl); font-weight: var(--font-weight-semibold); } -h2 { font-size: var(--text-xl); font-weight: var(--font-weight-semibold); } -h3 { font-size: var(--text-lg); font-weight: var(--font-weight-semibold); } -h4 { font-size: var(--text-base); font-weight: var(--font-weight-semibold); } -h5, h6 { font-size: var(--text-sm); font-weight: var(--font-weight-semibold); } +h1 { font-size: var(--text-3xl); font-weight: var(--font-weight-medium); letter-spacing: -0.02em; } +h2 { font-size: var(--text-2xl); font-weight: var(--font-weight-medium); letter-spacing: -0.015em; } +h3 { font-size: var(--text-xl); font-weight: var(--font-weight-medium); letter-spacing: -0.01em; } +h4 { font-size: var(--text-lg); font-weight: var(--font-weight-medium); letter-spacing: -0.005em; } +h5 { font-size: var(--text-base);font-weight: var(--font-weight-semibold); } +h6 { + font-size: var(--text-xs); font-weight: var(--font-weight-semibold); + text-transform: uppercase; letter-spacing: 0.12em; + color: var(--color-text-muted); +} -code, pre { - font-family: 'JetBrains Mono', monospace; +code, pre, kbd, .mono { + font-family: var(--font-mono); +} + +kbd { + display: inline-block; + padding: 1px 5px; + font-size: 0.75em; + font-weight: var(--font-weight-medium); + background: var(--color-bg-tertiary); + border: 1px solid var(--color-border-default); + border-radius: var(--radius-sm); + color: var(--color-text-secondary); + line-height: 1.4; } a { color: var(--color-primary); text-decoration: none; + transition: color var(--duration-fast) var(--ease-default); } a:hover { color: var(--color-primary-hover); } +/* Honor prefers-reduced-motion globally */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + /* Utility classes */ diff --git a/core/http/react-ui/src/pages/AgentCreate.jsx b/core/http/react-ui/src/pages/AgentCreate.jsx index 1ab5f5a76..5ee307f4e 100644 --- a/core/http/react-ui/src/pages/AgentCreate.jsx +++ b/core/http/react-ui/src/pages/AgentCreate.jsx @@ -97,7 +97,7 @@ function FormField({ field, value, onChange, disabled }) { rows={5} disabled={disabled} style={field.name.includes('prompt') || field.name.includes('template') || field.name.includes('script') - ? { fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' } : undefined} + ? { fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' } : undefined} />
) @@ -624,7 +624,7 @@ export default function AgentCreate() { value={mcpRawJson} onChange={(e) => setMcpRawJson(e.target.value)} rows={16} - style={{ fontFamily: 'monospace', fontSize: '0.85rem', whiteSpace: 'pre' }} + style={{ fontFamily: 'var(--font-mono)', fontSize: '0.85rem', whiteSpace: 'pre' }} placeholder={'{\n "mcpServers": {\n "my-server": {\n "command": "npx",\n "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],\n "env": {}\n }\n }\n}'} />
diff --git a/core/http/react-ui/src/pages/AgentJobDetails.jsx b/core/http/react-ui/src/pages/AgentJobDetails.jsx index 0919f6d84..f8409fedf 100644 --- a/core/http/react-ui/src/pages/AgentJobDetails.jsx +++ b/core/http/react-ui/src/pages/AgentJobDetails.jsx @@ -43,7 +43,7 @@ function TraceCard({ trace, index }) { {trace.type || 'unknown'} {trace.tool_name && ( - + {trace.tool_name} )} @@ -60,7 +60,7 @@ function TraceCard({ trace, index }) { {trace.content && (
               {trace.content}
@@ -71,7 +71,7 @@ function TraceCard({ trace, index }) {
               Arguments:
               
                 {typeof trace.arguments === 'string' ? trace.arguments : JSON.stringify(trace.arguments, null, 2)}
@@ -207,7 +207,7 @@ export default function AgentJobDetails() {
         
Job ID -

{job.id}

+

{job.id}

Task @@ -264,7 +264,7 @@ export default function AgentJobDetails() {
{Object.entries(job.cron_parameters).map(([k, v]) => ( - + {k}={v} ))} @@ -281,7 +281,7 @@ export default function AgentJobDetails() {
{Object.entries(job.parameters).map(([k, v]) => ( - + {k}={v} ))} diff --git a/core/http/react-ui/src/pages/AgentJobs.jsx b/core/http/react-ui/src/pages/AgentJobs.jsx index 927bcf9ab..807627c5c 100644 --- a/core/http/react-ui/src/pages/AgentJobs.jsx +++ b/core/http/react-ui/src/pages/AgentJobs.jsx @@ -253,7 +253,7 @@ export default function AgentJobs() {

Example MCP configuration (YAML):

-
{`mcp:
+            
{`mcp:
   stdio:
     - name: my-tool
       command: /path/to/tool
@@ -345,7 +345,7 @@ export default function AgentJobs() {
                       
                       
                         {task.cron ? (
-                          
+                          
                             {task.cron}
                           
                         ) : '-'}
@@ -426,7 +426,7 @@ export default function AgentJobs() {
                   {filteredJobs.map(job => (
                     
                       
-                         navigate(`/app/agent-jobs/jobs/${job.id}`)} style={{ cursor: 'pointer', color: 'var(--color-primary)', fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }}>
+                         navigate(`/app/agent-jobs/jobs/${job.id}`)} style={{ cursor: 'pointer', color: 'var(--color-primary)', fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }}>
                           {job.id?.slice(0, 12)}...
                         
                       
@@ -510,7 +510,7 @@ export default function AgentJobs() {
                 
                   {(items || []).map(job => (
                     
-                      {job.id?.slice(0, 12)}...
+                      {job.id?.slice(0, 12)}...
                       {job.task_id || '-'}
                       {statusBadge(job.status)}
                       {formatDate(job.created_at)}
@@ -566,7 +566,7 @@ export default function AgentJobs() {
                   onChange={(e) => setExecuteParams(e.target.value)}
                   rows={5}
                   placeholder={`topic=AI trends\nformat=markdown`}
-                  style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }}
+                  style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }}
                 />
                 

These will be available as {'{{.parameter_name}}'} in the prompt template. @@ -590,7 +590,7 @@ export default function AgentJobs() { {executeMultimedia[type].map((item, i) => (

{item.name || item.url?.slice(0, 40)}
@@ -229,13 +229,13 @@ export default function AgentTaskDetails() {
Execute by name -
+              
 {`curl -X POST ${window.location.origin}${basePath}/api/agent/tasks/${encodeURIComponent(task.name)}/execute`}
               
Execute with multimedia -
+              
 {`curl -X POST ${window.location.origin}${basePath}/api/agent/tasks/${encodeURIComponent(task.name)}/execute \\
   -H "Content-Type: application/json" \\
   -d '{"multimedia": {"images": [{"url": "https://example.com/image.jpg"}]}}'`}
@@ -243,7 +243,7 @@ export default function AgentTaskDetails() {
             
Check job status -
+              
 {`curl ${window.location.origin}${basePath}/api/agent/jobs/`}
               
@@ -261,7 +261,7 @@ export default function AgentTaskDetails() {
{wh.method || 'POST'} - {wh.url} + {wh.url}
))} @@ -283,7 +283,7 @@ export default function AgentTaskDetails() { {jobHistory.map(job => ( - + {job.id?.slice(0, 12)}... {statusBadge(job.status)} @@ -351,7 +351,7 @@ export default function AgentTaskDetails() { onChange={(e) => updateField('prompt', e.target.value)} rows={8} placeholder={`Write a summary about {{.topic}} in {{.format}} format.`} - style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }} + style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }} />

Use {'{{.parameter_name}}'} for dynamic parameters. Parameters are provided when executing the task. @@ -376,7 +376,7 @@ export default function AgentTaskDetails() { value={task.cron} onChange={(e) => { updateField('cron', e.target.value); validateCron(e.target.value) }} placeholder="0 */6 * * *" - style={{ fontFamily: "'JetBrains Mono', monospace" }} + style={{ fontFamily: 'var(--font-mono)' }} /> {cronError &&

{cronError}

}

@@ -392,7 +392,7 @@ export default function AgentTaskDetails() { onChange={(e) => updateField('cron_parameters', e.target.value)} rows={3} placeholder={`topic=daily news\nformat=bullet points`} - style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }} + style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }} />

Default parameters used when the cron triggers the task. @@ -437,7 +437,7 @@ export default function AgentTaskDetails() {

- updateMultimediaSource(i, 'headers', e.target.value)} placeholder='{"Authorization": "Bearer ..."}' style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }} /> + updateMultimediaSource(i, 'headers', e.target.value)} placeholder='{"Authorization": "Bearer ..."}' style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }} />
)) @@ -479,7 +479,7 @@ export default function AgentTaskDetails() {
- updateWebhook(i, 'headers', e.target.value)} placeholder='{"Content-Type": "application/json"}' style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }} /> + updateWebhook(i, 'headers', e.target.value)} placeholder='{"Content-Type": "application/json"}' style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }} />
@@ -489,7 +489,7 @@ export default function AgentTaskDetails() { onChange={(e) => updateWebhook(i, 'payload_template', e.target.value)} rows={3} placeholder={`{"text": "Job {{.Status}}: {{if .Error}}Error: {{.Error}}{{else}}{{.Result}}{{end}}"}`} - style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }} + style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }} />

Available: {'{{.Job}}'} {'{{.Task}}'} {'{{.Result}}'} {'{{.Error}}'} {'{{.Status}}'} diff --git a/core/http/react-ui/src/pages/BackendLogs.jsx b/core/http/react-ui/src/pages/BackendLogs.jsx index d8798d921..614fd6cef 100644 --- a/core/http/react-ui/src/pages/BackendLogs.jsx +++ b/core/http/react-ui/src/pages/BackendLogs.jsx @@ -229,7 +229,7 @@ function BackendLogsDetail({ modelId }) { borderRadius: 'var(--radius-md)', overflow: 'auto', maxHeight: 'calc(100vh - 280px)', - fontFamily: 'JetBrains Mono, Consolas, monospace', + fontFamily: 'var(--font-mono)', fontSize: '0.75rem', lineHeight: '1.5', }} diff --git a/core/http/react-ui/src/pages/Backends.jsx b/core/http/react-ui/src/pages/Backends.jsx index 26d479ea4..ea34ffdbb 100644 --- a/core/http/react-ui/src/pages/Backends.jsx +++ b/core/http/react-ui/src/pages/Backends.jsx @@ -591,7 +591,7 @@ function BackendDetail({ backend }) { {backend.tags?.length > 0 && ( -

+
{backend.tags.map(tag => ( {tag} ))} diff --git a/core/http/react-ui/src/pages/CollectionDetails.jsx b/core/http/react-ui/src/pages/CollectionDetails.jsx index afac4c9b3..200da2af0 100644 --- a/core/http/react-ui/src/pages/CollectionDetails.jsx +++ b/core/http/react-ui/src/pages/CollectionDetails.jsx @@ -282,7 +282,7 @@ export default function CollectionDetails() { .collection-detail-modal-content { white-space: pre-wrap; word-break: break-word; - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); font-size: 0.8125rem; background: var(--color-bg-secondary); border-radius: var(--radius-md); diff --git a/core/http/react-ui/src/pages/FineTune.jsx b/core/http/react-ui/src/pages/FineTune.jsx index fa153efb1..9a58a78e7 100644 --- a/core/http/react-ui/src/pages/FineTune.jsx +++ b/core/http/react-ui/src/pages/FineTune.jsx @@ -241,7 +241,7 @@ function SingleMetricChart({ data, valueKey, label, color, formatValue, events } return (
- + {label}
- + {evalData.length >= 1 ? ( - + ) : (
@@ -319,8 +319,8 @@ function ChartsGrid({ events }) {
)} - - + +
) } @@ -483,26 +483,26 @@ function CheckpointsPanel({ job, onResume, onExportCheckpoint }) { - - - - - - + + + + + + {checkpoints.map(cp => ( - - - - - + + + + - {/* Use Cases */} diff --git a/core/http/react-ui/src/pages/ModelEditor.jsx b/core/http/react-ui/src/pages/ModelEditor.jsx index 0d0d1ccff..575bef971 100644 --- a/core/http/react-ui/src/pages/ModelEditor.jsx +++ b/core/http/react-ui/src/pages/ModelEditor.jsx @@ -443,7 +443,7 @@ export default function ModelEditor() { setTab(t) }} style={{ - padding: '8px 16px', border: 'none', + padding: 'var(--spacing-sm) var(--spacing-md)', border: 'none', cursor: blocked ? 'not-allowed' : 'pointer', background: 'transparent', fontSize: '0.875rem', fontWeight: active ? 600 : 400, @@ -525,7 +525,7 @@ export default function ModelEditor() { placeholder="my-model-name" style={{ maxWidth: 400 }} /> -

+

Use letters, numbers, hyphens, underscores, and dots only.

diff --git a/core/http/react-ui/src/pages/Models.jsx b/core/http/react-ui/src/pages/Models.jsx index 98c8e13d3..3ba7f260c 100644 --- a/core/http/react-ui/src/pages/Models.jsx +++ b/core/http/react-ui/src/pages/Models.jsx @@ -41,7 +41,7 @@ function GalleryLoader() { minHeight: '280px', gap: 'var(--spacing-lg)', }}> {/* Animated dots */} -
+
{[0, 1, 2, 3, 4].map(i => (
{fit !== null && ( - + {fit ? 'Fits' : 'May not fit'} @@ -595,7 +595,7 @@ function ModelDetail({ model, fit, expandedFiles, setExpandedFiles }) { {model.estimated_vram_display && model.estimated_vram_display !== '0 B' ? ( - + {model.estimated_vram_display} {fit !== null && ( @@ -610,7 +610,7 @@ function ModelDetail({ model, fit, expandedFiles, setExpandedFiles }) { {model.tags?.length > 0 && ( -
+
{model.tags.map(tag => ( {tag} ))} @@ -651,17 +651,17 @@ function ModelDetail({ model, fit, expandedFiles, setExpandedFiles }) {
StepEpochLossCreatedPathActionsStepEpochLossCreatedPathActions
{cp.step}{cp.epoch?.toFixed(2)}{cp.loss?.toFixed(4)}{cp.created_at} + {cp.step}{cp.epoch?.toFixed(2)}{cp.loss?.toFixed(4)}{cp.created_at} {cp.path} - @@ -1269,7 +1269,7 @@ export default function FineTune() { onChange={e => setCustomRewardCode(e.target.value)} placeholder={"return [1.0 if '' in c else 0.0 for c in completions]"} rows={4} - style={{ fontFamily: 'monospace', fontSize: '0.8125rem' }} + style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }} />
@@ -1404,7 +1404,7 @@ export default function FineTune() {
setEvalEnabled(!evalEnabled)} style={{ - width: 36, height: 20, borderRadius: 10, position: 'relative', + width: 36, height: 20, borderRadius: "var(--radius-xl)", position: 'relative', background: evalEnabled ? 'var(--color-primary)' : 'var(--color-border)', transition: 'background 0.2s', cursor: 'pointer', flexShrink: 0, }} diff --git a/core/http/react-ui/src/pages/ImportModel.jsx b/core/http/react-ui/src/pages/ImportModel.jsx index f9f5adb76..b06cec175 100644 --- a/core/http/react-ui/src/pages/ImportModel.jsx +++ b/core/http/react-ui/src/pages/ImportModel.jsx @@ -128,7 +128,7 @@ const ADVANCED_PREF_KEYS = [ 'pipeline_type', 'scheduler_type', 'enable_parameters', 'cuda', ] -const hintStyle = { marginTop: '4px', fontSize: '0.75rem', color: 'var(--color-text-muted)' } +const hintStyle = { marginTop: 'var(--spacing-xs)', fontSize: '0.75rem', color: 'var(--color-text-muted)' } // hasCustomPrefs returns true when the user has set any preference beyond // backend/name/description, added a custom key-value pref with a non-empty @@ -557,13 +557,13 @@ export default function ImportModel() { )}
-
+
-
- - - + + + {files.map((f, i) => ( - - - + + diff --git a/core/http/react-ui/src/pages/NodeBackendLogs.jsx b/core/http/react-ui/src/pages/NodeBackendLogs.jsx index e6ff4b9b0..7f4841721 100644 --- a/core/http/react-ui/src/pages/NodeBackendLogs.jsx +++ b/core/http/react-ui/src/pages/NodeBackendLogs.jsx @@ -222,7 +222,7 @@ export default function NodeBackendLogs() { borderRadius: 'var(--radius-md)', overflow: 'auto', maxHeight: 'calc(100vh - 280px)', - fontFamily: 'JetBrains Mono, Consolas, monospace', + fontFamily: 'var(--font-mono)', fontSize: '0.75rem', lineHeight: '1.5', }} diff --git a/core/http/react-ui/src/pages/Nodes.jsx b/core/http/react-ui/src/pages/Nodes.jsx index 0e0698241..5ffecda79 100644 --- a/core/http/react-ui/src/pages/Nodes.jsx +++ b/core/http/react-ui/src/pages/Nodes.jsx @@ -92,7 +92,7 @@ function CommandBlock({ command, addToast }) {
                                 {Object.entries(node.labels).slice(0, 5).map(([k, v]) => (
                                   {k}={v}
@@ -709,7 +709,7 @@ export default function Nodes() {
                       
                       
@@ -802,7 +802,7 @@ export default function Nodes() { const stCfg = modelStateConfig[m.state] || modelStateConfig.idle return ( - - {backends.map(b => ( - - - setExpandedRow(expandedRow === i ? null : i)} style={{ cursor: 'pointer' }}> - + - + diff --git a/core/http/react-ui/src/pages/Usage.jsx b/core/http/react-ui/src/pages/Usage.jsx index ea0f042c4..63c8a7b59 100644 --- a/core/http/react-ui/src/pages/Usage.jsx +++ b/core/http/react-ui/src/pages/Usage.jsx @@ -28,7 +28,7 @@ function StatCard({ icon, label, value, muted }) { {label} -
+
{muted ? '~' : ''}{formatNumber(value)}
@@ -39,12 +39,12 @@ function UsageBar({ value, max }) { const pct = max > 0 ? Math.min((value / max) * 100, 100) : 0 return (
@@ -342,13 +342,13 @@ function PredictionCards({ predictions, quotaExhaustion, period }) {
{q.items.map((item, ii) => (
- + {item.label}
- + {formatNumber(item.current)}/{formatNumber(item.max)} {item.withinLimits ? ( @@ -409,12 +409,12 @@ function UsageTimeChart({ data, predictedData, period }) {
Tokens over time
- Prompt - Completion + Prompt + Completion {predictedData && predictedData.length > 0 && ( @@ -432,7 +432,7 @@ function UsageTimeChart({ data, predictedData, period }) { return ( - + {formatYLabel(t)} @@ -537,7 +537,7 @@ function UsageTimeChart({ data, predictedData, period }) { return ( {formatBucket(d.bucket, period)} @@ -556,7 +556,7 @@ function UsageTimeChart({ data, predictedData, period }) { borderRadius: 'var(--radius-md)', padding: 'var(--spacing-xs) var(--spacing-sm)', fontSize: '0.75rem', - fontFamily: 'JetBrains Mono, monospace', + fontFamily: 'var(--font-mono)', color: 'var(--color-text-primary)', pointerEvents: 'none', zIndex: 10, @@ -592,8 +592,8 @@ function ModelDistChart({ rows }) {
Token distribution by model
- Prompt - Completion + Prompt + Completion
@@ -603,17 +603,17 @@ function ModelDistChart({ rows }) { return (
{row.model}
-
+
{formatNumber(row.total_tokens)} @@ -702,7 +702,7 @@ export default function Usage() { const quotaExhaustion = computeQuotaExhaustion(quotas, timeSeries, period) const userPredictions = isAdmin && userRows.length > 0 ? generateUserPredictions(adminUsage, userRows, period) : {} - const monoCell = { fontFamily: 'JetBrains Mono, monospace', fontSize: '0.8125rem' } + const monoCell = { fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' } return (
diff --git a/core/http/react-ui/src/pages/auth.css b/core/http/react-ui/src/pages/auth.css index cbfbd1b6c..0860decbc 100644 --- a/core/http/react-ui/src/pages/auth.css +++ b/core/http/react-ui/src/pages/auth.css @@ -172,7 +172,7 @@ .user-email { font-size: 0.8125rem; - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); } /* ─── Table cells ─── */ @@ -201,7 +201,7 @@ /* ─── Monospace / code text ─── */ .mono-text { - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); color: var(--color-text-secondary); } @@ -350,7 +350,7 @@ } .invite-link-text { - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -493,7 +493,7 @@ .apikey-details { font-size: 0.6875rem; color: var(--color-text-muted); - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); } .apikey-revoke-btn { @@ -535,7 +535,7 @@ padding: var(--spacing-xs) var(--spacing-sm); background: var(--color-bg-primary); border-radius: var(--radius-sm); - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); font-size: 0.75rem; word-break: break-all; color: var(--color-text-primary); @@ -558,19 +558,20 @@ justify-content: center; background: var(--color-modal-backdrop); backdrop-filter: blur(4px); - animation: fadeIn 150ms ease; + animation: fadeIn var(--duration-normal) var(--ease-spring); } .modal-panel { background: var(--color-bg-secondary); - border: 1px solid var(--color-border-subtle); + border: 1px solid var(--color-border-strong); border-radius: var(--radius-lg); width: 90%; max-height: 80vh; display: flex; flex-direction: column; overflow: auto; - animation: slideUp 150ms ease; + box-shadow: var(--shadow-md); + animation: popIn var(--duration-slow) var(--ease-spring); } /* ─── Model list in permissions modal ─── */ @@ -613,7 +614,7 @@ } .model-item-name { - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); font-size: 0.75rem; } @@ -748,6 +749,6 @@ font-size: 0.6875rem; color: var(--color-text-secondary); white-space: nowrap; - font-family: 'JetBrains Mono', monospace; + font-family: var(--font-mono); flex-shrink: 0; } diff --git a/core/http/react-ui/src/theme.css b/core/http/react-ui/src/theme.css index 46eb74d19..53c97ebdb 100644 --- a/core/http/react-ui/src/theme.css +++ b/core/http/react-ui/src/theme.css @@ -1,206 +1,235 @@ -/* LocalAI Theme - CSS Variables System */ +/* LocalAI Theme — Nord palette (polar night + frost + aurora). + Adapted from claudemaster's Nord preset. Variable names preserved. */ :root, [data-theme="dark"] { - --color-bg-primary: #0b0d14; - --color-bg-secondary: #14172099; - --color-bg-secondary: #161923; - --color-bg-tertiary: #232838; - --color-bg-overlay: rgba(11, 13, 20, 0.95); + /* Surfaces — deep blue-black, beyond polar night */ + --color-bg-primary: #13171f; /* page — very dark, cool */ + --color-bg-secondary: #1a1f2a; /* sidebar, headers, cards */ + --color-bg-tertiary: #242a36; /* wells / sunken rows */ + --color-bg-overlay: rgba(19, 23, 31, 0.92); + --color-bg-hover: #242a36; - --color-surface-raised: #1a1e2c; - --color-surface-sunken: #090b12; - --color-surface-hover: #242938; + --color-surface-raised: #1a1f2a; + --color-surface-sunken: #0e1117; + --color-surface-hover: #242a36; + --color-surface-elevated: #2f3644; - --color-primary: #f1f3f8; - --color-primary-hover: #ffffff; - --color-primary-active: #d8dce6; - --color-primary-text: #0a0c12; - --color-primary-light: rgba(241, 243, 248, 0.08); - --color-primary-border: rgba(241, 243, 248, 0.22); + /* Primary — frost cyan (nord8) */ + --color-primary: #88c0d0; + --color-primary-hover: #9ccbd9; + --color-primary-active: #7ab4c4; + --color-primary-text: #2e3440; + --color-primary-light: rgba(136, 192, 208, 0.14); + --color-primary-border: rgba(136, 192, 208, 0.34); - --color-secondary: #6b7487; - --color-secondary-hover: #525b6e; - --color-secondary-light: rgba(107, 116, 135, 0.12); + --color-secondary: #81a1c1; /* nord9 */ + --color-secondary-hover: #8faed0; + --color-secondary-light: rgba(129, 161, 193, 0.12); - --color-accent: #e8a87c; - --color-accent-hover: #d89668; - --color-accent-light: rgba(232, 168, 124, 0.14); + /* Accent alias — frost cyan remains the brand accent */ + --color-accent: #88c0d0; + --color-accent-hover: #9ccbd9; + --color-accent-light: rgba(136, 192, 208, 0.14); + --color-accent-border: rgba(136, 192, 208, 0.34); - --color-text-primary: #f1f3f8; - --color-text-secondary: #a8adbd; - --color-text-muted: #6c7084; - --color-text-disabled: #3e4253; - --color-text-inverse: #FFFFFF; + /* Text — snow storm scale */ + --color-text-primary: #eceff4; /* nord6 */ + --color-text-secondary: #d8dee9; /* nord4 */ + --color-text-muted: #a1acb9; + --color-text-disabled: #6e7a8c; + --color-text-inverse: #2e3440; - --color-border-subtle: rgba(255, 255, 255, 0.06); - --color-border-default: rgba(255, 255, 255, 0.12); - --color-border-strong: rgba(255, 255, 255, 0.22); - --color-border-divider: rgba(255, 255, 255, 0.05); - --color-border-primary: rgba(255, 255, 255, 0.22); - --color-border-focus: rgba(241, 243, 248, 0.38); + /* Borders — cool blue-gray */ + --color-border-subtle: rgba(216, 222, 233, 0.06); + --color-border-default: rgba(216, 222, 233, 0.12); + --color-border-strong: rgba(216, 222, 233, 0.24); + --color-border-divider: rgba(216, 222, 233, 0.05); + --color-border-primary: rgba(136, 192, 208, 0.45); + --color-border-focus: rgba(136, 192, 208, 0.45); - --color-success: #22C55E; - --color-success-light: rgba(34, 197, 94, 0.14); - --color-success-border: rgba(34, 197, 94, 0.32); - --color-warning: #F59E0B; - --color-warning-light: rgba(245, 158, 11, 0.14); - --color-warning-border: rgba(245, 158, 11, 0.32); - --color-error: #EF4444; - --color-error-light: rgba(239, 68, 68, 0.14); - --color-error-border: rgba(239, 68, 68, 0.32); - --color-info: #7dc7d1; - --color-info-light: rgba(125, 199, 209, 0.14); - --color-info-border: rgba(125, 199, 209, 0.32); - --color-accent-border: rgba(232, 168, 124, 0.32); - --color-modal-backdrop: rgba(0, 0, 0, 0.7); + /* Status — aurora */ + --color-success: #a3be8c; /* nord14 */ + --color-success-light: rgba(163, 190, 140, 0.14); + --color-success-border: rgba(163, 190, 140, 0.32); + --color-warning: #ebcb8b; /* nord13 */ + --color-warning-light: rgba(235, 203, 139, 0.14); + --color-warning-border: rgba(235, 203, 139, 0.32); + --color-error: #bf616a; /* nord11 */ + --color-error-light: rgba(191, 97, 106, 0.14); + --color-error-border: rgba(191, 97, 106, 0.32); + --color-info: #81a1c1; /* nord9 */ + --color-info-light: rgba(129, 161, 193, 0.14); + --color-info-border: rgba(129, 161, 193, 0.32); - /* Data viz palette — distinct hues, readable on dark */ - --color-data-1: #7dc7d1; - --color-data-2: #f87171; - --color-data-3: #c084fc; - --color-data-4: #fbbf24; - --color-data-5: #34d399; - --color-data-6: #fb923c; - --color-data-7: #f472b6; - --color-data-8: #22d3ee; + --color-modal-backdrop: rgba(8, 11, 17, 0.68); - /* Log streams */ - --color-log-stdout: #d1d5db; - --color-log-stderr: #fca5a5; - --color-log-info: #93c5fd; - --color-log-warn: #fcd34d; + --color-focus-ring: rgba(136, 192, 208, 0.34); - --shadow-subtle: 0 1px 2px rgba(0, 0, 0, 0.4); - --shadow-sm: 0 2px 6px rgba(0, 0, 0, 0.45); - --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.5); - --shadow-lg: 0 16px 32px rgba(0, 0, 0, 0.55); + /* Data viz — full aurora + frost palette */ + --color-data-1: #88c0d0; /* frost cyan */ + --color-data-2: #bf616a; /* red */ + --color-data-3: #b48ead; /* purple */ + --color-data-4: #ebcb8b; /* yellow */ + --color-data-5: #a3be8c; /* green */ + --color-data-6: #d08770; /* orange */ + --color-data-7: #81a1c1; /* blue */ + --color-data-8: #8fbcbb; /* teal */ + + /* Log streams — tuned to Nord aurora */ + --color-log-stdout: #d8dee9; + --color-log-stderr: #bf616a; + --color-log-info: #88c0d0; + --color-log-warn: #ebcb8b; + + /* Shadows — cool, deeper */ + --shadow-subtle: 0 1px 2px rgba(0, 0, 0, 0.5); + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5), 0 4px 14px rgba(0, 0, 0, 0.45); + --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.55), 0 20px 48px rgba(0, 0, 0, 0.65); + --shadow-lg: 0 2px 8px rgba(0, 0, 0, 0.6), 0 28px 64px rgba(0, 0, 0, 0.7); --shadow-glow: var(--shadow-md); - --shadow-sidebar: 1px 0 0 rgba(255, 255, 255, 0.06); + --shadow-sidebar: 1px 0 0 rgba(216, 222, 233, 0.06); - /* Inset top-edge highlight — fakes elevation in dark mode */ - --shadow-inset-top: inset 0 1px 0 rgba(255, 255, 255, 0.06); + --shadow-inset-top: inset 0 1px 0 rgba(255, 255, 255, 0.05); + --shadow-inset-hi: inset 0 1px 0 rgba(255, 255, 255, 0.18); - --duration-fast: 150ms; - --duration-normal: 200ms; - --duration-slow: 300ms; - --ease-default: cubic-bezier(0.4, 0, 0.2, 1); + /* Motion */ + --duration-fast: 120ms; + --duration-normal: 180ms; + --duration-slow: 260ms; + --ease-default: cubic-bezier(0.22, 1, 0.36, 1); + --ease-spring: cubic-bezier(0.22, 1, 0.36, 1); + /* Spacing */ --spacing-xs: 0.25rem; --spacing-sm: 0.5rem; --spacing-md: 1rem; --spacing-lg: 1.5rem; --spacing-xl: 2rem; --spacing-2xl: 3rem; + --spacing-3xl: 4rem; - --radius-sm: 4px; - --radius-md: 6px; - --radius-lg: 10px; - --radius-xl: 14px; + /* Radii — sharp, editorial */ + --radius-sm: 3px; + --radius-md: 5px; + --radius-lg: 8px; + --radius-xl: 10px; --radius-full: 9999px; - /* Typography scale */ + /* Typography — Geist Variable + Geist Mono Variable */ + --font-sans: "Geist", "Geist Variable", -apple-system, BlinkMacSystemFont, "Segoe UI", ui-sans-serif, system-ui, sans-serif; + --font-mono: "Geist Mono", "Geist Mono Variable", ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace; + --text-xs: 0.6875rem; --text-sm: 0.8125rem; - --text-base: 0.9375rem; - --text-lg: 1.0625rem; + --text-base: 0.875rem; + --text-lg: 1rem; --text-xl: 1.25rem; - --text-2xl: 1.5rem; - --text-3xl: 1.875rem; + --text-2xl: 1.625rem; + --text-3xl: 2rem; + --text-4xl: 2.5rem; --font-weight-regular: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; - --leading-tight: 1.25; + --leading-tight: 1.2; --leading-snug: 1.4; --leading-normal: 1.55; --leading-relaxed: 1.7; --sidebar-width: 200px; --sidebar-width-collapsed: 52px; - --color-toggle-off: #3a4152; + --color-toggle-off: #2f3644; --color-toggle-on: var(--color-primary); } [data-theme="light"] { - /* Pearl palette — soft cool off-white, clean layered surfaces */ - --color-bg-primary: #f5f6f8; /* page: soft pearl */ - --color-bg-secondary: #ffffff; /* raised surface */ - --color-bg-tertiary: #e7e9ef; /* sunken wells */ - --color-bg-overlay: rgba(245, 246, 248, 0.92); + /* Snow storm */ + --color-bg-primary: #eceff4; /* nord6 */ + --color-bg-secondary: #ffffff; + --color-bg-tertiary: #e5e9f0; /* nord5 */ + --color-bg-overlay: rgba(236, 239, 244, 0.92); + --color-bg-hover: #e5e9f0; --color-surface-raised: #ffffff; - --color-surface-sunken: #eef0f4; - --color-surface-hover: #edeff4; + --color-surface-sunken: #e5e9f0; + --color-surface-hover: #d8dee9; + --color-surface-elevated: #d8dee9; /* nord4 */ - --color-primary: #14161f; /* near-black (cool-leaning) */ - --color-primary-hover: #000000; - --color-primary-active: #2a2e3a; - --color-primary-text: #FFFFFF; - --color-primary-light: rgba(20, 22, 31, 0.06); - --color-primary-border: rgba(20, 22, 31, 0.2); + /* Primary — deeper frost for WCAG on snow storm */ + --color-primary: #5e81ac; /* nord10 */ + --color-primary-hover: #4c6d92; + --color-primary-active: #3e5b7c; + --color-primary-text: #eceff4; + --color-primary-light: rgba(94, 129, 172, 0.12); + --color-primary-border: rgba(94, 129, 172, 0.34); - --color-secondary: #525966; - --color-secondary-hover: #3b414c; - --color-secondary-light: rgba(82, 89, 102, 0.1); + --color-secondary: #4c566a; /* nord3 */ + --color-secondary-hover: #3b4252; + --color-secondary-light: rgba(76, 86, 106, 0.1); - --color-accent: #c97b5a; /* muted terracotta accent */ - --color-accent-hover: #b56a4a; - --color-accent-light: rgba(201, 123, 90, 0.12); + --color-accent: #5e81ac; + --color-accent-hover: #4c6d92; + --color-accent-light: rgba(94, 129, 172, 0.12); + --color-accent-border: rgba(94, 129, 172, 0.32); - --color-text-primary: #14161f; /* crisp near-black, cool-leaning */ - --color-text-secondary: #555a68; - --color-text-muted: #8a8e9b; - --color-text-disabled: #c2c5cf; - --color-text-inverse: #FFFFFF; + --color-text-primary: #2e3440; /* nord0 */ + --color-text-secondary: #3b4252; /* nord1 */ + --color-text-muted: #6e7a8c; + --color-text-disabled: #a1acb9; + --color-text-inverse: #ffffff; - --color-border-subtle: rgba(20, 22, 31, 0.07); - --color-border-default: rgba(20, 22, 31, 0.14); - --color-border-strong: rgba(20, 22, 31, 0.32); - --color-border-divider: rgba(20, 22, 31, 0.05); - --color-border-primary: rgba(20, 22, 31, 0.22); - --color-border-focus: rgba(20, 22, 31, 0.45); + --color-border-subtle: rgba(46, 52, 64, 0.08); + --color-border-default: rgba(46, 52, 64, 0.14); + --color-border-strong: rgba(46, 52, 64, 0.28); + --color-border-divider: rgba(46, 52, 64, 0.06); + --color-border-primary: rgba(94, 129, 172, 0.45); + --color-border-focus: rgba(94, 129, 172, 0.45); - --color-success: #16A34A; - --color-success-light: rgba(22, 163, 74, 0.1); - --color-success-border: rgba(22, 163, 74, 0.3); - --color-warning: #D97706; - --color-warning-light: rgba(217, 119, 6, 0.1); - --color-warning-border: rgba(217, 119, 6, 0.3); - --color-error: #DC2626; - --color-error-light: rgba(220, 38, 38, 0.09); - --color-error-border: rgba(220, 38, 38, 0.3); - --color-info: #0f7583; - --color-info-light: rgba(15, 117, 131, 0.10); - --color-info-border: rgba(15, 117, 131, 0.3); - --color-accent-border: rgba(201, 123, 90, 0.3); - --color-modal-backdrop: rgba(20, 22, 31, 0.5); + /* Status — darker aurora for light mode contrast */ + --color-success: #6b8a5a; + --color-success-light: rgba(107, 138, 90, 0.12); + --color-success-border: rgba(107, 138, 90, 0.3); + --color-warning: #b08334; + --color-warning-light: rgba(176, 131, 52, 0.12); + --color-warning-border: rgba(176, 131, 52, 0.3); + --color-error: #a13e47; + --color-error-light: rgba(161, 62, 71, 0.1); + --color-error-border: rgba(161, 62, 71, 0.3); + --color-info: #4c6d92; + --color-info-light: rgba(76, 109, 146, 0.12); + --color-info-border: rgba(76, 109, 146, 0.3); - /* Data viz palette */ - --color-data-1: #0f7583; - --color-data-2: #c97b5a; - --color-data-3: #8b5cf6; - --color-data-4: #d97706; - --color-data-5: #059669; - --color-data-6: #dc2626; - --color-data-7: #db2777; - --color-data-8: #0891b2; + --color-modal-backdrop: rgba(46, 52, 64, 0.38); - --color-log-stdout: #14161f; - --color-log-stderr: #b91c1c; - --color-log-info: #0f7583; - --color-log-warn: #b45309; + --color-focus-ring: rgba(94, 129, 172, 0.34); - --shadow-subtle: 0 1px 2px rgba(20, 22, 31, 0.05); - --shadow-sm: 0 2px 6px rgba(20, 22, 31, 0.07); - --shadow-md: 0 8px 20px rgba(20, 22, 31, 0.08); - --shadow-lg: 0 20px 38px rgba(20, 22, 31, 0.1); - --shadow-glow: var(--shadow-md); - --shadow-sidebar: 4px 0 24px -12px rgba(20, 22, 31, 0.1), 1px 0 0 rgba(20, 22, 31, 0.06); + /* Data viz — muted aurora for light mode */ + --color-data-1: #5e81ac; + --color-data-2: #a13e47; + --color-data-3: #8b5a92; + --color-data-4: #b08334; + --color-data-5: #6b8a5a; + --color-data-6: #b8684f; + --color-data-7: #4c6d92; + --color-data-8: #5a9090; - --shadow-inset-top: inset 0 1px 0 rgba(255, 255, 255, 0.6); + --color-log-stdout: #2e3440; + --color-log-stderr: #a13e47; + --color-log-info: #4c6d92; + --color-log-warn: #b08334; - --color-toggle-off: #c5c8d2; + /* Soft cool shadows */ + --shadow-subtle: 0 1px 2px rgba(46, 52, 64, 0.05); + --shadow-sm: 0 1px 2px rgba(46, 52, 64, 0.07), 0 4px 14px rgba(46, 52, 64, 0.07); + --shadow-md: 0 2px 8px rgba(46, 52, 64, 0.1), 0 20px 48px rgba(46, 52, 64, 0.12); + --shadow-lg: 0 4px 12px rgba(46, 52, 64, 0.14), 0 28px 64px rgba(46, 52, 64, 0.16); + --shadow-sidebar: 1px 0 0 rgba(46, 52, 64, 0.06); + + --shadow-inset-top: inset 0 1px 0 rgba(255, 255, 255, 0.7); + --shadow-inset-hi: inset 0 1px 0 rgba(255, 255, 255, 0.8); + + --color-toggle-off: #c3cad6; --color-toggle-on: var(--color-primary); } diff --git a/core/http/react-ui/src/utils/cmTheme.js b/core/http/react-ui/src/utils/cmTheme.js index d7c91a112..1ef8caa42 100644 --- a/core/http/react-ui/src/utils/cmTheme.js +++ b/core/http/react-ui/src/utils/cmTheme.js @@ -2,126 +2,126 @@ import { EditorView } from '@codemirror/view' import { HighlightStyle, syntaxHighlighting } from '@codemirror/language' import { tags } from '@lezer/highlight' -// Dark theme — vibrant palette on deep indigo background +// Dark theme — Nord polar-night surfaces with aurora syntax highlighting const darkEditorTheme = EditorView.theme({ '&': { - backgroundColor: '#1a1a2e', - color: '#e2e8f0', - fontFamily: "'JetBrains Mono', 'Fira Code', monospace", + backgroundColor: '#13171f', + color: '#eceff4', + fontFamily: 'var(--font-mono)', fontSize: '0.8125rem', lineHeight: '1.5', }, '.cm-content': { - caretColor: '#a78bfa', + caretColor: '#88c0d0', padding: '0', }, - '.cm-cursor, .cm-dropCursor': { borderLeftColor: '#a78bfa', borderLeftWidth: '2px' }, + '.cm-cursor, .cm-dropCursor': { borderLeftColor: '#88c0d0', borderLeftWidth: '2px' }, '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': { - backgroundColor: 'rgba(139, 92, 246, 0.3)', + backgroundColor: 'rgba(136, 192, 208, 0.25)', }, '.cm-gutters': { - backgroundColor: '#16162a', - color: '#4c5772', - borderRight: '1px solid #2d2b55', + backgroundColor: '#1a1f2a', + color: '#6e7a8c', + borderRight: '1px solid #2f3644', }, - '.cm-activeLineGutter': { backgroundColor: 'rgba(139, 92, 246, 0.12)', color: '#8b8db5' }, - '.cm-activeLine': { backgroundColor: 'rgba(139, 92, 246, 0.06)' }, - '.cm-foldPlaceholder': { backgroundColor: '#2d2b55', border: 'none', color: '#8b8db5' }, - '.cm-matchingBracket': { backgroundColor: 'rgba(139, 92, 246, 0.25)', outline: '1px solid rgba(139, 92, 246, 0.5)' }, + '.cm-activeLineGutter': { backgroundColor: 'rgba(136, 192, 208, 0.1)', color: '#a1acb9' }, + '.cm-activeLine': { backgroundColor: 'rgba(136, 192, 208, 0.06)' }, + '.cm-foldPlaceholder': { backgroundColor: '#2f3644', border: 'none', color: '#a1acb9' }, + '.cm-matchingBracket': { backgroundColor: 'rgba(136, 192, 208, 0.22)', outline: '1px solid rgba(136, 192, 208, 0.5)' }, '.cm-tooltip': { - backgroundColor: '#1e1e3a', - border: '1px solid #2d2b55', - borderRadius: '6px', + backgroundColor: '#1a1f2a', + border: '1px solid #2f3644', + borderRadius: 'var(--radius-md)', boxShadow: '0 4px 16px rgba(0,0,0,0.5)', }, '.cm-tooltip-autocomplete': { - '& > ul': { fontFamily: "'JetBrains Mono', 'Fira Code', monospace", fontSize: '0.8125rem' }, - '& > ul > li': { padding: '4px 8px' }, - '& > ul > li[aria-selected]': { backgroundColor: 'rgba(139, 92, 246, 0.3)', color: '#f1f5f9' }, + '& > ul': { fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }, + '& > ul > li': { padding: 'var(--spacing-xs) var(--spacing-sm)' }, + '& > ul > li[aria-selected]': { backgroundColor: 'rgba(136, 192, 208, 0.22)', color: '#eceff4' }, }, - '.cm-tooltip.cm-completionInfo': { padding: '8px 10px', maxWidth: '300px' }, - '.cm-completionDetail': { color: '#8b8db5', fontStyle: 'italic', marginLeft: '0.5em' }, - '.cm-panels': { backgroundColor: '#16162a', color: '#e2e8f0' }, - '.cm-panels.cm-panels-top': { borderBottom: '1px solid #2d2b55' }, - '.cm-panels.cm-panels-bottom': { borderTop: '1px solid #2d2b55' }, - '.cm-searchMatch': { backgroundColor: 'rgba(250, 204, 21, 0.2)', outline: '1px solid rgba(250, 204, 21, 0.4)' }, - '.cm-searchMatch.cm-searchMatch-selected': { backgroundColor: 'rgba(250, 204, 21, 0.4)' }, - '.cm-selectionMatch': { backgroundColor: 'rgba(139, 92, 246, 0.15)' }, + '.cm-tooltip.cm-completionInfo': { padding: 'var(--spacing-sm)', maxWidth: '300px' }, + '.cm-completionDetail': { color: '#a1acb9', fontStyle: 'italic', marginLeft: '0.5em' }, + '.cm-panels': { backgroundColor: '#1a1f2a', color: '#eceff4' }, + '.cm-panels.cm-panels-top': { borderBottom: '1px solid #2f3644' }, + '.cm-panels.cm-panels-bottom': { borderTop: '1px solid #2f3644' }, + '.cm-searchMatch': { backgroundColor: 'rgba(235, 203, 139, 0.2)', outline: '1px solid rgba(235, 203, 139, 0.45)' }, + '.cm-searchMatch.cm-searchMatch-selected': { backgroundColor: 'rgba(235, 203, 139, 0.42)' }, + '.cm-selectionMatch': { backgroundColor: 'rgba(136, 192, 208, 0.12)' }, }, { dark: true }) const darkHighlightStyle = HighlightStyle.define([ - { tag: tags.propertyName, color: '#79c0ff', fontWeight: '500' }, // YAML keys — bright blue - { tag: tags.string, color: '#7ee787' }, // strings — vivid green - { tag: tags.number, color: '#ffa657' }, // numbers — warm orange - { tag: tags.bool, color: '#ff7eb6' }, // booleans — hot pink - { tag: tags.null, color: '#ff7eb6' }, // null — hot pink - { tag: tags.keyword, color: '#d2a8ff' }, // keywords — bright purple - { tag: tags.comment, color: '#5c6a82', fontStyle: 'italic' }, // comments — subtle - { tag: tags.meta, color: '#a5b4cf' }, // directives - { tag: tags.punctuation, color: '#8b949e' }, // colons, dashes - { tag: tags.atom, color: '#ff7eb6' }, // special values - { tag: tags.labelName, color: '#79c0ff', fontWeight: '500' }, // anchors/aliases + { tag: tags.propertyName, color: '#88c0d0', fontWeight: '500' }, // YAML keys — frost cyan + { tag: tags.string, color: '#a3be8c' }, // strings — aurora green + { tag: tags.number, color: '#d08770' }, // numbers — aurora orange + { tag: tags.bool, color: '#b48ead' }, // booleans — aurora purple + { tag: tags.null, color: '#b48ead' }, // null — aurora purple + { tag: tags.keyword, color: '#81a1c1' }, // keywords — frost blue + { tag: tags.comment, color: '#6e7a8c', fontStyle: 'italic' }, // comments — muted + { tag: tags.meta, color: '#d8dee9' }, // directives — snow storm + { tag: tags.punctuation, color: '#8fbcbb' }, // colons, dashes — frost teal + { tag: tags.atom, color: '#bf616a' }, // special values — aurora red + { tag: tags.labelName, color: '#88c0d0', fontWeight: '500' }, // anchors/aliases ]) -// Light theme — rich saturated colors on warm paper background +// Light theme — Nord snow-storm surfaces with darkened aurora highlighting const lightEditorTheme = EditorView.theme({ '&': { - backgroundColor: '#fafaf9', - color: '#1c1917', - fontFamily: "'JetBrains Mono', 'Fira Code', monospace", + backgroundColor: '#ffffff', + color: '#2e3440', + fontFamily: 'var(--font-mono)', fontSize: '0.8125rem', lineHeight: '1.5', }, '.cm-content': { - caretColor: '#7c3aed', + caretColor: '#5e81ac', padding: '0', }, - '.cm-cursor, .cm-dropCursor': { borderLeftColor: '#7c3aed', borderLeftWidth: '2px' }, + '.cm-cursor, .cm-dropCursor': { borderLeftColor: '#5e81ac', borderLeftWidth: '2px' }, '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': { - backgroundColor: 'rgba(124, 58, 237, 0.15)', + backgroundColor: 'rgba(94, 129, 172, 0.18)', }, '.cm-gutters': { - backgroundColor: '#f5f5f4', - color: '#a8a29e', - borderRight: '1px solid #e7e5e4', + backgroundColor: '#e5e9f0', + color: '#6e7a8c', + borderRight: '1px solid #d8dee9', }, - '.cm-activeLineGutter': { backgroundColor: 'rgba(124, 58, 237, 0.06)', color: '#78716c' }, - '.cm-activeLine': { backgroundColor: 'rgba(124, 58, 237, 0.03)' }, - '.cm-foldPlaceholder': { backgroundColor: '#e7e5e4', border: 'none', color: '#78716c' }, - '.cm-matchingBracket': { backgroundColor: 'rgba(124, 58, 237, 0.15)', outline: '1px solid rgba(124, 58, 237, 0.3)' }, + '.cm-activeLineGutter': { backgroundColor: 'rgba(94, 129, 172, 0.1)', color: '#3b4252' }, + '.cm-activeLine': { backgroundColor: 'rgba(94, 129, 172, 0.05)' }, + '.cm-foldPlaceholder': { backgroundColor: '#d8dee9', border: 'none', color: '#4c566a' }, + '.cm-matchingBracket': { backgroundColor: 'rgba(94, 129, 172, 0.18)', outline: '1px solid rgba(94, 129, 172, 0.35)' }, '.cm-tooltip': { backgroundColor: '#ffffff', - border: '1px solid #e7e5e4', - borderRadius: '6px', - boxShadow: '0 4px 16px rgba(0,0,0,0.08)', + border: '1px solid #d8dee9', + borderRadius: 'var(--radius-md)', + boxShadow: '0 4px 16px rgba(46, 52, 64, 0.12)', }, '.cm-tooltip-autocomplete': { - '& > ul': { fontFamily: "'JetBrains Mono', 'Fira Code', monospace", fontSize: '0.8125rem' }, - '& > ul > li': { padding: '4px 8px' }, - '& > ul > li[aria-selected]': { backgroundColor: 'rgba(124, 58, 237, 0.1)', color: '#1c1917' }, + '& > ul': { fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }, + '& > ul > li': { padding: 'var(--spacing-xs) var(--spacing-sm)' }, + '& > ul > li[aria-selected]': { backgroundColor: 'rgba(94, 129, 172, 0.14)', color: '#2e3440' }, }, - '.cm-tooltip.cm-completionInfo': { padding: '8px 10px', maxWidth: '300px' }, - '.cm-completionDetail': { color: '#78716c', fontStyle: 'italic', marginLeft: '0.5em' }, - '.cm-panels': { backgroundColor: '#f5f5f4', color: '#1c1917' }, - '.cm-panels.cm-panels-top': { borderBottom: '1px solid #e7e5e4' }, - '.cm-panels.cm-panels-bottom': { borderTop: '1px solid #e7e5e4' }, - '.cm-searchMatch': { backgroundColor: 'rgba(234, 179, 8, 0.25)', outline: '1px solid rgba(234, 179, 8, 0.5)' }, - '.cm-searchMatch.cm-searchMatch-selected': { backgroundColor: 'rgba(234, 179, 8, 0.45)' }, - '.cm-selectionMatch': { backgroundColor: 'rgba(124, 58, 237, 0.08)' }, + '.cm-tooltip.cm-completionInfo': { padding: 'var(--spacing-sm)', maxWidth: '300px' }, + '.cm-completionDetail': { color: '#6e7a8c', fontStyle: 'italic', marginLeft: '0.5em' }, + '.cm-panels': { backgroundColor: '#e5e9f0', color: '#2e3440' }, + '.cm-panels.cm-panels-top': { borderBottom: '1px solid #d8dee9' }, + '.cm-panels.cm-panels-bottom': { borderTop: '1px solid #d8dee9' }, + '.cm-searchMatch': { backgroundColor: 'rgba(176, 131, 52, 0.22)', outline: '1px solid rgba(176, 131, 52, 0.45)' }, + '.cm-searchMatch.cm-searchMatch-selected': { backgroundColor: 'rgba(176, 131, 52, 0.4)' }, + '.cm-selectionMatch': { backgroundColor: 'rgba(94, 129, 172, 0.1)' }, }) const lightHighlightStyle = HighlightStyle.define([ - { tag: tags.propertyName, color: '#0550ae', fontWeight: '500' }, // YAML keys — deep blue - { tag: tags.string, color: '#116329' }, // strings — forest green - { tag: tags.number, color: '#cf5500' }, // numbers — burnt orange - { tag: tags.bool, color: '#cf222e' }, // booleans — crimson - { tag: tags.null, color: '#cf222e' }, // null — crimson - { tag: tags.keyword, color: '#8250df' }, // keywords — vivid purple - { tag: tags.comment, color: '#a3a3a3', fontStyle: 'italic' }, // comments — soft gray - { tag: tags.meta, color: '#57606a' }, // directives - { tag: tags.punctuation, color: '#6e7781' }, // colons, dashes - { tag: tags.atom, color: '#cf222e' }, // special values - { tag: tags.labelName, color: '#0550ae', fontWeight: '500' }, // anchors/aliases + { tag: tags.propertyName, color: '#5e81ac', fontWeight: '500' }, // YAML keys — frost blue + { tag: tags.string, color: '#4c6b3a' }, // strings — deep aurora green + { tag: tags.number, color: '#b8684f' }, // numbers — warm orange + { tag: tags.bool, color: '#8b5a92' }, // booleans — muted purple + { tag: tags.null, color: '#8b5a92' }, // null — muted purple + { tag: tags.keyword, color: '#4c6d92' }, // keywords — deeper frost + { tag: tags.comment, color: '#7a8598', fontStyle: 'italic' }, // comments — cool gray + { tag: tags.meta, color: '#3b4252' }, // directives + { tag: tags.punctuation, color: '#5a8080' }, // colons, dashes — muted teal + { tag: tags.atom, color: '#a13e47' }, // special values — deep aurora red + { tag: tags.labelName, color: '#5e81ac', fontWeight: '500' }, // anchors/aliases ]) export const darkTheme = [darkEditorTheme, syntaxHighlighting(darkHighlightStyle)]
FilenameURISHA256FilenameURISHA256
{f.filename || '—'}{f.uri || '—'} + {f.filename || '—'}{f.uri || '—'} {f.sha256 ? f.sha256.substring(0, 16) + '...' : '—'}
{hasGPU && totalVRAMStr ? ( -
+
{vendorLabel && ( {vendorLabel} )} @@ -718,7 +718,7 @@ export default function Nodes() {
) : totalRAMStr ? ( -
+
CPU {usedRAMStr || '0'} / {totalRAMStr} RAM @@ -729,7 +729,7 @@ export default function Nodes() { )}
- + {timeAgo(node.last_heartbeat)}
+ {m.model_name} @@ -814,7 +814,7 @@ export default function Nodes() { {m.state} + {m.in_flight ?? 0} @@ -878,7 +878,7 @@ export default function Nodes() {
+ {b.name} @@ -922,9 +922,9 @@ export default function Nodes() { {node.labels && Object.entries(node.labels).map(([k, v]) => ( {k}={v} {modeLabel} @@ -1032,18 +1032,18 @@ export default function Nodes() { const sel = typeof cfg.node_selector === 'string' ? JSON.parse(cfg.node_selector) : cfg.node_selector return Object.entries(sel).map(([k,v]) => ( {k}={v} )) } catch { return {cfg.node_selector} } })() : Any node} + {isAutoScaling ? cfg.min_replicas : '-'} + {isAutoScaling ? (cfg.max_replicas || 'no limit') : '-'} diff --git a/core/http/react-ui/src/pages/P2P.jsx b/core/http/react-ui/src/pages/P2P.jsx index f514b6898..d680e468d 100644 --- a/core/http/react-ui/src/pages/P2P.jsx +++ b/core/http/react-ui/src/pages/P2P.jsx @@ -24,7 +24,7 @@ function NodeCard({ node, label, iconColor, iconBg }) {

{label}

-

+

{node.id}

@@ -68,7 +68,7 @@ function CommandBlock({ command, addToast }) {
           {token || 'Loading...'}
@@ -459,7 +459,7 @@ export default function P2P() {
                 
                 
                 
-
+
{[1, 2, 3].map(n => (
RPC
-
+
{['Layer 1-10', 'Layer 11-20', 'Layer 21-30'].map((label, i) => (
Ring / JACCL
-
+
{['Layers 1-16', 'Layers 17-32'].map((label, i) => (
GPU {i} -
-
+
+
{usedPct}% {formatBytes(gpu.used)} / {formatBytes(gpu.total)} @@ -231,8 +231,8 @@ export default function Settings() { const usedPct = resources.ram.total > 0 ? Math.round((resources.ram.used / resources.ram.total) * 100) : 0 return ( <> -
-
+
+
{usedPct}% {formatBytes(resources.ram.used)} / {formatBytes(resources.ram.total)} @@ -380,7 +380,7 @@ export default function Settings() { onChange={(e) => update('galleries_json', e.target.value)} rows={4} placeholder={'[\n { "url": "https://...", "name": "my-gallery" }\n]'} - style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }} + style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }} />
@@ -391,7 +391,7 @@ export default function Settings() { onChange={(e) => update('backend_galleries_json', e.target.value)} rows={4} placeholder={'[\n { "url": "https://...", "name": "my-backends" }\n]'} - style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }} + style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }} />
@@ -413,7 +413,7 @@ export default function Settings() { onChange={(e) => update('api_keys_text', e.target.value)} rows={4} placeholder="sk-key-1 sk-key-2" - style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }} + style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }} />
diff --git a/core/http/react-ui/src/pages/SkillEdit.jsx b/core/http/react-ui/src/pages/SkillEdit.jsx index c371ba8f0..9d93593c0 100644 --- a/core/http/react-ui/src/pages/SkillEdit.jsx +++ b/core/http/react-ui/src/pages/SkillEdit.jsx @@ -191,7 +191,7 @@ function ResourcesSection({ skillName, addToast }) { value={editor.content} onChange={(e) => setEditor((x) => ({ ...x, content: e.target.value }))} rows={14} - style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.875rem', marginBottom: 'var(--spacing-md)', width: '100%' }} + style={{ fontFamily: 'var(--font-mono)', fontSize: '0.875rem', marginBottom: 'var(--spacing-md)', width: '100%' }} />
@@ -594,7 +594,7 @@ export default function SkillEdit() { value={form.content} onChange={(e) => setForm((f) => ({ ...f, content: e.target.value }))} rows={14} - style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.875rem' }} + style={{ fontFamily: 'var(--font-mono)', fontSize: '0.875rem' }} />
diff --git a/core/http/react-ui/src/pages/Talk.jsx b/core/http/react-ui/src/pages/Talk.jsx index 2af98c91e..0bc9b5b66 100644 --- a/core/http/react-ui/src/pages/Talk.jsx +++ b/core/http/react-ui/src/pages/Talk.jsx @@ -525,7 +525,7 @@ export default function Talk() { padding: 'var(--spacing-xs)', border: '1px solid var(--color-border)', }}>
{item.label}
-
{item.value}
+
{item.value}
))}
@@ -623,7 +623,7 @@ export default function Talk() { ) : ( <>
{isConnected && ( )} @@ -660,12 +660,12 @@ export default function Talk() {

Waveform

+ style={{ width: '100%', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'var(--color-surface-sunken)' }} />

Spectrum (FFT)

+ style={{ width: '100%', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'var(--color-surface-sunken)' }} />
@@ -684,7 +684,7 @@ export default function Talk() { background: 'var(--color-bg-secondary)', borderRadius: 'var(--radius-sm)', padding: 'var(--spacing-xs)', }}>
{item.label}
-
{item.value}
+
{item.value}
))}
@@ -693,7 +693,7 @@ export default function Talk() { fontSize: '0.6875rem', color: 'var(--color-text-secondary)', background: 'var(--color-bg-secondary)', borderRadius: 'var(--radius-sm)', padding: 'var(--spacing-xs)', maxHeight: '8rem', overflowY: 'auto', - fontFamily: 'monospace', whiteSpace: 'pre-wrap', margin: 0, + fontFamily: 'var(--font-mono)', whiteSpace: 'pre-wrap', margin: 0, }}> {diagStats.raw || 'Waiting for stats...'}
diff --git a/core/http/react-ui/src/pages/Traces.jsx b/core/http/react-ui/src/pages/Traces.jsx index 23942cde4..c03c3d83b 100644 --- a/core/http/react-ui/src/pages/Traces.jsx +++ b/core/http/react-ui/src/pages/Traces.jsx @@ -102,7 +102,7 @@ function AudioSnippet({ data }) { {metrics.map(m => (
{m.label}
-
{m.value}
+
{m.value}
))} @@ -155,9 +155,9 @@ function DataFields({ data, nested }) { ) : ( )} - {key} + {key} {objValue && !expanded && {fieldSummary(value)}} - {!objValue && !large && {formatValue(value)}} + {!objValue && !large && {formatValue(value)}} {!objValue && large && !expanded && {truncateValue(value, 120)}} {expanded && objValue && ( @@ -170,7 +170,7 @@ function DataFields({ data, nested }) {
                     {formatLargeValue(value)}
@@ -250,7 +250,7 @@ function ApiTraceDetail({ trace }) {
           display: 'flex', alignItems: 'center', gap: 'var(--spacing-xs)',
         }}>
           
-          {trace.error}
+          {trace.error}
         
       )}
       
@@ -259,7 +259,7 @@ function ApiTraceDetail({ trace }) {
             {decodeTraceBody(trace.request?.body)}
@@ -270,7 +270,7 @@ function ApiTraceDetail({ trace }) {
           
             {decodeTraceBody(trace.response?.body)}
@@ -511,7 +511,7 @@ export default function Traces() {
                   
{trace.request?.method || '-'}{trace.request?.path || '-'}{trace.request?.path || '-'} {trace.response?.status || '-'} {trace.error @@ -552,7 +552,7 @@ export default function Traces() { {trace.type || '-'} {formatTimestamp(trace.timestamp)}{trace.model_name || '-'}{trace.model_name || '-'} {trace.summary || '-'}