From 70a2f3113611ab8ced85dab1039965bcfca5abc1 Mon Sep 17 00:00:00 2001 From: Tanuj Siripurapu Date: Mon, 11 Aug 2025 23:47:14 -0400 Subject: [PATCH] fixed image placement and added setting to position query at top or end of response --- main.ts | 535 ++++++++++++++++++++++++------ src/modals/LMStudioModal.ts | 2 +- src/modals/PerplexicaModal.ts | 2 +- src/modals/PerplexityModal.ts | 2 +- src/services/lmStudioService.ts | 2 +- src/services/perplexicaService.ts | 2 +- src/services/perplexityService.ts | 116 +++++-- styles.css | 73 +--- 8 files changed, 529 insertions(+), 205 deletions(-) diff --git a/main.ts b/main.ts index f1f43c0..40886cd 100644 --- a/main.ts +++ b/main.ts @@ -32,6 +32,9 @@ interface PerplexedPluginSettings { defaultFocusMode: string; defaultLMStudioModel: string; + // Display Settings + headerPosition: 'top' | 'bottom'; + // Prompt Settings prompts: { // System prompts @@ -72,6 +75,7 @@ const DEFAULT_SETTINGS: PerplexedPluginSettings = { perplexicaEndpoint: 'http://localhost:3030/api/search', perplexityEndpoint: 'https://api.perplexity.ai/chat/completions', lmStudioEndpoint: 'http://localhost:1234/v1/chat/completions', + headerPosition: 'top', requestBodyTemplate: `{ "chatModel": { "provider": "ollama", @@ -216,51 +220,142 @@ export default class PerplexedPlugin extends Plugin { private ribbonIconEl: HTMLElement | null = null; // Service instances - private perplexityService!: PerplexityService; - private perplexicaService!: PerplexicaService; - private lmStudioService!: LMStudioService; - private promptsService!: PromptsService; + private perplexityService!: PerplexityService | null; + private perplexicaService!: PerplexicaService | null; + private lmStudioService!: LMStudioService | null; + private promptsService!: PromptsService | null; async onload(): Promise { - 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)); + try { + console.log('Perplexed Plugin: Starting initialization...'); + + await this.loadSettings(); + console.log('Perplexed Plugin: Settings loaded successfully'); + + // Initialize prompts service first + try { + this.promptsService = new PromptsService(this.settings.prompts); + console.log('Perplexed Plugin: PromptsService initialized successfully'); + } catch (error) { + console.error('Perplexed Plugin: Failed to initialize PromptsService:', error); + new Notice('Failed to initialize PromptsService'); + this.promptsService = null; + } + + // Initialize services with error handling - only if promptsService is available + if (this.promptsService) { + try { + this.perplexityService = new PerplexityService({ + perplexityApiKey: this.settings.perplexityApiKey, + perplexityEndpoint: this.settings.perplexityEndpoint, + promptsService: this.promptsService, + requestTemplate: this.settings.perplexityRequestTemplate, + headerPosition: this.settings.headerPosition + }); + console.log('Perplexed Plugin: PerplexityService initialized successfully'); + } catch (error) { + console.error('Perplexed Plugin: Failed to initialize PerplexityService:', error); + new Notice('Failed to initialize PerplexityService'); + this.perplexityService = null; + } + + try { + this.perplexicaService = new PerplexicaService({ + perplexicaEndpoint: this.settings.perplexicaEndpoint, + localLLMPath: this.settings.localLLMPath, + defaultModel: this.settings.defaultModel, + promptsService: this.promptsService, + requestTemplate: this.settings.requestBodyTemplate + }); + console.log('Perplexed Plugin: PerplexicaService initialized successfully'); + } catch (error) { + console.error('Perplexed Plugin: Failed to initialize PerplexicaService:', error); + new Notice('Failed to initialize PerplexicaService'); + this.perplexicaService = null; + } + + try { + this.lmStudioService = new LMStudioService({ + lmStudioEndpoint: this.settings.lmStudioEndpoint, + promptsService: this.promptsService, + requestTemplate: this.settings.lmStudioRequestTemplate + }); + console.log('Perplexed Plugin: LMStudioService initialized successfully'); + } catch (error) { + console.error('Perplexed Plugin: Failed to initialize LMStudioService:', error); + new Notice('Failed to initialize LMStudioService'); + this.lmStudioService = null; + } + } else { + // If promptsService failed, set all other services to null + this.perplexityService = null; + this.perplexicaService = null; + this.lmStudioService = null; + console.log('Perplexed Plugin: Skipping service initialization due to PromptsService failure'); + } + + // Debug: Log current settings + console.log('Perplexed Plugin: Current Perplexica Path:', this.settings.perplexicaEndpoint); + console.log('Perplexed Plugin: 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(); + // This adds a settings tab so the user can configure various aspects of the plugin + this.addSettingTab(new PerplexedSettingTab(this.app, this)); + console.log('Perplexed Plugin: Settings tab added successfully'); + + // Register commands with error handling + try { + this.registerPerplexicaCommands(); + console.log('Perplexed Plugin: Perplexica commands registered successfully'); + } catch (error) { + console.error('Perplexed Plugin: Failed to register Perplexica commands:', error); + } + + try { + this.registerPerplexityCommands(); + console.log('Perplexed Plugin: Perplexity commands registered successfully'); + } catch (error) { + console.error('Perplexed Plugin: Failed to register Perplexity commands:', error); + } + + try { + this.registerLMStudioCommands(); + console.log('Perplexed Plugin: LM Studio commands registered successfully'); + } catch (error) { + console.error('Perplexed Plugin: Failed to register LM Studio commands:', error); + } + + try { + this.registerArticleGeneratorCommands(); + console.log('Perplexed Plugin: Article generator commands registered successfully'); + } catch (error) { + console.error('Perplexed Plugin: Failed to register article generator commands:', error); + } + + // Add debug command to check command status + this.addCommand({ + id: 'perplexed-debug-commands', + name: 'Debug: Check Perplexed Commands', + callback: () => { + this.debugCommands(); + } + }); + + // Add command to reinitialize services + this.addCommand({ + id: 'perplexed-reinitialize-services', + name: 'Reinitialize Perplexed Services', + callback: async () => { + await this.reinitializeServices(); + } + }); + + console.log('Perplexed Plugin: Initialization completed successfully'); + new Notice('Perplexed Plugin loaded successfully'); + + } catch (error) { + console.error('Perplexed Plugin: Critical initialization error:', error); + new Notice('Perplexed Plugin failed to load properly'); + } } onunload(): void { @@ -290,12 +385,18 @@ export default class PerplexedPlugin extends Plugin { return_related_questions?: boolean; search_recency_filter?: string; }): Promise { + if (!this.perplexityService) { + throw new Error('Perplexity service not initialized'); + } 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 { + if (!this.perplexicaService) { + throw new Error('Perplexica service not initialized'); + } await this.perplexicaService.queryPerplexica(query, focusMode, optimizationMode, stream, editor, options); } @@ -306,11 +407,14 @@ export default class PerplexedPlugin extends Plugin { system_prompt?: string; return_images?: boolean; }): Promise { + if (!this.lmStudioService) { + throw new Error('LM Studio service not initialized'); + } await this.lmStudioService.queryLMStudio(query, model, stream, editor, options); } // Getter for prompts service - public getPromptsService(): PromptsService { + public getPromptsService(): PromptsService | null { return this.promptsService; } @@ -349,51 +453,103 @@ export default class PerplexedPlugin extends Plugin { id: 'ask-perplexica', name: 'Ask Perplexica', editorCallback: (editor: Editor) => { - const modal = new PerplexicaModal(this.app, editor, this.perplexicaService, this.promptsService); - modal.open(); + try { + if (!this.perplexicaService) { + new Notice('Perplexica service not initialized. Please check console for errors and try the debug command.'); + console.error('Perplexica service is not initialized'); + return; + } + if (!this.promptsService) { + new Notice('Prompts service not initialized. Please check console for errors and try the debug command.'); + console.error('Prompts service is not initialized'); + return; + } + const modal = new PerplexicaModal(this.app, editor, this.perplexicaService, this.promptsService); + modal.open(); + } catch (error) { + console.error('Error opening Perplexica modal:', error); + new Notice('Failed to open Perplexica modal. Check console for details.'); + } } }); } 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(); + try { + // 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) => { + try { + if (!this.perplexityService) { + new Notice('Perplexity service not initialized. Please check console for errors and try the debug command.'); + console.error('Perplexity service is not initialized'); + return; + } + if (!this.promptsService) { + new Notice('Prompts service not initialized. Please check console for errors and try the debug command.'); + console.error('Prompts service is not initialized'); + return; + } + const modal = new PerplexityModal(this.app, editor, this.perplexityService, this.promptsService); + modal.open(); + } catch (error) { + console.error('Error opening Perplexity modal:', error); + new Notice('Failed to open Perplexity modal. Check console for details.'); } - }); - 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(); - } - }); + } + }); + + // Add a fallback command that shows service status + this.addCommand({ + id: 'perplexity-service-status', + name: 'Check Perplexity Service Status', + callback: () => { + if (this.perplexityService) { + new Notice('Perplexity service is initialized and ready'); + console.log('Perplexity service status: OK'); + } else { + new Notice('Perplexity service is NOT initialized. Check console for errors.'); + console.error('Perplexity service status: FAILED'); + } + } + }); + + console.log('Perplexed Plugin: Perplexity commands registered successfully'); + } catch (error) { + console.error('Perplexed Plugin: Error registering Perplexity commands:', error); + throw error; + } } private registerLMStudioCommands(): void { @@ -431,8 +587,23 @@ 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); - modal.open(); + try { + if (!this.lmStudioService) { + new Notice('LM Studio service not initialized. Please check console for errors and try the debug command.'); + console.error('LM Studio service is not initialized'); + return; + } + if (!this.promptsService) { + new Notice('Prompts service not initialized. Please check console for errors and try the debug command.'); + console.error('Prompts service is not initialized'); + return; + } + const modal = new LMStudioModal(this.app, editor, this.lmStudioService, this.promptsService); + modal.open(); + } catch (error) { + console.error('Error opening LM Studio modal:', error); + new Notice('Failed to open LM Studio modal. Check console for details.'); + } } }); } @@ -443,10 +614,128 @@ export default class PerplexedPlugin extends Plugin { id: 'generate-article', name: 'Generate One-Page Article', editorCallback: (editor: Editor) => { - new ArticleGeneratorModal(this.app, editor, this.perplexityService, this.promptsService).open(); + try { + if (!this.perplexityService) { + new Notice('Perplexity service not initialized. Please check console for errors and try the debug command.'); + console.error('Perplexity service is not initialized'); + return; + } + if (!this.promptsService) { + new Notice('Prompts service not initialized. Please check console for errors and try the debug command.'); + console.error('Prompts service is not initialized'); + return; + } + new ArticleGeneratorModal(this.app, editor, this.perplexityService, this.promptsService).open(); + } catch (error) { + console.error('Error opening Article Generator modal:', error); + new Notice('Failed to open Article Generator modal. Check console for details.'); + } } }); } + + private debugCommands(): void { + console.log('=== Perplexed Plugin Debug Information ==='); + console.log('Plugin instance:', this); + console.log('Settings:', this.settings); + console.log('Services status:'); + console.log('- PromptsService:', this.promptsService ? 'Initialized' : 'NOT INITIALIZED'); + console.log('- PerplexityService:', this.perplexityService ? 'Initialized' : 'NOT INITIALIZED'); + console.log('- PerplexicaService:', this.perplexicaService ? 'Initialized' : 'NOT INITIALIZED'); + console.log('- LMStudioService:', this.lmStudioService ? 'Initialized' : 'NOT INITIALIZED'); + + // Check if commands are registered in Obsidian + const registeredCommands = this.app.commands.commands; + const perplexedCommands = Object.keys(registeredCommands).filter(cmd => + cmd.startsWith('perplexed') || + cmd.includes('perplexity') || + cmd.includes('perplexica') || + cmd.includes('lmstudio') || + cmd.includes('generate-article') + ); + + console.log('Registered Perplexed commands:', perplexedCommands); + + if (perplexedCommands.length === 0) { + new Notice('No Perplexed commands found! Check console for details.'); + } else { + new Notice(`Found ${perplexedCommands.length} Perplexed commands. Check console for details.`); + } + + console.log('=== End Debug Information ==='); + } + + private async reinitializeServices(): Promise { + try { + console.log('Perplexed Plugin: Reinitializing services...'); + new Notice('Reinitializing Perplexed services...'); + + // Reinitialize prompts service first + try { + this.promptsService = new PromptsService(this.settings.prompts); + console.log('Perplexed Plugin: PromptsService reinitialized successfully'); + } catch (error) { + console.error('Perplexed Plugin: Failed to reinitialize PromptsService:', error); + this.promptsService = null; + } + + // Reinitialize other services only if promptsService is available + if (this.promptsService) { + try { + this.perplexityService = new PerplexityService({ + perplexityApiKey: this.settings.perplexityApiKey, + perplexityEndpoint: this.settings.perplexityEndpoint, + promptsService: this.promptsService, + requestTemplate: this.settings.perplexityRequestTemplate, + headerPosition: this.settings.headerPosition + }); + console.log('Perplexed Plugin: PerplexityService reinitialized successfully'); + } catch (error) { + console.error('Perplexed Plugin: Failed to reinitialize PerplexityService:', error); + this.perplexityService = null; + } + + try { + this.perplexicaService = new PerplexicaService({ + perplexicaEndpoint: this.settings.perplexicaEndpoint, + localLLMPath: this.settings.localLLMPath, + defaultModel: this.settings.defaultModel, + promptsService: this.promptsService, + requestTemplate: this.settings.requestBodyTemplate + }); + console.log('Perplexed Plugin: PerplexicaService reinitialized successfully'); + } catch (error) { + console.error('Perplexed Plugin: Failed to reinitialize PerplexicaService:', error); + this.perplexicaService = null; + } + + try { + this.lmStudioService = new LMStudioService({ + lmStudioEndpoint: this.settings.lmStudioEndpoint, + promptsService: this.promptsService, + requestTemplate: this.settings.lmStudioRequestTemplate + }); + console.log('Perplexed Plugin: LMStudioService reinitialized successfully'); + } catch (error) { + console.error('Perplexed Plugin: Failed to reinitialize LMStudioService:', error); + this.lmStudioService = null; + } + } else { + // If promptsService failed, set all other services to null + this.perplexityService = null; + this.perplexicaService = null; + this.lmStudioService = null; + console.log('Perplexed Plugin: Skipping service reinitialization due to PromptsService failure'); + } + + new Notice('Services reinitialization completed. Check console for details.'); + console.log('Perplexed Plugin: Services reinitialization completed'); + + } catch (error) { + console.error('Perplexed Plugin: Error during services reinitialization:', error); + new Notice('Failed to reinitialize services. Check console for details.'); + } + } } class PerplexedSettingTab extends PluginSettingTab { @@ -457,6 +746,14 @@ class PerplexedSettingTab extends PluginSettingTab { this.plugin = plugin; } + // Helper method to safely update prompts service + private updatePromptsService(): void { + const promptsService = this.plugin.getPromptsService(); + if (promptsService) { + promptsService.updateSettings(this.plugin.settings.prompts); + } + } + display(): void { const { containerEl } = this; containerEl.empty(); @@ -495,6 +792,19 @@ class PerplexedSettingTab extends PluginSettingTab { }) ); + new Setting(containerEl) + .setName('Header Position') + .setDesc('Where to place the query header in generated articles') + .addDropdown(dropdown => dropdown + .addOption('top', 'Top of article') + .addOption('bottom', 'Bottom of article') + .setValue(this.plugin.settings.headerPosition) + .onChange(async (value: string) => { + this.plugin.settings.headerPosition = value as 'top' | 'bottom'; + await this.plugin.saveSettings(); + }) + ); + // Perplexity Request Template const perplexityJsonSetting = new Setting(containerEl) .setName('Request Body Template') @@ -692,7 +1002,10 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.perplexitySystemPrompt) .onChange(async (value: string) => { this.plugin.settings.prompts.perplexitySystemPrompt = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + const promptsService = this.plugin.getPromptsService(); + if (promptsService) { + promptsService.updateSettings(this.plugin.settings.prompts); + } await this.plugin.saveSettings(); }) ); @@ -705,7 +1018,10 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.perplexicaSystemPrompt) .onChange(async (value: string) => { this.plugin.settings.prompts.perplexicaSystemPrompt = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + const promptsService = this.plugin.getPromptsService(); + if (promptsService) { + promptsService.updateSettings(this.plugin.settings.prompts); + } await this.plugin.saveSettings(); }) ); @@ -718,7 +1034,10 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.lmStudioDefaultSystemPrompt) .onChange(async (value: string) => { this.plugin.settings.prompts.lmStudioDefaultSystemPrompt = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + const promptsService = this.plugin.getPromptsService(); + if (promptsService) { + promptsService.updateSettings(this.plugin.settings.prompts); + } await this.plugin.saveSettings(); }) ); @@ -734,7 +1053,10 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.perplexityQueryPlaceholder) .onChange(async (value: string) => { this.plugin.settings.prompts.perplexityQueryPlaceholder = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + const promptsService = this.plugin.getPromptsService(); + if (promptsService) { + promptsService.updateSettings(this.plugin.settings.prompts); + } await this.plugin.saveSettings(); }) ); @@ -747,7 +1069,10 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.perplexicaQueryPlaceholder) .onChange(async (value: string) => { this.plugin.settings.prompts.perplexicaQueryPlaceholder = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + const promptsService = this.plugin.getPromptsService(); + if (promptsService) { + promptsService.updateSettings(this.plugin.settings.prompts); + } await this.plugin.saveSettings(); }) ); @@ -760,7 +1085,10 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.lmStudioQueryPlaceholder) .onChange(async (value: string) => { this.plugin.settings.prompts.lmStudioQueryPlaceholder = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + const promptsService = this.plugin.getPromptsService(); + if (promptsService) { + promptsService.updateSettings(this.plugin.settings.prompts); + } await this.plugin.saveSettings(); }) ); @@ -773,7 +1101,10 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.lmStudioSystemPromptPlaceholder) .onChange(async (value: string) => { this.plugin.settings.prompts.lmStudioSystemPromptPlaceholder = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + const promptsService = this.plugin.getPromptsService(); + if (promptsService) { + promptsService.updateSettings(this.plugin.settings.prompts); + } await this.plugin.saveSettings(); }) ); @@ -786,7 +1117,7 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.articleTermPlaceholder) .onChange(async (value: string) => { this.plugin.settings.prompts.articleTermPlaceholder = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + this.updatePromptsService(); await this.plugin.saveSettings(); }) ); @@ -802,7 +1133,7 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.deepResearchDescription) .onChange(async (value: string) => { this.plugin.settings.prompts.deepResearchDescription = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + this.updatePromptsService(); await this.plugin.saveSettings(); }) ); @@ -815,7 +1146,7 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.imagesToggleDescription) .onChange(async (value: string) => { this.plugin.settings.prompts.imagesToggleDescription = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + this.updatePromptsService(); await this.plugin.saveSettings(); }) ); @@ -828,7 +1159,7 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.imagesToggleGenericDescription) .onChange(async (value: string) => { this.plugin.settings.prompts.imagesToggleGenericDescription = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + this.updatePromptsService(); await this.plugin.saveSettings(); }) ); @@ -841,7 +1172,7 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.articleTermDescription) .onChange(async (value: string) => { this.plugin.settings.prompts.articleTermDescription = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + this.updatePromptsService(); await this.plugin.saveSettings(); }) ); @@ -857,7 +1188,7 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.deepResearchLoadingNotice) .onChange(async (value: string) => { this.plugin.settings.prompts.deepResearchLoadingNotice = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + this.updatePromptsService(); await this.plugin.saveSettings(); }) ); @@ -870,7 +1201,7 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.enterQuestionNotice) .onChange(async (value: string) => { this.plugin.settings.prompts.enterQuestionNotice = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + this.updatePromptsService(); await this.plugin.saveSettings(); }) ); @@ -883,7 +1214,7 @@ class PerplexedSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.prompts.enterTermNotice) .onChange(async (value: string) => { this.plugin.settings.prompts.enterTermNotice = value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + this.updatePromptsService(); await this.plugin.saveSettings(); }) ); @@ -906,7 +1237,7 @@ class PerplexedSettingTab extends PluginSettingTab { articleTemplateTextArea.addEventListener('input', async () => { this.plugin.settings.prompts.articleGeneratorTemplate = articleTemplateTextArea.value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + this.updatePromptsService(); await this.plugin.saveSettings(); }); @@ -930,7 +1261,7 @@ class PerplexedSettingTab extends PluginSettingTab { imagePromptsTextArea.addEventListener('input', async () => { this.plugin.settings.prompts.imageReferencesPrompt = imagePromptsTextArea.value; - this.plugin.getPromptsService().updateSettings(this.plugin.settings.prompts); + this.updatePromptsService(); await this.plugin.saveSettings(); }); diff --git a/src/modals/LMStudioModal.ts b/src/modals/LMStudioModal.ts index ba99585..bb48097 100644 --- a/src/modals/LMStudioModal.ts +++ b/src/modals/LMStudioModal.ts @@ -158,4 +158,4 @@ ${this.promptsService.getImageReferencesPrompt()}`; const {contentEl} = this; contentEl.empty(); } -} \ No newline at end of file +} diff --git a/src/modals/PerplexicaModal.ts b/src/modals/PerplexicaModal.ts index 5d4f6e4..034e403 100644 --- a/src/modals/PerplexicaModal.ts +++ b/src/modals/PerplexicaModal.ts @@ -124,4 +124,4 @@ ${this.promptsService.getImageReferencesPrompt()}`; const {contentEl} = this; contentEl.empty(); } -} \ No newline at end of file +} diff --git a/src/modals/PerplexityModal.ts b/src/modals/PerplexityModal.ts index 15f560c..1731fa7 100644 --- a/src/modals/PerplexityModal.ts +++ b/src/modals/PerplexityModal.ts @@ -165,4 +165,4 @@ ${this.promptsService.getImageReferencesPrompt()}`; const {contentEl} = this; contentEl.empty(); } -} \ No newline at end of file +} diff --git a/src/services/lmStudioService.ts b/src/services/lmStudioService.ts index bf14528..d5e0ed0 100644 --- a/src/services/lmStudioService.ts +++ b/src/services/lmStudioService.ts @@ -242,4 +242,4 @@ export class LMStudioService { editor.replaceRange(processedContent, responseCursor); } -} \ No newline at end of file +} diff --git a/src/services/perplexicaService.ts b/src/services/perplexicaService.ts index ce26bd2..a303459 100644 --- a/src/services/perplexicaService.ts +++ b/src/services/perplexicaService.ts @@ -254,4 +254,4 @@ export class PerplexicaService { editor.replaceRange(processedText, responseCursor); } -} \ No newline at end of file +} diff --git a/src/services/perplexityService.ts b/src/services/perplexityService.ts index b109e9c..795329f 100644 --- a/src/services/perplexityService.ts +++ b/src/services/perplexityService.ts @@ -13,6 +13,7 @@ export interface PerplexitySettings { perplexityEndpoint: string; promptsService?: any; // Will be PromptsService type requestTemplate?: string; + headerPosition?: 'top' | 'bottom'; } export class PerplexityService { @@ -50,23 +51,47 @@ export class PerplexityService { private processContentWithImages(content: string, images: any[]): string { if (!images || images.length === 0) return content; - let processedContent = content; - let imageIndex = 0; - const imageRegex = /\[IMAGE\s+(\d+):\s*(.*?)\]/gi; - let match; + console.log('🖼️ Processing images:', JSON.stringify(images, null, 2)); + console.log('📝 Original content length:', content.length); - while ((match = imageRegex.exec(content)) !== null && imageIndex < images.length) { - const [fullMatch, , description] = match; + let processedContent = content; + const imageRegex = /\[IMAGE\s+(\d+):\s*(.*?)\]/gi; + const matches: Array<{fullMatch: string, number: string, description: string, index: number}> = []; + + // First, collect all matches + let match; + while ((match = imageRegex.exec(content)) !== null) { + const [fullMatch, number, description] = match; + if (number && description) { + matches.push({ + fullMatch, + number: number.trim(), + description: description.trim(), + index: match.index + }); + } + } + + console.log('🔍 Found image markers:', matches); + + // Sort matches by their position in the content (descending) to avoid index shifting issues + matches.sort((a, b) => b.index - a.index); + + // Replace matches from end to beginning to avoid index shifting + matches.forEach((matchInfo) => { + const imageIndex = parseInt(matchInfo.number) - 1; // Convert 1-based to 0-based const image = images[imageIndex]; if (image && image.image_url) { - const imageMarkdown = `![${description || 'Image'}](${image.image_url})`; - processedContent = processedContent.replace(fullMatch, imageMarkdown); + const imageMarkdown = `![${matchInfo.description || 'Image'}](${image.image_url})`; + processedContent = processedContent.replace(matchInfo.fullMatch, imageMarkdown); + console.log(`✅ Replaced IMAGE ${matchInfo.number} with: ${image.image_url}`); + } else { + console.log(`❌ No image found for IMAGE ${matchInfo.number} (index ${imageIndex})`); } - - imageIndex++; - } + }); + console.log('📝 Processed content length:', processedContent.length); return processedContent; } @@ -139,7 +164,7 @@ export class PerplexityService { const isDeepResearch = model === 'sonar-deep-research'; const useStreaming = stream && !isDeepResearch; - // Insert query header at the current cursor position + // Insert query header based on headerPosition setting const cursor = editor.getCursor(); console.log('Initial cursor position:', cursor); @@ -150,18 +175,29 @@ export class PerplexityService { ? `\n\n***\n> [!info] **Perplexity Deep Research Query** (${timestamp})\n> **Question:**\n${processedQuery}\n> **Model:** ${model}\n> \n> 🔍 **Conducting exhaustive research across hundreds of sources...**\n> *This may take 30-60 seconds for comprehensive analysis.*\n> \n> ### **Deep Research Analysis**:\n\n` : `\n\n***\n> [!info] **Perplexity 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); + let responseCursor; - // 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 - }; + // Handle header position setting + if (this.settings.headerPosition === 'bottom') { + // For bottom placement, don't insert header now - we'll add it later + // Start streaming content directly at the cursor position + responseCursor = cursor; + } else { + // Default to top placement (including when headerPosition is undefined) + editor.replaceRange(headerText, cursor, cursor); + + // Calculate where the response content should start + const headerLines = headerText.split('\n'); + const lastLine = headerLines[headerLines.length - 1] || ''; + responseCursor = { + line: cursor.line + headerLines.length - 1, + ch: lastLine.length + }; + } console.log('Response cursor position:', responseCursor); + console.log('Header position setting:', this.settings.headerPosition); + console.log('Header text preview:', headerText ? headerText.substring(0, 100) + '...' : 'No header text'); // Show loading notice for deep research let loadingNotice: Notice | null = null; @@ -277,7 +313,7 @@ export class PerplexityService { throw new Error('No response body'); } - await this.handleStreamingResponse(response, editor, responseCursor, requestId); + await this.handleStreamingResponse(response, editor, responseCursor, requestId, headerText); } else { // Use Obsidian's request method for non-streaming with cache busting const response = await request({ @@ -300,6 +336,8 @@ export class PerplexityService { // Process images if available if (options?.return_images && data.images) { + console.log('🖼️ Processing images in non-streaming response:', data.images.length, 'images found'); + console.log('📝 Content before image processing:', content.substring(0, 500) + '...'); content = this.processContentWithImages(content, data.images); } @@ -316,6 +354,13 @@ export class PerplexityService { this.addCitations(editor, data.citations); } } + + // Add header at bottom if that setting is enabled + if (this.settings.headerPosition === 'bottom') { + const endOfDoc = editor.lastLine(); + const endPos = { line: endOfDoc, ch: editor.getLine(endOfDoc).length }; + editor.replaceRange('\n\n' + headerText, endPos); + } } // Add separator at the final cursor position @@ -350,7 +395,8 @@ export class PerplexityService { response: Response, editor: Editor, responseCursor: { line: number; ch: number }, - requestId?: number + requestId?: number, + headerText?: string ): Promise { const reader = response.body?.getReader(); if (!reader) throw new Error('No response body'); @@ -361,6 +407,8 @@ export class PerplexityService { let currentPos = { ...responseCursor }; let finalResponseData: any = null; + console.log(`📍 Initial currentPos:`, currentPos); + try { while (true) { const { done, value } = await reader.read(); @@ -403,6 +451,7 @@ export class PerplexityService { if (parsed.choices?.[0]?.delta?.content) { const content = parsed.choices[0].delta.content; if (content) { + console.log(`📝 Inserting content at position:`, currentPos, `Content:`, content.substring(0, 50) + '...'); editor.replaceRange(content, currentPos); // Update cursor position after insertion const contentLines = content.split('\n'); @@ -430,7 +479,7 @@ export class PerplexityService { // Process final metadata (citations, images) after streaming is complete if (finalResponseData) { console.log(`📝 Processing final response data [${requestId || 'unknown'}]:`, finalResponseData); - await this.processStreamingMetadata(finalResponseData, editor); + await this.processStreamingMetadata(finalResponseData, editor, headerText); } // Add final separator @@ -447,13 +496,16 @@ export class PerplexityService { private async processStreamingMetadata( finalResponseData: any, - editor: Editor + editor: Editor, + headerText?: string ): Promise { console.log('🔍 Perplexity Streaming Response Data:', JSON.stringify(finalResponseData, null, 2)); // Process images with intelligent placement if (finalResponseData.images && finalResponseData.images.length > 0) { + console.log('🖼️ Processing images in streaming response:', finalResponseData.images.length, 'images found'); const content = editor.getValue(); + console.log('📝 Content before image processing:', content.substring(0, 500) + '...'); const processedContent = this.processContentWithImages(content, finalResponseData.images); if (processedContent !== content) { @@ -491,5 +543,17 @@ export class PerplexityService { } else { console.log('📚 No sources/citations found in Perplexity streaming response'); } + + // Add header at bottom if that setting is enabled + if (this.settings.headerPosition === 'bottom' && headerText) { + console.log('📄 Adding header at bottom of document'); + const endOfDoc = editor.lastLine(); + const endPos = { line: endOfDoc, ch: editor.getLine(endOfDoc).length }; + console.log('📍 End position for header:', endPos); + editor.replaceRange('\n\n' + headerText, endPos); + console.log('✅ Header added at bottom'); + } else { + console.log('📄 Header position setting:', this.settings.headerPosition, 'Header text available:', !!headerText); + } } -} \ No newline at end of file +} diff --git a/styles.css b/styles.css index 4eb7e07..ac3ecf5 100644 --- a/styles.css +++ b/styles.css @@ -1,72 +1 @@ -/* 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 */ +.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}