mirror of
https://github.com/lossless-group/perplexed-plugin.git
synced 2026-07-22 06:49:50 +00:00
Round 3 of the marketplace submission (#12513) flagged 73 errors + 6 warnings against the bot's recommended eslint config. This ships the literal fixes the bot demanded so the rescan goes clean and the PR moves to human review. Sentence-case (66 errors). Applied the bot's exact "Expected: '...'" rewrites across main.ts, the 8 modal files, and claudeService.ts. The bot runs obsidianmd/ui/sentence-case with `enforceCamelCaseLower: true` and no brand allowlist, so "Ask Perplexity" becomes "Ask perplexity", "LM Studio" becomes "Lm studio", "PromptsService" becomes "promptsservice" — UI text now reads as broken. Brand capitalization is to be restored after marketplace acceptance, when we own the plugin entry on our own update cadence. Local eslint.config.mjs's brand-allowlist override also dropped so the local lint stops disagreeing with the bot. Async without await (2 errors). main.ts:504 `callback: async () => {}` reduced to `callback: () => {}` (the wrapped reinitializeServices() is sync). directoryTemplateService.ts:195 listTemplates reduced from `async function ... : Promise<TemplateFile[]>` to sync; the two callers in main.ts updated to drop `await`. no-restricted-globals (4 errors + 4 description-missing errors). All four streaming fetch() sites (directoryTemplateService, lmStudioService, perplexicaService, perplexityService) switched to `activeWindow.fetch`, matching the precedent already used for `activeWindow.setTimeout` elsewhere. The bare eslint-disable directives removed. SSE/streaming rationale (Obsidian's requestUrl buffers) preserved in adjacent comments. Unused catch bindings (6 warnings). Three in main.ts, plus lmStudioService:243, perplexicaService:255, logger.ts:47 — all collapsed from `catch (e)` / `catch (error)` to `catch { ... }`. Also included: changelog/2026-05-12_01.md documenting what the bot flagged, the diff cost (brand names lowercased mid-string), the non-sentence-case fixes, and verification. Verification: both eslint configs (local + bot-mirror recommended) exit 0; tsc -noEmit -skipLibCheck exit 0; production esbuild exit 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1850 lines
82 KiB
TypeScript
1850 lines
82 KiB
TypeScript
import type { App, Editor} from 'obsidian';
|
|
import { Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
|
|
|
// Import services
|
|
import { PerplexityService } from './src/services/perplexityService';
|
|
import { PerplexicaService } from './src/services/perplexicaService';
|
|
import { LMStudioService } from './src/services/lmStudioService';
|
|
import { ClaudeService } from './src/services/claudeService';
|
|
import { PromptsService } from './src/services/promptsService';
|
|
|
|
// Import modals
|
|
import { PerplexityModal } from './src/modals/PerplexityModal';
|
|
import { PerplexicaModal } from './src/modals/PerplexicaModal';
|
|
import { LMStudioModal } from './src/modals/LMStudioModal';
|
|
import { ClaudeModal } from './src/modals/ClaudeModal';
|
|
import { URLUpdateModal } from './src/modals/URLUpdateModal';
|
|
import { ArticleGeneratorModal } from './src/modals/ArticleGeneratorModal';
|
|
import { TextEnhancementModal } from './src/modals/TextEnhancementModal';
|
|
import { TextEnhancementWithImagesModal } from './src/modals/TextEnhancementWithImagesModal';
|
|
import { DirectoryTemplatePickerModal } from './src/modals/DirectoryTemplatePickerModal';
|
|
import { FolderPickerModal } from './src/modals/FolderPickerModal';
|
|
import { BatchConfirmModal } from './src/modals/BatchConfirmModal';
|
|
|
|
import {
|
|
applyTemplate as applyDirectoryTemplate,
|
|
applyTemplateBatch as applyDirectoryTemplateBatch,
|
|
listMarkdownFilesInFolder,
|
|
listTemplates as listDirectoryTemplates,
|
|
loadTemplate as loadDirectoryTemplate,
|
|
pathMatchesGlobs,
|
|
} from './src/services/directoryTemplateService';
|
|
import type { DirectoryTemplateSettings, ParsedTemplate } from './src/services/directoryTemplateService';
|
|
import type { TFile } from 'obsidian';
|
|
import { findImagesForSelection } from './src/services/findImagesService';
|
|
import type { FindImagesSettings } from './src/services/findImagesService';
|
|
import { reSeedMissingFiles, seedTemplatesIfMissing } from './src/services/templateSeederService';
|
|
|
|
|
|
interface PerplexedPluginSettings {
|
|
mySetting: string;
|
|
localLLMPath: string;
|
|
requestBodyTemplate: string;
|
|
perplexityRequestTemplate: string;
|
|
perplexityApiKey: string;
|
|
perplexicaEndpoint: string;
|
|
perplexityEndpoint: string;
|
|
lmStudioEndpoint: string;
|
|
lmStudioRequestTemplate: string;
|
|
anthropicApiKey: string;
|
|
claudeDefaultModel: string;
|
|
defaultModel: string;
|
|
defaultOptimizationMode: string;
|
|
defaultFocusMode: string;
|
|
defaultLMStudioModel: string;
|
|
|
|
// Display Settings
|
|
headerPosition: 'top' | 'bottom';
|
|
|
|
// Prompt Settings
|
|
prompts: {
|
|
// System prompts
|
|
perplexitySystemPrompt: string;
|
|
perplexicaSystemPrompt: string;
|
|
lmStudioDefaultSystemPrompt: string;
|
|
|
|
// Placeholder text
|
|
perplexityQueryPlaceholder: string;
|
|
perplexicaQueryPlaceholder: string;
|
|
lmStudioQueryPlaceholder: string;
|
|
lmStudioSystemPromptPlaceholder: string;
|
|
articleTermPlaceholder: string;
|
|
|
|
// Article generator template
|
|
articleGeneratorTemplate: string;
|
|
|
|
// Deep Research article generator template
|
|
deepResearchArticleTemplate: string;
|
|
|
|
// Image prompts
|
|
imageReferencesPrompt: string;
|
|
|
|
// Text enhancement prompt
|
|
enhancePrompt: string;
|
|
|
|
// Text enhancement with images prompt
|
|
enhanceWithImagesPrompt: string;
|
|
};
|
|
|
|
// Directory templates (v0.1 spike — see context-v/specs/Per-Directory-Profile-Templates.md)
|
|
directoryTemplatesRoot: string;
|
|
directoryTemplatesFrontmatterWhitelist: string[];
|
|
directoryTemplatesRequestTimeoutMs: number;
|
|
|
|
// Find images for selection
|
|
findImagesMaxImages: number;
|
|
}
|
|
|
|
const DEFAULT_SETTINGS: PerplexedPluginSettings = {
|
|
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',
|
|
headerPosition: 'top',
|
|
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: '',
|
|
anthropicApiKey: '',
|
|
claudeDefaultModel: 'claude-opus-4-7',
|
|
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 / Vane?",
|
|
lmStudioQueryPlaceholder: "What would you like to ask?",
|
|
lmStudioSystemPromptPlaceholder: "You are a helpful AI assistant...",
|
|
articleTermPlaceholder: "e.g., AI Copilots, AI Studios, Machine Learning, etc.",
|
|
|
|
|
|
|
|
// 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`,
|
|
|
|
// Deep Research article generator template
|
|
deepResearchArticleTemplate: `Conduct comprehensive research and write an in-depth article about "{TERM}".
|
|
|
|
**Research Requirements:**
|
|
- Conduct exhaustive research across hundreds of sources
|
|
- Analyze multiple perspectives and viewpoints
|
|
- Include academic, industry, and expert sources
|
|
- Provide detailed citations and references
|
|
- Examine historical context and evolution
|
|
- Consider global implications and regional variations
|
|
|
|
**Article Structure:**
|
|
|
|
1. **Executive Summary** (1 paragraph)
|
|
- Concise overview of key findings
|
|
- Main conclusions and implications
|
|
|
|
2. **Introduction and Definition** (2-3 paragraphs)
|
|
- Comprehensive definition and scope
|
|
- Historical context and evolution
|
|
- Current significance and relevance
|
|
|
|
3. **Comprehensive Analysis** (6-8 paragraphs)
|
|
- Detailed examination of core concepts
|
|
- Multiple perspectives and approaches
|
|
- Industry applications and use cases
|
|
- Technical implementation details
|
|
- Market analysis and competitive landscape
|
|
- Regulatory and ethical considerations
|
|
|
|
4. **Current State and Market Dynamics** (3-4 paragraphs)
|
|
- Global adoption patterns and trends
|
|
- Key players, technologies, and platforms
|
|
- Regional variations and cultural factors
|
|
- Economic impact and market size
|
|
- Recent developments and breakthroughs
|
|
|
|
5. **Challenges and Opportunities** (2-3 paragraphs)
|
|
- Technical challenges and limitations
|
|
- Implementation barriers and solutions
|
|
- Future opportunities and potential
|
|
- Risk factors and mitigation strategies
|
|
|
|
6. **Future Outlook and Predictions** (2-3 paragraphs)
|
|
- Short-term developments (1-2 years)
|
|
- Medium-term trends (3-5 years)
|
|
- Long-term implications (5+ years)
|
|
- Strategic recommendations
|
|
|
|
7. **Conclusion** (1-2 paragraphs)
|
|
- Synthesis of key findings
|
|
- Strategic implications
|
|
- Call to action or forward-looking statement
|
|
|
|
**Research Guidelines:**
|
|
- Include diverse source types (academic, industry, news, expert opinions)
|
|
- Provide detailed citations for all claims
|
|
- Analyze conflicting viewpoints and evidence
|
|
- Consider global and regional perspectives
|
|
- Include quantitative data where available
|
|
- Examine both benefits and risks
|
|
- Address ethical and societal implications
|
|
|
|
**Quality Standards:**
|
|
- Academic rigor with practical relevance
|
|
- Balanced analysis of multiple perspectives
|
|
- Evidence-based conclusions
|
|
- Clear, professional writing style
|
|
- Comprehensive bibliography`,
|
|
|
|
// 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]",
|
|
|
|
// Text enhancement prompt
|
|
enhancePrompt: "Please enhance the following text by improving clarity, adding relevant details, expanding on key points, and making it more comprehensive and engaging. Maintain the original meaning and tone while making it more informative and well-structured:\n\n{TEXT}",
|
|
|
|
// Text enhancement with images prompt
|
|
enhanceWithImagesPrompt: "Please provide 1-3 relevant images for the following text. Return ONLY the image markers in the format [IMAGE 1: description], [IMAGE 2: description], etc. Each image should illustrate a key concept, example, or visual representation related to the text. Do not include any other text or explanation:\n\n{TEXT}"
|
|
},
|
|
|
|
// Directory templates (v0.1 spike defaults)
|
|
directoryTemplatesRoot: 'zz-cf-lib/templates',
|
|
directoryTemplatesFrontmatterWhitelist: ['title', 'og_description', 'tags', 'og_image'],
|
|
directoryTemplatesRequestTimeoutMs: 300000,
|
|
|
|
// Find images for selection
|
|
findImagesMaxImages: 3
|
|
};
|
|
|
|
export default class PerplexedPlugin extends Plugin {
|
|
public settings: PerplexedPluginSettings = DEFAULT_SETTINGS;
|
|
private statusBarItemEl: HTMLElement | null = null;
|
|
private ribbonIconEl: HTMLElement | null = null;
|
|
private batchCancelled = false;
|
|
|
|
// Service instances
|
|
private perplexityService!: PerplexityService | null;
|
|
private perplexicaService!: PerplexicaService | null;
|
|
private lmStudioService!: LMStudioService | null;
|
|
private claudeService!: ClaudeService | null;
|
|
private promptsService!: PromptsService | null;
|
|
|
|
async onload(): Promise<void> {
|
|
try {
|
|
console.debug('Perplexed Plugin: Starting initialization...');
|
|
|
|
await this.loadSettings();
|
|
console.debug('Perplexed Plugin: Settings loaded successfully');
|
|
|
|
// First-run seeding: if the configured templates root is missing
|
|
// or empty, drop in the four shipped templates plus a README so a
|
|
// freshly-installed perplexed has working defaults out of the box.
|
|
// Idempotent — never overwrites existing files.
|
|
try {
|
|
const result = await seedTemplatesIfMissing(this.app, this.settings.directoryTemplatesRoot);
|
|
if (result.seeded > 0) {
|
|
console.debug(`Perplexed Plugin: seeded ${result.seeded.toString()} template(s) (${result.reason})`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: template seeding failed:', error);
|
|
}
|
|
|
|
// Initialize prompts service first
|
|
try {
|
|
this.promptsService = new PromptsService(this.settings.prompts);
|
|
console.debug('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.debug('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.debug('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.debug('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;
|
|
}
|
|
|
|
try {
|
|
this.claudeService = new ClaudeService({
|
|
anthropicApiKey: this.settings.anthropicApiKey,
|
|
promptsService: this.promptsService,
|
|
headerPosition: this.settings.headerPosition,
|
|
});
|
|
console.debug('Perplexed Plugin: ClaudeService initialized successfully');
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: Failed to initialize ClaudeService:', error);
|
|
new Notice('Failed to initialize claudeservice');
|
|
this.claudeService = null;
|
|
}
|
|
} else {
|
|
// If promptsService failed, set all other services to null
|
|
this.perplexityService = null;
|
|
this.perplexicaService = null;
|
|
this.lmStudioService = null;
|
|
this.claudeService = null;
|
|
console.debug('Perplexed Plugin: Skipping service initialization due to PromptsService failure');
|
|
}
|
|
|
|
// Debug: Log current settings
|
|
console.debug('Perplexed Plugin: Current Perplexica Path:', this.settings.perplexicaEndpoint);
|
|
console.debug('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));
|
|
console.debug('Perplexed Plugin: Settings tab added successfully');
|
|
|
|
// Register commands with error handling
|
|
try {
|
|
this.registerPerplexicaCommands();
|
|
console.debug('Perplexed Plugin: Perplexica commands registered successfully');
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: Failed to register Perplexica commands:', error);
|
|
}
|
|
|
|
try {
|
|
this.registerPerplexityCommands();
|
|
console.debug('Perplexed Plugin: Perplexity commands registered successfully');
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: Failed to register Perplexity commands:', error);
|
|
}
|
|
|
|
try {
|
|
this.registerLMStudioCommands();
|
|
console.debug('Perplexed Plugin: LM Studio commands registered successfully');
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: Failed to register LM Studio commands:', error);
|
|
}
|
|
|
|
try {
|
|
this.registerClaudeCommands();
|
|
console.debug('Perplexed Plugin: Claude commands registered successfully');
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: Failed to register Claude commands:', error);
|
|
}
|
|
|
|
try {
|
|
this.registerArticleGeneratorCommands();
|
|
console.debug('Perplexed Plugin: Article generator commands registered successfully');
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: Failed to register article generator commands:', error);
|
|
}
|
|
|
|
try {
|
|
this.registerTextEnhancementCommands();
|
|
console.debug('Perplexed Plugin: Text enhancement commands registered successfully');
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: Failed to register text enhancement commands:', error);
|
|
}
|
|
|
|
try {
|
|
this.registerTextEnhancementWithImagesCommands();
|
|
console.debug('Perplexed Plugin: Get related images commands registered successfully');
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: Failed to register get related images commands:', error);
|
|
}
|
|
|
|
// Diagnostic action: log registered commands to the console.
|
|
this.addCommand({
|
|
id: 'debug-status',
|
|
name: 'Debug: log registered actions',
|
|
callback: () => {
|
|
this.debugCommands();
|
|
}
|
|
});
|
|
|
|
// Add command to reset prompts to defaults
|
|
this.addCommand({
|
|
id: 'reset-prompts',
|
|
name: 'Reset prompts to default',
|
|
callback: async () => {
|
|
await this.resetPromptsToDefault();
|
|
}
|
|
});
|
|
|
|
// Reinitialize all provider services (Perplexity / Perplexica /
|
|
// LM Studio / Claude). Useful after editing settings.
|
|
this.addCommand({
|
|
id: 'reinitialize-services',
|
|
name: 'Reinitialize provider services',
|
|
callback: () => {
|
|
this.reinitializeServices();
|
|
}
|
|
});
|
|
|
|
// Directory templates (v0.1 spike). See context-v/specs/Per-Directory-Profile-Templates.md
|
|
this.addCommand({
|
|
id: 'apply-directory-template-to-current-file',
|
|
name: 'Apply directory template to current file',
|
|
callback: async () => {
|
|
await this.runApplyDirectoryTemplate();
|
|
}
|
|
});
|
|
|
|
// Batch run a directory template across every file in a folder (v0.2).
|
|
this.addCommand({
|
|
id: 'apply-directory-template-to-folder',
|
|
name: 'Apply directory template to all files in folder',
|
|
callback: () => {
|
|
this.runApplyDirectoryTemplateBatch();
|
|
}
|
|
});
|
|
|
|
// Cancel an in-flight batch run.
|
|
this.addCommand({
|
|
id: 'stop-directory-template-batch',
|
|
name: 'Stop directory template batch',
|
|
callback: () => {
|
|
this.batchCancelled = true;
|
|
new Notice('Stop requested — finishing current file then halting.');
|
|
}
|
|
});
|
|
|
|
// Find images for the current selection — anchors search on the
|
|
// selection's content + the active file's url/site_name frontmatter
|
|
// and distributes returned images between paragraphs.
|
|
this.addCommand({
|
|
id: 'find-images-for-selection',
|
|
name: 'Find images for selection',
|
|
editorCallback: (editor: Editor) => {
|
|
const file = this.app.workspace.getActiveFile();
|
|
if (!file) {
|
|
new Notice('No active file.');
|
|
return;
|
|
}
|
|
const findSettings: FindImagesSettings = {
|
|
perplexityApiKey: this.settings.perplexityApiKey,
|
|
perplexityEndpoint: this.settings.perplexityEndpoint,
|
|
maxImages: this.settings.findImagesMaxImages,
|
|
};
|
|
void findImagesForSelection(this.app, findSettings, file, editor);
|
|
}
|
|
});
|
|
|
|
console.debug('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 {
|
|
this.statusBarItemEl?.remove();
|
|
this.ribbonIconEl?.remove();
|
|
}
|
|
|
|
private async loadSettings() {
|
|
const savedData: Partial<PerplexedPluginSettings> = (await this.loadData()) as Partial<PerplexedPluginSettings> ?? {};
|
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, savedData);
|
|
|
|
// Ensure new fields are always present (migration for existing users)
|
|
if (!this.settings.prompts.deepResearchArticleTemplate) {
|
|
this.settings.prompts.deepResearchArticleTemplate = DEFAULT_SETTINGS.prompts.deepResearchArticleTemplate;
|
|
await this.saveSettings();
|
|
}
|
|
if (!this.settings.prompts.enhancePrompt) {
|
|
this.settings.prompts.enhancePrompt = DEFAULT_SETTINGS.prompts.enhancePrompt;
|
|
await this.saveSettings();
|
|
}
|
|
if (!this.settings.prompts.enhanceWithImagesPrompt) {
|
|
this.settings.prompts.enhanceWithImagesPrompt = DEFAULT_SETTINGS.prompts.enhanceWithImagesPrompt;
|
|
await this.saveSettings();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public async saveSettings(): Promise<void> {
|
|
try {
|
|
await this.saveData(this.settings);
|
|
} catch (error) {
|
|
console.error('Failed to save settings:', error);
|
|
new Notice('Failed to save settings');
|
|
}
|
|
}
|
|
|
|
// Delegate methods to services
|
|
public async queryPerplexity(query: string, model: string, stream: boolean, editor: Editor, options?: {
|
|
return_citations?: boolean;
|
|
return_images?: boolean;
|
|
return_related_questions?: boolean;
|
|
search_recency_filter?: string;
|
|
}): Promise<void> {
|
|
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<void> {
|
|
if (!this.perplexicaService) {
|
|
throw new Error('Perplexica service not initialized');
|
|
}
|
|
await this.perplexicaService.queryPerplexica(query, focusMode, optimizationMode, stream, editor, options);
|
|
}
|
|
|
|
public async queryLMStudio(query: string, model: string, stream: boolean, editor: Editor, options?: {
|
|
max_tokens?: number;
|
|
temperature?: number;
|
|
top_p?: number;
|
|
system_prompt?: string;
|
|
return_images?: boolean;
|
|
}): Promise<void> {
|
|
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 | null {
|
|
return this.promptsService;
|
|
}
|
|
|
|
private registerPerplexicaCommands(): void {
|
|
// Command to update Perplexica URL
|
|
this.addCommand({
|
|
id: 'update-perplexica-url',
|
|
name: 'Update perplexica / vane URL',
|
|
callback: () => {
|
|
const modal = new URLUpdateModal(this.app, {
|
|
title: 'Update Perplexica / Vane API URL',
|
|
label: 'Perplexica / Vane 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 / vane settings',
|
|
callback: () => {
|
|
new Notice(`Current Perplexica / Vane URL: ${this.settings.perplexicaEndpoint}`);
|
|
console.debug('Perplexica Settings:', this.settings);
|
|
}
|
|
});
|
|
|
|
// Command to ask Perplexica
|
|
this.addCommand({
|
|
id: 'ask-perplexica',
|
|
name: 'Ask perplexica / vane',
|
|
editorCallback: (editor: Editor) => {
|
|
try {
|
|
if (!this.perplexicaService) {
|
|
new Notice('Perplexica / vane 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 / vane modal. Check console for details.');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private registerPerplexityCommands(): void {
|
|
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.debug('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.');
|
|
}
|
|
}
|
|
});
|
|
|
|
// 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.debug('Perplexity service status: OK');
|
|
} else {
|
|
new Notice('Perplexity service is not initialized. Check console for errors.');
|
|
console.error('Perplexity service status: FAILED');
|
|
}
|
|
}
|
|
});
|
|
|
|
console.debug('Perplexed Plugin: Perplexity commands registered successfully');
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: Error registering Perplexity commands:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private registerClaudeCommands(): void {
|
|
this.addCommand({
|
|
id: 'ask-claude',
|
|
name: 'Ask Claude',
|
|
editorCallback: (editor: Editor) => {
|
|
if (!this.claudeService) {
|
|
new Notice('Claude service not initialized. Set ANTHROPIC_API_KEY in .env or settings, then reinitialize services.');
|
|
return;
|
|
}
|
|
if (!this.promptsService) {
|
|
new Notice('Prompts service not initialized.');
|
|
return;
|
|
}
|
|
new ClaudeModal(this.app, editor, this.claudeService, this.promptsService).open();
|
|
},
|
|
});
|
|
|
|
this.addCommand({
|
|
id: 'claude-service-status',
|
|
name: 'Check Claude service status',
|
|
callback: () => {
|
|
if (this.claudeService && this.settings.anthropicApiKey) {
|
|
new Notice('Claude service is initialized and an API key is configured.');
|
|
} else if (this.claudeService) {
|
|
new Notice('Claude service is initialized but no API key is set.');
|
|
} else {
|
|
new Notice('Claude service is not initialized.');
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
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.debug('LM Studio Settings:', this.settings);
|
|
}
|
|
});
|
|
|
|
// Command to ask LM Studio
|
|
this.addCommand({
|
|
id: 'ask-lmstudio',
|
|
name: 'Ask lm studio',
|
|
editorCallback: (editor: Editor) => {
|
|
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.');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private registerArticleGeneratorCommands(): void {
|
|
// Register Article Generator command
|
|
this.addCommand({
|
|
id: 'generate-article',
|
|
name: 'Generate one-page article',
|
|
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;
|
|
}
|
|
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 registerTextEnhancementCommands(): void {
|
|
// Register Text Enhancement command
|
|
this.addCommand({
|
|
id: 'enhance-text',
|
|
name: 'Enhance selected text with perplexity',
|
|
editorCallback: (editor: Editor) => {
|
|
try {
|
|
const selectedText = editor.getSelection();
|
|
if (!selectedText || selectedText.trim() === '') {
|
|
new Notice('Please select some text to enhance');
|
|
return;
|
|
}
|
|
|
|
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 TextEnhancementModal(this.app, editor, this.perplexityService, this.promptsService, selectedText).open();
|
|
} catch (error) {
|
|
console.error('Error opening Text Enhancement modal:', error);
|
|
new Notice('Failed to open text enhancement modal. Check console for details.');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private registerTextEnhancementWithImagesCommands(): void {
|
|
// Register Get Related Images command
|
|
this.addCommand({
|
|
id: 'enhance-text-with-images',
|
|
name: 'Get related images for selected text',
|
|
editorCallback: (editor: Editor) => {
|
|
try {
|
|
const selectedText = editor.getSelection();
|
|
if (!selectedText || selectedText.trim() === '') {
|
|
new Notice('Please select some text to get related images for');
|
|
return;
|
|
}
|
|
|
|
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 TextEnhancementWithImagesModal(this.app, editor, this.perplexityService, this.promptsService, selectedText).open();
|
|
} catch (error) {
|
|
console.error('Error opening Get Related Images modal:', error);
|
|
new Notice('Failed to open get related images modal. Check console for details.');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private debugCommands(): void {
|
|
console.debug('=== Perplexed Plugin Debug Information ===');
|
|
console.debug('Plugin instance:', this);
|
|
console.debug('Settings:', this.settings);
|
|
console.debug('Services status:');
|
|
console.debug('- PromptsService:', this.promptsService ? 'Initialized' : 'NOT INITIALIZED');
|
|
console.debug('- PerplexityService:', this.perplexityService ? 'Initialized' : 'NOT INITIALIZED');
|
|
console.debug('- PerplexicaService:', this.perplexicaService ? 'Initialized' : 'NOT INITIALIZED');
|
|
console.debug('- 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') ||
|
|
cmd.includes('enhance-text')
|
|
);
|
|
|
|
console.debug('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.debug('=== End Debug Information ===');
|
|
}
|
|
|
|
private async resetPromptsToDefault(): Promise<void> {
|
|
try {
|
|
console.debug('Perplexed Plugin: Resetting prompts to default...');
|
|
new Notice('Resetting prompts to default values...');
|
|
|
|
// Reset all prompt settings to default values
|
|
this.settings.prompts = { ...DEFAULT_SETTINGS.prompts };
|
|
|
|
// Save the updated settings
|
|
await this.saveSettings();
|
|
|
|
// Reinitialize the prompts service with new settings
|
|
if (this.promptsService) {
|
|
this.promptsService.updateSettings(this.settings.prompts);
|
|
console.debug('Perplexed Plugin: PromptsService updated with default settings');
|
|
}
|
|
|
|
new Notice('✅ Prompts reset to default values successfully');
|
|
console.debug('Perplexed Plugin: Prompts reset to default successfully');
|
|
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: Failed to reset prompts to default:', error);
|
|
new Notice('❌ Failed to reset prompts to default. Check console for details.');
|
|
}
|
|
}
|
|
|
|
private reinitializeServices(): void {
|
|
try {
|
|
console.debug('Perplexed Plugin: Reinitializing services...');
|
|
new Notice('Reinitializing perplexed services...');
|
|
|
|
// Reinitialize prompts service first
|
|
try {
|
|
this.promptsService = new PromptsService(this.settings.prompts);
|
|
console.debug('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.debug('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.debug('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.debug('Perplexed Plugin: LMStudioService reinitialized successfully');
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: Failed to reinitialize LMStudioService:', error);
|
|
this.lmStudioService = null;
|
|
}
|
|
|
|
try {
|
|
this.claudeService = new ClaudeService({
|
|
anthropicApiKey: this.settings.anthropicApiKey,
|
|
promptsService: this.promptsService,
|
|
headerPosition: this.settings.headerPosition,
|
|
});
|
|
console.debug('Perplexed Plugin: ClaudeService reinitialized successfully');
|
|
} catch (error) {
|
|
console.error('Perplexed Plugin: Failed to reinitialize ClaudeService:', error);
|
|
this.claudeService = null;
|
|
}
|
|
} else {
|
|
// If promptsService failed, set all other services to null
|
|
this.perplexityService = null;
|
|
this.perplexicaService = null;
|
|
this.lmStudioService = null;
|
|
this.claudeService = null;
|
|
console.debug('Perplexed Plugin: Skipping service reinitialization due to PromptsService failure');
|
|
}
|
|
|
|
new Notice('Services reinitialization completed. Check console for details.');
|
|
console.debug('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.');
|
|
}
|
|
}
|
|
|
|
private async runApplyDirectoryTemplate(): Promise<void> {
|
|
const target = this.app.workspace.getActiveFile();
|
|
if (!target) {
|
|
new Notice('No active file.');
|
|
return;
|
|
}
|
|
if (!this.settings.perplexityApiKey) {
|
|
new Notice('Perplexity API key is not set. Configure it in perplexed settings.');
|
|
return;
|
|
}
|
|
|
|
const root = this.settings.directoryTemplatesRoot;
|
|
const all = listDirectoryTemplates(this.app, root);
|
|
if (all.length === 0) {
|
|
new Notice(`No templates found under "${root}".`);
|
|
return;
|
|
}
|
|
|
|
const matching = all.filter(t => pathMatchesGlobs(target.path, t.appliesToPaths));
|
|
if (matching.length === 0) {
|
|
new Notice("No template's applies-to-paths matches this file.");
|
|
return;
|
|
}
|
|
|
|
const dirSettings: DirectoryTemplateSettings = {
|
|
perplexityApiKey: this.settings.perplexityApiKey,
|
|
perplexityEndpoint: this.settings.perplexityEndpoint,
|
|
templatesRoot: this.settings.directoryTemplatesRoot,
|
|
frontmatterWhitelist: this.settings.directoryTemplatesFrontmatterWhitelist,
|
|
requestTimeoutMs: this.settings.directoryTemplatesRequestTimeoutMs,
|
|
};
|
|
|
|
new DirectoryTemplatePickerModal(this.app, matching, (chosen) => {
|
|
void (async () => {
|
|
const parsed = await loadDirectoryTemplate(this.app, chosen.file);
|
|
if (!parsed) {
|
|
new Notice('Template parse error: missing or malformed cft block.');
|
|
return;
|
|
}
|
|
await applyDirectoryTemplate(this.app, dirSettings, target, parsed);
|
|
})();
|
|
}).open();
|
|
}
|
|
|
|
private runApplyDirectoryTemplateBatch(): void {
|
|
if (!this.settings.perplexityApiKey) {
|
|
new Notice('Perplexity API key is not set. Configure it in perplexed settings.');
|
|
return;
|
|
}
|
|
|
|
new FolderPickerModal(this.app, (folder) => {
|
|
void (async () => {
|
|
const folderPath = folder.path;
|
|
const filesInFolder = listMarkdownFilesInFolder(this.app, folderPath);
|
|
if (filesInFolder.length === 0) {
|
|
new Notice(`No markdown files in "${folderPath || '/'}".`);
|
|
return;
|
|
}
|
|
|
|
const all = listDirectoryTemplates(this.app, this.settings.directoryTemplatesRoot);
|
|
const matchingTemplates = all.filter(t =>
|
|
filesInFolder.some(f => pathMatchesGlobs(f.path, t.appliesToPaths))
|
|
);
|
|
if (matchingTemplates.length === 0) {
|
|
new Notice('No template matches any file in this folder.');
|
|
return;
|
|
}
|
|
|
|
new DirectoryTemplatePickerModal(this.app, matchingTemplates, (chosen) => {
|
|
void (async () => {
|
|
const parsed = await loadDirectoryTemplate(this.app, chosen.file);
|
|
if (!parsed) {
|
|
new Notice('Template parse error: missing or malformed cft block.');
|
|
return;
|
|
}
|
|
|
|
const filesForThisTemplate = filesInFolder.filter(f =>
|
|
pathMatchesGlobs(f.path, chosen.appliesToPaths)
|
|
);
|
|
|
|
let fillCount = 0;
|
|
let appendCount = 0;
|
|
for (const f of filesForThisTemplate) {
|
|
const content = await this.app.vault.cachedRead(f);
|
|
const afterFm = content.replace(/^---\n[\s\S]*?\n---\n?/, '');
|
|
if (afterFm.trim().length === 0) fillCount++;
|
|
else appendCount++;
|
|
}
|
|
|
|
const dirSettings: DirectoryTemplateSettings = {
|
|
perplexityApiKey: this.settings.perplexityApiKey,
|
|
perplexityEndpoint: this.settings.perplexityEndpoint,
|
|
templatesRoot: this.settings.directoryTemplatesRoot,
|
|
frontmatterWhitelist: this.settings.directoryTemplatesFrontmatterWhitelist,
|
|
requestTimeoutMs: this.settings.directoryTemplatesRequestTimeoutMs,
|
|
};
|
|
|
|
new BatchConfirmModal(this.app, {
|
|
folderPath,
|
|
templateTitle: chosen.title,
|
|
fileCount: filesForThisTemplate.length,
|
|
fillCount,
|
|
appendCount,
|
|
}, () => {
|
|
void this.executeBatch(dirSettings, parsed, filesForThisTemplate);
|
|
}).open();
|
|
})();
|
|
}).open();
|
|
})();
|
|
}).open();
|
|
}
|
|
|
|
private async executeBatch(
|
|
dirSettings: DirectoryTemplateSettings,
|
|
parsed: ParsedTemplate,
|
|
files: TFile[],
|
|
): Promise<void> {
|
|
this.batchCancelled = false;
|
|
const progressNotice = new Notice(`Batch starting on ${files.length.toString()} files…`, 0);
|
|
|
|
try {
|
|
const result = await applyDirectoryTemplateBatch(
|
|
this.app,
|
|
dirSettings,
|
|
files,
|
|
parsed,
|
|
(p) => {
|
|
progressNotice.setMessage(
|
|
`Applying ${p.current.toString()}/${p.total.toString()}: ${p.file.basename}`
|
|
);
|
|
},
|
|
() => this.batchCancelled,
|
|
);
|
|
|
|
const summary = [
|
|
`Batch ${result.cancelled ? 'cancelled' : 'complete'}.`,
|
|
`Filled: ${result.appliedFill.toString()}`,
|
|
`Appended: ${result.appliedAppend.toString()}`,
|
|
`Errored: ${result.errored.toString()}`,
|
|
].join(' ');
|
|
new Notice(summary, 8000);
|
|
|
|
if (result.errors.length > 0) {
|
|
console.warn('Directory-template batch errors:', result.errors);
|
|
}
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
new Notice(`Batch failed: ${msg}`);
|
|
} finally {
|
|
progressNotice.hide();
|
|
this.batchCancelled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
class PerplexedSettingTab extends PluginSettingTab {
|
|
plugin: PerplexedPlugin;
|
|
|
|
constructor(app: App, plugin: PerplexedPlugin) {
|
|
super(app, plugin);
|
|
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();
|
|
|
|
// Perplexity Section
|
|
new Setting(containerEl).setName("Perplexity (remote service)").setHeading();
|
|
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();
|
|
})
|
|
);
|
|
|
|
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')
|
|
.setDesc('JSON template for perplexity API requests');
|
|
|
|
// Create a textarea element for Perplexity
|
|
const perplexityTextArea = activeDocument.createEl('textarea');
|
|
perplexityTextArea.rows = 10;
|
|
perplexityTextArea.cols = 50;
|
|
perplexityTextArea.addClass('perplexed-json-textarea');
|
|
perplexityTextArea.placeholder = 'Enter perplexity JSON request template...';
|
|
|
|
// Set initial value if it exists
|
|
if (this.plugin.settings.perplexityRequestTemplate) {
|
|
try {
|
|
const config: unknown = JSON.parse(this.plugin.settings.perplexityRequestTemplate);
|
|
perplexityTextArea.value = JSON.stringify(config, null, 2);
|
|
} catch {
|
|
// If not valid JSON, use as is
|
|
perplexityTextArea.value = this.plugin.settings.perplexityRequestTemplate;
|
|
}
|
|
}
|
|
|
|
// Add input event listener for Perplexity
|
|
perplexityTextArea.addEventListener('input', () => void (async () => {
|
|
this.plugin.settings.perplexityRequestTemplate = perplexityTextArea.value;
|
|
await this.plugin.saveSettings();
|
|
})());
|
|
|
|
// Add the textarea to the setting
|
|
perplexityJsonSetting.settingEl.appendChild(perplexityTextArea);
|
|
|
|
// Claude (Anthropic) Section
|
|
new Setting(containerEl).setName("Claude (Anthropic)").setHeading();
|
|
containerEl.createEl('p', {
|
|
text: 'Configure Claude API access. Web-search citations supported in this iteration; document-grounded citations are deferred.',
|
|
cls: 'setting-item-description'
|
|
});
|
|
|
|
new Setting(containerEl)
|
|
.setName('Anthropic API key')
|
|
.setDesc('Your Anthropic API key. Read from ANTHROPIC_API_KEY in .env if set; can be overridden here.')
|
|
.addText(text => text
|
|
.setPlaceholder('Sk-ant-...')
|
|
.setValue(this.plugin.settings.anthropicApiKey)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.anthropicApiKey = value;
|
|
await this.plugin.saveSettings();
|
|
}));
|
|
|
|
new Setting(containerEl)
|
|
.setName('Default Claude model')
|
|
.setDesc('Default model used by the ask Claude command. Recommended: Claude-opus-4-7.')
|
|
.addDropdown(dropdown => dropdown
|
|
.addOption('claude-opus-4-7', 'Claude-opus-4-7 (recommended)')
|
|
.addOption('claude-opus-4-6', 'Claude-opus-4-6')
|
|
.addOption('claude-sonnet-4-6', 'Claude-sonnet-4-6')
|
|
.addOption('claude-haiku-4-5', 'Claude-haiku-4-5')
|
|
.setValue(this.plugin.settings.claudeDefaultModel)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.claudeDefaultModel = value;
|
|
await this.plugin.saveSettings();
|
|
}));
|
|
|
|
// Perplexica / Vane Section
|
|
new Setting(containerEl).setName("Perplexica / vane (self-hosted)").setHeading();
|
|
containerEl.createEl('p', {
|
|
text: 'Configure settings for your local perplexica / vane installation',
|
|
cls: 'setting-item-description'
|
|
});
|
|
|
|
new Setting(containerEl)
|
|
.setName('Endpoint')
|
|
.setDesc('API endpoint for your local perplexica / vane 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 / vane 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 / vane API requests');
|
|
|
|
// Create a textarea element for Perplexica
|
|
const perplexicaTextArea = activeDocument.createEl('textarea');
|
|
perplexicaTextArea.rows = 10;
|
|
perplexicaTextArea.cols = 50;
|
|
perplexicaTextArea.addClass('perplexed-json-textarea');
|
|
perplexicaTextArea.placeholder = 'Enter perplexica JSON request template...';
|
|
|
|
// Set initial value if it exists
|
|
if (this.plugin.settings.requestBodyTemplate) {
|
|
try {
|
|
const config: unknown = JSON.parse(this.plugin.settings.requestBodyTemplate);
|
|
perplexicaTextArea.value = JSON.stringify(config, null, 2);
|
|
} catch {
|
|
// If not valid JSON, use as is
|
|
perplexicaTextArea.value = this.plugin.settings.requestBodyTemplate;
|
|
}
|
|
}
|
|
|
|
// Add input event listener for Perplexica
|
|
perplexicaTextArea.addEventListener('input', () => void (async () => {
|
|
this.plugin.settings.requestBodyTemplate = perplexicaTextArea.value;
|
|
await this.plugin.saveSettings();
|
|
})());
|
|
|
|
// Add the textarea to the setting
|
|
perplexicaJsonSetting.settingEl.appendChild(perplexicaTextArea);
|
|
|
|
// LM Studio Section
|
|
new Setting(containerEl).setName("Lm studio (local models)").setHeading();
|
|
containerEl.createEl('p', {
|
|
text: 'Configure settings for your local lm studio installation with loaded models',
|
|
cls: 'setting-item-description'
|
|
});
|
|
|
|
new Setting(containerEl)
|
|
.setName('Endpoint')
|
|
.setDesc('API endpoint for your local lm studio instance')
|
|
.addText(text => text
|
|
.setPlaceholder('HTTP://localhost:1234/v1/chat/completions')
|
|
.setValue(this.plugin.settings.lmStudioEndpoint)
|
|
.onChange(async (value: string) => {
|
|
this.plugin.settings.lmStudioEndpoint = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
new Setting(containerEl)
|
|
.setName('Default model')
|
|
.setDesc('Default model name for lm studio to use')
|
|
.addText(text => text
|
|
.setPlaceholder('Ibm/granite-3.2-8b')
|
|
.setValue(this.plugin.settings.defaultLMStudioModel)
|
|
.onChange(async (value: string) => {
|
|
this.plugin.settings.defaultLMStudioModel = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// 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 = activeDocument.createEl('textarea');
|
|
lmStudioTextArea.rows = 10;
|
|
lmStudioTextArea.cols = 50;
|
|
lmStudioTextArea.addClass('perplexed-json-textarea');
|
|
lmStudioTextArea.placeholder = 'Enter lm studio JSON request template...';
|
|
|
|
// Set initial value if it exists
|
|
if (this.plugin.settings.lmStudioRequestTemplate) {
|
|
try {
|
|
const config: unknown = JSON.parse(this.plugin.settings.lmStudioRequestTemplate);
|
|
lmStudioTextArea.value = JSON.stringify(config, null, 2);
|
|
} catch {
|
|
// If not valid JSON, use as is
|
|
lmStudioTextArea.value = this.plugin.settings.lmStudioRequestTemplate;
|
|
}
|
|
}
|
|
|
|
// Add input event listener for LM Studio
|
|
lmStudioTextArea.addEventListener('input', () => void (async () => {
|
|
this.plugin.settings.lmStudioRequestTemplate = lmStudioTextArea.value;
|
|
await this.plugin.saveSettings();
|
|
})());
|
|
|
|
// Add the textarea to the setting
|
|
lmStudioJsonSetting.settingEl.appendChild(lmStudioTextArea);
|
|
|
|
// Prompts Section
|
|
new Setting(containerEl).setName("Prompts & text configuration").setHeading();
|
|
containerEl.createEl('p', {
|
|
text: 'Customize all prompts, placeholders, descriptions, and messages used throughout the plugin',
|
|
cls: 'setting-item-description'
|
|
});
|
|
|
|
// System Prompts
|
|
new Setting(containerEl).setName("System prompts").setHeading();
|
|
|
|
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;
|
|
const promptsService = this.plugin.getPromptsService();
|
|
if (promptsService) {
|
|
promptsService.updateSettings(this.plugin.settings.prompts);
|
|
}
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
new Setting(containerEl)
|
|
.setName('Perplexica / vane system prompt')
|
|
.setDesc('System prompt used for perplexica / vane requests')
|
|
.addTextArea(text => text
|
|
.setPlaceholder('Enter system prompt for perplexica / vane...')
|
|
.setValue(this.plugin.settings.prompts.perplexicaSystemPrompt)
|
|
.onChange(async (value: string) => {
|
|
this.plugin.settings.prompts.perplexicaSystemPrompt = value;
|
|
const promptsService = this.plugin.getPromptsService();
|
|
if (promptsService) {
|
|
promptsService.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;
|
|
const promptsService = this.plugin.getPromptsService();
|
|
if (promptsService) {
|
|
promptsService.updateSettings(this.plugin.settings.prompts);
|
|
}
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Placeholder Text
|
|
new Setting(containerEl).setName("Placeholder text").setHeading();
|
|
|
|
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;
|
|
const promptsService = this.plugin.getPromptsService();
|
|
if (promptsService) {
|
|
promptsService.updateSettings(this.plugin.settings.prompts);
|
|
}
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
new Setting(containerEl)
|
|
.setName('Perplexica / vane query placeholder')
|
|
.setDesc('Placeholder text for perplexica / vane 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;
|
|
const promptsService = this.plugin.getPromptsService();
|
|
if (promptsService) {
|
|
promptsService.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;
|
|
const promptsService = this.plugin.getPromptsService();
|
|
if (promptsService) {
|
|
promptsService.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;
|
|
const promptsService = this.plugin.getPromptsService();
|
|
if (promptsService) {
|
|
promptsService.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.updatePromptsService();
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
// Article Generator Template
|
|
new Setting(containerEl).setName("Article generator template").setHeading();
|
|
|
|
const articleTemplateSetting = new Setting(containerEl)
|
|
.setName('Article generator template')
|
|
.setDesc('Template for generating articles. Use {TERM} as placeholder for the term.');
|
|
|
|
const articleTemplateTextArea = activeDocument.createEl('textarea');
|
|
articleTemplateTextArea.rows = 15;
|
|
articleTemplateTextArea.cols = 50;
|
|
articleTemplateTextArea.addClass('perplexed-json-textarea is-tall');
|
|
articleTemplateTextArea.placeholder = 'Enter article generator template...';
|
|
articleTemplateTextArea.value = this.plugin.settings.prompts.articleGeneratorTemplate;
|
|
|
|
articleTemplateTextArea.addEventListener('input', () => void (async () => {
|
|
this.plugin.settings.prompts.articleGeneratorTemplate = articleTemplateTextArea.value;
|
|
this.updatePromptsService();
|
|
await this.plugin.saveSettings();
|
|
})());
|
|
|
|
articleTemplateSetting.settingEl.appendChild(articleTemplateTextArea);
|
|
|
|
// Deep Research Article Generator Template
|
|
const deepResearchTemplateSetting = new Setting(containerEl)
|
|
.setName('Deep research article generator template')
|
|
.setDesc('Template for generating articles with Deep Research model. Use {TERM} as placeholder for the term.');
|
|
|
|
const deepResearchTemplateTextArea = activeDocument.createEl('textarea');
|
|
deepResearchTemplateTextArea.rows = 20;
|
|
deepResearchTemplateTextArea.cols = 50;
|
|
deepResearchTemplateTextArea.addClass('perplexed-json-textarea is-tall');
|
|
deepResearchTemplateTextArea.placeholder = 'Enter deep research article generator template...';
|
|
deepResearchTemplateTextArea.value = this.plugin.settings.prompts.deepResearchArticleTemplate;
|
|
|
|
deepResearchTemplateTextArea.addEventListener('input', () => void (async () => {
|
|
this.plugin.settings.prompts.deepResearchArticleTemplate = deepResearchTemplateTextArea.value;
|
|
this.updatePromptsService();
|
|
await this.plugin.saveSettings();
|
|
})());
|
|
|
|
deepResearchTemplateSetting.settingEl.appendChild(deepResearchTemplateTextArea);
|
|
|
|
// Image Prompts
|
|
new Setting(containerEl).setName("Image prompts").setHeading();
|
|
|
|
const imagePromptsSetting = new Setting(containerEl)
|
|
.setName('Image references prompt')
|
|
.setDesc('Prompt added to queries when images are enabled');
|
|
|
|
const imagePromptsTextArea = activeDocument.createEl('textarea');
|
|
imagePromptsTextArea.rows = 8;
|
|
imagePromptsTextArea.cols = 50;
|
|
imagePromptsTextArea.addClass('perplexed-json-textarea');
|
|
imagePromptsTextArea.placeholder = 'Enter image references prompt...';
|
|
imagePromptsTextArea.value = this.plugin.settings.prompts.imageReferencesPrompt;
|
|
|
|
imagePromptsTextArea.addEventListener('input', () => void (async () => {
|
|
this.plugin.settings.prompts.imageReferencesPrompt = imagePromptsTextArea.value;
|
|
this.updatePromptsService();
|
|
await this.plugin.saveSettings();
|
|
})());
|
|
|
|
imagePromptsSetting.settingEl.appendChild(imagePromptsTextArea);
|
|
|
|
// Text Enhancement Prompt
|
|
new Setting(containerEl).setName("Text enhancement").setHeading();
|
|
|
|
const enhancePromptSetting = new Setting(containerEl)
|
|
.setName('Text enhancement prompt')
|
|
.setDesc('Template for enhancing selected text. Use {TEXT} as placeholder for the selected text.');
|
|
|
|
const enhancePromptTextArea = activeDocument.createEl('textarea');
|
|
enhancePromptTextArea.rows = 10;
|
|
enhancePromptTextArea.cols = 50;
|
|
enhancePromptTextArea.addClass('perplexed-json-textarea');
|
|
enhancePromptTextArea.placeholder = 'Enter text enhancement prompt template...';
|
|
enhancePromptTextArea.value = this.plugin.settings.prompts.enhancePrompt;
|
|
|
|
enhancePromptTextArea.addEventListener('input', () => void (async () => {
|
|
this.plugin.settings.prompts.enhancePrompt = enhancePromptTextArea.value;
|
|
this.updatePromptsService();
|
|
await this.plugin.saveSettings();
|
|
})());
|
|
|
|
enhancePromptSetting.settingEl.appendChild(enhancePromptTextArea);
|
|
|
|
// Text Enhancement with Images Prompt
|
|
const enhanceWithImagesPromptSetting = new Setting(containerEl)
|
|
.setName('Related images prompt')
|
|
.setDesc('Template for requesting related images for selected text. Use {TEXT} as placeholder for the selected text.');
|
|
|
|
const enhanceWithImagesPromptTextArea = activeDocument.createEl('textarea');
|
|
enhanceWithImagesPromptTextArea.rows = 10;
|
|
enhanceWithImagesPromptTextArea.cols = 50;
|
|
enhanceWithImagesPromptTextArea.addClass('perplexed-json-textarea');
|
|
enhanceWithImagesPromptTextArea.placeholder = 'Enter related images prompt template...';
|
|
enhanceWithImagesPromptTextArea.value = this.plugin.settings.prompts.enhanceWithImagesPrompt;
|
|
|
|
enhanceWithImagesPromptTextArea.addEventListener('input', () => void (async () => {
|
|
this.plugin.settings.prompts.enhanceWithImagesPrompt = enhanceWithImagesPromptTextArea.value;
|
|
this.updatePromptsService();
|
|
await this.plugin.saveSettings();
|
|
})());
|
|
|
|
enhanceWithImagesPromptSetting.settingEl.appendChild(enhanceWithImagesPromptTextArea);
|
|
|
|
// Directory Templates Section (v0.1 spike)
|
|
new Setting(containerEl).setName('Directory templates').setHeading();
|
|
containerEl.createEl('p', {
|
|
text: 'Apply a template (heading skeleton + per-section bullets) to fill a file via perplexity deep research. Templates live in a vault folder and are matched to files by glob.',
|
|
cls: 'setting-item-description'
|
|
});
|
|
|
|
new Setting(containerEl)
|
|
.setName('Templates root')
|
|
.setDesc('Vault-relative folder where directory templates live.')
|
|
.addText(text => text
|
|
.setPlaceholder('Zz-cf-lib/templates')
|
|
.setValue(this.plugin.settings.directoryTemplatesRoot)
|
|
.onChange(async (value: string) => {
|
|
this.plugin.settings.directoryTemplatesRoot = value.trim();
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
new Setting(containerEl)
|
|
.setName('Frontmatter whitelist')
|
|
.setDesc('Comma-separated list of frontmatter keys passed to the template as {{frontmatter}}. Other keys are filtered out.')
|
|
.addText(text => text
|
|
.setPlaceholder('Title, og_description, tags, og_image')
|
|
.setValue(this.plugin.settings.directoryTemplatesFrontmatterWhitelist.join(', '))
|
|
.onChange(async (value: string) => {
|
|
this.plugin.settings.directoryTemplatesFrontmatterWhitelist = value
|
|
.split(',')
|
|
.map(s => s.trim())
|
|
.filter(s => s.length > 0);
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
new Setting(containerEl)
|
|
.setName('Request timeout (ms)')
|
|
.setDesc('Maximum time to wait for the perplexity deep research response. Default 300000 (5 min).')
|
|
.addText(text => text
|
|
.setPlaceholder('300000')
|
|
.setValue(String(this.plugin.settings.directoryTemplatesRequestTimeoutMs))
|
|
.onChange(async (value: string) => {
|
|
const n = parseInt(value, 10);
|
|
if (!isNaN(n) && n > 0) {
|
|
this.plugin.settings.directoryTemplatesRequestTimeoutMs = n;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
|
|
new Setting(containerEl)
|
|
.setName('Re-seed templates')
|
|
.setDesc('Write any shipped template files that are missing from the templates root. Existing files are never overwritten — to reset a template to its shipped default, delete the file first then re-seed.')
|
|
.addButton(btn => btn
|
|
.setButtonText('Re-seed')
|
|
.onClick(async () => {
|
|
try {
|
|
await reSeedMissingFiles(this.plugin.app, this.plugin.settings.directoryTemplatesRoot);
|
|
} catch (error) {
|
|
const msg = error instanceof Error ? error.message : String(error);
|
|
new Notice(`Re-seed failed: ${msg}`);
|
|
}
|
|
})
|
|
);
|
|
|
|
// Find images for selection
|
|
new Setting(containerEl).setName('Find images for selection').setHeading();
|
|
containerEl.createEl('p', {
|
|
text: 'Highlight a passage, run "find images for selection". The plugin asks perplexity for screenshots that visually illustrate the passage, prefers images on the entity\'s domain (frontmatter URL), and embeds them between paragraphs.',
|
|
cls: 'setting-item-description'
|
|
});
|
|
|
|
new Setting(containerEl)
|
|
.setName('Max images')
|
|
.setDesc('Maximum number of images to embed per invocation.')
|
|
.addText(text => text
|
|
.setPlaceholder('3')
|
|
.setValue(String(this.plugin.settings.findImagesMaxImages))
|
|
.onChange(async (value: string) => {
|
|
const n = parseInt(value, 10);
|
|
if (!isNaN(n) && n > 0) {
|
|
this.plugin.settings.findImagesMaxImages = n;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
})
|
|
);
|
|
}
|
|
}
|