2025-03-11 18:39:59 +00:00
|
|
|
import becca from '../../../becca/becca.js';
|
|
|
|
import { sanitizeHtmlContent } from './note_content.js';
|
2025-04-01 18:48:39 +00:00
|
|
|
import { HIERARCHY_STRINGS } from '../constants/hierarchy_constants.js';
|
2025-03-11 18:39:59 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a list of parent notes for a given note
|
|
|
|
*/
|
|
|
|
export async function getParentNotes(noteId: string, maxParents: number = 5): Promise<{id: string, title: string}[]> {
|
|
|
|
const note = becca.getNote(noteId);
|
|
|
|
|
|
|
|
if (!note) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Use Becca API to get parent branches and notes
|
|
|
|
const parentBranches = note.getParentBranches();
|
|
|
|
|
|
|
|
if (!parentBranches || parentBranches.length === 0) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map to get parent notes, limiting to maxParents
|
|
|
|
const parentNotes = parentBranches
|
|
|
|
.slice(0, maxParents)
|
|
|
|
.map(branch => {
|
|
|
|
if (!branch.parentNote) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
id: branch.parentNote.noteId,
|
|
|
|
title: branch.parentNote.title
|
|
|
|
};
|
|
|
|
})
|
|
|
|
.filter(note => note !== null) as {id: string, title: string}[];
|
|
|
|
|
|
|
|
return parentNotes;
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error getting parent notes for ${noteId}:`, error);
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get hierarchical context of parent notes
|
|
|
|
* This function builds a representation of the note hierarchy to provide context
|
|
|
|
*/
|
|
|
|
export async function getParentContext(
|
|
|
|
noteId: string,
|
|
|
|
maxDepth: number = 3,
|
|
|
|
maxParents: number = 3,
|
|
|
|
includeCurrentNote: boolean = true
|
|
|
|
): Promise<string> {
|
|
|
|
// Note: getParentNotes has been updated to use Becca API
|
|
|
|
const note = becca.getNote(noteId);
|
|
|
|
|
|
|
|
if (!note) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
const visited = new Set<string>();
|
|
|
|
let context = "";
|
|
|
|
|
|
|
|
// Helper function to build the hierarchical context recursively
|
|
|
|
async function buildHierarchy(currentNoteId: string, depth: number, prefix: string = ""): Promise<void> {
|
|
|
|
if (depth > maxDepth || visited.has(currentNoteId)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
visited.add(currentNoteId);
|
|
|
|
const parentNotes = await getParentNotes(currentNoteId, maxParents);
|
|
|
|
|
|
|
|
for (const parent of parentNotes) {
|
|
|
|
// Add parent with proper indentation
|
|
|
|
context += `${prefix}- ${parent.title}\n`;
|
|
|
|
|
|
|
|
// Recursively add parents of this parent with increased indentation
|
|
|
|
await buildHierarchy(parent.id, depth + 1, prefix + " ");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build the hierarchy starting from the current note
|
|
|
|
await buildHierarchy(noteId, 1);
|
|
|
|
|
|
|
|
// Add the current note at the end with appropriate indentation
|
|
|
|
if (includeCurrentNote) {
|
|
|
|
// Determine the indentation level based on hierarchy depth
|
|
|
|
let indentation = "";
|
|
|
|
if (context) {
|
|
|
|
// If we have parent context, add the current note with proper indentation
|
|
|
|
indentation = " ".repeat(1); // One level deeper than parents
|
2025-04-01 18:48:39 +00:00
|
|
|
context += `${indentation}> ${HIERARCHY_STRINGS.PARENT_CONTEXT.CURRENT_NOTE(note.title)}\n`;
|
2025-03-11 18:39:59 +00:00
|
|
|
} else {
|
|
|
|
// If no parents, just add the current note
|
2025-04-01 18:48:39 +00:00
|
|
|
context += `> ${HIERARCHY_STRINGS.PARENT_CONTEXT.CURRENT_NOTE(note.title)}\n`;
|
2025-03-11 18:39:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!context) {
|
2025-04-01 18:51:37 +00:00
|
|
|
return HIERARCHY_STRINGS.PARENT_CONTEXT.NO_PARENT_CONTEXT;
|
2025-03-11 18:39:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return context;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get context from child notes
|
|
|
|
*/
|
|
|
|
export async function getChildContext(
|
|
|
|
noteId: string,
|
|
|
|
maxChildren: number = 10,
|
|
|
|
includeContent: boolean = false
|
|
|
|
): Promise<string> {
|
|
|
|
const note = becca.getNote(noteId);
|
|
|
|
|
|
|
|
if (!note) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Get child notes using Becca API
|
|
|
|
const childNotes = note.getChildNotes();
|
|
|
|
|
|
|
|
if (!childNotes || childNotes.length === 0) {
|
2025-04-01 18:51:37 +00:00
|
|
|
return HIERARCHY_STRINGS.CHILD_CONTEXT.NO_CHILD_NOTES;
|
2025-03-11 18:39:59 +00:00
|
|
|
}
|
|
|
|
|
2025-04-01 18:48:39 +00:00
|
|
|
let context = `${HIERARCHY_STRINGS.CHILD_CONTEXT.CHILD_NOTES_HEADER(childNotes.length)}\n`;
|
2025-03-11 18:39:59 +00:00
|
|
|
|
|
|
|
// Limit the number of children included in context
|
|
|
|
const limitedChildren = childNotes.slice(0, maxChildren);
|
|
|
|
|
|
|
|
for (const childNote of limitedChildren) {
|
|
|
|
context += `- ${childNote.title}\n`;
|
|
|
|
|
|
|
|
// Optionally include a snippet of content
|
|
|
|
if (includeContent) {
|
|
|
|
try {
|
|
|
|
const content = String(await childNote.getContent() || "");
|
|
|
|
|
|
|
|
// Truncate and sanitize content
|
|
|
|
const truncatedContent = sanitizeHtmlContent(content)
|
|
|
|
.substring(0, 100)
|
|
|
|
.trim()
|
|
|
|
.replace(/\n/g, ' ');
|
|
|
|
|
|
|
|
if (truncatedContent) {
|
2025-04-01 18:51:37 +00:00
|
|
|
context += ` ${HIERARCHY_STRINGS.CHILD_CONTEXT.CHILD_SUMMARY_PREFIX}${truncatedContent}${truncatedContent.length >= 100 ? '...' : ''}\n`;
|
2025-03-11 18:39:59 +00:00
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// Silently skip content errors
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add note about truncation if needed
|
|
|
|
if (childNotes.length > maxChildren) {
|
2025-04-01 18:48:39 +00:00
|
|
|
context += `${HIERARCHY_STRINGS.CHILD_CONTEXT.MORE_CHILDREN(childNotes.length - maxChildren)}\n`;
|
2025-03-11 18:39:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return context;
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error getting child context for ${noteId}:`, error);
|
2025-04-01 18:51:37 +00:00
|
|
|
return HIERARCHY_STRINGS.CHILD_CONTEXT.ERROR_RETRIEVING;
|
2025-03-11 18:39:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get context from linked notes (relations)
|
|
|
|
*/
|
|
|
|
export async function getLinkedNotesContext(
|
|
|
|
noteId: string,
|
|
|
|
maxRelations: number = 10
|
|
|
|
): Promise<string> {
|
|
|
|
const note = becca.getNote(noteId);
|
|
|
|
|
|
|
|
if (!note) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Get all relations using Becca API
|
|
|
|
const relations = note.getRelations();
|
|
|
|
|
|
|
|
if (!relations || relations.length === 0) {
|
2025-04-01 18:51:37 +00:00
|
|
|
return HIERARCHY_STRINGS.LINKED_NOTES.NO_LINKED_NOTES;
|
2025-03-11 18:39:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get incoming relations as well
|
|
|
|
const incomingRelations = note.getTargetRelations();
|
|
|
|
|
|
|
|
let context = "";
|
|
|
|
|
|
|
|
// Handle outgoing relations
|
|
|
|
if (relations.length > 0) {
|
2025-04-01 18:48:39 +00:00
|
|
|
context += `${HIERARCHY_STRINGS.LINKED_NOTES.OUTGOING_RELATIONS_HEADER(relations.length)}\n`;
|
2025-03-11 18:39:59 +00:00
|
|
|
|
|
|
|
// Limit the number of relations included in context
|
|
|
|
const limitedRelations = relations.slice(0, maxRelations);
|
|
|
|
|
|
|
|
for (const relation of limitedRelations) {
|
|
|
|
const targetNote = becca.getNote(relation.value || "");
|
|
|
|
if (targetNote) {
|
2025-04-01 18:51:37 +00:00
|
|
|
const relationName = relation.name || HIERARCHY_STRINGS.LINKED_NOTES.DEFAULT_RELATION;
|
2025-03-11 18:39:59 +00:00
|
|
|
context += `- ${relationName} → ${targetNote.title}\n`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add note about truncation if needed
|
|
|
|
if (relations.length > maxRelations) {
|
2025-04-01 18:48:39 +00:00
|
|
|
context += `${HIERARCHY_STRINGS.LINKED_NOTES.MORE_OUTGOING(relations.length - maxRelations)}\n`;
|
2025-03-11 18:39:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle incoming relations
|
|
|
|
if (incomingRelations && incomingRelations.length > 0) {
|
|
|
|
if (context) context += "\n";
|
|
|
|
|
2025-04-01 18:48:39 +00:00
|
|
|
context += `${HIERARCHY_STRINGS.LINKED_NOTES.INCOMING_RELATIONS_HEADER(incomingRelations.length)}\n`;
|
2025-03-11 18:39:59 +00:00
|
|
|
|
|
|
|
// Limit the number of relations included in context
|
|
|
|
const limitedIncoming = incomingRelations.slice(0, maxRelations);
|
|
|
|
|
|
|
|
for (const relation of limitedIncoming) {
|
|
|
|
const sourceNote = becca.getNote(relation.value || "");
|
|
|
|
if (sourceNote) {
|
2025-04-01 18:51:37 +00:00
|
|
|
const relationName = relation.name || HIERARCHY_STRINGS.LINKED_NOTES.DEFAULT_RELATION;
|
2025-03-11 18:39:59 +00:00
|
|
|
context += `- ${sourceNote.title} → ${relationName}\n`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add note about truncation if needed
|
|
|
|
if (incomingRelations.length > maxRelations) {
|
2025-04-01 18:48:39 +00:00
|
|
|
context += `${HIERARCHY_STRINGS.LINKED_NOTES.MORE_INCOMING(incomingRelations.length - maxRelations)}\n`;
|
2025-03-11 18:39:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-01 18:51:37 +00:00
|
|
|
return context || HIERARCHY_STRINGS.LINKED_NOTES.NO_LINKED_NOTES;
|
2025-03-11 18:39:59 +00:00
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error getting linked notes context for ${noteId}:`, error);
|
2025-04-01 18:51:37 +00:00
|
|
|
return HIERARCHY_STRINGS.LINKED_NOTES.ERROR_RETRIEVING;
|
2025-03-11 18:39:59 +00:00
|
|
|
}
|
|
|
|
}
|