refactor(main): backed up main and fixed it relative to refactor

On branch fix/lmstudio
 Changes to be committed:
	modified:   main.ts
	new file:   main.ts.bak
	new file:   src/core/PerplexedPlugin.ts
	modified:   src/core/PerplexedPluginCore.ts
	modified:   src/services/perplexityService.ts
	new file:   src/settings/PerplexedSettingTab.ts
	modified:   src/settings/PerplexedSettings.ts
	new file:   src/settings/PerplexitySettings.ts
This commit is contained in:
mpstaton 2025-07-26 18:00:59 +03:00
parent c4c75ad68b
commit a6417ec351
8 changed files with 1370 additions and 1074 deletions

1008
main.ts

File diff suppressed because it is too large Load diff

898
main.ts.bak Normal file
View file

@ -0,0 +1,898 @@
import { Notice } from 'obsidian';
import { PerplexedPluginCore } from './src/core/PerplexedPluginCore';
import { PerplexedSettingTab } from './src/settings/PerplexedSettingTab';
export default class PerplexedPlugin extends PerplexedPluginCore {
private statusBarItemEl?: HTMLElement;
private ribbonIconEl?: HTMLElement;
async onload(): Promise<void> {
await super.onload();
this.registerCommands();
this.setupUI();
}
protected registerCommands(): void {
this.addCommand({
id: 'show-perplexed-settings',
name: 'Show Settings',
callback: () => {
new PerplexedSettingTab(this.app, this).display();
}
});
}
protected setupUI(): void {
if (this.settings.showStatusBar) {
this.statusBarItemEl = this.addStatusBarItem();
this.statusBarItemEl.setText('Perplexed');
}
this.ribbonIconEl = this.addRibbonIcon(
'zap',
'Perplexed',
() => new Notice('Perplexed plugin is ready')
);
}
onunload() {
this.statusBarItemEl?.remove();
this.ribbonIconEl?.remove();
console.log('Perplexed plugin unloaded');
}
}
mySetting: 'default',
// Use host.docker.internal to connect to the host machine from Docker containers
localLLMPath: 'http://host.docker.internal:3030/api/search',
perplexicaEndpoint: 'http://localhost:3030/api/search',
perplexityEndpoint: 'https://api.perplexity.ai/chat/completions',
lmStudioEndpoint: 'http://localhost:1234/v1/chat/completions',
requestBodyTemplate: `{
"chatModel": {
"provider": "ollama",
"name": "llama3.2:latest"
},
"embeddingModel": {
"provider": "ollama",
"name": "llama3.2:latest"
},
"optimizationMode": "speed",
"focusMode": "webSearch",
"query": "What is Perplexica's architecture?",
"history": [
{
"role": "user",
"content": "What is Perplexica's architecture?"
}
],
"systemInstructions": "{{PERPLEXICA_SYSTEM_PROMPT}}",
"stream": false,
"maxTokens": 2048,
"temperature": 0.7
}`,
perplexityApiKey: process.env.PERPLEXITY_API_KEY || '',
perplexityRequestTemplate: `{
"model": "llama-3.1-sonar-small-128k-online",
"messages": [
{
"role": "system",
"content": "{{PERPLEXITY_SYSTEM_PROMPT}}"
},
{
"role": "user",
"content": "What is Perplexity AI's approach to search?"
}
],
"max_tokens": 2048,
"temperature": 0.7,
"top_p": 0.9,
"return_citations": true,
"search_domain_filter": [],
"return_images": false,
"return_related_questions": false,
"search_recency_filter": "month",
"top_k": 0,
"stream": false,
"presence_penalty": 0,
"frequency_penalty": 1
}`,
lmStudioRequestTemplate: `{
"model": "ibm/granite-3.2-8b",
"messages": [
{
"role": "user",
"content": "Hello, can you help me with this question?"
}
],
"max_tokens": 2048,
"temperature": 0.7,
"stream": false
}`,
defaultModel: 'llama3.2:latest',
defaultOptimizationMode: 'speed',
defaultFocusMode: 'webSearch',
defaultLMStudioModel: 'ibm/granite-3.2-8b',
// Prompt Settings
prompts: {
// System prompts
perplexitySystemPrompt: "You are a helpful AI assistant. Provide clear, concise, and accurate information with proper citations.",
perplexicaSystemPrompt: "You are a helpful AI assistant. Provide clear, concise, and accurate information.",
lmStudioDefaultSystemPrompt: "You are a helpful AI assistant. Provide clear, concise, and accurate information.",
// Placeholder text
perplexityQueryPlaceholder: "What would you like to ask Perplexity?",
perplexicaQueryPlaceholder: "What would you like to ask Perplexica?",
lmStudioQueryPlaceholder: "What would you like to ask?",
lmStudioSystemPromptPlaceholder: "You are a helpful AI assistant...",
articleTermPlaceholder: "e.g., AI Copilots, AI Studios, Machine Learning, etc.",
// Descriptions and labels
deepResearchDescription: "⚡ Deep Research: Exhaustive research across hundreds of sources with expert-level analysis. Higher cost but comprehensive results. Note: Streaming is disabled for this model.",
imagesToggleDescription: "Include image results from search - images will be integrated throughout the response where appropriate",
imagesToggleGenericDescription: "Include image references throughout the response where appropriate",
articleTermDescription: "Enter a vocabulary term to generate a comprehensive one-page article with images.",
// Notices and messages
deepResearchLoadingNotice: "🔍 Deep research in progress... This may take up to 60 seconds.",
enterQuestionNotice: "Please enter a question",
enterTermNotice: "Please enter a vocabulary term",
// Article generator template
articleGeneratorTemplate: `Write a comprehensive one-page article about "{TERM}".
Structure the article as follows:
1. **Introduction** (2-3 sentences)
- Define the term and its significance
- Provide context for why it matters
2. **Main Content** (3-4 paragraphs)
- Explain the concept in detail
- Include practical examples and use cases
- Discuss benefits and potential applications
- Address any challenges or considerations
3. **Current State and Trends** (1-2 paragraphs)
- Discuss current adoption and market status
- Mention key players or technologies
- Highlight recent developments
4. **Future Outlook** (1 paragraph)
- Predict future developments
- Discuss potential impact
5. **Conclusion** (1-2 sentences)
- Summarize key points
- End with a forward-looking statement
**Important Guidelines:**
- Keep the total length to approximately one page (500-800 words)
- Use clear, accessible language
- Include specific examples and real-world applications
- Make it engaging and informative for a general audience
- Use markdown formatting for structure
**Image References:**
Include [IMAGE 1: {TERM} concept diagram or illustration] after the introduction.
Include [IMAGE 2: {TERM} practical example or use case] after the main content section.
Include [IMAGE 3: {TERM} future trends or technology visualization] before the conclusion.
Replace "{TERM}" with the actual vocabulary term in the prompt.`,
// Image prompts
imageReferencesPrompt: "**Image References:**\nPlease include the following image references throughout your response where appropriate:\n- [IMAGE 1: Relevant diagram or illustration related to the topic]\n- [IMAGE 2: Practical example or use case visualization]\n- [IMAGE 3: Additional supporting visual content]"
}
};
editor: Editor,
options?: {
max_tokens?: number;
temperature?: number;
top_p?: number;
system_prompt?: string;
return_images?: boolean;
}
): 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
public getPromptsService(): PromptsService {
return this.promptsService;
}
private registerPerplexicaCommands(): void {
// Command to update Perplexica URL
this.addCommand({
id: 'update-perplexica-url',
name: 'Update Perplexica URL',
callback: () => {
const modal = new URLUpdateModal(this.app, {
title: 'Update Perplexica API URL',
label: 'Perplexica API URL',
placeholder: 'http://localhost:3030/api/search',
currentValue: this.settings.perplexicaEndpoint,
onSave: async (newUrl: string) => {
this.settings.perplexicaEndpoint = newUrl;
await this.saveSettings();
}
});
modal.open();
}
});
// Command to show current settings
this.addCommand({
id: 'show-perplexica-settings',
name: 'Show Perplexica Settings',
callback: () => {
new Notice(`Current Perplexica URL: ${this.settings.perplexicaEndpoint}`);
console.log('Perplexica Settings:', this.settings);
}
});
// Command to ask Perplexica
this.addCommand({
id: 'ask-perplexica',
name: 'Ask Perplexica',
editorCallback: (editor: Editor) => {
const modal = new PerplexicaModal(this.app, editor, this.perplexicaService, this.promptsService);
modal.open();
}
});
}
private registerPerplexityCommands(): void {
// Command to update Perplexity URL
this.addCommand({
id: 'update-perplexity-url',
name: 'Update Perplexity URL',
callback: () => {
const modal = new URLUpdateModal(this.app, {
title: 'Update Perplexity API URL',
label: 'Perplexity API URL',
placeholder: 'https://api.perplexity.ai/chat/completions',
currentValue: this.settings.perplexityEndpoint,
onSave: async (newUrl: string) => {
this.settings.perplexityEndpoint = newUrl;
await this.saveSettings();
}
});
modal.open();
}
});
// Command to show current Perplexity settings
this.addCommand({
id: 'show-perplexity-settings',
name: 'Show Perplexity Settings',
callback: () => {
new Notice(`Current Perplexity URL: ${this.settings.perplexityEndpoint}`);
console.log('Perplexity Settings:', this.settings);
}
});
// Command to ask Perplexity
this.addCommand({
id: 'ask-perplexity',
name: 'Ask Perplexity',
editorCallback: (editor: Editor) => {
const modal = new PerplexityModal(this.app, editor, this.perplexityService, this.promptsService);
modal.open();
}
});
}
private registerLMStudioCommands(): void {
// Command to update LM Studio URL
this.addCommand({
id: 'update-lmstudio-url',
name: 'Update LM Studio URL',
callback: () => {
const modal = new URLUpdateModal(this.app, {
title: 'Update LM Studio API URL',
label: 'LM Studio API URL',
placeholder: 'http://localhost:1234/v1/chat/completions',
currentValue: this.settings.lmStudioEndpoint,
onSave: async (newUrl: string) => {
this.settings.lmStudioEndpoint = newUrl;
await this.saveSettings();
}
});
modal.open();
}
});
// Command to show current LM Studio settings
this.addCommand({
id: 'show-lmstudio-settings',
name: 'Show LM Studio Settings',
callback: () => {
new Notice(`Current LM Studio URL: ${this.settings.lmStudioEndpoint}`);
console.log('LM Studio Settings:', this.settings);
}
});
// Command to ask LM Studio
this.addCommand({
id: 'ask-lmstudio',
name: 'Ask LM Studio',
editorCallback: (editor: Editor) => {
const modal = new LMStudioModal(this.app, editor, this, this.lmStudioService, this.promptsService);
modal.open();
}
});
}
private registerArticleGeneratorCommands(): void {
// Register Article Generator command
this.addCommand({
id: 'generate-article',
name: 'Generate One-Page Article',
editorCallback: (editor: Editor) => {
new ArticleGeneratorModal(this.app, editor, this.perplexityService, this.promptsService).open();
}
});
}
}
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' });
// Perplexity Section
const perplexityHeader = containerEl.createEl('h3', { text: 'Perplexity (Remote Service)' });
perplexityHeader.style.color = 'var(--text-accent)';
containerEl.createEl('p', {
text: 'Configure settings for the hosted Perplexity AI service',
cls: 'setting-item-description'
});
new Setting(containerEl)
.setName('Endpoint')
.setDesc('API endpoint for Perplexity service')
.addText(text => text
.setPlaceholder('https://api.perplexity.ai/chat/completions')
.setValue(this.plugin.settings.perplexityEndpoint)
.onChange(async (value: string) => {
this.plugin.settings.perplexityEndpoint = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('API Key')
.setDesc('Your Perplexity API key (required for remote service)')
.addText(text => text
.setPlaceholder('pplx-xxxxxxxxxxxxxxxxxxxxx')
.setValue(this.plugin.settings.perplexityApiKey)
.onChange(async (value: string) => {
this.plugin.settings.perplexityApiKey = value;
await this.plugin.saveSettings();
})
);
// Perplexity Request Template
const perplexityJsonSetting = new Setting(containerEl)
.setName('Request Body Template')
.setDesc('JSON template for Perplexity API requests');
// Create a textarea element for Perplexity
const perplexityTextArea = document.createElement('textarea');
perplexityTextArea.rows = 10;
perplexityTextArea.cols = 50;
perplexityTextArea.style.width = '100%';
perplexityTextArea.style.minHeight = '300px';
perplexityTextArea.style.fontFamily = 'monospace';
perplexityTextArea.placeholder = 'Enter Perplexity JSON request template...';
// Set initial value if it exists
if (this.plugin.settings.perplexityRequestTemplate) {
try {
const config = JSON.parse(this.plugin.settings.perplexityRequestTemplate);
perplexityTextArea.value = JSON.stringify(config, null, 2);
} catch (e) {
// If not valid JSON, use as is
perplexityTextArea.value = this.plugin.settings.perplexityRequestTemplate;
}
}
// Add input event listener for Perplexity
perplexityTextArea.addEventListener('input', async () => {
this.plugin.settings.perplexityRequestTemplate = perplexityTextArea.value;
await this.plugin.saveSettings();
});
// Add the textarea to the setting
perplexityJsonSetting.settingEl.appendChild(perplexityTextArea);
// Perplexica Section
const perplexicaHeader = containerEl.createEl('h3', { text: 'Perplexica (Self-Hosted)' });
perplexicaHeader.style.color = 'var(--text-accent)';
containerEl.createEl('p', {
text: 'Configure settings for your local Perplexica installation',
cls: 'setting-item-description'
});
new Setting(containerEl)
.setName('Endpoint')
.setDesc('API endpoint for your local Perplexica instance')
.addText(text => text
.setPlaceholder('http://localhost:3030/api/search')
.setValue(this.plugin.settings.perplexicaEndpoint)
.onChange(async (value: string) => {
this.plugin.settings.perplexicaEndpoint = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Fallback Container Path')
.setDesc('Alternative endpoint for Docker container setups')
.addText(text => text
.setPlaceholder('http://host.docker.internal:3030/api/search')
.setValue(this.plugin.settings.localLLMPath)
.onChange(async (value: string) => {
this.plugin.settings.localLLMPath = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Default Model')
.setDesc('Default AI model for Perplexica to use')
.addText(text => text
.setPlaceholder('llama3.2:latest')
.setValue(this.plugin.settings.defaultModel)
.onChange(async (value: string) => {
this.plugin.settings.defaultModel = value;
await this.plugin.saveSettings();
})
);
// Perplexica Request Template
const perplexicaJsonSetting = new Setting(containerEl)
.setName('Request Body Template')
.setDesc('JSON template for Perplexica API requests');
// Create a textarea element for Perplexica
const perplexicaTextArea = document.createElement('textarea');
perplexicaTextArea.rows = 10;
perplexicaTextArea.cols = 50;
perplexicaTextArea.style.width = '100%';
perplexicaTextArea.style.minHeight = '300px';
perplexicaTextArea.style.fontFamily = 'monospace';
perplexicaTextArea.placeholder = 'Enter Perplexica JSON request template...';
// Set initial value if it exists
if (this.plugin.settings.requestBodyTemplate) {
try {
const config = JSON.parse(this.plugin.settings.requestBodyTemplate);
perplexicaTextArea.value = JSON.stringify(config, null, 2);
} catch (e) {
// If not valid JSON, use as is
perplexicaTextArea.value = this.plugin.settings.requestBodyTemplate;
}
}
// Add input event listener for Perplexica
perplexicaTextArea.addEventListener('input', async () => {
this.plugin.settings.requestBodyTemplate = perplexicaTextArea.value;
await this.plugin.saveSettings();
});
// Add the textarea to the setting
perplexicaJsonSetting.settingEl.appendChild(perplexicaTextArea);
// LM Studio Section
const lmStudioHeader = containerEl.createEl('h3', { text: 'LM Studio (Local LLM)' });
lmStudioHeader.style.color = 'var(--text-accent)';
containerEl.createEl('p', {
text: 'Configure settings for LM Studio local LLM service',
cls: 'setting-item-description'
});
// Base URL setting
new Setting(containerEl)
.setName('Base URL')
.setDesc('Base URL for LM Studio API (e.g., http://localhost:1234)')
.addText(text => text
.setPlaceholder('http://localhost:1234')
.setValue(this.plugin.settings.lmStudioEndpoint)
.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 to use with LM Studio')
.addText(text => text
.setPlaceholder('ibm/granite-3.2-8b')
.setValue(this.plugin.settings.defaultLMStudioModel)
.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)
.setName('Request Body Template')
.setDesc('JSON template for LM Studio API requests');
// Create a textarea element for LM Studio
const lmStudioTextArea = document.createElement('textarea');
lmStudioTextArea.rows = 10;
lmStudioTextArea.cols = 50;
lmStudioTextArea.style.width = '100%';
lmStudioTextArea.style.minHeight = '300px';
lmStudioTextArea.style.fontFamily = 'monospace';
lmStudioTextArea.placeholder = 'Enter LM Studio JSON request template...';
// Set initial value if it exists
if (this.plugin.settings.lmStudioRequestTemplate) {
try {
const config = JSON.parse(this.plugin.settings.lmStudioRequestTemplate);
lmStudioTextArea.value = JSON.stringify(config, null, 2);
} catch (e) {
// If not valid JSON, use as is
lmStudioTextArea.value = this.plugin.settings.lmStudioRequestTemplate;
}
}
// Add input event listener for LM Studio
lmStudioTextArea.addEventListener('input', async () => {
this.plugin.settings.lmStudioRequestTemplate = lmStudioTextArea.value;
await this.plugin.saveSettings();
});
// Add the textarea to the setting
lmStudioJsonSetting.settingEl.appendChild(lmStudioTextArea);
// Prompts Section
const promptsHeader = containerEl.createEl('h3', { text: 'Prompts & Text Configuration' });
promptsHeader.style.color = 'var(--text-accent)';
containerEl.createEl('p', {
text: 'Customize all prompts, placeholders, descriptions, and messages used throughout the plugin',
cls: 'setting-item-description'
});
// System Prompts
containerEl.createEl('h4', { text: 'System Prompts' });
new Setting(containerEl)
.setName('Perplexity System Prompt')
.setDesc('System prompt used for Perplexity AI requests')
.addTextArea(text => text
.setPlaceholder('Enter system prompt for Perplexity...')
.setValue(this.plugin.settings.prompts.perplexitySystemPrompt)
.onChange(async (value: string) => {
this.plugin.settings.prompts.perplexitySystemPrompt = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Perplexica System Prompt')
.setDesc('System prompt used for Perplexica requests')
.addTextArea(text => text
.setPlaceholder('Enter system prompt for Perplexica...')
.setValue(this.plugin.settings.prompts.perplexicaSystemPrompt)
.onChange(async (value: string) => {
this.plugin.settings.prompts.perplexicaSystemPrompt = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('LM Studio Default System Prompt')
.setDesc('Default system prompt used for LM Studio requests')
.addTextArea(text => text
.setPlaceholder('Enter default system prompt for LM Studio...')
.setValue(this.plugin.settings.prompts.lmStudioDefaultSystemPrompt)
.onChange(async (value: string) => {
this.plugin.settings.prompts.lmStudioDefaultSystemPrompt = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
// Placeholder Text
containerEl.createEl('h4', { text: 'Placeholder Text' });
new Setting(containerEl)
.setName('Perplexity Query Placeholder')
.setDesc('Placeholder text for Perplexity query input')
.addText(text => text
.setPlaceholder('Enter placeholder text...')
.setValue(this.plugin.settings.prompts.perplexityQueryPlaceholder)
.onChange(async (value: string) => {
this.plugin.settings.prompts.perplexityQueryPlaceholder = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Perplexica Query Placeholder')
.setDesc('Placeholder text for Perplexica query input')
.addText(text => text
.setPlaceholder('Enter placeholder text...')
.setValue(this.plugin.settings.prompts.perplexicaQueryPlaceholder)
.onChange(async (value: string) => {
this.plugin.settings.prompts.perplexicaQueryPlaceholder = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('LM Studio Query Placeholder')
.setDesc('Placeholder text for LM Studio query input')
.addText(text => text
.setPlaceholder('Enter placeholder text...')
.setValue(this.plugin.settings.prompts.lmStudioQueryPlaceholder)
.onChange(async (value: string) => {
this.plugin.settings.prompts.lmStudioQueryPlaceholder = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('LM Studio System Prompt Placeholder')
.setDesc('Placeholder text for LM Studio system prompt input')
.addText(text => text
.setPlaceholder('Enter placeholder text...')
.setValue(this.plugin.settings.prompts.lmStudioSystemPromptPlaceholder)
.onChange(async (value: string) => {
this.plugin.settings.prompts.lmStudioSystemPromptPlaceholder = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Article Term Placeholder')
.setDesc('Placeholder text for article generator term input')
.addText(text => text
.setPlaceholder('Enter placeholder text...')
.setValue(this.plugin.settings.prompts.articleTermPlaceholder)
.onChange(async (value: string) => {
this.plugin.settings.prompts.articleTermPlaceholder = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
// Descriptions and Labels
containerEl.createEl('h4', { text: 'Descriptions & Labels' });
new Setting(containerEl)
.setName('Deep Research Description')
.setDesc('Description shown for deep research model')
.addTextArea(text => text
.setPlaceholder('Enter description...')
.setValue(this.plugin.settings.prompts.deepResearchDescription)
.onChange(async (value: string) => {
this.plugin.settings.prompts.deepResearchDescription = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Images Toggle Description')
.setDesc('Description for images toggle in Perplexity modal')
.addTextArea(text => text
.setPlaceholder('Enter description...')
.setValue(this.plugin.settings.prompts.imagesToggleDescription)
.onChange(async (value: string) => {
this.plugin.settings.prompts.imagesToggleDescription = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Images Toggle Generic Description')
.setDesc('Generic description for images toggle in other modals')
.addTextArea(text => text
.setPlaceholder('Enter description...')
.setValue(this.plugin.settings.prompts.imagesToggleGenericDescription)
.onChange(async (value: string) => {
this.plugin.settings.prompts.imagesToggleGenericDescription = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Article Term Description')
.setDesc('Description for article generator term input')
.addTextArea(text => text
.setPlaceholder('Enter description...')
.setValue(this.plugin.settings.prompts.articleTermDescription)
.onChange(async (value: string) => {
this.plugin.settings.prompts.articleTermDescription = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
// Notices and Messages
containerEl.createEl('h4', { text: 'Notices & Messages' });
new Setting(containerEl)
.setName('Deep Research Loading Notice')
.setDesc('Notice shown when deep research is in progress')
.addText(text => text
.setPlaceholder('Enter notice text...')
.setValue(this.plugin.settings.prompts.deepResearchLoadingNotice)
.onChange(async (value: string) => {
this.plugin.settings.prompts.deepResearchLoadingNotice = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Enter Question Notice')
.setDesc('Notice shown when no question is entered')
.addText(text => text
.setPlaceholder('Enter notice text...')
.setValue(this.plugin.settings.prompts.enterQuestionNotice)
.onChange(async (value: string) => {
this.plugin.settings.prompts.enterQuestionNotice = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Enter Term Notice')
.setDesc('Notice shown when no term is entered for article generator')
.addText(text => text
.setPlaceholder('Enter notice text...')
.setValue(this.plugin.settings.prompts.enterTermNotice)
.onChange(async (value: string) => {
this.plugin.settings.prompts.enterTermNotice = value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
})
);
// Article Generator Template
containerEl.createEl('h4', { text: 'Article Generator Template' });
const articleTemplateSetting = new Setting(containerEl)
.setName('Article Generator Template')
.setDesc('Template for generating articles. Use {TERM} as placeholder for the term.');
const articleTemplateTextArea = document.createElement('textarea');
articleTemplateTextArea.rows = 15;
articleTemplateTextArea.cols = 50;
articleTemplateTextArea.style.width = '100%';
articleTemplateTextArea.style.minHeight = '400px';
articleTemplateTextArea.style.fontFamily = 'monospace';
articleTemplateTextArea.placeholder = 'Enter article generator template...';
articleTemplateTextArea.value = this.plugin.settings.prompts.articleGeneratorTemplate;
articleTemplateTextArea.addEventListener('input', async () => {
this.plugin.settings.prompts.articleGeneratorTemplate = articleTemplateTextArea.value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
});
articleTemplateSetting.settingEl.appendChild(articleTemplateTextArea);
// Image Prompts
containerEl.createEl('h4', { text: 'Image Prompts' });
const imagePromptsSetting = new Setting(containerEl)
.setName('Image References Prompt')
.setDesc('Prompt added to queries when images are enabled');
const imagePromptsTextArea = document.createElement('textarea');
imagePromptsTextArea.rows = 8;
imagePromptsTextArea.cols = 50;
imagePromptsTextArea.style.width = '100%';
imagePromptsTextArea.style.minHeight = '200px';
imagePromptsTextArea.style.fontFamily = 'monospace';
imagePromptsTextArea.placeholder = 'Enter image references prompt...';
imagePromptsTextArea.value = this.plugin.settings.prompts.imageReferencesPrompt;
imagePromptsTextArea.addEventListener('input', async () => {
this.plugin.settings.prompts.imageReferencesPrompt = imagePromptsTextArea.value;
this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts);
await this.plugin.saveSettings();
});
imagePromptsSetting.settingEl.appendChild(imagePromptsTextArea);
}
}

View file

@ -0,0 +1,27 @@
import { App, Plugin, PluginSettingTab } from 'obsidian';
import { PerplexedSettings, DEFAULT_SETTINGS } from '../settings/PerplexedSettings';
import { PerplexedSettingTab } from '../settings/PerplexedSettingTab';
export default class PerplexedPlugin extends Plugin {
settings: PerplexedSettings;
async onload() {
await this.loadSettings();
// Add settings tab
this.addSettingTab(new PerplexedSettingTab(this.app, this));
}
async loadSettings(): Promise<void> {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings(): Promise<void> {
await this.saveData(this.settings);
}
updateStatusBar(): void {
// Implementation for status bar updates
console.log('Status bar updated');
}
}

View file

@ -1,13 +1,13 @@
import { App, Plugin } from 'obsidian';
import { Plugin } from 'obsidian';
import { PerplexedSettings, DEFAULT_SETTINGS } from '../settings/PerplexedSettings';
import { PerplexedSettingTab } from '../settings/PerplexedSettings';
import { PerplexedSettingTab } from '../settings/PerplexedSettingTab';
import { LMStudioSettings, DEFAULT_LMSTUDIO_SETTINGS } from '../settings/LMStudioSettings';
export default class PerplexedPluginCore extends Plugin {
settings: PerplexedSettings;
lmStudioSettings: LMStudioSettings;
export class PerplexedPluginCore extends Plugin {
settings: PerplexedSettings = { ...DEFAULT_SETTINGS };
lmStudioSettings: LMStudioSettings = { ...DEFAULT_LMSTUDIO_SETTINGS };
async onload() {
async onload(): Promise<void> {
await this.loadSettings();
// Add settings tab
@ -17,30 +17,46 @@ export default class PerplexedPluginCore extends Plugin {
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 loadSettings(): Promise<void> {
try {
// Load main settings
const loadedSettings = await this.loadData();
if (loadedSettings) {
this.settings = { ...DEFAULT_SETTINGS, ...loadedSettings };
}
// Load LM Studio settings
const loadedLMStudioSettings = await this.loadData('lmstudio-settings');
if (loadedLMStudioSettings) {
this.lmStudioSettings = {
...DEFAULT_LMSTUDIO_SETTINGS,
...loadedLMStudioSettings
};
}
} catch (error) {
console.error('Error loading settings:', error);
}
}
async saveSettings() {
// Save main settings
await this.saveData(this.settings);
// Save LM Studio settings
await this.saveData(this.lmStudioSettings);
async saveSettings(): Promise<void> {
try {
// Save main settings
await this.saveData(this.settings);
// Save LM Studio settings with a separate key
await this.saveData(this.lmStudioSettings, 'lmstudio-settings');
} catch (error) {
console.error('Error saving settings:', error);
}
}
private registerCommands() {
protected registerCommands(): void {
// Base implementation - can be overridden by child classes
}
protected setupUI(): void {
// Base implementation - can be overridden by child classes
} private registerCommands() {
// Register your commands here
// Example:
this.addCommand({

View file

@ -1,4 +1,5 @@
import { Editor, Notice } from 'obsidian';
import { PerplexitySettings } from '../settings/PerplexitySettings';
export interface PerplexityOptions {
return_citations?: boolean;
@ -7,12 +8,7 @@ export interface PerplexityOptions {
search_recency_filter?: string;
}
export interface PerplexitySettings {
perplexityApiKey: string;
perplexityEndpoint: string;
promptsService?: any; // Will be PromptsService type
requestTemplate?: string;
}
// Use the imported PerplexitySettings interface from settings file
export class PerplexityService {
private settings: PerplexitySettings;

View file

@ -0,0 +1,107 @@
import { App, PluginSettingTab, Setting } from 'obsidian';
import { PerplexedPluginCore } from '../core/PerplexedPluginCore';
export class PerplexedSettingTab extends PluginSettingTab {
plugin: PerplexedPluginCore;
constructor(app: App, plugin: PerplexedPluginCore) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl('h2', { text: 'Perplexed Plugin Settings' });
// Add general settings section
this.addGeneralSettings(containerEl);
// Add Perplexity settings section
this.addPerplexitySettings(containerEl);
// Add LM Studio settings section
this.addLMStudioSettings(containerEl);
}
private addGeneralSettings(containerEl: HTMLElement): void {
containerEl.createEl('h3', { text: 'General Settings' });
new Setting(containerEl)
.setName('Status Bar Position')
.setDesc('Position of the status bar item')
.addDropdown(dropdown => {
dropdown
.addOption('left', 'Left')
.addOption('right', 'Right')
.setValue(this.plugin.settings.statusBarPosition || 'left')
.onChange(async (value: string) => {
if (value === 'left' || value === 'right') {
this.plugin.settings.statusBarPosition = value;
await this.plugin.saveSettings();
}
});
return dropdown;
});
}
private addPerplexitySettings(containerEl: HTMLElement): void {
containerEl.createEl('h3', { text: 'Perplexity Settings' });
new Setting(containerEl)
.setName('Enable Perplexity')
.setDesc('Enable or disable the Perplexity provider')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enablePerplexity || false)
.onChange(async (value) => {
this.plugin.settings.enablePerplexity = value;
await this.plugin.saveSettings();
this.display(); // Refresh the settings view
}));
if (this.plugin.settings.enablePerplexity) {
new Setting(containerEl)
.setName('API Key')
.setDesc('Your Perplexity API key')
.addText(text => text
.setPlaceholder('Enter your API key')
.setValue(this.plugin.settings.perplexitySettings?.apiKey || '')
.onChange(async (value) => {
if (!this.plugin.settings.perplexitySettings) {
this.plugin.settings.perplexitySettings = {} as any;
}
this.plugin.settings.perplexitySettings.apiKey = value;
await this.plugin.saveSettings();
}));
}
}
private addLMStudioSettings(containerEl: HTMLElement): void {
containerEl.createEl('h3', { text: 'LM Studio Settings' });
new Setting(containerEl)
.setName('Enable LM Studio')
.setDesc('Enable or disable the LM Studio provider')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enableLMStudio || false)
.onChange(async (value) => {
this.plugin.settings.enableLMStudio = value;
await this.plugin.saveSettings();
this.display(); // Refresh the settings view
}));
if (this.plugin.settings.enableLMStudio) {
new Setting(containerEl)
.setName('LM Studio Endpoint')
.setDesc('The URL of your LM Studio server')
.addText(text => text
.setPlaceholder('http://localhost:1234')
.setValue(this.plugin.settings.lmStudioSettings?.endpoint || '')
.onChange(async (value) => {
this.plugin.settings.lmStudioSettings = this.plugin.settings.lmStudioSettings || {} as any;
this.plugin.settings.lmStudioSettings.endpoint = value;
await this.plugin.saveSettings();
}));
}
}
}

View file

@ -1,60 +1,76 @@
import { App, PluginSettingTab, Setting } from 'obsidian';
import PerplexedPlugin from '../../main';
import { PerplexitySettings, DEFAULT_PERPLEXITY_SETTINGS } from './PerplexitySettings';
export interface PerplexedSettings {
// General Settings
mySetting: string;
localLLMPath: string;
// General settings
enablePerplexity: boolean;
enablePerplexica: boolean;
enableLMStudio: boolean;
defaultProvider: 'perplexity' | 'perplexica' | 'lmstudio';
// Perplexity Settings
perplexityApiKey: string;
perplexityEndpoint: string;
// UI Settings
showStatusBar: boolean;
statusBarPosition: 'left' | 'right';
statusBarPriority: number;
// LM Studio Settings
lmStudioEndpoint: string;
defaultLMStudioModel: string;
// Prompt settings
defaultSystemPrompt: string;
customPrompts: Record<string, string>;
// Default Models and Modes
defaultModel: string;
defaultOptimizationMode: string;
defaultFocusMode: string;
// Service-specific settings
perplexitySettings: PerplexitySettings;
// Prompts
prompts: {
// System Prompts
perplexitySystemPrompt: string;
lmStudioDefaultSystemPrompt: string;
// Placeholders
perplexityQueryPlaceholder: string;
lmStudioQueryPlaceholder: string;
// LM Studio specific settings
lmStudioSettings: {
endpoint: string;
model: string;
temperature: number;
maxTokens: number;
};
// Perplexica specific settings
perplexicaSettings: {
endpoint: string;
apiKey: string;
model: string;
};
}
export const DEFAULT_SETTINGS: PerplexedSettings = {
mySetting: 'default',
localLLMPath: 'http://host.docker.internal:3030/api/search',
// General settings
enablePerplexity: true,
enablePerplexica: false,
enableLMStudio: false,
defaultProvider: 'perplexity',
// Perplexity Defaults
perplexityApiKey: '',
perplexityEndpoint: 'https://api.perplexity.ai',
// UI Settings
showStatusBar: true,
statusBarPosition: 'left',
statusBarPriority: 100,
// LM Studio Defaults
lmStudioEndpoint: 'http://localhost:1234',
defaultLMStudioModel: 'ibm/granite-3.2-8b',
// Prompt settings
defaultSystemPrompt: 'You are a helpful AI assistant.',
customPrompts: {},
// Default Models and Modes
defaultModel: 'sonar-medium-online',
defaultOptimizationMode: 'balanced',
defaultFocusMode: 'search',
// Perplexity settings
perplexitySettings: {
...DEFAULT_PERPLEXITY_SETTINGS
},
// 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...',
// LM Studio settings
lmStudioSettings: {
endpoint: 'http://localhost:1234',
model: 'lm-studio',
temperature: 0.7,
maxTokens: 2000
},
// Perplexica settings
perplexicaSettings: {
endpoint: 'https://api.perplexica.com',
apiKey: '',
model: 'perplexica'
}
};
@ -80,8 +96,41 @@ export class PerplexedSettingTab extends PluginSettingTab {
containerEl.createEl('h3', { text: 'General Settings' });
new Setting(containerEl)
.setName('Local LLM Path')
.setDesc('Path to your local LLM API endpoint')
.setName('Default Provider')
.setDesc('Select the default AI provider to use')
.addDropdown(dropdown => dropdown
.addOption('perplexity', 'Perplexity')
.addOption('perplexica', 'Perplexica')
.addOption('lmstudio', 'LM Studio')
.setValue(this.plugin.settings.defaultProvider)
.onChange(async (value: 'perplexity' | 'perplexica' | 'lmstudio') => {
this.plugin.settings.defaultProvider = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Show Status Bar')
.setDesc('Toggle the status bar visibility')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showStatusBar)
.onChange(async (value) => {
this.plugin.settings.showStatusBar = value;
await this.plugin.saveSettings();
this.plugin.updateStatusBar();
}));
new Setting(containerEl)
.setName('Status Bar Position')
.setDesc('Position of the status bar item')
.addDropdown(dropdown => dropdown
.addOption('left', 'Left')
.addOption('right', 'Right')
.setValue(this.plugin.settings.statusBarPosition)
.onChange(async (value: 'left' | 'right') => {
this.plugin.settings.statusBarPosition = value;
await this.plugin.saveSettings();
this.plugin.updateStatusBar();
}));
.addText(text => text
.setPlaceholder('http://localhost:3030/api/search')
.setValue(this.plugin.settings.localLLMPath)
@ -95,15 +144,116 @@ export class PerplexedSettingTab extends PluginSettingTab {
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)
.setName('Enable Perplexity')
.setDesc('Enable or disable the Perplexity provider')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enablePerplexity)
.onChange(async (value) => {
this.plugin.settings.perplexityApiKey = value;
this.plugin.settings.enablePerplexity = value;
await this.plugin.saveSettings();
this.display(); // Refresh the settings view
}));
if (this.plugin.settings.enablePerplexity) {
new Setting(containerEl)
.setName('API Key')
.setDesc('Your Perplexity API key')
.addText(text => text
.setPlaceholder('Enter your API key')
.setValue(this.plugin.settings.perplexitySettings.apiKey)
.onChange(async (value) => {
this.plugin.settings.perplexitySettings.apiKey = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Default Model')
.setDesc('Default model to use for Perplexity')
.addText(text => text
.setPlaceholder('pplx-7b-chat')
.setValue(this.plugin.settings.perplexitySettings.defaultModel)
.onChange(async (value) => {
this.plugin.settings.perplexitySettings.defaultModel = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Endpoint')
.setDesc('API endpoint for Perplexity')
.addText(text => text
.setPlaceholder('https://api.perplexity.ai')
.setValue(this.plugin.settings.perplexitySettings.endpoint)
.onChange(async (value) => {
this.plugin.settings.perplexitySettings.endpoint = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Temperature')
.setDesc('Controls randomness (0.0 to 2.0)')
.addSlider(slider => slider
.setLimits(0, 2, 0.1)
.setValue(this.plugin.settings.perplexitySettings.temperature)
.onChange(async (value) => {
this.plugin.settings.perplexitySettings.temperature = value;
await this.plugin.saveSettings();
})
.setDynamicTooltip()
);
new Setting(containerEl)
.setName('Max Tokens')
.setDesc('Maximum number of tokens to generate')
.addText(text => text
.setPlaceholder('2048')
.setValue(this.plugin.settings.perplexitySettings.maxTokens?.toString() || '2048')
.onChange(async (value) => {
const numValue = parseInt(value);
if (!isNaN(numValue)) {
this.plugin.settings.perplexitySettings.maxTokens = numValue;
await this.plugin.saveSettings();
}
}));
new Setting(containerEl)
.setName('Top P')
.setDesc('Controls diversity via nucleus sampling (0.0 to 1.0)')
.addSlider(slider => slider
.setLimits(0, 1, 0.05)
.setValue(this.plugin.settings.perplexitySettings.topP)
.onChange(async (value) => {
this.plugin.settings.perplexitySettings.topP = value;
await this.plugin.saveSettings();
})
.setDynamicTooltip()
);
new Setting(containerEl)
.setName('Frequency Penalty')
.setDesc('Penalize new tokens based on their frequency (-2.0 to 2.0)')
.addSlider(slider => slider
.setLimits(-2, 2, 0.1)
.setValue(this.plugin.settings.perplexitySettings.frequencyPenalty)
.onChange(async (value) => {
this.plugin.settings.perplexitySettings.frequencyPenalty = value;
await this.plugin.saveSettings();
})
.setDynamicTooltip()
);
new Setting(containerEl)
.setName('Presence Penalty')
.setDesc('Penalize new tokens based on their presence (-2.0 to 2.0)')
.addSlider(slider => slider
.setLimits(-2, 2, 0.1)
.setValue(this.plugin.settings.perplexitySettings.presencePenalty)
.onChange(async (value) => {
this.plugin.settings.perplexitySettings.presencePenalty = value;
await this.plugin.saveSettings();
})
.setDynamicTooltip()
);
}
}
private addLMStudioSettings(containerEl: HTMLElement): void {
@ -113,9 +263,9 @@ export class PerplexedSettingTab extends PluginSettingTab {
.setName('Endpoint')
.setDesc('LM Studio API endpoint (e.g., http://localhost:1234)')
.addText(text => text
.setValue(this.plugin.settings.lmStudioEndpoint)
.setValue(this.plugin.settings.lmStudioSettings.endpoint)
.onChange(async (value) => {
this.plugin.settings.lmStudioEndpoint = value;
this.plugin.settings.lmStudioSettings.endpoint = value;
await this.plugin.saveSettings();
}));
@ -123,9 +273,9 @@ export class PerplexedSettingTab extends PluginSettingTab {
.setName('Default Model')
.setDesc('Default model to use with LM Studio')
.addText(text => text
.setValue(this.plugin.settings.defaultLMStudioModel)
.setValue(this.plugin.settings.lmStudioSettings.model)
.onChange(async (value) => {
this.plugin.settings.defaultLMStudioModel = value;
this.plugin.settings.lmStudioSettings.model = value;
await this.plugin.saveSettings();
}));
}

View file

@ -0,0 +1,70 @@
export interface PerplexitySettings {
// API Configuration
apiKey: string;
endpoint: string;
defaultModel: string;
// Generation Parameters
temperature: number;
maxTokens: number;
topP: number;
frequencyPenalty: number;
presencePenalty: number;
// UI Settings
showStatusBar: boolean;
autoSuggest: boolean;
autoSuggestDelay: number;
showContextMenu: boolean;
// Debugging & Performance
debugMode: boolean;
requestTimeout: number;
maxRetries: number;
retryDelay: number;
// Prompts
defaultSystemPrompt: string;
defaultUserPrompt: string;
// Service-specific properties
perplexityApiKey: string;
perplexityEndpoint: string;
promptsService?: any; // Will be PromptsService type
requestTemplate?: string;
}
export const DEFAULT_PERPLEXITY_SETTINGS: PerplexitySettings = {
// API Configuration
apiKey: '',
endpoint: 'https://api.perplexity.ai',
defaultModel: 'pplx-7b-chat',
perplexityApiKey: '',
perplexityEndpoint: 'https://api.perplexity.ai',
// Generation Parameters
temperature: 0.7,
maxTokens: 2000,
topP: 0.9,
frequencyPenalty: 0.5,
presencePenalty: 0.5,
// UI Settings
showStatusBar: true,
autoSuggest: false,
autoSuggestDelay: 1000,
showContextMenu: true,
// Debugging & Performance
debugMode: false,
requestTimeout: 30000,
maxRetries: 3,
retryDelay: 1000,
// Prompts
defaultSystemPrompt: 'You are a helpful AI assistant.',
defaultUserPrompt: 'How can I help you today?',
// Service-specific properties
requestTemplate: ''
};