mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
perf(ui-tui): narrow overlay subscriptions to focused selectors
Subscribe overlay components to computed theme/session selectors instead of the full UI store so unrelated UI state updates trigger fewer overlay renders.
This commit is contained in:
parent
ee502e5640
commit
00d25595c1
2 changed files with 27 additions and 23 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { atom } from 'nanostores'
|
||||
import { atom, computed } from 'nanostores'
|
||||
|
||||
import { MOUSE_TRACKING } from '../config/env.js'
|
||||
import { ZERO } from '../domain/usage.js'
|
||||
|
|
@ -30,6 +30,9 @@ const buildUiState = (): UiState => ({
|
|||
|
||||
export const $uiState = atom<UiState>(buildUiState())
|
||||
|
||||
export const $uiTheme = computed($uiState, state => state.theme)
|
||||
export const $uiSessionId = computed($uiState, state => state.sid)
|
||||
|
||||
export const getUiState = () => $uiState.get()
|
||||
|
||||
export const patchUiState = (next: Partial<UiState> | ((state: UiState) => UiState)) =>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useStore } from '@nanostores/react'
|
|||
import { useGateway } from '../app/gatewayContext.js'
|
||||
import type { AppOverlaysProps } from '../app/interfaces.js'
|
||||
import { $overlayState, patchOverlayState } from '../app/overlayStore.js'
|
||||
import { $uiState } from '../app/uiStore.js'
|
||||
import { $uiSessionId, $uiTheme } from '../app/uiStore.js'
|
||||
|
||||
import { FloatBox } from './appChrome.js'
|
||||
import { MaskedPrompt } from './maskedPrompt.js'
|
||||
|
|
@ -24,12 +24,12 @@ export function PromptZone({
|
|||
onSudoSubmit
|
||||
}: Pick<AppOverlaysProps, 'cols' | 'onApprovalChoice' | 'onClarifyAnswer' | 'onSecretSubmit' | 'onSudoSubmit'>) {
|
||||
const overlay = useStore($overlayState)
|
||||
const ui = useStore($uiState)
|
||||
const theme = useStore($uiTheme)
|
||||
|
||||
if (overlay.approval) {
|
||||
return (
|
||||
<Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
|
||||
<ApprovalPrompt onChoice={onApprovalChoice} req={overlay.approval} t={ui.theme} />
|
||||
<ApprovalPrompt onChoice={onApprovalChoice} req={overlay.approval} t={theme} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ export function PromptZone({
|
|||
|
||||
return (
|
||||
<Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
|
||||
<ConfirmPrompt onCancel={onCancel} onConfirm={onConfirm} req={req} t={ui.theme} />
|
||||
<ConfirmPrompt onCancel={onCancel} onConfirm={onConfirm} req={req} t={theme} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ export function PromptZone({
|
|||
onAnswer={onClarifyAnswer}
|
||||
onCancel={() => onClarifyAnswer('')}
|
||||
req={overlay.clarify}
|
||||
t={ui.theme}
|
||||
t={theme}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
|
@ -68,7 +68,7 @@ export function PromptZone({
|
|||
if (overlay.sudo) {
|
||||
return (
|
||||
<Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
|
||||
<MaskedPrompt cols={cols} icon="🔐" label="sudo password required" onSubmit={onSudoSubmit} t={ui.theme} />
|
||||
<MaskedPrompt cols={cols} icon="🔐" label="sudo password required" onSubmit={onSudoSubmit} t={theme} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ export function PromptZone({
|
|||
label={overlay.secret.prompt}
|
||||
onSubmit={onSecretSubmit}
|
||||
sub={`for ${overlay.secret.envVar}`}
|
||||
t={ui.theme}
|
||||
t={theme}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
|
@ -101,7 +101,8 @@ export function FloatingOverlays({
|
|||
}: Pick<AppOverlaysProps, 'cols' | 'compIdx' | 'completions' | 'onModelSelect' | 'onPickerSelect' | 'pagerPageSize'>) {
|
||||
const { gw } = useGateway()
|
||||
const overlay = useStore($overlayState)
|
||||
const ui = useStore($uiState)
|
||||
const sid = useStore($uiSessionId)
|
||||
const theme = useStore($uiTheme)
|
||||
|
||||
const hasAny = overlay.modelPicker || overlay.pager || overlay.picker || overlay.skillsHub || completions.length
|
||||
|
||||
|
|
@ -119,40 +120,40 @@ export function FloatingOverlays({
|
|||
return (
|
||||
<Box alignItems="flex-start" bottom="100%" flexDirection="column" left={0} position="absolute" right={0}>
|
||||
{overlay.picker && (
|
||||
<FloatBox color={ui.theme.color.border}>
|
||||
<FloatBox color={theme.color.border}>
|
||||
<SessionPicker
|
||||
gw={gw}
|
||||
onCancel={() => patchOverlayState({ picker: false })}
|
||||
onSelect={onPickerSelect}
|
||||
t={ui.theme}
|
||||
t={theme}
|
||||
/>
|
||||
</FloatBox>
|
||||
)}
|
||||
|
||||
{overlay.modelPicker && (
|
||||
<FloatBox color={ui.theme.color.border}>
|
||||
<FloatBox color={theme.color.border}>
|
||||
<ModelPicker
|
||||
gw={gw}
|
||||
onCancel={() => patchOverlayState({ modelPicker: false })}
|
||||
onSelect={onModelSelect}
|
||||
sessionId={ui.sid}
|
||||
t={ui.theme}
|
||||
sessionId={sid}
|
||||
t={theme}
|
||||
/>
|
||||
</FloatBox>
|
||||
)}
|
||||
|
||||
{overlay.skillsHub && (
|
||||
<FloatBox color={ui.theme.color.border}>
|
||||
<SkillsHub gw={gw} onClose={() => patchOverlayState({ skillsHub: false })} t={ui.theme} />
|
||||
<FloatBox color={theme.color.border}>
|
||||
<SkillsHub gw={gw} onClose={() => patchOverlayState({ skillsHub: false })} t={theme} />
|
||||
</FloatBox>
|
||||
)}
|
||||
|
||||
{overlay.pager && (
|
||||
<FloatBox color={ui.theme.color.border}>
|
||||
<FloatBox color={theme.color.border}>
|
||||
<Box flexDirection="column" paddingX={1} paddingY={1}>
|
||||
{overlay.pager.title && (
|
||||
<Box justifyContent="center" marginBottom={1}>
|
||||
<Text bold color={ui.theme.color.primary}>
|
||||
<Text bold color={theme.color.primary}>
|
||||
{overlay.pager.title}
|
||||
</Text>
|
||||
</Box>
|
||||
|
|
@ -163,7 +164,7 @@ export function FloatingOverlays({
|
|||
))}
|
||||
|
||||
<Box marginTop={1}>
|
||||
<OverlayHint t={ui.theme}>
|
||||
<OverlayHint t={theme}>
|
||||
{overlay.pager.offset + pagerPageSize < overlay.pager.lines.length
|
||||
? `↑↓/jk line · Enter/Space/PgDn page · b/PgUp back · g/G top/bottom · Esc/q close (${Math.min(overlay.pager.offset + pagerPageSize, overlay.pager.lines.length)}/${overlay.pager.lines.length})`
|
||||
: `end · ↑↓/jk · b/PgUp back · g top · Esc/q close (${overlay.pager.lines.length} lines)`}
|
||||
|
|
@ -174,23 +175,23 @@ export function FloatingOverlays({
|
|||
)}
|
||||
|
||||
{!!completions.length && (
|
||||
<FloatBox color={ui.theme.color.primary}>
|
||||
<FloatBox color={theme.color.primary}>
|
||||
<Box flexDirection="column" width={Math.max(28, cols - 6)}>
|
||||
{completions.slice(start, start + viewportSize).map((item, i) => {
|
||||
const active = start + i === compIdx
|
||||
|
||||
return (
|
||||
<Box
|
||||
backgroundColor={active ? ui.theme.color.completionCurrentBg : undefined}
|
||||
backgroundColor={active ? theme.color.completionCurrentBg : undefined}
|
||||
flexDirection="row"
|
||||
key={`${start + i}:${item.text}:${item.display}:${item.meta ?? ''}`}
|
||||
width="100%"
|
||||
>
|
||||
<Text bold color={ui.theme.color.label}>
|
||||
<Text bold color={theme.color.label}>
|
||||
{' '}
|
||||
{item.display}
|
||||
</Text>
|
||||
{item.meta ? <Text color={ui.theme.color.muted}> {item.meta}</Text> : null}
|
||||
{item.meta ? <Text color={theme.color.muted}> {item.meta}</Text> : null}
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue