Files
LocalAI/core/http/endpoints/studio/draft.go
T
Ettore Di Giacinto 3bd93e18e7 fix(chats): let the draft add-ons list answer for the draft's switches
A new chat with the sandbox switched off in the composer showed no
"switched off" notice. GET /api/chats/draft/addons took only the model
and the agent, so it always reported the default commands state.

Accept the draft's switches as comma-separated query parameters
(addons_off, builtins_off, builtins_on), check them as the create request
does, and apply them to the draft record before the commands state is
worked out. The composer sends its choices each time an add-on or a
built-in group is switched, so the notice follows the switch.

Assisted-by: Claude:claude-sonnet-5-5 go-test playwright
2026-11-03 19:38:55 +00:00

103 lines
4.6 KiB
Go

package studio
import (
"errors"
"net/http"
"strings"
"github.com/labstack/echo/v4"
"github.com/mudler/LocalAI/core/http/auth"
studiosvc "github.com/mudler/LocalAI/core/services/studio"
)
// draftQuery reads the model and the agent of a chat that does not exist yet,
// and refuses what Create would refuse: an agent without the agents feature,
// a model the user may not use.
func (h *Handlers) draftQuery(c echo.Context) (model, agentID string, ok bool) {
model, agentID = c.QueryParam("model"), c.QueryParam("agent_id")
if agentID != "" && !auth.FeatureAllowed(c, auth.FeatureAgents) {
_ = fail(c, http.StatusForbidden, "authorization_error", "feature not enabled for your account: "+auth.FeatureAgents)
return "", "", false
}
if model != "" && h.models != nil {
switch err := h.models.Check(c.Request().Context(), auth.GetUser(c), model); {
case errors.Is(err, ErrUnknownModel):
_ = fail(c, http.StatusBadRequest, "invalid_request_error", "unknown model")
return "", "", false
case errors.Is(err, ErrModelNotAllowed):
_ = fail(c, http.StatusForbidden, "authorization_error", "you may not use this model")
return "", "", false
case err != nil:
_ = failFor(c, err)
return "", "", false
}
}
return model, agentID, true
}
// DraftTools lists the tools a new chat could switch.
// @Summary List the tools a new chat could switch on and off.
// @Description What GET /api/chats/{id}/tools lists, for a chat that does not exist yet: a chat on model, with the saved agent agent_id when it is set (the agent's model, when it has one, replaces model). Every switch is in its default state. The composer of a new chat shows it and sends the person's choices in the switches of POST /api/chats.
// @Tags chats
// @Param model query string false "the model of the new chat"
// @Param agent_id query string false "a saved agent of yours"
// @Success 200 {object} schema.ChatsToolsPanel "The tools and their default switches"
// @Failure 400 {object} schema.ErrorResponse "The request is not valid"
// @Failure 403 {object} schema.ErrorResponse "Refused"
// @Failure 404 {object} schema.ErrorResponse "No such agent"
// @Router /api/chats/draft/tools [get]
func (h *Handlers) DraftTools(c echo.Context) error {
model, agentID, ok := h.draftQuery(c)
if !ok {
return nil
}
p, err := h.svc.DraftTools(c.Request().Context(), userID(c), model, agentID)
if err != nil {
return failFor(c, err)
}
return writeToolsPanel(c, p)
}
// DraftAddons lists the add-ons a new chat could use.
// @Summary List the add-ons a new chat could use.
// @Description What GET /api/chats/{id}/addons lists, for a chat that does not exist yet, as GET /api/chats/draft/tools takes it: the add-ons with their switch and whether the chat could run commands at its first turn. The draft's current switches may be passed (addons_off, builtins_off, builtins_on: comma-separated lists, as the create request takes them) so the answer is the one the chat would get: with the shell group off, commands is switched_off.
// @Tags chats
// @Param model query string false "the model of the new chat"
// @Param agent_id query string false "a saved agent of yours"
// @Param addons_off query string false "comma-separated add-on IDs the draft switched off"
// @Param builtins_off query string false "comma-separated built-in groups the draft switched off"
// @Param builtins_on query string false "comma-separated opt-in built-in groups the draft switched on"
// @Success 200 {object} schema.ChatsAddonList "The add-ons and the commands state, with the draft switches applied"
// @Failure 400 {object} schema.ErrorResponse "The request is not valid"
// @Failure 403 {object} schema.ErrorResponse "Refused"
// @Failure 404 {object} schema.ErrorResponse "No such agent"
// @Router /api/chats/draft/addons [get]
func (h *Handlers) DraftAddons(c echo.Context) error {
model, agentID, ok := h.draftQuery(c)
if !ok {
return nil
}
list, cmds, err := h.svc.DraftAddons(c.Request().Context(), userID(c), model, agentID, draftSwitches(c))
if err != nil {
return failFor(c, err)
}
return writeAddonList(c, list, cmds)
}
// draftSwitches reads the switches of a draft from the query: comma-separated
// lists, also when the parameter is repeated. The service checks the names.
func draftSwitches(c echo.Context) studiosvc.Switches {
list := func(name string) []string {
var out []string
for _, v := range c.QueryParams()[name] {
for _, item := range strings.Split(v, ",") {
if item = strings.TrimSpace(item); item != "" {
out = append(out, item)
}
}
}
return out
}
return studiosvc.Switches{AddonsOff: list("addons_off"), BuiltinsOff: list("builtins_off"), BuiltinsOn: list("builtins_on")}
}