diff --git a/package-lock.json b/package-lock.json index e8fc66146025b19472c92b66854eacd847aa7cfc..3f82e64efe1419c5eda641b4060905c4008081b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@peerverity/ai-delegate", - "version": "0.2.4", + "version": "0.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@peerverity/ai-delegate", - "version": "0.2.4", + "version": "0.3.3", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.71.0", @@ -421,7 +421,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -448,7 +447,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -604,7 +602,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -2099,7 +2096,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2172,7 +2168,6 @@ "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -2222,7 +2217,6 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", diff --git a/package.json b/package.json index 38a7c1fb5755ca0db8e857ec889e285fb19e2651..02b2b156fa9a4d6473ab7734fc24efef0e9739d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@peerverity/ai-delegate", - "version": "0.3.4", + "version": "0.4.0", "description": "AI request manager with credential management and weight-based model selection", "main": "dist/ai-delegate.js", "types": "dist/index.d.ts", diff --git a/src/core/AiDelegate.ts b/src/core/AiDelegate.ts index 77a5c647aee1d2cb3a6098b93f622465629e2397..e4a3881a3e505bdcde269524b212b3f3191011b5 100644 --- a/src/core/AiDelegate.ts +++ b/src/core/AiDelegate.ts @@ -597,10 +597,11 @@ export class AiDelegate { /** * Open the prompt editor UI + * @param options - Optional configuration for filtering/highlighting prompts */ - async openPromptEditor(): Promise { + async openPromptEditor(options?: { filterTags?: string[] }): Promise { await this.ensureWorkerInitialized(); - await PromptEditorUI.open(this); + await PromptEditorUI.open(this, options); } /** diff --git a/src/index.ts b/src/index.ts index 2962b3d5fb6cf9393df660bcd0f524f9be38657c..b315318b52b46622980cf5db22d0428ecc724486 100644 --- a/src/index.ts +++ b/src/index.ts @@ -212,9 +212,10 @@ const api = { /** * Open the prompt editor UI + * @param options - Optional configuration for filtering/highlighting prompts */ - openPromptEditor: (): Promise => { - return AiDelegate.getInstance().openPromptEditor(); + openPromptEditor: (options?: { filterTags?: string[] }): Promise => { + return AiDelegate.getInstance().openPromptEditor(options); } }; diff --git a/src/prompts/TemplateEngine.ts b/src/prompts/TemplateEngine.ts index 8fc6c3f9ec8e4a3d39648b38536609828fa9d56e..2263abf609ed2133d1c91119b022652bb78d94b2 100644 --- a/src/prompts/TemplateEngine.ts +++ b/src/prompts/TemplateEngine.ts @@ -78,8 +78,10 @@ export class TemplateEngine { * * Checks: * - All params referenced in template are defined - * - All defined params are referenced in template * - Role is valid (user, assistant, system) + * + * Note: Defined params that are not used in the template are allowed. + * This supports user customization where they may remove parameters. */ validateDefinition(prompt: PromptDefinition, promptName?: string): void { const templateParams = this.extractParams(prompt.template); @@ -92,12 +94,9 @@ export class TemplateEngine { } } - // Check for unreferenced params (defined but not used in template) - for (const param of definedParams) { - if (!templateParams.includes(param)) { - throw new UnreferencedParamError(param, promptName); - } - } + // Note: We intentionally do NOT check for unreferenced params. + // Users may customize prompts and remove parameter placeholders, + // and the system should still work with the remaining params. // Validate role if (prompt.role && !['user', 'assistant', 'system'].includes(prompt.role)) { @@ -110,7 +109,7 @@ export class TemplateEngine { * * @param template - Template string with {param} placeholders * @param paramDefs - Parameter definitions (for defaults) - * @param values - Parameter values to substitute + * @param values - Parameter values to substitute (extra values are ignored) * @returns Rendered template string */ render( @@ -133,12 +132,9 @@ export class TemplateEngine { } } - // Check for unknown params in values - for (const param of Object.keys(values)) { - if (!templateParams.includes(param)) { - throw new UnknownParamError(param); - } - } + // Note: We intentionally do NOT check for unknown params in values. + // Users may customize prompts and remove parameter placeholders, + // and the caller may still pass all originally defined params. // Substitute parameters return template.replace(PARAM_REGEX, (_, paramName) => { diff --git a/src/types/prompts.ts b/src/types/prompts.ts index 04d45bfc292196afbbec0381ac4318519475ac44..a35c34a2596448b923221430bdbfddc6735d5894 100644 --- a/src/types/prompts.ts +++ b/src/types/prompts.ts @@ -31,6 +31,10 @@ export interface PromptDefinition { role?: MessageRole; /** Default model weight for this prompt (defaults to 'moderate') */ weight?: Weight; + /** Human-readable description of what this prompt does */ + description?: string; + /** Tags for categorizing/filtering prompts (e.g., ['conversational-assistant', 'results-analysis']) */ + tags?: string[]; } /** diff --git a/src/ui/PromptEditorUI.ts b/src/ui/PromptEditorUI.ts index b5108b49814f533dade55215415ca550e4d17dc7..dbee7d7702802d202963cdbe208e1698052a8f72 100644 --- a/src/ui/PromptEditorUI.ts +++ b/src/ui/PromptEditorUI.ts @@ -23,6 +23,14 @@ interface PromptAPI { * - Resetting modified prompts to upstream * - Deleting custom prompts */ +/** + * Options for opening the prompt editor + */ +export interface PromptEditorOptions { + /** Filter to show only prompts with these tags */ + filterTags?: string[]; +} + export class PromptEditorUI { private static isModalOpen = false; private static api: PromptAPI | null = null; @@ -30,16 +38,20 @@ export class PromptEditorUI { private static overlay: HTMLDivElement | null = null; private static originalTemplate: string = ''; private static originalWeight: string = ''; + private static filterTags: string[] | null = null; /** * Open the prompt editor + * @param api - The prompt API interface + * @param options - Optional configuration for filtering prompts */ - static async open(api: PromptAPI): Promise { + static async open(api: PromptAPI, options?: PromptEditorOptions): Promise { if (this.isModalOpen) { return; } this.api = api; + this.filterTags = options?.filterTags || null; this.isModalOpen = true; // Create modal overlay @@ -88,13 +100,36 @@ export class PromptEditorUI { this.api = null; } + /** + * Check if a prompt matches the current filter tags + */ + private static promptMatchesFilter(prompt: StoredPrompt): boolean { + if (!this.filterTags || this.filterTags.length === 0) { + return true; // No filter, show all + } + const promptTags = prompt.tags || []; + return this.filterTags.some(tag => promptTags.includes(tag)); + } + /** * Render the prompt list view */ private static async renderPromptList(modal: HTMLDivElement): Promise { if (!this.api) return; - const prompts = await this.api.listPrompts(); + const allPrompts = await this.api.listPrompts(); + + // Filter prompts if filterTags is set + const prompts = this.filterTags + ? allPrompts.filter(p => this.promptMatchesFilter(p)) + : allPrompts; + + const filterInfo = this.filterTags + ? `
+ Showing prompts for: ${this.filterTags.join(', ')} + +
` + : ''; modal.innerHTML = `
@@ -105,9 +140,11 @@ export class PromptEditorUI {
+ ${filterInfo} + ${prompts.length === 0 ? `
- No prompts registered yet. + ${this.filterTags ? 'No prompts match the current filter.' : 'No prompts registered yet.'}
` : `
@@ -120,6 +157,12 @@ export class PromptEditorUI { // Setup event handlers modal.querySelector('#ai-delegate-close-btn')?.addEventListener('click', () => this.close()); + // Clear filter button + modal.querySelector('#ai-delegate-clear-filter')?.addEventListener('click', () => { + this.filterTags = null; + this.renderPromptList(modal); + }); + // Setup prompt item click handlers prompts.forEach(prompt => { const item = modal.querySelector(`[data-prompt-name="${prompt.name}"]`); @@ -135,6 +178,18 @@ export class PromptEditorUI { ? 'Modified' : ''; + // Show description if available, otherwise fall back to template preview + const description = prompt.description + ? this.escapeHtml(prompt.description) + : `${this.escapeHtml(prompt.template.substring(0, 100))}${prompt.template.length > 100 ? '...' : ''}`; + + // Render tags if present + const tagsHtml = prompt.tags && prompt.tags.length > 0 + ? `
+ ${prompt.tags.map(tag => `${this.escapeHtml(tag)}`).join('')} +
` + : ''; + return `
v${this.escapeHtml(prompt.version)} ${modifiedBadge}
-
- ${this.escapeHtml(prompt.template.substring(0, 100))}${prompt.template.length > 100 ? '...' : ''} +
+ ${description}
+ ${tagsHtml}
`; } @@ -216,6 +272,22 @@ export class PromptEditorUI {
+ ${prompt.description ? ` +
+ +
${this.escapeHtml(prompt.description)}
+
+ ` : ''} + + ${prompt.tags && prompt.tags.length > 0 ? ` +
+ +
+ ${prompt.tags.map(tag => `${this.escapeHtml(tag)}`).join('')} +
+
+ ` : ''} +