ms365-mcp-server 1.1.16 → 1.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1122 -9
- package/dist/utils/batch-performance-monitor.js +106 -0
- package/dist/utils/batch-test-scenarios.js +277 -0
- package/dist/utils/context-aware-search.js +499 -0
- package/dist/utils/cross-reference-detector.js +352 -0
- package/dist/utils/document-workflow.js +433 -0
- package/dist/utils/enhanced-fuzzy-search.js +514 -0
- package/dist/utils/error-handler.js +337 -0
- package/dist/utils/intelligence-engine.js +71 -0
- package/dist/utils/intelligent-cache.js +379 -0
- package/dist/utils/large-mailbox-search.js +599 -0
- package/dist/utils/ms365-operations.js +730 -208
- package/dist/utils/performance-monitor.js +395 -0
- package/dist/utils/proactive-intelligence.js +390 -0
- package/dist/utils/rate-limiter.js +284 -0
- package/dist/utils/search-batch-pipeline.js +222 -0
- package/dist/utils/thread-reconstruction.js +700 -0
- package/package.json +1 -1
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import { EnhancedFuzzySearch } from './enhanced-fuzzy-search.js';
|
|
2
|
+
import { ProactiveIntelligence, EmailCategory } from './proactive-intelligence.js';
|
|
3
|
+
import { logger } from './api.js';
|
|
4
|
+
export class ContextAwareSearch {
|
|
5
|
+
constructor(ms365Operations) {
|
|
6
|
+
this.ms365Operations = ms365Operations;
|
|
7
|
+
this.fuzzySearch = new EnhancedFuzzySearch(ms365Operations);
|
|
8
|
+
this.proactiveIntelligence = new ProactiveIntelligence(ms365Operations);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Perform context-aware search
|
|
12
|
+
*/
|
|
13
|
+
async search(query, emails) {
|
|
14
|
+
logger.log(`🧭 Context-aware search for: "${query}"`);
|
|
15
|
+
// Parse the query to understand context and intent
|
|
16
|
+
const parsedQuery = this.parseQuery(query);
|
|
17
|
+
// Execute search based on parsed context
|
|
18
|
+
const results = await this.executeContextualSearch(parsedQuery, emails);
|
|
19
|
+
// Generate explanations and suggestions
|
|
20
|
+
const explanation = this.generateExplanation(parsedQuery, results);
|
|
21
|
+
const suggestions = this.generateSuggestions(parsedQuery, results);
|
|
22
|
+
return {
|
|
23
|
+
originalQuery: query,
|
|
24
|
+
parsedQuery,
|
|
25
|
+
emails: results.emails,
|
|
26
|
+
searchStrategy: results.strategy,
|
|
27
|
+
confidence: results.confidence,
|
|
28
|
+
suggestions,
|
|
29
|
+
explanation
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Parse natural language query to extract context and intent
|
|
34
|
+
*/
|
|
35
|
+
parseQuery(query) {
|
|
36
|
+
const lowerQuery = query.toLowerCase();
|
|
37
|
+
// Extract intent
|
|
38
|
+
const intent = this.extractIntent(lowerQuery);
|
|
39
|
+
// Extract entities
|
|
40
|
+
const entities = this.extractEntities(query);
|
|
41
|
+
// Extract time context
|
|
42
|
+
const timeContext = this.extractTimeContext(lowerQuery);
|
|
43
|
+
// Extract sender context
|
|
44
|
+
const senderContext = this.extractSenderContext(lowerQuery, entities);
|
|
45
|
+
// Extract category context
|
|
46
|
+
const categoryContext = this.extractCategoryContext(lowerQuery);
|
|
47
|
+
// Extract priority context
|
|
48
|
+
const priorityContext = this.extractPriorityContext(lowerQuery);
|
|
49
|
+
// Calculate overall confidence
|
|
50
|
+
const confidence = this.calculateParsingConfidence(intent, entities, timeContext);
|
|
51
|
+
return {
|
|
52
|
+
originalQuery: query,
|
|
53
|
+
intent,
|
|
54
|
+
entities,
|
|
55
|
+
timeContext,
|
|
56
|
+
senderContext,
|
|
57
|
+
categoryContext,
|
|
58
|
+
priorityContext,
|
|
59
|
+
confidence
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Extract search intent from query
|
|
64
|
+
*/
|
|
65
|
+
extractIntent(query) {
|
|
66
|
+
let intentType = 'general_search';
|
|
67
|
+
const verbs = [];
|
|
68
|
+
const objects = [];
|
|
69
|
+
const modifiers = [];
|
|
70
|
+
// Check intent patterns
|
|
71
|
+
for (const [type, patterns] of Object.entries(ContextAwareSearch.INTENT_PATTERNS)) {
|
|
72
|
+
for (const pattern of patterns) {
|
|
73
|
+
if (pattern.test(query)) {
|
|
74
|
+
intentType = type;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (intentType !== 'general_search')
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
// Extract verbs
|
|
82
|
+
const verbMatches = query.match(/\b(find|search|look|get|show|need|want)\b/g);
|
|
83
|
+
if (verbMatches)
|
|
84
|
+
verbs.push(...verbMatches);
|
|
85
|
+
// Extract objects
|
|
86
|
+
const objectMatches = query.match(/\b(email|message|document|file|attachment|notice|letter)\b/g);
|
|
87
|
+
if (objectMatches)
|
|
88
|
+
objects.push(...objectMatches);
|
|
89
|
+
// Extract modifiers
|
|
90
|
+
const modifierMatches = query.match(/\b(recent|latest|new|old|important|urgent)\b/g);
|
|
91
|
+
if (modifierMatches)
|
|
92
|
+
modifiers.push(...modifierMatches);
|
|
93
|
+
return {
|
|
94
|
+
type: intentType,
|
|
95
|
+
verbs,
|
|
96
|
+
objects,
|
|
97
|
+
modifiers
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Extract entities from query
|
|
102
|
+
*/
|
|
103
|
+
extractEntities(query) {
|
|
104
|
+
const entities = [];
|
|
105
|
+
for (const { pattern, type } of ContextAwareSearch.ENTITY_PATTERNS) {
|
|
106
|
+
let match;
|
|
107
|
+
while ((match = pattern.exec(query)) !== null) {
|
|
108
|
+
entities.push({
|
|
109
|
+
type: type,
|
|
110
|
+
value: match[0],
|
|
111
|
+
confidence: 0.8,
|
|
112
|
+
position: { start: match.index, end: match.index + match[0].length }
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return entities;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Extract time context from query
|
|
120
|
+
*/
|
|
121
|
+
extractTimeContext(query) {
|
|
122
|
+
for (const { pattern, type } of ContextAwareSearch.TIME_PATTERNS) {
|
|
123
|
+
const match = pattern.exec(query);
|
|
124
|
+
if (match) {
|
|
125
|
+
if (type === 'relative') {
|
|
126
|
+
return this.parseRelativeTime(match[0]);
|
|
127
|
+
}
|
|
128
|
+
else if (type === 'absolute') {
|
|
129
|
+
return this.parseAbsoluteTime(match[0]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Parse relative time expressions
|
|
137
|
+
*/
|
|
138
|
+
parseRelativeTime(timeExpression) {
|
|
139
|
+
const now = new Date();
|
|
140
|
+
let relativeAmount = 1;
|
|
141
|
+
let relativeUnit = 'days';
|
|
142
|
+
let startDate;
|
|
143
|
+
if (timeExpression.includes('few') || timeExpression.includes('couple')) {
|
|
144
|
+
relativeAmount = 3;
|
|
145
|
+
}
|
|
146
|
+
else if (timeExpression.includes('several')) {
|
|
147
|
+
relativeAmount = 5;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const numberMatch = timeExpression.match(/(\d+)/);
|
|
151
|
+
if (numberMatch) {
|
|
152
|
+
relativeAmount = parseInt(numberMatch[1]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (timeExpression.includes('week')) {
|
|
156
|
+
relativeUnit = 'weeks';
|
|
157
|
+
startDate = new Date(now.getTime() - relativeAmount * 7 * 24 * 60 * 60 * 1000);
|
|
158
|
+
}
|
|
159
|
+
else if (timeExpression.includes('month')) {
|
|
160
|
+
relativeUnit = 'months';
|
|
161
|
+
startDate = new Date(now.getFullYear(), now.getMonth() - relativeAmount, now.getDate());
|
|
162
|
+
}
|
|
163
|
+
else if (timeExpression.includes('year')) {
|
|
164
|
+
relativeUnit = 'years';
|
|
165
|
+
startDate = new Date(now.getFullYear() - relativeAmount, now.getMonth(), now.getDate());
|
|
166
|
+
}
|
|
167
|
+
else if (timeExpression.includes('today')) {
|
|
168
|
+
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
169
|
+
}
|
|
170
|
+
else if (timeExpression.includes('yesterday')) {
|
|
171
|
+
startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
172
|
+
}
|
|
173
|
+
else if (timeExpression.includes('recent')) {
|
|
174
|
+
relativeAmount = 7;
|
|
175
|
+
relativeUnit = 'days';
|
|
176
|
+
startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Default to days
|
|
180
|
+
startDate = new Date(now.getTime() - relativeAmount * 24 * 60 * 60 * 1000);
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
type: 'relative',
|
|
184
|
+
startDate,
|
|
185
|
+
endDate: now,
|
|
186
|
+
relativeAmount,
|
|
187
|
+
relativeUnit,
|
|
188
|
+
description: timeExpression
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Parse absolute time expressions
|
|
193
|
+
*/
|
|
194
|
+
parseAbsoluteTime(timeExpression) {
|
|
195
|
+
const date = new Date(timeExpression);
|
|
196
|
+
return {
|
|
197
|
+
type: 'absolute',
|
|
198
|
+
startDate: date,
|
|
199
|
+
endDate: date,
|
|
200
|
+
description: timeExpression
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Extract sender context
|
|
205
|
+
*/
|
|
206
|
+
extractSenderContext(query, entities) {
|
|
207
|
+
const emailEntities = entities.filter(e => e.type === 'email_address');
|
|
208
|
+
const personEntities = entities.filter(e => e.type === 'person');
|
|
209
|
+
if (emailEntities.length > 0) {
|
|
210
|
+
const email = emailEntities[0].value;
|
|
211
|
+
return {
|
|
212
|
+
email,
|
|
213
|
+
domain: email.split('@')[1]
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (personEntities.length > 0) {
|
|
217
|
+
return {
|
|
218
|
+
name: personEntities[0].value
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
// Look for "from" patterns
|
|
222
|
+
const fromMatch = query.match(/from\s+([a-zA-Z\s]+)/i);
|
|
223
|
+
if (fromMatch) {
|
|
224
|
+
return {
|
|
225
|
+
name: fromMatch[1].trim()
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Extract category context
|
|
232
|
+
*/
|
|
233
|
+
extractCategoryContext(query) {
|
|
234
|
+
const categories = [];
|
|
235
|
+
// Map keywords to categories
|
|
236
|
+
const categoryKeywords = {
|
|
237
|
+
[EmailCategory.GOVERNMENT]: ['government', 'federal', 'state', 'official', 'agency'],
|
|
238
|
+
[EmailCategory.TAX]: ['tax', 'irs', 'refund', 'filing', 'return'],
|
|
239
|
+
[EmailCategory.LEGAL]: ['legal', 'court', 'lawyer', 'attorney', 'lawsuit'],
|
|
240
|
+
[EmailCategory.FINANCIAL]: ['bank', 'financial', 'account', 'payment', 'invoice'],
|
|
241
|
+
[EmailCategory.HEALTHCARE]: ['medical', 'health', 'doctor', 'hospital', 'clinic'],
|
|
242
|
+
[EmailCategory.URGENT]: ['urgent', 'important', 'critical', 'asap', 'priority']
|
|
243
|
+
};
|
|
244
|
+
for (const [category, keywords] of Object.entries(categoryKeywords)) {
|
|
245
|
+
for (const keyword of keywords) {
|
|
246
|
+
if (query.includes(keyword)) {
|
|
247
|
+
categories.push({
|
|
248
|
+
category: category,
|
|
249
|
+
confidence: 0.8
|
|
250
|
+
});
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return categories;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Extract priority context
|
|
259
|
+
*/
|
|
260
|
+
extractPriorityContext(query) {
|
|
261
|
+
const urgentKeywords = ['urgent', 'asap', 'critical', 'emergency'];
|
|
262
|
+
const highKeywords = ['important', 'priority', 'significant'];
|
|
263
|
+
const foundUrgent = urgentKeywords.filter(keyword => query.includes(keyword));
|
|
264
|
+
const foundHigh = highKeywords.filter(keyword => query.includes(keyword));
|
|
265
|
+
if (foundUrgent.length > 0) {
|
|
266
|
+
return { level: 'urgent', keywords: foundUrgent };
|
|
267
|
+
}
|
|
268
|
+
else if (foundHigh.length > 0) {
|
|
269
|
+
return { level: 'high', keywords: foundHigh };
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Calculate parsing confidence
|
|
275
|
+
*/
|
|
276
|
+
calculateParsingConfidence(intent, entities, timeContext) {
|
|
277
|
+
let confidence = 0.5; // Base confidence
|
|
278
|
+
// Intent confidence
|
|
279
|
+
if (intent.type !== 'general_search')
|
|
280
|
+
confidence += 0.2;
|
|
281
|
+
if (intent.verbs.length > 0)
|
|
282
|
+
confidence += 0.1;
|
|
283
|
+
if (intent.objects.length > 0)
|
|
284
|
+
confidence += 0.1;
|
|
285
|
+
// Entity confidence
|
|
286
|
+
confidence += Math.min(entities.length * 0.05, 0.2);
|
|
287
|
+
// Time context confidence
|
|
288
|
+
if (timeContext)
|
|
289
|
+
confidence += 0.1;
|
|
290
|
+
return Math.min(confidence, 1.0);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Execute contextual search based on parsed query
|
|
294
|
+
*/
|
|
295
|
+
async executeContextualSearch(parsedQuery, emails) {
|
|
296
|
+
let filteredEmails = emails;
|
|
297
|
+
let strategy = 'general';
|
|
298
|
+
let confidence = parsedQuery.confidence;
|
|
299
|
+
// Apply time filtering
|
|
300
|
+
if (parsedQuery.timeContext) {
|
|
301
|
+
filteredEmails = this.applyTimeFilter(filteredEmails, parsedQuery.timeContext);
|
|
302
|
+
strategy += '_time_filtered';
|
|
303
|
+
}
|
|
304
|
+
// Apply sender filtering
|
|
305
|
+
if (parsedQuery.senderContext) {
|
|
306
|
+
filteredEmails = this.applySenderFilter(filteredEmails, parsedQuery.senderContext);
|
|
307
|
+
strategy += '_sender_filtered';
|
|
308
|
+
}
|
|
309
|
+
// Apply category filtering
|
|
310
|
+
if (parsedQuery.categoryContext && parsedQuery.categoryContext.length > 0) {
|
|
311
|
+
filteredEmails = await this.applyCategoryFilter(filteredEmails, parsedQuery.categoryContext);
|
|
312
|
+
strategy += '_category_filtered';
|
|
313
|
+
}
|
|
314
|
+
// Apply priority filtering
|
|
315
|
+
if (parsedQuery.priorityContext) {
|
|
316
|
+
filteredEmails = this.applyPriorityFilter(filteredEmails, parsedQuery.priorityContext);
|
|
317
|
+
strategy += '_priority_filtered';
|
|
318
|
+
}
|
|
319
|
+
// Perform fuzzy search on remaining emails
|
|
320
|
+
const cleanQuery = this.extractCleanQuery(parsedQuery);
|
|
321
|
+
if (cleanQuery) {
|
|
322
|
+
const fuzzyResults = await this.fuzzySearch.search(cleanQuery, filteredEmails);
|
|
323
|
+
filteredEmails = fuzzyResults.map(result => result.email);
|
|
324
|
+
strategy += '_fuzzy_search';
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
emails: filteredEmails,
|
|
328
|
+
strategy,
|
|
329
|
+
confidence
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Apply time filter
|
|
334
|
+
*/
|
|
335
|
+
applyTimeFilter(emails, timeContext) {
|
|
336
|
+
if (!timeContext.startDate)
|
|
337
|
+
return emails;
|
|
338
|
+
return emails.filter(email => {
|
|
339
|
+
const emailDate = new Date(email.receivedDateTime);
|
|
340
|
+
if (timeContext.endDate) {
|
|
341
|
+
return emailDate >= timeContext.startDate && emailDate <= timeContext.endDate;
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
return emailDate >= timeContext.startDate;
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Apply sender filter
|
|
350
|
+
*/
|
|
351
|
+
applySenderFilter(emails, senderContext) {
|
|
352
|
+
return emails.filter(email => {
|
|
353
|
+
if (senderContext.email) {
|
|
354
|
+
return email.from.address.toLowerCase().includes(senderContext.email.toLowerCase());
|
|
355
|
+
}
|
|
356
|
+
if (senderContext.name) {
|
|
357
|
+
return email.from.name.toLowerCase().includes(senderContext.name.toLowerCase());
|
|
358
|
+
}
|
|
359
|
+
if (senderContext.domain) {
|
|
360
|
+
return email.from.address.toLowerCase().includes(senderContext.domain.toLowerCase());
|
|
361
|
+
}
|
|
362
|
+
return true;
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Apply category filter
|
|
367
|
+
*/
|
|
368
|
+
async applyCategoryFilter(emails, categoryContexts) {
|
|
369
|
+
const classifications = await this.proactiveIntelligence.batchClassifyEmails(emails);
|
|
370
|
+
const targetCategories = categoryContexts.map(c => c.category);
|
|
371
|
+
return emails.filter(email => {
|
|
372
|
+
const classification = classifications.get(email.id);
|
|
373
|
+
if (!classification)
|
|
374
|
+
return false;
|
|
375
|
+
return classification.categories.some(category => targetCategories.includes(category));
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Apply priority filter
|
|
380
|
+
*/
|
|
381
|
+
applyPriorityFilter(emails, priorityContext) {
|
|
382
|
+
return emails.filter(email => {
|
|
383
|
+
const emailText = `${email.subject} ${email.bodyPreview}`.toLowerCase();
|
|
384
|
+
if (priorityContext.level === 'urgent') {
|
|
385
|
+
return email.importance === 'high' ||
|
|
386
|
+
priorityContext.keywords.some(keyword => emailText.includes(keyword));
|
|
387
|
+
}
|
|
388
|
+
else if (priorityContext.level === 'high') {
|
|
389
|
+
return email.importance === 'high' || email.importance === 'normal';
|
|
390
|
+
}
|
|
391
|
+
return true;
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Extract clean query for fuzzy search
|
|
396
|
+
*/
|
|
397
|
+
extractCleanQuery(parsedQuery) {
|
|
398
|
+
let query = parsedQuery.originalQuery.toLowerCase();
|
|
399
|
+
// Remove time expressions
|
|
400
|
+
if (parsedQuery.timeContext) {
|
|
401
|
+
query = query.replace(parsedQuery.timeContext.description.toLowerCase(), '');
|
|
402
|
+
}
|
|
403
|
+
// Remove sender expressions
|
|
404
|
+
if (parsedQuery.senderContext?.name) {
|
|
405
|
+
query = query.replace(parsedQuery.senderContext.name.toLowerCase(), '');
|
|
406
|
+
}
|
|
407
|
+
// Remove category keywords
|
|
408
|
+
if (parsedQuery.categoryContext) {
|
|
409
|
+
for (const context of parsedQuery.categoryContext) {
|
|
410
|
+
query = query.replace(context.category, '');
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// Remove common words
|
|
414
|
+
query = query.replace(/\b(find|search|look|from|in|the|and|or|for|with)\b/g, '');
|
|
415
|
+
return query.trim();
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Generate explanation of search results
|
|
419
|
+
*/
|
|
420
|
+
generateExplanation(parsedQuery, results) {
|
|
421
|
+
const parts = [];
|
|
422
|
+
parts.push(`Found ${results.emails.length} emails`);
|
|
423
|
+
if (parsedQuery.timeContext) {
|
|
424
|
+
parts.push(`from ${parsedQuery.timeContext.description}`);
|
|
425
|
+
}
|
|
426
|
+
if (parsedQuery.senderContext) {
|
|
427
|
+
if (parsedQuery.senderContext.name) {
|
|
428
|
+
parts.push(`from ${parsedQuery.senderContext.name}`);
|
|
429
|
+
}
|
|
430
|
+
if (parsedQuery.senderContext.email) {
|
|
431
|
+
parts.push(`from ${parsedQuery.senderContext.email}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (parsedQuery.categoryContext && parsedQuery.categoryContext.length > 0) {
|
|
435
|
+
const categories = parsedQuery.categoryContext.map(c => c.category).join(', ');
|
|
436
|
+
parts.push(`in categories: ${categories}`);
|
|
437
|
+
}
|
|
438
|
+
if (parsedQuery.priorityContext) {
|
|
439
|
+
parts.push(`with ${parsedQuery.priorityContext.level} priority`);
|
|
440
|
+
}
|
|
441
|
+
parts.push(`using ${results.strategy} strategy`);
|
|
442
|
+
return parts.join(' ');
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Generate search suggestions
|
|
446
|
+
*/
|
|
447
|
+
generateSuggestions(parsedQuery, results) {
|
|
448
|
+
const suggestions = [];
|
|
449
|
+
if (results.emails.length === 0) {
|
|
450
|
+
suggestions.push('Try broadening your search terms');
|
|
451
|
+
suggestions.push('Check spelling of names or keywords');
|
|
452
|
+
if (parsedQuery.timeContext) {
|
|
453
|
+
suggestions.push('Try expanding the time range');
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (results.emails.length > 50) {
|
|
457
|
+
suggestions.push('Try adding more specific criteria to narrow results');
|
|
458
|
+
if (!parsedQuery.timeContext) {
|
|
459
|
+
suggestions.push('Add a time range like "last month" or "recent"');
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (!parsedQuery.senderContext && parsedQuery.intent.type !== 'find_from_sender') {
|
|
463
|
+
suggestions.push('Try searching by sender: "from John" or "emails from manager"');
|
|
464
|
+
}
|
|
465
|
+
if (!parsedQuery.categoryContext || parsedQuery.categoryContext.length === 0) {
|
|
466
|
+
suggestions.push('Try category-specific searches: "tax emails" or "government notices"');
|
|
467
|
+
}
|
|
468
|
+
return suggestions;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// Intent patterns
|
|
472
|
+
ContextAwareSearch.INTENT_PATTERNS = {
|
|
473
|
+
find_documents: [/find.*documents?/i, /looking for.*files?/i, /need.*attachments?/i],
|
|
474
|
+
find_emails: [/find.*emails?/i, /search.*messages?/i, /looking for.*mail/i],
|
|
475
|
+
find_from_sender: [/from\s+(\w+)/i, /sent by\s+(\w+)/i, /emails? from/i],
|
|
476
|
+
find_by_category: [/tax.*emails?/i, /government.*mail/i, /legal.*documents?/i],
|
|
477
|
+
find_urgent: [/urgent/i, /important/i, /priority/i, /asap/i],
|
|
478
|
+
general_search: [/.*/]
|
|
479
|
+
};
|
|
480
|
+
// Time expression patterns
|
|
481
|
+
ContextAwareSearch.TIME_PATTERNS = [
|
|
482
|
+
{ pattern: /last (\d+) (days?|weeks?|months?|years?)/i, type: 'relative' },
|
|
483
|
+
{ pattern: /(few|several|couple of) (days?|weeks?|months?)/i, type: 'relative' },
|
|
484
|
+
{ pattern: /past (week|month|year)/i, type: 'relative' },
|
|
485
|
+
{ pattern: /this (week|month|year)/i, type: 'relative' },
|
|
486
|
+
{ pattern: /recent(ly)?/i, type: 'relative' },
|
|
487
|
+
{ pattern: /today/i, type: 'relative' },
|
|
488
|
+
{ pattern: /yesterday/i, type: 'relative' },
|
|
489
|
+
{ pattern: /(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})/i, type: 'absolute' },
|
|
490
|
+
{ pattern: /(january|february|march|april|may|june|july|august|september|october|november|december)/i, type: 'absolute' }
|
|
491
|
+
];
|
|
492
|
+
// Entity patterns
|
|
493
|
+
ContextAwareSearch.ENTITY_PATTERNS = [
|
|
494
|
+
{ pattern: /\b[A-Z][a-z]+ [A-Z][a-z]+\b/g, type: 'person' },
|
|
495
|
+
{ pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, type: 'email_address' },
|
|
496
|
+
{ pattern: /\b(government|irs|tax|court|legal|bank|hospital)\b/ig, type: 'organization' },
|
|
497
|
+
{ pattern: /\b(urgent|important|critical|asap|priority)\b/ig, type: 'urgency' },
|
|
498
|
+
{ pattern: /\b(pdf|doc|docx|excel|spreadsheet|document|file|attachment)\b/ig, type: 'document_type' }
|
|
499
|
+
];
|