mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-29 02:52:27 +08:00
show user at the top of settings if there are issues
This commit is contained in:
parent
1844ad7b49
commit
fe1faf77e2
@ -56,6 +56,9 @@ export default class AiSettingsWidget extends OptionsWidget {
|
|||||||
<div class="options-section">
|
<div class="options-section">
|
||||||
<h4>${t("ai_llm.title")}</h4>
|
<h4>${t("ai_llm.title")}</h4>
|
||||||
|
|
||||||
|
<!-- Add warning alert div -->
|
||||||
|
<div class="provider-validation-warning alert alert-warning" style="display: none;"></div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="tn-checkbox">
|
<label class="tn-checkbox">
|
||||||
<input class="ai-enabled form-check-input" type="checkbox">
|
<input class="ai-enabled form-check-input" type="checkbox">
|
||||||
@ -335,6 +338,8 @@ export default class AiSettingsWidget extends OptionsWidget {
|
|||||||
$aiEnabled.on('change', async () => {
|
$aiEnabled.on('change', async () => {
|
||||||
await this.updateOption('aiEnabled', $aiEnabled.prop('checked') ? "true" : "false");
|
await this.updateOption('aiEnabled', $aiEnabled.prop('checked') ? "true" : "false");
|
||||||
this.updateAiSectionVisibility();
|
this.updateAiSectionVisibility();
|
||||||
|
// Display validation warnings when AI is enabled/disabled
|
||||||
|
await this.displayValidationWarnings();
|
||||||
});
|
});
|
||||||
|
|
||||||
const $ollamaEnabled = this.$widget.find('.ollama-enabled');
|
const $ollamaEnabled = this.$widget.find('.ollama-enabled');
|
||||||
@ -345,6 +350,8 @@ export default class AiSettingsWidget extends OptionsWidget {
|
|||||||
const $aiProviderPrecedence = this.$widget.find('.ai-provider-precedence');
|
const $aiProviderPrecedence = this.$widget.find('.ai-provider-precedence');
|
||||||
$aiProviderPrecedence.on('change', async () => {
|
$aiProviderPrecedence.on('change', async () => {
|
||||||
await this.updateOption('aiProviderPrecedence', $aiProviderPrecedence.val() as string);
|
await this.updateOption('aiProviderPrecedence', $aiProviderPrecedence.val() as string);
|
||||||
|
// Display validation warnings after changing precedence list
|
||||||
|
await this.displayValidationWarnings();
|
||||||
});
|
});
|
||||||
|
|
||||||
const $aiTemperature = this.$widget.find('.ai-temperature');
|
const $aiTemperature = this.$widget.find('.ai-temperature');
|
||||||
@ -481,6 +488,8 @@ export default class AiSettingsWidget extends OptionsWidget {
|
|||||||
const $embeddingDefaultProvider = this.$widget.find('.embedding-default-provider');
|
const $embeddingDefaultProvider = this.$widget.find('.embedding-default-provider');
|
||||||
$embeddingDefaultProvider.on('change', async () => {
|
$embeddingDefaultProvider.on('change', async () => {
|
||||||
await this.updateOption('embeddingsDefaultProvider', $embeddingDefaultProvider.val() as string);
|
await this.updateOption('embeddingsDefaultProvider', $embeddingDefaultProvider.val() as string);
|
||||||
|
// Display validation warnings after changing default provider
|
||||||
|
await this.displayValidationWarnings();
|
||||||
});
|
});
|
||||||
|
|
||||||
const $embeddingGenerationLocation = this.$widget.find('.embedding-generation-location');
|
const $embeddingGenerationLocation = this.$widget.find('.embedding-generation-location');
|
||||||
@ -593,6 +602,9 @@ export default class AiSettingsWidget extends OptionsWidget {
|
|||||||
this.$widget.find('.embedding-default-dimension').val(options.embeddingDefaultDimension || '1536');
|
this.$widget.find('.embedding-default-dimension').val(options.embeddingDefaultDimension || '1536');
|
||||||
|
|
||||||
this.updateAiSectionVisibility();
|
this.updateAiSectionVisibility();
|
||||||
|
|
||||||
|
// Call displayValidationWarnings instead of directly calling validateEmbeddingProviders
|
||||||
|
this.displayValidationWarnings();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAiSectionVisibility() {
|
updateAiSectionVisibility() {
|
||||||
@ -1003,5 +1015,99 @@ export default class AiSettingsWidget extends OptionsWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace displayValidationWarnings method with client-side implementation
|
||||||
|
async displayValidationWarnings() {
|
||||||
|
if (!this.$widget) return;
|
||||||
|
|
||||||
|
const $warningDiv = this.$widget.find('.provider-validation-warning');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get required data from current settings
|
||||||
|
const aiEnabled = this.$widget.find('.ai-enabled').prop('checked');
|
||||||
|
|
||||||
|
// If AI isn't enabled, don't show warnings
|
||||||
|
if (!aiEnabled) {
|
||||||
|
$warningDiv.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get default embedding provider
|
||||||
|
const defaultProvider = this.$widget.find('.embedding-default-provider').val() as string;
|
||||||
|
|
||||||
|
// Get provider precedence
|
||||||
|
const precedenceStr = this.$widget.find('.ai-provider-precedence').val() as string;
|
||||||
|
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
|
||||||
|
// Since we don't have direct access to DB from client, we'll use the UI state
|
||||||
|
// This is an approximation - enabled providers are generally those with API keys or enabled state
|
||||||
|
const enabledProviders: string[] = [];
|
||||||
|
|
||||||
|
// OpenAI is enabled if API key is set
|
||||||
|
const openaiKey = this.$widget.find('.openai-api-key').val() as string;
|
||||||
|
if (openaiKey) {
|
||||||
|
enabledProviders.push('openai');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anthropic is enabled if API key is set
|
||||||
|
const anthropicKey = this.$widget.find('.anthropic-api-key').val() as string;
|
||||||
|
if (anthropicKey) {
|
||||||
|
enabledProviders.push('anthropic');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ollama is enabled if checkbox is checked
|
||||||
|
const ollamaEnabled = this.$widget.find('.ollama-enabled').prop('checked');
|
||||||
|
if (ollamaEnabled) {
|
||||||
|
enabledProviders.push('ollama');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local is always available
|
||||||
|
enabledProviders.push('local');
|
||||||
|
|
||||||
|
// Perform validation checks
|
||||||
|
const defaultInPrecedence = precedenceList.includes(defaultProvider);
|
||||||
|
const defaultIsEnabled = enabledProviders.includes(defaultProvider);
|
||||||
|
const allPrecedenceEnabled = precedenceList.every(p => enabledProviders.includes(p));
|
||||||
|
|
||||||
|
// Build warning message if there are issues
|
||||||
|
if (!defaultInPrecedence || !defaultIsEnabled || !allPrecedenceEnabled) {
|
||||||
|
let message = 'There are issues with your AI provider configuration:';
|
||||||
|
|
||||||
|
if (!defaultInPrecedence) {
|
||||||
|
message += `<br>• The default embedding provider "${defaultProvider}" is not in your provider precedence list.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defaultIsEnabled) {
|
||||||
|
message += `<br>• The default embedding provider "${defaultProvider}" is not enabled.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allPrecedenceEnabled) {
|
||||||
|
const disabledProviders = precedenceList.filter(p => !enabledProviders.includes(p));
|
||||||
|
message += `<br>• The following providers in your precedence list are not enabled: ${disabledProviders.join(', ')}.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
message += '<br><br>Please check your AI settings.';
|
||||||
|
|
||||||
|
$warningDiv.html(message);
|
||||||
|
$warningDiv.show();
|
||||||
|
} else {
|
||||||
|
$warningDiv.hide();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error validating embedding providers:', error);
|
||||||
|
$warningDiv.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import log from '../log.js';
|
|||||||
import { ContextExtractor } from './context/index.js';
|
import { ContextExtractor } from './context/index.js';
|
||||||
import semanticContextService from './semantic_context_service.js';
|
import semanticContextService from './semantic_context_service.js';
|
||||||
import indexService from './index_service.js';
|
import indexService from './index_service.js';
|
||||||
|
import { getEmbeddingProvider, getEnabledEmbeddingProviders } from './embeddings/providers.js';
|
||||||
|
|
||||||
type ServiceProviders = 'openai' | 'anthropic' | 'ollama';
|
type ServiceProviders = 'openai' | 'anthropic' | 'ollama';
|
||||||
|
|
||||||
@ -50,8 +51,13 @@ export class AIServiceManager {
|
|||||||
if (customOrder.startsWith('[') && customOrder.endsWith(']')) {
|
if (customOrder.startsWith('[') && customOrder.endsWith(']')) {
|
||||||
parsed = JSON.parse(customOrder);
|
parsed = JSON.parse(customOrder);
|
||||||
} else if (typeof customOrder === 'string') {
|
} else if (typeof customOrder === 'string') {
|
||||||
// If it's a simple string (like "ollama"), convert to single-item array
|
// If it's a string with commas, split it
|
||||||
parsed = [customOrder];
|
if (customOrder.includes(',')) {
|
||||||
|
parsed = customOrder.split(',').map(p => p.trim());
|
||||||
|
} else {
|
||||||
|
// If it's a simple string (like "ollama"), convert to single-item array
|
||||||
|
parsed = [customOrder];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback to default
|
// Fallback to default
|
||||||
parsed = defaultOrder;
|
parsed = defaultOrder;
|
||||||
@ -74,6 +80,10 @@ export class AIServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
|
|
||||||
|
// Remove the validateEmbeddingProviders call since we now do validation on the client
|
||||||
|
// this.validateEmbeddingProviders();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If options table doesn't exist yet, use defaults
|
// If options table doesn't exist yet, use defaults
|
||||||
@ -83,6 +93,87 @@ export class AIServiceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate embedding providers configuration
|
||||||
|
* - Check if embedding default provider is in provider precedence list
|
||||||
|
* - Check if all providers in precedence list and default provider are enabled
|
||||||
|
*
|
||||||
|
* @returns A warning message if there are issues, or null if everything is fine
|
||||||
|
*/
|
||||||
|
async validateEmbeddingProviders(): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
// Check if AI is enabled, if not, skip validation
|
||||||
|
const aiEnabled = await options.getOptionBool('aiEnabled');
|
||||||
|
if (!aiEnabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get default embedding provider
|
||||||
|
const defaultProviderName = await options.getOption('embeddingsDefaultProvider') || 'openai';
|
||||||
|
|
||||||
|
// Parse provider precedence list (similar to updateProviderOrder)
|
||||||
|
let precedenceList: string[] = [];
|
||||||
|
const precedenceOption = await options.getOption('aiProviderPrecedence');
|
||||||
|
|
||||||
|
if (precedenceOption) {
|
||||||
|
if (precedenceOption.startsWith('[') && precedenceOption.endsWith(']')) {
|
||||||
|
precedenceList = JSON.parse(precedenceOption);
|
||||||
|
} else if (typeof precedenceOption === 'string') {
|
||||||
|
if (precedenceOption.includes(',')) {
|
||||||
|
precedenceList = precedenceOption.split(',').map(p => p.trim());
|
||||||
|
} else {
|
||||||
|
precedenceList = [precedenceOption];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get enabled providers
|
||||||
|
const enabledProviders = await getEnabledEmbeddingProviders();
|
||||||
|
const enabledProviderNames = enabledProviders.map(p => p.name);
|
||||||
|
|
||||||
|
// Check if default provider is in precedence list
|
||||||
|
const defaultInPrecedence = precedenceList.includes(defaultProviderName);
|
||||||
|
|
||||||
|
// Check if default provider is enabled
|
||||||
|
const defaultIsEnabled = enabledProviderNames.includes(defaultProviderName);
|
||||||
|
|
||||||
|
// Check if all providers in precedence list are enabled
|
||||||
|
const allPrecedenceEnabled = precedenceList.every(p =>
|
||||||
|
enabledProviderNames.includes(p) || p === 'local');
|
||||||
|
|
||||||
|
// Return warning message if there are issues
|
||||||
|
if (!defaultInPrecedence || !defaultIsEnabled || !allPrecedenceEnabled) {
|
||||||
|
let message = 'There are issues with your AI provider configuration:';
|
||||||
|
|
||||||
|
if (!defaultInPrecedence) {
|
||||||
|
message += `\n• The default embedding provider "${defaultProviderName}" is not in your provider precedence list.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defaultIsEnabled) {
|
||||||
|
message += `\n• The default embedding provider "${defaultProviderName}" is not enabled.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allPrecedenceEnabled) {
|
||||||
|
const disabledProviders = precedenceList.filter(p =>
|
||||||
|
!enabledProviderNames.includes(p) && p !== 'local');
|
||||||
|
message += `\n• The following providers in your precedence list are not enabled: ${disabledProviders.join(', ')}.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
message += '\n\nPlease check your AI settings.';
|
||||||
|
|
||||||
|
// Log warning to console
|
||||||
|
log.error('AI Provider Configuration Warning: ' + message);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
log.error(`Error validating embedding providers: ${error}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure manager is initialized before using
|
* Ensure manager is initialized before using
|
||||||
*/
|
*/
|
||||||
@ -182,7 +273,7 @@ export class AIServiceManager {
|
|||||||
getSemanticContextService() {
|
getSemanticContextService() {
|
||||||
return semanticContextService;
|
return semanticContextService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the index service for managing knowledge base indexing
|
* Get the index service for managing knowledge base indexing
|
||||||
* @returns The index service instance
|
* @returns The index service instance
|
||||||
@ -217,6 +308,10 @@ export default {
|
|||||||
async generateChatCompletion(messages: Message[], options: ChatCompletionOptions = {}): Promise<ChatResponse> {
|
async generateChatCompletion(messages: Message[], options: ChatCompletionOptions = {}): Promise<ChatResponse> {
|
||||||
return getInstance().generateChatCompletion(messages, options);
|
return getInstance().generateChatCompletion(messages, options);
|
||||||
},
|
},
|
||||||
|
// Add validateEmbeddingProviders method
|
||||||
|
async validateEmbeddingProviders(): Promise<string | null> {
|
||||||
|
return getInstance().validateEmbeddingProviders();
|
||||||
|
},
|
||||||
// Context and index related methods
|
// Context and index related methods
|
||||||
getContextExtractor() {
|
getContextExtractor() {
|
||||||
return getInstance().getContextExtractor();
|
return getInstance().getContextExtractor();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user