mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-21 16:18:55 +00:00
feat(memory): per-field info tooltips + name the profile in the full-config modal
Fields can declare a longer 'info' text rendered as an (i) tooltip next to the label in both the panel and the modal; honcho uses it to spell out the session strategy, write frequency, and recall mode semantics. The modal description claimed 'the active profile' without saying which — show the active gateway profile name.
This commit is contained in:
parent
8333b04483
commit
a182ddbf9f
8 changed files with 40 additions and 6 deletions
|
|
@ -9,6 +9,11 @@ vi.mock('@/hermes', () => ({
|
|||
saveMemoryProviderConfig: (provider: string, values: unknown) => saveMemoryProviderConfig(provider, values)
|
||||
}))
|
||||
|
||||
vi.mock('@/store/profile', async () => {
|
||||
const { atom } = await import('nanostores')
|
||||
return { $activeGatewayProfile: atom('default') }
|
||||
})
|
||||
|
||||
vi.mock('@/store/notifications', () => ({
|
||||
notify: vi.fn(),
|
||||
notifyError: vi.fn()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useStore } from '@nanostores/react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
|
@ -15,7 +16,9 @@ import { ExternalLink, Loader2, Save, SlidersHorizontal } from '@/lib/icons'
|
|||
import { notify, notifyError } from '@/store/notifications'
|
||||
import type { MemoryProviderConfig, MemoryProviderField } from '@/types/hermes'
|
||||
|
||||
import { FieldControl } from './field-control'
|
||||
import { $activeGatewayProfile } from '@/store/profile'
|
||||
|
||||
import { FieldControl, FieldTitle } from './field-control'
|
||||
import { ListRow } from '../primitives'
|
||||
|
||||
// Secrets seed blank: values are write-only and blank keeps the stored one.
|
||||
|
|
@ -51,6 +54,7 @@ export function ProviderConfigModal({
|
|||
onOpenChange: (open: boolean) => void
|
||||
onSaved: () => Promise<void> | void
|
||||
}) {
|
||||
const activeProfile = useStore($activeGatewayProfile)
|
||||
const [values, setValues] = useState<Record<string, string>>({})
|
||||
const [seeded, setSeeded] = useState<Record<string, string>>({})
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
|
@ -87,8 +91,8 @@ export function ProviderConfigModal({
|
|||
<DialogHeader>
|
||||
<DialogTitle icon={SlidersHorizontal}>{config.label} — full configuration</DialogTitle>
|
||||
<DialogDescription>
|
||||
Every {config.label} option for the active profile. Blank fields fall back to the resolved host or
|
||||
built-in default.
|
||||
Every {config.label} option for the <span className="font-medium">{activeProfile}</span> profile. Blank
|
||||
fields fall back to the resolved host or built-in default.
|
||||
</DialogDescription>
|
||||
{config.docs_url && (
|
||||
<a
|
||||
|
|
@ -125,7 +129,7 @@ export function ProviderConfigModal({
|
|||
/>
|
||||
}
|
||||
description={field.description}
|
||||
title={field.label}
|
||||
title={<FieldTitle field={field} />}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@ vi.mock('@/hermes', () => ({
|
|||
runMemoryProviderAction(provider, action, values)
|
||||
}))
|
||||
|
||||
vi.mock('@/store/profile', async () => {
|
||||
const { atom } = await import('nanostores')
|
||||
return { $activeGatewayProfile: atom('default') }
|
||||
})
|
||||
|
||||
vi.mock('@/store/notifications', () => ({
|
||||
notify: vi.fn(),
|
||||
notifyError: vi.fn()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Loader2, Save, SlidersHorizontal } from '@/lib/icons'
|
|||
import { notify, notifyError } from '@/store/notifications'
|
||||
import type { MemoryProviderConfig } from '@/types/hermes'
|
||||
|
||||
import { FieldControl } from './field-control'
|
||||
import { FieldControl, FieldTitle } from './field-control'
|
||||
import { ListRow, LoadingState, Pill } from '../primitives'
|
||||
import { ProviderConfigModal } from './provider-config-modal'
|
||||
|
||||
|
|
@ -145,7 +145,7 @@ export function ProviderConfigPanel({ provider }: { provider: string }) {
|
|||
/>
|
||||
}
|
||||
description={field.description}
|
||||
title={field.label}
|
||||
title={<FieldTitle field={field} />}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ export interface MemoryProviderFieldOption {
|
|||
export interface MemoryProviderField {
|
||||
description: string
|
||||
group: string
|
||||
info?: string
|
||||
inline: boolean
|
||||
is_set: boolean
|
||||
key: string
|
||||
|
|
|
|||
|
|
@ -3891,6 +3891,7 @@ def _provider_field_entry(field: ProviderField) -> Dict[str, Any]:
|
|||
"label": field.label,
|
||||
"kind": field.kind,
|
||||
"description": field.description,
|
||||
"info": field.info,
|
||||
"placeholder": field.placeholder,
|
||||
"inline": field.inline,
|
||||
"group": field.group,
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ class ProviderField:
|
|||
env_fallbacks: tuple[str, ...] = ()
|
||||
inline: bool = False
|
||||
group: str = ""
|
||||
# Longer help text surfaced as an info tooltip next to the field label.
|
||||
info: str = ""
|
||||
# Host-block placement: "host" (per-profile) or "root"; flat-json ignores it.
|
||||
scope: str = "host"
|
||||
|
||||
|
|
|
|||
|
|
@ -100,6 +100,12 @@ CONFIG_SCHEMA = ProviderConfigSchema(
|
|||
kind=KIND_SELECT,
|
||||
default="per-directory",
|
||||
description="How conversations map to Honcho sessions.",
|
||||
info=(
|
||||
"Per session: every conversation gets its own Honcho session. "
|
||||
"Per directory: conversations from the same working directory share one. "
|
||||
"Per repo: conversations from the same git repo share one. "
|
||||
"Global: everything shares a single session."
|
||||
),
|
||||
options=(
|
||||
ProviderFieldOption("per-session", "Per session"),
|
||||
ProviderFieldOption("per-directory", "Per directory"),
|
||||
|
|
@ -181,6 +187,11 @@ CONFIG_SCHEMA = ProviderConfigSchema(
|
|||
kind=KIND_TEXT,
|
||||
default="async",
|
||||
description="When to flush messages: async, turn, session, or every N turns.",
|
||||
info=(
|
||||
"async: write in the background as messages arrive. "
|
||||
"turn: flush after each turn. session: flush when the session ends. "
|
||||
"A number N flushes every N turns."
|
||||
),
|
||||
placeholder="async | turn | session | N",
|
||||
group="Message writing",
|
||||
),
|
||||
|
|
@ -259,6 +270,11 @@ CONFIG_SCHEMA = ProviderConfigSchema(
|
|||
kind=KIND_SELECT,
|
||||
default="hybrid",
|
||||
description="How memory retrieval works: hybrid, context-only, or tools-only.",
|
||||
info=(
|
||||
"Hybrid: auto-injected context plus on-demand memory tools. "
|
||||
"Context only: injection without tools. "
|
||||
"Tools only: the model queries memory explicitly, nothing is injected."
|
||||
),
|
||||
options=(
|
||||
ProviderFieldOption("hybrid", "Hybrid"),
|
||||
ProviderFieldOption("context", "Context only"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue