${this.formatToolArgs(toolExecutionData.args || {})}
`;
} else if (toolExecutionData.action === 'complete') {
// Tool execution completed
const resultPreview = this.formatToolResult(toolExecutionData.result);
stepHtml = `
${this.escapeHtml(toolExecutionData.error || 'Unknown error')}
`;
}
if (stepHtml) {
stepElement.innerHTML = stepHtml;
this.toolExecutionSteps.appendChild(stepElement);
// Scroll to bottom of tool execution steps
this.toolExecutionSteps.scrollTop = this.toolExecutionSteps.scrollHeight;
console.log(`Added new tool execution step to UI`);
} else {
console.log(`No HTML generated for tool execution data:`, toolExecutionData);
}
}
/**
* Format tool arguments for display
*/
private formatToolArgs(args: any): string {
if (!args || typeof args !== 'object') return '';
return Object.entries(args)
.map(([key, value]) => {
// Format the value based on its type
let displayValue;
if (typeof value === 'string') {
displayValue = value.length > 50 ? `"${value.substring(0, 47)}..."` : `"${value}"`;
} else if (value === null) {
displayValue = 'null';
} else if (Array.isArray(value)) {
displayValue = '[...]'; // Simplified array representation
} else if (typeof value === 'object') {
displayValue = '{...}'; // Simplified object representation
} else {
displayValue = String(value);
}
return `/g, (match) => {
const placeholder = `__THINKING_BLOCK_${thinkingBlocks.length}__`;
thinkingBlocks.push(match);
return placeholder;
});
// Use marked library to parse the markdown
const markedContent = marked(processedContent, {
breaks: true, // Convert line breaks to
gfm: true, // Enable GitHub Flavored Markdown
silent: true // Ignore errors
});
// Handle potential promise (though it shouldn't be with our options)
if (typeof markedContent === 'string') {
processedContent = markedContent;
} else {
console.warn('Marked returned a promise unexpectedly');
// Use the original content as fallback
processedContent = content;
}
// Restore thinking visualization blocks
thinkingBlocks.forEach((block, index) => {
processedContent = processedContent.replace(`__THINKING_BLOCK_${index}__`, block);
});
return processedContent;
}
/**
* Show thinking state in the UI
*/
private showThinkingState(thinkingData: string) {
// Update the UI to show thinking indicator
const thinking = typeof thinkingData === 'string' ? thinkingData : 'Thinking...';
const toolExecutionStep = document.createElement('div');
toolExecutionStep.className = 'tool-step my-1';
toolExecutionStep.innerHTML = `
${this.escapeHtml(thinking)}
`;
this.toolExecutionInfo.style.display = 'block';
this.toolExecutionSteps.appendChild(toolExecutionStep);
this.toolExecutionSteps.scrollTop = this.toolExecutionSteps.scrollHeight;
}
/**
* Validate embedding providers configuration
* Check if there are issues with the embedding providers that might affect LLM functionality
*/
async validateEmbeddingProviders() {
try {
// Check if AI is enabled
const aiEnabled = options.is('aiEnabled');
if (!aiEnabled) {
this.validationWarning.style.display = 'none';
return;
}
// Get provider precedence
const precedenceStr = options.get('aiProviderPrecedence') || 'openai,anthropic,ollama';
let precedenceList: string[] = [];
if (precedenceStr) {
if (precedenceStr.startsWith('[') && precedenceStr.endsWith(']')) {
precedenceList = JSON.parse(precedenceStr);
} else if (precedenceStr.includes(',')) {
precedenceList = precedenceStr.split(',').map(p => p.trim());
} else {
precedenceList = [precedenceStr];
}
}
// Get enabled providers - this is a simplification since we don't have direct DB access
// We'll determine enabled status based on the presence of keys or settings
const enabledProviders: string[] = [];
// OpenAI is enabled if API key is set
const openaiKey = options.get('openaiApiKey');
if (openaiKey) {
enabledProviders.push('openai');
}
// Anthropic is enabled if API key is set
const anthropicKey = options.get('anthropicApiKey');
if (anthropicKey) {
enabledProviders.push('anthropic');
}
// Ollama is enabled if base URL is set
const ollamaBaseUrl = options.get('ollamaBaseUrl');
if (ollamaBaseUrl) {
enabledProviders.push('ollama');
}
// Local is always available
enabledProviders.push('local');
// Perform validation checks
const allPrecedenceEnabled = precedenceList.every((p: string) => enabledProviders.includes(p));
// Get embedding queue status
const embeddingStats = await server.get('llm/embeddings/stats') as {
success: boolean,
stats: {
totalNotesCount: number;
embeddedNotesCount: number;
queuedNotesCount: number;
failedNotesCount: number;
lastProcessedDate: string | null;
percentComplete: number;
}
};
const queuedNotes = embeddingStats?.stats?.queuedNotesCount || 0;
const hasEmbeddingsInQueue = queuedNotes > 0;
// Show warning if there are issues
if (!allPrecedenceEnabled || hasEmbeddingsInQueue) {
let message = '
AI Provider Configuration Issues';
message += '
';
if (!allPrecedenceEnabled) {
const disabledProviders = precedenceList.filter((p: string) => !enabledProviders.includes(p));
message += `- The following providers in your precedence list are not enabled: ${disabledProviders.join(', ')}.
`;
}
if (hasEmbeddingsInQueue) {
message += `- Currently processing embeddings for ${queuedNotes} notes. Some AI features may produce incomplete results until processing completes.
`;
}
message += '
';
message += '
';
// Update HTML content - no need to attach event listeners here anymore
this.validationWarning.innerHTML = message;
this.validationWarning.style.display = 'block';
} else {
this.validationWarning.style.display = 'none';
}
} catch (error) {
console.error('Error validating embedding providers:', error);
this.validationWarning.style.display = 'none';
}
}
}