mirror of
https://github.com/mudler/LocalAI.git
synced 2026-04-29 03:24:49 -04:00
feat(react-ui): editorial refresh with Nord palette and polished primitives (#9550)
* feat(react-ui): editorial refresh with Nord palette and polished primitives
Replaces the cool gray-blue theme with a deep Nord-inspired palette:
frost-cyan accent (#88c0d0) on deep blue-black surfaces (#13171f /
#1a1f2a / #242a36), snow-storm text scale, aurora status colours.
- Typography: Geist Variable + Geist Mono Variable (Google Fonts) with
ss01/ss03/cv11 stylistic alternates; strengthened h1-h6 hierarchy;
editorial negative tracking.
- Primitives: buttons gain depth (inset highlight + hover lift +
brightness filter); inputs become sunken wells with sage-swap-to-frost
focus rings; cards hover-lift and gain an .card--accent left-rail
variant; badges become mono caps rectangles with tabular-nums.
- Chrome: sidebar active state is now an inset left rail + tint
(no border-left); modals get popIn animation and proper shadow lift;
toasts carry an inset accent bar + slide-in instead of tinted fills;
operations bar breathes on active installs.
- Empty states: editorial pattern (eyebrow rule, large mono title,
52ch lede) that inherits gracefully even without page JSX edits.
- Chat: assistant bubbles drop the gray-nested-in-gray card for a
transparent pull-quote with a left border; user bubbles soften from
loud accent fill to a subtle frost tint.
- Motion: custom spring easing cubic-bezier(0.22,1,0.36,1), 180ms
standard; breathing/pulse/popIn keyframes; global prefers-reduced-
motion honoring.
- Radii tightened to 3/5/8/10px; warm-shadow tokens redone for cool
depth; ::selection, :focus-visible, kbd globals added.
- Migrated hardcoded 'JetBrains Mono' CSS literals to var(--font-mono)
so the Geist Mono swap lands everywhere.
Scope is intentionally tokens + primitives only. Page JSX and the
~1,800 inline style={{…}} instances are untouched and flagged as
follow-ups.
Assisted-by: Claude:claude-opus-4-7 [Read] [Edit] [Write]
* feat(react-ui): complete-coverage pass — migrate inline styles to tokens
Follows up the editorial/Nord token refresh with a mechanical sweep of
page JSX and shared components so nothing bypasses the design system.
- Font family: replaced 80+ 'JetBrains Mono' / 'Space Grotesk' inline
literals (and the string-CSS variants in CollectionDetails and
AgentStatus) with var(--font-mono) / var(--font-sans). SVG <text>
nodes that used the attribute form were switched to style={{ }} so
the CSS variable resolves.
- Radii: every unquoted numeric borderRadius (2/3/4/10) is now a
var(--radius-*) token; 50% and 999px kept as computed shapes.
- Spacing: clean-token gaps and margins (4/8/16px) moved to
var(--spacing-xs/sm/md); padding: '4px 8px' and '8px 16px' lifted
into token pairs. Micro-values (2/6/10/12px) left inline where no
token maps cleanly.
- Colors: Talk.jsx button/canvas-surface hardcodes moved to
var(--color-*); FineTune.jsx chart series colours now use the
--color-data-* Nord palette (cyan/red/purple/orange instead of
tailwind hex); AgentStatus tool-call icon and error tag hex swapped
for var(--color-warning) / var(--color-text-inverse).
- CodeMirror editor (utils/cmTheme.js): both themes rebased on Nord —
polar-night surfaces and aurora syntax highlighting (dark), snow-
storm surfaces with darkened aurora (light). Caret/selection/active
line/search now frost-cyan tinted instead of legacy indigo/purple.
Legitimately dynamic styles (computed widths, per-row colours, canvas
2D context fill/stroke for waveform and spectrogram drawing) remain
inline — they can't be expressed as CSS tokens.
29 files, +237/-237 — identity preserved, semantics re-anchored to
the token system.
Assisted-by: Claude:claude-opus-4-7 [Read] [Edit] [Write]
This commit is contained in:
committed by
GitHub
parent
9ab3496de2
commit
487e3fd2a4
@@ -7,7 +7,7 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@300..700&family=Geist+Mono:wght@300..700&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)' }}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
@@ -88,7 +88,7 @@ export default function ClientMCPDropdown({
|
||||
placeholder="Name (optional)"
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
style={{ width: '100%', marginBottom: '4px' }}
|
||||
style={{ width: '100%', marginBottom: 'var(--spacing-xs)' }}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
@@ -96,13 +96,13 @@ export default function ClientMCPDropdown({
|
||||
placeholder="Auth token (optional)"
|
||||
value={authToken}
|
||||
onChange={e => setAuthToken(e.target.value)}
|
||||
style={{ width: '100%', marginBottom: '4px' }}
|
||||
style={{ width: '100%', marginBottom: 'var(--spacing-xs)' }}
|
||||
/>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '0.8rem', marginBottom: '6px' }}>
|
||||
<input type="checkbox" checked={useProxy} onChange={e => setUseProxy(e.target.checked)} />
|
||||
Use CORS proxy
|
||||
</label>
|
||||
<div style={{ display: 'flex', gap: '4px', justifyContent: 'flex-end' }}>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-xs)', justifyContent: 'flex-end' }}>
|
||||
<button type="button" className="btn btn-sm btn-secondary" onClick={() => setAddDialog(false)}>Cancel</button>
|
||||
<button type="button" className="btn btn-sm btn-primary" onClick={handleAdd} disabled={!url.trim()}>Add</button>
|
||||
</div>
|
||||
|
||||
@@ -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 && <div style={{ color: 'var(--color-error)', fontSize: '0.75rem', marginTop: 2 }}>{parseError}</div>}
|
||||
</div>
|
||||
|
||||
@@ -158,7 +158,7 @@ export default function FieldBrowser({ fields, activeFieldPaths, onAddField }) {
|
||||
{field.description}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ fontSize: '0.6875rem', color: 'var(--color-text-muted)', marginTop: 1, fontFamily: 'monospace' }}>
|
||||
<div style={{ fontSize: '0.6875rem', color: 'var(--color-text-muted)', marginTop: 1, fontFamily: 'var(--font-mono)' }}>
|
||||
{field.path}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function ResourceMonitor() {
|
||||
<div className="resource-bar-container" style={{ flex: 1 }}>
|
||||
<div className="resource-bar" style={{ width: `${pct}%`, background: color }} />
|
||||
</div>
|
||||
<span style={{ fontSize: '0.8125rem', fontWeight: 600, fontFamily: "'JetBrains Mono', monospace", color, minWidth: '3em', textAlign: 'right' }}>
|
||||
<span style={{ fontSize: '0.8125rem', fontWeight: 600, fontFamily: 'var(--font-mono)', color, minWidth: '3em', textAlign: 'right' }}>
|
||||
{pct.toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
@@ -76,7 +76,7 @@ export default function ResourceMonitor() {
|
||||
<div className="resource-bar-container" style={{ flex: 1 }}>
|
||||
<div className="resource-bar" style={{ width: `${ram.usage_percent || 0}%`, background: percentColor(ram.usage_percent || 0) }} />
|
||||
</div>
|
||||
<span style={{ fontSize: '0.8125rem', fontWeight: 600, fontFamily: "'JetBrains Mono', monospace", color: percentColor(ram.usage_percent || 0), minWidth: '3em', textAlign: 'right' }}>
|
||||
<span style={{ fontSize: '0.8125rem', fontWeight: 600, fontFamily: 'var(--font-mono)', color: percentColor(ram.usage_percent || 0), minWidth: '3em', textAlign: 'right' }}>
|
||||
{(ram.usage_percent || 0).toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
@@ -91,7 +91,7 @@ export default function ResourceMonitor() {
|
||||
{isGpu && aggregate.gpu_count > 1 && (
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--color-text-secondary)', marginTop: 'var(--spacing-sm)', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>Total VRAM</span>
|
||||
<span style={{ fontFamily: "'JetBrains Mono', monospace" }}>
|
||||
<span style={{ fontFamily: 'var(--font-mono)' }}>
|
||||
{formatBytes(aggregate.used_memory)} / {formatBytes(aggregate.total_memory)} ({aggregate.usage_percent?.toFixed(1)}%)
|
||||
</span>
|
||||
</div>
|
||||
@@ -101,7 +101,7 @@ export default function ResourceMonitor() {
|
||||
{resources.storage_size != null && (
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--color-text-secondary)', marginTop: 'var(--spacing-sm)', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>Models storage</span>
|
||||
<span style={{ fontFamily: "'JetBrains Mono', monospace", color: 'var(--color-text-primary)' }}>
|
||||
<span style={{ fontFamily: 'var(--font-mono)', color: 'var(--color-text-primary)' }}>
|
||||
{formatBytes(resources.storage_size)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -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' }}
|
||||
/>
|
||||
</div>
|
||||
<div ref={listRef} role="listbox" style={{ overflowY: 'auto', maxHeight: 'min(200px, 50vh)' }}>
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function TemplateSelector({ onSelect }) {
|
||||
<p style={{ fontSize: '0.8125rem', color: 'var(--color-text-secondary)', lineHeight: 1.5, margin: 0 }}>
|
||||
{t.description}
|
||||
</p>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px', marginTop: 'var(--spacing-xs)' }}>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-xs)', marginTop: 'var(--spacing-xs)' }}>
|
||||
{Object.keys(t.fields).filter(k => k !== 'name').map(k => (
|
||||
<span key={k} className="badge" style={{
|
||||
fontSize: '0.6875rem', background: 'var(--color-bg-tertiary)',
|
||||
|
||||
@@ -187,7 +187,7 @@ export default function UnifiedMCPDropdown({
|
||||
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)' }}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
@@ -195,7 +195,7 @@ export default function UnifiedMCPDropdown({
|
||||
placeholder="Name (optional)"
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
style={{ width: '100%', marginBottom: '4px' }}
|
||||
style={{ width: '100%', marginBottom: 'var(--spacing-xs)' }}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
@@ -203,13 +203,13 @@ export default function UnifiedMCPDropdown({
|
||||
placeholder="Auth token (optional)"
|
||||
value={authToken}
|
||||
onChange={e => setAuthToken(e.target.value)}
|
||||
style={{ width: '100%', marginBottom: '4px' }}
|
||||
style={{ width: '100%', marginBottom: 'var(--spacing-xs)' }}
|
||||
/>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '0.8rem', marginBottom: '6px' }}>
|
||||
<input type="checkbox" checked={useProxy} onChange={e => setUseProxy(e.target.checked)} />
|
||||
Use CORS proxy
|
||||
</label>
|
||||
<div style={{ display: 'flex', gap: '4px', justifyContent: 'flex-end' }}>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-xs)', justifyContent: 'flex-end' }}>
|
||||
<button type="button" className="btn btn-sm btn-secondary" onClick={() => setAddDialog(false)}>Cancel</button>
|
||||
<button type="button" className="btn btn-sm btn-primary" onClick={handleAddClient} disabled={!url.trim()}>Add</button>
|
||||
</div>
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -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}'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@ function TraceCard({ trace, index }) {
|
||||
{trace.type || 'unknown'}
|
||||
</span>
|
||||
{trace.tool_name && (
|
||||
<span style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.75rem', color: 'var(--color-text-secondary)' }}>
|
||||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--color-text-secondary)' }}>
|
||||
{trace.tool_name}
|
||||
</span>
|
||||
)}
|
||||
@@ -60,7 +60,7 @@ function TraceCard({ trace, index }) {
|
||||
{trace.content && (
|
||||
<pre style={{
|
||||
whiteSpace: 'pre-wrap', wordBreak: 'break-word', margin: 0,
|
||||
fontFamily: "'JetBrains Mono', monospace", fontSize: '0.75rem',
|
||||
fontFamily: 'var(--font-mono)', fontSize: '0.75rem',
|
||||
color: 'var(--color-text-secondary)', lineHeight: 1.6,
|
||||
}}>
|
||||
{trace.content}
|
||||
@@ -71,7 +71,7 @@ function TraceCard({ trace, index }) {
|
||||
<span style={{ fontSize: '0.6875rem', fontWeight: 600, color: 'var(--color-text-muted)' }}>Arguments:</span>
|
||||
<pre style={{
|
||||
whiteSpace: 'pre-wrap', wordBreak: 'break-word', margin: '4px 0 0',
|
||||
fontFamily: "'JetBrains Mono', monospace", fontSize: '0.75rem',
|
||||
fontFamily: 'var(--font-mono)', fontSize: '0.75rem',
|
||||
color: 'var(--color-text-secondary)', lineHeight: 1.5,
|
||||
}}>
|
||||
{typeof trace.arguments === 'string' ? trace.arguments : JSON.stringify(trace.arguments, null, 2)}
|
||||
@@ -207,7 +207,7 @@ export default function AgentJobDetails() {
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 'var(--spacing-md)' }}>
|
||||
<div>
|
||||
<span className="form-label">Job ID</span>
|
||||
<p style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem', wordBreak: 'break-all' }}>{job.id}</p>
|
||||
<p style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem', wordBreak: 'break-all' }}>{job.id}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="form-label">Task</span>
|
||||
@@ -264,7 +264,7 @@ export default function AgentJobDetails() {
|
||||
</h3>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-xs)' }}>
|
||||
{Object.entries(job.cron_parameters).map(([k, v]) => (
|
||||
<span key={k} className="badge badge-info" style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.75rem' }}>
|
||||
<span key={k} className="badge badge-info" style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem' }}>
|
||||
{k}={v}
|
||||
</span>
|
||||
))}
|
||||
@@ -281,7 +281,7 @@ export default function AgentJobDetails() {
|
||||
</h3>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-xs)' }}>
|
||||
{Object.entries(job.parameters).map(([k, v]) => (
|
||||
<span key={k} className="badge badge-info" style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.75rem' }}>
|
||||
<span key={k} className="badge badge-info" style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem' }}>
|
||||
{k}={v}
|
||||
</span>
|
||||
))}
|
||||
|
||||
@@ -253,7 +253,7 @@ export default function AgentJobs() {
|
||||
</p>
|
||||
<div style={{ background: 'var(--color-bg-primary)', borderRadius: 'var(--radius-md)', padding: 'var(--spacing-md)', maxWidth: 500, margin: '0 auto var(--spacing-md)', textAlign: 'left' }}>
|
||||
<p style={{ fontSize: '0.8125rem', fontWeight: 600, marginBottom: 'var(--spacing-xs)' }}>Example MCP configuration (YAML):</p>
|
||||
<pre style={{ fontSize: '0.75rem', fontFamily: "'JetBrains Mono', monospace", color: 'var(--color-text-secondary)', whiteSpace: 'pre-wrap' }}>{`mcp:
|
||||
<pre style={{ fontSize: '0.75rem', fontFamily: 'var(--font-mono)', color: 'var(--color-text-secondary)', whiteSpace: 'pre-wrap' }}>{`mcp:
|
||||
stdio:
|
||||
- name: my-tool
|
||||
command: /path/to/tool
|
||||
@@ -345,7 +345,7 @@ export default function AgentJobs() {
|
||||
</td>
|
||||
<td>
|
||||
{task.cron ? (
|
||||
<span className="badge badge-info" style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.6875rem' }}>
|
||||
<span className="badge badge-info" style={{ fontFamily: 'var(--font-mono)', fontSize: '0.6875rem' }}>
|
||||
{task.cron}
|
||||
</span>
|
||||
) : '-'}
|
||||
@@ -426,7 +426,7 @@ export default function AgentJobs() {
|
||||
{filteredJobs.map(job => (
|
||||
<tr key={job.id}>
|
||||
<td>
|
||||
<a onClick={() => navigate(`/app/agent-jobs/jobs/${job.id}`)} style={{ cursor: 'pointer', color: 'var(--color-primary)', fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }}>
|
||||
<a onClick={() => 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)}...
|
||||
</a>
|
||||
</td>
|
||||
@@ -510,7 +510,7 @@ export default function AgentJobs() {
|
||||
<tbody>
|
||||
{(items || []).map(job => (
|
||||
<tr key={job.id}>
|
||||
<td style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }}>{job.id?.slice(0, 12)}...</td>
|
||||
<td style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }}>{job.id?.slice(0, 12)}...</td>
|
||||
<td>{job.task_id || '-'}</td>
|
||||
<td>{statusBadge(job.status)}</td>
|
||||
<td style={{ fontSize: '0.8125rem', color: 'var(--color-text-secondary)' }}>{formatDate(job.created_at)}</td>
|
||||
@@ -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' }}
|
||||
/>
|
||||
<p style={{ fontSize: '0.75rem', color: 'var(--color-text-muted)', marginTop: 'var(--spacing-xs)' }}>
|
||||
These will be available as {'{{.parameter_name}}'} in the prompt template.
|
||||
@@ -590,7 +590,7 @@ export default function AgentJobs() {
|
||||
{executeMultimedia[type].map((item, i) => (
|
||||
<div key={i} style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
background: 'var(--color-bg-primary)', borderRadius: 'var(--radius-sm)', padding: '4px 8px', fontSize: '0.75rem',
|
||||
background: 'var(--color-bg-primary)', borderRadius: 'var(--radius-sm)', padding: 'var(--spacing-xs) var(--spacing-sm)', fontSize: '0.75rem',
|
||||
}}>
|
||||
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{item.name || item.url?.slice(0, 40)}</span>
|
||||
<button onClick={() => removeMultimedia(type, i)} style={{ background: 'none', border: 'none', color: 'var(--color-error)', cursor: 'pointer', padding: '2px 4px' }}>
|
||||
|
||||
@@ -294,7 +294,7 @@ export default function AgentStatus() {
|
||||
.as-id {
|
||||
font-size: 0.6875rem;
|
||||
color: var(--color-text-muted);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.as-summary-item {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
@@ -303,7 +303,7 @@ export default function AgentStatus() {
|
||||
}
|
||||
.as-summary-item i { font-size: 0.625rem; flex-shrink: 0; }
|
||||
.as-summary-creation i { color: var(--color-primary); }
|
||||
.as-summary-tool-call i { color: #f59e0b; }
|
||||
.as-summary-tool-call i { color: var(--color-warning); }
|
||||
.as-summary-completion i { color: var(--color-success); }
|
||||
.as-summary-error i { color: var(--color-error); }
|
||||
.as-card-body {
|
||||
@@ -327,13 +327,13 @@ export default function AgentStatus() {
|
||||
background: var(--color-bg-tertiary); color: var(--color-text-muted);
|
||||
margin-right: 4px; vertical-align: middle;
|
||||
}
|
||||
.as-tag-error { background: var(--color-error); color: #fff; }
|
||||
.as-tag-error { background: var(--color-error); color: var(--color-text-inverse); }
|
||||
.as-error-text { color: var(--color-error); }
|
||||
.as-raw { margin-top: var(--spacing-sm); }
|
||||
.as-raw summary { font-size: 0.75rem; color: var(--color-text-muted); cursor: pointer; }
|
||||
.as-json {
|
||||
background: var(--color-bg-tertiary); border-radius: var(--radius-sm);
|
||||
padding: var(--spacing-sm); font-family: 'JetBrains Mono', monospace;
|
||||
padding: var(--spacing-sm); font-family: var(--font-mono);
|
||||
font-size: 0.75rem; overflow-x: auto; white-space: pre-wrap;
|
||||
word-break: break-word; max-height: 300px; overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ export default function AgentTaskDetails() {
|
||||
{task.cron && (
|
||||
<div>
|
||||
<span className="form-label">Cron Schedule</span>
|
||||
<p style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }}>{task.cron}</p>
|
||||
<p style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }}>{task.cron}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -229,13 +229,13 @@ export default function AgentTaskDetails() {
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-md)' }}>
|
||||
<div>
|
||||
<span className="form-label">Execute by name</span>
|
||||
<pre style={{ background: 'var(--color-bg-primary)', padding: 'var(--spacing-sm)', borderRadius: 'var(--radius-md)', fontSize: '0.75rem', fontFamily: "'JetBrains Mono', monospace", whiteSpace: 'pre-wrap', overflow: 'auto' }}>
|
||||
<pre style={{ background: 'var(--color-bg-primary)', padding: 'var(--spacing-sm)', borderRadius: 'var(--radius-md)', fontSize: '0.75rem', fontFamily: 'var(--font-mono)', whiteSpace: 'pre-wrap', overflow: 'auto' }}>
|
||||
{`curl -X POST ${window.location.origin}${basePath}/api/agent/tasks/${encodeURIComponent(task.name)}/execute`}
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
<span className="form-label">Execute with multimedia</span>
|
||||
<pre style={{ background: 'var(--color-bg-primary)', padding: 'var(--spacing-sm)', borderRadius: 'var(--radius-md)', fontSize: '0.75rem', fontFamily: "'JetBrains Mono', monospace", whiteSpace: 'pre-wrap', overflow: 'auto' }}>
|
||||
<pre style={{ background: 'var(--color-bg-primary)', padding: 'var(--spacing-sm)', borderRadius: 'var(--radius-md)', fontSize: '0.75rem', fontFamily: 'var(--font-mono)', whiteSpace: 'pre-wrap', overflow: 'auto' }}>
|
||||
{`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() {
|
||||
</div>
|
||||
<div>
|
||||
<span className="form-label">Check job status</span>
|
||||
<pre style={{ background: 'var(--color-bg-primary)', padding: 'var(--spacing-sm)', borderRadius: 'var(--radius-md)', fontSize: '0.75rem', fontFamily: "'JetBrains Mono', monospace", whiteSpace: 'pre-wrap', overflow: 'auto' }}>
|
||||
<pre style={{ background: 'var(--color-bg-primary)', padding: 'var(--spacing-sm)', borderRadius: 'var(--radius-md)', fontSize: '0.75rem', fontFamily: 'var(--font-mono)', whiteSpace: 'pre-wrap', overflow: 'auto' }}>
|
||||
{`curl ${window.location.origin}${basePath}/api/agent/jobs/<job-id>`}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -261,7 +261,7 @@ export default function AgentTaskDetails() {
|
||||
<div key={i} style={{ background: 'var(--color-bg-primary)', borderRadius: 'var(--radius-md)', padding: 'var(--spacing-sm)', marginBottom: 'var(--spacing-sm)' }}>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)', fontSize: '0.8125rem' }}>
|
||||
<span className="badge badge-info">{wh.method || 'POST'}</span>
|
||||
<span style={{ fontFamily: "'JetBrains Mono', monospace" }}>{wh.url}</span>
|
||||
<span style={{ fontFamily: 'var(--font-mono)' }}>{wh.url}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -283,7 +283,7 @@ export default function AgentTaskDetails() {
|
||||
<tbody>
|
||||
{jobHistory.map(job => (
|
||||
<tr key={job.id}>
|
||||
<td style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }}>
|
||||
<td style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }}>
|
||||
{job.id?.slice(0, 12)}...
|
||||
</td>
|
||||
<td>{statusBadge(job.status)}</td>
|
||||
@@ -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' }}
|
||||
/>
|
||||
<p style={{ fontSize: '0.75rem', color: 'var(--color-text-muted)', marginTop: 'var(--spacing-xs)' }}>
|
||||
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 && <p style={{ color: 'var(--color-error)', fontSize: '0.75rem', marginTop: 4 }}>{cronError}</p>}
|
||||
<p style={{ fontSize: '0.75rem', color: 'var(--color-text-muted)', marginTop: 'var(--spacing-xs)' }}>
|
||||
@@ -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' }}
|
||||
/>
|
||||
<p style={{ fontSize: '0.75rem', color: 'var(--color-text-muted)', marginTop: 'var(--spacing-xs)' }}>
|
||||
Default parameters used when the cron triggers the task.
|
||||
@@ -437,7 +437,7 @@ export default function AgentTaskDetails() {
|
||||
</div>
|
||||
<div className="form-group" style={{ marginTop: 'var(--spacing-xs)' }}>
|
||||
<label className="form-label">Headers (JSON)</label>
|
||||
<input className="input" value={ms.headers} onChange={(e) => updateMultimediaSource(i, 'headers', e.target.value)} placeholder='{"Authorization": "Bearer ..."}' style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }} />
|
||||
<input className="input" value={ms.headers} onChange={(e) => updateMultimediaSource(i, 'headers', e.target.value)} placeholder='{"Authorization": "Bearer ..."}' style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }} />
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
@@ -479,7 +479,7 @@ export default function AgentTaskDetails() {
|
||||
</div>
|
||||
<div className="form-group" style={{ marginTop: 'var(--spacing-xs)' }}>
|
||||
<label className="form-label">Headers (JSON)</label>
|
||||
<input className="input" value={wh.headers} onChange={(e) => updateWebhook(i, 'headers', e.target.value)} placeholder='{"Content-Type": "application/json"}' style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }} />
|
||||
<input className="input" value={wh.headers} onChange={(e) => updateWebhook(i, 'headers', e.target.value)} placeholder='{"Content-Type": "application/json"}' style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }} />
|
||||
</div>
|
||||
<div className="form-group" style={{ marginTop: 'var(--spacing-xs)' }}>
|
||||
<label className="form-label">Payload Template (Go template syntax)</label>
|
||||
@@ -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' }}
|
||||
/>
|
||||
<p style={{ fontSize: '0.75rem', color: 'var(--color-text-muted)', marginTop: 2 }}>
|
||||
Available: {'{{.Job}}'} {'{{.Task}}'} {'{{.Result}}'} {'{{.Error}}'} {'{{.Status}}'}
|
||||
|
||||
@@ -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',
|
||||
}}
|
||||
|
||||
@@ -591,7 +591,7 @@ function BackendDetail({ backend }) {
|
||||
</BackendDetailRow>
|
||||
<BackendDetailRow label="Tags">
|
||||
{backend.tags?.length > 0 && (
|
||||
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-xs)', flexWrap: 'wrap' }}>
|
||||
{backend.tags.map(tag => (
|
||||
<span key={tag} className="badge badge-info" style={{ fontSize: '0.6875rem' }}>{tag}</span>
|
||||
))}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -241,7 +241,7 @@ function SingleMetricChart({ data, valueKey, label, color, formatValue, events }
|
||||
return (
|
||||
<div>
|
||||
<div style={{ fontSize: '0.8125rem', fontWeight: 600, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ display: 'inline-block', width: 12, height: 3, background: color, borderRadius: 2 }} />
|
||||
<span style={{ display: 'inline-block', width: 12, height: 3, background: color, borderRadius: "var(--radius-sm)" }} />
|
||||
{label}
|
||||
</div>
|
||||
<svg
|
||||
@@ -308,9 +308,9 @@ function ChartsGrid({ events }) {
|
||||
|
||||
return (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--spacing-md)', marginBottom: 'var(--spacing-md)' }}>
|
||||
<SingleMetricChart data={lossData} valueKey="loss" label="Training Loss" color="#3b82f6" events={events} />
|
||||
<SingleMetricChart data={lossData} valueKey="loss" label="Training Loss" color="var(--color-data-7)" events={events} />
|
||||
{evalData.length >= 1 ? (
|
||||
<SingleMetricChart data={evalData} valueKey="eval_loss" label="Eval Loss" color="#ef4444" events={events} />
|
||||
<SingleMetricChart data={evalData} valueKey="eval_loss" label="Eval Loss" color="var(--color-data-2)" events={events} />
|
||||
) : (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--color-bg-secondary)', borderRadius: 'var(--radius-sm)', minHeight: 120 }}>
|
||||
<span style={{ fontSize: '0.8125rem', color: 'var(--color-text-muted)' }}>
|
||||
@@ -319,8 +319,8 @@ function ChartsGrid({ events }) {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<SingleMetricChart data={lrData} valueKey="learning_rate" label="Learning Rate" color="#8b5cf6" formatValue={fmtExp} events={events} />
|
||||
<SingleMetricChart data={gradNormData} valueKey="grad_norm" label="Gradient Norm" color="#f97316" events={events} />
|
||||
<SingleMetricChart data={lrData} valueKey="learning_rate" label="Learning Rate" color="var(--color-data-3)" formatValue={fmtExp} events={events} />
|
||||
<SingleMetricChart data={gradNormData} valueKey="grad_norm" label="Gradient Norm" color="var(--color-data-6)" events={events} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -483,26 +483,26 @@ function CheckpointsPanel({ job, onResume, onExportCheckpoint }) {
|
||||
<table style={{ width: '100%', fontSize: '0.8125rem', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '1px solid var(--color-border-subtle)', textAlign: 'left' }}>
|
||||
<th style={{ padding: '4px 8px' }}>Step</th>
|
||||
<th style={{ padding: '4px 8px' }}>Epoch</th>
|
||||
<th style={{ padding: '4px 8px' }}>Loss</th>
|
||||
<th style={{ padding: '4px 8px' }}>Created</th>
|
||||
<th style={{ padding: '4px 8px' }}>Path</th>
|
||||
<th style={{ padding: '4px 8px' }}>Actions</th>
|
||||
<th style={{ padding: 'var(--spacing-xs) var(--spacing-sm)' }}>Step</th>
|
||||
<th style={{ padding: 'var(--spacing-xs) var(--spacing-sm)' }}>Epoch</th>
|
||||
<th style={{ padding: 'var(--spacing-xs) var(--spacing-sm)' }}>Loss</th>
|
||||
<th style={{ padding: 'var(--spacing-xs) var(--spacing-sm)' }}>Created</th>
|
||||
<th style={{ padding: 'var(--spacing-xs) var(--spacing-sm)' }}>Path</th>
|
||||
<th style={{ padding: 'var(--spacing-xs) var(--spacing-sm)' }}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{checkpoints.map(cp => (
|
||||
<tr key={cp.path} style={{ borderBottom: '1px solid var(--color-border-subtle)' }}>
|
||||
<td style={{ padding: '4px 8px' }}>{cp.step}</td>
|
||||
<td style={{ padding: '4px 8px' }}>{cp.epoch?.toFixed(2)}</td>
|
||||
<td style={{ padding: '4px 8px' }}>{cp.loss?.toFixed(4)}</td>
|
||||
<td style={{ padding: '4px 8px' }}>{cp.created_at}</td>
|
||||
<td style={{ padding: '4px 8px', maxWidth: '200px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={cp.path}>
|
||||
<td style={{ padding: 'var(--spacing-xs) var(--spacing-sm)' }}>{cp.step}</td>
|
||||
<td style={{ padding: 'var(--spacing-xs) var(--spacing-sm)' }}>{cp.epoch?.toFixed(2)}</td>
|
||||
<td style={{ padding: 'var(--spacing-xs) var(--spacing-sm)' }}>{cp.loss?.toFixed(4)}</td>
|
||||
<td style={{ padding: 'var(--spacing-xs) var(--spacing-sm)' }}>{cp.created_at}</td>
|
||||
<td style={{ padding: 'var(--spacing-xs) var(--spacing-sm)', maxWidth: '200px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={cp.path}>
|
||||
{cp.path} <CopyButton text={cp.path} />
|
||||
</td>
|
||||
<td style={{ padding: '4px 8px', whiteSpace: 'nowrap' }}>
|
||||
<button className="btn" style={{ fontSize: '0.7rem', padding: '2px 6px', marginRight: '4px' }}
|
||||
<td style={{ padding: 'var(--spacing-xs) var(--spacing-sm)', whiteSpace: 'nowrap' }}>
|
||||
<button className="btn" style={{ fontSize: '0.7rem', padding: '2px 6px', marginRight: 'var(--spacing-xs)' }}
|
||||
onClick={() => onResume(cp)} title="Resume training from this checkpoint">
|
||||
<i className="fas fa-play" /> Resume
|
||||
</button>
|
||||
@@ -1269,7 +1269,7 @@ export default function FineTune() {
|
||||
onChange={e => setCustomRewardCode(e.target.value)}
|
||||
placeholder={"return [1.0 if '<think>' 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' }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)' }}>
|
||||
@@ -1404,7 +1404,7 @@ export default function FineTune() {
|
||||
<div
|
||||
onClick={() => 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,
|
||||
}}
|
||||
|
||||
@@ -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() {
|
||||
)}
|
||||
|
||||
<div className="form-group">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '4px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 'var(--spacing-xs)' }}>
|
||||
<label className="form-label" style={{ marginBottom: 0 }}>
|
||||
Model URI
|
||||
</label>
|
||||
<a href="https://huggingface.co/models?sort=trending" target="_blank" rel="noreferrer"
|
||||
className="btn btn-secondary" style={{ fontSize: '0.7rem', padding: '3px 8px' }}>
|
||||
Browse models on HF <i className="fas fa-external-link-alt" aria-hidden="true" style={{ marginLeft: '4px' }} />
|
||||
Browse models on HF <i className="fas fa-external-link-alt" aria-hidden="true" style={{ marginLeft: 'var(--spacing-xs)' }} />
|
||||
</a>
|
||||
</div>
|
||||
<input
|
||||
@@ -593,9 +593,9 @@ export default function ImportModel() {
|
||||
<i className={fmt.icon} aria-hidden="true" style={{ color: fmt.color }} />
|
||||
{fmt.title}
|
||||
</h4>
|
||||
<div style={{ paddingLeft: '20px', fontSize: '0.75rem', fontFamily: 'monospace' }}>
|
||||
<div style={{ paddingLeft: '20px', fontSize: '0.75rem', fontFamily: 'var(--font-mono)' }}>
|
||||
{fmt.examples.map((ex, j) => (
|
||||
<div key={j} style={{ marginBottom: '4px' }}>
|
||||
<div key={j} style={{ marginBottom: 'var(--spacing-xs)' }}>
|
||||
<code style={{ color: 'var(--color-success)' }}>{ex.prefix}</code>
|
||||
<span style={{ color: 'var(--color-text-secondary)' }}>{ex.suffix}</span>
|
||||
<p style={{ color: 'var(--color-text-muted)', marginTop: '1px', fontFamily: 'inherit' }}>{ex.desc}</p>
|
||||
@@ -706,7 +706,7 @@ export default function ImportModel() {
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-sm)', cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={prefs.embeddings} onChange={e => updatePref('embeddings', e.target.checked)} disabled={isSubmitting} />
|
||||
<span style={{ fontSize: '0.875rem', fontWeight: 500, color: 'var(--color-text-secondary)' }}>
|
||||
Embeddings
|
||||
@@ -741,7 +741,7 @@ export default function ImportModel() {
|
||||
<p style={hintStyle}>Enabled parameters for diffusers backend (comma-separated).</p>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-sm)', cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={prefs.cuda} onChange={e => updatePref('cuda', e.target.checked)} disabled={isSubmitting} />
|
||||
<span style={{ fontSize: '0.875rem', fontWeight: 500, color: 'var(--color-text-secondary)' }}>
|
||||
CUDA
|
||||
@@ -831,10 +831,10 @@ export default function ImportModel() {
|
||||
<i className="fas fa-memory" aria-hidden="true" style={{ color: 'var(--color-primary)' }} />
|
||||
<strong>Estimated requirements</strong>
|
||||
{estimate.sizeDisplay && estimate.sizeDisplay !== '0 B' && (
|
||||
<span><i className="fas fa-download" aria-hidden="true" style={{ color: 'var(--color-primary)', marginRight: '4px' }} />Download: {estimate.sizeDisplay}</span>
|
||||
<span><i className="fas fa-download" aria-hidden="true" style={{ color: 'var(--color-primary)', marginRight: 'var(--spacing-xs)' }} />Download: {estimate.sizeDisplay}</span>
|
||||
)}
|
||||
{estimate.vramDisplay && estimate.vramDisplay !== '0 B' && (
|
||||
<span><i className="fas fa-microchip" aria-hidden="true" style={{ color: 'var(--color-primary)', marginRight: '4px' }} />VRAM: {estimate.vramDisplay}</span>
|
||||
<span><i className="fas fa-microchip" aria-hidden="true" style={{ color: 'var(--color-primary)', marginRight: 'var(--spacing-xs)' }} />VRAM: {estimate.vramDisplay}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -939,7 +939,7 @@ export default function ImportModel() {
|
||||
<PowerTabs value={powerTab} onChange={setPowerTab} />
|
||||
</div>
|
||||
<div style={{ padding: 'var(--spacing-md)', borderTop: '1px solid var(--color-border-default)', borderBottom: '1px solid var(--color-border-default)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<h2 style={{ fontSize: '1.125rem', fontWeight: 600, display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<h2 style={{ fontSize: '1.125rem', fontWeight: 600, display: 'flex', alignItems: 'center', gap: 'var(--spacing-sm)' }}>
|
||||
<i className="fas fa-code" aria-hidden="true" style={{ color: 'var(--color-data-3)' }} />
|
||||
YAML Configuration Editor
|
||||
</h2>
|
||||
|
||||
@@ -502,7 +502,7 @@ export default function Manage() {
|
||||
</td>
|
||||
{/* Use Cases */}
|
||||
<td>
|
||||
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-xs)', flexWrap: 'wrap' }}>
|
||||
<a href="#" onClick={(e) => { e.preventDefault(); navigate(`/app/chat/${encodeURIComponent(model.id)}`) }} className="badge badge-info" style={{ textDecoration: 'none', cursor: 'pointer' }}>Chat</a>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -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 }}
|
||||
/>
|
||||
<p style={{ marginTop: '4px', fontSize: '0.75rem', color: 'var(--color-text-muted)' }}>
|
||||
<p style={{ marginTop: 'var(--spacing-xs)', fontSize: '0.75rem', color: 'var(--color-text-muted)' }}>
|
||||
Use letters, numbers, hyphens, underscores, and dots only.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@ function GalleryLoader() {
|
||||
minHeight: '280px', gap: 'var(--spacing-lg)',
|
||||
}}>
|
||||
{/* Animated dots */}
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)' }}>
|
||||
{[0, 1, 2, 3, 4].map(i => (
|
||||
<div key={i} style={{
|
||||
width: 10, height: 10, borderRadius: '50%',
|
||||
@@ -444,7 +444,7 @@ export default function Models() {
|
||||
)}
|
||||
</span>
|
||||
{fit !== null && (
|
||||
<span style={{ fontSize: '0.6875rem', display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
<span style={{ fontSize: '0.6875rem', display: 'flex', alignItems: 'center', gap: 'var(--spacing-xs)' }}>
|
||||
<i className="fas fa-microchip" style={{ color: fit ? 'var(--color-success)' : 'var(--color-error)' }} />
|
||||
<span style={{ color: fit ? 'var(--color-success)' : 'var(--color-error)' }}>
|
||||
{fit ? 'Fits' : 'May not fit'}
|
||||
@@ -595,7 +595,7 @@ function ModelDetail({ model, fit, expandedFiles, setExpandedFiles }) {
|
||||
</DetailRow>
|
||||
<DetailRow label="VRAM">
|
||||
{model.estimated_vram_display && model.estimated_vram_display !== '0 B' ? (
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-sm)' }}>
|
||||
{model.estimated_vram_display}
|
||||
{fit !== null && (
|
||||
<span style={{ fontSize: '0.75rem', color: fit ? 'var(--color-success)' : 'var(--color-error)' }}>
|
||||
@@ -610,7 +610,7 @@ function ModelDetail({ model, fit, expandedFiles, setExpandedFiles }) {
|
||||
</DetailRow>
|
||||
<DetailRow label="Tags">
|
||||
{model.tags?.length > 0 && (
|
||||
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-xs)', flexWrap: 'wrap' }}>
|
||||
{model.tags.map(tag => (
|
||||
<span key={tag} className="badge badge-info" style={{ fontSize: '0.6875rem' }}>{tag}</span>
|
||||
))}
|
||||
@@ -651,17 +651,17 @@ function ModelDetail({ model, fit, expandedFiles, setExpandedFiles }) {
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.75rem' }}>
|
||||
<thead>
|
||||
<tr style={{ background: 'var(--color-bg-tertiary)' }}>
|
||||
<th style={{ padding: '4px 8px', textAlign: 'left', fontWeight: 500 }}>Filename</th>
|
||||
<th style={{ padding: '4px 8px', textAlign: 'left', fontWeight: 500 }}>URI</th>
|
||||
<th style={{ padding: '4px 8px', textAlign: 'left', fontWeight: 500 }}>SHA256</th>
|
||||
<th style={{ padding: 'var(--spacing-xs) var(--spacing-sm)', textAlign: 'left', fontWeight: 500 }}>Filename</th>
|
||||
<th style={{ padding: 'var(--spacing-xs) var(--spacing-sm)', textAlign: 'left', fontWeight: 500 }}>URI</th>
|
||||
<th style={{ padding: 'var(--spacing-xs) var(--spacing-sm)', textAlign: 'left', fontWeight: 500 }}>SHA256</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{files.map((f, i) => (
|
||||
<tr key={i} style={{ borderTop: '1px solid var(--color-border-subtle)' }}>
|
||||
<td style={{ padding: '4px 8px', fontFamily: 'monospace' }}>{f.filename || '—'}</td>
|
||||
<td style={{ padding: '4px 8px', wordBreak: 'break-all', maxWidth: 300 }}>{f.uri || '—'}</td>
|
||||
<td style={{ padding: '4px 8px', fontFamily: 'monospace', fontSize: '0.6875rem', color: 'var(--color-text-muted)' }}>
|
||||
<td style={{ padding: 'var(--spacing-xs) var(--spacing-sm)', fontFamily: 'var(--font-mono)' }}>{f.filename || '—'}</td>
|
||||
<td style={{ padding: 'var(--spacing-xs) var(--spacing-sm)', wordBreak: 'break-all', maxWidth: 300 }}>{f.uri || '—'}</td>
|
||||
<td style={{ padding: 'var(--spacing-xs) var(--spacing-sm)', fontFamily: 'var(--font-mono)', fontSize: '0.6875rem', color: 'var(--color-text-muted)' }}>
|
||||
{f.sha256 ? f.sha256.substring(0, 16) + '...' : '—'}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -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',
|
||||
}}
|
||||
|
||||
@@ -92,7 +92,7 @@ function CommandBlock({ command, addToast }) {
|
||||
<pre style={{
|
||||
background: 'var(--color-bg-primary)', padding: 'var(--spacing-md)',
|
||||
paddingRight: 'var(--spacing-xl)', borderRadius: 'var(--radius-md)',
|
||||
fontSize: '0.8125rem', fontFamily: "'JetBrains Mono', monospace",
|
||||
fontSize: '0.8125rem', fontFamily: 'var(--font-mono)',
|
||||
whiteSpace: 'pre-wrap', wordBreak: 'break-all',
|
||||
color: 'var(--color-warning)', overflow: 'auto',
|
||||
border: '1px solid var(--color-border-subtle)',
|
||||
@@ -104,7 +104,7 @@ function CommandBlock({ command, addToast }) {
|
||||
style={{
|
||||
position: 'absolute', top: 8, right: 8,
|
||||
background: 'var(--color-bg-secondary)', border: '1px solid var(--color-border-subtle)',
|
||||
borderRadius: 'var(--radius-sm)', padding: '4px 8px', cursor: 'pointer',
|
||||
borderRadius: 'var(--radius-sm)', padding: 'var(--spacing-xs) var(--spacing-sm)', cursor: 'pointer',
|
||||
color: 'var(--color-text-secondary)', fontSize: '0.75rem',
|
||||
}}
|
||||
title="Copy"
|
||||
@@ -686,7 +686,7 @@ export default function Nodes() {
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 3, marginTop: 3 }}>
|
||||
{Object.entries(node.labels).slice(0, 5).map(([k, v]) => (
|
||||
<span key={k} className="cell-mono" style={{
|
||||
padding: '1px 5px', borderRadius: 3,
|
||||
padding: '1px 5px', borderRadius: "var(--radius-sm)",
|
||||
background: 'var(--color-bg-tertiary)',
|
||||
border: '1px solid var(--color-border-subtle)',
|
||||
}}>{k}={v}</span>
|
||||
@@ -709,7 +709,7 @@ export default function Nodes() {
|
||||
</td>
|
||||
<td>
|
||||
{hasGPU && totalVRAMStr ? (
|
||||
<div style={{ fontSize: '0.8125rem', fontFamily: "'JetBrains Mono', monospace" }}>
|
||||
<div style={{ fontSize: '0.8125rem', fontFamily: 'var(--font-mono)' }}>
|
||||
{vendorLabel && (
|
||||
<span style={{ color: 'var(--color-text-secondary)', marginRight: 4 }}>{vendorLabel}</span>
|
||||
)}
|
||||
@@ -718,7 +718,7 @@ export default function Nodes() {
|
||||
</span>
|
||||
</div>
|
||||
) : totalRAMStr ? (
|
||||
<div style={{ fontSize: '0.8125rem', fontFamily: "'JetBrains Mono', monospace" }}>
|
||||
<div style={{ fontSize: '0.8125rem', fontFamily: 'var(--font-mono)' }}>
|
||||
<span style={{ color: 'var(--color-text-secondary)', marginRight: 4 }}>CPU</span>
|
||||
<span style={{ color: 'var(--color-text-muted)' }}>
|
||||
{usedRAMStr || '0'} / {totalRAMStr} RAM
|
||||
@@ -729,7 +729,7 @@ export default function Nodes() {
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<span style={{ fontSize: '0.8125rem', fontFamily: "'JetBrains Mono', monospace", color: 'var(--color-text-secondary)' }}>
|
||||
<span style={{ fontSize: '0.8125rem', fontFamily: 'var(--font-mono)', color: 'var(--color-text-secondary)' }}>
|
||||
{timeAgo(node.last_heartbeat)}
|
||||
</span>
|
||||
</td>
|
||||
@@ -802,7 +802,7 @@ export default function Nodes() {
|
||||
const stCfg = modelStateConfig[m.state] || modelStateConfig.idle
|
||||
return (
|
||||
<tr key={m.id || m.model_name}>
|
||||
<td style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }}>
|
||||
<td style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }}>
|
||||
{m.model_name}
|
||||
</td>
|
||||
<td>
|
||||
@@ -814,7 +814,7 @@ export default function Nodes() {
|
||||
{m.state}
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }}>
|
||||
<td style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }}>
|
||||
{m.in_flight ?? 0}
|
||||
</td>
|
||||
<td>
|
||||
@@ -878,7 +878,7 @@ export default function Nodes() {
|
||||
<tbody>
|
||||
{backends.map(b => (
|
||||
<tr key={b.name}>
|
||||
<td style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem' }}>
|
||||
<td style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }}>
|
||||
{b.name}
|
||||
</td>
|
||||
<td>
|
||||
@@ -922,9 +922,9 @@ export default function Nodes() {
|
||||
{node.labels && Object.entries(node.labels).map(([k, v]) => (
|
||||
<span key={k} style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 4,
|
||||
fontSize: '0.75rem', padding: '2px 8px', borderRadius: 4,
|
||||
fontSize: '0.75rem', padding: '2px 8px', borderRadius: "var(--radius-sm)",
|
||||
background: 'var(--color-bg-tertiary)', border: '1px solid var(--color-border-subtle)',
|
||||
fontFamily: "'JetBrains Mono', monospace",
|
||||
fontFamily: 'var(--font-mono)',
|
||||
}}>
|
||||
{k}={v}
|
||||
<button
|
||||
@@ -1021,7 +1021,7 @@ export default function Nodes() {
|
||||
<td style={{ fontWeight: 600, fontSize: '0.875rem' }}>{cfg.model_name}</td>
|
||||
<td>
|
||||
<span style={{
|
||||
display: 'inline-block', fontSize: '0.75rem', padding: '2px 8px', borderRadius: 3,
|
||||
display: 'inline-block', fontSize: '0.75rem', padding: '2px 8px', borderRadius: "var(--radius-sm)",
|
||||
background: 'var(--color-bg-tertiary)', border: `1px solid ${modeColor}`,
|
||||
color: modeColor, fontWeight: 600,
|
||||
}}>{modeLabel}</span>
|
||||
@@ -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]) => (
|
||||
<span key={k} style={{
|
||||
display: 'inline-block', fontSize: '0.75rem', padding: '2px 6px', borderRadius: 3,
|
||||
display: 'inline-block', fontSize: '0.75rem', padding: '2px 6px', borderRadius: "var(--radius-sm)",
|
||||
background: 'var(--color-bg-tertiary)', border: '1px solid var(--color-border-subtle)',
|
||||
fontFamily: "'JetBrains Mono', monospace", marginRight: 4,
|
||||
fontFamily: 'var(--font-mono)', marginRight: 4,
|
||||
}}>{k}={v}</span>
|
||||
))
|
||||
} catch { return <span style={{ color: 'var(--color-text-muted)', fontSize: '0.8125rem' }}>{cfg.node_selector}</span> }
|
||||
})() : <span style={{ color: 'var(--color-text-muted)', fontSize: '0.8125rem' }}>Any node</span>}
|
||||
</td>
|
||||
<td style={{ fontFamily: "'JetBrains Mono', monospace" }}>
|
||||
<td style={{ fontFamily: 'var(--font-mono)' }}>
|
||||
{isAutoScaling ? cfg.min_replicas : '-'}
|
||||
</td>
|
||||
<td style={{ fontFamily: "'JetBrains Mono', monospace" }}>
|
||||
<td style={{ fontFamily: 'var(--font-mono)' }}>
|
||||
{isAutoScaling ? (cfg.max_replicas || 'no limit') : '-'}
|
||||
</td>
|
||||
<td style={{ textAlign: 'right' }}>
|
||||
|
||||
@@ -24,7 +24,7 @@ function NodeCard({ node, label, iconColor, iconBg }) {
|
||||
</div>
|
||||
<div>
|
||||
<h4 style={{ fontSize: '0.875rem', fontWeight: 600 }}>{label}</h4>
|
||||
<p style={{ fontSize: '0.75rem', fontFamily: "'JetBrains Mono', monospace", color: 'var(--color-text-secondary)', wordBreak: 'break-all' }}>
|
||||
<p style={{ fontSize: '0.75rem', fontFamily: 'var(--font-mono)', color: 'var(--color-text-secondary)', wordBreak: 'break-all' }}>
|
||||
{node.id}
|
||||
</p>
|
||||
</div>
|
||||
@@ -68,7 +68,7 @@ function CommandBlock({ command, addToast }) {
|
||||
<pre style={{
|
||||
background: 'var(--color-bg-primary)', padding: 'var(--spacing-md)',
|
||||
paddingRight: 'var(--spacing-xl)', borderRadius: 'var(--radius-md)',
|
||||
fontSize: '0.8125rem', fontFamily: "'JetBrains Mono', monospace",
|
||||
fontSize: '0.8125rem', fontFamily: 'var(--font-mono)',
|
||||
whiteSpace: 'pre-wrap', wordBreak: 'break-all',
|
||||
color: 'var(--color-warning)', overflow: 'auto',
|
||||
border: '1px solid var(--color-border-subtle)',
|
||||
@@ -80,7 +80,7 @@ function CommandBlock({ command, addToast }) {
|
||||
style={{
|
||||
position: 'absolute', top: 8, right: 8,
|
||||
background: 'var(--color-bg-secondary)', border: '1px solid var(--color-border-subtle)',
|
||||
borderRadius: 'var(--radius-sm)', padding: '4px 8px', cursor: 'pointer',
|
||||
borderRadius: 'var(--radius-sm)', padding: 'var(--spacing-xs) var(--spacing-sm)', cursor: 'pointer',
|
||||
color: 'var(--color-text-secondary)', fontSize: '0.75rem',
|
||||
}}
|
||||
title="Copy"
|
||||
@@ -327,7 +327,7 @@ export default function P2P() {
|
||||
padding: 'var(--spacing-md)', borderRadius: 'var(--radius-md)',
|
||||
wordBreak: 'break-all', whiteSpace: 'pre-wrap',
|
||||
border: '1px solid var(--color-border-subtle)', cursor: 'pointer',
|
||||
fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem',
|
||||
fontFamily: 'var(--font-mono)', fontSize: '0.8125rem',
|
||||
}}
|
||||
>
|
||||
{token || 'Loading...'}
|
||||
@@ -459,7 +459,7 @@ export default function P2P() {
|
||||
</div>
|
||||
<i className="fas fa-arrow-right" style={{ color: 'var(--color-text-muted)', fontSize: '1rem' }} />
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ display: 'flex', gap: '4px', marginBottom: 'var(--spacing-xs)' }}>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-xs)', marginBottom: 'var(--spacing-xs)' }}>
|
||||
{[1, 2, 3].map(n => (
|
||||
<div key={n} style={{
|
||||
width: 36, height: 36, borderRadius: 'var(--radius-sm)',
|
||||
@@ -607,7 +607,7 @@ export default function P2P() {
|
||||
<span style={{ fontSize: '0.625rem', color: 'var(--color-text-muted)' }}>RPC</span>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ display: 'flex', gap: '4px', marginBottom: 'var(--spacing-xs)' }}>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-xs)', marginBottom: 'var(--spacing-xs)' }}>
|
||||
{['Layer 1-10', 'Layer 11-20', 'Layer 21-30'].map((label, i) => (
|
||||
<div key={i} style={{ textAlign: 'center' }}>
|
||||
<div style={{
|
||||
@@ -687,7 +687,7 @@ export default function P2P() {
|
||||
<span style={{ fontSize: '0.625rem', color: 'var(--color-text-muted)' }}>Ring / JACCL</span>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ display: 'flex', gap: '4px', marginBottom: 'var(--spacing-xs)' }}>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-xs)', marginBottom: 'var(--spacing-xs)' }}>
|
||||
{['Layers 1-16', 'Layers 17-32'].map((label, i) => (
|
||||
<div key={i} style={{ textAlign: 'center' }}>
|
||||
<div style={{
|
||||
|
||||
@@ -217,8 +217,8 @@ export default function Settings() {
|
||||
return (
|
||||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-xs)', marginBottom: i < resources.gpus.length - 1 ? 4 : 0 }}>
|
||||
<span style={{ color: 'var(--color-text-muted)', minWidth: 60 }}>GPU {i}</span>
|
||||
<div style={{ flex: 1, height: 6, background: 'var(--color-bg-primary)', borderRadius: 3, overflow: 'hidden' }}>
|
||||
<div style={{ width: `${usedPct}%`, height: '100%', background: percentColor(usedPct), borderRadius: 3 }} />
|
||||
<div style={{ flex: 1, height: 6, background: 'var(--color-bg-primary)', borderRadius: "var(--radius-sm)", overflow: 'hidden' }}>
|
||||
<div style={{ width: `${usedPct}%`, height: '100%', background: percentColor(usedPct), borderRadius: "var(--radius-sm)" }} />
|
||||
</div>
|
||||
<span style={{ color: percentColor(usedPct), minWidth: 40, textAlign: 'right' }}>{usedPct}%</span>
|
||||
<span style={{ color: 'var(--color-text-muted)' }}>{formatBytes(gpu.used)} / {formatBytes(gpu.total)}</span>
|
||||
@@ -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 (
|
||||
<>
|
||||
<div style={{ flex: 1, height: 6, background: 'var(--color-bg-primary)', borderRadius: 3, overflow: 'hidden' }}>
|
||||
<div style={{ width: `${usedPct}%`, height: '100%', background: percentColor(usedPct), borderRadius: 3 }} />
|
||||
<div style={{ flex: 1, height: 6, background: 'var(--color-bg-primary)', borderRadius: "var(--radius-sm)", overflow: 'hidden' }}>
|
||||
<div style={{ width: `${usedPct}%`, height: '100%', background: percentColor(usedPct), borderRadius: "var(--radius-sm)" }} />
|
||||
</div>
|
||||
<span style={{ color: percentColor(usedPct), minWidth: 40, textAlign: 'right' }}>{usedPct}%</span>
|
||||
<span style={{ color: 'var(--color-text-muted)' }}>{formatBytes(resources.ram.used)} / {formatBytes(resources.ram.total)}</span>
|
||||
@@ -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' }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginTop: 'var(--spacing-sm)' }}>
|
||||
@@ -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' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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%' }}
|
||||
/>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)', justifyContent: 'flex-end' }}>
|
||||
<button className="btn btn-secondary" onClick={() => setEditor((e) => ({ ...e, open: false }))}>Cancel</button>
|
||||
@@ -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' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -525,7 +525,7 @@ export default function Talk() {
|
||||
padding: 'var(--spacing-xs)', border: '1px solid var(--color-border)',
|
||||
}}>
|
||||
<div style={{ color: 'var(--color-text-secondary)', marginBottom: 2 }}>{item.label}</div>
|
||||
<div style={{ fontFamily: 'monospace', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{item.value}</div>
|
||||
<div style={{ fontFamily: 'var(--font-mono)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{item.value}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -623,7 +623,7 @@ export default function Talk() {
|
||||
) : (
|
||||
<>
|
||||
<button className="btn" onClick={sendTestTone}
|
||||
style={{ background: 'var(--color-accent)', color: '#fff', border: 'none' }}>
|
||||
style={{ background: 'var(--color-accent)', color: 'var(--color-primary-text)', border: 'none' }}>
|
||||
<i className="fas fa-wave-square" style={{ marginRight: 'var(--spacing-xs)' }} /> Test Tone
|
||||
</button>
|
||||
<button className="btn btn-secondary" onClick={toggleDiagnostics}>
|
||||
@@ -634,7 +634,7 @@ export default function Talk() {
|
||||
</div>
|
||||
{isConnected && (
|
||||
<button className="btn" onClick={disconnect}
|
||||
style={{ background: 'var(--color-error)', color: '#fff', border: 'none' }}>
|
||||
style={{ background: 'var(--color-error)', color: 'var(--color-text-inverse)', border: 'none' }}>
|
||||
<i className="fas fa-plug-circle-xmark" style={{ marginRight: 'var(--spacing-xs)' }} /> Disconnect
|
||||
</button>
|
||||
)}
|
||||
@@ -660,12 +660,12 @@ export default function Talk() {
|
||||
<div>
|
||||
<p style={{ fontSize: '0.6875rem', color: 'var(--color-text-secondary)', marginBottom: 2 }}>Waveform</p>
|
||||
<canvas ref={waveCanvasRef} width={400} height={120}
|
||||
style={{ width: '100%', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: '#000' }} />
|
||||
style={{ width: '100%', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'var(--color-surface-sunken)' }} />
|
||||
</div>
|
||||
<div>
|
||||
<p style={{ fontSize: '0.6875rem', color: 'var(--color-text-secondary)', marginBottom: 2 }}>Spectrum (FFT)</p>
|
||||
<canvas ref={specCanvasRef} width={400} height={120}
|
||||
style={{ width: '100%', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: '#000' }} />
|
||||
style={{ width: '100%', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'var(--color-surface-sunken)' }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -684,7 +684,7 @@ export default function Talk() {
|
||||
background: 'var(--color-bg-secondary)', borderRadius: 'var(--radius-sm)', padding: 'var(--spacing-xs)',
|
||||
}}>
|
||||
<div style={{ color: 'var(--color-text-secondary)', fontSize: '0.6875rem' }}>{item.label}</div>
|
||||
<div style={{ fontFamily: 'monospace' }}>{item.value}</div>
|
||||
<div style={{ fontFamily: 'var(--font-mono)' }}>{item.value}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -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...'}
|
||||
</pre>
|
||||
|
||||
@@ -102,7 +102,7 @@ function AudioSnippet({ data }) {
|
||||
{metrics.map(m => (
|
||||
<div key={m.label} style={{ background: 'var(--color-bg-secondary)', borderRadius: 'var(--radius-sm)', padding: 'var(--spacing-xs)' }}>
|
||||
<div style={{ color: 'var(--color-text-secondary)' }}>{m.label}</div>
|
||||
<div style={{ fontFamily: 'monospace' }}>{m.value}</div>
|
||||
<div style={{ fontFamily: 'var(--font-mono)' }}>{m.value}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -155,9 +155,9 @@ function DataFields({ data, nested }) {
|
||||
) : (
|
||||
<span style={{ width: 12, flexShrink: 0 }} />
|
||||
)}
|
||||
<span style={{ fontFamily: 'monospace', color: 'var(--color-primary)', flexShrink: 0 }}>{key}</span>
|
||||
<span style={{ fontFamily: 'var(--font-mono)', color: 'var(--color-primary)', flexShrink: 0 }}>{key}</span>
|
||||
{objValue && !expanded && <span style={{ fontSize: '0.75rem', color: 'var(--color-text-secondary)' }}>{fieldSummary(value)}</span>}
|
||||
{!objValue && !large && <span style={{ fontFamily: 'monospace', fontSize: '0.75rem', color: 'var(--color-text-secondary)' }}>{formatValue(value)}</span>}
|
||||
{!objValue && !large && <span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--color-text-secondary)' }}>{formatValue(value)}</span>}
|
||||
{!objValue && large && !expanded && <span style={{ fontSize: '0.75rem', color: 'var(--color-text-secondary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{truncateValue(value, 120)}</span>}
|
||||
</div>
|
||||
{expanded && objValue && (
|
||||
@@ -170,7 +170,7 @@ function DataFields({ data, nested }) {
|
||||
<pre style={{
|
||||
background: 'var(--color-bg-primary)', border: '1px solid var(--color-border)',
|
||||
borderRadius: 'var(--radius-sm)', padding: 'var(--spacing-sm)',
|
||||
fontSize: '0.75rem', fontFamily: 'monospace', whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
||||
fontSize: '0.75rem', fontFamily: 'var(--font-mono)', whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
||||
overflow: 'auto', maxHeight: '50vh', margin: 0,
|
||||
}}>
|
||||
{formatLargeValue(value)}
|
||||
@@ -250,7 +250,7 @@ function ApiTraceDetail({ trace }) {
|
||||
display: 'flex', alignItems: 'center', gap: 'var(--spacing-xs)',
|
||||
}}>
|
||||
<i className="fas fa-exclamation-triangle" style={{ color: 'var(--color-error)' }} />
|
||||
<span style={{ color: 'var(--color-error)', fontSize: '0.8125rem', fontFamily: 'monospace', wordBreak: 'break-all' }}>{trace.error}</span>
|
||||
<span style={{ color: 'var(--color-error)', fontSize: '0.8125rem', fontFamily: 'var(--font-mono)', wordBreak: 'break-all' }}>{trace.error}</span>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--spacing-md)' }}>
|
||||
@@ -259,7 +259,7 @@ function ApiTraceDetail({ trace }) {
|
||||
<pre style={{
|
||||
background: 'var(--color-bg-primary)', border: '1px solid var(--color-border)',
|
||||
borderRadius: 'var(--radius-sm)', padding: 'var(--spacing-sm)',
|
||||
fontSize: '0.75rem', fontFamily: 'monospace', whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
||||
fontSize: '0.75rem', fontFamily: 'var(--font-mono)', whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
||||
overflow: 'auto', maxHeight: '50vh', margin: 0,
|
||||
}}>
|
||||
{decodeTraceBody(trace.request?.body)}
|
||||
@@ -270,7 +270,7 @@ function ApiTraceDetail({ trace }) {
|
||||
<pre style={{
|
||||
background: 'var(--color-bg-primary)', border: '1px solid var(--color-border)',
|
||||
borderRadius: 'var(--radius-sm)', padding: 'var(--spacing-sm)',
|
||||
fontSize: '0.75rem', fontFamily: 'monospace', whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
||||
fontSize: '0.75rem', fontFamily: 'var(--font-mono)', whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
||||
overflow: 'auto', maxHeight: '50vh', margin: 0,
|
||||
}}>
|
||||
{decodeTraceBody(trace.response?.body)}
|
||||
@@ -511,7 +511,7 @@ export default function Traces() {
|
||||
<tr onClick={() => setExpandedRow(expandedRow === i ? null : i)} style={{ cursor: 'pointer' }}>
|
||||
<td><i className={`fas fa-chevron-${expandedRow === i ? 'down' : 'right'}`} style={{ fontSize: '0.7rem' }} /></td>
|
||||
<td><span className="badge badge-info">{trace.request?.method || '-'}</span></td>
|
||||
<td style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: '0.8125rem' }}>{trace.request?.path || '-'}</td>
|
||||
<td style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }}>{trace.request?.path || '-'}</td>
|
||||
<td><span className={`badge ${(trace.response?.status || 0) < 400 ? 'badge-success' : 'badge-error'}`}>{trace.response?.status || '-'}</span></td>
|
||||
<td style={{ textAlign: 'center' }}>
|
||||
{trace.error
|
||||
@@ -552,7 +552,7 @@ export default function Traces() {
|
||||
<td><i className={`fas fa-chevron-${expandedRow === i ? 'down' : 'right'}`} style={{ fontSize: '0.7rem' }} /></td>
|
||||
<td><span style={typeBadgeStyle(trace.type)}>{trace.type || '-'}</span></td>
|
||||
<td style={{ fontSize: '0.8125rem', color: 'var(--color-text-secondary)' }}>{formatTimestamp(trace.timestamp)}</td>
|
||||
<td style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: '0.8125rem' }}>{trace.model_name || '-'}</td>
|
||||
<td style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem' }}>{trace.model_name || '-'}</td>
|
||||
<td style={{ maxWidth: '300px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{trace.summary || '-'}
|
||||
</td>
|
||||
|
||||
@@ -28,7 +28,7 @@ function StatCard({ icon, label, value, muted }) {
|
||||
<i className={icon} style={{ color: 'var(--color-text-muted)', fontSize: '0.75rem' }} />
|
||||
<span style={{ fontSize: '0.6875rem', color: 'var(--color-text-muted)', fontWeight: 500, textTransform: 'uppercase', letterSpacing: '0.03em' }}>{label}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '1.375rem', fontWeight: 700, fontFamily: 'JetBrains Mono, monospace', color: muted ? 'var(--color-text-secondary)' : 'var(--color-text-primary)' }}>
|
||||
<div style={{ fontSize: '1.375rem', fontWeight: 700, fontFamily: 'var(--font-mono)', color: muted ? 'var(--color-text-secondary)' : 'var(--color-text-primary)' }}>
|
||||
{muted ? '~' : ''}{formatNumber(value)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,12 +39,12 @@ function UsageBar({ value, max }) {
|
||||
const pct = max > 0 ? Math.min((value / max) * 100, 100) : 0
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%', height: 6, borderRadius: 3,
|
||||
width: '100%', height: 6, borderRadius: "var(--radius-sm)",
|
||||
background: 'var(--color-bg-primary)',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<div style={{
|
||||
width: `${pct}%`, height: '100%', borderRadius: 3,
|
||||
width: `${pct}%`, height: '100%', borderRadius: "var(--radius-sm)",
|
||||
background: 'var(--color-primary)',
|
||||
transition: 'width 0.3s ease',
|
||||
}} />
|
||||
@@ -342,13 +342,13 @@ function PredictionCards({ predictions, quotaExhaustion, period }) {
|
||||
</div>
|
||||
{q.items.map((item, ii) => (
|
||||
<div key={ii} style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-sm)', marginBottom: 4 }}>
|
||||
<span style={{ minWidth: 70, fontSize: '0.75rem', color: 'var(--color-text-muted)', fontFamily: 'JetBrains Mono, monospace' }}>
|
||||
<span style={{ minWidth: 70, fontSize: '0.75rem', color: 'var(--color-text-muted)', fontFamily: 'var(--font-mono)' }}>
|
||||
{item.label}
|
||||
</span>
|
||||
<div style={{ flex: 1, maxWidth: 200 }}>
|
||||
<UsageBar value={item.current} max={item.max} />
|
||||
</div>
|
||||
<span style={{ fontSize: '0.75rem', fontFamily: 'JetBrains Mono, monospace', color: 'var(--color-text-muted)', minWidth: 100 }}>
|
||||
<span style={{ fontSize: '0.75rem', fontFamily: 'var(--font-mono)', color: 'var(--color-text-muted)', minWidth: 100 }}>
|
||||
{formatNumber(item.current)}/{formatNumber(item.max)}
|
||||
</span>
|
||||
{item.withinLimits ? (
|
||||
@@ -409,12 +409,12 @@ function UsageTimeChart({ data, predictedData, period }) {
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 'var(--spacing-sm)' }}>
|
||||
<span style={{ fontSize: '0.875rem', fontWeight: 600, color: 'var(--color-text-primary)' }}>Tokens over time</span>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-md)', fontSize: '0.6875rem', color: 'var(--color-text-muted)' }}>
|
||||
<span><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: 'var(--color-primary)', marginRight: 4, verticalAlign: 'middle' }} />Prompt</span>
|
||||
<span><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: 'var(--color-primary)', opacity: 0.35, marginRight: 4, verticalAlign: 'middle' }} />Completion</span>
|
||||
<span><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: "var(--radius-sm)", background: 'var(--color-primary)', marginRight: 4, verticalAlign: 'middle' }} />Prompt</span>
|
||||
<span><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: "var(--radius-sm)", background: 'var(--color-primary)', opacity: 0.35, marginRight: 4, verticalAlign: 'middle' }} />Completion</span>
|
||||
{predictedData && predictedData.length > 0 && (
|
||||
<span>
|
||||
<span style={{
|
||||
display: 'inline-block', width: 8, height: 8, borderRadius: 2,
|
||||
display: 'inline-block', width: 8, height: 8, borderRadius: "var(--radius-sm)",
|
||||
border: '1.5px dashed var(--color-primary)', background: 'transparent',
|
||||
marginRight: 4, verticalAlign: 'middle', opacity: 0.6,
|
||||
}} />
|
||||
@@ -432,7 +432,7 @@ function UsageTimeChart({ data, predictedData, period }) {
|
||||
return (
|
||||
<g key={i}>
|
||||
<line x1={0} y1={y} x2={chartW} y2={y} stroke="var(--color-border)" strokeOpacity={0.5} strokeDasharray={i === 0 ? 'none' : '3,3'} />
|
||||
<text x={-8} y={y + 4} textAnchor="end" fontSize="10" fill="var(--color-text-muted)" fontFamily="JetBrains Mono, monospace">
|
||||
<text x={-8} y={y + 4} textAnchor="end" fontSize="10" fill="var(--color-text-muted)" style={{ fontFamily: 'var(--font-mono)' }}>
|
||||
{formatYLabel(t)}
|
||||
</text>
|
||||
</g>
|
||||
@@ -537,7 +537,7 @@ function UsageTimeChart({ data, predictedData, period }) {
|
||||
return (
|
||||
<text key={d.bucket} x={x} y={chartH + 16} textAnchor="middle" fontSize="10"
|
||||
fill={d.predicted ? 'var(--color-text-muted)' : 'var(--color-text-secondary)'}
|
||||
fontFamily="JetBrains Mono, monospace"
|
||||
style={{ fontFamily: 'var(--font-mono)' }}
|
||||
fontStyle={d.predicted ? 'italic' : 'normal'}
|
||||
>
|
||||
{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 }) {
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 'var(--spacing-sm)' }}>
|
||||
<span style={{ fontSize: '0.875rem', fontWeight: 600, color: 'var(--color-text-primary)' }}>Token distribution by model</span>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-md)', fontSize: '0.6875rem', color: 'var(--color-text-muted)' }}>
|
||||
<span><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: 'var(--color-primary)', marginRight: 4, verticalAlign: 'middle' }} />Prompt</span>
|
||||
<span><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: 'var(--color-primary)', opacity: 0.35, marginRight: 4, verticalAlign: 'middle' }} />Completion</span>
|
||||
<span><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: "var(--radius-sm)", background: 'var(--color-primary)', marginRight: 4, verticalAlign: 'middle' }} />Prompt</span>
|
||||
<span><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: "var(--radius-sm)", background: 'var(--color-primary)', opacity: 0.35, marginRight: 4, verticalAlign: 'middle' }} />Completion</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: gap }}>
|
||||
@@ -603,17 +603,17 @@ function ModelDistChart({ rows }) {
|
||||
return (
|
||||
<div key={row.model} style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-sm)' }}>
|
||||
<div style={{
|
||||
width: 120, minWidth: 120, fontSize: '0.75rem', fontFamily: 'JetBrains Mono, monospace',
|
||||
width: 120, minWidth: 120, fontSize: '0.75rem', fontFamily: 'var(--font-mono)',
|
||||
color: 'var(--color-text-secondary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
||||
}} title={row.model}>
|
||||
{row.model}
|
||||
</div>
|
||||
<div style={{ flex: 1, height: barH, background: 'var(--color-bg-primary)', borderRadius: 4, overflow: 'hidden', display: 'flex' }}>
|
||||
<div style={{ flex: 1, height: barH, background: 'var(--color-bg-primary)', borderRadius: "var(--radius-sm)", overflow: 'hidden', display: 'flex' }}>
|
||||
<div style={{ width: `${promptPct}%`, height: '100%', background: 'var(--color-primary)', transition: 'width 0.3s ease' }} />
|
||||
<div style={{ width: `${compPct}%`, height: '100%', background: 'var(--color-primary)', opacity: 0.35, transition: 'width 0.3s ease' }} />
|
||||
</div>
|
||||
<div style={{
|
||||
minWidth: 60, textAlign: 'right', fontSize: '0.75rem', fontFamily: 'JetBrains Mono, monospace',
|
||||
minWidth: 60, textAlign: 'right', fontSize: '0.75rem', fontFamily: 'var(--font-mono)',
|
||||
color: 'var(--color-text-muted)', fontWeight: 600,
|
||||
}}>
|
||||
{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 (
|
||||
<div className="page">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
156
core/http/react-ui/src/utils/cmTheme.js
vendored
156
core/http/react-ui/src/utils/cmTheme.js
vendored
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user