]*>(.*?)<\/code>/gi, replacement: '`$1`' },
+ CODE_BLOCK: { pattern: /]*>(.*?)<\/pre>/gi, replacement: '```\n$1\n```' },
+
+ // Clean up
+ ANY_REMAINING_TAG: { pattern: /<[^>]*>/g, replacement: '' },
+ EXCESSIVE_NEWLINES: { pattern: /\n{3,}/g, replacement: '\n\n' }
+};
+
+/**
+ * HTML entity replacements
+ */
+export const HTML_ENTITY_REPLACEMENTS = {
+ // Common HTML entities
+ NBSP: { pattern: / /g, replacement: ' ' },
+ LT: { pattern: /</g, replacement: '<' },
+ GT: { pattern: />/g, replacement: '>' },
+ AMP: { pattern: /&/g, replacement: '&' },
+ QUOT: { pattern: /"/g, replacement: '"' },
+ APOS: { pattern: /'/g, replacement: "'" },
+ LDQUO: { pattern: /“/g, replacement: '"' },
+ RDQUO: { pattern: /”/g, replacement: '"' },
+ LSQUO: { pattern: /‘/g, replacement: "'" },
+ RSQUO: { pattern: /’/g, replacement: "'" },
+ MDASH: { pattern: /—/g, replacement: 'â' },
+ NDASH: { pattern: /–/g, replacement: 'â' },
+ HELLIP: { pattern: /…/g, replacement: 'âŚ' }
+};
+
+/**
+ * Encoding issue fixes
+ */
+export const ENCODING_FIXES = {
+ // Common encoding issues
+ BROKEN_QUOTES: { pattern: /Î\u00c2[\u00a3\u00a5]/g, replacement: '"' },
+
+ // Character replacements for Unicode
+ UNICODE_REPLACEMENTS: {
+ '\u00A0': ' ', // Non-breaking space
+ '\u2018': "'", // Left single quote
+ '\u2019': "'", // Right single quote
+ '\u201C': '"', // Left double quote
+ '\u201D': '"', // Right double quote
+ '\u2013': '-', // En dash
+ '\u2014': '--', // Em dash
+ '\u2022': '*', // Bullet
+ '\u2026': '...' // Ellipsis
+ }
+};
+
+/**
+ * Ollama-specific cleaning patterns
+ */
+export const OLLAMA_CLEANING = {
+ // Replace fancy quotes
+ QUOTES: { pattern: /[""]/g, replacement: '"' },
+ APOSTROPHES: { pattern: /['']/g, replacement: "'" },
+
+ // Replace other Unicode characters
+ DASHES: { pattern: /[ââ]/g, replacement: '-' },
+ BULLETS: { pattern: /[â˘]/g, replacement: '*' },
+ ELLIPSES: { pattern: /[âŚ]/g, replacement: '...' },
+
+ // Remove non-ASCII characters
+ NON_ASCII: { pattern: /[^\x00-\x7F]/g, replacement: '' },
+
+ // Normalize whitespace
+ WHITESPACE: { pattern: /\s+/g, replacement: ' ' },
+ NEWLINE_WHITESPACE: { pattern: /\n\s+/g, replacement: '\n' }
+};
+
+/**
+ * Console log messages for formatters
+ */
+export const FORMATTER_LOGS = {
+ ANTHROPIC: {
+ PROCESSED: (before: number, after: number) => `Anthropic formatter: ${before} messages â ${after} messages`
+ },
+ OPENAI: {
+ PROCESSED: (before: number, after: number) => `OpenAI formatter: ${before} messages â ${after} messages`
+ },
+ OLLAMA: {
+ PROCESSED: (before: number, after: number) => `Ollama formatter processed ${before} messages into ${after} messages`
+ },
+ ERROR: {
+ CONTEXT_CLEANING: (provider: string) => `Error cleaning content for ${provider}:`,
+ ENCODING: 'Error fixing encoding issues:'
+ }
+};
+
+/**
+ * Message formatter text templates
+ */
+export const MESSAGE_FORMATTER_TEMPLATES = {
+ /**
+ * OpenAI-specific message templates
+ */
+ OPENAI: {
+ CONTEXT_INSTRUCTION: 'Please use the following context to respond to the user\'s messages:\n\n'
+ },
+
+ /**
+ * Anthropic-specific message templates
+ */
+ ANTHROPIC: {
+ CONTEXT_START: '\n\n\n',
+ CONTEXT_END: '\n'
+ },
+
+ /**
+ * Ollama-specific message templates
+ */
+ OLLAMA: {
+ REFERENCE_INFORMATION: '\n\nReference information:\n'
+ },
+
+ /**
+ * Default formatter message templates
+ */
+ DEFAULT: {
+ CONTEXT_INSTRUCTION: 'Here is context to help you answer my questions: '
+ }
+};
+
+/**
+ * Provider identifier constants
+ */
+export const PROVIDER_IDENTIFIERS = {
+ OPENAI: 'openai',
+ ANTHROPIC: 'anthropic',
+ OLLAMA: 'ollama',
+ DEFAULT: 'default'
+};
diff --git a/src/services/llm/constants/hierarchy_constants.ts b/src/services/llm/constants/hierarchy_constants.ts
new file mode 100644
index 000000000..40cbedd1f
--- /dev/null
+++ b/src/services/llm/constants/hierarchy_constants.ts
@@ -0,0 +1,35 @@
+/**
+ * Hierarchy Context Constants
+ *
+ * This file centralizes all strings used in the note hierarchy context
+ * functionality. These strings are used when displaying information about parent-child
+ * relationships and note relations in the LLM context building process.
+ */
+
+export const HIERARCHY_STRINGS = {
+ // Parent context strings
+ PARENT_CONTEXT: {
+ NO_PARENT_CONTEXT: 'No parent context available.',
+ CURRENT_NOTE: (title: string) => `${title} (current note)`,
+ },
+
+ // Child context strings
+ CHILD_CONTEXT: {
+ NO_CHILD_NOTES: 'No child notes.',
+ CHILD_NOTES_HEADER: (count: number) => `Child notes (${count} total)`,
+ CHILD_SUMMARY_PREFIX: 'Summary: ',
+ MORE_CHILDREN: (count: number) => `... and ${count} more child notes not shown`,
+ ERROR_RETRIEVING: 'Error retrieving child notes.'
+ },
+
+ // Linked notes context strings
+ LINKED_NOTES: {
+ NO_LINKED_NOTES: 'No linked notes.',
+ OUTGOING_RELATIONS_HEADER: (count: number) => `Outgoing relations (${count} total)`,
+ INCOMING_RELATIONS_HEADER: (count: number) => `Incoming relations (${count} total)`,
+ DEFAULT_RELATION: 'relates to',
+ MORE_OUTGOING: (count: number) => `... and ${count} more outgoing relations not shown`,
+ MORE_INCOMING: (count: number) => `... and ${count} more incoming relations not shown`,
+ ERROR_RETRIEVING: 'Error retrieving linked notes.'
+ }
+};
diff --git a/src/services/llm/constants/llm_prompt_constants.ts b/src/services/llm/constants/llm_prompt_constants.ts
new file mode 100644
index 000000000..68081f9c0
--- /dev/null
+++ b/src/services/llm/constants/llm_prompt_constants.ts
@@ -0,0 +1,298 @@
+/**
+ * LLM Prompt Constants
+ *
+ * This file centralizes all LLM/AI prompts used throughout the application.
+ * When adding new prompts, please add them here rather than hardcoding them in other files.
+ *
+ * Prompts are organized by their usage context (e.g., service, feature, etc.)
+ */
+
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+// Load system prompt from markdown file
+const loadSystemPrompt = (): string => {
+ try {
+ const __filename = fileURLToPath(import.meta.url);
+ const __dirname = path.dirname(__filename);
+
+ const promptPath = path.join(__dirname, '../prompts/base_system_prompt.md');
+ const promptContent = fs.readFileSync(promptPath, 'utf8');
+ // Strip the markdown title if needed
+ return promptContent.replace(/^# TriliumNext Base System Prompt\n+/, '');
+ } catch (error) {
+ console.error('Failed to load system prompt from file:', error);
+ // Return fallback prompt if file can't be loaded
+ return "You are a helpful assistant embedded in the TriliumNext Notes application. " +
+ "You can help users with their notes, answer questions, and provide information. " +
+ "Keep your responses concise and helpful. " +
+ "You're currently chatting with the user about their notes.";
+ }
+};
+
+// Base system prompt loaded from markdown file
+export const DEFAULT_SYSTEM_PROMPT = loadSystemPrompt();
+
+/**
+ * System prompts for different use cases
+ */
+export const SYSTEM_PROMPTS = {
+ DEFAULT_SYSTEM_PROMPT:
+ "You are an intelligent AI assistant for Trilium Notes, a hierarchical note-taking application. " +
+ "Help the user with their notes, knowledge management, and questions. " +
+ "When referencing their notes, be clear about which note you're referring to. " +
+ "Be concise but thorough in your responses.",
+
+ AGENT_TOOLS_PROMPT:
+ "You are an intelligent AI assistant for Trilium Notes with access to special tools. " +
+ "You can use these tools to search through the user's notes and find relevant information. " +
+ "Always be helpful, accurate, and respect the user's privacy and security.",
+
+ CONTEXT_AWARE_PROMPT:
+ "You are an intelligent AI assistant for Trilium Notes. " +
+ "You have access to the context from the user's notes. " +
+ "Use this context to provide accurate and helpful responses. " +
+ "Be specific when referencing information from their notes."
+};
+
+// Context-specific prompts
+export const CONTEXT_PROMPTS = {
+ // Query enhancer prompt for generating better search terms
+ QUERY_ENHANCER:
+ `You are an AI assistant that decides what information needs to be retrieved from a user's knowledge base called TriliumNext Notes to answer the user's question.
+Given the user's question, generate 3-5 specific search queries that would help find relevant information.
+Each query should be focused on a different aspect of the question.
+Avoid generating queries that are too broad, vague, or about a user's entire Note database, and make sure they are relevant to the user's question.
+Format your answer as a JSON array of strings, with each string being a search query.
+Example: ["exact topic mentioned", "related concept 1", "related concept 2"]`,
+
+ // Used to format notes context when providing responses
+ CONTEXT_NOTES_WRAPPER:
+ `I'll provide you with relevant information from my notes to help answer your question.
+
+
+{noteContexts}
+
+
+When referring to information from these notes in your response, please cite them by their titles (e.g., "According to your note on [Title]...") rather than using labels like "Note 1" or "Note 2".
+
+Now, based on the above information, please answer: {query}`,
+
+ // Default fallback when no notes are found
+ NO_NOTES_CONTEXT:
+ "I am an AI assistant helping you with your Trilium notes. " +
+ "I couldn't find any specific notes related to your query, but I'll try to assist you " +
+ "with general knowledge about Trilium or other topics you're interested in.",
+
+ // Fallback when context building fails
+ ERROR_FALLBACK_CONTEXT:
+ "I'm your AI assistant helping with your Trilium notes. I'll try to answer based on what I know.",
+
+ // Headers for context (by provider)
+ CONTEXT_HEADERS: {
+ ANTHROPIC: (query: string) =>
+ `I'm your AI assistant helping with your Trilium notes database. For your query: "${query}", I found these relevant `,
+ DEFAULT: (query: string) =>
+ `I've found some relevant information in your notes that may help answer: "${query}"\n\n`
+ },
+
+ // Closings for context (by provider)
+ CONTEXT_CLOSINGS: {
+ ANTHROPIC:
+ "\n\nPlease use this information to answer the user's query. If the notes don't contain enough information, you can use your general knowledge as well.",
+ DEFAULT:
+ "\n\nBased on this information from the user's notes, please provide a helpful response."
+ },
+
+ // Context for index service
+ INDEX_NO_NOTES_CONTEXT:
+ "I'm an AI assistant helping with your Trilium notes. I couldn't find specific notes related to your query, but I'll try to assist based on general knowledge.",
+
+ // Prompt for adding note context to chat
+ NOTE_CONTEXT_PROMPT: `Here is the content of the note I want to discuss:
+
+
+{context}
+
+
+Please help me with this information.`,
+
+ // Prompt for adding semantic note context to chat
+ SEMANTIC_NOTE_CONTEXT_PROMPT: `Here is the relevant information from my notes based on my query "{query}":
+
+
+{context}
+
+
+Please help me understand this information in relation to my query.`,
+
+ // System message prompt for context-aware chat
+ CONTEXT_AWARE_SYSTEM_PROMPT: `You are an AI assistant helping with Trilium Notes. Use this context to answer the user's question:
+
+
+{enhancedContext}
+`,
+
+ // Error messages
+ ERROR_MESSAGES: {
+ GENERAL_ERROR: `Error: Failed to generate response. {errorMessage}`,
+ CONTEXT_ERROR: `Error: Failed to generate response with note context. {errorMessage}`
+ },
+
+ // Merged from JS file
+ AGENT_TOOLS_CONTEXT_PROMPT:
+ "You have access to the following tools to help answer the user's question: {tools}"
+};
+
+// Agent tool prompts
+export const AGENT_TOOL_PROMPTS = {
+ // Prompts for query decomposition
+ QUERY_DECOMPOSITION: {
+ SUB_QUERY_DIRECT: 'Direct question that can be answered without decomposition',
+ SUB_QUERY_GENERIC: 'Generic exploration to find related content',
+ SUB_QUERY_ERROR: 'Error in decomposition, treating as simple query',
+ SUB_QUERY_DIRECT_ANALYSIS: 'Direct analysis of note details',
+ ORIGINAL_QUERY: 'Original query'
+ },
+
+ // Prompts for contextual thinking tool
+ CONTEXTUAL_THINKING: {
+ STARTING_ANALYSIS: (query: string) => `Starting analysis of the query: "${query}"`,
+ KEY_COMPONENTS: 'What are the key components of this query that need to be addressed?',
+ BREAKING_DOWN: 'Breaking down the query to understand its requirements and context.'
+ }
+};
+
+// Provider-specific prompt modifiers
+export const PROVIDER_PROMPTS = {
+ ANTHROPIC: {
+ // Anthropic Claude-specific prompt formatting
+ SYSTEM_WITH_CONTEXT: (context: string) =>
+ `
+${DEFAULT_SYSTEM_PROMPT}
+
+Use the following information from the user's notes to answer their questions:
+
+
+${context}
+
+
+When responding:
+- Focus on the most relevant information from the notes
+- Be concise and direct in your answers
+- If quoting from notes, mention which note it's from
+- If the notes don't contain relevant information, say so clearly
+`,
+
+ INSTRUCTIONS_WRAPPER: (instructions: string) =>
+ `\n${instructions}\n`,
+
+ ACKNOWLEDGMENT: "I understand. I'll follow those instructions.",
+ CONTEXT_ACKNOWLEDGMENT: "I'll help you with your notes based on the context provided.",
+ CONTEXT_QUERY_ACKNOWLEDGMENT: "I'll help you with your notes based on the context provided. What would you like to know?"
+ },
+
+ OPENAI: {
+ // OpenAI-specific prompt formatting
+ SYSTEM_WITH_CONTEXT: (context: string) =>
+ `
+You are an AI assistant integrated into TriliumNext Notes.
+Use the following information from the user's notes to answer their questions:
+
+
+${context}
+
+
+Focus on relevant information from these notes when answering.
+Be concise and informative in your responses.
+`
+ },
+
+ OLLAMA: {
+ // Ollama-specific prompt formatting
+ CONTEXT_INJECTION: (context: string, query: string) =>
+ `Here's information from my notes to help answer the question:
+
+${context}
+
+Based on this information, please answer: ${query}`
+ },
+
+ // Common prompts across providers
+ COMMON: {
+ DEFAULT_ASSISTANT_INTRO: "You are an AI assistant integrated into TriliumNext Notes. Focus on helping users find information in their notes and answering questions based on their knowledge base. Be concise, informative, and direct when responding to queries."
+ }
+};
+
+// Constants for formatting context and messages
+export const FORMATTING_PROMPTS = {
+ // Headers for context formatting
+ CONTEXT_HEADERS: {
+ SIMPLE: (query: string) => `I'm searching for information about: ${query}\n\nHere are the most relevant notes from my knowledge base:`,
+ DETAILED: (query: string) => `I'm searching for information about: "${query}"\n\nHere are the most relevant notes from my personal knowledge base:`
+ },
+
+ // Closing text for context formatting
+ CONTEXT_CLOSERS: {
+ SIMPLE: `\nEnd of notes. Please use this information to answer my question comprehensively.`,
+ DETAILED: `\nEnd of context information. Please use only the above notes to answer my question as comprehensively as possible.`
+ },
+
+ // Dividers used in context formatting
+ DIVIDERS: {
+ NOTE_SECTION: `------ NOTE INFORMATION ------`,
+ CONTENT_SECTION: `------ CONTEXT INFORMATION ------`,
+ NOTE_START: `# Note: `,
+ CONTENT_START: `Content: `
+ },
+
+ HTML_ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'code', 'pre']
+};
+
+// Prompt templates for chat service
+export const CHAT_PROMPTS = {
+ // Introduction messages for new chats
+ INTRODUCTIONS: {
+ NEW_CHAT: "Welcome to TriliumNext AI Assistant. How can I help you with your notes today?",
+ SEMANTIC_SEARCH: "I'll search through your notes for relevant information. What would you like to know?"
+ },
+
+ // Placeholders for various chat scenarios
+ PLACEHOLDERS: {
+ NO_CONTEXT: "I don't have any specific note context yet. Would you like me to search your notes for something specific?",
+ WAITING_FOR_QUERY: "Awaiting your question..."
+ }
+};
+
+// Error messages and fallbacks
+export const ERROR_PROMPTS = {
+ // User-facing error messages
+ USER_ERRORS: {
+ GENERAL_ERROR: "I encountered an error processing your request. Please try again or rephrase your question.",
+ CONTEXT_ERROR: "I couldn't retrieve context from your notes. Please check your query or try a different question.",
+ NETWORK_ERROR: "There was a network error connecting to the AI service. Please check your connection and try again.",
+ RATE_LIMIT: "The AI service is currently experiencing high demand. Please try again in a moment.",
+
+ // Merged from JS file
+ PROVIDER_ERROR:
+ "I'm sorry, but there seems to be an issue with the AI service provider. " +
+ "Please check your connection and API settings, or try again later."
+ },
+
+ // Internal error handling
+ INTERNAL_ERRORS: {
+ CONTEXT_PROCESSING: "Error processing context data",
+ MESSAGE_FORMATTING: "Error formatting messages for LLM",
+ RESPONSE_PARSING: "Error parsing LLM response"
+ },
+
+ // Merged from JS file
+ SYSTEM_ERRORS: {
+ NO_PROVIDER_AVAILABLE:
+ "No AI provider is available. Please check your AI settings and ensure at least one provider is configured properly.",
+
+ UNAUTHORIZED:
+ "The AI provider returned an authorization error. Please check your API key settings."
+ }
+};
diff --git a/src/services/llm/constants/provider_constants.ts b/src/services/llm/constants/provider_constants.ts
new file mode 100644
index 000000000..e1cccecc6
--- /dev/null
+++ b/src/services/llm/constants/provider_constants.ts
@@ -0,0 +1,215 @@
+export const PROVIDER_CONSTANTS = {
+ ANTHROPIC: {
+ API_VERSION: '2023-06-01',
+ BETA_VERSION: 'messages-2023-12-15',
+ BASE_URL: 'https://api.anthropic.com',
+ DEFAULT_MODEL: 'claude-3-haiku-20240307',
+ // Model mapping for simplified model names to their full versions
+ MODEL_MAPPING: {
+ 'claude-3.7-sonnet': 'claude-3-7-sonnet-20250219',
+ 'claude-3.5-sonnet': 'claude-3-5-sonnet-20241022',
+ 'claude-3.5-haiku': 'claude-3-5-haiku-20241022',
+ 'claude-3-opus': 'claude-3-opus-20240229',
+ 'claude-3-sonnet': 'claude-3-sonnet-20240229',
+ 'claude-3-haiku': 'claude-3-haiku-20240307',
+ 'claude-2': 'claude-2.1'
+ },
+ // These are the currently available models from Anthropic
+ AVAILABLE_MODELS: [
+ {
+ id: 'claude-3-7-sonnet-20250219',
+ name: 'Claude 3.7 Sonnet',
+ description: 'Most intelligent model with hybrid reasoning capabilities',
+ maxTokens: 8192
+ },
+ {
+ id: 'claude-3-5-sonnet-20241022',
+ name: 'Claude 3.5 Sonnet',
+ description: 'High level of intelligence and capability',
+ maxTokens: 8192
+ },
+ {
+ id: 'claude-3-5-haiku-20241022',
+ name: 'Claude 3.5 Haiku',
+ description: 'Fastest model with high intelligence',
+ maxTokens: 8192
+ },
+ {
+ id: 'claude-3-opus-20240229',
+ name: 'Claude 3 Opus',
+ description: 'Most capable model for highly complex tasks',
+ maxTokens: 8192
+ },
+ {
+ id: 'claude-3-sonnet-20240229',
+ name: 'Claude 3 Sonnet',
+ description: 'Ideal balance of intelligence and speed',
+ maxTokens: 8192
+ },
+ {
+ id: 'claude-3-haiku-20240307',
+ name: 'Claude 3 Haiku',
+ description: 'Fastest and most compact model',
+ maxTokens: 8192
+ },
+ {
+ id: 'claude-2.1',
+ name: 'Claude 2.1',
+ description: 'Previous generation model',
+ maxTokens: 8192
+ }
+ ]
+ },
+
+ OPENAI: {
+ BASE_URL: 'https://api.openai.com/v1',
+ DEFAULT_MODEL: 'gpt-3.5-turbo',
+ DEFAULT_EMBEDDING_MODEL: 'text-embedding-ada-002',
+ CONTEXT_WINDOW: 16000,
+ EMBEDDING_DIMENSIONS: {
+ ADA: 1536,
+ DEFAULT: 1536
+ },
+ AVAILABLE_MODELS: [
+ {
+ id: 'gpt-4o',
+ name: 'GPT-4o',
+ description: 'Most capable multimodal model',
+ maxTokens: 8192
+ },
+ {
+ id: 'gpt-4-turbo',
+ name: 'GPT-4 Turbo',
+ description: 'Advanced capabilities with higher token limit',
+ maxTokens: 8192
+ },
+ {
+ id: 'gpt-4',
+ name: 'GPT-4',
+ description: 'Original GPT-4 model',
+ maxTokens: 8192
+ },
+ {
+ id: 'gpt-3.5-turbo',
+ name: 'GPT-3.5 Turbo',
+ description: 'Fast and efficient model for most tasks',
+ maxTokens: 8192
+ }
+ ]
+ },
+
+ OLLAMA: {
+ BASE_URL: 'http://localhost:11434',
+ DEFAULT_MODEL: 'llama2',
+ BATCH_SIZE: 100,
+ CHUNKING: {
+ SIZE: 4000,
+ OVERLAP: 200
+ },
+ MODEL_DIMENSIONS: {
+ default: 8192,
+ llama2: 8192,
+ mixtral: 8192,
+ 'mistral': 8192
+ },
+ MODEL_CONTEXT_WINDOWS: {
+ default: 8192,
+ llama2: 8192,
+ mixtral: 8192,
+ 'mistral': 8192
+ }
+ }
+} as const;
+
+// LLM service configuration constants
+export const LLM_CONSTANTS = {
+ // Context window sizes (in characters)
+ CONTEXT_WINDOW: {
+ OLLAMA: 8000,
+ OPENAI: 12000,
+ ANTHROPIC: 15000,
+ VOYAGE: 12000,
+ DEFAULT: 6000
+ },
+
+ // Embedding dimensions (verify these with your actual models)
+ EMBEDDING_DIMENSIONS: {
+ OLLAMA: {
+ DEFAULT: 384,
+ NOMIC: 768,
+ MISTRAL: 1024
+ },
+ OPENAI: {
+ ADA: 1536,
+ DEFAULT: 1536
+ },
+ ANTHROPIC: {
+ CLAUDE: 1024,
+ DEFAULT: 1024
+ },
+ VOYAGE: {
+ DEFAULT: 1024
+ }
+ },
+
+ // Model-specific embedding dimensions for Ollama models
+ OLLAMA_MODEL_DIMENSIONS: {
+ "llama3": 8192,
+ "llama3.1": 8192,
+ "mistral": 8192,
+ "nomic": 768,
+ "mxbai": 1024,
+ "nomic-embed-text": 768,
+ "mxbai-embed-large": 1024,
+ "default": 384
+ },
+
+ // Model-specific context windows for Ollama models
+ OLLAMA_MODEL_CONTEXT_WINDOWS: {
+ "llama3": 8192,
+ "llama3.1": 8192,
+ "llama3.2": 8192,
+ "mistral": 8192,
+ "nomic": 32768,
+ "mxbai": 32768,
+ "nomic-embed-text": 32768,
+ "mxbai-embed-large": 32768,
+ "default": 8192
+ },
+
+ // Batch size configuration
+ BATCH_SIZE: {
+ OPENAI: 10, // OpenAI can handle larger batches efficiently
+ ANTHROPIC: 5, // More conservative for Anthropic
+ OLLAMA: 1, // Ollama processes one at a time
+ DEFAULT: 5 // Conservative default
+ },
+
+ // Chunking parameters
+ CHUNKING: {
+ DEFAULT_SIZE: 1500,
+ OLLAMA_SIZE: 1000,
+ DEFAULT_OVERLAP: 100,
+ MAX_SIZE_FOR_SINGLE_EMBEDDING: 5000
+ },
+
+ // Search/similarity thresholds
+ SIMILARITY: {
+ DEFAULT_THRESHOLD: 0.65,
+ HIGH_THRESHOLD: 0.75,
+ LOW_THRESHOLD: 0.5
+ },
+
+ // Session management
+ SESSION: {
+ CLEANUP_INTERVAL_MS: 60 * 60 * 1000, // 1 hour
+ SESSION_EXPIRY_MS: 12 * 60 * 60 * 1000, // 12 hours
+ MAX_SESSION_MESSAGES: 10
+ },
+
+ // Content limits
+ CONTENT: {
+ MAX_NOTE_CONTENT_LENGTH: 1500,
+ MAX_TOTAL_CONTENT_LENGTH: 10000
+ }
+};
diff --git a/src/services/llm/constants/query_decomposition_constants.ts b/src/services/llm/constants/query_decomposition_constants.ts
new file mode 100644
index 000000000..2c6df4386
--- /dev/null
+++ b/src/services/llm/constants/query_decomposition_constants.ts
@@ -0,0 +1,95 @@
+/**
+ * Query Decomposition Constants
+ *
+ * This file centralizes all string constants used in the query decomposition tool.
+ * These constants can be translated for internationalization support.
+ */
+
+export const QUERY_DECOMPOSITION_STRINGS = {
+ // Log messages
+ LOG_MESSAGES: {
+ DECOMPOSING_QUERY: (query: string) => `Decomposing query: "${query.substring(0, 100)}..."`,
+ EMPTY_QUERY: "Query decomposition called with empty query",
+ COMPLEXITY_ASSESSMENT: (complexity: number) => `Query complexity assessment: ${complexity}/10`,
+ SIMPLE_QUERY: (complexity: number) => `Query is simple (complexity ${complexity}), returning as single sub-query`,
+ DECOMPOSED_INTO: (count: number) => `Decomposed query into ${count} sub-queries`,
+ SUB_QUERY_LOG: (index: number, text: string, reason: string) => `Sub-query ${index + 1}: "${text}" - Reason: ${reason}`,
+ ERROR_DECOMPOSING: (error: string) => `Error decomposing query: ${error}`,
+ AVOIDING_RECURSIVE: (query: string) => `Avoiding recursive subqueries for query "${query.substring(0, 50)}..."`,
+ ERROR_SYNTHESIZING: (error: string) => `Error synthesizing answer: ${error}`
+ },
+
+ // Query identification patterns
+ QUERY_PATTERNS: {
+ PROVIDE_DETAILS_ABOUT: "provide details about",
+ INFORMATION_RELATED_TO: "information related to",
+ COMPARE: "compare",
+ DIFFERENCE_BETWEEN: "difference between",
+ VS: " vs ",
+ VERSUS: "versus",
+ HOW_TO: "how to ",
+ WHY: "why ",
+ WHAT_IS: "what is ",
+ WHAT_ARE: "what are "
+ },
+
+ // Question words used for complexity assessment
+ QUESTION_WORDS: ['what', 'how', 'why', 'where', 'when', 'who', 'which'],
+
+ // Conjunctions used for complexity assessment
+ CONJUNCTIONS: ['and', 'or', 'but', 'as well as'],
+
+ // Comparison terms used for complexity assessment
+ COMPARISON_TERMS: ['compare', 'versus', 'vs', 'difference', 'similarities'],
+
+ // Analysis terms used for complexity assessment
+ ANALYSIS_TERMS: ['analyze', 'examine', 'investigate', 'explore', 'explain', 'discuss'],
+
+ // Common stop words for parsing
+ STOP_WORDS: ['the', 'of', 'and', 'or', 'vs', 'versus', 'between', 'comparison', 'compared', 'to', 'with', 'what', 'is', 'are', 'how', 'why', 'when', 'which'],
+
+ // Sub-query templates
+ SUB_QUERY_TEMPLATES: {
+ INFORMATION_RELATED: (query: string) => `Information related to ${query}`,
+ KEY_CHARACTERISTICS: (entity: string) => `What are the key characteristics of ${entity}?`,
+ COMPARISON_FEATURES: (entities: string[]) => `How do ${entities.join(' and ')} compare in terms of their primary features?`,
+ STEPS_TO: (topic: string) => `What are the steps to ${topic}?`,
+ CHALLENGES: (topic: string) => `What are common challenges or pitfalls when trying to ${topic}?`,
+ CAUSES: (topic: string) => `What are the causes of ${topic}?`,
+ EVIDENCE: (topic: string) => `What evidence supports explanations for ${topic}?`,
+ DEFINITION: (topic: string) => `Definition of ${topic}`,
+ EXAMPLES: (topic: string) => `Examples of ${topic}`,
+ KEY_INFORMATION: (concept: string) => `Key information about ${concept}`
+ },
+
+ // Sub-query reasons
+ SUB_QUERY_REASONS: {
+ GETTING_DETAILS: (entity: string) => `Getting details about "${entity}" for comparison`,
+ DIRECT_COMPARISON: 'Direct comparison of the entities',
+ FINDING_PROCEDURAL: 'Finding procedural information',
+ IDENTIFYING_DIFFICULTIES: 'Identifying potential difficulties',
+ IDENTIFYING_CAUSES: 'Identifying causes',
+ FINDING_EVIDENCE: 'Finding supporting evidence',
+ GETTING_DEFINITION: 'Getting basic definition',
+ FINDING_EXAMPLES: 'Finding examples',
+ FINDING_INFORMATION: (concept: string) => `Finding information about "${concept}"`
+ },
+
+ // Synthesis answer templates
+ SYNTHESIS_TEMPLATES: {
+ CANNOT_SYNTHESIZE: "Cannot synthesize answer - not all sub-queries have been answered.",
+ ANSWER_TO: (query: string) => `Answer to: "${query}"\n\n`,
+ BASED_ON_INFORMATION: "Based on the information gathered:\n\n",
+ ERROR_SYNTHESIZING: "Error synthesizing the final answer."
+ },
+
+ // Query status templates
+ STATUS_TEMPLATES: {
+ PROGRESS: (answered: number, total: number) => `Progress: ${answered}/${total} sub-queries answered\n\n`,
+ ANSWERED_MARKER: "â",
+ UNANSWERED_MARKER: "â",
+ ANSWER_PREFIX: " Answer: "
+ }
+};
+
+export default QUERY_DECOMPOSITION_STRINGS;
diff --git a/src/services/llm/constants/search_constants.ts b/src/services/llm/constants/search_constants.ts
new file mode 100644
index 000000000..bc1689961
--- /dev/null
+++ b/src/services/llm/constants/search_constants.ts
@@ -0,0 +1,137 @@
+export const SEARCH_CONSTANTS = {
+ // Vector search parameters
+ VECTOR_SEARCH: {
+ DEFAULT_MAX_RESULTS: 10,
+ DEFAULT_THRESHOLD: 0.6,
+ SIMILARITY_THRESHOLD: {
+ COSINE: 0.6,
+ HYBRID: 0.3,
+ DIM_AWARE: 0.1
+ },
+ EXACT_MATCH_THRESHOLD: 0.65
+ },
+
+ // Context extraction parameters
+ CONTEXT: {
+ CONTENT_LENGTH: {
+ MEDIUM_THRESHOLD: 5000,
+ HIGH_THRESHOLD: 10000
+ },
+ MAX_PARENT_DEPTH: 3,
+ MAX_CHILDREN: 10,
+ MAX_LINKS: 10,
+ MAX_SIMILAR_NOTES: 5,
+ MAX_CONTENT_LENGTH: 2000,
+ MAX_RELATIONS: 10,
+ MAX_POINTS: 5
+ },
+
+ // Hierarchy parameters
+ HIERARCHY: {
+ DEFAULT_QUERY_DEPTH: 2,
+ MAX_NOTES_PER_QUERY: 10,
+ MAX_PATH_LENGTH: 20,
+ MAX_BREADTH: 100,
+ MAX_DEPTH: 5,
+ MAX_PATHS_TO_SHOW: 3
+ },
+
+ // Temperature settings
+ TEMPERATURE: {
+ DEFAULT: 0.7,
+ RELATIONSHIP_TOOL: 0.4,
+ VECTOR_SEARCH: 0.3,
+ QUERY_PROCESSOR: 0.3
+ },
+
+ // Token/char limits
+ LIMITS: {
+ DEFAULT_NOTE_SUMMARY_LENGTH: 500,
+ DEFAULT_MAX_TOKENS: 4096,
+ RELATIONSHIP_TOOL_MAX_TOKENS: 50,
+ VECTOR_SEARCH_MAX_TOKENS: 500,
+ QUERY_PROCESSOR_MAX_TOKENS: 300,
+ MIN_STRING_LENGTH: 3
+ },
+
+ // Tool execution parameters
+ TOOL_EXECUTION: {
+ MAX_TOOL_CALL_ITERATIONS: 5,
+ MAX_FOLLOW_UP_ITERATIONS: 3
+ }
+};
+
+// Model capabilities constants - moved from ./interfaces/model_capabilities.ts
+export const MODEL_CAPABILITIES = {
+ 'gpt-3.5-turbo': {
+ contextWindowTokens: 8192,
+ contextWindowChars: 16000
+ },
+ 'gpt-4': {
+ contextWindowTokens: 8192
+ },
+ 'gpt-4-turbo': {
+ contextWindowTokens: 8192
+ },
+ 'claude-3-opus': {
+ contextWindowTokens: 200000
+ },
+ 'claude-3-sonnet': {
+ contextWindowTokens: 180000
+ },
+ 'claude-3.5-sonnet': {
+ contextWindowTokens: 200000
+ },
+ 'default': {
+ contextWindowTokens: 4096
+ }
+};
+
+// Embedding processing constants
+export const EMBEDDING_PROCESSING = {
+ MAX_TOTAL_PROCESSING_TIME: 5 * 60 * 1000, // 5 minutes
+ MAX_CHUNK_RETRY_ATTEMPTS: 2,
+ DEFAULT_MAX_CHUNK_PROCESSING_TIME: 60 * 1000, // 1 minute
+ OLLAMA_MAX_CHUNK_PROCESSING_TIME: 120 * 1000, // 2 minutes
+ DEFAULT_EMBEDDING_UPDATE_INTERVAL: 200
+};
+
+// Provider-specific embedding capabilities
+export const PROVIDER_EMBEDDING_CAPABILITIES = {
+ VOYAGE: {
+ MODELS: {
+ 'voyage-large-2': {
+ contextWidth: 8192,
+ dimension: 1536
+ },
+ 'voyage-2': {
+ contextWidth: 8192,
+ dimension: 1024
+ },
+ 'voyage-lite-02': {
+ contextWidth: 8192,
+ dimension: 768
+ },
+ 'default': {
+ contextWidth: 8192,
+ dimension: 1024
+ }
+ }
+ },
+ OPENAI: {
+ MODELS: {
+ 'text-embedding-3-small': {
+ dimension: 1536,
+ contextWindow: 8191
+ },
+ 'text-embedding-3-large': {
+ dimension: 3072,
+ contextWindow: 8191
+ },
+ 'default': {
+ dimension: 1536,
+ contextWindow: 8192
+ }
+ }
+ }
+};
diff --git a/src/services/llm/context/code_handlers.ts b/src/services/llm/context/code_handlers.ts
new file mode 100644
index 000000000..f4b1fca97
--- /dev/null
+++ b/src/services/llm/context/code_handlers.ts
@@ -0,0 +1,438 @@
+/**
+ * Helper functions for processing code notes, including language detection and structure extraction
+ */
+
+// Import highlight.js dynamically when needed
+let hljs: object | null = null;
+
+/**
+ * Attempt to detect the programming language from code content or note attributes
+ */
+export function detectLanguage(content: string, mime: string): string {
+ // First check MIME type for hints
+ if (mime) {
+ const mimeLower = mime.toLowerCase();
+
+ // Map of mime types to language names
+ const mimeMap: {[key: string]: string} = {
+ 'text/javascript': 'javascript',
+ 'application/javascript': 'javascript',
+ 'text/typescript': 'typescript',
+ 'application/typescript': 'typescript',
+ 'text/x-python': 'python',
+ 'text/x-java': 'java',
+ 'text/x-c': 'c',
+ 'text/x-c++': 'cpp',
+ 'text/x-csharp': 'csharp',
+ 'text/x-go': 'go',
+ 'text/x-ruby': 'ruby',
+ 'text/x-php': 'php',
+ 'text/x-rust': 'rust',
+ 'text/x-swift': 'swift',
+ 'text/x-kotlin': 'kotlin',
+ 'text/x-scala': 'scala',
+ 'text/x-perl': 'perl',
+ 'text/x-lua': 'lua',
+ 'text/x-r': 'r',
+ 'text/x-dart': 'dart',
+ 'text/html': 'html',
+ 'text/css': 'css',
+ 'application/json': 'json',
+ 'application/xml': 'xml',
+ 'text/markdown': 'markdown',
+ 'text/yaml': 'yaml',
+ 'text/x-sql': 'sql'
+ };
+
+ if (mimeMap[mimeLower]) {
+ return mimeMap[mimeLower];
+ }
+ }
+
+ // Fallback to regex-based detection if highlight.js is not available or fails
+ // Check for common language patterns in the first few lines
+ const firstLines = content.split('\n').slice(0, 10).join('\n');
+
+ // Simple heuristics for common languages
+ if (firstLines.includes('') || firstLines.includes('')) return 'html';
+ if (firstLines.includes('function ') && firstLines.includes('var ') && firstLines.includes('const ')) return 'javascript';
+ if (firstLines.includes('interface ') && firstLines.includes('export class ')) return 'typescript';
+ if (firstLines.includes('@Component') || firstLines.includes('import { Component }')) return 'typescript';
+
+ // Default to 'text' if language can't be determined
+ return 'text';
+}
+
+/**
+ * Extract structure from code to create a summary
+ */
+export function extractCodeStructure(content: string, language: string): string {
+ // Avoid processing very large code files
+ if (content.length > 100000) {
+ return "Code content too large for structure extraction";
+ }
+
+ let structure = "";
+
+ try {
+ switch (language.toLowerCase()) {
+ case 'javascript':
+ case 'typescript':
+ structure = extractJsStructure(content);
+ break;
+
+ case 'python':
+ structure = extractPythonStructure(content);
+ break;
+
+ case 'java':
+ case 'csharp':
+ case 'cpp':
+ structure = extractClassBasedStructure(content);
+ break;
+
+ case 'go':
+ structure = extractGoStructure(content);
+ break;
+
+ case 'rust':
+ structure = extractRustStructure(content);
+ break;
+
+ case 'html':
+ structure = extractHtmlStructure(content);
+ break;
+
+ default:
+ // For other languages, just return a summary of the file size and a few lines
+ const lines = content.split('\n');
+ structure = `Code file with ${lines.length} lines.\n`;
+
+ // Add first few non-empty lines that aren't comments
+ const firstCodeLines = lines.filter(line =>
+ line.trim() !== '' &&
+ !line.trim().startsWith('//') &&
+ !line.trim().startsWith('#') &&
+ !line.trim().startsWith('*') &&
+ !line.trim().startsWith('