mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-06 04:23:15 -05:00
Memory now has one switch, On or Off, in the Memory tab of the account settings. Off means nothing from the notes is put in the prompt, the model has no memory skill, tool or note kind, nothing is proposed or saved (also by triggers and other runs that start by themselves), and the model is told in one line that memory is off. The notes stay and can still be read, exported and deleted. A chat can be private: the Memory row of its Tools menu is the memory switch group, stored with the chat. The narrowest level wins: chat, agent, person, server. A saved agent cannot widen the choice of the person. The tools list says which level is off (memory.code, blocked_by, fix); the chat shows a chip and one line, and the agent form shows the person's switch as a read-only line. New API: GET and PUT /api/chats/memory for the switch and the save level, and GET /api/chats/memory/export for a zip of the notes. The Memory tab gains the switch, the three save levels, Export and Forget everything. Assisted-by: Claude:claude-sonnet-5-5
138 lines
4.7 KiB
Go
138 lines
4.7 KiB
Go
package studio
|
|
|
|
import (
|
|
"archive/zip"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/mudler/LocalAI/core/services/studio/contextfiles"
|
|
)
|
|
|
|
// ErrMemorySettings means the memory settings cannot be stored.
|
|
var ErrMemorySettings = errors.New("studio: the memory settings cannot be stored on this server")
|
|
|
|
// MemorySettings is the person's own memory choices, as the Memory tab shows
|
|
// them. The server keeps them with the person's other settings and every
|
|
// frontend reads the same answer.
|
|
type MemorySettings struct {
|
|
// Enabled is the switch in force: memory On or Off for every chat of the
|
|
// person. A chat, a messaging connection or an agent can only narrow it.
|
|
Enabled bool `json:"enabled"`
|
|
// EnabledSet is true once the person chose. Before that the switch is on,
|
|
// unless they had disabled the Memory add-on to stop memory.
|
|
EnabledSet bool `json:"enabled_set"`
|
|
// SaveMode is how a note is saved: ask, auto or never.
|
|
SaveMode string `json:"save_mode"`
|
|
// SaveModeSet is true once the person chose in this setting. Before that
|
|
// the value they gave on the page of the Memory add-on, if any, applies.
|
|
SaveModeSet bool `json:"save_mode_set"`
|
|
// Provider says what serves memory: "builtin".
|
|
Provider string `json:"provider"`
|
|
// Available is false when the server has no files: notes are files.
|
|
Available bool `json:"available"`
|
|
}
|
|
|
|
// MemoryUpdate is a change to the settings. A nil field is left alone.
|
|
type MemoryUpdate struct {
|
|
Enabled *bool
|
|
SaveMode *string
|
|
}
|
|
|
|
// MemorySettings reads the memory settings of a person.
|
|
func (s *Service) MemorySettings(ctx context.Context, user string) (MemorySettings, error) {
|
|
legacy := legacyMemoryOf(s.addonContextSets(ctx, user))
|
|
out := MemorySettings{Provider: s.memory.ID(), Available: s.d.Files.Available(), SaveMode: SaveAsk}
|
|
out.Enabled = s.memoryUserOn(ctx, user, legacy)
|
|
if s.d.UserSetting != nil {
|
|
if v, ok, err := s.d.UserSetting(ctx, principal(user), HostSettingsID, MemoryEnabledKey); err == nil && ok && (v == MemoryOn || v == MemoryOff) {
|
|
out.EnabledSet = true
|
|
}
|
|
if v, ok, err := s.d.UserSetting(ctx, principal(user), HostSettingsID, MemorySaveKey); err == nil && ok && ValidSaveMode(v) {
|
|
out.SaveModeSet = true
|
|
}
|
|
}
|
|
out.SaveMode = s.changes.storedSaveMode(ctx, user)
|
|
return out, nil
|
|
}
|
|
|
|
// SetMemory stores the choices of a person. A value that is not one of the
|
|
// allowed ones is ErrInvalidSettings.
|
|
func (s *Service) SetMemory(ctx context.Context, user string, in MemoryUpdate) (MemorySettings, error) {
|
|
if in.SaveMode != nil && !ValidSaveMode(*in.SaveMode) {
|
|
return MemorySettings{}, fmt.Errorf("%w: save_mode must be ask, auto or never", ErrInvalidSettings)
|
|
}
|
|
if (in.Enabled != nil || in.SaveMode != nil) && s.d.SetUserSetting == nil {
|
|
return MemorySettings{}, ErrMemorySettings
|
|
}
|
|
if in.Enabled != nil {
|
|
v := MemoryOff
|
|
if *in.Enabled {
|
|
v = MemoryOn
|
|
}
|
|
if err := s.d.SetUserSetting(ctx, principal(user), HostSettingsID, MemoryEnabledKey, v); err != nil {
|
|
return MemorySettings{}, fmt.Errorf("%w: %v", ErrMemorySettings, err)
|
|
}
|
|
}
|
|
if in.SaveMode != nil {
|
|
if err := s.d.SetUserSetting(ctx, principal(user), HostSettingsID, MemorySaveKey, *in.SaveMode); err != nil {
|
|
return MemorySettings{}, fmt.Errorf("%w: %v", ErrMemorySettings, err)
|
|
}
|
|
}
|
|
return s.MemorySettings(ctx, user)
|
|
}
|
|
|
|
// ErrNoNotes means a scope has no notes to export.
|
|
var ErrNoNotes = errors.New("studio: there are no notes to export")
|
|
|
|
// ExportMemory writes the notes of a person as a zip file: the Markdown files
|
|
// of the memory folders, with their paths in the home. scope and key narrow it
|
|
// to one group, as the context files list names them (user; agent and the
|
|
// agent ID; channel and the folder name); empty exports all. It works while
|
|
// memory is off: the notes belong to the person.
|
|
func (s *Service) ExportMemory(ctx context.Context, user string, w io.Writer, scope, key string) (int, error) {
|
|
list, err := s.ListContextFiles(ctx, user)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
var paths []string
|
|
for _, g := range list.Groups {
|
|
if g.Source != MemoryAddonID {
|
|
continue
|
|
}
|
|
if scope != "" && (string(g.Scope) != scope || g.Key != key) {
|
|
continue
|
|
}
|
|
for _, f := range g.Files {
|
|
paths = append(paths, f.Path)
|
|
}
|
|
}
|
|
if len(paths) == 0 {
|
|
return 0, ErrNoNotes
|
|
}
|
|
zw := zip.NewWriter(w)
|
|
rd := homeReader{f: s.d.Files}
|
|
n := 0
|
|
for _, p := range paths {
|
|
data, err := rd.ReadFile(ctx, principal(user), p, 1<<20)
|
|
if err != nil {
|
|
if errors.Is(err, contextfiles.ErrNotExist) {
|
|
continue
|
|
}
|
|
return n, err
|
|
}
|
|
fw, err := zw.Create(strings.TrimPrefix(path.Clean(p), "/"))
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
if _, err := fw.Write(data); err != nil {
|
|
return n, err
|
|
}
|
|
n++
|
|
}
|
|
return n, zw.Close()
|
|
}
|