diff --git a/plans/6.md b/plans/6.md new file mode 100644 index 0000000..fbd95ee --- /dev/null +++ b/plans/6.md @@ -0,0 +1,611 @@ +# Implementation Plan for Issue #6 + +## Issue Summary + +**Title**: Import prompts from remote url on page load + +**Description**: Allow users to set a remote URL that returns exported prompts so the app can automatically fetch them on page load. This enables prompt sharing and automatic distribution from a centralized source. + +**Author**: tomzx + +## Feasibility Assessment + +- **Status**: ✅ Feasible +- **Complexity**: Medium +- **Estimated Effort**: 4-6 hours + +**Assessment Rationale**: +- The codebase already has a robust `PromptsManager` with import/export functionality +- Settings system exists with localStorage persistence (`SettingsManager`) +- Manager pattern is well-established for adding new functionality +- Main architectural challenge is async fetching on initialization without blocking app load +- CORS must be handled (server must support CORS or be same-origin) + +## Proposed Solution + +### Overview + +Create a new `RemotePromptsManager` that: +1. Fetches prompts from configured remote URLs on page load +2. Caches fetched prompts with a configurable timeout (default: 1 hour) +3. Merges remote prompts with existing prompts using existing `PromptsManager.importPrompts()` with `replace=false` +4. Provides UI in Settings for managing remote sources (add/remove/enable/disable) +5. Handles network errors gracefully without blocking app initialization + +### Expected Remote Data Format + +The remote URL should return JSON with the following structure: + +```json +{ + "version": "1.0", + "name": "Optional collection name", + "description": "Optional description", + "prompts": [ + { + "name": "Prompt Name", + "prompt": "The prompt text with {placeholders}", + "triggerTiming": "custom", + "customDelay": "1s", + "keyboardShortcut": "", + "llmService": "", + "llmModel": "", + "actionType": "feedback" + } + ] +} +``` + +**Validation Rules**: +- Must be valid JSON +- Must have `prompts` array +- Each prompt must have `name` and `prompt` (required) +- Other fields will use defaults if missing +- Existing `PromptsManager.importPrompts()` handles validation + +### Configuration + +Users will configure remote sources in Settings with: +- **URL**: The remote endpoint to fetch +- **Name**: Human-readable label (e.g., "Team Prompts") +- **Enabled**: Whether to auto-fetch from this source + +Stored in `SettingsManager` as: +```javascript +remotePromptUrls: [ + { + url: "https://example.com/prompts.json", + name: "Team Prompts", + enabled: true, + lastFetch: "2025-12-08T12:00:00Z", + lastError: null + } +] +``` + +### Implementation Steps + +#### Step 1: Create RemotePromptsManager + +**New File**: `components/RemotePromptsManager.js` + +**Responsibilities**: +- Fetch prompts from remote URLs +- Cache fetched prompts with timeout +- Coordinate with PromptsManager for importing +- Track fetch history and errors + +**Key Methods**: +```javascript +class RemotePromptsManager { + constructor(promptsManager, settingsManager) { + this.promptsManager = promptsManager; + this.settingsManager = settingsManager; + this.cacheTimeout = 3600000; // 1 hour default + this.storageKey = 'ai_editor_remote_prompts_cache'; + } + + async initialize() { + // Auto-fetch from all enabled sources + await this.fetchAllRemotePrompts(); + } + + async fetchAllRemotePrompts() { + const sources = this.getEnabledSources(); + const results = await Promise.allSettled( + sources.map(s => this.fetchFromSource(s)) + ); + // Handle results and show notifications + } + + async fetchFromSource(source) { + // Check cache first + const cached = this.getCachedData(source); + if (cached) return cached; + + // Fetch from URL + const response = await fetch(source.url); + if (!response.ok) throw new Error(...); + + const data = await response.json(); + // Validate structure + + // Import via PromptsManager + const result = await this.importFetchedPrompts(data.prompts, source); + + // Cache the result + this.setCachedData(source, data); + + // Update source metadata + this.updateSourceMetadata(source, { lastFetch: Date.now(), lastError: null }); + + return result; + } + + getCachedData(source) { + const cache = JSON.parse(localStorage.getItem(this.storageKey) || '{}'); + const cached = cache[source.url]; + if (!cached) return null; + + const age = Date.now() - cached.timestamp; + if (age > this.cacheTimeout) { + delete cache[source.url]; + localStorage.setItem(this.storageKey, JSON.stringify(cache)); + return null; + } + + return cached.data; + } + + setCachedData(source, data) { + const cache = JSON.parse(localStorage.getItem(this.storageKey) || '{}'); + cache[source.url] = { + data, + timestamp: Date.now(), + source: source.name + }; + localStorage.setItem(this.storageKey, JSON.stringify(cache)); + } + + clearCache() { + localStorage.removeItem(this.storageKey); + } + + getSources() { + return this.settingsManager.getSetting('remotePromptUrls') || []; + } + + getEnabledSources() { + return this.getSources().filter(s => s.enabled); + } +} +``` + +**Files to create**: `components/RemotePromptsManager.js` + +#### Step 2: Update SettingsManager + +**File**: `components/SettingsManager.js` + +**Changes**: +1. Add `remotePromptUrls` to `defaultSettings` (empty array) +2. Add methods for managing remote sources: + - `getRemotePromptUrls()` + - `addRemotePromptUrl(url, name)` + - `removeRemotePromptUrl(url)` + - `updateRemotePromptUrl(url, updates)` +3. Add UI setup methods in `setupUI()` or new `setupRemotePromptsUI()` + +**Code additions**: +```javascript +// In defaultSettings +remotePromptUrls: [] + +// New methods +getRemotePromptUrls() { + return this.settings.remotePromptUrls || []; +} + +addRemotePromptUrl(url, name) { + const sources = this.getRemotePromptUrls(); + if (sources.find(s => s.url === url)) { + throw new Error('Source with this URL already exists'); + } + const newSource = { + url, + name: name || url, + enabled: true, + lastFetch: null, + lastError: null, + addedAt: new Date().toISOString() + }; + this.settings.remotePromptUrls = [...sources, newSource]; + this.saveSettings(); + this.notifyChange('remotePromptUrls', this.settings.remotePromptUrls); + return newSource; +} + +removeRemotePromptUrl(url) { + const sources = this.getRemotePromptUrls().filter(s => s.url !== url); + this.settings.remotePromptUrls = sources; + this.saveSettings(); + this.notifyChange('remotePromptUrls', this.settings.remotePromptUrls); +} + +updateRemotePromptUrl(url, updates) { + const sources = this.getRemotePromptUrls(); + const index = sources.findIndex(s => s.url === url); + if (index === -1) throw new Error('Source not found'); + sources[index] = { ...sources[index], ...updates }; + this.settings.remotePromptUrls = sources; + this.saveSettings(); + this.notifyChange('remotePromptUrls', this.settings.remotePromptUrls); +} +``` + +**Files to modify**: `components/SettingsManager.js` + +#### Step 3: Update Main App Initialization + +**File**: `script.js` + +**Changes**: Initialize `RemotePromptsManager` after other managers + +**Location**: In `initializeManagers()`, after `importExportManager` (line ~110) + +```javascript +initializeManagers() { + // ... existing managers ... + this.importExportManager = new ImportExportManager(); + + // NEW: Initialize remote prompts manager + this.remotePromptsManager = new RemotePromptsManager( + this.promptsManager, + this.settingsManager + ); + + // ... rest of initialization ... +} + +// In the setTimeout block (after other managers initialize) +setTimeout(async () => { + this.settingsManager.setupUI(); + await this.usageTracker.initialize(); + await this.historyManager.initialize(); + await this.importExportManager.initialize(); + this.setupImportExportUI(); + + // NEW: Initialize remote prompts + await this.remotePromptsManager.initialize(); + this.setupRemotePromptsUI(); +}, 0); +``` + +**Add setup method**: +```javascript +setupRemotePromptsUI() { + const addBtn = document.getElementById('addRemotePromptSourceBtn'); + const fetchBtn = document.getElementById('fetchRemotePromptsBtn'); + const clearCacheBtn = document.getElementById('clearRemotePromptsCacheBtn'); + + if (addBtn) { + addBtn.addEventListener('click', () => this.showAddRemoteSourceDialog()); + } + if (fetchBtn) { + fetchBtn.addEventListener('click', () => this.remotePromptsManager.fetchAllRemotePrompts()); + } + if (clearCacheBtn) { + clearCacheBtn.addEventListener('click', () => { + this.remotePromptsManager.clearCache(); + if (window.app?.notificationManager) { + window.app.notificationManager.success('Remote prompts cache cleared'); + } + }); + } + + this.renderRemoteSourcesList(); +} + +renderRemoteSourcesList() { + const sources = this.remotePromptsManager.getSources(); + const container = document.getElementById('remotePromptsList'); + if (!container) return; + + // Render list of sources with status indicators + // Similar to renderCustomServicesList() in SettingsManager +} +``` + +**Files to modify**: `script.js` + +#### Step 4: Add UI to index.html + +**File**: `index.html` + +**Location**: In Settings tab content, after Custom LLM Services section (after line ~400) + +**Add new section**: +```html +
No remote sources configured.
+
+ Expected format: JSON with a prompts array.
+ Each prompt needs name and prompt fields.
+