mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
fix(tui): stabilize skin prompt width
Normalize skin prompt symbols to trimmed single-line text and measure the active prompt width so wide skin glyphs do not wrap or distort the composer.
This commit is contained in:
@@ -44,6 +44,7 @@ describe('input metrics helpers', () => {
|
||||
|
||||
it('reserves gutters on wide panes without starving narrow composer width', () => {
|
||||
expect(stableComposerColumns(100, 3)).toBe(93)
|
||||
expect(stableComposerColumns(100, 5)).toBe(91)
|
||||
expect(stableComposerColumns(10, 3)).toBe(5)
|
||||
expect(stableComposerColumns(6, 3)).toBe(1)
|
||||
})
|
||||
|
||||
@@ -76,6 +76,11 @@ describe('fromSkin', () => {
|
||||
expect(brand.prompt).toBe('$')
|
||||
})
|
||||
|
||||
it('normalizes skin prompt symbols to one trimmed line', () => {
|
||||
expect(fromSkin({}, { prompt_symbol: ' ⚔ ❯ \n' }).brand.prompt).toBe('⚔ ❯')
|
||||
expect(fromSkin({}, { prompt_symbol: '\n\t' }).brand.prompt).toBe(DEFAULT_THEME.brand.prompt)
|
||||
})
|
||||
|
||||
it('defaults for empty skin', () => {
|
||||
expect(fromSkin({}, {}).color).toEqual(DEFAULT_THEME.color)
|
||||
expect(fromSkin({}, {}).brand.icon).toBe(DEFAULT_THEME.brand.icon)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AlternateScreen, Box, NoSelect, ScrollBox, Text } from '@hermes/ink'
|
||||
import { AlternateScreen, Box, NoSelect, ScrollBox, stringWidth, Text } from '@hermes/ink'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { Fragment, memo, useMemo } from 'react'
|
||||
|
||||
@@ -113,8 +113,10 @@ const ComposerPane = memo(function ComposerPane({
|
||||
const ui = useStore($uiState)
|
||||
const isBlocked = useStore($isBlocked)
|
||||
const sh = (composer.inputBuf[0] ?? composer.input).startsWith('!')
|
||||
const pw = sh ? 2 : 3
|
||||
const inputColumns = stableComposerColumns(composer.cols, pw)
|
||||
const promptText = sh ? '$' : ui.theme.brand.prompt
|
||||
const promptLabel = `${promptText} `
|
||||
const promptWidth = Math.max(1, stringWidth(promptLabel))
|
||||
const inputColumns = stableComposerColumns(composer.cols, promptWidth)
|
||||
const inputHeight = inputVisualHeight(composer.input, inputColumns)
|
||||
|
||||
return (
|
||||
@@ -158,8 +160,8 @@ const ComposerPane = memo(function ComposerPane({
|
||||
<>
|
||||
{composer.inputBuf.map((line, i) => (
|
||||
<Box key={i}>
|
||||
<Box width={3}>
|
||||
<Text color={ui.theme.color.muted}>{i === 0 ? `${ui.theme.brand.prompt} ` : ' '}</Text>
|
||||
<Box width={promptWidth}>
|
||||
<Text color={ui.theme.color.muted}>{i === 0 ? promptLabel : ' '.repeat(promptWidth)}</Text>
|
||||
</Box>
|
||||
|
||||
<Text color={ui.theme.color.text}>{line || ' '}</Text>
|
||||
@@ -167,12 +169,12 @@ const ComposerPane = memo(function ComposerPane({
|
||||
))}
|
||||
|
||||
<Box position="relative">
|
||||
<Box width={pw}>
|
||||
<Box width={promptWidth}>
|
||||
{sh ? (
|
||||
<Text color={ui.theme.color.shellDollar}>$ </Text>
|
||||
<Text color={ui.theme.color.shellDollar}>{promptLabel}</Text>
|
||||
) : (
|
||||
<Text bold color={ui.theme.color.prompt}>
|
||||
{composer.inputBuf.length ? ' ' : `${ui.theme.brand.prompt} `}
|
||||
{composer.inputBuf.length ? ' '.repeat(promptWidth) : promptLabel}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -88,6 +88,14 @@ const BRAND: ThemeBrand = {
|
||||
helpHeader: '(^_^)? Commands'
|
||||
}
|
||||
|
||||
const cleanPromptSymbol = (s: string | undefined, fallback: string) => {
|
||||
const cleaned = String(s ?? '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
|
||||
return cleaned || fallback
|
||||
}
|
||||
|
||||
export const DARK_THEME: Theme = {
|
||||
color: {
|
||||
primary: '#FFD700',
|
||||
@@ -254,7 +262,7 @@ export function fromSkin(
|
||||
brand: {
|
||||
name: branding.agent_name ?? d.brand.name,
|
||||
icon: d.brand.icon,
|
||||
prompt: branding.prompt_symbol ?? d.brand.prompt,
|
||||
prompt: cleanPromptSymbol(branding.prompt_symbol, d.brand.prompt),
|
||||
welcome: branding.welcome ?? d.brand.welcome,
|
||||
goodbye: branding.goodbye ?? d.brand.goodbye,
|
||||
tool: toolPrefix || d.brand.tool,
|
||||
|
||||
Reference in New Issue
Block a user