mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-30 19:19:03 +01: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 string with commas, split it
 | ||||||
|  |                         if (customOrder.includes(',')) { | ||||||
|  |                             parsed = customOrder.split(',').map(p => p.trim()); | ||||||
|  |                         } else { | ||||||
|                             // If it's a simple string (like "ollama"), convert to single-item array
 |                             // If it's a simple string (like "ollama"), convert to single-item array
 | ||||||
|                             parsed = [customOrder]; |                             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 | ||||||
|      */ |      */ | ||||||
| @ -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
	 perf3ct
						perf3ct