mirror of
https://github.com/shane-lamb/obsidian-llm-docs.git
synced 2026-07-22 05:44:55 +00:00
model picker: enable pinning
This commit is contained in:
parent
20b9722ad2
commit
a1d59b59ca
6 changed files with 127 additions and 65 deletions
|
|
@ -2,35 +2,7 @@ import { LlmConnectionSettings } from './settings'
|
|||
import { getAvailableOpenaiModels } from './open-ai'
|
||||
import { modelCacheUpdated } from './registry'
|
||||
|
||||
const modelToConnectionCache = new Map<string, string>()
|
||||
|
||||
export interface ConnectionModel {
|
||||
connection: LlmConnectionSettings
|
||||
model: string
|
||||
}
|
||||
|
||||
const modelExclusionList = [
|
||||
/^dall-e/,
|
||||
/embedding/,
|
||||
/(^|-)tts(-|$)/,
|
||||
/^whisper-/,
|
||||
]
|
||||
|
||||
export function getCachedConnectionModels(connections: LlmConnectionSettings[]): ConnectionModel[] {
|
||||
const models: ConnectionModel[] = []
|
||||
for (const connection of connections) {
|
||||
const connectionId = getConnectionId(connection)
|
||||
for (const [model, id] of modelToConnectionCache) {
|
||||
if (id === connectionId) {
|
||||
if (modelExclusionList.some((regex) => regex.test(model))) {
|
||||
continue
|
||||
}
|
||||
models.push({ connection, model })
|
||||
}
|
||||
}
|
||||
}
|
||||
return models
|
||||
}
|
||||
export const modelToConnectionCache = new Map<string, string>()
|
||||
|
||||
export function getConnectionId(connection: LlmConnectionSettings) {
|
||||
return connection.type + connection.baseUrl
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export default class LlmDocsPlugin extends Plugin implements ILlmDocsPlugin {
|
|||
editorCallback: async (editor, view) => {
|
||||
if (!view.file) return
|
||||
|
||||
const model = await new ModelPickerModal(this.app, this.settings.connections).openAndGetResult()
|
||||
const model = await new ModelPickerModal(this.app, this).openAndGetResult()
|
||||
if (model) {
|
||||
await this.app.fileManager.processFrontMatter(view.file, (frontmatter) => {
|
||||
frontmatter.model = model
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ import { ModelPickerModal } from './model-picker'
|
|||
import { FolderSuggest } from './folder-suggest'
|
||||
|
||||
export class SettingsTab extends PluginSettingTab {
|
||||
plugin: LlmDocsPlugin
|
||||
|
||||
unsubscribe = modelCacheUpdated.on(() => this.display())
|
||||
|
||||
constructor(app: App, plugin: LlmDocsPlugin) {
|
||||
constructor(
|
||||
app: App,
|
||||
private plugin: LlmDocsPlugin,
|
||||
) {
|
||||
super(app, plugin)
|
||||
this.plugin = plugin
|
||||
}
|
||||
|
||||
hide() {
|
||||
|
|
@ -89,10 +89,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||
.setDesc('The default LLM model variant to use for new LLM documents')
|
||||
.addButton((button) => {
|
||||
button.setButtonText('Select').onClick(async () => {
|
||||
const model = await new ModelPickerModal(
|
||||
this.app,
|
||||
this.plugin.settings.connections,
|
||||
).openAndGetResult()
|
||||
const model = await new ModelPickerModal(this.app, this.plugin).openAndGetResult()
|
||||
if (model) {
|
||||
await save(model)
|
||||
// force refresh of textbox in a way that is resilient to
|
||||
|
|
|
|||
|
|
@ -1,26 +1,20 @@
|
|||
import { App, SuggestModal } from 'obsidian'
|
||||
import { LlmConnectionSettings } from '../settings'
|
||||
import {
|
||||
ConnectionModel,
|
||||
getAllAvailableModelsAndUpdateCache,
|
||||
getCachedConnectionModels,
|
||||
getConnectionId,
|
||||
} from '../connection-models'
|
||||
import { App, getIcon, SuggestModal } from 'obsidian'
|
||||
import { PluginSettings } from '../settings'
|
||||
import { getAllAvailableModelsAndUpdateCache, getConnectionId, modelToConnectionCache } from '../connection-models'
|
||||
import { modelCacheUpdated } from '../registry'
|
||||
import { ValueEmitter } from '../utils'
|
||||
import LlmDocsPlugin from '../main'
|
||||
|
||||
export class ModelPickerModal extends SuggestModal<ConnectionModel> {
|
||||
private models: ConnectionModel[]
|
||||
export class ModelPickerModal extends SuggestModal<Model> {
|
||||
private models: Model[]
|
||||
private onResult = new ValueEmitter<string | null>()
|
||||
private unsubscribe = modelCacheUpdated.on(() => {
|
||||
this.refreshModels()
|
||||
// a bit dangerous as we are invoking internals, but there's no other way to refresh the list once opened
|
||||
;(this as any).updateSuggestions()
|
||||
})
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
private connections: LlmConnectionSettings[],
|
||||
private plugin: LlmDocsPlugin,
|
||||
) {
|
||||
super(app)
|
||||
this.refreshModels()
|
||||
|
|
@ -39,38 +33,57 @@ export class ModelPickerModal extends SuggestModal<ConnectionModel> {
|
|||
|
||||
private refreshModels() {
|
||||
const collator = new Intl.Collator()
|
||||
this.models = getCachedConnectionModels(this.connections).sort((a, b) =>
|
||||
collator.compare(sortKey(a), sortKey(b)),
|
||||
)
|
||||
this.models = getCachedModels(this.plugin.settings).sort((a, b) => collator.compare(sortKey(a), sortKey(b)))
|
||||
// a bit dangerous as we are invoking internals, but there's no other way to refresh the list once opened
|
||||
;(this as any).updateSuggestions()
|
||||
}
|
||||
|
||||
private getEmptyStateText(): string {
|
||||
if (!this.connections.length) {
|
||||
if (!this.plugin.settings.connections.length) {
|
||||
return `No results. You need to add a connection first!`
|
||||
}
|
||||
return 'Loading...'
|
||||
}
|
||||
|
||||
getSuggestions(query: string): ConnectionModel[] {
|
||||
return this.models.filter((model) => model.model.toLowerCase().includes(query.toLowerCase()))
|
||||
getSuggestions(query: string): Model[] {
|
||||
return this.models.filter((model) => model.name.toLowerCase().includes(query.toLowerCase()))
|
||||
}
|
||||
|
||||
renderSuggestion({ connection, model }: ConnectionModel, el: HTMLElement) {
|
||||
el.createEl('div', { text: model, cls: 'llmdocs-suggest-item-title' })
|
||||
renderSuggestion(model: Model, el: HTMLElement) {
|
||||
const pin = el.createEl('div', { cls: 'llmdocs-suggest-item-pin' })
|
||||
pin.onclick = async (evt: MouseEvent) => {
|
||||
evt.stopPropagation()
|
||||
await this.togglePinned(model)
|
||||
}
|
||||
if (model.isPinned) {
|
||||
pin.addClass('pinned')
|
||||
}
|
||||
pin.append(getIcon('pin') as Node)
|
||||
el.createEl('div', { text: model.name, cls: 'llmdocs-suggest-item-title' })
|
||||
el.createEl('small', {
|
||||
text: connection.baseUrl,
|
||||
text: model.connectionUrl,
|
||||
cls: 'llmdocs-suggest-item-subtext',
|
||||
})
|
||||
}
|
||||
|
||||
onChooseSuggestion(model: ConnectionModel, evt: MouseEvent | KeyboardEvent) {
|
||||
this.onResult.emit(model.model)
|
||||
async togglePinned(model: Model) {
|
||||
if (model.isPinned) {
|
||||
delete this.plugin.settings.pinnedModels[model.name]
|
||||
} else {
|
||||
this.plugin.settings.pinnedModels[model.name] = true
|
||||
}
|
||||
await this.plugin.saveSettings()
|
||||
this.refreshModels()
|
||||
}
|
||||
|
||||
onChooseSuggestion(model: Model, evt: MouseEvent | KeyboardEvent) {
|
||||
this.onResult.emit(model.name)
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
super.onOpen()
|
||||
|
||||
getAllAvailableModelsAndUpdateCache(this.connections).then()
|
||||
getAllAvailableModelsAndUpdateCache(this.plugin.settings.connections).then()
|
||||
}
|
||||
|
||||
onClose() {
|
||||
|
|
@ -86,6 +99,51 @@ export class ModelPickerModal extends SuggestModal<ConnectionModel> {
|
|||
}
|
||||
}
|
||||
|
||||
function sortKey(model: ConnectionModel): string {
|
||||
return getConnectionId(model.connection) + '_' + model.model
|
||||
function sortKey(model: Model): string {
|
||||
return (model.isPinned ? '0' : '1') + (model.isDefault ? '0' : '1') + model.connectionIndex + '_' + model.name
|
||||
}
|
||||
|
||||
interface Model {
|
||||
name: string
|
||||
connectionUrl: string
|
||||
connectionIndex: number
|
||||
isDefault: boolean
|
||||
isPinned: boolean
|
||||
}
|
||||
|
||||
const modelExclusionList = [/^dall-e/, /embedding/, /(^|-)tts(-|$)/, /^whisper-/]
|
||||
|
||||
function getCachedModels(settings: PluginSettings): Model[] {
|
||||
const defaultModel = settings.defaults.model
|
||||
const pinnedModels = settings.pinnedModels
|
||||
|
||||
const connectionUrls = new Map<string, string>()
|
||||
const connectionIndices = new Map<string, number>()
|
||||
settings.connections.forEach((connection, index) => {
|
||||
const connectionId = getConnectionId(connection)
|
||||
connectionUrls.set(connectionId, connection.baseUrl)
|
||||
connectionIndices.set(connectionId, index)
|
||||
})
|
||||
|
||||
const models: Model[] = []
|
||||
for (const [modelName, connectionId] of modelToConnectionCache) {
|
||||
if (modelExclusionList.some((regex) => regex.test(modelName))) {
|
||||
continue
|
||||
}
|
||||
|
||||
const connectionUrl = connectionUrls.get(connectionId)
|
||||
const connectionIndex = connectionIndices.get(connectionId)
|
||||
if (connectionUrl === undefined || connectionIndex === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
models.push({
|
||||
connectionUrl,
|
||||
connectionIndex,
|
||||
name: modelName,
|
||||
isDefault: modelName === defaultModel,
|
||||
isPinned: pinnedModels[modelName] ?? false,
|
||||
})
|
||||
}
|
||||
return models
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export interface PluginSettings {
|
|||
docsDir: string
|
||||
connections: LlmConnectionSettings[]
|
||||
defaults: DefaultsSettings
|
||||
pinnedModels: Record<string, boolean>
|
||||
}
|
||||
|
||||
export enum DocOpenMethods {
|
||||
|
|
@ -27,6 +28,7 @@ export interface LlmConnectionSettings {
|
|||
export const defaultPluginSettings: PluginSettings = {
|
||||
docsDir: 'LLM',
|
||||
connections: [],
|
||||
pinnedModels: {},
|
||||
defaults: {
|
||||
model: 'gpt-4o',
|
||||
systemPrompt: '',
|
||||
|
|
|
|||
33
styles.css
33
styles.css
|
|
@ -74,3 +74,36 @@ If your plugin does not need CSS, delete this file.
|
|||
.llmdocs-suggest-item-subtext {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.llmdocs-suggest-item-pin {
|
||||
float: right;
|
||||
border-radius: 2.5em;
|
||||
width: 2.5em;
|
||||
height: 2.5em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
visibility: hidden;
|
||||
color: var(--icon-color);
|
||||
opacity: var(--icon-opacity);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.is-selected .llmdocs-suggest-item-pin {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.llmdocs-suggest-item-pin:hover {
|
||||
opacity: var(--icon-opacity-hover);
|
||||
color: var(--icon-color-hover);
|
||||
background-color: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
.llmdocs-suggest-item-pin.pinned {
|
||||
visibility: visible;
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
.llmdocs-suggest-item-pin.pinned:hover {
|
||||
background-color: var(--background-primary);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue