72de690894
Chatbot functionality * baseline working * missing test file * more tests
120 lines
4.0 KiB
Go
120 lines
4.0 KiB
Go
package bot
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
|
||
"queryorchestration/internal/database/repository"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
// BuildConversationHistory returns the last cfg.RecentTurns completed
|
||
// turns for the session as a flat slice of ConversationMessage entries
|
||
// in ascending ordinal order: each turn produces (user prompt, assistant
|
||
// completion). Plan §7 mapping table.
|
||
//
|
||
// An empty session returns a non-nil zero-length slice so the FastAPI
|
||
// payload emits "conversation_history": [] (the swagger requires a
|
||
// non-null array).
|
||
func (s *Service) BuildConversationHistory(ctx context.Context, sessionID uuid.UUID) ([]ConversationMessage, error) {
|
||
rows, err := s.dbCfg.GetDBQueries().GetCompletedTurnsForSessionAscending(ctx, &repository.GetCompletedTurnsForSessionAscendingParams{
|
||
SessionID: sessionID,
|
||
TurnLimit: int32(s.cfg.RecentTurns), //nolint:gosec // RecentTurns is bounded [1, 5] by ChatbotConfig.Validate
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("bot: list completed turns: %w", err)
|
||
}
|
||
out := make([]ConversationMessage, 0, len(rows)*2)
|
||
for _, r := range rows {
|
||
userMsg := ConversationMessage{
|
||
Role: "user",
|
||
Content: r.Prompt,
|
||
Timestamp: r.CreatedAt.Time,
|
||
}
|
||
assistantContent := ""
|
||
if r.Completion != nil {
|
||
assistantContent = *r.Completion
|
||
}
|
||
assistantMsg := ConversationMessage{
|
||
Role: "assistant",
|
||
Content: assistantContent,
|
||
Timestamp: r.CreatedAt.Time,
|
||
}
|
||
out = append(out, userMsg, assistantMsg)
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
// BuildChatScope assembles the ChatScope payload sent to FastAPI. Plan
|
||
// §7 mapping table:
|
||
//
|
||
// - Stored doc ids ∪ folder-expanded doc ids; stored folder ids preserved.
|
||
// - Plan §4 fallback: empty scope tables mean "all documents for the
|
||
// client", with folders returning an empty list.
|
||
//
|
||
// All ids are returned as strings so the swagger's array-of-string
|
||
// schema matches without callers re-stringifying. Document deduplication
|
||
// is performed in-memory using a UUID set; folder ids are emitted in
|
||
// stable insertion order (sorted by SQL).
|
||
func (s *Service) BuildChatScope(ctx context.Context, sessionID uuid.UUID, clientID string) (ChatScope, error) {
|
||
queries := s.dbCfg.GetDBQueries()
|
||
|
||
storedDocs, err := queries.ListBotSessionDocumentsForSession(ctx, sessionID)
|
||
if err != nil {
|
||
return ChatScope{}, fmt.Errorf("bot: list session documents: %w", err)
|
||
}
|
||
storedFolders, err := queries.ListBotSessionFoldersForSession(ctx, sessionID)
|
||
if err != nil {
|
||
return ChatScope{}, fmt.Errorf("bot: list session folders: %w", err)
|
||
}
|
||
|
||
// Plan §4 fallback: empty scope tables → all client docs, no folders.
|
||
if len(storedDocs) == 0 && len(storedFolders) == 0 {
|
||
all, err := queries.ListDocumentsByClient(ctx, clientID)
|
||
if err != nil {
|
||
return ChatScope{}, fmt.Errorf("bot: list all client documents: %w", err)
|
||
}
|
||
docIDs := make([]string, 0, len(all))
|
||
for _, row := range all {
|
||
docIDs = append(docIDs, row.ID.String())
|
||
}
|
||
return ChatScope{DocumentIDs: docIDs, FolderIDs: []string{}}, nil
|
||
}
|
||
|
||
// Stored docs + folder-expansion. Dedup via set since a stored doc
|
||
// may also live under a stored folder.
|
||
seen := make(map[uuid.UUID]struct{}, len(storedDocs))
|
||
docIDs := make([]string, 0, len(storedDocs))
|
||
for _, id := range storedDocs {
|
||
if _, dup := seen[id]; dup {
|
||
continue
|
||
}
|
||
seen[id] = struct{}{}
|
||
docIDs = append(docIDs, id.String())
|
||
}
|
||
|
||
for _, folderID := range storedFolders {
|
||
expanded, err := queries.GetDocumentIDsInFolderTreeForClient(ctx, &repository.GetDocumentIDsInFolderTreeForClientParams{
|
||
FolderID: folderID,
|
||
ClientID: clientID,
|
||
})
|
||
if err != nil {
|
||
return ChatScope{}, fmt.Errorf("bot: expand folder tree: %w", err)
|
||
}
|
||
for _, id := range expanded {
|
||
if _, dup := seen[id]; dup {
|
||
continue
|
||
}
|
||
seen[id] = struct{}{}
|
||
docIDs = append(docIDs, id.String())
|
||
}
|
||
}
|
||
|
||
folderIDs := make([]string, 0, len(storedFolders))
|
||
for _, id := range storedFolders {
|
||
folderIDs = append(folderIDs, id.String())
|
||
}
|
||
return ChatScope{DocumentIDs: docIDs, FolderIDs: folderIDs}, nil
|
||
}
|