import { Box, Text } from '@hermes/ink' 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 { FloatBox } from './appChrome.js' import { MaskedPrompt } from './maskedPrompt.js' import { ModelPicker } from './modelPicker.js' import { OverlayControls } from './overlayControls.js' import { ApprovalPrompt, ClarifyPrompt, ConfirmPrompt } from './prompts.js' import { SessionPicker } from './sessionPicker.js' import { SkillsHub } from './skillsHub.js' const COMPLETION_WINDOW = 16 export function PromptZone({ cols, onApprovalChoice, onClarifyAnswer, onSecretSubmit, onSudoSubmit }: Pick) { const overlay = useStore($overlayState) const ui = useStore($uiState) if (overlay.approval) { return ( ) } if (overlay.confirm) { const req = overlay.confirm const onConfirm = () => { patchOverlayState({ confirm: null }) req.onConfirm() } const onCancel = () => patchOverlayState({ confirm: null }) return ( ) } if (overlay.clarify) { return ( onClarifyAnswer('')} req={overlay.clarify} t={ui.theme} /> ) } if (overlay.sudo) { return ( ) } if (overlay.secret) { return ( ) } return null } export function FloatingOverlays({ cols, compIdx, completions, onModelSelect, onPickerSelect, pagerPageSize }: Pick) { const { gw } = useGateway() const overlay = useStore($overlayState) const ui = useStore($uiState) const hasAny = overlay.modelPicker || overlay.pager || overlay.picker || overlay.skillsHub || completions.length if (!hasAny) { return null } // Fixed viewport centered on compIdx — previously the slice end was // compIdx + 8 so the dropdown grew from 8 rows to 16 as the user scrolled // down, bouncing the height on every keystroke. const viewportSize = Math.min(COMPLETION_WINDOW, completions.length) const start = Math.max(0, Math.min(compIdx - Math.floor(COMPLETION_WINDOW / 2), completions.length - viewportSize)) return ( {overlay.picker && ( patchOverlayState({ picker: false })} onSelect={onPickerSelect} t={ui.theme} /> )} {overlay.modelPicker && ( patchOverlayState({ modelPicker: false })} onSelect={onModelSelect} sessionId={ui.sid} t={ui.theme} /> )} {overlay.skillsHub && ( patchOverlayState({ skillsHub: false })} t={ui.theme} /> )} {overlay.pager && ( {overlay.pager.title && ( {overlay.pager.title} )} {overlay.pager.lines.slice(overlay.pager.offset, overlay.pager.offset + pagerPageSize).map((line, i) => ( {line} ))} {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)`} )} {!!completions.length && ( {completions.slice(start, start + viewportSize).map((item, i) => { const active = start + i === compIdx return ( {' '} {item.display} {item.meta ? {item.meta} : null} ) })} )} ) }