mirror of
https://github.com/lossless-group/perplexed-plugin.git
synced 2026-07-22 06:49:50 +00:00
improve(modal) All modals now inject the required prompt to place images THROUGHOUT a file
This commit is contained in:
parent
ee286d2330
commit
ef85565f19
6 changed files with 188 additions and 37 deletions
7
main.ts
7
main.ts
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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})\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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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})\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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue