add missing translation strings

This commit is contained in:
perf3ct 2025-03-30 19:20:33 +00:00
parent c701bc5b0b
commit d8d41a14cf
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
3 changed files with 44 additions and 415 deletions

View File

@ -136,9 +136,7 @@ export default class AiSettingsWidget extends OptionsWidget {
this.setupChangeHandler('.embedding-dimension-strategy', 'embeddingDimensionStrategy');
this.setupChangeHandler('.embedding-provider-precedence', 'embeddingProviderPrecedence', true);
// Set up sortable behavior for the embedding provider precedence list
this.setupEmbeddingProviderSortable();
this.setupAiProviderSortable();
// No sortable behavior needed anymore
// Embedding stats refresh button
const $refreshStats = this.$widget.find('.embedding-refresh-stats');
@ -433,369 +431,6 @@ export default class AiSettingsWidget extends OptionsWidget {
}
}
/**
* Setup sortable behavior for embedding provider precedence
*/
setupEmbeddingProviderSortable() {
if (!this.$widget) return;
const $embeddingProviderPrecedence = this.$widget.find('.embedding-provider-precedence');
const $sortableList = this.$widget.find('.embedding-provider-sortable');
const $items = $sortableList.find('li');
// Make list items draggable
$items.each((index, item) => this.setupEmbeddingProviderItemDragHandlers($(item)));
// Setup the remove buttons
this.setupEmbeddingProviderRemoveHandlers();
// Setup disabled providers list restore handlers
this.$widget.find('.embedding-provider-disabled li').each((index, item) => {
this.setupEmbeddingProviderRestoreHandler($(item));
});
// Initialize the order based on saved value
this.initializeEmbeddingProviderOrder();
}
/**
* Setup sortable behavior for AI provider precedence
*/
setupAiProviderSortable() {
if (!this.$widget) return;
const $aiProviderPrecedence = this.$widget.find('.ai-provider-precedence');
const $sortableList = this.$widget.find('.provider-sortable');
const $items = $sortableList.find('li');
// Make list items draggable
$items.each((index, item) => this.setupAiItemDragHandlers($(item)));
// Setup the remove buttons
this.setupAiProviderRemoveHandlers();
// Setup disabled providers list restore handlers
this.$widget.find('.provider-disabled li').each((index, item) => {
this.setupAiProviderRestoreHandler($(item));
});
// Initialize the order based on saved value
this.initializeAiProviderOrder();
}
/**
* Setup drag handlers for an embedding provider list item
*/
setupEmbeddingProviderItemDragHandlers($item: JQuery) {
if (!this.$widget) return;
const self = this;
const $embeddingProviderPrecedence = this.$widget.find('.embedding-provider-precedence');
const $embeddingSortableList = this.$widget.find('.embedding-provider-sortable');
// Setup dragstart handler
$item.on('dragstart', function(e: JQuery.DragStartEvent) {
$(this).addClass('dragging');
e.originalEvent?.dataTransfer?.setData('text/plain', '');
});
// Setup dragend handler
$item.on('dragend', function() {
$(this).removeClass('dragging');
// Update the hidden input value
const providers = $embeddingSortableList.find('li').map(function() {
return $(this).data('provider');
}).get().join(',');
// Only update if we have providers or if the current value isn't empty
// This prevents setting an empty string when all providers are removed
if (providers || $embeddingProviderPrecedence.val()) {
$embeddingProviderPrecedence.val(providers);
$embeddingProviderPrecedence.trigger('change');
}
});
// Additional drag event handlers ...
// All other drag event handlers would be implemented here
}
/**
* Setup event handlers for embedding provider remove buttons
*/
setupEmbeddingProviderRemoveHandlers() {
if (!this.$widget) return;
const self = this;
const $embeddingProviderPrecedence = this.$widget.find('.embedding-provider-precedence');
const $embeddingSortableList = this.$widget.find('.embedding-provider-sortable');
// Remove any existing handlers to prevent duplicates
this.$widget.find('.remove-provider').off('click');
// Add handlers
this.$widget.find('.remove-provider').on('click', function() {
const $item = $(this).closest('li');
const provider = $item.data('provider');
const providerName = self.getProviderDisplayName(provider);
// Create a new item for the disabled list
const $disabledItem = $(`
<li class="standard-list-item d-flex align-items-center" data-provider="${provider}">
<strong class="flex-grow-1">${providerName}</strong>
<button class="icon-action restore-provider" title="${t("ai_llm.restore_provider")}">
<span class="bx bx-plus"></span>
</button>
</li>
`);
// Move to disabled list
self.$widget?.find('.embedding-provider-disabled').append($disabledItem);
self.setupEmbeddingProviderRestoreHandler($disabledItem);
$item.remove();
// Update the precedence value
const providers = $embeddingSortableList.find('li').map(function() {
return $(this).data('provider');
}).get().join(',');
$embeddingProviderPrecedence.val(providers);
$embeddingProviderPrecedence.trigger('change');
// Show disabled providers container
self.$widget?.find('.disabled-providers-container').show();
});
}
/**
* Setup event handler for embedding provider restore button
*/
setupEmbeddingProviderRestoreHandler($item: JQuery) {
if (!this.$widget) return;
const self = this;
const $embeddingProviderPrecedence = this.$widget.find('.embedding-provider-precedence');
const $embeddingSortableList = this.$widget.find('.embedding-provider-sortable');
// Remove any existing handlers to prevent duplicates
$item.find('.restore-provider').off('click');
// Add handlers
$item.find('.restore-provider').on('click', function() {
const $disabledItem = $(this).closest('li');
const provider = $disabledItem.data('provider');
const providerName = self.getProviderDisplayName(provider);
// Create a new item for the active list
const $activeItem = $(`
<li class="standard-list-item d-flex align-items-center" data-provider="${provider}" draggable="true">
<span class="drag-handle bx bx-dots-vertical-rounded me-2"></span>
<strong class="flex-grow-1">${providerName}</strong>
<button class="icon-action remove-provider" title="${t("ai_llm.remove_provider")}">
<span class="bx bx-x"></span>
</button>
</li>
`);
// Move to active list
$embeddingSortableList.append($activeItem);
self.setupEmbeddingProviderItemDragHandlers($activeItem);
self.setupEmbeddingProviderRemoveHandlers();
$disabledItem.remove();
// Update the precedence value
const providers = $embeddingSortableList.find('li').map(function() {
return $(this).data('provider');
}).get().join(',');
$embeddingProviderPrecedence.val(providers);
$embeddingProviderPrecedence.trigger('change');
// Hide disabled providers container if it's now empty
if (self.$widget?.find('.embedding-provider-disabled li').length === 0) {
self.$widget?.find('.disabled-providers-container').hide();
}
});
}
/**
* Initialize the embedding provider precedence order based on saved values
*/
initializeEmbeddingProviderOrder() {
if (!this.$widget) return;
const $embeddingProviderPrecedence = this.$widget.find('.embedding-provider-precedence');
const $sortableList = this.$widget.find('.embedding-provider-sortable');
// Get the current value
const savedValue = $embeddingProviderPrecedence.val() as string;
// If no saved value, don't proceed with initialization to avoid triggering the "empty" change
if (!savedValue) return;
// Get all available providers
const allProviders = ['openai', 'voyage', 'ollama', 'local'];
const savedProviders = savedValue.split(',');
// Clear all items from the disabled list first to avoid duplicates
this.$widget.find('.embedding-provider-disabled').empty();
// Find disabled providers (providers in allProviders but not in savedProviders)
const disabledProviders = allProviders.filter(p => !savedProviders.includes(p));
// Move saved providers to the end in the correct order
savedProviders.forEach(provider => {
const $item = $sortableList.find(`li[data-provider="${provider}"]`);
if ($item.length) {
$sortableList.append($item); // Move to the end in the correct order
}
});
// Setup remove click handlers first to ensure they work when simulating clicks
this.setupEmbeddingProviderRemoveHandlers();
// Move disabled providers to the disabled list
disabledProviders.forEach(provider => {
const $item = $sortableList.find(`li[data-provider="${provider}"]`);
if ($item.length) {
// Simulate clicking the remove button to move it to the disabled list
$item.find('.remove-provider').trigger('click');
} else {
// If it's not in the active list already, manually create it in the disabled list
const providerName = this.getProviderDisplayName(provider);
const $disabledItem = $(`
<li class="standard-list-item d-flex align-items-center" data-provider="${provider}">
<strong class="flex-grow-1">${providerName}</strong>
<button class="icon-action restore-provider" title="${t("ai_llm.restore_provider")}">
<span class="bx bx-plus"></span>
</button>
</li>
`);
this.$widget.find('.embedding-provider-disabled').append($disabledItem);
// Add restore button handler
this.setupEmbeddingProviderRestoreHandler($disabledItem);
}
});
// Show/hide the disabled providers container
const $disabledContainer = this.$widget.find('.disabled-providers-container');
const hasDisabledProviders = this.$widget.find('.embedding-provider-disabled li').length > 0;
$disabledContainer.toggle(hasDisabledProviders);
}
/**
* Setup drag handlers for an AI provider list item
*/
setupAiItemDragHandlers($item: JQuery) {
if (!this.$widget) return;
const self = this;
const $aiProviderPrecedence = this.$widget.find('.ai-provider-precedence');
const $aiSortableList = this.$widget.find('.provider-sortable');
// Setup dragstart handler
$item.on('dragstart', function(e: JQuery.DragStartEvent) {
$(this).addClass('dragging');
e.originalEvent?.dataTransfer?.setData('text/plain', '');
});
// Setup dragend handler
$item.on('dragend', function() {
$(this).removeClass('dragging');
// Update the hidden input value
const providers = $aiSortableList.find('li').map(function() {
return $(this).data('provider');
}).get().join(',');
$aiProviderPrecedence.val(providers);
$aiProviderPrecedence.trigger('change');
});
// Additional drag event handlers would go here...
}
/**
* Initialize the AI provider precedence order based on saved values
*/
initializeAiProviderOrder() {
if (!this.$widget) return;
const $aiProviderPrecedence = this.$widget.find('.ai-provider-precedence');
const $aiSortableList = this.$widget.find('.provider-sortable');
// Get the current value
const savedValue = $aiProviderPrecedence.val() as string;
if (!savedValue) return;
// Get all available providers
const allProviders = ['openai', 'anthropic', 'ollama', 'voyage'];
const savedProviders = savedValue.split(',');
// Clear all items from the disabled list first to avoid duplicates
this.$widget.find('.provider-disabled').empty();
// Find disabled providers (providers in allProviders but not in savedProviders)
const disabledProviders = allProviders.filter(p => !savedProviders.includes(p));
// Move saved providers to the end in the correct order
savedProviders.forEach(provider => {
const $item = $aiSortableList.find(`li[data-provider="${provider}"]`);
if ($item.length) {
$aiSortableList.append($item); // Move to the end in the correct order
}
});
// Setup remove click handlers first to ensure they work when simulating clicks
this.setupAiProviderRemoveHandlers();
// Move disabled providers to the disabled list
disabledProviders.forEach(provider => {
const $item = $aiSortableList.find(`li[data-provider="${provider}"]`);
if ($item.length) {
// Simulate clicking the remove button to move it to the disabled list
$item.find('.remove-ai-provider').trigger('click');
} else {
// If it's not in the active list already, manually create it in the disabled list
const providerName = this.getProviderDisplayName(provider);
const $disabledItem = $(`
<li class="standard-list-item d-flex align-items-center" data-provider="${provider}">
<strong class="flex-grow-1">${providerName}</strong>
<button class="icon-action restore-ai-provider" title="${t("ai_llm.restore_provider")}">
<span class="bx bx-plus"></span>
</button>
</li>
`);
this.$widget.find('.provider-disabled').append($disabledItem);
// Add restore button handler
this.setupAiProviderRestoreHandler($disabledItem);
}
});
// Show/hide the disabled providers container
const $disabledContainer = this.$widget.find('.disabled-ai-providers-container');
const hasDisabledProviders = this.$widget.find('.provider-disabled li').length > 0;
$disabledContainer.toggle(hasDisabledProviders);
}
/**
* Setup event handlers for AI provider remove buttons
*/
setupAiProviderRemoveHandlers() {
if (!this.$widget) return;
// Implementation would go here...
}
/**
* Setup event handler for AI provider restore button
*/
setupAiProviderRestoreHandler($item: JQuery) {
if (!this.$widget) return;
// Implementation would go here...
}
/**
* Called when the options have been loaded from the server
*/
@ -836,10 +471,6 @@ export default class AiSettingsWidget extends OptionsWidget {
this.$widget.find('.max-notes-per-llm-query').val(options.maxNotesPerLlmQuery || '3');
this.$widget.find('.embedding-dimension-strategy').val(options.embeddingDimensionStrategy || 'auto');
// Initialize sortable lists
this.initializeEmbeddingProviderOrder();
this.initializeAiProviderOrder();
// Display validation warnings
this.displayValidationWarnings();
}

View File

@ -277,50 +277,9 @@ export const TPL = `
<div class="form-text">${t("ai_llm.rebuild_index_description")}</div>
</div>
<!-- Provider order container -->
<!-- Note about embedding provider precedence -->
<div class="form-group mt-3">
<h5>${t("ai_llm.provider_order")}</h5>
<div class="form-text">${t("ai_llm.provider_order_description")}</div>
<div class="provider-order-container">
<ul class="embedding-provider-sortable standard-list mt-2">
<li class="standard-list-item d-flex align-items-center" data-provider="openai" draggable="true">
<span class="drag-handle bx bx-dots-vertical-rounded me-2"></span>
<strong class="flex-grow-1">OpenAI</strong>
<button class="icon-action remove-provider" title="${t("ai_llm.remove_provider")}">
<span class="bx bx-x"></span>
</button>
</li>
<li class="standard-list-item d-flex align-items-center" data-provider="voyage" draggable="true">
<span class="drag-handle bx bx-dots-vertical-rounded me-2"></span>
<strong class="flex-grow-1">Voyage</strong>
<button class="icon-action remove-provider" title="${t("ai_llm.remove_provider")}">
<span class="bx bx-x"></span>
</button>
</li>
<li class="standard-list-item d-flex align-items-center" data-provider="ollama" draggable="true">
<span class="drag-handle bx bx-dots-vertical-rounded me-2"></span>
<strong class="flex-grow-1">Ollama</strong>
<button class="icon-action remove-provider" title="${t("ai_llm.remove_provider")}">
<span class="bx bx-x"></span>
</button>
</li>
<li class="standard-list-item d-flex align-items-center" data-provider="local" draggable="true">
<span class="drag-handle bx bx-dots-vertical-rounded me-2"></span>
<strong class="flex-grow-1">Local</strong>
<button class="icon-action remove-provider" title="${t("ai_llm.remove_provider")}">
<span class="bx bx-x"></span>
</button>
</li>
</ul>
</div>
<!-- Disabled providers container -->
<div class="disabled-providers-container" style="display: none;">
<h6 class="mt-3">${t("ai_llm.disabled_providers")}</h6>
<ul class="embedding-provider-disabled standard-list mt-2">
<!-- Disabled providers will be added here -->
</ul>
</div>
<h5>${t("ai_llm.embedding_providers_order")}</h5>
<div class="form-text mt-2">${t("ai_llm.embedding_providers_order_description")}</div>
</div>
</div>`;

View File

@ -1123,7 +1123,19 @@
"layout-horizontal-description": "launcher bar is underneath the tab bar, the tab bar is now full width."
},
"ai_llm": {
"embeddings_configuration": "Embeddings Configuration",
"title": "AI & Embedding Settings",
"embedding_statistics": "Embedding Statistics",
"processed_notes": "Processed Notes",
"total_notes": "Total Notes",
"progress": "Progress",
"queued_notes": "Queued Notes",
"failed_notes": "Failed Notes",
"last_processed": "Last Processed",
"refresh_stats": "Refresh Statistics",
"no_failed_embeddings": "No failed embeddings found.",
"enable_ai_features": "Enable AI/LLM features",
"enable_ai_description": "Enable AI features like note summarization, content generation, and other LLM capabilities",
"openai_tab": "OpenAI",
"anthropic_tab": "Anthropic",
"voyage_tab": "Voyage AI",
@ -1142,6 +1154,9 @@
"openai_configuration": "OpenAI Configuration",
"openai_settings": "OpenAI Settings",
"api_key": "API Key",
"url": "Base URL",
"model": "Model",
"refresh_models": "Refresh Models",
"openai_api_key_description": "Your OpenAI API key for accessing their AI services",
"anthropic_api_key_description": "Your Anthropic API key for accessing Claude models",
"default_model": "Default Model",
@ -1152,6 +1167,15 @@
"base_url": "Base URL",
"url": "URL",
"openai_url_description": "Default: https://api.openai.com/v1",
"anthropic_settings": "Anthropic Settings",
"anthropic_url_description": "Base URL for the Anthropic API (default: https://api.anthropic.com)",
"anthropic_model_description": "Anthropic Claude models for chat completion",
"voyage_settings": "Voyage AI Settings",
"voyage_api_key_description": "Your Voyage AI API key for accessing embeddings services",
"ollama_settings": "Ollama Settings",
"ollama_url_description": "URL for the Ollama API (default: http://localhost:11434)",
"ollama_model_description": "Ollama model to use for chat completion",
"ollama_embedding_model_description": "Specialized model for generating embeddings (vector representations)",
"anthropic_configuration": "Anthropic Configuration",
"anthropic_model_description": "Examples: claude-3-opus-20240229, claude-3-sonnet-20240229",
"voyage_embedding_model_description": "Voyage AI embedding models for text embeddings (voyage-2 recommended)",
@ -1173,8 +1197,23 @@
"embedding_default_provider": "Default Provider",
"embedding_default_provider_description": "Select the default provider used for generating note embeddings",
"embedding_provider_precedence": "Embedding Provider Precedence",
"embedding_providers_order": "Embedding Provider Order",
"embedding_providers_order_description": "Set the order of embedding providers in comma-separated format (e.g., \"openai,voyage,ollama,local\")",
"embeddings_configuration": "Embeddings Configuration",
"enable_automatic_indexing": "Enable Automatic Indexing",
"enable_automatic_indexing_description": "Automatically generate embeddings for new and updated notes",
"embedding_auto_update_enabled": "Auto-update Embeddings",
"embedding_auto_update_enabled_description": "Automatically update embeddings when notes are modified",
"rebuild_index": "Rebuild Index",
"rebuild_index_description": "Regenerate all note embeddings (may take some time for large note collections)",
"embedding_provider_precedence_description": "Comma-separated list of providers in order of precedence for embeddings search (e.g., 'openai,ollama,anthropic')",
"embedding_dimension_strategy": "Embedding dimension strategy",
"embedding_dimension_strategy": "Embedding Dimension Strategy",
"embedding_dimension_auto": "Auto (Recommended)",
"embedding_dimension_fixed": "Fixed",
"embedding_similarity_threshold": "Similarity Threshold",
"embedding_similarity_threshold_description": "Minimum similarity score for notes to be included in search results (0-1)",
"max_notes_per_llm_query": "Max Notes Per Query",
"max_notes_per_llm_query_description": "Maximum number of similar notes to include in AI context",
"embedding_dimension_strategy_description": "Choose how embeddings are handled. 'Native' preserves maximum information by adapting smaller vectors to match larger ones (recommended). 'Regenerate' creates new embeddings with the target model for specific search needs.",
"drag_providers_to_reorder": "Drag providers up or down to set your preferred order for embedding searches",
"active_providers": "Active Providers",