From e5dd66abb5f6d5cf9e98230dae9b1332aa241142 Mon Sep 17 00:00:00 2001 From: Kevin On <40454531+kevin-on@users.noreply.github.com> Date: Sun, 1 Dec 2024 16:16:26 +0900 Subject: [PATCH 1/2] feat: Add dynamic Ollama model selection dropdown - Replace text input for Ollama model with dropdown populated from API - Add automatic model list fetching when base URL changes --- src/settings/SettingTab.tsx | 103 ++++++++++++++++++++++++++++-------- src/utils/ollama.ts | 12 +++++ 2 files changed, 94 insertions(+), 21 deletions(-) create mode 100644 src/utils/ollama.ts diff --git a/src/settings/SettingTab.tsx b/src/settings/SettingTab.tsx index 6a25b42..6254e15 100644 --- a/src/settings/SettingTab.tsx +++ b/src/settings/SettingTab.tsx @@ -1,4 +1,11 @@ -import { App, Modal, PluginSettingTab, Setting, TFile } from 'obsidian' +import { + App, + DropdownComponent, + Modal, + PluginSettingTab, + Setting, + TFile, +} from 'obsidian' import { APPLY_MODEL_OPTIONS, @@ -7,6 +14,7 @@ import { } from '../constants' import SmartCopilotPlugin from '../main' import { findFilesMatchingPatterns } from '../utils/globUtils' +import { getOllamaModels } from '../utils/ollama' export class SmartCopilotSettingTab extends PluginSettingTab { plugin: SmartCopilotPlugin @@ -203,13 +211,71 @@ export class SmartCopilotSettingTab extends PluginSettingTab { const ollamaContainer = containerEl.createDiv( 'smtcmp-settings-model-container', ) + let modelDropdown: DropdownComponent | null = null // Store reference to the dropdown + + const updateModelOptions = async ( + baseUrl: string, + dropdown: DropdownComponent, + ) => { + const currentValue = dropdown.getValue() + dropdown.selectEl.empty() + + try { + const models = await getOllamaModels(baseUrl) + if (models.length > 0) { + const modelOptions = models.reduce>( + (acc, model) => { + acc[model] = model + return acc + }, + {}, + ) + dropdown.addOptions(modelOptions) + + if (models.includes(currentValue)) { + dropdown.setValue(currentValue) + } else { + dropdown.setValue(models[0]) + await this.plugin.setSettings({ + ...this.plugin.settings, + ollamaChatModel: { + ...this.plugin.settings.ollamaChatModel, + model: models[0], + }, + }) + } + } else { + dropdown.addOption('', 'No models found - check base URL') + dropdown.setValue('') + await this.plugin.setSettings({ + ...this.plugin.settings, + ollamaChatModel: { + ...this.plugin.settings.ollamaChatModel, + model: '', + }, + }) + } + } catch (error) { + console.error('Failed to fetch Ollama models:', error) + dropdown.addOption('', 'No models found - check base URL') + dropdown.setValue('') + await this.plugin.setSettings({ + ...this.plugin.settings, + ollamaChatModel: { + ...this.plugin.settings.ollamaChatModel, + model: '', + }, + }) + } + } + // Base URL Setting new Setting(ollamaContainer) .setName('Base URL') .setDesc( 'The API endpoint for your Ollama service (e.g., http://127.0.0.1:11434)', ) - .addText((text) => + .addText((text) => { text .setPlaceholder('http://127.0.0.1:11434') .setValue(this.plugin.settings.ollamaChatModel.baseUrl || '') @@ -221,28 +287,23 @@ export class SmartCopilotSettingTab extends PluginSettingTab { baseUrl: value, }, }) - }), - ) + if (modelDropdown) { + await updateModelOptions(value, modelDropdown) + } + }) + }) + // Model Setting new Setting(ollamaContainer) .setName('Model Name') - .setDesc( - 'The specific model to use with your service (e.g., llama-3.1-70b, mixtral-8x7b)', - ) - .addText((text) => - text - .setPlaceholder('llama-3.1-70b') - .setValue(this.plugin.settings.ollamaChatModel.model || '') - .onChange(async (value) => { - await this.plugin.setSettings({ - ...this.plugin.settings, - ollamaChatModel: { - ...this.plugin.settings.ollamaChatModel, - model: value, - }, - }) - }), - ) + .setDesc('Select a model from your Ollama instance') + .addDropdown(async (dropdown) => { + modelDropdown = dropdown + await updateModelOptions( + this.plugin.settings.ollamaChatModel.baseUrl, + dropdown, + ) + }) } renderOpenAICompatibleChatModelSettings(containerEl: HTMLElement): void { diff --git a/src/utils/ollama.ts b/src/utils/ollama.ts new file mode 100644 index 0000000..44d1ad1 --- /dev/null +++ b/src/utils/ollama.ts @@ -0,0 +1,12 @@ +import { requestUrl } from 'obsidian' + +export async function getOllamaModels(ollamaUrl: string) { + try { + const response = (await requestUrl(`${ollamaUrl}/api/tags`)).json as { + models: { name: string }[] + } + return response.models.map((model) => model.name) + } catch (error) { + return [] + } +} From bf64fb6ccc5d5250c15e00507862529d43f946a7 Mon Sep 17 00:00:00 2001 From: Kevin On <40454531+kevin-on@users.noreply.github.com> Date: Sun, 1 Dec 2024 19:40:33 +0900 Subject: [PATCH 2/2] refactor: move updateModelOptions to class method --- src/settings/SettingTab.tsx | 117 ++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/src/settings/SettingTab.tsx b/src/settings/SettingTab.tsx index 6254e15..2c6f0da 100644 --- a/src/settings/SettingTab.tsx +++ b/src/settings/SettingTab.tsx @@ -213,62 +213,6 @@ export class SmartCopilotSettingTab extends PluginSettingTab { ) let modelDropdown: DropdownComponent | null = null // Store reference to the dropdown - const updateModelOptions = async ( - baseUrl: string, - dropdown: DropdownComponent, - ) => { - const currentValue = dropdown.getValue() - dropdown.selectEl.empty() - - try { - const models = await getOllamaModels(baseUrl) - if (models.length > 0) { - const modelOptions = models.reduce>( - (acc, model) => { - acc[model] = model - return acc - }, - {}, - ) - dropdown.addOptions(modelOptions) - - if (models.includes(currentValue)) { - dropdown.setValue(currentValue) - } else { - dropdown.setValue(models[0]) - await this.plugin.setSettings({ - ...this.plugin.settings, - ollamaChatModel: { - ...this.plugin.settings.ollamaChatModel, - model: models[0], - }, - }) - } - } else { - dropdown.addOption('', 'No models found - check base URL') - dropdown.setValue('') - await this.plugin.setSettings({ - ...this.plugin.settings, - ollamaChatModel: { - ...this.plugin.settings.ollamaChatModel, - model: '', - }, - }) - } - } catch (error) { - console.error('Failed to fetch Ollama models:', error) - dropdown.addOption('', 'No models found - check base URL') - dropdown.setValue('') - await this.plugin.setSettings({ - ...this.plugin.settings, - ollamaChatModel: { - ...this.plugin.settings.ollamaChatModel, - model: '', - }, - }) - } - } - // Base URL Setting new Setting(ollamaContainer) .setName('Base URL') @@ -288,7 +232,7 @@ export class SmartCopilotSettingTab extends PluginSettingTab { }, }) if (modelDropdown) { - await updateModelOptions(value, modelDropdown) + await this.updateOllamaModelOptions(value, modelDropdown) } }) }) @@ -299,7 +243,7 @@ export class SmartCopilotSettingTab extends PluginSettingTab { .setDesc('Select a model from your Ollama instance') .addDropdown(async (dropdown) => { modelDropdown = dropdown - await updateModelOptions( + await this.updateOllamaModelOptions( this.plugin.settings.ollamaChatModel.baseUrl, dropdown, ) @@ -643,6 +587,63 @@ export class SmartCopilotSettingTab extends PluginSettingTab { }), ) } + + private async updateOllamaModelOptions( + baseUrl: string, + dropdown: DropdownComponent, + ): Promise { + const currentValue = dropdown.getValue() + dropdown.selectEl.empty() + + try { + const models = await getOllamaModels(baseUrl) + if (models.length > 0) { + const modelOptions = models.reduce>( + (acc, model) => { + acc[model] = model + return acc + }, + {}, + ) + + dropdown.addOptions(modelOptions) + + if (models.includes(currentValue)) { + dropdown.setValue(currentValue) + } else { + dropdown.setValue(models[0]) + await this.plugin.setSettings({ + ...this.plugin.settings, + ollamaChatModel: { + ...this.plugin.settings.ollamaChatModel, + model: models[0], + }, + }) + } + } else { + dropdown.addOption('', 'No models found - check base URL') + dropdown.setValue('') + await this.plugin.setSettings({ + ...this.plugin.settings, + ollamaChatModel: { + ...this.plugin.settings.ollamaChatModel, + model: '', + }, + }) + } + } catch (error) { + console.error('Failed to fetch Ollama models:', error) + dropdown.addOption('', 'No models found - check base URL') + dropdown.setValue('') + await this.plugin.setSettings({ + ...this.plugin.settings, + ollamaChatModel: { + ...this.plugin.settings.ollamaChatModel, + model: '', + }, + }) + } + } } class ExcludedFilesModal extends Modal {