Files
aiturk-hermes-ide/apps/desktop/src/app/settings/memory/field-control.tsx
T

135 lines
3.8 KiB
TypeScript

import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Switch } from '@/components/ui/switch'
import { Textarea } from '@/components/ui/textarea'
import { Tip } from '@/components/ui/tooltip'
import { useI18n } from '@/i18n'
import { Check, Info } from '@/lib/icons'
import type { MemoryProviderField } from '@/types/hermes'
import { CONTROL_TEXT } from '../constants'
// Fade the placeholder well below set values so example text never reads as data.
const FIELD_INPUT = `font-mono ${CONTROL_TEXT} placeholder:text-muted-foreground/45`
// Field label with an optional info tooltip, shared by the panel and modal rows.
export function FieldTitle({ field }: { field: MemoryProviderField }) {
const { t } = useI18n()
if (!field.info) {
return <>{field.label}</>
}
return (
<span className="inline-flex items-center gap-1.5">
{field.label}
<Tip className="max-w-60 font-normal leading-snug whitespace-normal" label={field.info}>
<Info aria-label={t.settings.runtime.memory.about(field.label)} className="size-3.5 text-muted-foreground/70" />
</Tip>
</span>
)
}
// Values are edited as strings; the backend coerces them to native types.
export function FieldControl({
field,
value,
onChange,
onCommit
}: {
field: MemoryProviderField
value: string
onChange: (value: string) => void
// Present on autosaving surfaces: discrete controls commit on change, text-like
// controls commit on blur. Absent (the modal), edits stay drafts until Save.
onCommit?: (value: string) => void
}) {
const { t } = useI18n()
const set = (next: string) => {
onChange(next)
onCommit?.(next)
}
const commitDraft = onCommit ? () => onCommit(value) : undefined
if (field.kind === 'bool') {
return <Switch checked={value === 'true'} onCheckedChange={checked => set(checked ? 'true' : 'false')} />
}
if (field.kind === 'number') {
return (
<Input
className={FIELD_INPUT}
inputMode="numeric"
onBlur={commitDraft}
onChange={event => onChange(event.target.value)}
placeholder={field.placeholder}
type="number"
value={value}
/>
)
}
if (field.kind === 'json') {
return (
<Textarea
className={FIELD_INPUT}
onBlur={commitDraft}
onChange={event => onChange(event.target.value)}
placeholder={field.placeholder}
spellCheck={false}
value={value}
/>
)
}
if (field.kind === 'select') {
return (
<Select onValueChange={set} value={value}>
<SelectTrigger className={CONTROL_TEXT}>
<SelectValue />
</SelectTrigger>
<SelectContent>
{field.options.map(option => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
)
}
if (field.kind === 'secret') {
return (
<div className="flex flex-col gap-1">
<Input
className={`w-full ${FIELD_INPUT}`}
onBlur={commitDraft}
onChange={event => onChange(event.target.value)}
placeholder={field.is_set ? t.settings.runtime.memory.keepSecret : field.placeholder}
type="password"
value={value}
/>
{field.is_set && (
<span className="inline-flex items-center gap-1 self-start font-mono text-[0.65rem] text-(--ui-text-tertiary)">
<Check className="size-3 text-(--ui-accent-secondary)" />
{t.settings.runtime.memory.set}
</span>
)}
</div>
)
}
return (
<Input
className={FIELD_INPUT}
onBlur={commitDraft}
onChange={event => onChange(event.target.value)}
placeholder={field.placeholder}
value={value}
/>
)
}