mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
Imported VS Code themes now carry their integrated-terminal ANSI palette (`terminal.ansi*`), keyed to the painted variant (terminal / darkTerminal). The terminal adopts it when the full base-8 set is present and keeps its VS Code defaults otherwise; withSurface still owns the background, so the pane stays translucent. Pull the command palette and session switcher into a shared top-center HUD (`floating-hud.ts`): no dim/blur backdrop, one compact text + item-padding size, sidebar-label-style section headers (brand-tinted, uppercase), and the themed portal scrollbar.
107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
import { useStore } from '@nanostores/react'
|
|
import { useEffect, useRef } from 'react'
|
|
import { createPortal } from 'react-dom'
|
|
import { useNavigate } from 'react-router-dom'
|
|
|
|
import { sessionTitle } from '@/lib/chat-runtime'
|
|
import { cn } from '@/lib/utils'
|
|
import { $attentionSessionIds, $workingSessionIds } from '@/store/session'
|
|
import { $switcherIndex, $switcherOpen, $switcherSessions, closeSwitcher } from '@/store/session-switcher'
|
|
|
|
import { HUD_ITEM, HUD_POSITION, HUD_SURFACE, HUD_TEXT } from './floating-hud'
|
|
import { sessionRoute } from './routes'
|
|
|
|
// Compact session-switcher HUD — keyboard-driven from `use-keybinds`, rows
|
|
// clickable via mousedown (Ctrl+click on macOS). No Dialog: Tab stays global.
|
|
export function SessionSwitcher() {
|
|
const open = useStore($switcherOpen)
|
|
const sessions = useStore($switcherSessions)
|
|
const index = useStore($switcherIndex)
|
|
const working = useStore($workingSessionIds)
|
|
const attention = useStore($attentionSessionIds)
|
|
const navigate = useNavigate()
|
|
|
|
const activeRef = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
activeRef.current?.scrollIntoView({ block: 'nearest' })
|
|
}, [index, open])
|
|
|
|
if (!open || sessions.length === 0) {
|
|
return null
|
|
}
|
|
|
|
const workingIds = new Set(working)
|
|
const attentionIds = new Set(attention)
|
|
|
|
const pick = (sessionId: string) => {
|
|
closeSwitcher()
|
|
navigate(sessionRoute(sessionId))
|
|
}
|
|
|
|
return createPortal(
|
|
<>
|
|
{/* Transparent click-catcher: click-away closes, but no dim/blur. */}
|
|
<div
|
|
className="fixed inset-0 z-[219]"
|
|
onMouseDown={e => {
|
|
e.preventDefault()
|
|
closeSwitcher()
|
|
}}
|
|
/>
|
|
<div
|
|
className={cn(
|
|
HUD_POSITION,
|
|
HUD_SURFACE,
|
|
'dt-portal-scrollbar z-[220] max-h-[min(22rem,64vh)] w-[min(19rem,calc(100vw-2rem))] select-none overflow-y-auto p-1'
|
|
)}
|
|
>
|
|
{sessions.map((session, i) => {
|
|
const selected = i === index
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex cursor-pointer items-center rounded leading-tight',
|
|
HUD_ITEM,
|
|
HUD_TEXT,
|
|
selected ? 'bg-accent text-accent-foreground' : 'text-(--ui-text-secondary) hover:bg-(--ui-row-hover-background)'
|
|
)}
|
|
key={session.id}
|
|
onMouseDown={e => {
|
|
e.preventDefault()
|
|
pick(session.id)
|
|
}}
|
|
ref={selected ? activeRef : undefined}
|
|
>
|
|
<SwitcherDot attention={attentionIds.has(session.id)} working={workingIds.has(session.id)} />
|
|
<span className="min-w-0 flex-1 truncate">{sessionTitle(session)}</span>
|
|
{i < 9 && (
|
|
<span
|
|
className={cn(
|
|
'shrink-0 font-mono text-[0.625rem] tabular-nums',
|
|
selected ? 'text-accent-foreground/70' : 'text-(--ui-text-quaternary)'
|
|
)}
|
|
>
|
|
⌃{i + 1}
|
|
</span>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</>,
|
|
document.body
|
|
)
|
|
}
|
|
|
|
function SwitcherDot({ attention, working }: { attention: boolean; working: boolean }) {
|
|
return (
|
|
<span
|
|
className={cn(
|
|
'size-1 shrink-0 rounded-full',
|
|
attention ? 'bg-amber-400' : working ? 'animate-pulse bg-(--ui-accent)' : 'bg-(--ui-text-quaternary)/50'
|
|
)}
|
|
/>
|
|
)
|
|
}
|