@@ -470,6 +503,9 @@ export default class AiSettingsWidget extends OptionsWidget {
await this.displayValidationWarnings();
});
+ // Set up provider precedence drag-and-drop functionality
+ this.setupProviderPrecedence();
+
const $aiTemperature = this.$widget.find('.ai-temperature');
$aiTemperature.on('change', async () => {
await this.updateOption('aiTemperature', $aiTemperature.val() as string);
@@ -1624,5 +1660,207 @@ export default class AiSettingsWidget extends OptionsWidget {
$warningDiv.hide();
}
}
+
+ /**
+ * Set up drag and drop functionality for AI provider precedence
+ */
+ setupProviderPrecedence() {
+ if (!this.$widget) return;
+
+ const $aiProviderPrecedence = this.$widget.find('.ai-provider-precedence');
+ const $aiSortableList = this.$widget.find('.provider-sortable');
+
+ // Track the item being dragged
+ let aiDraggedItem: HTMLElement | null = null;
+
+ // Function to update the hidden input with current order
+ const updateAiPrecedenceValue = () => {
+ const providers = $aiSortableList.find('li').map(function() {
+ return $(this).data('provider');
+ }).get().join(',');
+
+ $aiProviderPrecedence.val(providers);
+ // Trigger the change event to save the option
+ $aiProviderPrecedence.trigger('change');
+
+ // 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 drag handlers for a list item
+ const setupAiDragHandlers = ($item: JQuery) => {
+ // Start dragging
+ $item.on('dragstart', function(e: JQuery.DragStartEvent) {
+ aiDraggedItem = this;
+ setTimeout(() => $(this).addClass('dragging'), 0);
+ // Set data for drag operation
+ e.originalEvent?.dataTransfer?.setData('text/plain', '');
+ });
+
+ // End dragging
+ $item.on('dragend', function() {
+ $(this).removeClass('dragging');
+ aiDraggedItem = null;
+ // Update the precedence value when dragging ends
+ updateAiPrecedenceValue();
+ });
+
+ // Dragging over an item
+ $item.on('dragover', function(e: JQuery.DragOverEvent) {
+ e.preventDefault();
+ if (!aiDraggedItem || this === aiDraggedItem) return;
+
+ $(this).addClass('drag-over');
+ });
+
+ // Leaving an item
+ $item.on('dragleave', function() {
+ $(this).removeClass('drag-over');
+ });
+
+ // Dropping on an item
+ $item.on('drop', function(e: JQuery.DropEvent) {
+ e.preventDefault();
+ $(this).removeClass('drag-over');
+
+ if (!aiDraggedItem || this === aiDraggedItem) return;
+
+ // Get the positions of the dragged item and drop target
+ const allItems = Array.from($aiSortableList.find('li').get()) as HTMLElement[];
+ const draggedIndex = allItems.indexOf(aiDraggedItem as HTMLElement);
+ const dropIndex = allItems.indexOf(this as HTMLElement);
+
+ if (draggedIndex < dropIndex) {
+ // Insert after
+ $(this).after(aiDraggedItem);
+ } else {
+ // Insert before
+ $(this).before(aiDraggedItem);
+ }
+
+ // Update the precedence value after reordering
+ updateAiPrecedenceValue();
+ });
+ };
+
+ // Make all list items draggable
+ const $aiListItems = $aiSortableList.find('li');
+ $aiListItems.attr('draggable', 'true');
+ $aiListItems.each((_, item) => {
+ setupAiDragHandlers($(item));
+ });
+
+ // Handle remove provider button clicks
+ this.$widget.find('.remove-ai-provider').on('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const $button = $(e.currentTarget);
+ const $item = $button.closest('li');
+ const provider = $item.data('provider');
+ const providerName = $item.find('strong').text();
+
+ // Create a new item for the disabled list
+ const $disabledItem = $(`
+
+ ${providerName}
+
+
+ `);
+
+ // Add to disabled list
+ this.$widget.find('.provider-disabled').append($disabledItem);
+
+ // Remove from active list
+ $item.remove();
+
+ // Update the hidden input value
+ updateAiPrecedenceValue();
+
+ // Add restore button handler
+ $disabledItem.find('.restore-ai-provider').on('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const $restoreButton = $(e.currentTarget);
+ const $disabledItem = $restoreButton.closest('li');
+ const provider = $disabledItem.data('provider');
+ const providerName = $disabledItem.find('strong').text();
+
+ // Create a new item for the active list
+ const $activeItem = $(`
+
+
+ ${providerName}
+
+
+ `);
+
+ // Make draggable
+ $activeItem.attr('draggable', 'true');
+ setupAiDragHandlers($activeItem);
+
+ // Add remove button handler
+ $activeItem.find('.remove-ai-provider').on('click', function(e) {
+ $(this).closest('li').find('.remove-ai-provider').trigger('click');
+ });
+
+ // Add to active list
+ $aiSortableList.append($activeItem);
+
+ // Remove from disabled list
+ $disabledItem.remove();
+
+ // Update the hidden input value
+ updateAiPrecedenceValue();
+ });
+ });
+
+ // Initialize by setting the value based on current order
+ updateAiPrecedenceValue();
+
+ // Process the saved preference value
+ const initializeAiProviderOrder = () => {
+ // Get the current value
+ const savedValue = $aiProviderPrecedence.val() as string;
+ if (!savedValue) return;
+
+ // Get all available providers
+ const allProviders = ['openai', 'anthropic', 'ollama'];
+ const savedProviders = savedValue.split(',');
+
+ // 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
+ }
+ });
+
+ // 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
+ $item.find('.remove-ai-provider').trigger('click');
+ }
+ });
+
+ // Update the value again after reordering
+ updateAiPrecedenceValue();
+ };
+
+ // Initialize provider order
+ initializeAiProviderOrder();
+ }
}