feat: the assistant's talk is kept in the database per user and the model sees a trimmed window of it; clear is a broom; the ball steps aside for the chat and comes back where the chat was

This commit is contained in:
redtea committed 2026-10-15 22:35:10 +03:00
1 parent 92a90646a5
commit 00617451aa
6 files changed
+286 -63

No files matched your search

+43 -1
View File
@@ -130,7 +130,7 @@ func TestAIChatNeedsUserOn(t *testing.T) {
_ = s.db.SetConfig(cfgAIType, "openai")
_ = s.db.SetConfig(cfgAIURL, srv.URL)
post := func() *httptest.ResponseRecorder {
req := httptest.NewRequest(http.MethodPost, "/api/panel/ai/chat", strings.NewReader(`{"messages":[{"role":"user","content":"hi"}],"context":"x"}`))
req := httptest.NewRequest(http.MethodPost, "/api/panel/ai/chat", strings.NewReader(`{"message":"hi","context":"x"}`))
req.AddCookie(c)
w := httptest.NewRecorder()
s.Handler().ServeHTTP(w, req)
@@ -146,6 +146,10 @@ func TestAIChatNeedsUserOn(t *testing.T) {
if w.Code != 200 || string(b) != "ok" {
t.Fatalf("on: %d %q", w.Code, b)
}
// The talk is kept: the question and the answer.
if h := s.aiHistory(a.ID, 10); len(h) != 2 || h[0].Content != "hi" || h[1].Content != "ok" {
t.Fatalf("history: %+v", h)
}
}
func TestVersionAtLeast(t *testing.T) {
@@ -158,3 +162,41 @@ func TestVersionAtLeast(t *testing.T) {
}
}
}
// The window the model sees: newest first within the budget, old results
// and answers cut down, results as the user's words, same roles merged.
func TestAIWindow(t *testing.T) {
big := strings.Repeat("x", 5000)
rows := []aiMsg{
{Role: "user", Content: "старое"},
{Role: "assistant", Content: big},
{Role: "result", Content: big},
{Role: "user", Content: "дальше"},
{Role: "assistant", Content: "ок"},
{Role: "result", Content: "вывод"},
}
w := aiWindow(rows, 100000)
if len(w) != 5 || w[0].Role != "user" || w[len(w)-1].Role != "user" {
t.Fatalf("roles: %+v", w)
}
if len(w[1].Content) > 3200 || len(w[2].Content) > 1700+len("дальше")+40 {
t.Fatalf("old not cut: %d %d", len(w[1].Content), len(w[2].Content))
}
if !strings.Contains(w[4].Content, "вывод") || !strings.HasPrefix(w[4].Content, "Результат") {
t.Fatalf("last result: %q", w[4].Content)
}
// A small budget keeps the newest, starting with the user.
w = aiWindow(rows, 60)
if len(w) == 0 || w[0].Role != "user" || !strings.Contains(w[len(w)-1].Content, "вывод") {
t.Fatalf("small: %+v", w)
}
}
func TestStripThink(t *testing.T) {
if got := stripThink("::думаю::\nхм\n::ответ::\n391"); got != "391" {
t.Fatalf("%q", got)
}
if got := stripThink("просто"); got != "просто" {
t.Fatalf("%q", got)
}
}