diff --git a/internal/server/ai.go b/internal/server/ai.go index f997be9..755b512 100644 --- a/internal/server/ai.go +++ b/internal/server/ai.go @@ -47,6 +47,12 @@ func aiNativeKey(id string) string { return "ai_native_" + id } // default; an explicit "0" means the user turned it off. func aiUserKey(id int64) string { return "ai_user_" + strconv.FormatInt(id, 10) } +// aiOn: the user has the assistant - on unless turned off. +func (s *Server) aiOn(id int64) bool { + v, _ := s.db.GetConfig(aiUserKey(id)) + return v != "0" +} + // aiConfig is one model the assistant can ask: its id and the names the chat // shows (Short, up to 12 characters, on the button of the model in use), the // kind of API, its address, model and key. @@ -833,7 +839,7 @@ func (s *Server) aiAdopt(owner int64) { // text with "[ошибка: …]". func (s *Server) handleAIChat(w http.ResponseWriter, r *http.Request) { a := s.currentAdmin(r) - if on, _ := s.db.GetConfig(aiUserKey(a.ID)); on == "0" { + if !s.aiOn(a.ID) { http.Error(w, "ассистент выключен в профиле", http.StatusForbidden) return } diff --git a/internal/server/aion_test.go b/internal/server/aion_test.go new file mode 100644 index 0000000..d5abfdf --- /dev/null +++ b/internal/server/aion_test.go @@ -0,0 +1,42 @@ +package server + +import ( + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +// The assistant is on unless the user turned it off - for the web search +// and page reading as for the chat (a user who never touched the switch +// has no setting at all). +func TestAISearchAndFetchOnByDefault(t *testing.T) { + s, c := newTestServer(t) + sx := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, `{"results":[{"title":"Intel","url":"https://example.com/intel","content":"процессоры"}]}`) + })) + defer sx.Close() + _ = s.db.SetConfig(cfgSearchEngine, "searxng") + _ = s.db.SetConfig(cfgSearchURL, sx.URL) + post := func(path, body string) string { + req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(body)) + req.AddCookie(c) + w := httptest.NewRecorder() + s.Handler().ServeHTTP(w, req) + return w.Body.String() + } + if got := post("/api/panel/ai/search", `{"q":"intel"}`); !strings.Contains(got, `"ok":true`) { + t.Fatalf("search by default: %s", got) + } + if got := post("/api/panel/ai/fetch", `{"url":"не адрес"}`); strings.Contains(got, "выключен") { + t.Fatalf("fetch by default: %s", got) + } + a, _ := s.db.AdminByUsername("adm") + _ = s.db.SetConfig(aiUserKey(a.ID), "0") + for _, p := range []string{"/api/panel/ai/search", "/api/panel/ai/fetch"} { + if got := post(p, `{"q":"intel","url":"https://example.com"}`); !strings.Contains(got, "выключен") { + t.Fatalf("%s with the assistant off: %s", p, got) + } + } +} diff --git a/internal/server/fetch.go b/internal/server/fetch.go index f21d7dd..541c790 100644 --- a/internal/server/fetch.go +++ b/internal/server/fetch.go @@ -179,7 +179,7 @@ func pageText(page string) (string, string) { // assistant on. func (s *Server) handleAIFetch(w http.ResponseWriter, r *http.Request) { a := s.currentAdmin(r) - if on, _ := s.db.GetConfig(aiUserKey(a.ID)); on != "1" { + if !s.aiOn(a.ID) { writeJSON(w, map[string]any{"ok": false, "error": "ассистент выключен в профиле"}) return } diff --git a/internal/server/search.go b/internal/server/search.go index 9dc6c39..2c7a432 100644 --- a/internal/server/search.go +++ b/internal/server/search.go @@ -262,7 +262,7 @@ func webSearch(ctx context.Context, q string, n int) ([]searchResult, error) { // assistant on (the «Поиск» switch is the page's: it lets the model ask). func (s *Server) handleAISearch(w http.ResponseWriter, r *http.Request) { a := s.currentAdmin(r) - if on, _ := s.db.GetConfig(aiUserKey(a.ID)); on != "1" { + if !s.aiOn(a.ID) { writeJSON(w, map[string]any{"ok": false, "error": "ассистент выключен в профиле"}) return }