mirror of
https://github.com/mudler/LocalAI.git
synced 2026-10-28 00:59:28 -04:00
fix(chat-history): create the store after the auth database is opened (#12788)
The conversation routes are only registered when the chat history store exists, and the store needs the auth database. It was created in start(), which New() runs before it opens the auth database, so the store was always nil and /api/conversations answered 404 with auth enabled; the React UI fell back to localStorage and nothing was synced between devices. The route tests register the routes with a store directly and never went through New(). Create the store right after the auth database is opened, and cover it through New(): with auth the store exists, without auth or with the WebUI disabled it stays nil. Docs-Not-Needed: bug fix; the documented server-side chat history now works as described Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
This commit is contained in:
1 parent
e684fdf084
commit
6fc2bc4967
3 files changed
+71
-15
No files matched your search
@@ -676,24 +676,29 @@ func (a *Application) start() error {
|
||||
|
||||
a.agentJobService = agentJobService
|
||||
|
||||
// Initialize chat history store for the WebUI (issue #9432). Reuses the
|
||||
// shared auth database so per-user chat history sits alongside agent
|
||||
// configs and jobs in one place — mudler's review on #9902 called out
|
||||
// the file-based store as inconsistent with the rest of LocalAI's
|
||||
// per-user state. When auth is disabled the store stays nil and the
|
||||
// React UI falls back to localStorage.
|
||||
if !a.applicationConfig.DisableWebUI && a.authDB != nil {
|
||||
store, err := chathistory.New(a.authDB)
|
||||
if err != nil {
|
||||
xlog.Warn("Chat history persistence disabled: failed to initialise store", "error", err)
|
||||
} else {
|
||||
a.chatHistoryStore = store
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initChatHistoryStore initializes the chat history store for the WebUI
|
||||
// (issue #9432). It reuses the shared auth database so per-user chat history
|
||||
// sits alongside agent configs and jobs in one place — mudler's review on
|
||||
// #9902 called out the file-based store as inconsistent with the rest of
|
||||
// LocalAI's per-user state. When auth is disabled the store stays nil and the
|
||||
// React UI falls back to localStorage. It must run after the auth database is
|
||||
// opened: start() runs before that, so a store set up there was always nil
|
||||
// and the conversation routes were never registered.
|
||||
func (a *Application) initChatHistoryStore() {
|
||||
if a.applicationConfig.DisableWebUI || a.authDB == nil {
|
||||
return
|
||||
}
|
||||
store, err := chathistory.New(a.authDB)
|
||||
if err != nil {
|
||||
xlog.Warn("Chat history persistence disabled: failed to initialise store", "error", err)
|
||||
return
|
||||
}
|
||||
a.chatHistoryStore = store
|
||||
}
|
||||
|
||||
// agentPoolOptions builds the agent pool's dependencies. WorkQueue stays a nil
|
||||
// interface without distributed services, because the pool reads a non-nil
|
||||
// WorkQueue as distributed mode.
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//go:build auth
|
||||
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mudler/LocalAI/core/config"
|
||||
"github.com/mudler/LocalAI/pkg/system"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
// The conversation routes are registered only when the store exists, so a
|
||||
// store that New() never creates means /api/conversations answers 404 even
|
||||
// with auth on. Built through New(), the same path the server takes.
|
||||
var _ = Describe("chat history store at startup", func() {
|
||||
newApp := func(opts ...config.AppOption) *Application {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
DeferCleanup(cancel)
|
||||
state, err := system.GetSystemState(system.WithModelPath(GinkgoT().TempDir()), system.WithBackendPath(GinkgoT().TempDir()))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
base := []config.AppOption{config.WithContext(ctx), config.WithSystemState(state), config.DisableMetricsEndpoint,
|
||||
config.WithDisableLocalAIAssistant(true), config.WithDisableStats(true), config.WithDataPath(GinkgoT().TempDir())}
|
||||
app, err := New(append(base, opts...)...)
|
||||
if app != nil {
|
||||
DeferCleanup(func() { Expect(app.Shutdown()).To(Succeed()) })
|
||||
}
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return app
|
||||
}
|
||||
withAuth := func() []config.AppOption {
|
||||
return []config.AppOption{config.WithAuthEnabled(true),
|
||||
config.WithAuthDatabaseURL(filepath.Join(GinkgoT().TempDir(), "auth.db"))}
|
||||
}
|
||||
|
||||
It("is created when auth is enabled", func() {
|
||||
Expect(newApp(withAuth()...).ChatHistoryStore()).NotTo(BeNil())
|
||||
})
|
||||
|
||||
It("stays nil with the WebUI disabled", func() {
|
||||
Expect(newApp(append(withAuth(), config.DisableWebUI)...).ChatHistoryStore()).To(BeNil())
|
||||
})
|
||||
|
||||
It("stays nil without auth, so the UI keeps localStorage", func() {
|
||||
Expect(newApp().ChatHistoryStore()).To(BeNil())
|
||||
})
|
||||
})
|
||||
@@ -174,6 +174,7 @@ func New(opts ...config.AppOption) (*Application, error) {
|
||||
}
|
||||
}()
|
||||
}
|
||||
application.initChatHistoryStore()
|
||||
|
||||
// Initialize the OTel + Prometheus metric pipeline before any
|
||||
// counter is created. monitoring.NewLocalAIMetricsService calls
|
||||
|
||||
Reference in new issue
Block a user