Skip to content

Commit a3d1988

Browse files
committed
Ensure Knowledge Graphs performs optimally (ensuring the content you are looking can be found with ease!)
1 parent bdf8bb7 commit a3d1988

13 files changed

Lines changed: 853 additions & 282 deletions

CHANGES.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@
5555

5656
- Enhance the prompt used when Smart Hubs are included in a user's reponse
5757

58-
5958
## 0.1.0
6059

6160
- Integrate Knowledge Graphs using Neo4j
61+
62+
## 0.1.1
63+
64+
- Update current note's state when title is updated
65+
- Align chevron icons properly when listing folders in Notes Tab
66+
- Ensure Knowledge Graphs performs optimally (ensuring the content you are looking can be found with ease!)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "notebit",
33
"description": "Notebit is a cross-platform desktop note-taking application built with Electron, React, and TypeScript.",
4-
"version": "0.1.0",
4+
"version": "0.1.1",
55
"author": {
66
"email": "mikeymooney1991@gmail.com",
77
"name": "Michael Mooney"

src/shared/routers/notes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,12 @@ export const notesRouter = router({
474474
return await deleteNote(input);
475475
}),
476476

477+
deleteNoteVectors: publicProcedure
478+
.input(z.string())
479+
.mutation(async ({ input }) => {
480+
await deleteNoteVectors(input);
481+
}),
482+
477483
// Move note or folder
478484
moveItem: publicProcedure
479485
.input(

src/shared/routers/smartHubs.ts

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import fs from 'fs';
1313
import neo4jService from '../services/neo4jService';
1414
import smartHubsKnowledgeGraphService from '../services/smartHubsKnowledgeGraphService';
1515
import { EntityType } from '../services/entityExtractor';
16+
import vectorStorageService from '../services/vectorStorageService';
1617

1718
// Import electron dialog conditionally in main process only
1819
let dialog: any;
@@ -359,23 +360,21 @@ export const smartHubsRouter = router({
359360
hybridSearch: publicProcedure
360361
.input(
361362
z.object({
363+
queryText: z.string(),
362364
queryEmbedding: z.array(z.number()),
363365
smartHubIds: z.array(z.string()),
364366
similarityThreshold: z.number().min(0).max(1).optional().default(0.7),
365367
limit: z.number().optional().default(5),
366-
graphDepth: z.number().optional().default(2),
367-
graphResultCount: z.number().optional().default(5),
368368
})
369369
)
370370
.query(async ({ input }) => {
371371
try {
372372
const results = await smartHubsKnowledgeGraphService.hybridSearch(
373+
input.queryText,
373374
input.queryEmbedding,
374375
input.smartHubIds,
375376
input.similarityThreshold,
376-
input.limit,
377-
input.graphDepth,
378-
input.graphResultCount
377+
input.limit
379378
);
380379

381380
return results;
@@ -385,6 +384,35 @@ export const smartHubsRouter = router({
385384
}
386385
}),
387386

387+
/**
388+
* Knowledge graph search
389+
*/
390+
knowledgeGraphSearch: publicProcedure
391+
.input(
392+
z.object({
393+
query: z.string(),
394+
smartHubIds: z.array(z.string()),
395+
similarityThreshold: z.number().min(0).max(1).optional().default(0.7),
396+
limit: z.number().optional().default(5),
397+
})
398+
)
399+
.query(async ({ input }) => {
400+
try {
401+
const results =
402+
await smartHubsKnowledgeGraphService.knowledgeGraphSearch(
403+
input.query,
404+
input.smartHubIds,
405+
input.similarityThreshold,
406+
input.limit
407+
);
408+
409+
return results;
410+
} catch (error) {
411+
console.error('Error performing hybrid search:', error);
412+
return [];
413+
}
414+
}),
415+
388416
/**
389417
* Get content for hybrid search results
390418
*/
@@ -515,4 +543,39 @@ export const smartHubsRouter = router({
515543
return false;
516544
}
517545
}),
546+
547+
/**
548+
* Check Neo4j connection status
549+
* Returns detailed information about the Neo4j connection
550+
*/
551+
checkNeo4jStatus: publicProcedure.query(async () => {
552+
try {
553+
return await neo4jService.checkNeo4jStatus();
554+
} catch (error) {
555+
console.error('Error checking Neo4j status:', error);
556+
return {
557+
isConfigured: false,
558+
isConnected: false,
559+
message: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
560+
};
561+
}
562+
}),
563+
564+
/**
565+
* Get all documents for a smart hub
566+
* Used for rebuilding knowledge graph
567+
*/
568+
getAllDocuments: publicProcedure
569+
.input(z.string())
570+
.query(async ({ input: smartHubId }) => {
571+
try {
572+
return await vectorStorageService.getAllDocuments(smartHubId);
573+
} catch (error) {
574+
console.error(
575+
`Error getting documents for smart hub ${smartHubId}:`,
576+
error
577+
);
578+
return [];
579+
}
580+
}),
518581
});

src/shared/services/entityExtractor.ts

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ export class EntityExtractor {
8585
}
8686

8787
try {
88-
const entities: ExtractedEntity[] = [];
8988
const entityMap = new Map<string, ExtractedEntity>();
9089

9190
// Use compromise for entity extraction
@@ -190,7 +189,8 @@ export class EntityExtractor {
190189
entityMap.set(key, {
191190
type: 'Concept',
192191
name,
193-
confidence: 0.5, // Lower confidence for general concepts
192+
// Give higher confidence to important concepts
193+
confidence: 0.5,
194194
mentions: 1,
195195
});
196196
}
@@ -199,13 +199,15 @@ export class EntityExtractor {
199199
}
200200

201201
// Convert map to array and sort by confidence and mentions
202-
return Array.from(entityMap.values()).sort((a, b) => {
202+
const result = Array.from(entityMap.values()).sort((a, b) => {
203203
// Sort by confidence first, then by mentions
204204
if (b.confidence !== a.confidence) {
205205
return b.confidence - a.confidence;
206206
}
207207
return b.mentions - a.mentions;
208208
});
209+
210+
return result;
209211
} catch (error) {
210212
console.error('Error extracting entities:', error);
211213
return [];
@@ -220,6 +222,11 @@ export class EntityExtractor {
220222
private extractTechnologyTerms(text: string): string[] {
221223
const techTerms: string[] = [];
222224

225+
// Helper function to escape special regex characters
226+
const escapeRegExp = (string: string): string => {
227+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
228+
};
229+
223230
// Common technology keywords
224231
const techKeywords = [
225232
'api',
@@ -323,50 +330,67 @@ export class EntityExtractor {
323330

324331
// Check for tech names (specific technologies)
325332
for (const tech of techNames) {
326-
const regex = new RegExp(`\\b${tech}\\b`, 'gi');
327-
if (regex.test(text)) {
328-
techTerms.push(tech);
333+
// Escape special regex characters
334+
const escapedTech = escapeRegExp(tech);
335+
try {
336+
const regex = new RegExp(`\\b${escapedTech}\\b`, 'gi');
337+
if (regex.test(text)) {
338+
techTerms.push(tech);
339+
}
340+
} catch (error) {
341+
console.error(
342+
`Error creating regex for technology term "${tech}":`,
343+
error
344+
);
345+
// Continue with other terms even if one fails
329346
}
330347
}
331348

332349
// Extract phrases containing tech keywords
333350
const sentences = text.split(/[.!?]+/);
334351
for (const sentence of sentences) {
335352
for (const keyword of techKeywords) {
336-
const regex = new RegExp(`\\b${keyword}\\b`, 'i');
337-
if (regex.test(sentence)) {
338-
// Extract noun phrases around the keyword
339-
const words = sentence.split(/\s+/);
340-
const keywordIndex = words.findIndex((word) =>
341-
word.toLowerCase().includes(keyword)
342-
);
343-
344-
if (keywordIndex >= 0) {
345-
// Try to extract a meaningful phrase (up to 3 words)
346-
let phrase = words[keywordIndex];
347-
348-
// Add preceding word if it looks like an adjective or proper noun
349-
if (
350-
keywordIndex > 0 &&
351-
!words[keywordIndex - 1].match(
352-
/^(the|a|an|this|that|these|those|my|your|our|their)$/i
353-
)
354-
) {
355-
phrase = words[keywordIndex - 1] + ' ' + phrase;
353+
try {
354+
// Escape special regex characters
355+
const escapedKeyword = escapeRegExp(keyword);
356+
const regex = new RegExp(`\\b${escapedKeyword}\\b`, 'i');
357+
if (regex.test(sentence)) {
358+
// Extract noun phrases around the keyword
359+
const words = sentence.split(/\s+/);
360+
const keywordIndex = words.findIndex((word) =>
361+
word.toLowerCase().includes(keyword)
362+
);
363+
364+
if (keywordIndex >= 0) {
365+
// Try to extract a meaningful phrase (up to 3 words)
366+
let phrase = words[keywordIndex];
367+
368+
// Add preceding word if it looks like an adjective or proper noun
369+
if (
370+
keywordIndex > 0 &&
371+
!words[keywordIndex - 1].match(
372+
/^(the|a|an|this|that|these|those|my|your|our|their)$/i
373+
)
374+
) {
375+
phrase = words[keywordIndex - 1] + ' ' + phrase;
376+
}
377+
378+
// Add following word if it looks like it could be part of the phrase
379+
if (
380+
keywordIndex < words.length - 1 &&
381+
!words[keywordIndex + 1].match(
382+
/^(is|are|was|were|will|would|could|should|and|or|but)$/i
383+
)
384+
) {
385+
phrase = phrase + ' ' + words[keywordIndex + 1];
386+
}
387+
388+
techTerms.push(phrase.trim());
356389
}
357-
358-
// Add following word if it looks like it could be part of the phrase
359-
if (
360-
keywordIndex < words.length - 1 &&
361-
!words[keywordIndex + 1].match(
362-
/^(is|are|was|were|will|would|could|should|and|or|but)$/i
363-
)
364-
) {
365-
phrase = phrase + ' ' + words[keywordIndex + 1];
366-
}
367-
368-
techTerms.push(phrase.trim());
369390
}
391+
} catch (error) {
392+
console.error(`Error processing keyword "${keyword}":`, error);
393+
// Continue with other keywords even if one fails
370394
}
371395
}
372396
}

0 commit comments

Comments
 (0)