File size: 4,306 Bytes
34367da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import { Router } from 'express';
import { MemoryRepository } from './memoryRepository.js';
import { MemoryEntityInput, CmaContextRequest } from '@widget-tdc/mcp-types';

export const memoryRouter = Router();
const memoryRepo = new MemoryRepository();

// Simple in-memory cache with TTL
class SimpleCache {
  private cache = new Map<string, { data: any; expiry: number }>();

  set(key: string, data: any, ttlMs: number = 300000): void { // 5 min default
    this.cache.set(key, { data, expiry: Date.now() + ttlMs });
  }

  get(key: string): any | null {
    const entry = this.cache.get(key);
    if (!entry) return null;

    if (Date.now() > entry.expiry) {
      this.cache.delete(key);
      return null;
    }

    return entry.data;
  }

  clear(): void {
    this.cache.clear();
  }
}

const contextCache = new SimpleCache();

// Ingest a memory entity
memoryRouter.post('/ingest', async (req, res) => {
  try {
    const input: MemoryEntityInput = req.body;

    if (!input.orgId || !input.entityType || !input.content) {
      return res.status(400).json({
        error: 'Missing required fields: orgId, entityType, content',
      });
    }

    const entityId = await memoryRepo.ingestEntity(input);

    // Clear context cache when new memory is added to ensure freshness
    contextCache.clear();

    res.json({
      success: true,
      id: entityId,
    });
  } catch (error: any) {
    console.error('Memory ingest error:', error);
    res.status(500).json({
      success: false,
      error: error.message,
    });
  }
});

// Get contextual prompt with memories
memoryRouter.post('/contextual-prompt', async (req, res) => {
  try {
    const request: CmaContextRequest = req.body;

    if (!request.orgId || !request.userId || !request.userQuery) {
      return res.status(400).json({
        error: 'Missing required fields: orgId, userId, userQuery',
      });
    }

    // Create cache key from request parameters
    const cacheKey = `${request.orgId}:${request.userId}:${request.userQuery}:${JSON.stringify(request.keywords || [])}:${request.widgetData || ''}`;

    // Check cache first
    const cachedResult = contextCache.get(cacheKey);
    if (cachedResult) {
      return res.json({
        success: true,
        ...cachedResult,
        cached: true,
      });
    }

    // Search for relevant memories with enhanced semantic search
    const memories = await memoryRepo.searchEntities({
      orgId: request.orgId,
      userId: request.userId,
      keywords: request.keywords || [],
      limit: 5,
    });

    // Build enhanced contextual prompt with semantic scoring
    const memoryContext = memories.map(m => {
      const score = m.semanticScore ? ` (semantic score: ${(m.semanticScore * 100).toFixed(1)}%)` : '';
      return `[${m.entity_type}] ${m.content} (importance: ${m.importance})${score}`;
    }).join('\n');

    const prompt = `

Context from memory (enhanced with semantic search):

${memoryContext}



Widget data:

${request.widgetData || 'None'}



User query:

${request.userQuery}



Please provide a hyper-contextual response considering the semantic relationships and importance scores above.

    `.trim();

    const result = {
      prompt,
      memories: memories.map(m => ({
        id: m.id,
        content: m.content,
        importance: m.importance,
        semanticScore: m.semanticScore,
      })),
    };

    // Cache the result for 5 minutes
    contextCache.set(cacheKey, result, 300000);

    res.json({
      success: true,
      ...result,
      cached: false,
    });
  } catch (error: any) {
    console.error('Contextual prompt error:', error);
    res.status(500).json({
      success: false,
      error: error.message,
    });
  }
});

// Search memories
memoryRouter.post('/search', async (req, res) => {
  try {
    const query = req.body;
    const memories = await memoryRepo.searchEntities(query);

    res.json({
      success: true,
      memories,
      count: memories.length,
    });
  } catch (error: any) {
    console.error('Memory search error:', error);
    res.status(500).json({
      success: false,
      error: error.message,
    });
  }
});