mirror of
https://github.com/lossless-group/perplexed-plugin.git
synced 2026-07-22 06:49:50 +00:00
attempt(lmstudio): attempt to fix lmstudio
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
This commit is contained in:
parent
8b3149ee67
commit
c4c75ad68b
9 changed files with 894 additions and 322 deletions
|
|
@ -1,3 +1,4 @@
|
|||

|
||||
# Perplexed: AI Content Generation for Obsidian
|
||||
|
||||
**Perplexed** is an Obsidian plugin that enables AI-powered content generation with source citations using [Perplexity](https://www.perplexity.ai/) and [Perplexica](https://perplexica.io/). This plugin brings research-grade AI capabilities directly into your Obsidian workspace, allowing you to generate well-cited content for your notes.
|
||||
|
|
|
|||
384
main.ts
384
main.ts
|
|
@ -1,68 +1,156 @@
|
|||
import { App, Editor, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
import * as dotenv from 'dotenv';
|
||||
import { App, Editor, Notice, PluginSettingTab, Setting } from 'obsidian';
|
||||
import PerplexedPluginCore from './src/core/PerplexedPluginCore';
|
||||
|
||||
// Import services
|
||||
// Load environment variables
|
||||
import * as dotenv from 'dotenv';
|
||||
dotenv.config({ path: `${process.cwd()}/.env` });
|
||||
|
||||
// Services
|
||||
import { PerplexityService } from './src/services/perplexityService';
|
||||
import { PerplexicaService } from './src/services/perplexicaService';
|
||||
import { LMStudioService } from './src/services/lmStudioService';
|
||||
import { PromptsService } from './src/services/promptsService';
|
||||
|
||||
// Import modals
|
||||
// Modals
|
||||
import { PerplexityModal } from './src/modals/PerplexityModal';
|
||||
import { PerplexicaModal } from './src/modals/PerplexicaModal';
|
||||
import { LMStudioModal } from './src/modals/LMStudioModal';
|
||||
import { URLUpdateModal } from './src/modals/URLUpdateModal';
|
||||
import { ArticleGeneratorModal } from './src/modals/ArticleGeneratorModal';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config({ path: `${process.cwd()}/.env` });
|
||||
// Settings and types
|
||||
import { PerplexedSettings } from './src/settings/PerplexedSettings';
|
||||
import type { PerplexitySettings } from './src/services/perplexityService';
|
||||
import type { PerplexicaSettings } from './src/services/perplexicaService';
|
||||
import { LMStudioSettings } from './src/settings/LMStudioSettings';
|
||||
|
||||
interface PerplexedPluginSettings {
|
||||
mySetting: string;
|
||||
localLLMPath: string;
|
||||
requestBodyTemplate: string;
|
||||
perplexityRequestTemplate: string;
|
||||
perplexityApiKey: string;
|
||||
perplexicaEndpoint: string;
|
||||
perplexityEndpoint: string;
|
||||
lmStudioEndpoint: string;
|
||||
lmStudioRequestTemplate: string;
|
||||
defaultModel: string;
|
||||
defaultOptimizationMode: string;
|
||||
defaultFocusMode: string;
|
||||
defaultLMStudioModel: string;
|
||||
/**
|
||||
* Main plugin class that extends the core functionality
|
||||
*/
|
||||
export default class PerplexedPlugin extends PerplexedPluginCore {
|
||||
// Service instances with proper types
|
||||
public promptsService!: PromptsService;
|
||||
public perplexityService!: PerplexityService;
|
||||
public perplexicaService!: PerplexicaService;
|
||||
public lmStudioService!: LMStudioService;
|
||||
|
||||
// Settings interfaces
|
||||
public settings!: PerplexedSettings;
|
||||
|
||||
// Service settings
|
||||
public perplexitySettings!: PerplexitySettings;
|
||||
public perplexicaSettings!: PerplexicaSettings;
|
||||
public lmStudioSettings!: LMStudioSettings;
|
||||
|
||||
// UI Elements
|
||||
private statusBarItemEl: HTMLElement | null = null;
|
||||
private ribbonIconEl: HTMLElement | null = null;
|
||||
|
||||
/**
|
||||
* Initialize service-specific settings
|
||||
*/
|
||||
private initializeServiceSettings(): void {
|
||||
// Initialize Perplexity settings
|
||||
this.perplexitySettings = {
|
||||
perplexityApiKey: this.settings.perplexityApiKey || '',
|
||||
perplexityEndpoint: this.settings.perplexityEndpoint || 'https://api.perplexity.ai',
|
||||
promptsService: this.promptsService
|
||||
};
|
||||
|
||||
// Initialize Perplexica settings
|
||||
this.perplexicaSettings = {
|
||||
perplexicaEndpoint: this.settings.perplexityEndpoint || 'https://api.perplexity.ai',
|
||||
localLLMPath: this.settings.localLLMPath || '',
|
||||
defaultModel: this.settings.defaultModel || 'gpt-3.5-turbo',
|
||||
promptsService: this.promptsService
|
||||
};
|
||||
|
||||
// Initialize LM Studio settings
|
||||
this.lmStudioSettings = {
|
||||
endpoints: {
|
||||
baseUrl: this.settings.lmStudioEndpoint || 'http://localhost:1234',
|
||||
chatCompletions: '/v1/chat/completions',
|
||||
completions: '/v1/completions',
|
||||
embeddings: '/v1/embeddings',
|
||||
models: '/v1/models'
|
||||
},
|
||||
defaultModel: this.settings.defaultLMStudioModel || 'ibm/granite-3.2-8b',
|
||||
promptsService: this.promptsService
|
||||
};
|
||||
}
|
||||
|
||||
// Prompt Settings
|
||||
prompts: {
|
||||
// System prompts
|
||||
perplexitySystemPrompt: string;
|
||||
perplexicaSystemPrompt: string;
|
||||
lmStudioDefaultSystemPrompt: string;
|
||||
/**
|
||||
* Initialize all services with their respective settings
|
||||
*/
|
||||
private initializeServices(): void {
|
||||
// Initialize Perplexity service
|
||||
this.perplexityService = new PerplexityService(this.perplexitySettings);
|
||||
|
||||
// Placeholder text
|
||||
perplexityQueryPlaceholder: string;
|
||||
perplexicaQueryPlaceholder: string;
|
||||
lmStudioQueryPlaceholder: string;
|
||||
lmStudioSystemPromptPlaceholder: string;
|
||||
articleTermPlaceholder: string;
|
||||
// Initialize Perplexica service
|
||||
this.perplexicaService = new PerplexicaService(this.perplexicaSettings);
|
||||
|
||||
// Descriptions and labels
|
||||
deepResearchDescription: string;
|
||||
imagesToggleDescription: string;
|
||||
imagesToggleGenericDescription: string;
|
||||
articleTermDescription: string;
|
||||
// Initialize LM Studio service
|
||||
this.lmStudioService = new LMStudioService(this.lmStudioSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the UI components
|
||||
*/
|
||||
private initializeUI(): void {
|
||||
// Add status bar item if enabled
|
||||
if (this.settings.showStatusBar) {
|
||||
this.statusBarItemEl = this.addStatusBarItem();
|
||||
this.statusBarItemEl.setText('Perplexed');
|
||||
}
|
||||
|
||||
// Notices and messages
|
||||
deepResearchLoadingNotice: string;
|
||||
enterQuestionNotice: string;
|
||||
enterTermNotice: string;
|
||||
// Add ribbon icon
|
||||
this.ribbonIconEl = this.addRibbonIcon(
|
||||
'zap',
|
||||
'Perplexed',
|
||||
() => {
|
||||
// Open command palette
|
||||
// @ts-ignore - app is available in the Obsidian context
|
||||
this.app.commands.executeCommandById('perplexed:open-command-palette');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the plugin
|
||||
*/
|
||||
async onload() {
|
||||
await super.onload();
|
||||
|
||||
// Article generator template
|
||||
articleGeneratorTemplate: string;
|
||||
// Initialize prompts service first as it's used by other services
|
||||
this.promptsService = new PromptsService(this.settings.prompts);
|
||||
|
||||
// Image prompts
|
||||
imageReferencesPrompt: string;
|
||||
};
|
||||
// Initialize service settings
|
||||
this.initializeServiceSettings();
|
||||
|
||||
// Initialize services with their respective settings
|
||||
this.initializeServices();
|
||||
|
||||
// Register commands and UI elements
|
||||
this.registerCommands();
|
||||
this.initializeUI();
|
||||
|
||||
// Add settings tab
|
||||
this.addSettingTab(new PerplexedSettingTab(this.app, this));
|
||||
|
||||
// Register service-specific commands
|
||||
this.registerPerplexicaCommands();
|
||||
this.registerPerplexityCommands();
|
||||
this.registerLMStudioCommands();
|
||||
this.registerArticleGeneratorCommands();
|
||||
|
||||
console.log('Perplexed plugin loaded');
|
||||
}
|
||||
|
||||
onunload() {
|
||||
this.statusBarItemEl?.remove();
|
||||
this.ribbonIconEl?.remove();
|
||||
console.log('Perplexed plugin unloaded');
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: PerplexedPluginSettings = {
|
||||
|
|
@ -210,103 +298,29 @@ Replace "{TERM}" with the actual vocabulary term in the prompt.`,
|
|||
}
|
||||
};
|
||||
|
||||
export default class PerplexedPlugin extends Plugin {
|
||||
public settings: PerplexedPluginSettings = DEFAULT_SETTINGS;
|
||||
private statusBarItemEl: HTMLElement | null = null;
|
||||
private ribbonIconEl: HTMLElement | null = null;
|
||||
|
||||
// Service instances
|
||||
private perplexityService!: PerplexityService;
|
||||
private perplexicaService!: PerplexicaService;
|
||||
private lmStudioService!: LMStudioService;
|
||||
private promptsService!: PromptsService;
|
||||
|
||||
async onload(): Promise<void> {
|
||||
await this.loadSettings();
|
||||
|
||||
// Initialize prompts service
|
||||
this.promptsService = new PromptsService(this.settings.prompts);
|
||||
|
||||
// Initialize services
|
||||
this.perplexityService = new PerplexityService({
|
||||
perplexityApiKey: this.settings.perplexityApiKey,
|
||||
perplexityEndpoint: this.settings.perplexityEndpoint,
|
||||
promptsService: this.promptsService,
|
||||
requestTemplate: this.settings.perplexityRequestTemplate
|
||||
});
|
||||
|
||||
this.perplexicaService = new PerplexicaService({
|
||||
perplexicaEndpoint: this.settings.perplexicaEndpoint,
|
||||
localLLMPath: this.settings.localLLMPath,
|
||||
defaultModel: this.settings.defaultModel,
|
||||
promptsService: this.promptsService,
|
||||
requestTemplate: this.settings.requestBodyTemplate
|
||||
});
|
||||
|
||||
this.lmStudioService = new LMStudioService({
|
||||
lmStudioEndpoint: this.settings.lmStudioEndpoint,
|
||||
promptsService: this.promptsService,
|
||||
requestTemplate: this.settings.lmStudioRequestTemplate
|
||||
});
|
||||
|
||||
// Debug: Log current settings
|
||||
console.log('Current Perplexica Path:', this.settings.perplexicaEndpoint);
|
||||
console.log('Full settings:', JSON.stringify(this.settings, null, 2));
|
||||
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new PerplexedSettingTab(this.app, this));
|
||||
|
||||
// Register commands
|
||||
this.registerPerplexicaCommands();
|
||||
this.registerPerplexityCommands();
|
||||
this.registerLMStudioCommands();
|
||||
this.registerArticleGeneratorCommands();
|
||||
}
|
||||
|
||||
onunload(): void {
|
||||
this.statusBarItemEl?.remove();
|
||||
this.ribbonIconEl?.remove();
|
||||
}
|
||||
|
||||
private async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async saveSettings(): Promise<void> {
|
||||
try {
|
||||
await this.saveData(this.settings);
|
||||
} catch (error) {
|
||||
console.error('Failed to save settings:', error);
|
||||
new Notice('Failed to save settings');
|
||||
editor: Editor,
|
||||
options?: {
|
||||
max_tokens?: number;
|
||||
temperature?: number;
|
||||
top_p?: number;
|
||||
system_prompt?: string;
|
||||
return_images?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate methods to services
|
||||
public async queryPerplexity(query: string, model: string, stream: boolean, editor: Editor, options?: {
|
||||
return_citations?: boolean;
|
||||
return_images?: boolean;
|
||||
return_related_questions?: boolean;
|
||||
search_recency_filter?: string;
|
||||
}): Promise<void> {
|
||||
await this.perplexityService.queryPerplexity(query, model, stream, editor, options);
|
||||
}
|
||||
|
||||
public async queryPerplexica(query: string, focusMode: string, optimizationMode: string, stream: boolean, editor: Editor, options?: {
|
||||
return_images?: boolean;
|
||||
}): Promise<void> {
|
||||
await this.perplexicaService.queryPerplexica(query, focusMode, optimizationMode, stream, editor, options);
|
||||
}
|
||||
|
||||
public async queryLMStudio(query: string, model: string, stream: boolean, editor: Editor, options?: {
|
||||
max_tokens?: number;
|
||||
temperature?: number;
|
||||
top_p?: number;
|
||||
system_prompt?: string;
|
||||
return_images?: boolean;
|
||||
}): Promise<void> {
|
||||
await this.lmStudioService.queryLMStudio(query, model, stream, editor, options);
|
||||
): Promise<void> {
|
||||
// Use the provided model or fall back to the default from settings
|
||||
const modelToUse = model || this.settings.defaultLMStudioModel;
|
||||
|
||||
// Log the model being used for debugging
|
||||
console.log('Using model:', modelToUse);
|
||||
|
||||
await this.lmStudioService.queryLMStudio(
|
||||
query,
|
||||
modelToUse,
|
||||
stream,
|
||||
editor,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
// Getter for prompts service
|
||||
|
|
@ -431,7 +445,7 @@ export default class PerplexedPlugin extends Plugin {
|
|||
id: 'ask-lmstudio',
|
||||
name: 'Ask LM Studio',
|
||||
editorCallback: (editor: Editor) => {
|
||||
const modal = new LMStudioModal(this.app, editor, this.lmStudioService, this.promptsService);
|
||||
const modal = new LMStudioModal(this.app, editor, this, this.lmStudioService, this.promptsService);
|
||||
modal.open();
|
||||
}
|
||||
});
|
||||
|
|
@ -608,36 +622,94 @@ class PerplexedSettingTab extends PluginSettingTab {
|
|||
perplexicaJsonSetting.settingEl.appendChild(perplexicaTextArea);
|
||||
|
||||
// LM Studio Section
|
||||
const lmStudioHeader = containerEl.createEl('h3', { text: 'LM Studio (Local Models)' });
|
||||
const lmStudioHeader = containerEl.createEl('h3', { text: 'LM Studio (Local LLM)' });
|
||||
lmStudioHeader.style.color = 'var(--text-accent)';
|
||||
containerEl.createEl('p', {
|
||||
text: 'Configure settings for your local LM Studio installation with loaded models',
|
||||
text: 'Configure settings for LM Studio local LLM service',
|
||||
cls: 'setting-item-description'
|
||||
});
|
||||
|
||||
// Base URL setting
|
||||
new Setting(containerEl)
|
||||
.setName('Endpoint')
|
||||
.setDesc('API endpoint for your local LM Studio instance')
|
||||
.setName('Base URL')
|
||||
.setDesc('Base URL for LM Studio API (e.g., http://localhost:1234)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('http://localhost:1234/v1/chat/completions')
|
||||
.setPlaceholder('http://localhost:1234')
|
||||
.setValue(this.plugin.settings.lmStudioEndpoint)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.lmStudioEndpoint = value;
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.lmStudioEndpoint = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
}));
|
||||
|
||||
// Endpoints configuration
|
||||
containerEl.createEl('h4', { text: 'API Endpoints' }).style.marginTop = '20px';
|
||||
|
||||
// Chat Completions Endpoint
|
||||
new Setting(containerEl)
|
||||
.setName('Chat Completions')
|
||||
.setDesc('Endpoint for chat completions')
|
||||
.addText(text => text
|
||||
.setValue('/v1/chat/completions')
|
||||
.setDisabled(true));
|
||||
|
||||
// Completions Endpoint
|
||||
new Setting(containerEl)
|
||||
.setName('Completions')
|
||||
.setDesc('Endpoint for completions')
|
||||
.addText(text => text
|
||||
.setValue('/v1/completions')
|
||||
.setDisabled(true));
|
||||
|
||||
// Embeddings Endpoint
|
||||
new Setting(containerEl)
|
||||
.setName('Embeddings')
|
||||
.setDesc('Endpoint for embeddings')
|
||||
.addText(text => text
|
||||
.setValue('/v1/embeddings')
|
||||
.setDisabled(true));
|
||||
|
||||
// Models Endpoint
|
||||
new Setting(containerEl)
|
||||
.setName('Models')
|
||||
.setDesc('Endpoint for listing available models')
|
||||
.addText(text => text
|
||||
.setValue('/v1/models')
|
||||
.setDisabled(true));
|
||||
|
||||
// Default Model
|
||||
new Setting(containerEl)
|
||||
.setName('Default Model')
|
||||
.setDesc('Default model name for LM Studio to use')
|
||||
.setDesc('Default model to use with LM Studio')
|
||||
.addText(text => text
|
||||
.setPlaceholder('ibm/granite-3.2-8b')
|
||||
.setValue(this.plugin.settings.defaultLMStudioModel)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.defaultLMStudioModel = value;
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.defaultLMStudioModel = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
}));
|
||||
|
||||
// Test Connection Button
|
||||
const testConnectionSetting = new Setting(containerEl)
|
||||
.setName('Test Connection')
|
||||
.setDesc('Verify connection to LM Studio API');
|
||||
|
||||
testConnectionSetting.addButton(button => {
|
||||
button.setButtonText('Test Connection')
|
||||
.onClick(async () => {
|
||||
try {
|
||||
const response = await fetch(`${this.plugin.settings.lmStudioEndpoint}/v1/models`);
|
||||
if (response.ok) {
|
||||
new Notice('✅ Successfully connected to LM Studio API');
|
||||
} else {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.error('LM Studio connection test failed:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
||||
new Notice(`❌ Failed to connect to LM Studio: ${errorMessage}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// LM Studio Request Template
|
||||
const lmStudioJsonSetting = new Setting(containerEl)
|
||||
|
|
|
|||
54
src/core/PerplexedPluginCore.ts
Normal file
54
src/core/PerplexedPluginCore.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { App, Plugin } from 'obsidian';
|
||||
import { PerplexedSettings, DEFAULT_SETTINGS } from '../settings/PerplexedSettings';
|
||||
import { PerplexedSettingTab } from '../settings/PerplexedSettings';
|
||||
import { LMStudioSettings, DEFAULT_LMSTUDIO_SETTINGS } from '../settings/LMStudioSettings';
|
||||
|
||||
export default class PerplexedPluginCore extends Plugin {
|
||||
settings: PerplexedSettings;
|
||||
lmStudioSettings: LMStudioSettings;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
// Add settings tab
|
||||
this.addSettingTab(new PerplexedSettingTab(this.app, this));
|
||||
|
||||
// Register commands and other plugin initialization
|
||||
this.registerCommands();
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
// Load main settings
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
|
||||
// Load LM Studio settings
|
||||
this.lmStudioSettings = Object.assign(
|
||||
{},
|
||||
DEFAULT_LMSTUDIO_SETTINGS,
|
||||
await this.loadData()
|
||||
);
|
||||
|
||||
// Save any defaults that were missing
|
||||
await this.saveSettings();
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
// Save main settings
|
||||
await this.saveData(this.settings);
|
||||
|
||||
// Save LM Studio settings
|
||||
await this.saveData(this.lmStudioSettings);
|
||||
}
|
||||
|
||||
private registerCommands() {
|
||||
// Register your commands here
|
||||
// Example:
|
||||
this.addCommand({
|
||||
id: 'perplexed-query',
|
||||
name: 'Query Perplexed',
|
||||
callback: () => {
|
||||
// Command implementation
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
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;
|
||||
|
|
@ -14,9 +16,10 @@ export class LMStudioModal extends Modal {
|
|||
private systemPromptInput!: HTMLTextAreaElement;
|
||||
private imagesToggle!: HTMLInputElement;
|
||||
|
||||
constructor(app: App, editor: Editor, lmStudioService: LMStudioService, promptsService: PromptsService) {
|
||||
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;
|
||||
}
|
||||
|
|
@ -43,10 +46,25 @@ export class LMStudioModal extends Modal {
|
|||
const modelDiv = form.createDiv({cls: 'setting-item'});
|
||||
modelDiv.createEl('label', {text: 'Model'});
|
||||
this.modelSelect = modelDiv.createEl('select', {cls: 'dropdown'});
|
||||
// Use common LM Studio models - these would be dynamically loaded ideally
|
||||
['ibm/granite-3.2-8b', 'microsoft/phi-4-reasoning-plus', 'google/gemma-3-12b', 'meta-llama/llama-3.2-3b-instruct', 'custom-model'].forEach(model => {
|
||||
|
||||
// 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 === 'ibm/granite-3.2-8b') option.selected = true;
|
||||
if (model === defaultModel) {
|
||||
option.selected = true;
|
||||
}
|
||||
});
|
||||
|
||||
// System prompt
|
||||
|
|
|
|||
|
|
@ -1,4 +1,27 @@
|
|||
import { Editor, Notice } from 'obsidian';
|
||||
import { LMStudioSettings } from '../settings/LMStudioSettings';
|
||||
|
||||
export interface ChatMessage {
|
||||
role: 'system' | 'user' | 'assistant';
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface LMStudioResponse {
|
||||
id: string;
|
||||
object: string;
|
||||
created: number;
|
||||
model: string;
|
||||
choices: Array<{
|
||||
index: number;
|
||||
message: ChatMessage;
|
||||
finish_reason: string | null;
|
||||
}>;
|
||||
usage?: {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LMStudioOptions {
|
||||
max_tokens?: number;
|
||||
|
|
@ -8,19 +31,29 @@ export interface LMStudioOptions {
|
|||
return_images?: boolean;
|
||||
}
|
||||
|
||||
export interface LMStudioSettings {
|
||||
lmStudioEndpoint: string;
|
||||
promptsService?: any; // Will be PromptsService type
|
||||
requestTemplate?: string;
|
||||
export interface LMStudioEndpointConfig {
|
||||
baseUrl: string;
|
||||
chatCompletions: string;
|
||||
completions: string;
|
||||
embeddings: string;
|
||||
models: string;
|
||||
}
|
||||
|
||||
// LMStudioSettings is now imported from LMStudioSettings.ts
|
||||
|
||||
export class LMStudioService {
|
||||
private settings: LMStudioSettings;
|
||||
|
||||
private readonly settings: LMStudioSettings;
|
||||
private promptsService: any;
|
||||
|
||||
constructor(settings: LMStudioSettings) {
|
||||
this.settings = settings;
|
||||
this.promptsService = settings.promptsService;
|
||||
this.initializePromptsService();
|
||||
}
|
||||
|
||||
private initializePromptsService() {
|
||||
// Initialize prompts service if needed
|
||||
// This can be implemented based on your specific requirements
|
||||
}
|
||||
|
||||
private processContentWithImages(content: string): string {
|
||||
|
|
@ -41,120 +74,12 @@ export class LMStudioService {
|
|||
}
|
||||
|
||||
if (imageIndex > 0) {
|
||||
console.log(`🔄 Processed ${imageIndex} image markers in LM Studio content`);
|
||||
console.log(` Processed ${imageIndex} image markers in LM Studio content`);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
public async queryLMStudio(
|
||||
query: string,
|
||||
model: string,
|
||||
stream: boolean,
|
||||
editor: Editor,
|
||||
options?: LMStudioOptions
|
||||
): Promise<void> {
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
// Insert query header at the current cursor position
|
||||
const cursor = editor.getCursor();
|
||||
console.log('Initial cursor position:', cursor);
|
||||
|
||||
// Process query to handle multi-line content in callout
|
||||
const processedQuery = query.split('\n').map(line => `> ${line}`).join('\n');
|
||||
|
||||
const headerText = `\n\n***\n> [!info] **LM Studio Query** (${timestamp})\n> **Question:**\n${processedQuery}\n> **Model:** ${model}\n> \n> ### **Response from ${model}**:\n\n`;
|
||||
|
||||
// Insert the header at the cursor position
|
||||
editor.replaceRange(headerText, cursor, cursor);
|
||||
|
||||
// Calculate where the response content should start
|
||||
const headerLines = headerText.split('\n');
|
||||
const lastLine = headerLines[headerLines.length - 1] || '';
|
||||
const responseCursor = {
|
||||
line: cursor.line + headerLines.length - 1,
|
||||
ch: lastLine.length
|
||||
};
|
||||
|
||||
console.log('Response cursor position:', responseCursor);
|
||||
|
||||
try {
|
||||
const messages: any[] = [];
|
||||
|
||||
// Add system message if provided
|
||||
if (options?.system_prompt) {
|
||||
messages.push({ role: 'system', content: options.system_prompt });
|
||||
}
|
||||
|
||||
// Add user query
|
||||
messages.push({ role: 'user', content: query });
|
||||
|
||||
// Use template if available, otherwise construct payload manually
|
||||
let payload: any;
|
||||
if (this.settings.requestTemplate) {
|
||||
try {
|
||||
const processedTemplate = this.promptsService?.processTemplate(this.settings.requestTemplate) || this.settings.requestTemplate;
|
||||
payload = JSON.parse(processedTemplate);
|
||||
// Override with current parameters
|
||||
payload.model = model;
|
||||
payload.messages = messages;
|
||||
payload.stream = stream;
|
||||
payload.max_tokens = options?.max_tokens ?? 2048;
|
||||
payload.temperature = options?.temperature ?? 0.7;
|
||||
payload.top_p = options?.top_p ?? 0.9;
|
||||
} catch (error) {
|
||||
console.warn('Failed to parse request template, using default payload:', error);
|
||||
payload = {
|
||||
model,
|
||||
messages,
|
||||
stream,
|
||||
max_tokens: options?.max_tokens ?? 2048,
|
||||
temperature: options?.temperature ?? 0.7,
|
||||
top_p: options?.top_p ?? 0.9
|
||||
};
|
||||
}
|
||||
} else {
|
||||
payload = {
|
||||
model,
|
||||
messages,
|
||||
stream,
|
||||
max_tokens: options?.max_tokens ?? 2048,
|
||||
temperature: options?.temperature ?? 0.7,
|
||||
top_p: options?.top_p ?? 0.9
|
||||
};
|
||||
}
|
||||
|
||||
const response = await fetch(this.settings.lmStudioEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
// LM Studio doesn't require API key for local access
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
let finalCursor = responseCursor;
|
||||
|
||||
if (stream) {
|
||||
await this.handleStreamingResponse(response, editor, responseCursor, options);
|
||||
} else {
|
||||
await this.handleNonStreamingResponse(response, editor, responseCursor, options);
|
||||
}
|
||||
|
||||
// Add separator at the final cursor position
|
||||
editor.replaceRange('\n\n***\n', finalCursor);
|
||||
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
new Notice(`LM Studio Error: ${errorMsg}`);
|
||||
editor.replaceRange(`\n**Error:** ${errorMsg}\n\n***\n`, editor.getCursor());
|
||||
}
|
||||
}
|
||||
|
||||
private async handleStreamingResponse(
|
||||
response: Response,
|
||||
editor: Editor,
|
||||
|
|
@ -165,55 +90,60 @@ export class LMStudioService {
|
|||
if (!reader) throw new Error('No response body');
|
||||
|
||||
let buffer = '';
|
||||
let currentPos = responseCursor;
|
||||
let currentPos = { ...responseCursor };
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = new TextDecoder().decode(value);
|
||||
buffer += chunk;
|
||||
|
||||
// Process complete lines from buffer
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim().startsWith('data: ')) {
|
||||
const data = line.replace('data: ', '').trim();
|
||||
if (data === '[DONE]') continue;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
if (parsed.choices?.[0]?.delta?.content) {
|
||||
let content = parsed.choices[0].delta.content;
|
||||
|
||||
// Process images if enabled
|
||||
if (options?.return_images) {
|
||||
content = this.processContentWithImages(content);
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = new TextDecoder().decode(value);
|
||||
buffer += chunk;
|
||||
|
||||
// Process complete lines from buffer
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim().startsWith('data: ')) {
|
||||
const data = line.replace('data: ', '').trim();
|
||||
if (data === '[DONE]') continue;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
if (parsed.choices?.[0]?.delta?.content) {
|
||||
let content = parsed.choices[0].delta.content;
|
||||
|
||||
// Process images if enabled
|
||||
if (options?.return_images) {
|
||||
content = this.processContentWithImages(content);
|
||||
}
|
||||
|
||||
editor.replaceRange(content, currentPos);
|
||||
// Update cursor position after insertion
|
||||
const lines = content.split('\n');
|
||||
if (lines.length === 1) {
|
||||
currentPos = { line: currentPos.line, ch: currentPos.ch + content.length };
|
||||
} else {
|
||||
currentPos = {
|
||||
line: currentPos.line + lines.length - 1,
|
||||
ch: lines[lines.length - 1]?.length || 0
|
||||
};
|
||||
}
|
||||
// Scroll to follow the new content
|
||||
editor.scrollIntoView({ from: currentPos, to: currentPos }, true);
|
||||
// Small delay to make scrolling smoother
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
}
|
||||
|
||||
editor.replaceRange(content, currentPos);
|
||||
// Update cursor position after insertion
|
||||
const lines = content.split('\n');
|
||||
if (lines.length === 1) {
|
||||
currentPos = { line: currentPos.line, ch: currentPos.ch + content.length };
|
||||
} else {
|
||||
currentPos = {
|
||||
line: currentPos.line + lines.length - 1,
|
||||
ch: lines[lines.length - 1]?.length || 0
|
||||
};
|
||||
}
|
||||
// Scroll to follow the new content
|
||||
editor.scrollIntoView({ from: currentPos, to: currentPos }, true);
|
||||
// Small delay to make scrolling smoother
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
} catch (e) {
|
||||
// Ignore JSON parse errors for partial chunks
|
||||
console.error('Error parsing streaming chunk:', e);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore JSON parse errors for partial chunks
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -234,4 +164,139 @@ export class LMStudioService {
|
|||
|
||||
editor.replaceRange(processedContent, responseCursor);
|
||||
}
|
||||
|
||||
private async makeRequest(
|
||||
endpoint: string,
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
|
||||
data?: unknown
|
||||
): Promise<Response> {
|
||||
const url = `${this.settings.endpoints.baseUrl}${endpoint}`;
|
||||
|
||||
const headers: HeadersInit = new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
});
|
||||
|
||||
const options: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
};
|
||||
|
||||
if (data !== undefined) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, options);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
new Notice(`Request failed: ${errorMessage}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async listModels(): Promise<{data: Array<{id: string}>, error?: string}> {
|
||||
try {
|
||||
const response = await this.makeRequest(this.settings.endpoints.models, 'GET');
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error listing models:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async queryLMStudio(
|
||||
editor: Editor,
|
||||
model?: string,
|
||||
messages: ChatMessage[] = [],
|
||||
stream = true,
|
||||
options: LMStudioOptions = {}
|
||||
): Promise<void> {
|
||||
if (!editor) {
|
||||
throw new Error('Editor instance is required');
|
||||
}
|
||||
|
||||
const cursor = editor.getCursor();
|
||||
const timestamp = new Date().toLocaleString();
|
||||
const modelToUse = model || this.settings.defaultModel || 'unknown-model';
|
||||
|
||||
// Process query for display
|
||||
const processedQuery = messages.length > 0
|
||||
? messages.map(message => `> ${message.content}`).join('\n')
|
||||
: '';
|
||||
|
||||
const headerText = `\n\n***\n> [!info] **LM Studio Query** (${timestamp})\n> **Question:**\n${processedQuery}\n> **Model:** ${modelToUse}\n> \n> ### **Response from ${modelToUse}**:\n\n`;
|
||||
|
||||
// Insert header and get response position
|
||||
editor.replaceRange(headerText, cursor);
|
||||
const headerLines = headerText.split('\n');
|
||||
const lastLine = headerLines[headerLines.length - 1] || '';
|
||||
const responseCursor = {
|
||||
line: cursor.line + headerLines.length - 1,
|
||||
ch: lastLine.length
|
||||
};
|
||||
|
||||
try {
|
||||
// Prepare messages array with system prompt if provided
|
||||
const messagesToSend = [...messages];
|
||||
|
||||
if (options.system_prompt) {
|
||||
messagesToSend.unshift({
|
||||
role: 'system',
|
||||
content: options.system_prompt
|
||||
});
|
||||
}
|
||||
|
||||
// Build the request payload
|
||||
let payload: Record<string, unknown> = {
|
||||
model: modelToUse,
|
||||
messages: messagesToSend,
|
||||
stream,
|
||||
temperature: options.temperature ?? 0.7,
|
||||
max_tokens: options.max_tokens ?? 2048,
|
||||
top_p: options.top_p ?? 0.9
|
||||
};
|
||||
|
||||
// Apply request template if available
|
||||
if (this.settings.requestTemplate) {
|
||||
try {
|
||||
const processedTemplate = this.promptsService?.processTemplate?.(this.settings.requestTemplate) ||
|
||||
this.settings.requestTemplate;
|
||||
const templatePayload = JSON.parse(processedTemplate);
|
||||
// Merge with template, allowing template to be overridden
|
||||
Object.assign(payload, templatePayload);
|
||||
} catch (error) {
|
||||
console.warn('Failed to parse request template, using default payload:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Make the API request
|
||||
const response = await this.makeRequest(
|
||||
this.settings.endpoints.chatCompletions,
|
||||
'POST',
|
||||
payload
|
||||
);
|
||||
|
||||
// Handle the response based on streaming preference
|
||||
if (stream) {
|
||||
await this.handleStreamingResponse(response, editor, responseCursor, options);
|
||||
} else {
|
||||
await this.handleNonStreamingResponse(response, editor, responseCursor, options);
|
||||
}
|
||||
|
||||
// Add a separator after the response
|
||||
editor.replaceRange('\n\n---\n\n', editor.getCursor());
|
||||
} catch (error) {
|
||||
console.error('Error querying LM Studio:', error);
|
||||
// Show error to the user
|
||||
const errorMessage = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
||||
editor.replaceRange(`\n\n${errorMessage}\n\n`, editor.getCursor());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
113
src/settings/LMStudioSettings.ts
Normal file
113
src/settings/LMStudioSettings.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import { App, Notice, Setting } from 'obsidian';
|
||||
|
||||
export interface LMStudioEndpointConfig {
|
||||
baseUrl: string;
|
||||
chatCompletions: string;
|
||||
completions: string;
|
||||
embeddings: string;
|
||||
models: string;
|
||||
}
|
||||
|
||||
export interface LMStudioSettings {
|
||||
endpoints: LMStudioEndpointConfig;
|
||||
defaultModel: string;
|
||||
requestTemplate?: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_LMSTUDIO_SETTINGS: LMStudioSettings = {
|
||||
endpoints: {
|
||||
baseUrl: 'http://localhost:1234',
|
||||
chatCompletions: '/v1/chat/completions',
|
||||
completions: '/v1/completions',
|
||||
embeddings: '/v1/embeddings',
|
||||
models: '/v1/models'
|
||||
},
|
||||
defaultModel: 'ibm/granite-3.2-8b',
|
||||
requestTemplate: ''
|
||||
};
|
||||
|
||||
export class LMStudioSettingSection {
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private readonly containerEl: HTMLElement,
|
||||
private settings: LMStudioSettings,
|
||||
private readonly onSettingsChange: () => Promise<void>
|
||||
) {}
|
||||
|
||||
public display(): void {
|
||||
this.containerEl.createEl('h3', { text: 'LM Studio Settings' });
|
||||
|
||||
this.addBaseUrlSetting();
|
||||
this.addDefaultModelSetting();
|
||||
this.addEndpointsInfo();
|
||||
this.addTestConnectionButton();
|
||||
}
|
||||
|
||||
private addBaseUrlSetting(): void {
|
||||
new Setting(this.containerEl)
|
||||
.setName('Base URL')
|
||||
.setDesc('The base URL of your LM Studio server (e.g., http://localhost:1234)')
|
||||
.addText(text => text
|
||||
.setValue(this.settings.endpoints.baseUrl)
|
||||
.onChange(async (value: string) => {
|
||||
this.settings.endpoints.baseUrl = value.trim();
|
||||
await this.onSettingsChange();
|
||||
}));
|
||||
}
|
||||
|
||||
private addDefaultModelSetting(): void {
|
||||
new Setting(this.containerEl)
|
||||
.setName('Default Model')
|
||||
.setDesc('The default model to use for completions')
|
||||
.addText(text => text
|
||||
.setValue(this.settings.defaultModel)
|
||||
.onChange(async (value: string) => {
|
||||
this.settings.defaultModel = value;
|
||||
await this.onSettingsChange();
|
||||
}));
|
||||
}
|
||||
|
||||
private addEndpointsInfo(): void {
|
||||
const endpointsContainer = this.containerEl.createDiv('setting-item');
|
||||
endpointsContainer.createDiv({ text: 'API Endpoints', cls: 'setting-item-name' });
|
||||
const endpointsDesc = endpointsContainer.createDiv('setting-item-description');
|
||||
|
||||
const { endpoints } = this.settings;
|
||||
const endpointList = [
|
||||
`Chat Completions: ${endpoints.chatCompletions}`,
|
||||
`Completions: ${endpoints.completions}`,
|
||||
`Embeddings: ${endpoints.embeddings}`,
|
||||
`Models: ${endpoints.models}`
|
||||
];
|
||||
|
||||
endpointList.forEach(endpoint => {
|
||||
endpointsDesc.createEl('div', { text: endpoint });
|
||||
});
|
||||
}
|
||||
|
||||
private addTestConnectionButton(): void {
|
||||
new Setting(this.containerEl)
|
||||
.setName('Test Connection')
|
||||
.setDesc('Test the connection to your LM Studio server')
|
||||
.addButton(button => button
|
||||
.setButtonText('Test')
|
||||
.onClick(async () => {
|
||||
try {
|
||||
const url = `${this.settings.endpoints.baseUrl}${this.settings.endpoints.models}`;
|
||||
const response = await fetch(url, { method: 'HEAD' });
|
||||
|
||||
if (response.ok) {
|
||||
new Notice('✅ Successfully connected to LM Studio server');
|
||||
} else {
|
||||
new Notice(`❌ Failed to connect: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
new Notice(`❌ Connection error: ${message}`);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export default LMStudioSettingSection;
|
||||
|
||||
132
src/settings/PerplexedSettings.ts
Normal file
132
src/settings/PerplexedSettings.ts
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import { App, PluginSettingTab, Setting } from 'obsidian';
|
||||
import PerplexedPlugin from '../../main';
|
||||
|
||||
export interface PerplexedSettings {
|
||||
// General Settings
|
||||
mySetting: string;
|
||||
localLLMPath: string;
|
||||
|
||||
// Perplexity Settings
|
||||
perplexityApiKey: string;
|
||||
perplexityEndpoint: string;
|
||||
|
||||
// LM Studio Settings
|
||||
lmStudioEndpoint: string;
|
||||
defaultLMStudioModel: string;
|
||||
|
||||
// Default Models and Modes
|
||||
defaultModel: string;
|
||||
defaultOptimizationMode: string;
|
||||
defaultFocusMode: string;
|
||||
|
||||
// Prompts
|
||||
prompts: {
|
||||
// System Prompts
|
||||
perplexitySystemPrompt: string;
|
||||
lmStudioDefaultSystemPrompt: string;
|
||||
|
||||
// Placeholders
|
||||
perplexityQueryPlaceholder: string;
|
||||
lmStudioQueryPlaceholder: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: PerplexedSettings = {
|
||||
mySetting: 'default',
|
||||
localLLMPath: 'http://host.docker.internal:3030/api/search',
|
||||
|
||||
// Perplexity Defaults
|
||||
perplexityApiKey: '',
|
||||
perplexityEndpoint: 'https://api.perplexity.ai',
|
||||
|
||||
// LM Studio Defaults
|
||||
lmStudioEndpoint: 'http://localhost:1234',
|
||||
defaultLMStudioModel: 'ibm/granite-3.2-8b',
|
||||
|
||||
// Default Models and Modes
|
||||
defaultModel: 'sonar-medium-online',
|
||||
defaultOptimizationMode: 'balanced',
|
||||
defaultFocusMode: 'search',
|
||||
|
||||
// Default Prompts
|
||||
prompts: {
|
||||
perplexitySystemPrompt: 'You are a helpful AI assistant that provides accurate and concise responses.',
|
||||
lmStudioDefaultSystemPrompt: 'You are a helpful AI assistant running locally.',
|
||||
|
||||
perplexityQueryPlaceholder: 'Ask me anything...',
|
||||
lmStudioQueryPlaceholder: 'Ask me anything...',
|
||||
}
|
||||
};
|
||||
|
||||
export class PerplexedSettingTab extends PluginSettingTab {
|
||||
plugin: PerplexedPlugin;
|
||||
|
||||
constructor(app: App, plugin: PerplexedPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
containerEl.createEl('h2', { text: 'Perplexed Plugin Settings' });
|
||||
|
||||
this.addGeneralSettings(containerEl);
|
||||
this.addPerplexitySettings(containerEl);
|
||||
this.addLMStudioSettings(containerEl);
|
||||
}
|
||||
|
||||
private addGeneralSettings(containerEl: HTMLElement): void {
|
||||
containerEl.createEl('h3', { text: 'General Settings' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Local LLM Path')
|
||||
.setDesc('Path to your local LLM API endpoint')
|
||||
.addText(text => text
|
||||
.setPlaceholder('http://localhost:3030/api/search')
|
||||
.setValue(this.plugin.settings.localLLMPath)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.localLLMPath = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
|
||||
private addPerplexitySettings(containerEl: HTMLElement): void {
|
||||
containerEl.createEl('h3', { text: 'Perplexity Settings' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('API Key')
|
||||
.setDesc('Your Perplexity API key')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your API key')
|
||||
.setValue(this.plugin.settings.perplexityApiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.perplexityApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
|
||||
private addLMStudioSettings(containerEl: HTMLElement): void {
|
||||
containerEl.createEl('h3', { text: 'LM Studio Settings' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Endpoint')
|
||||
.setDesc('LM Studio API endpoint (e.g., http://localhost:1234)')
|
||||
.addText(text => text
|
||||
.setValue(this.plugin.settings.lmStudioEndpoint)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.lmStudioEndpoint = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Default Model')
|
||||
.setDesc('Default model to use with LM Studio')
|
||||
.addText(text => text
|
||||
.setValue(this.plugin.settings.defaultLMStudioModel)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.defaultLMStudioModel = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
}
|
||||
52
src/types/obsidian.d.ts
vendored
52
src/types/obsidian.d.ts
vendored
|
|
@ -1,16 +1,22 @@
|
|||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian';
|
||||
import { App, Editor as ObsidianEditor, MarkdownView, Modal, Notice as ObsidianNotice, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian';
|
||||
|
||||
declare module 'obsidian' {
|
||||
interface App {
|
||||
commands: any;
|
||||
}
|
||||
|
||||
interface Editor {
|
||||
interface Editor extends ObsidianEditor {
|
||||
getSelection(): string;
|
||||
replaceSelection(text: string): void;
|
||||
getCursor(): { line: number, ch: number };
|
||||
replaceRange(text: string, from: { line: number, ch: number }, to?: { line: number, ch: number }): void;
|
||||
getCursor(from?: boolean): { line: number, ch: number };
|
||||
setCursor(line: number, ch: number): void;
|
||||
lastLine(): number;
|
||||
getLine(line: number): string;
|
||||
}
|
||||
|
||||
interface Notice extends ObsidianNotice {
|
||||
// Extend if needed with additional methods
|
||||
}
|
||||
|
||||
interface MarkdownView {
|
||||
|
|
@ -21,4 +27,44 @@ declare module 'obsidian' {
|
|||
interface PluginManifest {
|
||||
dir: string;
|
||||
}
|
||||
|
||||
// Additional utility types for LM Studio service
|
||||
interface LMStudioOptions {
|
||||
system_prompt?: string;
|
||||
max_tokens?: number;
|
||||
temperature?: number;
|
||||
top_p?: number;
|
||||
return_images?: boolean;
|
||||
[key: string]: any; // For additional options
|
||||
}
|
||||
|
||||
interface ChatMessage {
|
||||
role: 'system' | 'user' | 'assistant' | 'function';
|
||||
content: string;
|
||||
name?: string;
|
||||
function_call?: {
|
||||
name: string;
|
||||
arguments: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface LMStudioResponse {
|
||||
id: string;
|
||||
object: string;
|
||||
created: number;
|
||||
model: string;
|
||||
choices: Array<{
|
||||
index: number;
|
||||
message: {
|
||||
role: string;
|
||||
content: string;
|
||||
};
|
||||
finish_reason: string;
|
||||
}>;
|
||||
usage?: {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
73
styles.css
73
styles.css
|
|
@ -1 +1,72 @@
|
|||
.perplexity-modal .text-input{width:100%;margin:8px 0;padding:12px}.perplexity-modal .setting-item-description{font-size:12px;color:var(--text-muted);margin-top:5px}.perplexity-modal .setting-item-description.images-description{font-size:11px;margin-top:3px}.article-generator-modal .text-input{width:100%;margin:8px 0;padding:12px}.article-generator-modal .setting-item-description{font-size:12px;color:var(--text-muted);margin-top:5px}.article-generator-modal .term-description{display:block!important;margin-top:8px;margin-bottom:10px;width:100%!important;flex-basis:100%!important;order:2}.article-generator-modal .setting-item{flex-direction:column!important;align-items:flex-start!important}.article-generator-modal .setting-item>*{width:100%!important}.article-generator-modal .hidden-input{display:none}.perplexica-modal .text-input{width:100%;margin:8px 0;padding:12px}.lmstudio-modal .text-input{width:100%;margin:8px 0;padding:12px}.lmstudio-modal .system-prompt-input{width:100%;min-height:60px}.url-update-modal .text-input{width:100%;margin:8px 0;padding:12px}
|
||||
/* src/styles/perplexity-modal.css */
|
||||
.perplexity-modal .text-input {
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
padding: 12px;
|
||||
}
|
||||
.perplexity-modal .setting-item-description {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 5px;
|
||||
}
|
||||
.perplexity-modal .setting-item-description.images-description {
|
||||
font-size: 11px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
/* src/styles/article-generator-modal.css */
|
||||
.article-generator-modal .text-input {
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
padding: 12px;
|
||||
}
|
||||
.article-generator-modal .setting-item-description {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 5px;
|
||||
}
|
||||
.article-generator-modal .term-description {
|
||||
display: block !important;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 10px;
|
||||
width: 100% !important;
|
||||
flex-basis: 100% !important;
|
||||
order: 2;
|
||||
}
|
||||
.article-generator-modal .setting-item {
|
||||
flex-direction: column !important;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
.article-generator-modal .setting-item > * {
|
||||
width: 100% !important;
|
||||
}
|
||||
.article-generator-modal .hidden-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* src/styles/perplexica-modal.css */
|
||||
.perplexica-modal .text-input {
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
/* src/styles/lmstudio-modal.css */
|
||||
.lmstudio-modal .text-input {
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
padding: 12px;
|
||||
}
|
||||
.lmstudio-modal .system-prompt-input {
|
||||
width: 100%;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
/* src/styles/url-update-modal.css */
|
||||
.url-update-modal .text-input {
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
/* src/styles/main.css */
|
||||
|
|
|
|||
Loading…
Reference in a new issue