Files
gooseek/backend/internal/computer/memory.go
home 06fe57c765 feat: Go backend, enhanced search, new widgets, Docker deploy
Major changes:
- Add Go backend (backend/) with microservices architecture
- Enhanced master-agents-svc: reranker, content-classifier, stealth-crawler,
  proxy-manager, media-search, fastClassifier, language detection
- New web-svc widgets: KnowledgeCard, ProductCard, ProfileCard, VideoCard,
  UnifiedCard, CardGallery, InlineImageGallery, SourcesPanel, RelatedQuestions
- Improved discover-svc with discover-db integration
- Docker deployment improvements (Caddyfile, vendor.sh, BUILD.md)
- Library-svc: project_id schema migration
- Remove deprecated finance-svc and travel-svc
- Localization improvements across services

Made-with: Cursor
2026-02-27 04:15:32 +03:00

378 lines
7.5 KiB
Go

package computer
import (
"context"
"encoding/json"
"strings"
"sync"
"time"
"github.com/google/uuid"
)
type MemoryStore struct {
repo MemoryRepository
cache map[string][]MemoryEntry
mu sync.RWMutex
}
func NewMemoryStore(repo MemoryRepository) *MemoryStore {
return &MemoryStore{
repo: repo,
cache: make(map[string][]MemoryEntry),
}
}
func (m *MemoryStore) Store(ctx context.Context, userID string, entry *MemoryEntry) error {
if entry.ID == "" {
entry.ID = uuid.New().String()
}
entry.UserID = userID
if entry.CreatedAt.IsZero() {
entry.CreatedAt = time.Now()
}
if m.repo != nil {
if err := m.repo.Store(ctx, entry); err != nil {
return err
}
}
m.mu.Lock()
m.cache[userID] = append(m.cache[userID], *entry)
if len(m.cache[userID]) > 1000 {
m.cache[userID] = m.cache[userID][len(m.cache[userID])-500:]
}
m.mu.Unlock()
return nil
}
func (m *MemoryStore) StoreResult(ctx context.Context, userID, taskID, key string, value interface{}) error {
valueJSON, _ := json.Marshal(value)
entry := &MemoryEntry{
UserID: userID,
TaskID: taskID,
Key: key,
Value: string(valueJSON),
Type: MemoryTypeResult,
CreatedAt: time.Now(),
}
return m.Store(ctx, userID, entry)
}
func (m *MemoryStore) StoreFact(ctx context.Context, userID, key string, value interface{}, tags []string) error {
entry := &MemoryEntry{
UserID: userID,
Key: key,
Value: value,
Type: MemoryTypeFact,
Tags: tags,
CreatedAt: time.Now(),
}
return m.Store(ctx, userID, entry)
}
func (m *MemoryStore) StorePreference(ctx context.Context, userID, key string, value interface{}) error {
entry := &MemoryEntry{
UserID: userID,
Key: key,
Value: value,
Type: MemoryTypePreference,
CreatedAt: time.Now(),
}
return m.Store(ctx, userID, entry)
}
func (m *MemoryStore) StoreContext(ctx context.Context, userID, taskID, key string, value interface{}, ttl time.Duration) error {
expiresAt := time.Now().Add(ttl)
entry := &MemoryEntry{
UserID: userID,
TaskID: taskID,
Key: key,
Value: value,
Type: MemoryTypeContext,
CreatedAt: time.Now(),
ExpiresAt: &expiresAt,
}
return m.Store(ctx, userID, entry)
}
func (m *MemoryStore) Recall(ctx context.Context, userID string, query string, limit int) ([]MemoryEntry, error) {
if m.repo != nil {
entries, err := m.repo.Search(ctx, userID, query, limit)
if err == nil && len(entries) > 0 {
return entries, nil
}
}
m.mu.RLock()
cached := m.cache[userID]
m.mu.RUnlock()
if len(cached) == 0 {
return nil, nil
}
queryLower := strings.ToLower(query)
queryTerms := strings.Fields(queryLower)
type scored struct {
entry MemoryEntry
score int
}
var results []scored
now := time.Now()
for _, entry := range cached {
if entry.ExpiresAt != nil && entry.ExpiresAt.Before(now) {
continue
}
score := 0
keyLower := strings.ToLower(entry.Key)
for _, term := range queryTerms {
if strings.Contains(keyLower, term) {
score += 3
}
}
if valueStr, ok := entry.Value.(string); ok {
valueLower := strings.ToLower(valueStr)
for _, term := range queryTerms {
if strings.Contains(valueLower, term) {
score += 1
}
}
}
for _, tag := range entry.Tags {
tagLower := strings.ToLower(tag)
for _, term := range queryTerms {
if strings.Contains(tagLower, term) {
score += 2
}
}
}
if score > 0 {
results = append(results, scored{entry: entry, score: score})
}
}
for i := 0; i < len(results)-1; i++ {
for j := i + 1; j < len(results); j++ {
if results[j].score > results[i].score {
results[i], results[j] = results[j], results[i]
}
}
}
if len(results) > limit {
results = results[:limit]
}
entries := make([]MemoryEntry, len(results))
for i, r := range results {
entries[i] = r.entry
}
return entries, nil
}
func (m *MemoryStore) GetByUser(ctx context.Context, userID string, limit int) ([]MemoryEntry, error) {
if m.repo != nil {
return m.repo.GetByUser(ctx, userID, limit)
}
m.mu.RLock()
cached := m.cache[userID]
m.mu.RUnlock()
if len(cached) > limit {
return cached[len(cached)-limit:], nil
}
return cached, nil
}
func (m *MemoryStore) GetByTask(ctx context.Context, taskID string) ([]MemoryEntry, error) {
if m.repo != nil {
return m.repo.GetByTask(ctx, taskID)
}
var result []MemoryEntry
m.mu.RLock()
for _, entries := range m.cache {
for _, e := range entries {
if e.TaskID == taskID {
result = append(result, e)
}
}
}
m.mu.RUnlock()
return result, nil
}
func (m *MemoryStore) GetTaskContext(ctx context.Context, taskID string) (map[string]interface{}, error) {
entries, err := m.GetByTask(ctx, taskID)
if err != nil {
return nil, err
}
context := make(map[string]interface{})
for _, e := range entries {
context[e.Key] = e.Value
}
return context, nil
}
func (m *MemoryStore) GetUserContext(ctx context.Context, userID string) (map[string]interface{}, error) {
entries, err := m.GetByUser(ctx, userID, 100)
if err != nil {
return nil, err
}
context := make(map[string]interface{})
for _, e := range entries {
if e.Type == MemoryTypePreference || e.Type == MemoryTypeFact {
context[e.Key] = e.Value
}
}
return context, nil
}
func (m *MemoryStore) GetPreferences(ctx context.Context, userID string) (map[string]interface{}, error) {
entries, err := m.GetByUser(ctx, userID, 100)
if err != nil {
return nil, err
}
prefs := make(map[string]interface{})
for _, e := range entries {
if e.Type == MemoryTypePreference {
prefs[e.Key] = e.Value
}
}
return prefs, nil
}
func (m *MemoryStore) GetFacts(ctx context.Context, userID string) ([]MemoryEntry, error) {
entries, err := m.GetByUser(ctx, userID, 100)
if err != nil {
return nil, err
}
var facts []MemoryEntry
for _, e := range entries {
if e.Type == MemoryTypeFact {
facts = append(facts, e)
}
}
return facts, nil
}
func (m *MemoryStore) Delete(ctx context.Context, id string) error {
if m.repo != nil {
return m.repo.Delete(ctx, id)
}
m.mu.Lock()
for userID, entries := range m.cache {
for i, e := range entries {
if e.ID == id {
m.cache[userID] = append(entries[:i], entries[i+1:]...)
break
}
}
}
m.mu.Unlock()
return nil
}
func (m *MemoryStore) Clear(ctx context.Context, userID string) error {
m.mu.Lock()
delete(m.cache, userID)
m.mu.Unlock()
return nil
}
func (m *MemoryStore) ClearTask(ctx context.Context, taskID string) error {
m.mu.Lock()
for userID, entries := range m.cache {
var filtered []MemoryEntry
for _, e := range entries {
if e.TaskID != taskID {
filtered = append(filtered, e)
}
}
m.cache[userID] = filtered
}
m.mu.Unlock()
return nil
}
func (m *MemoryStore) Cleanup(ctx context.Context) error {
now := time.Now()
m.mu.Lock()
for userID, entries := range m.cache {
var valid []MemoryEntry
for _, e := range entries {
if e.ExpiresAt == nil || e.ExpiresAt.After(now) {
valid = append(valid, e)
}
}
m.cache[userID] = valid
}
m.mu.Unlock()
return nil
}
func (m *MemoryStore) Stats(userID string) map[string]int {
m.mu.RLock()
entries := m.cache[userID]
m.mu.RUnlock()
stats := map[string]int{
"total": len(entries),
"facts": 0,
"preferences": 0,
"context": 0,
"results": 0,
}
for _, e := range entries {
switch e.Type {
case MemoryTypeFact:
stats["facts"]++
case MemoryTypePreference:
stats["preferences"]++
case MemoryTypeContext:
stats["context"]++
case MemoryTypeResult:
stats["results"]++
}
}
return stats
}