mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-09-02 12:01:14 +00:00
* 🎨 feat: Color the Context Gauge by Category and Collapse its Breakdown The context window bar becomes a stacked meter — one hue per category — and the breakdown collapses behind a disclosure so the gauge alone is the default view. The collapse choice persists per user. Adds a categorical series scale (`rgb-series-1`…`rgb-series-7`) to the versioned theme registry, so themes and `REACT_APP_THEME_SERIES_*` can retint it. Hues are anchored on LibreChat's own brand tokens; every step was computed rather than picked, by enumerating slot orderings and snapping each step until all gates passed in both modes: worst adjacent CVD ΔE 12.4 light / 13.0 dark (target 8) worst adjacent normal-vision 19.0 light / 19.0 dark (floor 15) contrast all 14 steps ≥ 3:1 on both the popover surface and the meter track Slot order is the colour-vision-deficiency safety mechanism, not cosmetics. Reserved status colors are never reused for series identity, and the circular composer gauge is deliberately untouched — it answers "how close am I to the limit", which stays a status question. - `SegmentedMeter` + `MeterSwatch` land beside `Progress` in `@librechat/client`, owning the 2px surface gaps, rounded ends, the min-width floor, and the hatch. The category-to-slot mapping stays feature-local: the palette is theme data, the mapping is not. - Every present category gets a 2px floor so a 251-token row cannot render as 0.09px; the shortfall comes out of free space, never another category. - Deferred tools keep their family's hue and add a 135° hatch, so a hue never means two things. Segments are reordered to put each deferred pair beside its parent, which is also the adjacency the palette was validated on. - Messages is drawn as a translucent fill with a solid edge: it is the only category the user grows, and the form difference doubles as secondary encoding. - A row carries a swatch if and only if it is a segment. The estimate path knows the total but not the composition, so it keeps a single unsegmented fill. - Usage totals gain a "Totals" heading, and row text lifts to primary ink on hover/focus. - The popover widens 256px → 288px to absorb the chevron and the legend swatches. Guardrails: the series scale is held to the 3:1 mark floor on both surfaces, the app CSS defaults are held in step with the runtime themes, and each slot is asserted to resolve to a Tailwind utility backed by its CSS variable. * 🐛 fix: Address Codex Review on the Segmented Context Gauge Three P2 findings, all confirmed. **Gaps inflated the fill.** Segment widths were percentages of the whole track while `gap-[2px]` was added on top, so the gaps ate into the free-space remainder instead of living inside the filled region. Measured on the real component: a window at 47.2% painted 55.6% full, and the bar read full at ~94%. Each segment now surrenders its share of the gap budget, so fills plus gaps span exactly the used fraction. Same case now paints 50.2%. The residual 3.0pp is the `SEGMENT_MIN` floor doing its job — five sub-pixel categories rounded up to 2px each. That overshoot is deliberate and bounded, it comes out of free space rather than a neighbouring category, and the doc comment now states the magnitude instead of leaving it implicit. **No reference-theme test.** The suite only exercised the bundled token tables, so it could not detect the shared component becoming coupled to LibreChat's values. Adds a deliberately different reference `ThemeDefinition` and asserts the registry accepts it, the values reach the applied CSS variables, and every rendered mark takes its colour from those variables — no literal colours in the tree. `SegmentedMeter.tsx` also joins the shared-primitive colour guardrail. **Series tokens missing from the public maps.** `IThemeVariables` and `IThemeColors` are exported for downstream consumers to type their CSS-variable and Tailwind maps, and would have rejected the new keys. Adds the series entries to both, plus a compile-time guard in the registry so a slot added to one map and missed in another fails the build. The guard deliberately lives in `registry.ts`, not the spec: `tsconfig.json` excludes `*.spec.ts`, so an assertion there is never checked by the build — verified by removing a key from each map in turn and confirming the error. * ✅ fix: Expand the Breakdown in the Context Gauge e2e Specs `e2e/specs/mock/usage.spec.ts` asserts on rows that now sit behind the disclosure, so four tests failed on the collapsed default. My miss — I updated the component spec and never grepped for e2e coverage. `openBreakdown` now expands the detail after opening, so every caller that reads a row keeps working; the helper is idempotent, since a reload restores an already-expanded preference. The one inline `gauge.click()` that duplicated the helper now uses it. Adds the case the regression should have been caught by, and which only e2e can reach: the popover opens to the gauge alone with no detail mounted, expanding reveals the labelled Totals section, and the choice survives a real reload through localStorage without a second click. `e2e/specs/real/usage.spec.ts` reads the totals the same way. It also hovered rather than clicked, which never opened the popover at all — hover surfaces only the compact snapshot tooltip, as the mock spec asserts.
111 lines
3.9 KiB
JavaScript
111 lines
3.9 KiB
JavaScript
// const { fontFamily } = require('tailwindcss/defaultTheme');
|
|
const {
|
|
createTailwindColors,
|
|
} = require('../packages/client/src/theme/utils/createTailwindColors.js');
|
|
const libreChatTailwindPreset = require('../packages/client/tailwind.preset.cjs');
|
|
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: [
|
|
'./src/**/*.{js,jsx,ts,tsx}',
|
|
// Include component library files
|
|
'../packages/client/src/**/*.{js,jsx,ts,tsx}',
|
|
],
|
|
// darkMode: 'class',
|
|
darkMode: ['class'],
|
|
presets: [libreChatTailwindPreset],
|
|
theme: {
|
|
fontFamily: {
|
|
sans: ['Inter', 'sans-serif'],
|
|
mono: ['Roboto Mono', 'monospace'],
|
|
},
|
|
// fontFamily: {
|
|
// sans: ['Söhne', 'sans-serif'],
|
|
// mono: ['Söhne Mono', 'monospace'],
|
|
// },
|
|
extend: {
|
|
width: {
|
|
authPageWidth: '370px',
|
|
},
|
|
keyframes: {
|
|
'accordion-down': {
|
|
from: { height: 0 },
|
|
to: { height: 'var(--radix-accordion-content-height)' },
|
|
},
|
|
'accordion-up': {
|
|
from: { height: 'var(--radix-accordion-content-height)' },
|
|
to: { height: 0 },
|
|
},
|
|
/** Radix Collapsible exposes its own height variable, not the accordion one. */
|
|
'collapsible-down': {
|
|
from: { height: 0 },
|
|
to: { height: 'var(--radix-collapsible-content-height)' },
|
|
},
|
|
'collapsible-up': {
|
|
from: { height: 'var(--radix-collapsible-content-height)' },
|
|
to: { height: 0 },
|
|
},
|
|
'slide-in-right': {
|
|
'0%': { transform: 'translateX(100%)' },
|
|
'100%': { transform: 'translateX(0)' },
|
|
},
|
|
'slide-in-left': {
|
|
'0%': { transform: 'translateX(-100%)' },
|
|
'100%': { transform: 'translateX(0)' },
|
|
},
|
|
'slide-out-left': {
|
|
'0%': { transform: 'translateX(0)' },
|
|
'100%': { transform: 'translateX(-100%)' },
|
|
},
|
|
'slide-out-right': {
|
|
'0%': { transform: 'translateX(0)' },
|
|
'100%': { transform: 'translateX(100%)' },
|
|
},
|
|
'shortcut-shake': {
|
|
'0%, 100%': { transform: 'translateX(0)' },
|
|
'25%': { transform: 'translateX(-3px)' },
|
|
'75%': { transform: 'translateX(3px)' },
|
|
},
|
|
/** Named distinctly: `blink` is already taken by keyframes in style.css. */
|
|
'logo-blink': {
|
|
'0%, 100%': { opacity: '1' },
|
|
'50%': { opacity: '0' },
|
|
},
|
|
'refresh-link-spin': {
|
|
from: { transform: 'rotate(0deg)' },
|
|
to: { transform: 'rotate(360deg)' },
|
|
},
|
|
'reset-spin': {
|
|
from: { transform: 'rotate(0deg)' },
|
|
to: { transform: 'rotate(-360deg)' },
|
|
},
|
|
},
|
|
animation: {
|
|
'fade-in': 'fadeIn 0.5s ease-out forwards',
|
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
|
'accordion-up': 'accordion-up 0.2s ease-out',
|
|
'collapsible-down': 'collapsible-down 0.2s ease-out',
|
|
'collapsible-up': 'collapsible-up 0.2s ease-out',
|
|
'slide-in-right': 'slide-in-right 300ms cubic-bezier(0.25, 0.1, 0.25, 1)',
|
|
'slide-in-left': 'slide-in-left 300ms cubic-bezier(0.25, 0.1, 0.25, 1)',
|
|
'slide-out-left': 'slide-out-left 300ms cubic-bezier(0.25, 0.1, 0.25, 1)',
|
|
'slide-out-right': 'slide-out-right 300ms cubic-bezier(0.25, 0.1, 0.25, 1)',
|
|
'shortcut-shake': 'shortcut-shake 0.25s ease-in-out',
|
|
'logo-blink': 'logo-blink 3s infinite',
|
|
'refresh-link-spin': 'refresh-link-spin 650ms cubic-bezier(0.42, 0, 0.58, 1)',
|
|
'reset-spin': 'reset-spin 500ms cubic-bezier(0.22, 1, 0.36, 1)',
|
|
},
|
|
colors: createTailwindColors(),
|
|
borderRadius: {
|
|
lg: 'var(--radius)',
|
|
md: 'calc(var(--radius) - 2px)',
|
|
sm: 'calc(var(--radius) - 4px)',
|
|
},
|
|
},
|
|
},
|
|
plugins: [
|
|
require('tailwindcss-animate'),
|
|
require('tailwindcss-radix'),
|
|
// require('@tailwindcss/typography'),
|
|
],
|
|
};
|