Files
LocalAI/core/services/studio/draftswitches.go
T
Ettore Di Giacinto f97a96cc83 fix(chats): keep the draft commands state and let Hide work
The draft tools and add-on lists, and the tools list of a stored chat,
read the execution status without an execution scope. The host logged
an error and reported the status as not selected. They now run in a
scope; a draft has no conversation, so its status says "draft".

In the Home composer, switching a built-in tool group on or off reset
the commands state to the loading value and never read it again. The
composer then showed the unknown warning for good. The Hide button also
did nothing for that state, because it needed a binding identity that
the loading value does not have. A draft now keeps the state the list
gave it, and Hide works for any notice that is shown.

Assisted-by: Claude:claude-sonnet-5-5 go ginkgo node
2026-11-03 17:23:29 +00:00

69 lines
2.4 KiB
Go

package studio
import (
"context"
"github.com/mudler/LocalAI/core/services/studio/session"
)
// The composer of a new chat shows the Tools and Skills menus before the chat
// exists. It lists what a chat on the chosen model (and saved agent) could
// switch, keeps the person's choices as a draft and sends them with the
// create request (CreateInput.Switches), so the chat is stored with them.
// DraftTools lists what a new chat on model, with the saved agent agentID when
// it is set, could use, every switch in its default state. The agent's model,
// when it has one, replaces model, as Create does.
func (s *Service) DraftTools(ctx context.Context, user, model, agentID string) (ToolsPanel, error) {
rec, err := s.draftRecord(ctx, user, model, agentID)
if err != nil {
return ToolsPanel{}, err
}
// The add-on gate reads the execution status. A draft has no chat, so the
// scope has no conversation and the status says "draft".
ctx, closeScope := s.executionScope(ctx, principal(user), rec.ID)
defer closeScope()
return s.toolsFor(ctx, user, rec)
}
// DraftAddons lists the add-ons of a new chat as DraftTools does, and whether
// it could run commands at its first turn.
func (s *Service) DraftAddons(ctx context.Context, user, model, agentID string) ([]AddonInfo, Commands, error) {
rec, err := s.draftRecord(ctx, user, model, agentID)
if err != nil {
return nil, Commands{}, err
}
ctx, closeScope := s.executionScope(ctx, principal(user), rec.ID)
defer closeScope()
list, err := s.addonsFor(ctx, user, rec)
if err != nil {
return nil, Commands{}, err
}
commands, err := s.commandsFor(ctx, principal(user), rec)
if err != nil {
return nil, Commands{}, err
}
return list, commands, nil
}
// draftRecord is the record a new chat would have, with no ID and no switch.
func (s *Service) draftRecord(ctx context.Context, user, model, agentID string) (session.ChatRecord, error) {
rec := session.ChatRecord{Principal: principal(user), Kind: session.KindChat, Model: model, AgentID: agentID}
if agentID == "" {
return rec, nil
}
if s.d.Agents == nil {
return rec, ErrAgentsOff
}
ag, err := s.d.Agents.Get(ctx, principal(user), agentID)
if err != nil {
return rec, err
}
if ag.Model != "" {
rec.Model = ag.Model
}
// The opt-in groups the agent starts with are on in the draft.
rec.BuiltinsOn = mergeGroups(rec.BuiltinsOn, ag.BuiltinsOn)
return rec, nil
}