feat(web): configure session preview limit (#629)

This commit is contained in:
NightWatcher314
2026-05-17 11:52:30 +08:00
committed by GitHub
parent 86780e94d0
commit 7bb7c7d91a
7 changed files with 322 additions and 4 deletions
+18
View File
@@ -51,6 +51,13 @@ vi.mock('@/hooks/useTerminalToolDisplayMode', () => ({
],
}))
vi.mock('@/hooks/useSessionPreviewLimit', () => ({
MIN_SESSION_PREVIEW_LIMIT: 1,
MAX_SESSION_PREVIEW_LIMIT: 99,
normalizeSessionPreviewLimit: (value: number) => Number.isInteger(value) ? Math.min(99, Math.max(1, value)) : 8,
useSessionPreviewLimit: () => ({ sessionPreviewLimit: 8, setSessionPreviewLimit: vi.fn() }),
}))
vi.mock('@/hooks/useChatSurfaceColors', () => ({
useChatSurfaceColors: () => ({
toolGroupBackground: 'default',
@@ -170,6 +177,9 @@ describe('SettingsPage', () => {
const calledKeys = spyT.mock.calls.map((call) => call[0])
expect(calledKeys).toContain('settings.display.appearance')
expect(calledKeys).toContain('settings.display.appearance.system')
expect(calledKeys).toContain('settings.display.sessionPreviewLimit')
expect(calledKeys).toContain('settings.display.sessionPreviewLimit.decrease')
expect(calledKeys).toContain('settings.display.sessionPreviewLimit.increase')
})
it('renders the Terminal Font Size setting', () => {
@@ -178,6 +188,14 @@ describe('SettingsPage', () => {
expect(screen.getAllByText('13px').length).toBeGreaterThanOrEqual(1)
})
it('renders the Session Preview Limit setting', () => {
renderWithProviders(<SettingsPage />)
expect(screen.getAllByText('Sessions Before Folding').length).toBeGreaterThanOrEqual(1)
expect(screen.getByLabelText('Sessions Before Folding')).toHaveValue(8)
expect(screen.getAllByLabelText('Show fewer sessions before folding').length).toBeGreaterThanOrEqual(1)
expect(screen.getAllByLabelText('Show more sessions before folding').length).toBeGreaterThanOrEqual(1)
})
it('renders the Enter Key setting', () => {
renderWithProviders(<SettingsPage />)
expect(screen.getAllByText('Enter Key').length).toBeGreaterThanOrEqual(1)
+133
View File
@@ -6,6 +6,12 @@ import { getFontScaleOptions, useFontScale, type FontScale } from '@/hooks/useFo
import { getTerminalFontSizeOptions, useTerminalFontSize, type TerminalFontSize } from '@/hooks/useTerminalFontSize'
import { getComposerEnterBehaviorOptions, useComposerEnterBehavior, type ComposerEnterBehavior } from '@/hooks/useComposerEnterBehavior'
import { getTerminalToolDisplayModeOptions, useTerminalToolDisplayMode, type TerminalToolDisplayMode } from '@/hooks/useTerminalToolDisplayMode'
import {
MAX_SESSION_PREVIEW_LIMIT,
MIN_SESSION_PREVIEW_LIMIT,
normalizeSessionPreviewLimit,
useSessionPreviewLimit,
} from '@/hooks/useSessionPreviewLimit'
import {
getChatSurfaceColorPickerValue,
getChatSurfaceColorPresetOptions,
@@ -82,6 +88,125 @@ function ChevronDownIcon(props: { className?: string }) {
)
}
function MinusIcon(props: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={props.className}
>
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
)
}
function PlusIcon(props: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={props.className}
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
)
}
function SessionPreviewLimitControl(props: {
label: string
value: number
onChange: (value: number) => void
decreaseLabel: string
increaseLabel: string
}) {
const [draft, setDraft] = useState(String(props.value))
useEffect(() => {
setDraft(String(props.value))
}, [props.value])
const commitDraft = () => {
const parsed = draft.trim() === '' ? props.value : Number(draft)
const next = normalizeSessionPreviewLimit(parsed)
props.onChange(next)
setDraft(String(next))
}
const step = (delta: number) => {
const next = normalizeSessionPreviewLimit(props.value + delta)
props.onChange(next)
setDraft(String(next))
}
return (
<div className="flex w-full items-center justify-between gap-3 px-3 py-3">
<label htmlFor="session-preview-limit" className="text-[var(--app-fg)]">
{props.label}
</label>
<div className="flex h-9 shrink-0 items-center rounded-lg border border-[var(--app-border)] bg-[var(--app-bg)] shadow-sm">
<button
type="button"
onClick={() => step(-1)}
disabled={props.value <= MIN_SESSION_PREVIEW_LIMIT}
aria-label={props.decreaseLabel}
title={props.decreaseLabel}
className="flex h-8 w-8 items-center justify-center rounded-l-lg text-[var(--app-hint)] transition-colors hover:bg-[var(--app-subtle-bg)] hover:text-[var(--app-fg)] disabled:cursor-not-allowed disabled:opacity-40"
>
<MinusIcon className="h-3.5 w-3.5" />
</button>
<input
id="session-preview-limit"
type="number"
inputMode="numeric"
min={MIN_SESSION_PREVIEW_LIMIT}
max={MAX_SESSION_PREVIEW_LIMIT}
value={draft}
onChange={(event) => setDraft(event.target.value)}
onBlur={commitDraft}
onKeyDown={(event) => {
if (event.key === 'Enter') {
event.preventDefault()
commitDraft()
event.currentTarget.blur()
}
if (event.key === 'Escape') {
event.preventDefault()
setDraft(String(props.value))
event.currentTarget.blur()
}
}}
className="h-8 w-14 border-x border-[var(--app-border)] bg-transparent text-center text-sm font-medium tabular-nums text-[var(--app-fg)] outline-none focus:bg-[var(--app-subtle-bg)]"
/>
<button
type="button"
onClick={() => step(1)}
disabled={props.value >= MAX_SESSION_PREVIEW_LIMIT}
aria-label={props.increaseLabel}
title={props.increaseLabel}
className="flex h-8 w-8 items-center justify-center rounded-r-lg text-[var(--app-hint)] transition-colors hover:bg-[var(--app-subtle-bg)] hover:text-[var(--app-fg)] disabled:cursor-not-allowed disabled:opacity-40"
>
<PlusIcon className="h-3.5 w-3.5" />
</button>
</div>
</div>
)
}
function ChatSurfaceColorControl(props: {
label: string
preference: ChatSurfaceColorPreference
@@ -158,6 +283,7 @@ export default function SettingsPage() {
const voiceContainerRef = useRef<HTMLDivElement>(null)
const { fontScale, setFontScale } = useFontScale()
const { terminalFontSize, setTerminalFontSize } = useTerminalFontSize()
const { sessionPreviewLimit, setSessionPreviewLimit } = useSessionPreviewLimit()
const { composerEnterBehavior, setComposerEnterBehavior } = useComposerEnterBehavior()
const { terminalToolDisplayMode, setTerminalToolDisplayMode } = useTerminalToolDisplayMode()
const {
@@ -499,6 +625,13 @@ export default function SettingsPage() {
</div>
)}
</div>
<SessionPreviewLimitControl
label={t('settings.display.sessionPreviewLimit')}
value={sessionPreviewLimit}
onChange={setSessionPreviewLimit}
decreaseLabel={t('settings.display.sessionPreviewLimit.decrease')}
increaseLabel={t('settings.display.sessionPreviewLimit.increase')}
/>
</div>
{/* Chat section */}