Files
LocalAI/core/application/addon_usersettings.go
T
Ettore Di Giacinto 7ce4507da1 feat(addons): keep per-person settings that an add-on declares
An add-on can declare userSettings in its manifest: a key, the allowed
values and a default. The host keeps each person's answer in the account
database every frontend shares (one file when accounts are off), checks
every write against the declaration, and never asks the add-on.

GET and PUT /api/addons/{id}/user-settings read and write them. A write
needs the person's own browser session, so no API key, model or tool can
change one. A setting may carry a role that the host reads for its own
rules; the host fixes the values and the default of a role so a manifest
cannot loosen it. Pages reach their settings through sdk.userSettings,
which is bound to the add-on that calls it.

Assisted-by: Claude:claude-sonnet-5-5 go node
2026-11-03 22:46:22 +00:00

64 lines
2.0 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
}