mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-29 11:42:04 +00:00
* fix(desktop): unify dialog/overlay buttons on shared Button component
Replace raw <button> action/text controls across the modal layer (boot
failure, install, update, onboarding, clarify, model-visibility,
notifications, gateway menu) with the shared Button + its variants
(text / ghost / icon-xs). Drops the bespoke square-cornered styling so
every dialog matches the app's slightly-rounded button system, and
swaps clarify-tool's hardcoded "Skip" for the existing i18n string.
* feat(desktop): add dev-only dialog gallery for auditing overlays
A code-split, DEV-gated harness (toggle ⌘/Ctrl+Alt+Shift+D) that triggers
every dialog/overlay so their buttons can be eyeballed in one place:
store-driven overlays (boot failure, updates, notifications, sudo/secret)
plus in-place dialogs (confirm, profile create/rename, attach-url, model
picker/visibility, clarify, tool approval). Never ships to production.
* fix(desktop): use Ctrl+Shift+D for dialog gallery (mac-friendly)
The Cmd/Ctrl+Alt+Shift+D chord is impractical on macOS (Option mangles
the keypress). Ctrl+Shift+D is the same chord on every platform and uses
neither Cmd nor Option.
* fix(desktop): stop overriding button icon size to size-4
Action buttons hardcoded size-4 icons, overriding the Button component's
built-in size-3.5. That extra 2px is why boot-failure / onboarding / gateway
buttons looked chunkier than the settings "Apply" (size-3.5 spinner) despite
being the same component+size. Drop the overrides so icons inherit 3.5.
* feat(desktop): add BrandMark, use it in the updates overlay hero
New BrandMark renders the white logo.png on a hardcoded brand-blue tile
(#0000F2 light / #222 dark), replacing the generic Sparkles hero glyph in
the "update available" overlay. Trying it here first to iterate on the look.
NOTE: apps/desktop/public/logo.png is currently a 1x1 placeholder — the tile
renders now; the glyph appears once the real white logo art is dropped in.
* feat(desktop): add real logo.png asset, render it white in BrandMark
logo.png is blue line-art on transparent, so force it white via filter to
read on both the brand-blue (#0000F2) and near-black (#222) tiles. Bump the
glyph to 62% of the tile for the portrait aspect.
* fix(desktop): BrandMark renders logo as-is, no light bg/radius/padding
Drop the white filter, the hardcoded light-mode blue tile, the radius, and
the inner padding. Logo now fills the tile over a transparent surface in
light mode; dark keeps the #222 tile.
* fix(desktop): bump updates-overlay BrandMark to size-16
* feat(desktop): use downscaled karb.webp in BrandMark
Swap the BrandMark glyph to karb.webp, downscaled from 1129x1418/888KB to
254x320/81KB for the hero badge.
* feat(desktop): use nous-girl mark in BrandMark, invert in dark
Key the white background to transparent so only the black line-art remains
(384px/20KB webp). Light mode shows black art; dark mode flips it white via
dark:invert on the #222 tile. Drop the now-unused karb.webp and logo.png.
* fix(desktop): BrandMark uses nous-girl as-is (no transparent/invert)
The dark-mode invert read as a creepy negative. Use the opaque black-on-white
mark unchanged in both themes; drop the white-key, dark:invert, and #222 tile.
* fix(desktop): give BrandMark an explicit white bg tile
* fix(desktop): use nous-girl.jpg directly in BrandMark
* perf(desktop): downscale nous-girl.jpg to 256x256 (466KB -> 19KB)
* style(desktop): bump nous light --theme-secondary to 14% blue
* fix(desktop): outline button is transparent, not chrome-filled
The outline variant used bg-background (the chrome color), so on cards/overlays
with a different surface it rendered as an odd gray-blue fill (visible on the
boot overlay's Repair install / Use local gateway). Make it bg-transparent so
it inherits the surface like a real outline. Reverts the unrelated
--theme-secondary tweak.
* fix(desktop): clean outline button — thin border, no shadow/fill
Drop shadow-xs and the resting fills (light chrome bg, dark bg-input/30) so
outline is just a thin clean border with a subtle hover, in both themes.
* fix(desktop): stop forcing tertiary bg on outline buttons
A global [data-variant='outline'] rule set background: var(--ui-bg-tertiary),
which (attribute-selector specificity) overrode the cva bg-transparent — so
outline buttons always showed the pale tertiary fill on cards/overlays
regardless of the variant classes. Scope that fill to secondary only; outline
is now a true transparent border.
* style(desktop): unified overlay design system + restore #38631 flat-UI
Overlays/dialogs/toasts share a custom shadow-nous (downward-weighted) and
--stroke-nous hairline instead of hard borders: boot-failure, install,
notifications, model-picker, onboarding, prompt-overlays, updates, Dialog.
- button: outline is a 1px inset ring (no fill/shadow); chrome lives in Button
- BrandMark: 256px nous-girl mark replaces sparkle glyphs (updates/onboarding/about)
- onboarding: conditional header, lemniscate-bloom loaders, OTP device-code boxes,
NOUS CONNECTED hero (ascii decode) + cuneiform easter egg, "Begin" matrix exit
- shared LogView + ErrorState; math/ascii loaders over "Loading..." text
- appearance-settings flattened to SegmentedControl/ListRow; keybind-panel on
shadow-nous + text-variant reset
- restore flat-UI clobbered by #38631's stale-squash (4a1907bd1): command-center,
profiles, skills, messaging, cron de-boxed; shared SearchField + PAGE_INSET_X;
profiles back on OverlaySplitLayout; skills tabs+search one row, no row dividers
* refactor(desktop): clean pass — drop dead code, dedupe, fix stale docs
- log-view: drop unused `bare` prop + forwardRef (no caller uses ref)
- install-overlay: drop `stateOverride` (only the removed dev gallery used it)
- profiles: ProfilesViewProps down to { onClose } (drop vestigial section/titlebar)
- onboarding: hoist shared PROVIDER_ROW_CLASS (was duplicated 2x)
- brand-mark / error-state: tighten comments, fix stale AlertCircle reference
150 lines
5.3 KiB
TypeScript
150 lines
5.3 KiB
TypeScript
import { IconLayoutDashboard } from '@tabler/icons-react'
|
|
|
|
import { StatusDot, type StatusTone } from '@/components/status-dot'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Tip } from '@/components/ui/tooltip'
|
|
import { useI18n } from '@/i18n'
|
|
import { Activity, AlertCircle } from '@/lib/icons'
|
|
import type { RuntimeReadinessResult } from '@/lib/runtime-readiness'
|
|
import { cn } from '@/lib/utils'
|
|
import type { StatusResponse } from '@/types/hermes'
|
|
|
|
interface GatewayMenuPanelProps {
|
|
gatewayState: string
|
|
inferenceStatus: RuntimeReadinessResult | null
|
|
logLines: readonly string[]
|
|
onOpenSystem: () => void
|
|
statusSnapshot: StatusResponse | null
|
|
}
|
|
|
|
const PLATFORM_TONE: Record<string, StatusTone> = {
|
|
connected: 'good',
|
|
connecting: 'warn',
|
|
retrying: 'warn',
|
|
pending_restart: 'warn',
|
|
startup_failed: 'bad',
|
|
fatal: 'bad'
|
|
}
|
|
|
|
const prettyState = (state: string) => state.replace(/_/g, ' ').replace(/^./, c => c.toUpperCase())
|
|
|
|
// Strip leading "YYYY-MM-DD HH:MM:SS,mmm " and "[runtime_id] " prefixes from
|
|
// log lines so they don't dominate the display. Full text preserved on hover.
|
|
const TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}[,.\d]*\s+/
|
|
const RUNTIME_BRACKET_RE = /^\[[^\]]+]\s+/
|
|
const trimLogLine = (raw: string) => raw.trim().replace(TIMESTAMP_RE, '').replace(RUNTIME_BRACKET_RE, '')
|
|
|
|
export function GatewayMenuPanel({
|
|
gatewayState,
|
|
inferenceStatus,
|
|
logLines,
|
|
onOpenSystem,
|
|
statusSnapshot
|
|
}: GatewayMenuPanelProps) {
|
|
const { t } = useI18n()
|
|
const copy = t.shell.gatewayMenu
|
|
const gatewayOpen = gatewayState === 'open'
|
|
const gatewayConnecting = gatewayState === 'connecting'
|
|
const inferenceReady = gatewayOpen && inferenceStatus?.ready === true
|
|
|
|
const connectionLabel = gatewayOpen
|
|
? copy.connected
|
|
: gatewayConnecting
|
|
? copy.connecting
|
|
: prettyState(gatewayState || copy.offline)
|
|
|
|
const inferenceLabel = gatewayOpen
|
|
? inferenceStatus?.ready
|
|
? copy.inferenceReady
|
|
: inferenceStatus
|
|
? copy.inferenceNotReady
|
|
: copy.checkingInference
|
|
: copy.disconnected
|
|
|
|
const platforms = Object.entries(statusSnapshot?.gateway_platforms || {}).sort(([l], [r]) => l.localeCompare(r))
|
|
const recentLogs = logLines.slice(-5)
|
|
|
|
return (
|
|
<div className="text-sm">
|
|
<div className="flex items-center justify-between gap-2 px-3 py-2.5">
|
|
<div className="flex min-w-0 items-center gap-2">
|
|
{inferenceReady ? (
|
|
<Activity className="size-3.5 text-primary" />
|
|
) : (
|
|
<AlertCircle className={cn('size-3.5', gatewayOpen ? 'text-amber-600' : 'text-destructive')} />
|
|
)}
|
|
<span className="font-medium">{copy.gateway}</span>
|
|
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
<StatusDot tone={inferenceReady ? 'good' : gatewayOpen ? 'warn' : 'bad'} />
|
|
{inferenceLabel}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center">
|
|
<Tip label={copy.openSystem}>
|
|
<Button
|
|
aria-label={copy.openSystem}
|
|
className="text-muted-foreground hover:text-foreground"
|
|
onClick={onOpenSystem}
|
|
size="icon-sm"
|
|
variant="ghost"
|
|
>
|
|
<IconLayoutDashboard />
|
|
</Button>
|
|
</Tip>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border-t border-border/50 px-3 py-2 text-xs text-muted-foreground">
|
|
<div>{copy.connection(connectionLabel)}</div>
|
|
{inferenceStatus?.reason && <div className="mt-1 line-clamp-3">{inferenceStatus.reason}</div>}
|
|
</div>
|
|
|
|
{recentLogs.length > 0 && (
|
|
<div className="border-t border-border/50 px-3 py-2">
|
|
<SectionLabel>{copy.recentActivity}</SectionLabel>
|
|
<ul className="mt-1.5 space-y-0.5">
|
|
{recentLogs.map((line, index) => (
|
|
<Tip key={`${index}:${line}`} label={line.trim()}>
|
|
<li className="truncate font-mono text-[0.68rem] text-muted-foreground/85">
|
|
{trimLogLine(line) || '\u00A0'}
|
|
</li>
|
|
</Tip>
|
|
))}
|
|
</ul>
|
|
<Button
|
|
className="-ml-2 mt-1.5 font-medium text-muted-foreground"
|
|
onClick={onOpenSystem}
|
|
size="xs"
|
|
type="button"
|
|
variant="text"
|
|
>
|
|
{copy.viewAllLogs}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
{platforms.length > 0 && (
|
|
<div className="border-t border-border/50 px-3 py-2">
|
|
<SectionLabel>{copy.messagingPlatforms}</SectionLabel>
|
|
<ul className="mt-1.5 space-y-1">
|
|
{platforms.map(([name, platform]) => (
|
|
<li className="flex items-center justify-between gap-2 text-xs" key={name}>
|
|
<span className="truncate capitalize">{name}</span>
|
|
<span className="flex items-center gap-1.5 text-[0.66rem] text-muted-foreground">
|
|
<StatusDot tone={PLATFORM_TONE[platform.state] || 'muted'} />
|
|
{prettyState(platform.state)}
|
|
</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function SectionLabel({ children }: { children: string }) {
|
|
return (
|
|
<div className="text-[0.62rem] font-semibold uppercase tracking-[0.14em] text-muted-foreground/80">{children}</div>
|
|
)
|
|
}
|