improve(modal) All modals now inject the required prompt to place images THROUGHOUT a file

This commit is contained in:
TanujKS 2025-07-22 13:37:21 -07:00
parent ee286d2330
commit ef85565f19
6 changed files with 188 additions and 37 deletions

View file

@ -179,8 +179,10 @@ export default class PerplexedPlugin extends Plugin {
await this.perplexityService.queryPerplexity(query, model, stream, editor, options);
}
public async queryPerplexica(query: string, focusMode: string, optimizationMode: string, stream: boolean, editor: Editor): Promise<void> {
await this.perplexicaService.queryPerplexica(query, focusMode, optimizationMode, stream, editor);
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?: {
@ -188,6 +190,7 @@ export default class PerplexedPlugin extends Plugin {
temperature?: number;
top_p?: number;
system_prompt?: string;
return_images?: boolean;
}): Promise<void> {
await this.lmStudioService.queryLMStudio(query, model, stream, editor, options);
}

View file

@ -10,6 +10,7 @@ export class LMStudioModal extends Modal {
private maxTokensInput!: HTMLInputElement;
private temperatureInput!: HTMLInputElement;
private systemPromptInput!: HTMLTextAreaElement;
private imagesToggle!: HTMLInputElement;
constructor(app: App, editor: Editor, lmStudioService: LMStudioService) {
super(app);
@ -79,6 +80,17 @@ export class LMStudioModal extends Modal {
cls: 'text-input'
});
// Images toggle
const imagesDiv = form.createDiv({cls: 'setting-item'});
const imagesLabel = imagesDiv.createEl('label');
this.imagesToggle = imagesLabel.createEl('input', {type: 'checkbox'});
this.imagesToggle.checked = false;
imagesLabel.createSpan({text: ' Include Images'});
// Add description for images toggle
const imagesDesc = imagesDiv.createDiv({cls: 'setting-item-description images-description'});
imagesDesc.textContent = 'Include image references throughout the response where appropriate';
// Stream toggle
const streamDiv = form.createDiv({cls: 'setting-item'});
const streamLabel = streamDiv.createEl('label');
@ -110,9 +122,22 @@ export class LMStudioModal extends Modal {
return;
}
// If images are enabled, add image markers to the query
let processedQuery = query;
if (this.imagesToggle.checked) {
processedQuery = `${query}
**Image References:**
Please include the following image references throughout your response where appropriate:
- [IMAGE 1: Relevant diagram or illustration related to the topic]
- [IMAGE 2: Practical example or use case visualization]
- [IMAGE 3: Additional supporting visual content]`;
}
const options: LMStudioOptions = {
max_tokens: parseInt(this.maxTokensInput.value) || 2048,
temperature: parseFloat(this.temperatureInput.value) || 0.7
temperature: parseFloat(this.temperatureInput.value) || 0.7,
return_images: this.imagesToggle.checked
};
const systemPrompt = this.systemPromptInput.value.trim();
@ -122,7 +147,7 @@ export class LMStudioModal extends Modal {
this.close();
await this.lmStudioService.queryLMStudio(
query,
processedQuery,
this.modelSelect.value,
this.streamToggle.checked,
this.editor,

View file

@ -1,5 +1,5 @@
import { App, Modal, Notice, Editor } from 'obsidian';
import { PerplexicaService } from '../services/perplexicaService';
import { PerplexicaService, PerplexicaOptions } from '../services/perplexicaService';
export class PerplexicaModal extends Modal {
private editor: Editor;
@ -8,6 +8,7 @@ export class PerplexicaModal extends Modal {
private focusModeSelect!: HTMLSelectElement;
private optimizationSelect!: HTMLSelectElement;
private streamToggle!: HTMLInputElement;
private imagesToggle!: HTMLInputElement;
constructor(app: App, editor: Editor, perplexicaService: PerplexicaService) {
super(app);
@ -51,6 +52,17 @@ export class PerplexicaModal extends Modal {
if (mode === 'balanced') option.selected = true;
});
// Images toggle
const imagesDiv = form.createDiv({cls: 'setting-item'});
const imagesLabel = imagesDiv.createEl('label');
this.imagesToggle = imagesLabel.createEl('input', {type: 'checkbox'});
this.imagesToggle.checked = false;
imagesLabel.createSpan({text: ' Include Images'});
// Add description for images toggle
const imagesDesc = imagesDiv.createDiv({cls: 'setting-item-description images-description'});
imagesDesc.textContent = 'Include image references throughout the response where appropriate';
// Stream toggle
const streamDiv = form.createDiv({cls: 'setting-item'});
const streamLabel = streamDiv.createEl('label');
@ -82,13 +94,30 @@ export class PerplexicaModal extends Modal {
return;
}
// If images are enabled, add image markers to the query
let processedQuery = query;
if (this.imagesToggle.checked) {
processedQuery = `${query}
**Image References:**
Please include the following image references throughout your response where appropriate:
- [IMAGE 1: Relevant diagram or illustration related to the topic]
- [IMAGE 2: Practical example or use case visualization]
- [IMAGE 3: Additional supporting visual content]`;
}
const options: PerplexicaOptions = {
return_images: this.imagesToggle.checked
};
this.close();
await this.perplexicaService.queryPerplexica(
query,
processedQuery,
this.focusModeSelect.value,
this.optimizationSelect.value,
this.streamToggle.checked,
this.editor
this.editor,
options
);
}

View file

@ -75,7 +75,7 @@ export class PerplexityModal extends Modal {
// Add description for images toggle
const imagesDesc = imagesDiv.createDiv({cls: 'setting-item-description images-description'});
imagesDesc.textContent = 'Include image results from search - images will appear in an Images section';
imagesDesc.textContent = 'Include image results from search - images will be integrated throughout the response where appropriate';
// Related questions toggle
const relatedQuestionsDiv = form.createDiv({cls: 'setting-item'});
@ -133,6 +133,18 @@ export class PerplexityModal extends Modal {
return;
}
// If images are enabled, add image markers to the query
let processedQuery = query;
if (this.imagesToggle.checked) {
processedQuery = `${query}
**Image References:**
Please include the following image references throughout your response where appropriate:
- [IMAGE 1: Relevant diagram or illustration related to the topic]
- [IMAGE 2: Practical example or use case visualization]
- [IMAGE 3: Additional supporting visual content]`;
}
const options: PerplexityOptions = {
return_citations: this.citationsToggle.checked,
return_images: this.imagesToggle.checked,
@ -142,7 +154,7 @@ export class PerplexityModal extends Modal {
this.close();
await this.perplexityService.queryPerplexity(
query,
processedQuery,
this.modelSelect.value,
this.streamToggle.checked,
this.editor,

View file

@ -5,6 +5,7 @@ export interface LMStudioOptions {
temperature?: number;
top_p?: number;
system_prompt?: string;
return_images?: boolean;
}
export interface LMStudioSettings {
@ -18,6 +19,30 @@ export class LMStudioService {
this.settings = settings;
}
private processContentWithImages(content: string): string {
// Find image markers like [IMAGE 1: description] or [IMAGE 1: description]
const imageRegex = /\[IMAGE\s+(\d+):\s*(.*?)\]/gi;
let match;
let imageIndex = 0;
while ((match = imageRegex.exec(content)) !== null) {
const description = match[2]?.trim() || '';
// Create a placeholder image markdown with description
const imageMarkdown = `\n\n![${description}](https://via.placeholder.com/600x400/cccccc/666666?text=${encodeURIComponent(description)})\n*${description}*\n`;
// Replace the marker with the placeholder image
content = content.replace(match[0], imageMarkdown);
imageIndex++;
}
if (imageIndex > 0) {
console.log(`🔄 Processed ${imageIndex} image markers in LM Studio content`);
}
return content;
}
public async queryLMStudio(
query: string,
model: string,
@ -85,9 +110,9 @@ export class LMStudioService {
let finalCursor = responseCursor;
if (stream) {
await this.handleStreamingResponse(response, editor, responseCursor);
await this.handleStreamingResponse(response, editor, responseCursor, options);
} else {
await this.handleNonStreamingResponse(response, editor, responseCursor);
await this.handleNonStreamingResponse(response, editor, responseCursor, options);
}
// Add separator at the final cursor position
@ -103,7 +128,8 @@ export class LMStudioService {
private async handleStreamingResponse(
response: Response,
editor: Editor,
responseCursor: { line: number; ch: number }
responseCursor: { line: number; ch: number },
options?: LMStudioOptions
): Promise<void> {
const reader = response.body?.getReader();
if (!reader) throw new Error('No response body');
@ -130,7 +156,13 @@ export class LMStudioService {
try {
const parsed = JSON.parse(data);
if (parsed.choices?.[0]?.delta?.content) {
const content = 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');
@ -158,11 +190,18 @@ export class LMStudioService {
private async handleNonStreamingResponse(
response: Response,
editor: Editor,
responseCursor: { line: number; ch: number }
responseCursor: { line: number; ch: number },
options?: LMStudioOptions
): Promise<void> {
const data = await response.json();
const content = data.choices?.[0]?.message?.content || 'No response received';
editor.replaceRange(content, responseCursor);
// Process images if enabled
let processedContent = content;
if (options?.return_images) {
processedContent = this.processContentWithImages(content);
}
editor.replaceRange(processedContent, responseCursor);
}
}

View file

@ -6,6 +6,10 @@ export interface PerplexicaSettings {
defaultModel: string;
}
export interface PerplexicaOptions {
return_images?: boolean;
}
export class PerplexicaService {
private settings: PerplexicaSettings;
@ -13,12 +17,37 @@ export class PerplexicaService {
this.settings = settings;
}
private processContentWithImages(content: string): string {
// Find image markers like [IMAGE 1: description] or [IMAGE 1: description]
const imageRegex = /\[IMAGE\s+(\d+):\s*(.*?)\]/gi;
let match;
let imageIndex = 0;
while ((match = imageRegex.exec(content)) !== null) {
const description = match[2]?.trim() || '';
// Create a placeholder image markdown with description
const imageMarkdown = `\n\n![${description}](https://via.placeholder.com/600x400/cccccc/666666?text=${encodeURIComponent(description)})\n*${description}*\n`;
// Replace the marker with the placeholder image
content = content.replace(match[0], imageMarkdown);
imageIndex++;
}
if (imageIndex > 0) {
console.log(`🔄 Processed ${imageIndex} image markers in Perplexica content`);
}
return content;
}
public async queryPerplexica(
query: string,
focusMode: string,
optimizationMode: string,
stream: boolean,
editor: Editor
editor: Editor,
options?: PerplexicaOptions
): Promise<void> {
const timestamp = new Date().toISOString();
@ -77,9 +106,9 @@ export class PerplexicaService {
}
if (stream) {
await this.handleStreamingResponse(response, editor, responseCursor);
await this.handleStreamingResponse(response, editor, responseCursor, options);
} else {
await this.handleNonStreamingResponse(response, editor, responseCursor);
await this.handleNonStreamingResponse(response, editor, responseCursor, options);
}
// Add separator
@ -102,7 +131,7 @@ export class PerplexicaService {
}
}
private async handleStreamingResponse(response: Response, editor: Editor, responseCursor: { line: number; ch: number }): Promise<void> {
private async handleStreamingResponse(response: Response, editor: Editor, responseCursor: { line: number; ch: number }, options?: PerplexicaOptions): Promise<void> {
const reader = response.body?.getReader();
if (!reader) throw new Error('No response body');
@ -117,23 +146,30 @@ export class PerplexicaService {
for (const line of lines) {
try {
const parsed = JSON.parse(line);
if (parsed.type === 'response' && parsed.data) {
editor.replaceRange(parsed.data, currentPos);
// Update cursor position
const lines = parsed.data.split('\n');
if (lines.length === 1) {
currentPos = { line: currentPos.line, ch: currentPos.ch + parsed.data.length };
} else {
currentPos = {
line: currentPos.line + lines.length - 1,
ch: lines[lines.length - 1]?.length || 0
};
if (parsed.type === 'response' && parsed.data) {
let content = parsed.data;
// Process images if enabled
if (options?.return_images) {
content = this.processContentWithImages(content);
}
editor.replaceRange(content, currentPos);
// Update cursor position
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));
}
// 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
}
@ -141,8 +177,15 @@ export class PerplexicaService {
}
}
private async handleNonStreamingResponse(response: Response, editor: Editor, responseCursor: { line: number; ch: number }): Promise<void> {
private async handleNonStreamingResponse(response: Response, editor: Editor, responseCursor: { line: number; ch: number }, options?: PerplexicaOptions): Promise<void> {
const text = await response.text();
editor.replaceRange(text, responseCursor);
// Process images if enabled
let processedText = text;
if (options?.return_images) {
processedText = this.processContentWithImages(text);
}
editor.replaceRange(processedText, responseCursor);
}
}