191 lines
4.4 KiB
Go
191 lines
4.4 KiB
Go
package store
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
"reichard.io/aethera/pkg/slices"
|
|
)
|
|
|
|
var _ Store = (*FileStore)(nil)
|
|
|
|
// Settings represents the application settings
|
|
type Settings struct {
|
|
APIEndpoint string `json:"api_endpoint,omitempty"`
|
|
ImageEditSelector string `json:"image_edit_selector,omitempty"`
|
|
ImageGenerationSelector string `json:"image_generation_selector,omitempty"`
|
|
TextGenerationSelector string `json:"text_generation_selector,omitempty"`
|
|
}
|
|
|
|
// FileStore implements the Store interface using a file-based storage
|
|
type FileStore struct {
|
|
filePath string
|
|
chatDir string
|
|
}
|
|
|
|
// NewFileStore creates a new FileStore with the specified file path
|
|
func NewFileStore(filePath string) (*FileStore, error) {
|
|
// Derive Chat Directory
|
|
chatDir := filepath.Join(filepath.Dir(filePath), "chats")
|
|
|
|
// Ensure Chat Directory Exists
|
|
if err := os.MkdirAll(chatDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("failed to create directory: %w", err)
|
|
}
|
|
|
|
return &FileStore{
|
|
filePath: filePath,
|
|
chatDir: chatDir,
|
|
}, nil
|
|
}
|
|
|
|
// GetSettings reads and returns the settings from the file
|
|
func (fs *FileStore) GetSettings() (*Settings, error) {
|
|
data, err := os.ReadFile(fs.filePath)
|
|
if err != nil {
|
|
return &Settings{}, nil
|
|
}
|
|
|
|
var settings Settings
|
|
err = json.Unmarshal(data, &settings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &settings, nil
|
|
}
|
|
|
|
// SaveSettings saves the settings to the file
|
|
func (fs *FileStore) SaveSettings(settings *Settings) error {
|
|
data, err := json.MarshalIndent(settings, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(fs.filePath, data, 0644)
|
|
}
|
|
|
|
// GetChat retrieves a chat from disk
|
|
func (fs *FileStore) GetChat(chatID uuid.UUID) (*Chat, error) {
|
|
filePath := filepath.Join(fs.chatDir, chatID.String()+".json")
|
|
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, ErrChatNotFound
|
|
}
|
|
return nil, fmt.Errorf("failed to read chat: %w", err)
|
|
}
|
|
|
|
var chat Chat
|
|
if err := json.Unmarshal(data, &chat); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal chat: %w", err)
|
|
}
|
|
|
|
return &chat, nil
|
|
}
|
|
|
|
// SaveChat creates or updates a chat and persists it to disk
|
|
func (fs *FileStore) SaveChat(c *Chat) error {
|
|
c.ensureDefaults()
|
|
return fs.saveChatSession(c)
|
|
}
|
|
|
|
// DeleteChat removes a chat from disk
|
|
func (fs *FileStore) DeleteChat(chatID uuid.UUID) error {
|
|
filePath := filepath.Join(fs.chatDir, chatID.String()+".json")
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return ErrChatNotFound
|
|
}
|
|
|
|
if err := os.Remove(filePath); err != nil {
|
|
return fmt.Errorf("failed to delete chat: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ListChats returns all persisted chats
|
|
func (fs *FileStore) ListChats() ([]*Chat, error) {
|
|
// Read Files
|
|
entries, err := os.ReadDir(fs.chatDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read chat directory: %w", err)
|
|
}
|
|
|
|
var chats []*Chat
|
|
for _, entry := range entries {
|
|
// Ensure JSON
|
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
|
|
continue
|
|
}
|
|
|
|
// Extract Chat ID
|
|
rawChatID := strings.TrimSuffix(entry.Name(), ".json")
|
|
chatID, err := uuid.Parse(rawChatID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: invalid chat id %s", err, rawChatID)
|
|
}
|
|
|
|
// Read & Parse Chat
|
|
chat, err := fs.GetChat(chatID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: failed to read chat id %s", err, rawChatID)
|
|
}
|
|
|
|
chats = append(chats, chat)
|
|
}
|
|
|
|
return chats, nil
|
|
}
|
|
|
|
// SaveChatMessage creates or updates a chat message to a chat and persists it to disk
|
|
func (fs *FileStore) SaveChatMessage(m *Message) error {
|
|
if m.ChatID == uuid.Nil {
|
|
return ErrNilChatID
|
|
}
|
|
m.ensureDefaults()
|
|
|
|
// Get Chat
|
|
chat, err := fs.GetChat(m.ChatID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Find Existing
|
|
existingMsg, found := slices.FindFirst(chat.Messages, func(item *Message) bool {
|
|
return item.ID == m.ID
|
|
})
|
|
|
|
// Upsert
|
|
if found {
|
|
*existingMsg = *m
|
|
} else {
|
|
chat.Messages = append(chat.Messages, m)
|
|
}
|
|
|
|
// Save
|
|
return fs.saveChatSession(chat)
|
|
}
|
|
|
|
// saveChatSession is a helper method to save a chat to disk
|
|
func (fs *FileStore) saveChatSession(session *Chat) error {
|
|
filePath := filepath.Join(fs.chatDir, session.ID.String()+".json")
|
|
|
|
data, err := json.MarshalIndent(session, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal chat: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(filePath, data, 0644); err != nil {
|
|
return fmt.Errorf("failed to write chat file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|