fix(web): compact terminal tool cards by default (#601)

This commit is contained in:
junes
2026-05-09 09:00:18 +08:00
committed by GitHub
parent 3eac3456b4
commit 3a6574f2b9
12 changed files with 278 additions and 7 deletions
@@ -0,0 +1,32 @@
import { beforeEach, describe, expect, it } from 'vitest'
import {
DEFAULT_TERMINAL_TOOL_DISPLAY_MODE,
getInitialTerminalToolDisplayMode,
getTerminalToolDisplayModeOptions,
} from './useTerminalToolDisplayMode'
describe('useTerminalToolDisplayMode helpers', () => {
beforeEach(() => {
window.localStorage.clear()
})
it('returns the allowed terminal tool display options', () => {
expect(getTerminalToolDisplayModeOptions()).toEqual([
{ value: 'compact', labelKey: 'settings.chat.terminalToolDisplay.compact' },
{ value: 'detailed', labelKey: 'settings.chat.terminalToolDisplay.detailed' },
])
})
it('falls back to the default display mode for missing or invalid storage values', () => {
expect(getInitialTerminalToolDisplayMode()).toBe(DEFAULT_TERMINAL_TOOL_DISPLAY_MODE)
window.localStorage.setItem('hapi-terminal-tool-display-mode', 'invalid')
expect(getInitialTerminalToolDisplayMode()).toBe(DEFAULT_TERMINAL_TOOL_DISPLAY_MODE)
})
it('reads a valid stored terminal tool display mode', () => {
window.localStorage.setItem('hapi-terminal-tool-display-mode', 'detailed')
expect(getInitialTerminalToolDisplayMode()).toBe('detailed')
})
})
@@ -0,0 +1,99 @@
import { useCallback, useEffect, useState } from 'react'
export type TerminalToolDisplayMode = 'compact' | 'detailed'
export const DEFAULT_TERMINAL_TOOL_DISPLAY_MODE: TerminalToolDisplayMode = 'compact'
export function getTerminalToolDisplayModeOptions(): ReadonlyArray<{ value: TerminalToolDisplayMode; labelKey: string }> {
return [
{ value: 'compact', labelKey: 'settings.chat.terminalToolDisplay.compact' },
{ value: 'detailed', labelKey: 'settings.chat.terminalToolDisplay.detailed' },
]
}
function getTerminalToolDisplayModeStorageKey(): string {
return 'hapi-terminal-tool-display-mode'
}
function isBrowser(): boolean {
return typeof window !== 'undefined' && typeof document !== 'undefined'
}
function safeGetItem(key: string): string | null {
if (!isBrowser()) {
return null
}
try {
return localStorage.getItem(key)
} catch {
return null
}
}
function safeSetItem(key: string, value: string): void {
if (!isBrowser()) {
return
}
try {
localStorage.setItem(key, value)
} catch {
// Ignore storage errors
}
}
function safeRemoveItem(key: string): void {
if (!isBrowser()) {
return
}
try {
localStorage.removeItem(key)
} catch {
// Ignore storage errors
}
}
function parseTerminalToolDisplayMode(raw: string | null): TerminalToolDisplayMode {
if (raw === 'compact' || raw === 'detailed') {
return raw
}
return DEFAULT_TERMINAL_TOOL_DISPLAY_MODE
}
export function getInitialTerminalToolDisplayMode(): TerminalToolDisplayMode {
return parseTerminalToolDisplayMode(safeGetItem(getTerminalToolDisplayModeStorageKey()))
}
export function useTerminalToolDisplayMode(): {
terminalToolDisplayMode: TerminalToolDisplayMode
setTerminalToolDisplayMode: (mode: TerminalToolDisplayMode) => void
} {
const [terminalToolDisplayMode, setTerminalToolDisplayModeState] = useState<TerminalToolDisplayMode>(getInitialTerminalToolDisplayMode)
useEffect(() => {
if (!isBrowser()) {
return
}
const onStorage = (event: StorageEvent) => {
if (event.key !== getTerminalToolDisplayModeStorageKey()) {
return
}
setTerminalToolDisplayModeState(parseTerminalToolDisplayMode(event.newValue))
}
window.addEventListener('storage', onStorage)
return () => window.removeEventListener('storage', onStorage)
}, [])
const setTerminalToolDisplayMode = useCallback((mode: TerminalToolDisplayMode) => {
setTerminalToolDisplayModeState(mode)
if (mode === DEFAULT_TERMINAL_TOOL_DISPLAY_MODE) {
safeRemoveItem(getTerminalToolDisplayModeStorageKey())
} else {
safeSetItem(getTerminalToolDisplayModeStorageKey(), mode)
}
}, [])
return { terminalToolDisplayMode, setTerminalToolDisplayMode }
}