mirror of
https://github.com/lossless-group/perplexed-plugin.git
synced 2026-07-22 17:00:32 +00:00
On branch fix/lmstudio Changes to be committed: modified: README.md modified: main.ts new file: src/core/PerplexedPluginCore.ts modified: src/modals/LMStudioModal.ts modified: src/services/lmStudioService.ts new file: src/settings/LMStudioSettings.ts new file: src/settings/PerplexedSettings.ts modified: src/types/obsidian.d.ts modified: styles.css
179 lines
No EOL
6.3 KiB
TypeScript
179 lines
No EOL
6.3 KiB
TypeScript
import { App, Modal, Notice, Editor } from 'obsidian';
|
|
import { LMStudioService, LMStudioOptions } from '../services/lmStudioService';
|
|
import { PromptsService } from '../services/promptsService';
|
|
import type PerplexedPlugin from '../../main';
|
|
|
|
export class LMStudioModal extends Modal {
|
|
private editor: Editor;
|
|
private lmStudioService: LMStudioService;
|
|
private promptsService: PromptsService;
|
|
private plugin: PerplexedPlugin;
|
|
private queryInput!: HTMLTextAreaElement;
|
|
private modelSelect!: HTMLSelectElement;
|
|
private streamToggle!: HTMLInputElement;
|
|
private maxTokensInput!: HTMLInputElement;
|
|
private temperatureInput!: HTMLInputElement;
|
|
private systemPromptInput!: HTMLTextAreaElement;
|
|
private imagesToggle!: HTMLInputElement;
|
|
|
|
constructor(app: App, editor: Editor, plugin: PerplexedPlugin, lmStudioService: LMStudioService, promptsService: PromptsService) {
|
|
super(app);
|
|
this.editor = editor;
|
|
this.plugin = plugin;
|
|
this.lmStudioService = lmStudioService;
|
|
this.promptsService = promptsService;
|
|
}
|
|
|
|
onOpen() {
|
|
const {contentEl} = this;
|
|
contentEl.addClass('lmstudio-modal');
|
|
contentEl.createEl('h2', {text: 'Ask LM Studio'});
|
|
|
|
const form = contentEl.createEl('form');
|
|
|
|
// Query input
|
|
const queryDiv = form.createDiv({cls: 'setting-item'});
|
|
queryDiv.createEl('label', {text: 'Your Question'});
|
|
this.queryInput = queryDiv.createEl('textarea', {
|
|
cls: 'text-input',
|
|
attr: {
|
|
rows: '4',
|
|
placeholder: this.promptsService.getLMStudioQueryPlaceholder()
|
|
}
|
|
});
|
|
|
|
// Model selection
|
|
const modelDiv = form.createDiv({cls: 'setting-item'});
|
|
modelDiv.createEl('label', {text: 'Model'});
|
|
this.modelSelect = modelDiv.createEl('select', {cls: 'dropdown'});
|
|
|
|
// Available models - these would ideally be dynamically loaded
|
|
const models = [
|
|
'ibm/granite-3.2-8b',
|
|
'microsoft/phi-4-reasoning-plus',
|
|
'google/gemma-3-12b',
|
|
'meta-llama/llama-3.2-3b-instruct',
|
|
'custom-model'
|
|
];
|
|
|
|
// Get default model from settings or use first model as fallback
|
|
const defaultModel = this.plugin?.settings?.defaultLMStudioModel || models[0];
|
|
|
|
// Create model options
|
|
models.forEach(model => {
|
|
const option = this.modelSelect.createEl('option', {value: model, text: model});
|
|
if (model === defaultModel) {
|
|
option.selected = true;
|
|
}
|
|
});
|
|
|
|
// System prompt
|
|
const systemDiv = form.createDiv({cls: 'setting-item'});
|
|
systemDiv.createEl('label', {text: 'System Prompt (Optional)'});
|
|
this.systemPromptInput = systemDiv.createEl('textarea', {
|
|
cls: 'text-input system-prompt-input',
|
|
attr: {
|
|
rows: '2',
|
|
placeholder: this.promptsService.getLMStudioSystemPromptPlaceholder()
|
|
}
|
|
});
|
|
|
|
// Max tokens
|
|
const maxTokensDiv = form.createDiv({cls: 'setting-item'});
|
|
maxTokensDiv.createEl('label', {text: 'Max Tokens'});
|
|
this.maxTokensInput = maxTokensDiv.createEl('input', {
|
|
type: 'number',
|
|
value: '2048',
|
|
cls: 'text-input'
|
|
});
|
|
|
|
// Temperature
|
|
const tempDiv = form.createDiv({cls: 'setting-item'});
|
|
tempDiv.createEl('label', {text: 'Temperature (0.0 - 2.0)'});
|
|
this.temperatureInput = tempDiv.createEl('input', {
|
|
type: 'number',
|
|
value: '0.7',
|
|
attr: {
|
|
step: '0.1',
|
|
min: '0',
|
|
max: '2'
|
|
},
|
|
cls: 'text-input'
|
|
});
|
|
|
|
// Images toggle
|
|
const imagesDiv = form.createDiv({cls: 'setting-item'});
|
|
const imagesLabel = imagesDiv.createEl('label');
|
|
this.imagesToggle = imagesLabel.createEl('input', {type: 'checkbox'});
|
|
this.imagesToggle.checked = false;
|
|
imagesLabel.createSpan({text: ' Include Images'});
|
|
|
|
// Add description for images toggle
|
|
const imagesDesc = imagesDiv.createDiv({cls: 'setting-item-description images-description'});
|
|
imagesDesc.textContent = this.promptsService.getImagesToggleGenericDescription();
|
|
|
|
// Stream toggle
|
|
const streamDiv = form.createDiv({cls: 'setting-item'});
|
|
const streamLabel = streamDiv.createEl('label');
|
|
this.streamToggle = streamLabel.createEl('input', {type: 'checkbox'});
|
|
this.streamToggle.checked = true;
|
|
streamLabel.createSpan({text: ' Stream response'});
|
|
|
|
const buttonDiv = contentEl.createDiv({cls: 'setting-item'});
|
|
const askButton = buttonDiv.createEl('button', {
|
|
text: 'Ask LM Studio',
|
|
cls: 'mod-cta'
|
|
});
|
|
|
|
form.onsubmit = (e) => {
|
|
e.preventDefault();
|
|
this.onSubmit();
|
|
};
|
|
|
|
askButton.onclick = () => this.onSubmit();
|
|
|
|
// Focus on the query input
|
|
setTimeout(() => this.queryInput.focus(), 100);
|
|
}
|
|
|
|
async onSubmit() {
|
|
const query = this.queryInput.value.trim();
|
|
if (!query) {
|
|
new Notice(this.promptsService.getEnterQuestionNotice());
|
|
return;
|
|
}
|
|
|
|
// If images are enabled, add image markers to the query
|
|
let processedQuery = query;
|
|
if (this.imagesToggle.checked) {
|
|
processedQuery = `${query}
|
|
|
|
${this.promptsService.getImageReferencesPrompt()}`;
|
|
}
|
|
|
|
const options: LMStudioOptions = {
|
|
max_tokens: parseInt(this.maxTokensInput.value) || 2048,
|
|
temperature: parseFloat(this.temperatureInput.value) || 0.7,
|
|
return_images: this.imagesToggle.checked
|
|
};
|
|
|
|
const systemPrompt = this.systemPromptInput.value.trim();
|
|
if (systemPrompt) {
|
|
options.system_prompt = systemPrompt;
|
|
}
|
|
|
|
this.close();
|
|
await this.lmStudioService.queryLMStudio(
|
|
processedQuery,
|
|
this.modelSelect.value,
|
|
this.streamToggle.checked,
|
|
this.editor,
|
|
options
|
|
);
|
|
}
|
|
|
|
onClose() {
|
|
const {contentEl} = this;
|
|
contentEl.empty();
|
|
}
|
|
}
|