Files
LocalAI/core/application/addon_usersettings.go
T
Ettore Di Giacinto 82b9984e06 feat(memory): serve the notes of memory from the host
The Memory add-on only declared three index files, two skills and a save
setting; the host did the rest. Without the add-on a memory proposal
worked but nothing was read back, and removing the add-on silently reset
the save level.

The host now loads the three indexes (user, agent, conversation) with
the same caps and data wrapping, serves context_read, ships the two
skills as built-in skills with the same text, and keeps the save level
as its own per-person setting (ask, auto, never; default ask). The code
sits behind a small MemoryProvider interface.

The add-on stays installed and harmless: its index files and skills are
ignored while the host serves memory, so nothing loads twice. A person
who chose a level on its page keeps it until they choose in the host
setting; auto still needs the add-on to run. A person who disabled the
add-on and never chose keeps memory off. Saved agents still read notes
only when their add-ons list names memory, and messaging chats still
ask before saving.

A person with no note and no add-on gets no extra block in the prompt.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:25:40 +00:00

78 lines
2.5 KiB
Go

// SPDX-License-Identifier: MIT
package application
import (
"context"
"fmt"
"path/filepath"
"github.com/mudler/LocalAI/core/http/auth"
"github.com/mudler/LocalAI/core/services/addons/usersettings"
"github.com/mudler/xlog"
)
// AddonUserSettingsStore returns the store of the per-person add-on settings,
// created once so the routes and the chats share it. An error is cached: a
// store that cannot be opened stays unavailable, and startup stops rather than
// fall back to a copy that another frontend would not see.
func (a *Application) AddonUserSettingsStore() (usersettings.Store, error) {
a.addonUserSettingsOnce.Do(func() {
if db := a.authDB; db != nil {
s, err := usersettings.NewGormStore(db)
if err != nil {
a.addonUserSettingsErr = fmt.Errorf("open the add-on user settings table: %w", err)
return
}
a.addonUserSettings = s
return
}
path := ""
if dp := a.applicationConfig.DataPath; dp != "" {
path = filepath.Join(dp, "addon-user-settings.json")
} else {
xlog.Warn("No data path and no account database: add-on user settings are kept in memory only and do not survive a restart")
}
s, err := usersettings.NewFileStore(path)
if err != nil {
a.addonUserSettingsErr = fmt.Errorf("open the add-on user settings file %q: %w", path, err)
return
}
a.addonUserSettings = s
})
return a.addonUserSettings, a.addonUserSettingsErr
}
// studioUserSetting reads one stored answer for a chat owner. Without an
// account database there is one local owner, whichever name the chat carries;
// the routes write under the same key. A store that cannot answer is an error:
// the caller treats it as the default, never as a looser choice.
func (a *Application) studioUserSetting(ctx context.Context, user, addonID, key string) (string, bool, error) {
store, err := a.AddonUserSettingsStore()
if err != nil {
return "", false, err
}
if a.authDB == nil {
user = auth.AddonLocalOwnerID
}
m, err := store.Get(ctx, user, addonID)
if err != nil {
return "", false, err
}
v, ok := m[key]
return v, ok, nil
}
// studioSetUserSetting stores one answer for a chat owner, in the store that
// studioUserSetting reads. The host's own settings (the memory switch and the
// save level) go through it.
func (a *Application) studioSetUserSetting(ctx context.Context, user, addonID, key, value string) error {
store, err := a.AddonUserSettingsStore()
if err != nil {
return err
}
if a.authDB == nil {
user = auth.AddonLocalOwnerID
}
return store.Set(ctx, user, addonID, key, value)
}