import { useStore } from '@nanostores/react' import { useEffect, useState } from 'react' import { Button } from '@/components/ui/button' import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { saveMemoryProviderConfig } from '@/hermes' import { useI18n } from '@/i18n' import { localizeMemoryField } from '@/i18n/tr-memory-fields' import { ExternalLink, Loader2, Save, SlidersHorizontal } from '@/lib/icons' import { notify, notifyError } from '@/store/notifications' import { $activeGatewayProfile } from '@/store/profile' import type { MemoryProviderConfig, MemoryProviderField } from '@/types/hermes' import { ListRow } from '../primitives' import { FieldControl, FieldTitle } from './field-control' // Secrets seed blank: values are write-only and blank keeps the stored one. function seedAll(config: MemoryProviderConfig): Record { return Object.fromEntries(config.fields.map(field => [field.key, field.kind === 'secret' ? '' : field.value])) } // Group fields in declared order, preserving first-seen group sequence. function groupFields(fields: MemoryProviderField[]): [string, MemoryProviderField[]][] { const groups: [string, MemoryProviderField[]][] = [] for (const field of fields) { const name = field.group || 'Other' const bucket = groups.find(([key]) => key === name) if (bucket) { bucket[1].push(field) } else { groups.push([name, [field]]) } } return groups } export function ProviderConfigModal({ config, profile = null, provider, open, onOpenChange, onSaved }: { config: MemoryProviderConfig profile?: null | string provider: string open: boolean onOpenChange: (open: boolean) => void onSaved: () => Promise | void }) { const { t, locale } = useI18n() const copy = t.settings.runtime.memory const activeProfile = useStore($activeGatewayProfile) const [values, setValues] = useState>({}) const [seeded, setSeeded] = useState>({}) const [saving, setSaving] = useState(false) // Reseed on open so edits never start from a stale prior-session snapshot. useEffect(() => { if (open) { const seed = seedAll(config) setSeeded(seed) setValues(seed) } }, [open, config]) const save = async () => { // Untouched keys stay unsubmitted; runtime defaults still own their values. const edited = Object.fromEntries(Object.entries(values).filter(([key, value]) => value !== seeded[key])) setSaving(true) try { await saveMemoryProviderConfig(provider, edited, profile) notify({ kind: 'success', title: copy.saved(config.label), message: copy.updated }) await onSaved() onOpenChange(false) } catch (err) { notifyError(err, copy.saveFailed(config.label)) } finally { setSaving(false) } } return ( {copy.fullTitle(config.label)} {copy.fullDescription(config.label, profile ?? activeProfile)} {config.docs_url && ( { event.preventDefault() void window.hermesDesktop?.openExternal?.(config.docs_url) }} rel="noreferrer" target="_blank" > {copy.reference(config.label)} )}
{groupFields(config.fields.map(field => localizeMemoryField(config.name, field, locale))).map( ([group, fields]) => (

{group === 'Other' ? copy.other : group}

{fields.map(field => (
setValues(current => ({ ...current, [field.key]: value }))} value={values[field.key] ?? ''} /> } description={field.description} title={} />
))}
) )}
) }