856 lines
23 KiB
TypeScript
856 lines
23 KiB
TypeScript
import {
|
|
App,
|
|
DropdownComponent,
|
|
Modal,
|
|
PluginSettingTab,
|
|
Setting,
|
|
TFile,
|
|
} from 'obsidian';
|
|
import * as React from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
// import {
|
|
// EMBEDDING_MODEL_OPTIONS,
|
|
// } from '../constants'
|
|
|
|
import InfioPlugin from '../main';
|
|
import { findFilesMatchingPatterns } from '../utils/glob-utils';
|
|
import { getOllamaModels } from '../utils/ollama';
|
|
|
|
import AutoCompleteSettings from './AutoCompleteSettings';
|
|
import CustomSettings from './CustomSettings';
|
|
|
|
export class InfioSettingTab extends PluginSettingTab {
|
|
plugin: InfioPlugin
|
|
|
|
constructor(app: App, plugin: InfioPlugin) {
|
|
super(app, plugin)
|
|
this.plugin = plugin
|
|
}
|
|
|
|
display(): void {
|
|
const { containerEl } = this
|
|
containerEl.empty()
|
|
this.renderModelsSection(containerEl)
|
|
this.renderAPIKeysSection(containerEl)
|
|
this.renderDefaultModelSection(containerEl)
|
|
this.renderRAGSection(containerEl)
|
|
this.renderAutoCompleteSection(containerEl)
|
|
}
|
|
|
|
renderModelsSection(containerEl: HTMLElement): void {
|
|
const div = containerEl.createDiv("div");
|
|
const sections = createRoot(div);
|
|
sections.render(<CustomSettings plugin={this.plugin} />);
|
|
}
|
|
|
|
renderAPIKeysSection(containerEl: HTMLElement): void {
|
|
new Setting(containerEl)
|
|
.setHeading()
|
|
.setName('API keys')
|
|
new Setting(containerEl)
|
|
.setName('Infio API key')
|
|
.setClass("infio-chat-setting-item-container")
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('Enter your API key')
|
|
.setValue(this.plugin.settings.infioApiKey)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
infioApiKey: value,
|
|
})
|
|
}),
|
|
)
|
|
|
|
new Setting(containerEl)
|
|
.setName('Anthropic API key')
|
|
.setClass("infio-chat-setting-item-container-append")
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('Enter your API key')
|
|
.setValue(this.plugin.settings.anthropicApiKey)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
anthropicApiKey: value,
|
|
})
|
|
}),
|
|
)
|
|
|
|
new Setting(containerEl)
|
|
.setName('Deepseek API key')
|
|
.setClass("infio-chat-setting-item-container-append")
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('Enter your API key')
|
|
.setValue(this.plugin.settings.deepseekApiKey)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
deepseekApiKey: value,
|
|
})
|
|
}),
|
|
)
|
|
|
|
new Setting(containerEl)
|
|
.setName('OpenAI API key')
|
|
.setClass("infio-chat-setting-item-container-append")
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('Enter your API key')
|
|
.setValue(this.plugin.settings.openAIApiKey)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
openAIApiKey: value,
|
|
})
|
|
}),
|
|
)
|
|
|
|
new Setting(containerEl)
|
|
.setName('Gemini API key')
|
|
.setClass("infio-chat-setting-item-container-append")
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('Enter your API key')
|
|
.setValue(this.plugin.settings.geminiApiKey)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
geminiApiKey: value,
|
|
})
|
|
}),
|
|
)
|
|
|
|
new Setting(containerEl)
|
|
.setName('Groq API key')
|
|
.setClass("infio-chat-setting-item-container-append")
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('Enter your API key')
|
|
.setValue(this.plugin.settings.groqApiKey)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
groqApiKey: value,
|
|
})
|
|
}),
|
|
)
|
|
}
|
|
|
|
renderDefaultModelSection(containerEl: HTMLElement): void {
|
|
new Setting(containerEl).setHeading().setName('Default Model')
|
|
new Setting(containerEl)
|
|
.setName('Default chat model')
|
|
.setClass("infio-chat-setting-item-container")
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOptions(
|
|
this.plugin.settings.activeModels
|
|
.reduce<Record<string, string>>((acc, option) => {
|
|
if (!option.isEmbeddingModel && option.enabled) {
|
|
acc[option.name] = option.name
|
|
}
|
|
return acc
|
|
}, {}),
|
|
)
|
|
.setValue(this.plugin.settings.chatModelId)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
chatModelId: value,
|
|
})
|
|
// Force refresh to show/hide Ollama and OpenAI-compatible settings
|
|
this.display()
|
|
}),
|
|
)
|
|
if (this.plugin.settings.chatModelId === 'ollama') {
|
|
this.renderOllamaChatModelSettings(containerEl)
|
|
}
|
|
if (this.plugin.settings.chatModelId === 'openai-compatible') {
|
|
this.renderOpenAICompatibleChatModelSettings(containerEl)
|
|
}
|
|
|
|
new Setting(containerEl)
|
|
.setName('Default apply model')
|
|
.setClass("infio-chat-setting-item-container-append")
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOptions(
|
|
this.plugin.settings.activeModels
|
|
.reduce<Record<string, string>>(
|
|
(acc, option) => {
|
|
if (!option.isEmbeddingModel && option.enabled) {
|
|
acc[option.name] = option.name
|
|
}
|
|
return acc
|
|
},
|
|
{},
|
|
),
|
|
)
|
|
.setValue(this.plugin.settings.applyModelId)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
applyModelId: value,
|
|
})
|
|
// Force refresh to show/hide Ollama and OpenAI-compatible settings
|
|
this.display()
|
|
}),
|
|
)
|
|
if (this.plugin.settings.applyModelId === 'ollama') {
|
|
this.renderOllamaApplyModelSettings(containerEl)
|
|
}
|
|
if (this.plugin.settings.applyModelId === 'openai-compatible') {
|
|
this.renderOpenAICompatibleApplyModelSettings(containerEl)
|
|
}
|
|
|
|
new Setting(containerEl)
|
|
.setName('Default embedding model')
|
|
.setClass("infio-chat-setting-item-container-append")
|
|
.addDropdown((dropdown) =>
|
|
dropdown
|
|
.addOptions(
|
|
this.plugin.settings.activeModels
|
|
.reduce<Record<string, string>>(
|
|
(acc, option) => {
|
|
if (option.isEmbeddingModel && option.enabled) {
|
|
acc[option.name] = option.name
|
|
}
|
|
return acc
|
|
},
|
|
{},
|
|
),
|
|
)
|
|
.setValue(this.plugin.settings.embeddingModelId)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
embeddingModelId: value,
|
|
})
|
|
// Force refresh to show/hide Ollama settings
|
|
this.display()
|
|
}),
|
|
)
|
|
if (this.plugin.settings.embeddingModelId.startsWith('ollama/')) {
|
|
this.renderOllamaEmbeddingModelSettings(containerEl)
|
|
}
|
|
|
|
new Setting(containerEl)
|
|
.setHeading()
|
|
.setName('System prompt')
|
|
.setDesc('This prompt will be added to the beginning of every chat.')
|
|
|
|
new Setting(containerEl)
|
|
.setClass('infio-chat-settings-textarea')
|
|
.addTextArea((text) =>
|
|
text
|
|
.setValue(this.plugin.settings.systemPrompt)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
systemPrompt: value,
|
|
})
|
|
}),
|
|
)
|
|
}
|
|
|
|
renderOllamaChatModelSettings(containerEl: HTMLElement): void {
|
|
const ollamaContainer = containerEl.createDiv(
|
|
'infio-chat-settings-model-container',
|
|
)
|
|
let modelDropdown: DropdownComponent | null = null // Store reference to the dropdown
|
|
|
|
// Base URL Setting
|
|
new Setting(ollamaContainer)
|
|
.setName('Base URL')
|
|
.setClass("infio-chat-setting-item-container-append")
|
|
.setDesc(
|
|
'The API endpoint for your Ollama service (e.g., http://127.0.0.1:11434)',
|
|
)
|
|
.addText((text) => {
|
|
text
|
|
.setPlaceholder('http://127.0.0.1:11434')
|
|
.setValue(this.plugin.settings.ollamaChatModel.baseUrl || '')
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ollamaChatModel: {
|
|
...this.plugin.settings.ollamaChatModel,
|
|
baseUrl: value,
|
|
},
|
|
})
|
|
if (modelDropdown) {
|
|
await this.updateOllamaModelOptions({
|
|
baseUrl: value,
|
|
dropdown: modelDropdown,
|
|
onModelChange: async (model: string) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ollamaChatModel: {
|
|
...this.plugin.settings.ollamaChatModel,
|
|
model,
|
|
},
|
|
})
|
|
},
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
// Model Setting
|
|
new Setting(ollamaContainer)
|
|
.setName('Model Name')
|
|
.setDesc('Select a model from your Ollama instance')
|
|
.addDropdown(async (dropdown) => {
|
|
const currentModel = this.plugin.settings.ollamaChatModel.model
|
|
modelDropdown = dropdown
|
|
.addOption(currentModel, currentModel)
|
|
.setValue(currentModel)
|
|
await this.updateOllamaModelOptions({
|
|
baseUrl: this.plugin.settings.ollamaChatModel.baseUrl,
|
|
dropdown,
|
|
onModelChange: async (model: string) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ollamaChatModel: {
|
|
...this.plugin.settings.ollamaChatModel,
|
|
model,
|
|
},
|
|
})
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
renderOpenAICompatibleChatModelSettings(containerEl: HTMLElement): void {
|
|
const openAICompatContainer = containerEl.createDiv(
|
|
'infio-chat-settings-model-container',
|
|
)
|
|
|
|
new Setting(openAICompatContainer)
|
|
.setName('Base URL')
|
|
.setDesc(
|
|
'The API endpoint for your OpenAI-compatible service (e.g., https://api.example.com/v1)',
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('https://api.example.com/v1')
|
|
.setValue(
|
|
this.plugin.settings.openAICompatibleChatModel.baseUrl || '',
|
|
)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
openAICompatibleChatModel: {
|
|
...this.plugin.settings.openAICompatibleChatModel,
|
|
baseUrl: value,
|
|
},
|
|
})
|
|
}),
|
|
)
|
|
|
|
new Setting(openAICompatContainer)
|
|
.setName('API Key')
|
|
.setDesc('Your authentication key for the OpenAI-compatible service')
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('Enter your API key')
|
|
.setValue(this.plugin.settings.openAICompatibleChatModel.apiKey || '')
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
openAICompatibleChatModel: {
|
|
...this.plugin.settings.openAICompatibleChatModel,
|
|
apiKey: value,
|
|
},
|
|
})
|
|
}),
|
|
)
|
|
|
|
new Setting(openAICompatContainer)
|
|
.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.openAICompatibleChatModel.model || '')
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
openAICompatibleChatModel: {
|
|
...this.plugin.settings.openAICompatibleChatModel,
|
|
model: value,
|
|
},
|
|
})
|
|
}),
|
|
)
|
|
}
|
|
|
|
renderOllamaApplyModelSettings(containerEl: HTMLElement): void {
|
|
const ollamaContainer = containerEl.createDiv(
|
|
'infio-chat-settings-model-container',
|
|
)
|
|
let modelDropdown: DropdownComponent | null = null // Store reference to the dropdown
|
|
|
|
// 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) => {
|
|
text
|
|
.setPlaceholder('http://127.0.0.1:11434')
|
|
.setValue(this.plugin.settings.ollamaApplyModel.baseUrl || '')
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ollamaApplyModel: {
|
|
...this.plugin.settings.ollamaApplyModel,
|
|
baseUrl: value,
|
|
},
|
|
})
|
|
if (modelDropdown) {
|
|
await this.updateOllamaModelOptions({
|
|
baseUrl: value,
|
|
dropdown: modelDropdown,
|
|
onModelChange: async (model: string) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ollamaApplyModel: {
|
|
...this.plugin.settings.ollamaApplyModel,
|
|
model,
|
|
},
|
|
})
|
|
},
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
// Model Setting
|
|
new Setting(ollamaContainer)
|
|
.setName('Model Name')
|
|
.setDesc('Select a model from your Ollama instance')
|
|
.addDropdown(async (dropdown) => {
|
|
const currentModel = this.plugin.settings.ollamaApplyModel.model
|
|
modelDropdown = dropdown
|
|
.addOption(currentModel, currentModel)
|
|
.setValue(currentModel)
|
|
await this.updateOllamaModelOptions({
|
|
baseUrl: this.plugin.settings.ollamaApplyModel.baseUrl,
|
|
dropdown,
|
|
onModelChange: async (model: string) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ollamaApplyModel: {
|
|
...this.plugin.settings.ollamaApplyModel,
|
|
model,
|
|
},
|
|
})
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
renderOpenAICompatibleApplyModelSettings(containerEl: HTMLElement): void {
|
|
const openAICompatContainer = containerEl.createDiv(
|
|
'infio-chat-settings-model-container',
|
|
)
|
|
|
|
new Setting(openAICompatContainer)
|
|
.setName('Base URL')
|
|
.setDesc(
|
|
'The API endpoint for your OpenAI-compatible service (e.g., https://api.example.com/v1)',
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('https://api.example.com/v1')
|
|
.setValue(
|
|
this.plugin.settings.openAICompatibleApplyModel.baseUrl || '',
|
|
)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
openAICompatibleApplyModel: {
|
|
...this.plugin.settings.openAICompatibleApplyModel,
|
|
baseUrl: value,
|
|
},
|
|
})
|
|
}),
|
|
)
|
|
|
|
new Setting(openAICompatContainer)
|
|
.setName('API Key')
|
|
.setDesc('Your authentication key for the OpenAI-compatible service')
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('Enter your API key')
|
|
.setValue(
|
|
this.plugin.settings.openAICompatibleApplyModel.apiKey || '',
|
|
)
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
openAICompatibleApplyModel: {
|
|
...this.plugin.settings.openAICompatibleApplyModel,
|
|
apiKey: value,
|
|
},
|
|
})
|
|
}),
|
|
)
|
|
|
|
new Setting(openAICompatContainer)
|
|
.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.openAICompatibleApplyModel.model || '')
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
openAICompatibleApplyModel: {
|
|
...this.plugin.settings.openAICompatibleApplyModel,
|
|
model: value,
|
|
},
|
|
})
|
|
}),
|
|
)
|
|
}
|
|
|
|
renderOllamaEmbeddingModelSettings(containerEl: HTMLElement): void {
|
|
const ollamaContainer = containerEl.createDiv(
|
|
'infio-chat-settings-model-container',
|
|
)
|
|
|
|
new Setting(ollamaContainer)
|
|
.setName('Base URL')
|
|
.setDesc(
|
|
'The API endpoint for your Ollama service (e.g., http://127.0.0.1:11434)',
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('http://127.0.0.1:11434')
|
|
.setValue(this.plugin.settings.ollamaEmbeddingModel.baseUrl || '')
|
|
.onChange(async (value) => {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ollamaEmbeddingModel: {
|
|
...this.plugin.settings.ollamaEmbeddingModel,
|
|
baseUrl: value,
|
|
},
|
|
})
|
|
}),
|
|
)
|
|
}
|
|
|
|
renderRAGSection(containerEl: HTMLElement): void {
|
|
new Setting(containerEl).setHeading().setName('RAG')
|
|
|
|
new Setting(containerEl)
|
|
.setName('Include patterns')
|
|
.setDesc(
|
|
'If any patterns are specified, ONLY files matching at least one pattern will be included in indexing. One pattern per line. Uses glob patterns (e.g., "notes/*", "*.md"). Leave empty to include all files not excluded by exclude patterns. After changing this, use the command "Rebuild entire vault index" to apply changes.',
|
|
)
|
|
.addButton((button) =>
|
|
button.setButtonText('Test patterns').onClick(async () => {
|
|
const patterns = this.plugin.settings.ragOptions.includePatterns
|
|
const includedFiles = await findFilesMatchingPatterns(
|
|
patterns,
|
|
this.plugin.app.vault,
|
|
)
|
|
new IncludedFilesModal(this.app, includedFiles, patterns).open()
|
|
}),
|
|
)
|
|
new Setting(containerEl)
|
|
.setClass('infio-chat-settings-textarea')
|
|
.addTextArea((text) =>
|
|
text
|
|
.setValue(this.plugin.settings.ragOptions.includePatterns.join('\n'))
|
|
.onChange(async (value) => {
|
|
const patterns = value
|
|
.split('\n')
|
|
.map((p) => p.trim())
|
|
.filter((p) => p.length > 0)
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ragOptions: {
|
|
...this.plugin.settings.ragOptions,
|
|
includePatterns: patterns,
|
|
},
|
|
})
|
|
}),
|
|
)
|
|
|
|
new Setting(containerEl)
|
|
.setName('Exclude patterns')
|
|
.setDesc(
|
|
'Files matching ANY of these patterns will be excluded from indexing. One pattern per line. Uses glob patterns (e.g., "private/*", "*.tmp"). Leave empty to exclude nothing. After changing this, use the command "Rebuild entire vault index" to apply changes.',
|
|
)
|
|
.addButton((button) =>
|
|
button.setButtonText('Test patterns').onClick(async () => {
|
|
const patterns = this.plugin.settings.ragOptions.excludePatterns
|
|
const excludedFiles = await findFilesMatchingPatterns(
|
|
patterns,
|
|
this.plugin.app.vault,
|
|
)
|
|
new ExcludedFilesModal(this.app, excludedFiles).open()
|
|
}),
|
|
)
|
|
new Setting(containerEl)
|
|
.setClass('infio-chat-settings-textarea')
|
|
.addTextArea((text) =>
|
|
text
|
|
.setValue(this.plugin.settings.ragOptions.excludePatterns.join('\n'))
|
|
.onChange(async (value) => {
|
|
const patterns = value
|
|
.split('\n')
|
|
.map((p) => p.trim())
|
|
.filter((p) => p.length > 0)
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ragOptions: {
|
|
...this.plugin.settings.ragOptions,
|
|
excludePatterns: patterns,
|
|
},
|
|
})
|
|
}),
|
|
)
|
|
|
|
new Setting(containerEl)
|
|
.setName('Chunk size')
|
|
.setDesc(
|
|
'Set the chunk size for text splitting. After changing this, please re-index the vault using the "Rebuild entire vault index" command.',
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('1000')
|
|
.setValue(String(this.plugin.settings.ragOptions.chunkSize))
|
|
.onChange(async (value) => {
|
|
const chunkSize = parseInt(value, 10)
|
|
if (!isNaN(chunkSize)) {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ragOptions: {
|
|
...this.plugin.settings.ragOptions,
|
|
chunkSize,
|
|
},
|
|
})
|
|
}
|
|
}),
|
|
)
|
|
|
|
new Setting(containerEl)
|
|
.setName('Threshold tokens')
|
|
.setDesc(
|
|
'Maximum number of tokens before switching to RAG. If the total tokens from mentioned files exceed this, RAG will be used instead of including all file contents.',
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('8192')
|
|
.setValue(String(this.plugin.settings.ragOptions.thresholdTokens))
|
|
.onChange(async (value) => {
|
|
const thresholdTokens = parseInt(value, 10)
|
|
if (!isNaN(thresholdTokens)) {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ragOptions: {
|
|
...this.plugin.settings.ragOptions,
|
|
thresholdTokens,
|
|
},
|
|
})
|
|
}
|
|
}),
|
|
)
|
|
|
|
new Setting(containerEl)
|
|
.setName('Minimum similarity')
|
|
.setDesc(
|
|
'Minimum similarity score for RAG results. Higher values return more relevant but potentially fewer results.',
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('0.0')
|
|
.setValue(String(this.plugin.settings.ragOptions.minSimilarity))
|
|
.onChange(async (value) => {
|
|
const minSimilarity = parseFloat(value)
|
|
if (!isNaN(minSimilarity)) {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ragOptions: {
|
|
...this.plugin.settings.ragOptions,
|
|
minSimilarity,
|
|
},
|
|
})
|
|
}
|
|
}),
|
|
)
|
|
|
|
new Setting(containerEl)
|
|
.setName('Limit')
|
|
.setDesc(
|
|
'Maximum number of RAG results to include in the prompt. Higher values provide more context but increase token usage.',
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder('10')
|
|
.setValue(String(this.plugin.settings.ragOptions.limit))
|
|
.onChange(async (value) => {
|
|
const limit = parseInt(value, 10)
|
|
if (!isNaN(limit)) {
|
|
await this.plugin.setSettings({
|
|
...this.plugin.settings,
|
|
ragOptions: {
|
|
...this.plugin.settings.ragOptions,
|
|
limit,
|
|
},
|
|
})
|
|
}
|
|
}),
|
|
)
|
|
}
|
|
|
|
renderAutoCompleteSection(containerEl: HTMLElement): void {
|
|
const div = containerEl.createDiv("div");
|
|
|
|
const sections = createRoot(div);
|
|
sections.render(
|
|
<React.StrictMode>
|
|
<AutoCompleteSettings
|
|
onSettingsChanged={async (settings) => {
|
|
this.plugin.setSettings(settings);
|
|
}}
|
|
settings={this.plugin.settings}
|
|
/>
|
|
</React.StrictMode>
|
|
);
|
|
}
|
|
|
|
private async updateOllamaModelOptions({
|
|
baseUrl,
|
|
dropdown,
|
|
onModelChange,
|
|
}: {
|
|
baseUrl: string
|
|
dropdown: DropdownComponent
|
|
onModelChange: (model: string) => Promise<void>
|
|
}): Promise<void> {
|
|
const currentValue = dropdown.getValue()
|
|
dropdown.selectEl.empty()
|
|
|
|
try {
|
|
const models = await getOllamaModels(baseUrl)
|
|
if (models.length > 0) {
|
|
const modelOptions = models.reduce<Record<string, string>>(
|
|
(acc, model) => {
|
|
acc[model] = model
|
|
return acc
|
|
},
|
|
{},
|
|
)
|
|
|
|
dropdown.addOptions(modelOptions)
|
|
|
|
if (models.includes(currentValue)) {
|
|
dropdown.setValue(currentValue)
|
|
} else {
|
|
dropdown.setValue(models[0])
|
|
await onModelChange(models[0])
|
|
}
|
|
} else {
|
|
dropdown.addOption('', 'No models found - check base URL')
|
|
dropdown.setValue('')
|
|
await onModelChange('')
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch Ollama models:', error)
|
|
dropdown.addOption('', 'No models found - check base URL')
|
|
dropdown.setValue('')
|
|
await onModelChange('')
|
|
}
|
|
|
|
dropdown.onChange(async (value) => {
|
|
await onModelChange(value)
|
|
})
|
|
}
|
|
}
|
|
|
|
class ExcludedFilesModal extends Modal {
|
|
private files: TFile[]
|
|
|
|
constructor(app: App, files: TFile[]) {
|
|
super(app)
|
|
this.files = files
|
|
}
|
|
|
|
onOpen() {
|
|
const { contentEl } = this
|
|
contentEl.empty()
|
|
|
|
this.titleEl.setText(`Excluded Files (${this.files.length})`)
|
|
|
|
if (this.files.length === 0) {
|
|
contentEl.createEl('p', { text: 'No files match the exclusion patterns' })
|
|
return
|
|
}
|
|
|
|
const list = contentEl.createEl('ul')
|
|
this.files.forEach((file) => {
|
|
list.createEl('li', { text: file.path })
|
|
})
|
|
}
|
|
|
|
onClose() {
|
|
const { contentEl } = this
|
|
contentEl.empty()
|
|
}
|
|
}
|
|
|
|
class IncludedFilesModal extends Modal {
|
|
private files: TFile[]
|
|
private patterns: string[]
|
|
|
|
constructor(app: App, files: TFile[], patterns: string[]) {
|
|
super(app)
|
|
this.files = files
|
|
this.patterns = patterns
|
|
}
|
|
|
|
onOpen() {
|
|
const { contentEl } = this
|
|
contentEl.empty()
|
|
|
|
this.titleEl.setText(`Included Files (${this.files.length})`)
|
|
|
|
if (this.patterns.length === 0) {
|
|
contentEl.createEl('p', {
|
|
text: 'No inclusion patterns specified - all files will be included (except those matching exclusion patterns)',
|
|
})
|
|
return
|
|
}
|
|
|
|
if (this.files.length === 0) {
|
|
contentEl.createEl('p', {
|
|
text: 'No files match the inclusion patterns',
|
|
})
|
|
return
|
|
}
|
|
|
|
const list = contentEl.createEl('ul')
|
|
this.files.forEach((file) => {
|
|
list.createEl('li', { text: file.path })
|
|
})
|
|
}
|
|
|
|
onClose() {
|
|
const { contentEl } = this
|
|
contentEl.empty()
|
|
}
|
|
}
|