feat(gateway): persist chat sessions and messages in PostgreSQL
- store: new chat_sessions/chat_messages tables with CRUD, ILIKE message search, and auto-titling from the first user message - context: optional Persister on the sliding-window store — messages are saved asynchronously on Add, sessions not in memory are restored from the database on Get - orchestrator: ProcessInput/FinishRound now carry userID so persisted messages have an owner - api: real session handlers (list/create/delete), message history and clear endpoints, working message search and three-format export (markdown/txt/json), fixing the short-ID panic in export - main: migration, store wiring, ctxStore persister adapter Tests: chat store handler lifecycle (fake store), context persister save/restore, stub auth login flow updated.
This commit is contained in:
1 parent
f44bf26050
commit
f8196c111c
11 files changed
+787
-27
No files matched your search
@@ -10,12 +10,23 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/model"
|
||||
)
|
||||
|
||||
// Persister saves conversation messages durably and can restore them
|
||||
// (e.g. from PostgreSQL) when a session is not in memory.
|
||||
type Persister interface {
|
||||
// SaveMessage persists one message. Called synchronously by Add —
|
||||
// implementations should be fast or dispatch async work themselves.
|
||||
SaveMessage(sessionID, userID string, msg model.Message)
|
||||
// LoadRecent returns the most recent messages of a session, ascending.
|
||||
LoadRecent(sessionID string, limit int) []model.Message
|
||||
}
|
||||
|
||||
// Store holds conversation history with a sliding window.
|
||||
type Store struct {
|
||||
mu sync.RWMutex
|
||||
sessions map[string]*SessionHistory
|
||||
maxTokens int // Maximum tokens to include in context
|
||||
maxTurns int // Maximum message turns to keep
|
||||
persister Persister
|
||||
}
|
||||
|
||||
// SessionHistory holds messages for a single session.
|
||||
@@ -39,14 +50,31 @@ func NewStore(maxTokens, maxTurns int) *Store {
|
||||
}
|
||||
}
|
||||
|
||||
// SetPersister injects durable persistence for conversation messages.
|
||||
func (s *Store) SetPersister(p Persister) {
|
||||
s.mu.Lock()
|
||||
s.persister = p
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// Add appends a message to the session history.
|
||||
func (s *Store) Add(sessionID string, msg model.Message) {
|
||||
s.add(sessionID, "", msg)
|
||||
}
|
||||
|
||||
// AddWithUser appends a message and records the owning user for persistence.
|
||||
func (s *Store) AddWithUser(sessionID, userID string, msg model.Message) {
|
||||
s.add(sessionID, userID, msg)
|
||||
}
|
||||
|
||||
func (s *Store) add(sessionID, userID string, msg model.Message) {
|
||||
s.mu.Lock()
|
||||
sh, ok := s.sessions[sessionID]
|
||||
if !ok {
|
||||
sh = &SessionHistory{}
|
||||
s.sessions[sessionID] = sh
|
||||
}
|
||||
persister := s.persister
|
||||
s.mu.Unlock()
|
||||
|
||||
sh.mu.Lock()
|
||||
@@ -56,15 +84,33 @@ func (s *Store) Add(sessionID string, msg model.Message) {
|
||||
sh.Messages = sh.Messages[len(sh.Messages)-s.maxTurns:]
|
||||
}
|
||||
sh.mu.Unlock()
|
||||
|
||||
// Persist asynchronously — never block the conversation on the database.
|
||||
if persister != nil && (msg.Role == "user" || msg.Role == "assistant") {
|
||||
go persister.SaveMessage(sessionID, userID, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves recent messages for a session, trimmed to fit token budget.
|
||||
// Sessions missing from memory are restored from the persister if available.
|
||||
func (s *Store) Get(sessionID string) []model.Message {
|
||||
s.mu.RLock()
|
||||
sh, ok := s.sessions[sessionID]
|
||||
persister := s.persister
|
||||
s.mu.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return nil
|
||||
if persister == nil {
|
||||
return nil
|
||||
}
|
||||
loaded := persister.LoadRecent(sessionID, s.maxTurns)
|
||||
if len(loaded) == 0 {
|
||||
return nil
|
||||
}
|
||||
sh = &SessionHistory{Messages: loaded}
|
||||
s.mu.Lock()
|
||||
s.sessions[sessionID] = sh
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
sh.mu.RLock()
|
||||
|
||||
Reference in new issue
Block a user