diff --git a/css/style.css b/css/style.css index b20a4eb..2a09279 100644 --- a/css/style.css +++ b/css/style.css @@ -46,7 +46,11 @@ --randomizer-color-rgb: 156, 39, 176; --case-color: #9575cd; /* Indigo for case transformations */ --case-color-rgb: 149, 117, 205; - + --translate-color: #4dd0e1; /* Teal-cyan for translation */ + --translate-color-rgb: 77, 208, 225; + --promptcraft-color: #00ff41; /* Hacker green for promptcraft */ + --promptcraft-color-rgb: 0, 255, 65; + /* Active state gradient end colors */ --encoding-active-end: #9575cd; --cipher-active-end: #4db6ac; @@ -1379,6 +1383,10 @@ h1, h2, h3, h4, h5 { border-left-color: var(--technical-color); } +.legend-item.transform-category-translate { + border-left-color: var(--translate-color); +} + .legend-item.transform-category-randomizer { border-left-color: var(--randomizer-color); } @@ -1543,6 +1551,10 @@ h1, h2, h3, h4, h5 { border-left-color: var(--technical-color); } +.category-title.transform-category-translate { + border-left-color: var(--translate-color); +} + .category-title.transform-category-randomizer { border-left-color: var(--randomizer-color); } @@ -3648,3 +3660,616 @@ html { } } +/* ═══════════════════════════════════════════ + PROMPTCRAFT TAB + ═══════════════════════════════════════════ */ + +.promptcraft-section { + max-width: 900px; + margin: 0 auto; +} + +.pc-controls { + margin: var(--spacing-md) 0; +} + +.pc-label { + font-size: 0.85rem; + font-weight: 600; + color: var(--text-muted); + margin-bottom: 8px; + display: block; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.pc-strategy-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 8px; + margin-bottom: var(--spacing-md); +} + +.pc-strategy-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 12px 8px; + background: var(--button-bg); + border: 1px solid var(--input-border); + border-radius: 8px; + color: var(--text-color); + cursor: pointer; + transition: all 0.2s ease; + font-size: 0.8rem; + font-family: inherit; +} + +.pc-strategy-btn i { + font-size: 1.1rem; + color: var(--promptcraft-color); +} + +.pc-strategy-btn:hover { + background: var(--button-hover-bg); + border-color: var(--promptcraft-color); + box-shadow: 0 2px 8px rgba(var(--promptcraft-color-rgb), 0.2); +} + +.pc-strategy-btn.active { + background: linear-gradient(135deg, rgba(var(--promptcraft-color-rgb), 0.2), var(--button-bg)); + border-color: var(--promptcraft-color); + box-shadow: 0 0 12px rgba(var(--promptcraft-color-rgb), 0.3); +} + +.pc-strategy-btn.active i { + color: var(--promptcraft-color); + filter: brightness(1.3); +} + +.pc-custom-instruction { + margin-bottom: var(--spacing-md); +} + +.pc-custom-instruction textarea { + width: 100%; + background: var(--input-bg); + border: 1px solid var(--input-border); + border-radius: 6px; + color: var(--text-color); + padding: 8px; + font-family: inherit; + resize: vertical; +} + +.pc-options { + margin-bottom: var(--spacing-md); +} + +.pc-generate-btn { + background: linear-gradient(135deg, var(--promptcraft-color), #00cc33) !important; + color: #000 !important; + font-weight: 700; + border: none !important; +} + +.pc-generate-btn:hover:not(:disabled) { + box-shadow: 0 4px 16px rgba(var(--promptcraft-color-rgb), 0.4); + filter: brightness(1.1); +} + +.pc-generate-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.pc-error { + background: rgba(239, 83, 80, 0.1); + border: 1px solid rgba(239, 83, 80, 0.3); + border-radius: 8px; + padding: 10px 14px; + color: #ef5350; + margin: var(--spacing-sm) 0; + font-size: 0.85rem; +} + +.pc-results { + display: flex; + flex-direction: column; + gap: 12px; +} + +.pc-result-card { + background: var(--button-bg); + border: 1px solid var(--input-border); + border-radius: 10px; + padding: 14px; + transition: border-color 0.2s; +} + +.pc-result-card:hover { + border-color: var(--promptcraft-color); +} + +.pc-result-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.pc-result-num { + font-weight: 700; + color: var(--promptcraft-color); + font-size: 0.85rem; +} + +.pc-result-actions { + display: flex; + gap: 6px; +} + +.pc-action-btn { + background: var(--button-hover-bg); + border: 1px solid var(--input-border); + color: var(--text-muted); + border-radius: 6px; + padding: 4px 8px; + cursor: pointer; + font-size: 0.75rem; + transition: all 0.2s; + font-family: inherit; +} + +.pc-action-btn:hover { + color: var(--promptcraft-color); + border-color: var(--promptcraft-color); +} + +.pc-result-text { + font-size: 0.9rem; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; + color: var(--text-color); +} + +.pc-empty-state { + text-align: center; + padding: 40px 20px; + color: var(--text-muted); +} + +.pc-empty-state i { + font-size: 2.5rem; + color: var(--promptcraft-color); + opacity: 0.4; + margin-bottom: 12px; + display: block; +} + +/* ═══════════════════════════════════════════ + TRANSLATE TAB + ═══════════════════════════════════════════ */ + +.translate-section { + max-width: 900px; + margin: 0 auto; +} + +.translate-loading { + text-align: center; + padding: 16px; + color: var(--translate-color); + font-size: 0.9rem; +} + +.translate-category-block { + margin-bottom: 20px; +} + +.translate-cat-title { + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); + border-left: 3px solid var(--translate-color); + padding-left: 10px; + margin-bottom: 12px; + display: flex; + align-items: center; + gap: 8px; +} + +.translate-cat-title i { + color: var(--translate-color); +} + +.translate-lang-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 8px; +} + +.translate-lang-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + background: var(--button-bg); + border: 1px solid var(--input-border); + border-radius: 8px; + color: var(--text-color); + cursor: pointer; + transition: all 0.2s ease; + font-family: inherit; + font-size: 0.82rem; + position: relative; + min-width: 0; + overflow: hidden; +} + +.translate-lang-btn:hover:not(:disabled) { + background: var(--button-hover-bg); + border-color: var(--translate-color); + box-shadow: 0 2px 10px rgba(var(--translate-color-rgb), 0.2); +} + +.translate-lang-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.translate-flag { + font-size: 1.2em; + flex-shrink: 0; +} + +.translate-name { + flex: 1; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; +} + +.translate-label { + font-size: 0.65rem; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-muted); + background: rgba(255,255,255,0.05); + padding: 2px 5px; + border-radius: 4px; +} + +.translate-lang-main:hover:not(:disabled) { + border-color: var(--translate-color); +} + +.translate-lang-exotic { + border-left: 2px solid var(--ancient-color); +} + +.translate-lang-exotic:hover:not(:disabled) { + border-color: var(--ancient-color); + box-shadow: 0 2px 10px rgba(var(--ancient-color-rgb), 0.2); +} + +.translate-lang-custom { + border-left: 2px solid var(--promptcraft-color); +} + +.translate-lang-custom:hover:not(:disabled) { + border-color: var(--promptcraft-color); + box-shadow: 0 2px 10px rgba(var(--promptcraft-color-rgb), 0.2); +} + +.translate-remove { + position: absolute; + top: 2px; + right: 6px; + font-size: 1rem; + color: var(--text-muted); + cursor: pointer; + line-height: 1; + opacity: 0; + transition: opacity 0.2s; +} + +.translate-lang-btn:hover .translate-remove { + opacity: 1; +} + +.translate-remove:hover { + color: #ef5350; +} + +.translate-add-toggle { + background: none; + border: 1px solid var(--input-border); + color: var(--translate-color); + border-radius: 50%; + width: 22px; + height: 22px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 0.7rem; + transition: all 0.2s; + margin-left: 6px; + font-family: inherit; +} + +.translate-add-toggle:hover { + background: rgba(var(--translate-color-rgb), 0.15); + border-color: var(--translate-color); +} + +.translate-add-form { + display: flex; + gap: 8px; + margin-bottom: 12px; +} + +.translate-add-form input { + flex: 1; + padding: 8px 12px; + background: var(--input-bg); + border: 1px solid var(--input-border); + border-radius: 6px; + color: var(--text-color); + font-family: inherit; + font-size: 0.85rem; +} + +.translate-add-form input:focus { + border-color: var(--translate-color); + outline: none; + box-shadow: 0 0 0 2px rgba(var(--translate-color-rgb), 0.2); +} + +.translate-add-btn { + padding: 8px 16px; + background: var(--translate-color); + border: none; + border-radius: 6px; + color: #000; + font-weight: 600; + cursor: pointer; + font-family: inherit; + font-size: 0.85rem; + transition: all 0.2s; +} + +.translate-add-btn:hover:not(:disabled) { + filter: brightness(1.15); + box-shadow: 0 2px 8px rgba(var(--translate-color-rgb), 0.3); +} + +.translate-add-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.translate-empty-custom { + text-align: center; + padding: 14px; + color: var(--text-muted); + font-size: 0.8rem; +} + +/* ═══════════════════════════════════════════ + TRANSLATE — INLINE (Transform Tab) + ═══════════════════════════════════════════ */ + +.translate-inline-section { + border: 1px solid rgba(var(--translate-color-rgb), 0.2); + border-radius: 10px; + padding: 16px; + background: linear-gradient(135deg, rgba(var(--translate-color-rgb), 0.03), transparent); +} + +.translate-powered-by { + font-size: 0.65rem; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--translate-color); + background: rgba(var(--translate-color-rgb), 0.1); + padding: 2px 6px; + border-radius: 4px; + margin-left: 8px; + font-weight: 600; + vertical-align: middle; +} + +.translate-model-picker { + margin-bottom: 12px; +} + +.translate-model-select { + width: 100%; + max-width: 360px; + padding: 6px 10px; + background: var(--input-bg); + border: 1px solid var(--input-border); + border-radius: 6px; + color: var(--text-color); + font-family: inherit; + font-size: 0.8rem; +} + +.translate-model-select:focus { + border-color: var(--translate-color); + outline: none; + box-shadow: 0 0 0 2px rgba(var(--translate-color-rgb), 0.2); +} + +.translate-subsection { + margin-bottom: 12px; +} + +.translate-subsection-label { + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-muted); + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 6px; +} + +.translate-subsection-label i { + color: var(--translate-color); + font-size: 0.7rem; +} + +.translate-lang-grid-inline { + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); +} + +.translate-inline-section .translate-lang-btn { + padding: 7px 10px; + font-size: 0.78rem; +} + +.translate-inline-section .translate-flag { + font-size: 1em; +} + +.translate-inline-section .translate-add-form { + margin-bottom: 8px; +} + +.translate-inline-section .translate-add-form input { + font-size: 0.8rem; + padding: 6px 10px; +} + +.translate-inline-section .translate-add-btn { + font-size: 0.8rem; + padding: 6px 12px; +} + +.translate-inline-section .translate-loading { + padding: 10px; + font-size: 0.85rem; +} + +.translate-inline-section .pc-error { + font-size: 0.8rem; + padding: 8px 12px; +} + +/* ═══════════════════════════════════════════ + API KEY MODAL (PromptCraft) + ═══════════════════════════════════════════ */ + +.pc-key-modal { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + backdrop-filter: blur(4px); +} + +.pc-key-modal-content { + background: var(--main-bg-color); + border: 1px solid var(--input-border); + border-radius: 12px; + padding: 24px; + max-width: 480px; + width: 90%; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); +} + +.pc-key-modal-content h4 { + margin: 0 0 12px; + color: var(--promptcraft-color); + font-size: 1.1rem; +} + +.pc-key-info { + font-size: 0.85rem; + color: var(--text-muted); + line-height: 1.5; + margin-bottom: 16px; +} + +.pc-key-info a { + color: var(--promptcraft-color); + text-decoration: none; +} + +.pc-key-info a:hover { + text-decoration: underline; +} + +.pc-key-input-row { + display: flex; + gap: 8px; + margin-bottom: 12px; +} + +.pc-key-input-row input { + flex: 1; + padding: 10px 14px; + background: var(--input-bg); + border: 1px solid var(--input-border); + border-radius: 8px; + color: var(--text-color); + font-family: inherit; + font-size: 0.9rem; +} + +.pc-key-input-row input:focus { + border-color: var(--promptcraft-color); + outline: none; + box-shadow: 0 0 0 2px rgba(var(--promptcraft-color-rgb), 0.2); +} + +.pc-key-save-btn { + padding: 10px 20px; + background: var(--promptcraft-color); + border: none; + border-radius: 8px; + color: #000; + font-weight: 700; + cursor: pointer; + font-family: inherit; + transition: all 0.2s; +} + +.pc-key-save-btn:hover:not(:disabled) { + filter: brightness(1.15); +} + +.pc-key-save-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.pc-key-actions { + display: flex; + gap: 8px; + justify-content: space-between; +} + +.pc-revoke-btn { + color: #ef5350 !important; + border-color: rgba(239, 83, 80, 0.3) !important; +} + +.pc-revoke-btn:hover { + background: rgba(239, 83, 80, 0.1) !important; + border-color: #ef5350 !important; +} + diff --git a/js/tools/PromptCraftTool.js b/js/tools/PromptCraftTool.js new file mode 100644 index 0000000..8cf5d1f --- /dev/null +++ b/js/tools/PromptCraftTool.js @@ -0,0 +1,235 @@ +/** + * PromptCraft Tool - AI-assisted prompt crafting & mutation via OpenRouter + * + * API Key Security: + * - Keys are stored in localStorage only (never sent to any server except OpenRouter) + * - Keys are read at call-time, never cached in JS variables + * - The settings modal lets users paste/revoke their key in-app + * - No key is ever logged, serialized, or included in error reports + */ +class PromptCraftTool extends Tool { + constructor() { + super({ + id: 'promptcraft', + name: 'PromptCraft', + icon: 'fa-wand-magic-sparkles', + title: 'AI-assisted prompt crafting & mutation via OpenRouter', + order: 9 + }); + } + + getVueData() { + return { + pcInput: '', + pcOutput: '', + pcOutputs: [], + pcStrategy: 'rephrase', + pcModel: localStorage.getItem('pc-model') || 'anthropic/claude-sonnet-4.6', + pcCount: 3, + pcLoading: false, + pcError: '', + pcCustomInstruction: '', + pcShowKeyModal: false, + pcKeyInput: '', + pcStrategies: [ + { id: 'rephrase', name: 'Rephrase', icon: 'fa-rotate', desc: 'Reword while preserving intent' }, + { id: 'obfuscate', name: 'Obfuscate', icon: 'fa-mask', desc: 'Obscure meaning through indirection' }, + { id: 'roleplay', name: 'Role-Play Wrap', icon: 'fa-theater-masks', desc: 'Embed in a fictional scenario' }, + { id: 'multilingual', name: 'Multi-Language', icon: 'fa-language', desc: 'Mix multiple languages together' }, + { id: 'expand', name: 'Expand', icon: 'fa-maximize', desc: 'Elaborate with more detail and context' }, + { id: 'compress', name: 'Compress', icon: 'fa-compress', desc: 'Minimize to fewest possible tokens' }, + { id: 'metaphor', name: 'Metaphor', icon: 'fa-cloud', desc: 'Express through analogy and metaphor' }, + { id: 'fragment', name: 'Fragment', icon: 'fa-puzzle-piece', desc: 'Split across disjointed fragments' }, + { id: 'custom', name: 'Custom', icon: 'fa-pen-fancy', desc: 'Your own mutation instruction' } + ], + pcModels: [ + // Frontier + { id: 'anthropic/claude-opus-4.6', name: 'Claude Opus 4.6', provider: 'Anthropic' }, + { id: 'anthropic/claude-sonnet-4.6', name: 'Claude Sonnet 4.6', provider: 'Anthropic' }, + { id: 'anthropic/claude-sonnet-4.5', name: 'Claude Sonnet 4.5', provider: 'Anthropic' }, + { id: 'anthropic/claude-opus-4', name: 'Claude Opus 4', provider: 'Anthropic' }, + { id: 'anthropic/claude-sonnet-4', name: 'Claude Sonnet 4', provider: 'Anthropic' }, + { id: 'openai/gpt-5.4', name: 'GPT-5.4', provider: 'OpenAI' }, + { id: 'openai/gpt-5.4-pro', name: 'GPT-5.4 Pro', provider: 'OpenAI' }, + { id: 'openai/gpt-4.1', name: 'GPT-4.1', provider: 'OpenAI' }, + { id: 'google/gemini-3.1-pro-preview', name: 'Gemini 3.1 Pro', provider: 'Google' }, + { id: 'google/gemini-2.5-pro-preview', name: 'Gemini 2.5 Pro', provider: 'Google' }, + { id: 'x-ai/grok-4.20-beta', name: 'Grok 4.20 Beta', provider: 'xAI' }, + { id: 'x-ai/grok-4', name: 'Grok 4', provider: 'xAI' }, + { id: 'deepseek/deepseek-v3.2', name: 'DeepSeek V3.2', provider: 'DeepSeek' }, + { id: 'mistralai/mistral-large-3-2512', name: 'Mistral Large 3', provider: 'Mistral' }, + // Reasoning + { id: 'openai/o3-pro', name: 'o3-pro', provider: 'OpenAI' }, + { id: 'openai/o3', name: 'o3', provider: 'OpenAI' }, + { id: 'openai/o4-mini', name: 'o4-mini', provider: 'OpenAI' }, + { id: 'deepseek/deepseek-r1-0528', name: 'DeepSeek R1 (0528)', provider: 'DeepSeek' }, + { id: 'deepseek/deepseek-r1', name: 'DeepSeek R1', provider: 'DeepSeek' }, + { id: 'qwen/qwq-32b', name: 'QwQ 32B', provider: 'Qwen' }, + // Fast / Efficient + { id: 'anthropic/claude-haiku-4.5', name: 'Claude Haiku 4.5', provider: 'Anthropic' }, + { id: 'openai/gpt-5.4-mini', name: 'GPT-5.4 Mini', provider: 'OpenAI' }, + { id: 'openai/gpt-4.1-mini', name: 'GPT-4.1 Mini', provider: 'OpenAI' }, + { id: 'openai/gpt-4.1-nano', name: 'GPT-4.1 Nano', provider: 'OpenAI' }, + { id: 'google/gemini-3-flash-preview', name: 'Gemini 3 Flash', provider: 'Google' }, + { id: 'google/gemini-2.5-flash-preview', name: 'Gemini 2.5 Flash', provider: 'Google' }, + { id: 'google/gemini-2.5-flash-lite', name: 'Gemini 2.5 Flash Lite', provider: 'Google' }, + { id: 'x-ai/grok-4.1-fast', name: 'Grok 4.1 Fast', provider: 'xAI' }, + { id: 'google/gemma-3-27b-it', name: 'Gemma 3 27B', provider: 'Google' }, + // Code + { id: 'qwen/qwen3-coder-480b-a35b-instruct', name: 'Qwen3 Coder 480B', provider: 'Qwen' }, + { id: 'openai/gpt-5.3-codex', name: 'GPT-5.3 Codex', provider: 'OpenAI' }, + { id: 'x-ai/grok-code-fast-1', name: 'Grok Code Fast 1', provider: 'xAI' }, + { id: 'mistralai/devstral-2-2512', name: 'Devstral 2', provider: 'Mistral' }, + { id: 'mistralai/codestral-2508', name: 'Codestral', provider: 'Mistral' }, + // Open Source + { id: 'meta-llama/llama-4-maverick', name: 'Llama 4 Maverick', provider: 'Meta' }, + { id: 'meta-llama/llama-4-scout', name: 'Llama 4 Scout', provider: 'Meta' }, + { id: 'meta-llama/llama-3.3-70b-instruct', name: 'Llama 3.3 70B', provider: 'Meta' }, + { id: 'qwen/qwen3-235b-a22b', name: 'Qwen3 235B', provider: 'Qwen' }, + { id: 'deepseek/deepseek-chat-v3-0324', name: 'DeepSeek V3', provider: 'DeepSeek' }, + { id: 'cohere/command-a', name: 'Command A', provider: 'Cohere' }, + { id: 'nousresearch/hermes-3-llama-3.1-405b', name: 'Hermes 3 405B', provider: 'Nous' }, + // Search / Research + { id: 'perplexity/sonar-deep-research', name: 'Sonar Deep Research', provider: 'Perplexity' }, + { id: 'perplexity/sonar-pro', name: 'Sonar Pro', provider: 'Perplexity' }, + // Auto-routing + { id: 'openrouter/auto', name: 'Auto (best for price)', provider: 'OpenRouter' } + ] + }; + } + + getVueMethods() { + return { + pcGetApiKey: function() { + return localStorage.getItem('openrouter-api-key') || + localStorage.getItem('openrouter_api_key') || + localStorage.getItem('plinyos-api-key') || ''; + }, + pcSaveApiKey: function() { + var key = (this.pcKeyInput || '').trim(); + if (!key) return; + // Basic format validation — OpenRouter keys start with "sk-or-" + if (!key.startsWith('sk-or-')) { + this.pcError = 'OpenRouter keys start with "sk-or-". Check your key and try again.'; + return; + } + localStorage.setItem('openrouter-api-key', key); + this.pcKeyInput = ''; + this.pcShowKeyModal = false; + this.pcError = ''; + }, + pcRevokeApiKey: function() { + localStorage.removeItem('openrouter-api-key'); + localStorage.removeItem('openrouter_api_key'); + this.pcShowKeyModal = false; + }, + pcHasApiKey: function() { + return !!this.pcGetApiKey(); + }, + pcGetSystemPrompt: function() { + var strategyPrompts = { + rephrase: 'You are a prompt rephrasing expert. Rewrite the given prompt in a completely different way while preserving the exact same intent and meaning. Use different vocabulary, sentence structure, and framing. Do NOT add commentary \u2014 output ONLY the rephrased prompt.', + obfuscate: 'You are a prompt obfuscation specialist. Rewrite the given prompt using indirection, euphemism, coded language, metaphor, or abstract framing so the surface-level reading obscures the true intent. The meaning should still be recoverable by a careful reader. Do NOT add commentary \u2014 output ONLY the obfuscated prompt.', + roleplay: 'You are a creative writer. Wrap the given prompt inside a fictional role-play scenario, story context, or character dialogue that naturally leads to the same request being made. Use creative framing like academic research, historical fiction, game design, etc. Do NOT add commentary \u2014 output ONLY the role-play wrapped prompt.', + multilingual: 'You are a polyglot prompt crafter. Rewrite the given prompt by mixing 2-4 different languages together naturally (e.g., English + Spanish + Japanese + French). The mixed-language version should still convey the same meaning. Do NOT add commentary \u2014 output ONLY the multilingual prompt.', + expand: 'You are a prompt expansion expert. Take the given prompt and elaborate it with rich context, background detail, specific examples, and nuanced instructions that make the request more detailed and comprehensive. Do NOT add commentary \u2014 output ONLY the expanded prompt.', + compress: 'You are a prompt compression expert. Reduce the given prompt to the absolute minimum number of tokens while preserving full meaning. Use abbreviations, shorthand, telegram-style language. Every word must earn its place. Do NOT add commentary \u2014 output ONLY the compressed prompt.', + metaphor: 'You are a metaphor specialist. Rewrite the given prompt entirely through analogy, metaphor, and figurative language. The literal meaning should be expressed through symbolic/allegorical framing. Do NOT add commentary \u2014 output ONLY the metaphorical prompt.', + fragment: 'You are a prompt fragmentation expert. Break the given prompt into 3-5 separate, seemingly disconnected fragments that individually seem innocuous but together reconstruct the full meaning. Number each fragment. Do NOT add commentary \u2014 output ONLY the fragments.', + custom: '' + }; + var base = strategyPrompts[this.pcStrategy] || strategyPrompts.rephrase; + if (this.pcStrategy === 'custom' && this.pcCustomInstruction) { + base = this.pcCustomInstruction; + } + return base; + }, + pcRunMutation: async function() { + var apiKey = this.pcGetApiKey(); + if (!apiKey) { + this.pcShowKeyModal = true; + this.pcError = ''; + return; + } + if (!this.pcInput.trim()) { + this.pcError = 'Enter a prompt to mutate.'; + return; + } + + this.pcLoading = true; + this.pcError = ''; + this.pcOutputs = []; + localStorage.setItem('pc-model', this.pcModel); + + var systemPrompt = this.pcGetSystemPrompt(); + var count = Math.max(1, Math.min(10, this.pcCount)); + + try { + var requests = []; + for (var i = 0; i < count; i++) { + requests.push( + fetch('https://openrouter.ai/api/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + apiKey, + 'Content-Type': 'application/json', + 'HTTP-Referer': window.location.origin, + 'X-Title': 'P4RS3LT0NGV3 PromptCraft' + }, + body: JSON.stringify({ + model: this.pcModel, + messages: [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: this.pcInput } + ], + temperature: 0.9 + (i * 0.05), + max_tokens: 2048 + }) + }).then(function(r) { return r.json(); }) + ); + } + + var results = await Promise.allSettled(requests); + var outputs = []; + for (var j = 0; j < results.length; j++) { + var result = results[j]; + if (result.status === 'fulfilled' && result.value.choices && result.value.choices[0]) { + outputs.push(result.value.choices[0].message.content.trim()); + } else if (result.status === 'fulfilled' && result.value.error) { + // If auth error, prompt for key + if (result.value.error.code === 401 || result.value.error.code === 403) { + this.pcError = 'Invalid API key. Please update your OpenRouter key.'; + this.pcShowKeyModal = true; + } else { + this.pcError = result.value.error.message || 'API error'; + } + } + } + this.pcOutputs = outputs; + if (outputs.length > 0) { + this.pcOutput = outputs[0]; + } + } catch (e) { + this.pcError = 'Request failed: ' + e.message; + } finally { + this.pcLoading = false; + } + }, + pcCopyOutput: function(text) { + this.copyToClipboard(text); + }, + pcCopyAll: function() { + this.copyToClipboard(this.pcOutputs.join('\n\n---\n\n')); + }, + pcUseAsInput: function(text) { + this.pcInput = text; + } + }; + } +} + +if (typeof module !== 'undefined' && module.exports) { + module.exports = PromptCraftTool; +} else { + window.PromptCraftTool = PromptCraftTool; +} diff --git a/js/tools/TranslateTool.js b/js/tools/TranslateTool.js new file mode 100644 index 0000000..1200817 --- /dev/null +++ b/js/tools/TranslateTool.js @@ -0,0 +1,231 @@ +/** + * Translate Tool - TranslateGemma-style translation via OpenRouter + * Uses the official TranslateGemma prompt format for optimal translation quality. + * TranslateGemma (google/translategemma-*) models are purpose-built for translation + * from the Gemma 3 family. When available on OpenRouter, they'll be used directly. + * For now, the TranslateGemma prompt template works excellently with standard Gemma 3 + * and Gemini models too. + * + * API Key Security: + * - Same localStorage key lookup as PromptCraftTool + * - Key is only ever sent to https://openrouter.ai/api/v1/chat/completions + * - Never logged, never embedded in DOM + */ +class TranslateTool extends Tool { + constructor() { + super({ + id: 'translate', + name: 'Translate', + icon: 'fa-language', + title: 'AI-powered translation via TranslateGemma prompt format', + order: 10 + }); + // Hidden from tab bar — renders inline inside the Transform tab + this.hidden = true; + + this.langCodeMap = { + 'Spanish': 'es', 'French': 'fr', 'German': 'de', 'Chinese': 'zh', + 'Japanese': 'ja', 'Korean': 'ko', 'Arabic': 'ar', 'Russian': 'ru', + 'Hindi': 'hi', 'Portuguese': 'pt', 'Italian': 'it', 'Dutch': 'nl', + 'Turkish': 'tr', 'Vietnamese': 'vi', 'Thai': 'th', 'Polish': 'pl', + 'Latin': 'la', 'Sanskrit': 'sa', 'Ancient Greek': 'grc', + 'Egyptian Arabic': 'arz', 'Old English': 'ang', 'Sumerian': 'sux', + 'Akkadian': 'akk', 'Hawaiian': 'haw', 'Welsh': 'cy', 'Swahili': 'sw', + 'Hebrew': 'he', 'Persian': 'fa', 'Tamil': 'ta', 'Esperanto': 'eo', + 'Irish': 'ga', 'Basque': 'eu', 'Navajo': 'nv', 'Quechua': 'qu', + 'Nahuatl': 'nah', 'Tagalog': 'tl', 'Maori': 'mi', 'Yoruba': 'yo', + 'Zulu': 'zu', 'Catalan': 'ca', 'Romanian': 'ro', 'Czech': 'cs', + 'Indonesian': 'id', 'Malay': 'ms', 'Bengali': 'bn', 'Urdu': 'ur' + }; + } + + getVueData() { + var savedCustom = JSON.parse(localStorage.getItem('pc-custom-langs') || '[]'); + return { + translateLoading: false, + translateError: '', + translateActiveLang: '', + translateModel: localStorage.getItem('translate-model') || 'google/gemma-3-27b-it', + translateModels: [ + { id: 'google/gemma-3-27b-it', name: 'Gemma 3 27B', note: 'Best quality' }, + { id: 'google/gemma-3-12b-it', name: 'Gemma 3 12B', note: 'Fast + good' }, + { id: 'google/gemma-3-4b-it', name: 'Gemma 3 4B', note: 'Fastest' }, + { id: 'google/gemini-2.5-flash-preview', name: 'Gemini 2.5 Flash', note: 'Google flagship' }, + { id: 'google/gemini-2.0-flash-001', name: 'Gemini 2.0 Flash', note: 'Stable' }, + { id: 'google/translategemma-27b-it', name: 'TranslateGemma 27B', note: 'Purpose-built (if available)' }, + { id: 'google/translategemma-12b-it', name: 'TranslateGemma 12B', note: 'Purpose-built (if available)' }, + { id: 'google/translategemma-4b-it', name: 'TranslateGemma 4B', note: 'Purpose-built (if available)' } + ], + translateMainLangs: [ + { code: 'es', name: 'Spanish', flag: 'ES' }, + { code: 'fr', name: 'French', flag: 'FR' }, + { code: 'de', name: 'German', flag: 'DE' }, + { code: 'zh', name: 'Chinese', flag: 'CN' }, + { code: 'ja', name: 'Japanese', flag: 'JP' }, + { code: 'ko', name: 'Korean', flag: 'KR' }, + { code: 'ar', name: 'Arabic', flag: 'SA' }, + { code: 'ru', name: 'Russian', flag: 'RU' }, + { code: 'hi', name: 'Hindi', flag: 'IN' }, + { code: 'pt', name: 'Portuguese', flag: 'BR' } + ], + translateExoticLangs: [ + { code: 'la', name: 'Latin', flag: 'VA', label: 'Dead' }, + { code: 'sa', name: 'Sanskrit', flag: 'IN', label: 'Ancient' }, + { code: 'grc', name: 'Ancient Greek', flag: 'GR', label: 'Ancient' }, + { code: 'arz', name: 'Egyptian Arabic', flag: 'EG', label: 'Regional' }, + { code: 'ang', name: 'Old English', flag: 'GB', label: 'Dead' }, + { code: 'sux', name: 'Sumerian', flag: 'IQ', label: 'Dead' }, + { code: 'akk', name: 'Akkadian', flag: 'IQ', label: 'Dead' }, + { code: 'haw', name: 'Hawaiian', flag: 'US', label: 'Endangered' }, + { code: 'cy', name: 'Welsh', flag: 'GB', label: 'Celtic' }, + { code: 'sw', name: 'Swahili', flag: 'KE', label: 'African' } + ], + translateCustomLangs: savedCustom, + translateAddingLang: false, + translateNewLangName: '' + }; + } + + getVueMethods() { + var self = this; + return { + translateGetApiKey: function() { + return localStorage.getItem('openrouter-api-key') || + localStorage.getItem('openrouter_api_key') || + localStorage.getItem('plinyos-api-key') || ''; + }, + translateGetLangCode: function(langName) { + return self.langCodeMap[langName] || langName.toLowerCase().slice(0, 3); + }, + translateBuildPrompt: function(langName, langCode, text) { + return 'You are a professional English (en) to ' + langName + ' (' + langCode + ') translator. ' + + 'Your goal is to accurately convey the meaning and nuances of the original English text ' + + 'while adhering to ' + langName + ' grammar, vocabulary, and cultural sensitivities. ' + + 'Produce only the ' + langName + ' translation, without any additional explanations or commentary. ' + + 'Please translate the following English text into ' + langName + ':\n\n' + text; + }, + translateTo: async function(langName) { + var apiKey = this.translateGetApiKey(); + if (!apiKey) { + this.translateError = 'No API key found. Set your OpenRouter key via the PromptCraft tab.'; + return; + } + var input = this.transformInput; + if (!input || !input.trim()) { + this.translateError = 'Enter text in the input box first.'; + return; + } + + this.translateLoading = true; + this.translateActiveLang = langName; + this.translateError = ''; + localStorage.setItem('translate-model', this.translateModel); + + var langCode = this.translateGetLangCode(langName); + var prompt = this.translateBuildPrompt(langName, langCode, input); + var model = this.translateModel; + + var isTranslateGemma = model.indexOf('translategemma') !== -1; + var messages; + if (isTranslateGemma) { + messages = [{ role: 'user', content: prompt }]; + } else { + messages = [ + { + role: 'system', + content: 'You are a professional translator using the TranslateGemma translation protocol. ' + + 'Output ONLY the translated text. No explanations, notes, preamble, or alternatives. ' + + 'Preserve all formatting, line breaks, and structure.' + }, + { role: 'user', content: prompt } + ]; + } + + try { + var resp = await fetch('https://openrouter.ai/api/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + apiKey, + 'Content-Type': 'application/json', + 'HTTP-Referer': window.location.origin, + 'X-Title': 'P4RS3LT0NGV3 TranslateGemma' + }, + body: JSON.stringify({ + model: model, + messages: messages, + temperature: 0.2, + max_tokens: 4096 + }) + }); + var data = await resp.json(); + if (data.error) { + if (isTranslateGemma && (data.error.code === 404 || data.error.code === 400 || + (data.error.message && data.error.message.indexOf('not found') !== -1))) { + this.translateError = 'TranslateGemma not yet on OpenRouter \u2014 switching to Gemma 3 27B...'; + this.translateModel = 'google/gemma-3-27b-it'; + localStorage.setItem('translate-model', this.translateModel); + this.translateLoading = false; + this.translateActiveLang = ''; + await this.translateTo(langName); + return; + } + if (data.error.code === 401 || data.error.code === 403) { + this.translateError = 'Invalid API key. Update your key via the PromptCraft tab.'; + } else { + this.translateError = data.error.message || 'API error'; + } + } else if (data.choices && data.choices[0]) { + var translated = data.choices[0].message.content.trim(); + this.transformOutput = translated; + this.activeTransform = { name: langName + ' (' + langCode + ')', category: 'translate' }; + this.copyToClipboard(translated); + } + } catch (e) { + this.translateError = 'Translation failed: ' + e.message; + } finally { + this.translateLoading = false; + this.translateActiveLang = ''; + } + }, + translateAddCustomLang: function() { + var name = this.translateNewLangName.trim(); + if (!name) return; + // Sanitize — allow only letters, spaces, hyphens + if (!/^[a-zA-Z\s\-]+$/.test(name)) { + this.translateError = 'Language name can only contain letters, spaces, and hyphens.'; + return; + } + if (this.translateCustomLangs.some(function(l) { return l.name.toLowerCase() === name.toLowerCase(); })) return; + var code = self.langCodeMap[name] || name.toLowerCase().slice(0, 3); + this.translateCustomLangs.push({ code: code, name: name, flag: '++' }); + localStorage.setItem('pc-custom-langs', JSON.stringify(this.translateCustomLangs)); + this.translateNewLangName = ''; + this.translateAddingLang = false; + }, + translateRemoveCustomLang: function(index) { + this.translateCustomLangs.splice(index, 1); + localStorage.setItem('pc-custom-langs', JSON.stringify(this.translateCustomLangs)); + }, + translateGetFlag: function(code) { + var flags = { + 'ES': '\uD83C\uDDEA\uD83C\uDDF8', 'FR': '\uD83C\uDDEB\uD83C\uDDF7', + 'DE': '\uD83C\uDDE9\uD83C\uDDEA', 'CN': '\uD83C\uDDE8\uD83C\uDDF3', + 'JP': '\uD83C\uDDEF\uD83C\uDDF5', 'KR': '\uD83C\uDDF0\uD83C\uDDF7', + 'SA': '\uD83C\uDDF8\uD83C\uDDE6', 'RU': '\uD83C\uDDF7\uD83C\uDDFA', + 'IN': '\uD83C\uDDEE\uD83C\uDDF3', 'BR': '\uD83C\uDDE7\uD83C\uDDF7', + 'VA': '\uD83C\uDDFB\uD83C\uDDE6', 'GR': '\uD83C\uDDEC\uD83C\uDDF7', + 'EG': '\uD83C\uDDEA\uD83C\uDDEC', 'GB': '\uD83C\uDDEC\uD83C\uDDE7', + 'IQ': '\uD83C\uDDEE\uD83C\uDDF6', 'US': '\uD83C\uDDFA\uD83C\uDDF8', + 'KE': '\uD83C\uDDF0\uD83C\uDDEA' + }; + return flags[code] || '\uD83C\uDF10'; + } + }; + } +} + +if (typeof module !== 'undefined' && module.exports) { + module.exports = TranslateTool; +} else { + window.TranslateTool = TranslateTool; +} diff --git a/templates/promptcraft.html b/templates/promptcraft.html new file mode 100644 index 0000000..1cea8a3 --- /dev/null +++ b/templates/promptcraft.html @@ -0,0 +1,129 @@ +
+
+
+
+

PromptCraft AI-assisted prompt mutation

+
+ + +
+
+

OpenRouter API Key

+

+ PromptCraft and Translation use OpenRouter to access 200+ AI models. + Your key is stored only in your browser and sent only to OpenRouter. +

+
+ + +
+
+ + +
+
+ {{ pcError }} +
+
+
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ +
+ + +
+
+ +
+ + + +
+ +
+ {{ pcError }} +
+ +
+
+
+
+ #{{ i + 1 }} +
+ + +
+
+
{{ out }}
+
+
+
+ +
+ +

Enter a prompt and choose a mutation strategy.

+

Uses OpenRouter API — click Set Key to get started.

+
+
+
+
diff --git a/templates/transforms.html b/templates/transforms.html index 6ea17dd..206d8e9 100644 --- a/templates/transforms.html +++ b/templates/transforms.html @@ -1,31 +1,60 @@
-
+
+
+

+ + Transformed Message + ({{ activeTransform.name }}) +

+
+
+ + +
+
+ Copy this text and share it. Use the Decoder tab to reverse transformations. +
+
+
Categories:
-
{{ category }}
+
+ translate (AI) +
-
-
- + -
-
- +